From 49e1ad49c8fe0a6fbb7cba67cc030ef73125dcc7 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 26 Jan 2015 17:31:30 -0500 Subject: [PATCH 001/999] Allow normal volume to overwrite in start Binds Fixes #9981 Allows a volume which was created by docker (ie, in /var/lib/docker/vfs/dir) to be used as a Bind argument via the container start API and overwrite an existing volume. For example: ```bash docker create -v /foo --name one docker create -v /foo --name two ``` This allows the volume from `one` to be passed into the container start API as a bind to `two`, and it will overwrite it. This was possible before 7107898d5cf0f86dc1c6dab29e9dbdad3edc9411 Signed-off-by: Brian Goff --- daemon/volumes.go | 4 +- integration-cli/docker_api_containers_test.go | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/daemon/volumes.go b/daemon/volumes.go index fdfc35a93..1cdcf6c29 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -25,6 +25,7 @@ type Mount struct { Writable bool copyData bool from *Container + isBind bool } func (mnt *Mount) Export(resource string) (io.ReadCloser, error) { @@ -80,7 +81,7 @@ func (m *Mount) initialize() error { if hostPath, exists := m.container.Volumes[m.MountToPath]; exists { // If this is a bind-mount/volumes-from, maybe it was passed in at start instead of create // We need to make sure bind-mounts/volumes-from passed on start can override existing ones. - if !m.volume.IsBindMount && m.from == nil { + if (!m.volume.IsBindMount && !m.isBind) && m.from == nil { return nil } if m.volume.Path == hostPath { @@ -172,6 +173,7 @@ func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) volume: vol, MountToPath: mountToPath, Writable: writable, + isBind: true, // in case the volume itself is a normal volume, but is being mounted in as a bindmount here } } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index ad995b73f..b5ebc00e7 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -403,3 +403,40 @@ func TestBuildApiDockerfileSymlink(t *testing.T) { logDone("container REST API - check build w/bad Dockerfile symlink path") } + +// #9981 - Allow a docker created volume (ie, one in /var/lib/docker/volumes) to be used to overwrite (via passing in Binds on api start) an existing volume +func TestPostContainerBindNormalVolume(t *testing.T) { + defer deleteAllContainers() + + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=one", "busybox")) + if err != nil { + t.Fatal(err, out) + } + + fooDir, err := inspectFieldMap("one", "Volumes", "/foo") + if err != nil { + t.Fatal(err) + } + + out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=two", "busybox")) + if err != nil { + t.Fatal(err, out) + } + + bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}} + _, err = sockRequest("POST", "/containers/two/start", bindSpec) + if err != nil && !strings.Contains(err.Error(), "204 No Content") { + t.Fatal(err) + } + + fooDir2, err := inspectFieldMap("two", "Volumes", "/foo") + if err != nil { + t.Fatal(err) + } + + if fooDir2 != fooDir { + t.Fatal("expected volume path to be %s, got: %s", fooDir, fooDir2) + } + + logDone("container REST API - can use path from normal volume as bind-mount to overwrite another volume") +} From ab181ce55707de6f47d662dcdf6eab9c6c040906 Mon Sep 17 00:00:00 2001 From: Iavael Date: Mon, 2 Mar 2015 02:55:28 +0300 Subject: [PATCH 002/999] Fixed handling hardlinks to symlinks in tar stream Signed-off-by: Iavael --- pkg/archive/archive.go | 16 ++++++++++++++-- pkg/archive/archive_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index bce66a505..9e43d28cb 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -349,7 +349,13 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L // 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 hdr.Typeflag == tar.TypeLink { + if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { + if err := os.Chmod(path, hdrInfo.Mode()); err != nil { + return err + } + } + } else if hdr.Typeflag != tar.TypeSymlink { if err := os.Chmod(path, hdrInfo.Mode()); err != nil { return err } @@ -357,7 +363,13 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L 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 hdr.Typeflag == tar.TypeLink { + if fi, err := os.Lstat(hdr.Linkname); err == nil && (fi.Mode()&os.ModeSymlink == 0) { + if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform { + return err + } + } + } else if hdr.Typeflag != tar.TypeSymlink { if err := system.UtimesNano(path, ts); err != nil && err != system.ErrNotSupportedPlatform { return err } diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index 6cd95d5ad..c127b307e 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -435,6 +435,34 @@ func TestUntarInvalidFilenames(t *testing.T) { } } +func TestUntarHardlinkToSymlink(t *testing.T) { + for i, headers := range [][]*tar.Header{ + { + { + Name: "symlink1", + Typeflag: tar.TypeSymlink, + Linkname: "regfile", + Mode: 0644, + }, + { + Name: "symlink2", + Typeflag: tar.TypeLink, + Linkname: "symlink1", + Mode: 0644, + }, + { + Name: "regfile", + Typeflag: tar.TypeReg, + Mode: 0644, + }, + }, + } { + if err := testBreakout("untar", "docker-TestUntarHardlinkToSymlink", headers); err != nil { + t.Fatalf("i=%d. %v", i, err) + } + } +} + func TestUntarInvalidHardlink(t *testing.T) { for i, headers := range [][]*tar.Header{ { // try reading victim/hello (../) From 4e65c1c319afffc325853b88c9aef0c42ec83482 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Martins?= Date: Tue, 10 Feb 2015 14:57:41 +0000 Subject: [PATCH 003/999] Dealing with trailing whitespaces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Created a validation that detects all trailing whitespaces from every text file that isn't *.go, *.md, vendor/*, docs/theme/mkdocs/tipuesearch* Removed trailing whitespaces from every text file except from vendor/* builder/parser/testfiles*, docs/theme/mkdocs/tipuesearch* and *.md Signed-off-by: André Martins --- MAINTAINERS | 26 ++++++------ Makefile | 2 +- NOTICE | 4 +- contrib/completion/bash/docker | 6 +-- .../desktop-integration/gparted/Dockerfile | 2 +- contrib/host-integration/manager/systemd | 4 +- contrib/init/sysvinit-redhat/docker.sysconfig | 2 +- contrib/mkimage-debootstrap.sh | 38 ++++++++--------- contrib/report-issue.sh | 42 +++++++++---------- .../examples/postgresql_service.Dockerfile | 4 +- hack/make.sh | 1 + hack/make/.validate | 12 +++--- hack/make/dynbinary | 2 +- hack/make/tgz | 12 +++--- hack/make/validate-spaces | 33 +++++++++++++++ hack/vendor.sh | 12 +++--- integration-cli/docker_cli_build_test.go | 2 +- integration/fixtures/https/client-cert.pem | 12 +++--- .../fixtures/https/client-rogue-cert.pem | 12 +++--- integration/fixtures/https/server-cert.pem | 14 +++---- .../fixtures/https/server-rogue-cert.pem | 14 +++---- 21 files changed, 145 insertions(+), 111 deletions(-) create mode 100644 hack/make/validate-spaces diff --git a/MAINTAINERS b/MAINTAINERS index 04951bf45..052b7e783 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -113,7 +113,7 @@ It is the responsibility of the subsystem maintainers to process patches affecti manner. * If the change affects areas of the code which are not part of a subsystem, -or if subsystem maintainers are unable to reach a timely decision, it must be approved by +or if subsystem maintainers are unable to reach a timely decision, it must be approved by the core maintainers. * If the change affects the UI or public APIs, or if it represents a major change in architecture, @@ -200,11 +200,11 @@ for each. 2-code-review = "requires more code changes" 1-design-review = "raises design concerns" 4-merge = "general case" - + # Docs approval [Rules.review.docs-approval] # Changes and additions to docs must be reviewed and approved (LGTM'd) by a minimum of two docs sub-project maintainers. - # If the docs change originates with a docs maintainer, only one additional LGTM is required (since we assume a docs maintainer approves of their own PR). + # If the docs change originates with a docs maintainer, only one additional LGTM is required (since we assume a docs maintainer approves of their own PR). # Merge [Rules.review.states.4-merge] @@ -268,7 +268,7 @@ made through a pull request. # The chief architect is responsible for the overall integrity of the technical architecture # across all subsystems, and the consistency of APIs and UI. - # + # # Changes to UI, public APIs and overall architecture (for example a plugin system) must # be approved by the chief architect. "Chief Architect" = "shykes" @@ -314,7 +314,7 @@ made through a pull request. ] # The chief maintainer is responsible for all aspects of quality for the project including - # code reviews, usability, stability, security, performance, etc. + # code reviews, usability, stability, security, performance, etc. # The most important function of the chief maintainer is to lead by example. On the first # day of a new maintainer, the best advice should be "follow the C.M.'s example and you'll # be fine". @@ -359,9 +359,9 @@ made through a pull request. # has a dedicated group of maintainers, which are dedicated to that subsytem and responsible # for its quality. # This "cellular division" is the primary mechanism for scaling maintenance of the project as it grows. - # + # # The maintainers of each subsytem are responsible for: - # + # # 1. Exposing a clear road map for improving their subsystem. # 2. Deliver prompt feedback and decisions on pull requests affecting their subsystem. # 3. Be available to anyone with questions, bug reports, criticism etc. @@ -371,9 +371,9 @@ made through a pull request. # road map of the project. # # #### How to review patches to your subsystem - # + # # Accepting pull requests: - # + # # - If the pull request appears to be ready to merge, give it a `LGTM`, which # stands for "Looks Good To Me". # - If the pull request has some small problems that need to be changed, make @@ -384,9 +384,9 @@ made through a pull request. # - If the PR only needs a few changes before being merged, any MAINTAINER can # make a replacement PR that incorporates the existing commits and fixes the # problems before a fast track merge. - # + # # Closing pull requests: - # + # # - If a PR appears to be abandoned, after having attempted to contact the # original contributor, then a replacement PR may be made. Once the # replacement PR is made, any contributor may close the original one. @@ -584,12 +584,12 @@ made through a pull request. Name = "Solomon Hykes" Email = "solomon@docker.com" GitHub = "shykes" - + [people.spf13] Name = "Steve Francia" Email = "steve.francia@gmail.com" GitHub = "spf13" - + [people.sven] Name = "Sven Dowideit" Email = "SvenDowideit@home.org.au" diff --git a/Makefile b/Makefile index 1c71e00fa..311244bb4 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ test-docker-py: build $(DOCKER_RUN_DOCKER) hack/make.sh binary test-docker-py validate: build - $(DOCKER_RUN_DOCKER) hack/make.sh validate-gofmt validate-dco validate-toml + $(DOCKER_RUN_DOCKER) hack/make.sh validate-gofmt validate-dco validate-toml validate-spaces shell: build $(DOCKER_RUN_DOCKER) bash diff --git a/NOTICE b/NOTICE index 435ace7f0..8e84d0f3b 100644 --- a/NOTICE +++ b/NOTICE @@ -10,9 +10,9 @@ The following is courtesy of our legal counsel: Use and transfer of Docker may be subject to certain restrictions by the -United States and other governments. +United States and other governments. It is your responsibility to ensure that your use and/or transfer does not -violate applicable laws. +violate applicable laws. For more information, please see http://www.bis.doc.gov diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 5d48e02cf..a8d114b69 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -22,7 +22,7 @@ # must have access to the socket for the completions to function correctly # # Note for developers: -# Please arrange options sorted alphabetically by long name with the short +# Please arrange options sorted alphabetically by long name with the short # options immediately following their corresponding long form. # This order should be applied to lists, alternatives and code blocks. @@ -257,8 +257,8 @@ _docker_build() { ;; --file|-f) _filedir - return - ;; + return + ;; esac case "$cur" in diff --git a/contrib/desktop-integration/gparted/Dockerfile b/contrib/desktop-integration/gparted/Dockerfile index e76e65897..3ddb23208 100644 --- a/contrib/desktop-integration/gparted/Dockerfile +++ b/contrib/desktop-integration/gparted/Dockerfile @@ -3,7 +3,7 @@ # AUTHOR: Jessica Frazelle # COMMENTS: # This file describes how to build a gparted container with all -# dependencies installed. It uses native X11 unix socket. +# dependencies installed. It uses native X11 unix socket. # Tested on Debian Jessie # USAGE: # # Download gparted Dockerfile diff --git a/contrib/host-integration/manager/systemd b/contrib/host-integration/manager/systemd index 0431b3ced..c1ab34ef0 100755 --- a/contrib/host-integration/manager/systemd +++ b/contrib/host-integration/manager/systemd @@ -10,11 +10,11 @@ cat <<-EOF Description=$desc Author=$auth After=docker.service - + [Service] ExecStart=/usr/bin/docker start -a $cid ExecStop=/usr/bin/docker stop -t 2 $cid - + [Install] WantedBy=local.target EOF diff --git a/contrib/init/sysvinit-redhat/docker.sysconfig b/contrib/init/sysvinit-redhat/docker.sysconfig index 9c99dd196..5f9b7e53e 100644 --- a/contrib/init/sysvinit-redhat/docker.sysconfig +++ b/contrib/init/sysvinit-redhat/docker.sysconfig @@ -1,5 +1,5 @@ # /etc/sysconfig/docker -# +# # Other arguments to pass to the docker daemon process # These will be parsed by the sysv initscript and appended # to the arguments list passed to docker -d diff --git a/contrib/mkimage-debootstrap.sh b/contrib/mkimage-debootstrap.sh index d9d6aae63..412a5ce0a 100755 --- a/contrib/mkimage-debootstrap.sh +++ b/contrib/mkimage-debootstrap.sh @@ -14,9 +14,9 @@ justTar= usage() { echo >&2 - + echo >&2 "usage: $0 [options] repo suite [mirror]" - + echo >&2 echo >&2 'options: (not recommended)' echo >&2 " -p set an http_proxy for debootstrap" @@ -26,20 +26,20 @@ usage() { echo >&2 " -s # skip version detection and tagging (ie, precise also tagged as 12.04)" echo >&2 " # note that this will also skip adding universe and/or security/updates to sources.list" echo >&2 " -t # just create a tarball, especially for dockerbrew (uses repo as tarball name)" - + echo >&2 echo >&2 " ie: $0 username/debian squeeze" echo >&2 " $0 username/debian squeeze http://ftp.uk.debian.org/debian/" - + echo >&2 echo >&2 " ie: $0 username/ubuntu precise" echo >&2 " $0 username/ubuntu precise http://mirrors.melbourne.co.uk/ubuntu/" - + echo >&2 echo >&2 " ie: $0 -t precise.tar.bz2 precise" echo >&2 " $0 -t wheezy.tgz wheezy" echo >&2 " $0 -t wheezy-uk.tar.xz wheezy http://ftp.uk.debian.org/debian/" - + echo >&2 } @@ -145,10 +145,10 @@ if [ -z "$strictDebootstrap" ]; then sudo chroot . dpkg-divert --local --rename --add /sbin/initctl sudo ln -sf /bin/true sbin/initctl # see https://github.com/docker/docker/issues/446#issuecomment-16953173 - + # shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB) sudo chroot . apt-get clean - + if strings usr/bin/dpkg | grep -q unsafe-io; then # while we're at it, apt is unnecessarily slow inside containers # this forces dpkg not to call sync() after package extraction and speeds up install @@ -159,7 +159,7 @@ if [ -z "$strictDebootstrap" ]; then # (see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=584254#82), # and ubuntu lucid/10.04 only has 1.15.5.6 fi - + # we want to effectively run "apt-get clean" after every install to keep images small (see output of "apt-get clean -s" for context) { aptGetClean='"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true";' @@ -167,17 +167,17 @@ if [ -z "$strictDebootstrap" ]; then echo "APT::Update::Post-Invoke { ${aptGetClean} };" echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' } | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null - + # and remove the translations, too echo 'Acquire::Languages "none";' | sudo tee etc/apt/apt.conf.d/no-languages > /dev/null - + # helpful undo lines for each the above tweaks (for lack of a better home to keep track of them): # rm /usr/sbin/policy-rc.d # rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl # rm /etc/dpkg/dpkg.cfg.d/02apt-speedup # rm /etc/apt/apt.conf.d/no-cache # rm /etc/apt/apt.conf.d/no-languages - + if [ -z "$skipDetection" ]; then # see also rudimentary platform detection in hack/install.sh lsbDist='' @@ -187,14 +187,14 @@ if [ -z "$strictDebootstrap" ]; then if [ -z "$lsbDist" ] && [ -r etc/debian_version ]; then lsbDist='Debian' fi - + case "$lsbDist" in Debian) # add the updates and security repositories if [ "$suite" != "$debianUnstable" -a "$suite" != 'unstable' ]; then # ${suite}-updates only applies to non-unstable sudo sed -i "p; s/ $suite main$/ ${suite}-updates main/" etc/apt/sources.list - + # same for security updates echo "deb http://security.debian.org/ $suite/updates main" | sudo tee -a etc/apt/sources.list > /dev/null fi @@ -220,7 +220,7 @@ if [ -z "$strictDebootstrap" ]; then ;; esac fi - + # 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 @@ -229,23 +229,23 @@ fi if [ "$justTar" ]; then # create the tarball file so it has the right permissions (ie, not root) touch "$repo" - + # fill the tarball sudo tar --numeric-owner -caf "$repo" . else # create the image (and tag $repo:$suite) sudo tar --numeric-owner -c . | $docker import - $repo:$suite - + # test the image $docker run -i -t $repo:$suite echo success - + if [ -z "$skipDetection" ]; then case "$lsbDist" in Debian) if [ "$suite" = "$debianStable" -o "$suite" = 'stable' ] && [ -r etc/debian_version ]; then # tag latest $docker tag $repo:$suite $repo:latest - + if [ -r etc/debian_version ]; then # tag the specific debian release version (which is only reasonable to tag on debian stable) ver=$(cat etc/debian_version) diff --git a/contrib/report-issue.sh b/contrib/report-issue.sh index 5ef2ecee2..cb54f1a5b 100644 --- a/contrib/report-issue.sh +++ b/contrib/report-issue.sh @@ -29,41 +29,41 @@ function template() { # this should always match the template from CONTRIBUTING.md cat <<- EOM Description of problem: - - + + \`docker version\`: `${DOCKER_COMMAND} -D version` - - + + \`docker info\`: `${DOCKER_COMMAND} -D info` - - + + \`uname -a\`: `uname -a` - - + + Environment details (AWS, VirtualBox, physical, etc.): - - + + How reproducible: - - + + Steps to Reproduce: 1. 2. 3. - - + + Actual Results: - - + + Expected Results: - - + + Additional info: - - + + EOM } @@ -81,7 +81,7 @@ echo -ne "Do you use \`sudo\` to call docker? [y|N]: " read -r -n 1 use_sudo echo "" -if [ "x${use_sudo}" = "xy" -o "x${use_sudo}" = "xY" ]; then +if [ "x${use_sudo}" = "xy" -o "x${use_sudo}" = "xY" ]; then export DOCKER_COMMAND="sudo ${DOCKER}" fi diff --git a/docs/sources/examples/postgresql_service.Dockerfile b/docs/sources/examples/postgresql_service.Dockerfile index 9c0c0d4fc..740f180f5 100644 --- a/docs/sources/examples/postgresql_service.Dockerfile +++ b/docs/sources/examples/postgresql_service.Dockerfile @@ -6,7 +6,7 @@ FROM ubuntu 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 +# It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8 # Add PostgreSQL's repository. It contains the most recent stable release @@ -33,7 +33,7 @@ RUN /etc/init.d/postgresql start &&\ createdb -O docker docker # Adjust PostgreSQL configuration so that remote connections to the -# database are possible. +# database are possible. RUN echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/9.3/main/pg_hba.conf # And add ``listen_addresses`` to ``/etc/postgresql/9.3/main/postgresql.conf`` diff --git a/hack/make.sh b/hack/make.sh index 0db70a750..14eae5032 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -45,6 +45,7 @@ DEFAULT_BUNDLES=( validate-dco validate-gofmt validate-toml + validate-spaces binary diff --git a/hack/make/.validate b/hack/make/.validate index 022809154..7397d0fa1 100644 --- a/hack/make/.validate +++ b/hack/make/.validate @@ -3,23 +3,23 @@ if [ -z "$VALIDATE_UPSTREAM" ]; then # this is kind of an expensive check, so let's not do this twice if we # are running more than one validate bundlescript - + VALIDATE_REPO='https://github.com/docker/docker.git' VALIDATE_BRANCH='master' - + if [ "$TRAVIS" = 'true' -a "$TRAVIS_PULL_REQUEST" != 'false' ]; then VALIDATE_REPO="https://github.com/${TRAVIS_REPO_SLUG}.git" VALIDATE_BRANCH="${TRAVIS_BRANCH}" fi - + VALIDATE_HEAD="$(git rev-parse --verify HEAD)" - + git fetch -q "$VALIDATE_REPO" "refs/heads/$VALIDATE_BRANCH" VALIDATE_UPSTREAM="$(git rev-parse --verify FETCH_HEAD)" - + VALIDATE_COMMIT_LOG="$VALIDATE_UPSTREAM..$VALIDATE_HEAD" VALIDATE_COMMIT_DIFF="$VALIDATE_UPSTREAM...$VALIDATE_HEAD" - + validate_diff() { if [ "$VALIDATE_UPSTREAM" != "$VALIDATE_HEAD" ]; then git diff "$VALIDATE_COMMIT_DIFF" "$@" diff --git a/hack/make/dynbinary b/hack/make/dynbinary index 861a19214..f9b43b0e7 100644 --- a/hack/make/dynbinary +++ b/hack/make/dynbinary @@ -5,7 +5,7 @@ DEST=$1 if [ -z "$DOCKER_CLIENTONLY" ]; then source "$(dirname "$BASH_SOURCE")/.dockerinit" - + hash_files "$DEST/dockerinit-$VERSION" else # DOCKER_CLIENTONLY must be truthy, so we don't need to bother with dockerinit :) diff --git a/hack/make/tgz b/hack/make/tgz index 7234218da..fa297e1f5 100644 --- a/hack/make/tgz +++ b/hack/make/tgz @@ -18,17 +18,17 @@ for d in "$CROSS/"*/*; do BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION" mkdir -p "$DEST/$GOOS/$GOARCH" TGZ="$DEST/$GOOS/$GOARCH/$BINARY_NAME.tgz" - + mkdir -p "$DEST/build" - + mkdir -p "$DEST/build/usr/local/bin" cp -L "$d/$BINARY_FULLNAME" "$DEST/build/usr/local/bin/docker$BINARY_EXTENSION" - + tar --numeric-owner --owner 0 -C "$DEST/build" -czf "$TGZ" usr - + hash_files "$TGZ" - + rm -rf "$DEST/build" - + echo "Created tgz: $TGZ" done diff --git a/hack/make/validate-spaces b/hack/make/validate-spaces new file mode 100644 index 000000000..a16d6370b --- /dev/null +++ b/hack/make/validate-spaces @@ -0,0 +1,33 @@ +#!/bin/bash + +source "$(dirname "$BASH_SOURCE")/.validate" + +#Ignoring files from vendor/, builder/parser/testfiles*, docs/theme/mkdocs/tipuesearch*, ending with .md and .go +ignoreFiles='^builder/parser/testfiles*|^docs/theme/mkdocs/tipuesearch*|^vendor/|\.md$|\.go$' + +IFS=$'\n' +files=( $(validate_diff --diff-filter=ACMR --name-only | grep -v "$ignoreFiles" || true) ) +unset IFS + +badFiles=() +for f in "${files[@]}"; do + if [ "$(git show "$VALIDATE_HEAD:$f" | grep '[[:space:]]$')" ]; then + badFiles+=( "$f" ) + fi +done + +if [ ${#badFiles[@]} -eq 0 ]; then + echo 'Congratulations! All text files are properly formatted.' +else + { + echo "These files have trailing whitespaces:" + for f in "${badFiles[@]}"; do + echo " - $f" + done + echo + echo 'Please reformat the above files using, for example:' + echo '"ex -sc "'"%s/[[:space:]]*$//g|x"'" file" and commit the result.' + echo + } >&2 + false +fi diff --git a/hack/vendor.sh b/hack/vendor.sh index f174ef6a1..eb7ca2603 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -11,17 +11,17 @@ clone() { vcs=$1 pkg=$2 rev=$3 - + pkg_url=https://$pkg target_dir=src/$pkg - + echo -n "$pkg @ $rev: " - + if [ -d $target_dir ]; then echo -n 'rm old, ' rm -fr $target_dir fi - + echo -n 'clone, ' case $vcs in git) @@ -32,10 +32,10 @@ clone() { hg clone --quiet --updaterev $rev $pkg_url $target_dir ;; esac - + echo -n 'rm VCS, ' ( cd $target_dir && rm -rf .{git,hg} ) - + echo done } diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 1c3c070cd..d677fd863 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -4455,7 +4455,7 @@ func TestBuildExoticShellInterpolation(t *testing.T) { _, err := buildImage(name, ` FROM busybox - + ENV SOME_VAR a.b.c RUN [ "$SOME_VAR" = 'a.b.c' ] diff --git a/integration/fixtures/https/client-cert.pem b/integration/fixtures/https/client-cert.pem index c05ed47c2..cf891ff9e 100644 --- a/integration/fixtures/https/client-cert.pem +++ b/integration/fixtures/https/client-cert.pem @@ -23,20 +23,20 @@ Certificate: 7e:4e:78:7d:0a:9e:8f:42:43 Exponent: 65537 (0x10001) X509v3 extensions: - X509v3 Basic Constraints: + X509v3 Basic Constraints: CA:FALSE - Netscape Comment: + Netscape Comment: Easy-RSA Generated Certificate - X509v3 Subject Key Identifier: + 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: + 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: + X509v3 Extended Key Usage: TLS Web Client Authentication - X509v3 Key Usage: + 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: diff --git a/integration/fixtures/https/client-rogue-cert.pem b/integration/fixtures/https/client-rogue-cert.pem index 21ae4bd57..039073fbe 100644 --- a/integration/fixtures/https/client-rogue-cert.pem +++ b/integration/fixtures/https/client-rogue-cert.pem @@ -23,20 +23,20 @@ Certificate: 1d:7b:6c:7b:be:89:6b:88:8b Exponent: 65537 (0x10001) X509v3 extensions: - X509v3 Basic Constraints: + X509v3 Basic Constraints: CA:FALSE - Netscape Comment: + Netscape Comment: Easy-RSA Generated Certificate - X509v3 Subject Key Identifier: + 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: + 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: + X509v3 Extended Key Usage: TLS Web Client Authentication - X509v3 Key Usage: + 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: diff --git a/integration/fixtures/https/server-cert.pem b/integration/fixtures/https/server-cert.pem index 08abfd1a3..4ec153ac2 100644 --- a/integration/fixtures/https/server-cert.pem +++ b/integration/fixtures/https/server-cert.pem @@ -23,22 +23,22 @@ Certificate: a8:05:32:1e:f9:95:09:14:75 Exponent: 65537 (0x10001) X509v3 extensions: - X509v3 Basic Constraints: + X509v3 Basic Constraints: CA:FALSE - Netscape Cert Type: + Netscape Cert Type: SSL Server - Netscape Comment: + Netscape Comment: Easy-RSA Generated Server Certificate - X509v3 Subject Key Identifier: + 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: + 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: + X509v3 Extended Key Usage: TLS Web Server Authentication - X509v3 Key Usage: + 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: diff --git a/integration/fixtures/https/server-rogue-cert.pem b/integration/fixtures/https/server-rogue-cert.pem index 28feba665..c0fcf5257 100644 --- a/integration/fixtures/https/server-rogue-cert.pem +++ b/integration/fixtures/https/server-rogue-cert.pem @@ -23,22 +23,22 @@ Certificate: 9e:02:5c:be:65:98:a4:b4:b5 Exponent: 65537 (0x10001) X509v3 extensions: - X509v3 Basic Constraints: + X509v3 Basic Constraints: CA:FALSE - Netscape Cert Type: + Netscape Cert Type: SSL Server - Netscape Comment: + Netscape Comment: Easy-RSA Generated Server Certificate - X509v3 Subject Key Identifier: + 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: + 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: + X509v3 Extended Key Usage: TLS Web Server Authentication - X509v3 Key Usage: + 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: From 2ce37f6616762900aa941c0644dece9cdbf90124 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 16 Mar 2015 16:29:26 -0400 Subject: [PATCH 004/999] pkg/archive: ignore mtime changes on directories on overlay fs, the mtime of directories changes in a container where new files are added in an upper layer (e.g. '/etc'). This flags the directory as a change where there was none. Closes #9874 Signed-off-by: Vincent Batts --- pkg/archive/changes.go | 4 ++-- pkg/archive/changes_test.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/archive/changes.go b/pkg/archive/changes.go index f2ac2a356..c3cb4ebe0 100644 --- a/pkg/archive/changes.go +++ b/pkg/archive/changes.go @@ -220,8 +220,8 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { oldStat.Gid() != newStat.Gid() || oldStat.Rdev() != newStat.Rdev() || // Don't look at size for dirs, its not a good measure of change - (oldStat.Size() != newStat.Size() && oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR) || - !sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || + (oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR && + (!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) || bytes.Compare(oldChild.capability, newChild.capability) != 0 { change := Change{ Path: newChild.path(), diff --git a/pkg/archive/changes_test.go b/pkg/archive/changes_test.go index 8f32d7b30..53ec575b6 100644 --- a/pkg/archive/changes_test.go +++ b/pkg/archive/changes_test.go @@ -218,7 +218,6 @@ func TestChangesDirsMutated(t *testing.T) { expectedChanges := []Change{ {"/dir1", ChangeDelete}, {"/dir2", ChangeModify}, - {"/dir3", ChangeModify}, {"/dirnew", ChangeAdd}, {"/file1", ChangeDelete}, {"/file2", ChangeModify}, From 418b7a9abbb31c4aa226931edfaa626f251cc00c Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Wed, 18 Mar 2015 18:10:51 +0100 Subject: [PATCH 005/999] restrict bash completion for hostdir arg to directories The previous state assumed that the HOSTPATH argument referred to a file. As clarified by moxiegirl in PR #11305, it is a directory. Adjusted completion to reflect this. Signed-off-by: Harald Albers --- contrib/completion/bash/docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 115cc15b3..ca874bc10 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -325,7 +325,7 @@ _docker_cp() { (( counter++ )) if [ $cword -eq $counter ]; then - _filedir + _filedir -d return fi ;; From 028f7987fe455d958d08db98ae267d9fd4cf3813 Mon Sep 17 00:00:00 2001 From: unclejack Date: Fri, 12 Dec 2014 22:10:09 +0200 Subject: [PATCH 006/999] pkg/ioutils: add tests for BufReader Signed-off-by: Cristian Staretu --- pkg/ioutils/readers_test.go | 58 +++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/pkg/ioutils/readers_test.go b/pkg/ioutils/readers_test.go index a7a2dad17..0af978e06 100644 --- a/pkg/ioutils/readers_test.go +++ b/pkg/ioutils/readers_test.go @@ -32,3 +32,61 @@ func TestBufReader(t *testing.T) { t.Error(string(output)) } } + +type repeatedReader struct { + readCount int + maxReads int + data []byte +} + +func newRepeatedReader(max int, data []byte) *repeatedReader { + return &repeatedReader{0, max, data} +} + +func (r *repeatedReader) Read(p []byte) (int, error) { + if r.readCount >= r.maxReads { + return 0, io.EOF + } + r.readCount++ + n := copy(p, r.data) + return n, nil +} + +func testWithData(data []byte, reads int) { + reader := newRepeatedReader(reads, data) + bufReader := NewBufReader(reader) + io.Copy(ioutil.Discard, bufReader) +} + +func Benchmark1M10BytesReads(b *testing.B) { + reads := 1000000 + readSize := int64(10) + data := make([]byte, readSize) + b.SetBytes(readSize * int64(reads)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + testWithData(data, reads) + } +} + +func Benchmark1M1024BytesReads(b *testing.B) { + reads := 1000000 + readSize := int64(1024) + data := make([]byte, readSize) + b.SetBytes(readSize * int64(reads)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + testWithData(data, reads) + } +} + +func Benchmark10k32KBytesReads(b *testing.B) { + reads := 10000 + readSize := int64(32 * 1024) + data := make([]byte, readSize) + b.SetBytes(readSize * int64(reads)) + b.ResetTimer() + for i := 0; i < b.N; i++ { + testWithData(data, reads) + } +} From e5ea2b235720db8dec7689b410de1830f707284a Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 8 Dec 2014 16:10:36 +0200 Subject: [PATCH 007/999] pkg/ioutils: avoid huge Buffer growth in bufreader Signed-off-by: Cristian Staretu --- pkg/ioutils/readers.go | 119 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 111 insertions(+), 8 deletions(-) diff --git a/pkg/ioutils/readers.go b/pkg/ioutils/readers.go index 22f46fbd9..58ff1af63 100644 --- a/pkg/ioutils/readers.go +++ b/pkg/ioutils/readers.go @@ -2,8 +2,11 @@ package ioutils import ( "bytes" + "crypto/rand" "io" + "math/big" "sync" + "time" ) type readCloserWrapper struct { @@ -42,20 +45,40 @@ func NewReaderErrWrapper(r io.Reader, closer func()) io.Reader { } } +// bufReader allows the underlying reader to continue to produce +// output by pre-emptively reading from the wrapped reader. +// This is achieved by buffering this data in bufReader's +// expanding buffer. type bufReader struct { sync.Mutex - buf *bytes.Buffer - reader io.Reader - err error - wait sync.Cond - drainBuf []byte + buf *bytes.Buffer + reader io.Reader + err error + wait sync.Cond + drainBuf []byte + reuseBuf []byte + maxReuse int64 + resetTimeout time.Duration + bufLenResetThreshold int64 + maxReadDataReset int64 } func NewBufReader(r io.Reader) *bufReader { + var timeout int + if randVal, err := rand.Int(rand.Reader, big.NewInt(120)); err == nil { + timeout = int(randVal.Int64()) + 180 + } else { + timeout = 300 + } reader := &bufReader{ - buf: &bytes.Buffer{}, - drainBuf: make([]byte, 1024), - reader: r, + buf: &bytes.Buffer{}, + drainBuf: make([]byte, 1024), + reuseBuf: make([]byte, 4096), + maxReuse: 1000, + resetTimeout: time.Second * time.Duration(timeout), + bufLenResetThreshold: 100 * 1024, + maxReadDataReset: 10 * 1024 * 1024, + reader: r, } reader.wait.L = &reader.Mutex go reader.drain() @@ -74,14 +97,94 @@ func NewBufReaderWithDrainbufAndBuffer(r io.Reader, drainBuffer []byte, buffer * } func (r *bufReader) drain() { + var ( + duration time.Duration + lastReset time.Time + now time.Time + reset bool + bufLen int64 + dataSinceReset int64 + maxBufLen int64 + reuseBufLen int64 + reuseCount int64 + ) + reuseBufLen = int64(len(r.reuseBuf)) + lastReset = time.Now() for { n, err := r.reader.Read(r.drainBuf) + dataSinceReset += int64(n) r.Lock() + bufLen = int64(r.buf.Len()) + if bufLen > maxBufLen { + maxBufLen = bufLen + } + + // Avoid unbounded growth of the buffer over time. + // This has been discovered to be the only non-intrusive + // solution to the unbounded growth of the buffer. + // Alternative solutions such as compression, multiple + // buffers, channels and other similar pieces of code + // were reducing throughput, overall Docker performance + // or simply crashed Docker. + // This solution releases the buffer when specific + // conditions are met to avoid the continuous resizing + // of the buffer for long lived containers. + // + // Move data to the front of the buffer if it's + // smaller than what reuseBuf can store + if bufLen > 0 && reuseBufLen >= bufLen { + n, _ := r.buf.Read(r.reuseBuf) + r.buf.Write(r.reuseBuf[0:n]) + // Take action if the buffer has been reused too many + // times and if there's data in the buffer. + // The timeout is also used as means to avoid doing + // these operations more often or less often than + // required. + // The various conditions try to detect heavy activity + // in the buffer which might be indicators of heavy + // growth of the buffer. + } else if reuseCount >= r.maxReuse && bufLen > 0 { + now = time.Now() + duration = now.Sub(lastReset) + timeoutReached := duration >= r.resetTimeout + + // The timeout has been reached and the + // buffered data couldn't be moved to the front + // of the buffer, so the buffer gets reset. + if timeoutReached && bufLen > reuseBufLen { + reset = true + } + // The amount of buffered data is too high now, + // reset the buffer. + if timeoutReached && maxBufLen >= r.bufLenResetThreshold { + reset = true + } + // Reset the buffer if a certain amount of + // data has gone through the buffer since the + // last reset. + if timeoutReached && dataSinceReset >= r.maxReadDataReset { + reset = true + } + // The buffered data is moved to a fresh buffer, + // swap the old buffer with the new one and + // reset all counters. + if reset { + newbuf := &bytes.Buffer{} + newbuf.ReadFrom(r.buf) + r.buf = newbuf + lastReset = now + reset = false + dataSinceReset = 0 + maxBufLen = 0 + reuseCount = 0 + } + } if err != nil { r.err = err } else { r.buf.Write(r.drainBuf[0:n]) } + reuseCount++ r.wait.Signal() r.Unlock() if err != nil { From d62f25e4220d1d1ca792adf9c7423ee60a00c0d7 Mon Sep 17 00:00:00 2001 From: Michal Fojtik Date: Tue, 3 Feb 2015 12:41:21 +0100 Subject: [PATCH 008/999] Fix lxc-start in lxc>1.1.0 where containers start daemonized by default Signed-off-by: Michal Fojtik --- daemon/execdriver/lxc/driver.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index f45c21445..b30e7d216 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -20,6 +20,7 @@ import ( "github.com/docker/docker/daemon/execdriver" sysinfo "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/term" + "github.com/docker/docker/pkg/version" "github.com/docker/docker/utils" "github.com/docker/libcontainer" "github.com/docker/libcontainer/cgroups" @@ -115,6 +116,13 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba "-n", c.ID, "-f", configPath, } + + // From lxc>=1.1 the default behavior is to daemonize containers after start + lxcVersion := version.Version(d.version()) + if lxcVersion.GreaterThanOrEqualTo(version.Version("1.1")) { + params = append(params, "-F") + } + if c.Network.ContainerID != "" { params = append(params, "--share-net", c.Network.ContainerID, From fe9fe1473cc54c4d2962391d6fa05ecc1c2c96f1 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Wed, 18 Mar 2015 13:56:47 -0400 Subject: [PATCH 009/999] We want to allow the sharing of /dev from the host into the container. docker run -v /dev:/dev should stop mounting other default mounts in i libcontainer otherwise directories and devices like /dev/ptx get mishandled. We want to be able to run libvirtd for launching vms and it needs access to the hosts /dev. This is a key componant of OpenStack. Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- daemon/execdriver/native/create.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index a988fba52..fa53621c4 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -220,8 +220,12 @@ func (d *driver) setupMounts(container *configs.Config, c *execdriver.Command) e // Filter out mounts that are overriden by user supplied mounts var defaultMounts []*configs.Mount + _, mountDev := userMounts["/dev"] for _, m := range container.Mounts { if _, ok := userMounts[m.Destination]; !ok { + if mountDev && strings.HasPrefix(m.Destination, "/dev/") { + continue + } defaultMounts = append(defaultMounts, m) } } From 3c136333af94c04eb59d7af9ee9be15c5bc6a129 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Mon, 16 Mar 2015 14:53:41 -0400 Subject: [PATCH 010/999] Btrfs has eliminated the BTRFS_BUILD_VERSION in latest version They say we should only use the BTRFS_LIB_VERSION They will no longer support this since it had to be managed manually Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- daemon/graphdriver/btrfs/btrfs.go | 3 --- daemon/graphdriver/btrfs/version.go | 3 --- daemon/graphdriver/btrfs/version_none.go | 3 --- daemon/graphdriver/btrfs/version_test.go | 6 +++--- 4 files changed, 3 insertions(+), 12 deletions(-) diff --git a/daemon/graphdriver/btrfs/btrfs.go b/daemon/graphdriver/btrfs/btrfs.go index 1830ad4e8..ef51bac83 100644 --- a/daemon/graphdriver/btrfs/btrfs.go +++ b/daemon/graphdriver/btrfs/btrfs.go @@ -61,9 +61,6 @@ func (d *Driver) String() string { func (d *Driver) Status() [][2]string { status := [][2]string{} - if bv := BtrfsBuildVersion(); bv != "-" { - status = append(status, [2]string{"Build Version", bv}) - } if lv := BtrfsLibVersion(); lv != -1 { status = append(status, [2]string{"Library Version", fmt.Sprintf("%d", lv)}) } diff --git a/daemon/graphdriver/btrfs/version.go b/daemon/graphdriver/btrfs/version.go index 89ed85749..8a96305b5 100644 --- a/daemon/graphdriver/btrfs/version.go +++ b/daemon/graphdriver/btrfs/version.go @@ -16,9 +16,6 @@ int my_btrfs_lib_version() { */ import "C" -func BtrfsBuildVersion() string { - return string(C.BTRFS_BUILD_VERSION) -} func BtrfsLibVersion() int { return int(C.BTRFS_LIB_VERSION) } diff --git a/daemon/graphdriver/btrfs/version_none.go b/daemon/graphdriver/btrfs/version_none.go index 69a4e51cf..d191d36f3 100644 --- a/daemon/graphdriver/btrfs/version_none.go +++ b/daemon/graphdriver/btrfs/version_none.go @@ -5,9 +5,6 @@ package btrfs // TODO(vbatts) remove this work-around once supported linux distros are on // btrfs utililties of >= 3.16.1 -func BtrfsBuildVersion() string { - return "-" -} func BtrfsLibVersion() int { return -1 } diff --git a/daemon/graphdriver/btrfs/version_test.go b/daemon/graphdriver/btrfs/version_test.go index d96e33f3d..2d5104d5e 100644 --- a/daemon/graphdriver/btrfs/version_test.go +++ b/daemon/graphdriver/btrfs/version_test.go @@ -6,8 +6,8 @@ import ( "testing" ) -func TestBuildVersion(t *testing.T) { - if len(BtrfsBuildVersion()) == 0 { - t.Errorf("expected output from btrfs build version, but got empty string") +func TestLibVersion(t *testing.T) { + if BtrfsLibVersion() <= 0 { + t.Errorf("expected output from btrfs lib version > 0") } } From 0aa250bd60e472dadfd8e9e1f90465e041b03983 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Sat, 21 Mar 2015 10:16:18 +0800 Subject: [PATCH 011/999] fix decode data loss when using int64 in json The problem is when I create container though REST api, set memory limit in hostConfig, the memory limit didn't work. Because when we DecodeEnv, we got hostConfig part of Env like this: {"Binds":["/:/tmp"],"CpuShares":512,"CpusetCpus":"0,1","Devices":[],"Memory":1.6777216e+07,"MemorySwap":0} And we cannot unmarshal number 1.6777216e+07 into Go value of type int64, so we got 0. We can fix this by setting Decoder as UseNumber(). Signed-off-by: Qiang Huang --- engine/env.go | 5 ++++- engine/env_test.go | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/engine/env.go b/engine/env.go index a671f13c6..c6c673271 100644 --- a/engine/env.go +++ b/engine/env.go @@ -189,7 +189,10 @@ func (decoder *Decoder) Decode() (*Env, error) { // is returned. func (env *Env) Decode(src io.Reader) error { m := make(map[string]interface{}) - if err := json.NewDecoder(src).Decode(&m); err != nil { + d := json.NewDecoder(src) + // We need this or we'll lose data when we decode int64 in json + d.UseNumber() + if err := d.Decode(&m); err != nil { return err } for k, v := range m { diff --git a/engine/env_test.go b/engine/env_test.go index 2ed99d0fe..dd282fef0 100644 --- a/engine/env_test.go +++ b/engine/env_test.go @@ -78,6 +78,26 @@ func TestSetenv(t *testing.T) { } } +func TestDecodeEnv(t *testing.T) { + job := mkJob(t, "dummy") + type tmp struct { + Id1 int64 + Id2 int64 + } + body := []byte("{\"tags\":{\"Id1\":123, \"Id2\":1234567}}") + if err := job.DecodeEnv(bytes.NewBuffer(body)); err != nil { + t.Fatalf("DecodeEnv failed: %v", err) + } + mytag := tmp{} + if val := job.GetenvJson("tags", &mytag); val != nil { + t.Fatalf("GetenvJson returns incorrect value: %s", val) + } + + if mytag.Id1 != 123 || mytag.Id2 != 1234567 { + t.Fatal("Get wrong values set by job.DecodeEnv") + } +} + func TestSetenvBool(t *testing.T) { job := mkJob(t, "dummy") job.SetenvBool("foo", true) From 6d66e3e7a5ecb021a9e89c4f85fadecf23e2000c Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 28 Jan 2015 18:28:48 -0800 Subject: [PATCH 012/999] Fix some escaping around env var processing Clarify in the docs that ENV is not recursive Closes #10391 Signed-off-by: Doug Davis --- builder/evaluator.go | 6 +- builder/parser/line_parsers.go | 15 +- builder/parser/testfiles/env/Dockerfile | 8 + builder/parser/testfiles/env/result | 18 +- builder/shell_parser.go | 209 +++++++++++++++++++++++ builder/shell_parser_test.go | 51 ++++++ builder/support.go | 41 ----- builder/words | 43 +++++ docs/sources/reference/builder.md | 11 ++ integration-cli/docker_cli_build_test.go | 66 +++++-- 10 files changed, 402 insertions(+), 66 deletions(-) create mode 100644 builder/shell_parser.go create mode 100644 builder/shell_parser_test.go create mode 100644 builder/words diff --git a/builder/evaluator.go b/builder/evaluator.go index 985656f16..ba2726084 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -302,7 +302,11 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error { var str string str = ast.Value if _, ok := replaceEnvAllowed[cmd]; ok { - str = b.replaceEnv(ast.Value) + var err error + str, err = ProcessWord(ast.Value, b.Config.Env) + if err != nil { + return err + } } strList[i+l] = str msgList[i] = ast.Value diff --git a/builder/parser/line_parsers.go b/builder/parser/line_parsers.go index 45c929ee6..3026a0b06 100644 --- a/builder/parser/line_parsers.go +++ b/builder/parser/line_parsers.go @@ -90,7 +90,7 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) { if blankOK || len(word) > 0 { words = append(words, word) - // Look for = and if no there assume + // Look for = and if not there assume // we're doing the old stuff and // just read the rest of the line if !strings.Contains(word, "=") { @@ -107,12 +107,15 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) { quote = ch blankOK = true phase = inQuote - continue } if ch == '\\' { if pos+1 == len(rest) { continue // just skip \ at end } + // If we're not quoted and we see a \, then always just + // add \ plus the char to the word, even if the char + // is a quote. + word += string(ch) pos++ ch = rune(rest[pos]) } @@ -122,15 +125,17 @@ func parseNameVal(rest string, key string) (*Node, map[string]bool, error) { if phase == inQuote { if ch == quote { phase = inWord - continue } - if ch == '\\' { + // \ is special except for ' quotes - can't escape anything for ' + if ch == '\\' && quote != '\'' { if pos+1 == len(rest) { phase = inWord continue // just skip \ at end } pos++ - ch = rune(rest[pos]) + nextCh := rune(rest[pos]) + word += string(ch) + ch = nextCh } word += string(ch) } diff --git a/builder/parser/testfiles/env/Dockerfile b/builder/parser/testfiles/env/Dockerfile index bb78503cc..08fa18ace 100644 --- a/builder/parser/testfiles/env/Dockerfile +++ b/builder/parser/testfiles/env/Dockerfile @@ -7,6 +7,14 @@ ENV name=value\ value2 ENV name="value'quote space'value2" ENV name='value"double quote"value2' ENV name=value\ value2 name2=value2\ value3 +ENV name="a\"b" +ENV name="a\'b" +ENV name='a\'b' +ENV name='a\'b'' +ENV name='a\"b' +ENV name="''" +# don't put anything after the next line - it must be the last line of the +# Dockerfile and it must end with \ ENV name=value \ name1=value1 \ name2="value2a \ diff --git a/builder/parser/testfiles/env/result b/builder/parser/testfiles/env/result index a473d0fa3..ba0a6dd7c 100644 --- a/builder/parser/testfiles/env/result +++ b/builder/parser/testfiles/env/result @@ -2,9 +2,15 @@ (env "name" "value") (env "name" "value") (env "name" "value" "name2" "value2") -(env "name" "value value1") -(env "name" "value value2") -(env "name" "value'quote space'value2") -(env "name" "value\"double quote\"value2") -(env "name" "value value2" "name2" "value2 value3") -(env "name" "value" "name1" "value1" "name2" "value2a value2b" "name3" "value3an\"value3b\"" "name4" "value4a\\nvalue4b") +(env "name" "\"value value1\"") +(env "name" "value\\ value2") +(env "name" "\"value'quote space'value2\"") +(env "name" "'value\"double quote\"value2'") +(env "name" "value\\ value2" "name2" "value2\\ value3") +(env "name" "\"a\\\"b\"") +(env "name" "\"a\\'b\"") +(env "name" "'a\\'b'") +(env "name" "'a\\'b''") +(env "name" "'a\\\"b'") +(env "name" "\"''\"") +(env "name" "value" "name1" "value1" "name2" "\"value2a value2b\"" "name3" "\"value3a\\n\\\"value3b\\\"\"" "name4" "\"value4a\\\\nvalue4b\"") diff --git a/builder/shell_parser.go b/builder/shell_parser.go new file mode 100644 index 000000000..b8c746773 --- /dev/null +++ b/builder/shell_parser.go @@ -0,0 +1,209 @@ +package builder + +// This will take a single word and an array of env variables and +// process all quotes (" and ') as well as $xxx and ${xxx} env variable +// tokens. Tries to mimic bash shell process. +// It doesn't support all flavors of ${xx:...} formats but new ones can +// be added by adding code to the "special ${} format processing" section + +import ( + "fmt" + "strings" + "unicode" +) + +type shellWord struct { + word string + envs []string + pos int +} + +func ProcessWord(word string, env []string) (string, error) { + sw := &shellWord{ + word: word, + envs: env, + pos: 0, + } + return sw.process() +} + +func (sw *shellWord) process() (string, error) { + return sw.processStopOn('\000') +} + +// Process the word, starting at 'pos', and stop when we get to the +// end of the word or the 'stopChar' character +func (sw *shellWord) processStopOn(stopChar rune) (string, error) { + var result string + var charFuncMapping = map[rune]func() (string, error){ + '\'': sw.processSingleQuote, + '"': sw.processDoubleQuote, + '$': sw.processDollar, + } + + for sw.pos < len(sw.word) { + ch := sw.peek() + if stopChar != '\000' && ch == stopChar { + sw.next() + break + } + if fn, ok := charFuncMapping[ch]; ok { + // Call special processing func for certain chars + tmp, err := fn() + if err != nil { + return "", err + } + result += tmp + } else { + // Not special, just add it to the result + ch = sw.next() + if ch == '\\' { + // '\' escapes, except end of line + ch = sw.next() + if ch == '\000' { + continue + } + } + result += string(ch) + } + } + + return result, nil +} + +func (sw *shellWord) peek() rune { + if sw.pos == len(sw.word) { + return '\000' + } + return rune(sw.word[sw.pos]) +} + +func (sw *shellWord) next() rune { + if sw.pos == len(sw.word) { + return '\000' + } + ch := rune(sw.word[sw.pos]) + sw.pos++ + return ch +} + +func (sw *shellWord) processSingleQuote() (string, error) { + // All chars between single quotes are taken as-is + // Note, you can't escape ' + var result string + + sw.next() + + for { + ch := sw.next() + if ch == '\000' || ch == '\'' { + break + } + result += string(ch) + } + return result, nil +} + +func (sw *shellWord) processDoubleQuote() (string, error) { + // All chars up to the next " are taken as-is, even ', except any $ chars + // But you can escape " with a \ + var result string + + sw.next() + + for sw.pos < len(sw.word) { + ch := sw.peek() + if ch == '"' { + sw.next() + break + } + if ch == '$' { + tmp, err := sw.processDollar() + if err != nil { + return "", err + } + result += tmp + } else { + ch = sw.next() + if ch == '\\' { + chNext := sw.peek() + + if chNext == '\000' { + // Ignore \ at end of word + continue + } + + if chNext == '"' || chNext == '$' { + // \" and \$ can be escaped, all other \'s are left as-is + ch = sw.next() + } + } + result += string(ch) + } + } + + return result, nil +} + +func (sw *shellWord) processDollar() (string, error) { + sw.next() + ch := sw.peek() + if ch == '{' { + sw.next() + name := sw.processName() + ch = sw.peek() + if ch == '}' { + // Normal ${xx} case + sw.next() + return sw.getEnv(name), nil + } + return "", fmt.Errorf("Unsupported ${} substitution: %s", sw.word) + } else { + // $xxx case + name := sw.processName() + if name == "" { + return "$", nil + } + return sw.getEnv(name), nil + } +} + +func (sw *shellWord) processName() string { + // Read in a name (alphanumeric or _) + // If it starts with a numeric then just return $# + var name string + + for sw.pos < len(sw.word) { + ch := sw.peek() + if len(name) == 0 && unicode.IsDigit(ch) { + ch = sw.next() + return string(ch) + } + if !unicode.IsLetter(ch) && !unicode.IsDigit(ch) && ch != '_' { + break + } + ch = sw.next() + name += string(ch) + } + + return name +} + +func (sw *shellWord) getEnv(name string) string { + for _, env := range sw.envs { + i := strings.Index(env, "=") + if i < 0 { + if name == env { + // Should probably never get here, but just in case treat + // it like "var" and "var=" are the same + return "" + } + continue + } + if name != env[:i] { + continue + } + return env[i+1:] + } + return "" +} diff --git a/builder/shell_parser_test.go b/builder/shell_parser_test.go new file mode 100644 index 000000000..79260492f --- /dev/null +++ b/builder/shell_parser_test.go @@ -0,0 +1,51 @@ +package builder + +import ( + "bufio" + "os" + "strings" + "testing" +) + +func TestShellParser(t *testing.T) { + file, err := os.Open("words") + if err != nil { + t.Fatalf("Can't open 'words': %s", err) + } + defer file.Close() + + scanner := bufio.NewScanner(file) + envs := []string{"PWD=/home", "SHELL=bash"} + for scanner.Scan() { + line := scanner.Text() + + // Trim comments and blank lines + i := strings.Index(line, "#") + if i >= 0 { + line = line[:i] + } + line = strings.TrimSpace(line) + + if line == "" { + continue + } + + words := strings.Split(line, "|") + if len(words) != 2 { + t.Fatalf("Error in 'words' - should be 2 words:%q", words) + } + + words[0] = strings.TrimSpace(words[0]) + words[1] = strings.TrimSpace(words[1]) + + newWord, err := ProcessWord(words[0], envs) + + if err != nil { + newWord = "error" + } + + if newWord != words[1] { + t.Fatalf("Error. Src: %s Calc: %s Expected: %s", words[0], newWord, words[1]) + } + } +} diff --git a/builder/support.go b/builder/support.go index 6833457f3..787ff10cc 100644 --- a/builder/support.go +++ b/builder/support.go @@ -1,50 +1,9 @@ package builder import ( - "regexp" "strings" ) -var ( - // `\\\\+|[^\\]|\b|\A` - match any number of "\\" (ie, properly-escaped backslashes), or a single non-backslash character, or a word boundary, or beginning-of-line - // `\$` - match literal $ - // `[[:alnum:]_]+` - match things like `$SOME_VAR` - // `{[[:alnum:]_]+}` - match things like `${SOME_VAR}` - tokenEnvInterpolation = regexp.MustCompile(`(\\|\\\\+|[^\\]|\b|\A)\$([[:alnum:]_]+|{[[:alnum:]_]+})`) - // this intentionally punts on more exotic interpolations like ${SOME_VAR%suffix} and lets the shell handle those directly -) - -// handle environment replacement. Used in dispatcher. -func (b *Builder) replaceEnv(str string) string { - for _, match := range tokenEnvInterpolation.FindAllString(str, -1) { - idx := strings.Index(match, "\\$") - if idx != -1 { - if idx+2 >= len(match) { - str = strings.Replace(str, match, "\\$", -1) - continue - } - - prefix := match[:idx] - stripped := match[idx+2:] - str = strings.Replace(str, match, prefix+"$"+stripped, -1) - continue - } - - match = match[strings.Index(match, "$"):] - matchKey := strings.Trim(match, "${}") - - for _, keyval := range b.Config.Env { - tmp := strings.SplitN(keyval, "=", 2) - if tmp[0] == matchKey { - str = strings.Replace(str, match, tmp[1], -1) - break - } - } - } - - return str -} - func handleJsonArgs(args []string, attributes map[string]bool) []string { if len(args) == 0 { return []string{} diff --git a/builder/words b/builder/words new file mode 100644 index 000000000..2148f7253 --- /dev/null +++ b/builder/words @@ -0,0 +1,43 @@ +hello | hello +he'll'o | hello +he'llo | hello +he\'llo | he'llo +he\\'llo | he\llo +abc\tdef | abctdef +"abc\tdef" | abc\tdef +'abc\tdef' | abc\tdef +hello\ | hello +hello\\ | hello\ +"hello | hello +"hello\" | hello" +"hel'lo" | hel'lo +'hello | hello +'hello\' | hello\ +"''" | '' +$. | $. +$1 | +he$1x | hex +he$.x | he$.x +he$pwd. | he. +he$PWD | he/home +he\$PWD | he$PWD +he\\$PWD | he\/home +he\${} | he${} +he\${}xx | he${}xx +he${} | he +he${}xx | hexx +he${hi} | he +he${hi}xx | hexx +he${PWD} | he/home +he${.} | error +'he${XX}' | he${XX} +"he${PWD}" | he/home +"he'$PWD'" | he'/home' +"$PWD" | /home +'$PWD' | $PWD +'\$PWD' | \$PWD +'"hello"' | "hello" +he\$PWD | he$PWD +"he\$PWD" | he$PWD +'he\$PWD' | he\$PWD +he${PWD | error diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 6955d31e0..9e56abf63 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -146,6 +146,17 @@ The instructions that handle environment variables in the `Dockerfile` are: `ONBUILD` instructions are **NOT** supported for environment replacement, even the instructions above. +Environment variable subtitution will use the same value for each variable +throughout the entire command. In other words, in this example: + + ENV abc=hello + ENV abc=bye def=$abc + ENV ghi=$abc + +will result in `def` having a value of `hello`, not `bye`. However, +`ghi` will have a value of `bye` because it is not part of the same command +that set `abc` to `bye`. + ## The `.dockerignore` file If a file named `.dockerignore` exists in the source repository, then it diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index c83759d75..6e78c59ad 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -239,9 +239,18 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) { _, err := buildImage(name, ` - FROM scratch - ENV foo foo + FROM busybox + ENV foo zzz ENV bar ${foo} + ENV abc1='$foo' + ENV env1=$foo env2=${foo} env3="$foo" env4="${foo}" + RUN [ "$abc1" = '$foo' ] && (echo "$abc1" | grep -q foo) + ENV abc2="\$foo" + RUN [ "$abc2" = '$foo' ] && (echo "$abc2" | grep -q foo) + ENV abc3 '$foo' + RUN [ "$abc3" = '$foo' ] && (echo "$abc3" | grep -q foo) + ENV abc4 "\$foo" + RUN [ "$abc4" = '$foo' ] && (echo "$abc4" | grep -q foo) `, true) if err != nil { @@ -260,13 +269,19 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) { } found := false + envCount := 0 for _, env := range envResult { parts := strings.SplitN(env, "=", 2) if parts[0] == "bar" { found = true - if parts[1] != "foo" { - t.Fatalf("Could not find replaced var for env `bar`: got %q instead of `foo`", parts[1]) + if parts[1] != "zzz" { + t.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", parts[1]) + } + } else if strings.HasPrefix(parts[0], "env") { + envCount++ + if parts[1] != "zzz" { + t.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) } } } @@ -275,6 +290,10 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) { t.Fatal("Never found the `bar` env variable") } + if envCount != 4 { + t.Fatalf("Didn't find all env vars - only saw %d\n%s", envCount, envResult) + } + logDone("build - env environment replacement") } @@ -361,8 +380,8 @@ func TestBuildHandleEscapes(t *testing.T) { t.Fatal(err) } - if _, ok := result[`\\\\\\${FOO}`]; !ok { - t.Fatal(`Could not find volume \\\\\\${FOO} set from env foo in volumes table`) + if _, ok := result[`\\\${FOO}`]; !ok { + t.Fatal(`Could not find volume \\\${FOO} set from env foo in volumes table`, result) } logDone("build - handle escapes") @@ -2128,7 +2147,7 @@ func TestBuildRelativeWorkdir(t *testing.T) { func TestBuildWorkdirWithEnvVariables(t *testing.T) { name := "testbuildworkdirwithenvvariables" - expected := "/test1/test2/$MISSING_VAR" + expected := "/test1/test2" defer deleteImages(name) _, err := buildImage(name, `FROM busybox @@ -3897,9 +3916,9 @@ ENV abc=zzz TO=/docker/world/hello ADD $FROM $TO RUN [ "$(cat $TO)" = "hello" ] ENV abc "zzz" -RUN [ $abc = \"zzz\" ] +RUN [ $abc = "zzz" ] ENV abc 'yyy' -RUN [ $abc = \'yyy\' ] +RUN [ $abc = 'yyy' ] ENV abc= RUN [ "$abc" = "" ] @@ -3915,13 +3934,34 @@ RUN [ "$abc" = "'foo'" ] ENV abc=\"foo\" RUN [ "$abc" = "\"foo\"" ] ENV abc "foo" -RUN [ "$abc" = "\"foo\"" ] +RUN [ "$abc" = "foo" ] ENV abc 'foo' -RUN [ "$abc" = "'foo'" ] +RUN [ "$abc" = 'foo' ] ENV abc \'foo\' -RUN [ "$abc" = "\\'foo\\'" ] +RUN [ "$abc" = "'foo'" ] ENV abc \"foo\" -RUN [ "$abc" = "\\\"foo\\\"" ] +RUN [ "$abc" = '"foo"' ] + +ENV e1=bar +ENV e2=$e1 +ENV e3=$e11 +ENV e4=\$e1 +ENV e5=\$e11 +RUN [ "$e0,$e1,$e2,$e3,$e4,$e5" = ',bar,bar,,$e1,$e11' ] + +ENV ee1 bar +ENV ee2 $ee1 +ENV ee3 $ee11 +ENV ee4 \$ee1 +ENV ee5 \$ee11 +RUN [ "$ee1,$ee2,$ee3,$ee4,$ee5" = 'bar,bar,,$ee1,$ee11' ] + +ENV eee1="foo" +ENV eee2='foo' +ENV eee3 "foo" +ENV eee4 'foo' +RUN [ "$eee1,$eee2,$eee3,$eee4" = 'foo,foo,foo,foo' ] + ` ctx, err := fakeContext(dockerfile, map[string]string{ "hello/docker/world": "hello", From b7dc9040f04fe8dacd2f14c6b93d1b0bb6bde333 Mon Sep 17 00:00:00 2001 From: Mitch Capper Date: Sun, 15 Mar 2015 10:19:15 -0700 Subject: [PATCH 013/999] Change windows default permissions to 755 not 711, read access for all poses little security risk and prevents breaking existing Dockerfiles Signed-off-by: Mitch Capper --- integration-cli/test_vars_windows.go | 4 ++-- pkg/archive/archive_windows.go | 5 ++--- pkg/archive/archive_windows_test.go | 10 +++++----- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/integration-cli/test_vars_windows.go b/integration-cli/test_vars_windows.go index 3cad4bcee..f81ac53cc 100644 --- a/integration-cli/test_vars_windows.go +++ b/integration-cli/test_vars_windows.go @@ -6,6 +6,6 @@ const ( // identifies if test suite is running on a unix platform isUnixCli = false - // this is the expected file permission set on windows: gh#11047 - expectedFileChmod = "-rwx------" + // this is the expected file permission set on windows: gh#11395 + expectedFileChmod = "-rwxr-xr-x" ) diff --git a/pkg/archive/archive_windows.go b/pkg/archive/archive_windows.go index 96a93ee7a..6caef3b73 100644 --- a/pkg/archive/archive_windows.go +++ b/pkg/archive/archive_windows.go @@ -28,10 +28,9 @@ func CanonicalTarNameForPath(p string) (string, error) { // chmodTarEntry is used to adjust the file permissions used in tar header based // on the platform the archival is done. func chmodTarEntry(perm os.FileMode) os.FileMode { - // Clear r/w on grp/others: no precise equivalen of group/others on NTFS. - perm &= 0711 + perm &= 0755 // Add the x bit: make everything +x from windows - perm |= 0100 + perm |= 0111 return perm } diff --git a/pkg/archive/archive_windows_test.go b/pkg/archive/archive_windows_test.go index 0c97a1040..b33e0fb00 100644 --- a/pkg/archive/archive_windows_test.go +++ b/pkg/archive/archive_windows_test.go @@ -51,11 +51,11 @@ func TestChmodTarEntry(t *testing.T) { cases := []struct { in, expected os.FileMode }{ - {0000, 0100}, - {0777, 0711}, - {0644, 0700}, - {0755, 0711}, - {0444, 0500}, + {0000, 0111}, + {0777, 0755}, + {0644, 0755}, + {0755, 0755}, + {0444, 0555}, } for _, v := range cases { if out := chmodTarEntry(v.in); out != v.expected { From eaecd8b1b5871a4d17be27e3615106587eec1d3a Mon Sep 17 00:00:00 2001 From: sidharthamani Date: Mon, 9 Mar 2015 11:40:57 -0700 Subject: [PATCH 014/999] add syslog driver Signed-off-by: wlan0 --- daemon/config.go | 2 +- daemon/container.go | 7 +++ daemon/logger/syslog/syslog.go | 54 +++++++++++++++++++ docs/man/docker-create.1.md | 2 +- docs/man/docker-run.1.md | 2 +- docs/man/docker.1.md | 2 +- .../reference/api/docker_remote_api_v1.18.md | 2 +- docs/sources/reference/run.md | 5 ++ 8 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 daemon/logger/syslog/syslog.go diff --git a/daemon/config.go b/daemon/config.go index 4adc025ee..9b38fde4e 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -83,7 +83,7 @@ func (config *Config) InstallFlags() { opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon") config.Ulimits = make(map[string]*ulimit.Ulimit) opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers") - flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Containers logging driver(json-file/none)") + flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Containers logging driver") } func getDefaultNetworkMtu() int { diff --git a/daemon/container.go b/daemon/container.go index 05dcd7626..dabaedbe8 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -23,6 +23,7 @@ import ( "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/logger" "github.com/docker/docker/daemon/logger/jsonfilelog" + "github.com/docker/docker/daemon/logger/syslog" "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/links" @@ -1377,6 +1378,12 @@ func (container *Container) startLogging() error { return err } l = dl + case "syslog": + dl, err := syslog.New(container.ID[:12]) + if err != nil { + return err + } + l = dl case "none": return nil default: diff --git a/daemon/logger/syslog/syslog.go b/daemon/logger/syslog/syslog.go new file mode 100644 index 000000000..1f73d91d6 --- /dev/null +++ b/daemon/logger/syslog/syslog.go @@ -0,0 +1,54 @@ +package syslog + +import ( + "fmt" + "log/syslog" + "os" + "path" + "sync" + + "github.com/docker/docker/daemon/logger" +) + +type Syslog struct { + writer *syslog.Writer + tag string + mu sync.Mutex +} + +func New(tag string) (logger.Logger, error) { + log, err := syslog.New(syslog.LOG_USER, path.Base(os.Args[0])) + if err != nil { + return nil, err + } + return &Syslog{ + writer: log, + tag: tag, + }, nil +} + +func (s *Syslog) Log(msg *logger.Message) error { + logMessage := fmt.Sprintf("%s: %s", s.tag, string(msg.Line)) + if msg.Source == "stderr" { + if err := s.writer.Err(logMessage); err != nil { + return err + } + + } else { + if err := s.writer.Info(logMessage); err != nil { + return err + } + } + return nil +} + +func (s *Syslog) Close() error { + if s.writer != nil { + return s.writer.Close() + } + return nil +} + +func (s *Syslog) Name() string { + return "Syslog" +} diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index ddb234d37..44ce96fc2 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -117,7 +117,7 @@ IMAGE [COMMAND] [ARG...] **--lxc-conf**=[] (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" -**--log-driver**="|*json-file*|*none*" +**--log-driver**="|*json-file*|*syslog*|*none*" Logging driver for container. Default is defined by daemon `--log-driver` flag. **Warning**: `docker logs` command works only for `json-file` logging driver. diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 234d8dc52..bd9109375 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -218,7 +218,7 @@ which interface and port to use. **--lxc-conf**=[] (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" -**--log-driver**="|*json-file*|*none*" +**--log-driver**="|*json-file*|*syslog*|*none*" Logging driver for container. Default is defined by daemon `--log-driver` flag. **Warning**: `docker logs` command works only for `json-file` logging driver. diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index 530fa9501..c9fe3eae9 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -89,7 +89,7 @@ unix://[/path/to/socket] to use. **--label**="[]" Set key=value labels to the daemon (displayed in `docker info`) -**--log-driver**="*json-file*|*none*" +**--log-driver**="*json-file*|*syslog*|*none*" Container's logging driver. Default is `default`. **Warning**: `docker logs` command works only for `json-file` logging driver. diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index ac22563b7..ca923235a 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -258,7 +258,7 @@ Json Parameters: `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` - **LogConfig** - Logging configuration to container, format `{ "Type": "", "Config": {"key1": "val1"}} - Available types: `json-file`, `none`. + Available types: `json-file`, `syslog`, `none`. `json-file` logging driver. Query Parameters: diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 052a35823..29734eafe 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -656,6 +656,11 @@ this driver. Default logging driver for Docker. Writes JSON messages to file. `docker logs` command is available only for this logging driver +## Logging driver: syslog + +Syslog logging driver for Docker. Writes log messages to syslog. `docker logs` +command is not available for this logging driver + ## Overriding Dockerfile image defaults When a developer builds an image from a [*Dockerfile*](/reference/builder) From 33448ac3c9c0c63fb07f7bf04cec998d3536fc10 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Sun, 22 Mar 2015 17:56:05 -0700 Subject: [PATCH 015/999] Restore TestPullVerified test Signed-off-by: Arnaud Porterie --- integration-cli/docker_cli_pull_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index c55bd2e67..926e76343 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -55,8 +55,6 @@ func TestPullImageWithAliases(t *testing.T) { // pulling library/hello-world should show verified message func TestPullVerified(t *testing.T) { - t.Skip("problems verifying library/hello-world (to be fixed)") - // Image must be pulled from central repository to get verified message // unless keychain is manually updated to contain the daemon's sign key. From f07ac12791e4af7f199a893705204481588062ae Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Sun, 22 Mar 2015 18:45:01 -0700 Subject: [PATCH 016/999] Document VERSION file update scheme Signed-off-by: Arnaud Porterie --- project/RELEASE-CHECKLIST.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/project/RELEASE-CHECKLIST.md b/project/RELEASE-CHECKLIST.md index d9382b901..10af71c81 100644 --- a/project/RELEASE-CHECKLIST.md +++ b/project/RELEASE-CHECKLIST.md @@ -364,7 +364,17 @@ echo "https://github.com/$GITHUBUSER/docker/compare/docker:master...$GITHUBUSER: Again, get two maintainers to validate, then merge, then push that pretty blue button to delete your branch. -### 13. Rejoice and Evangelize! +### 13. Update the API docs and VERSION files + +Now that version X.Y.Z is out, time to start working on the next! Update the +content of the `VERSION` file to be the next minor (incrementing Y) and add the +`-dev` suffix. For example, after 1.5.0 release, the `VERSION` file gets +updated to `1.6.0-dev` (as in "1.6.0 in the making"). + +Also create a new entry in `docs/sources/reference/api/` by copying the latest +and bumping the version number (in both the file's name and content). + +### 14. Rejoice and Evangelize! Congratulations! You're done. From 7dc1af146d24c346ac74c9dcb1df98f9ef51339d Mon Sep 17 00:00:00 2001 From: Mabin Date: Mon, 16 Mar 2015 17:08:22 +0800 Subject: [PATCH 017/999] Fix hanging up problem when start and attach multiple containers Signed-off-by: Mabin --- api/client/commands.go | 50 +++++++++++------------- integration-cli/docker_cli_start_test.go | 46 ++++++++++++++++++++++ 2 files changed, 69 insertions(+), 27 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index e10bee3b1..9adf09abf 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -723,18 +723,6 @@ func (cli *DockerCli) CmdStart(args ...string) error { cmd.Require(flag.Min, 1) utils.ParseFlags(cmd, args, true) - hijacked := make(chan io.Closer) - // Block the return until the chan gets closed - defer func() { - log.Debugf("CmdStart() returned, defer waiting for hijack to finish.") - if _, ok := <-hijacked; ok { - log.Errorf("Hijack did not finish (chan still open)") - } - if *openStdin || *attach { - cli.in.Close() - } - }() - if *attach || *openStdin { if cmd.NArg() > 1 { return fmt.Errorf("You cannot start and attach multiple containers at once.") @@ -770,26 +758,34 @@ func (cli *DockerCli) CmdStart(args ...string) error { v.Set("stdout", "1") v.Set("stderr", "1") + hijacked := make(chan io.Closer) + // Block the return until the chan gets closed + defer func() { + log.Debugf("CmdStart() returned, defer waiting for hijack to finish.") + if _, ok := <-hijacked; ok { + log.Errorf("Hijack did not finish (chan still open)") + } + cli.in.Close() + }() cErr = promise.Go(func() error { return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil) }) - } else { - close(hijacked) + + // Acknowledge the hijack before starting + select { + case closer := <-hijacked: + // Make sure that the hijack gets closed when returning (results + // in closing the hijack chan and freeing server's goroutines) + if closer != nil { + defer closer.Close() + } + case err := <-cErr: + if err != nil { + return err + } + } } - // Acknowledge the hijack before starting - select { - case closer := <-hijacked: - // Make sure that the hijack gets closed when returning (results - // in closing the hijack chan and freeing server's goroutines) - if closer != nil { - defer closer.Close() - } - case err := <-cErr: - if err != nil { - return err - } - } var encounteredError error for _, name := range cmd.Args() { _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false)) diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index 01f0ef95a..3ec04c916 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -240,3 +240,49 @@ func TestStartMultipleContainers(t *testing.T) { logDone("start - start multiple containers continue on one failed") } + +func TestStartAttachMultipleContainers(t *testing.T) { + + var cmd *exec.Cmd + + defer deleteAllContainers() + // run multiple containers to test + for _, container := range []string{"test1", "test2", "test3"} { + cmd = exec.Command(dockerBinary, "run", "-d", "--name", container, "busybox", "top") + if out, _, err := runCommandWithOutput(cmd); err != nil { + t.Fatal(out, err) + } + } + + // stop all the containers + for _, container := range []string{"test1", "test2", "test3"} { + cmd = exec.Command(dockerBinary, "stop", container) + if out, _, err := runCommandWithOutput(cmd); err != nil { + t.Fatal(out, err) + } + } + + // test start and attach multiple containers at once, expected error + for _, option := range []string{"-a", "-i", "-ai"} { + cmd = exec.Command(dockerBinary, "start", option, "test1", "test2", "test3") + out, _, err := runCommandWithOutput(cmd) + if !strings.Contains(out, "You cannot start and attach multiple containers at once.") || err == nil { + t.Fatal("Expected error but got none") + } + } + + // confirm the state of all the containers be stopped + for container, expected := range map[string]string{"test1": "false", "test2": "false", "test3": "false"} { + cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", container) + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(out, err) + } + out = strings.Trim(out, "\r\n") + if out != expected { + t.Fatal("Container running state wrong") + } + } + + logDone("start - error on start and attach multiple containers at once") +} From 5de1e7bc3a823e1a4fa35ddf75401d233b3e4692 Mon Sep 17 00:00:00 2001 From: bobby abbott Date: Sun, 22 Mar 2015 22:31:46 -0700 Subject: [PATCH 018/999] Refactors pkg/testutils Solves #11579. Signed-off-by: bobby abbott --- engine/env_test.go | 18 +++++++++++++++--- pkg/testutils/README.md | 2 -- pkg/testutils/utils.go | 37 ------------------------------------- 3 files changed, 15 insertions(+), 42 deletions(-) delete mode 100644 pkg/testutils/README.md delete mode 100644 pkg/testutils/utils.go diff --git a/engine/env_test.go b/engine/env_test.go index 2ed99d0fe..5182783bb 100644 --- a/engine/env_test.go +++ b/engine/env_test.go @@ -3,12 +3,24 @@ package engine import ( "bytes" "encoding/json" + "math/rand" "testing" "time" - - "github.com/docker/docker/pkg/testutils" ) +const chars = "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "~!@#$%^&*()-_+={}[]\\|<,>.?/\"';:` " + +// RandomString returns random string of specified length +func RandomString(length int) string { + res := make([]byte, length) + for i := 0; i < length; i++ { + res[i] = chars[rand.Intn(len(chars))] + } + return string(res) +} + func TestEnvLenZero(t *testing.T) { env := &Env{} if env.Len() != 0 { @@ -185,7 +197,7 @@ func TestMultiMap(t *testing.T) { func testMap(l int) [][2]string { res := make([][2]string, l) for i := 0; i < l; i++ { - t := [2]string{testutils.RandomString(5), testutils.RandomString(20)} + t := [2]string{RandomString(5), RandomString(20)} res[i] = t } return res diff --git a/pkg/testutils/README.md b/pkg/testutils/README.md deleted file mode 100644 index a208a90e6..000000000 --- a/pkg/testutils/README.md +++ /dev/null @@ -1,2 +0,0 @@ -`testutils` is a collection of utility functions to facilitate the writing -of tests. It is used in various places by the Docker test suite. diff --git a/pkg/testutils/utils.go b/pkg/testutils/utils.go deleted file mode 100644 index 9c664ff25..000000000 --- a/pkg/testutils/utils.go +++ /dev/null @@ -1,37 +0,0 @@ -package testutils - -import ( - "math/rand" - "testing" - "time" -) - -const chars = "abcdefghijklmnopqrstuvwxyz" + - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "~!@#$%^&*()-_+={}[]\\|<,>.?/\"';:` " - -// Timeout calls f and waits for 100ms for it to complete. -// If it doesn't, it causes the tests to fail. -// t must be a valid testing context. -func Timeout(t *testing.T, f func()) { - onTimeout := time.After(100 * time.Millisecond) - onDone := make(chan bool) - go func() { - f() - close(onDone) - }() - select { - case <-onTimeout: - t.Fatalf("timeout") - case <-onDone: - } -} - -// RandomString returns random string of specified length -func RandomString(length int) string { - res := make([]byte, length) - for i := 0; i < length; i++ { - res[i] = chars[rand.Intn(len(chars))] - } - return string(res) -} From a91b2431a303f919b0737d95639d8e445124cb23 Mon Sep 17 00:00:00 2001 From: bobby abbott Date: Sun, 22 Mar 2015 23:27:04 -0700 Subject: [PATCH 019/999] Refactor pkg/networkfs Solves #11591 Signed-off-by: bobby abbott --- api/client/commands.go | 2 +- daemon/container.go | 4 ++-- daemon/daemon.go | 2 +- daemon/networkdriver/bridge/driver.go | 2 +- integration-cli/docker_cli_run_test.go | 2 +- pkg/{networkfs => }/etchosts/etchosts.go | 0 pkg/{networkfs => }/etchosts/etchosts_test.go | 0 pkg/{networkfs => }/resolvconf/resolvconf.go | 0 pkg/{networkfs => }/resolvconf/resolvconf_test.go | 0 9 files changed, 6 insertions(+), 6 deletions(-) rename pkg/{networkfs => }/etchosts/etchosts.go (100%) rename pkg/{networkfs => }/etchosts/etchosts_test.go (100%) rename pkg/{networkfs => }/resolvconf/resolvconf.go (100%) rename pkg/{networkfs => }/resolvconf/resolvconf_test.go (100%) diff --git a/api/client/commands.go b/api/client/commands.go index 2b837596e..6de8ca01f 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -37,11 +37,11 @@ import ( "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/pkg/networkfs/resolvconf" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/filters" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/promise" + "github.com/docker/docker/pkg/resolvconf" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/term" diff --git a/daemon/container.go b/daemon/container.go index 0fff3238e..4b7c58558 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -31,10 +31,10 @@ import ( "github.com/docker/docker/pkg/broadcastwriter" "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/directory" + "github.com/docker/docker/pkg/etchosts" "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/networkfs/etchosts" - "github.com/docker/docker/pkg/networkfs/resolvconf" "github.com/docker/docker/pkg/promise" + "github.com/docker/docker/pkg/resolvconf" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/runconfig" diff --git a/daemon/daemon.go b/daemon/daemon.go index ebb43e248..6a27a085a 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -35,9 +35,9 @@ import ( "github.com/docker/docker/pkg/graphdb" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/namesgenerator" - "github.com/docker/docker/pkg/networkfs/resolvconf" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/kernel" + "github.com/docker/docker/pkg/resolvconf" "github.com/docker/docker/pkg/sysinfo" "github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/runconfig" diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index aa139b9a3..b8dfdf948 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -17,8 +17,8 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/nat" "github.com/docker/docker/pkg/iptables" - "github.com/docker/docker/pkg/networkfs/resolvconf" "github.com/docker/docker/pkg/parsers/kernel" + "github.com/docker/docker/pkg/resolvconf" "github.com/docker/libcontainer/netlink" ) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 636ef36e1..f192468e0 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -20,7 +20,7 @@ import ( "time" "github.com/docker/docker/nat" - "github.com/docker/docker/pkg/networkfs/resolvconf" + "github.com/docker/docker/pkg/resolvconf" ) // "test123" should be printed by docker run diff --git a/pkg/networkfs/etchosts/etchosts.go b/pkg/etchosts/etchosts.go similarity index 100% rename from pkg/networkfs/etchosts/etchosts.go rename to pkg/etchosts/etchosts.go diff --git a/pkg/networkfs/etchosts/etchosts_test.go b/pkg/etchosts/etchosts_test.go similarity index 100% rename from pkg/networkfs/etchosts/etchosts_test.go rename to pkg/etchosts/etchosts_test.go diff --git a/pkg/networkfs/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go similarity index 100% rename from pkg/networkfs/resolvconf/resolvconf.go rename to pkg/resolvconf/resolvconf.go diff --git a/pkg/networkfs/resolvconf/resolvconf_test.go b/pkg/resolvconf/resolvconf_test.go similarity index 100% rename from pkg/networkfs/resolvconf/resolvconf_test.go rename to pkg/resolvconf/resolvconf_test.go From d124197cc726a3b69cc3cde8c4d560f3f0e1af9c Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 22 Mar 2015 19:09:12 +0100 Subject: [PATCH 020/999] Remove container if --rm flag is passed and container cannot be started Signed-off-by: Antonio Murdaca --- api/client/commands.go | 11 +++++-- integration-cli/docker_cli_run_test.go | 43 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 2b837596e..cb0cd2423 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -2499,6 +2499,14 @@ func (cli *DockerCli) CmdRun(args ...string) error { } } + defer func() { + if *flAutoRemove { + if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, false)); err != nil { + log.Errorf("Error deleting container: %s", err) + } + } + }() + //start the container if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, false)); err != nil { return err @@ -2536,9 +2544,6 @@ func (cli *DockerCli) CmdRun(args ...string) error { if _, status, err = getExitCode(cli, createResponse.ID); err != nil { return err } - if _, _, err := readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, false)); err != nil { - return err - } } else { // No Autoremove: Simply retrieve the exit code if !config.Tty { diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 636ef36e1..4f6f646a5 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3348,3 +3348,46 @@ func TestRunVolumesFromRestartAfterRemoved(t *testing.T) { logDone("run - can restart a volumes-from container after producer is removed") } + +// run container with --rm should remove container if exit code != 0 +func TestRunContainerWithRmFlagExitCodeNotEqualToZero(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "--rm", "busybox", "ls", "/notexists") + out, _, err := runCommandWithOutput(runCmd) + if err == nil { + t.Fatal("Expected docker run to fail", out, err) + } + + out, err = getAllContainers() + if err != nil { + t.Fatal(out, err) + } + + if out != "" { + t.Fatal("Expected not to have containers", out) + } + + logDone("run - container is removed if run with --rm and exit code != 0") +} + +func TestRunContainerWithRmFlagCannotStartContainer(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "--rm", "busybox", "commandNotFound") + out, _, err := runCommandWithOutput(runCmd) + if err == nil { + t.Fatal("Expected docker run to fail", out, err) + } + + out, err = getAllContainers() + if err != nil { + t.Fatal(out, err) + } + + if out != "" { + t.Fatal("Expected not to have containers", out) + } + + logDone("run - container is removed if run with --rm and cannot start") +} From 5daa9260bcef2a47c493adc04e6638c9d200a9cf Mon Sep 17 00:00:00 2001 From: Srini Brahmaroutu Date: Fri, 27 Feb 2015 22:18:11 +0000 Subject: [PATCH 021/999] Test image api through local V1 repo Closes #10967 Signed-off-by: Srini Brahmaroutu --- integration-cli/docker_cli_pull_test.go | 2 ++ integration-cli/requirements.go | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index c55bd2e67..fec9700e2 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -108,6 +108,8 @@ func TestPullNonExistingImage(t *testing.T) { // pulling an image from the central registry using official names should work // ensure all pulls result in the same image func TestPullImageOfficialNames(t *testing.T) { + testRequires(t, Network) + names := []string{ "docker.io/hello-world", "index.docker.io/hello-world", diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go index 346d0cdf6..cdd999187 100644 --- a/integration-cli/requirements.go +++ b/integration-cli/requirements.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "log" + "net/http" "os/exec" "strings" "testing" @@ -32,6 +33,16 @@ var ( func() bool { return supportsExec }, "Test requires 'docker exec' capabilities on the tested daemon.", } + Network = TestRequirement{ + func() bool { + resp, err := http.Get("http://hub.docker.com") + if resp != nil { + resp.Body.Close() + } + return err == nil + }, + "Test requires network availability, environment variable set to none to run in a non-network enabled mode.", + } RegistryHosting = TestRequirement{ func() bool { // for now registry binary is built only if we're running inside From b38ff8c83d82b7958c62c07a8b82e4b85f0311c0 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Mon, 23 Mar 2015 18:38:43 +0000 Subject: [PATCH 022/999] Disable ANSI emulation in certain windows shells This disables recently added ANSI emulation feature in certain Windows shells (like ConEmu) where ANSI output is emulated by default with builtin functionality in the shell. MSYS (mingw) runs in cmd.exe window and it doesn't support emulation. Cygwin doesn't even pass terminal handles to docker.exe as far as I can tell, stdin/stdout/stderr handles are behaving like non-TTY. Therefore not even including that in the check. Signed-off-by: Ahmet Alp Balkan --- pkg/term/term_windows.go | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/pkg/term/term_windows.go b/pkg/term/term_windows.go index e6d9466a6..abda841cb 100644 --- a/pkg/term/term_windows.go +++ b/pkg/term/term_windows.go @@ -3,6 +3,7 @@ package term import ( "io" + "os" "github.com/docker/docker/pkg/term/winconsole" ) @@ -114,5 +115,23 @@ func GetFdInfo(in interface{}) (uintptr, bool) { } func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { - return winconsole.StdStreams() + var shouldEmulateANSI bool + switch { + case os.Getenv("ConEmuANSI") == "ON": + // ConEmu shell, ansi emulated by default and ConEmu does an extensively + // good emulation. + shouldEmulateANSI = false + case os.Getenv("MSYSTEM") != "": + // MSYS (mingw) cannot fully emulate well and still shows escape characters + // mostly because it's still running on cmd.exe window. + shouldEmulateANSI = true + default: + shouldEmulateANSI = true + } + + if shouldEmulateANSI { + return winconsole.StdStreams() + } + + return os.Stdin, os.Stdout, os.Stderr } From 3a939d99870eaad34aea2010b64147a1e60bb3c5 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 23 Mar 2015 14:46:44 -0400 Subject: [PATCH 023/999] make.sh: leave around the generated version For positerity (largely of packagers) lets leave around the generated version files that happen during build. They're already ignored in git, and recreated on every build. Signed-off-by: Vincent Batts --- hack/make.sh | 3 --- 1 file changed, 3 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index 0db70a750..118d4327f 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -265,9 +265,6 @@ main() { bundle $SCRIPTDIR/make/$bundle echo done - - # if we get all the way through successfully, let's delete our autogenerated code! - rm -r autogen } main "$@" From 8f025aae36fd040d8d2617890b258b5460ae44db Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 23 Mar 2015 19:59:30 +0100 Subject: [PATCH 024/999] Refactor syslog Log else clause Signed-off-by: Antonio Murdaca --- daemon/logger/syslog/syslog.go | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/daemon/logger/syslog/syslog.go b/daemon/logger/syslog/syslog.go index 1f73d91d6..e7054d257 100644 --- a/daemon/logger/syslog/syslog.go +++ b/daemon/logger/syslog/syslog.go @@ -30,16 +30,9 @@ func New(tag string) (logger.Logger, error) { func (s *Syslog) Log(msg *logger.Message) error { logMessage := fmt.Sprintf("%s: %s", s.tag, string(msg.Line)) if msg.Source == "stderr" { - if err := s.writer.Err(logMessage); err != nil { - return err - } - - } else { - if err := s.writer.Info(logMessage); err != nil { - return err - } + return s.writer.Err(logMessage) } - return nil + return s.writer.Info(logMessage) } func (s *Syslog) Close() error { From e600df2d9732b8c40d056563e361ded6cd5ce773 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 23 Mar 2015 20:03:24 +0100 Subject: [PATCH 025/999] Remove redunant nil check, s.writer cannot be nil Signed-off-by: Antonio Murdaca --- daemon/logger/syslog/syslog.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/daemon/logger/syslog/syslog.go b/daemon/logger/syslog/syslog.go index 1f73d91d6..cb8976a17 100644 --- a/daemon/logger/syslog/syslog.go +++ b/daemon/logger/syslog/syslog.go @@ -43,10 +43,7 @@ func (s *Syslog) Log(msg *logger.Message) error { } func (s *Syslog) Close() error { - if s.writer != nil { - return s.writer.Close() - } - return nil + return s.writer.Close() } func (s *Syslog) Name() string { From 8b02d85e1728b48729b2fb8553b2ec4b56a30d37 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 23 Mar 2015 20:20:43 +0100 Subject: [PATCH 026/999] Remove hardcoded error Signed-off-by: Antonio Murdaca --- builder/evaluator.go | 7 +------ integration-cli/docker_cli_build_test.go | 2 +- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/builder/evaluator.go b/builder/evaluator.go index b74c1ba6a..aff0e4eab 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -20,7 +20,6 @@ package builder import ( - "errors" "fmt" "io" "os" @@ -42,10 +41,6 @@ import ( "github.com/docker/docker/utils" ) -var ( - ErrDockerfileEmpty = errors.New("Dockerfile cannot be empty") -) - // Environment variable interpolation will happen on these statements only. var replaceEnvAllowed = map[string]struct{}{ command.Env: {}, @@ -225,7 +220,7 @@ func (b *Builder) readDockerfile() error { return fmt.Errorf("Cannot locate specified Dockerfile: %s", origFile) } if fi.Size() == 0 { - return ErrDockerfileEmpty + return fmt.Errorf("The Dockerfile (%s) cannot be empty", origFile) } f, err := os.Open(filename) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 44d631385..239dce6e8 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -3504,7 +3504,7 @@ func TestBuildFailsDockerfileEmpty(t *testing.T) { defer deleteImages(name) _, err := buildImage(name, ``, true) if err != nil { - if !strings.Contains(err.Error(), "Dockerfile cannot be empty") { + if !strings.Contains(err.Error(), "The Dockerfile (Dockerfile) cannot be empty") { t.Fatalf("Wrong error %v, must be about empty Dockerfile", err) } } else { From 8e4d9f3cf9669f45b0591eea27c47b6f64d89c2d Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Sat, 21 Mar 2015 16:57:22 -0400 Subject: [PATCH 027/999] Improve err message when parsing kernel port range Signed-off-by: Brian Goff --- daemon/networkdriver/portallocator/portallocator.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/daemon/networkdriver/portallocator/portallocator.go b/daemon/networkdriver/portallocator/portallocator.go index da9f98739..3a4f8e6ae 100644 --- a/daemon/networkdriver/portallocator/portallocator.go +++ b/daemon/networkdriver/portallocator/portallocator.go @@ -70,10 +70,11 @@ func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated { func init() { const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range" + portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", beginPortRange, endPortRange) file, err := os.Open(portRangeKernelParam) if err != nil { - log.Warnf("Failed to read %s kernel parameter: %v", portRangeKernelParam, err) + log.Warnf("port allocator - %s due to error: %v", portRangeFallback, err) return } var start, end int @@ -82,7 +83,7 @@ func init() { if err == nil { err = fmt.Errorf("unexpected count of parsed numbers (%d)", n) } - log.Errorf("Failed to parse port range from %s: %v", portRangeKernelParam, err) + log.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err) return } beginPortRange = start From 664ef0cbe2113e8307135e656cc6db9199f8268e Mon Sep 17 00:00:00 2001 From: George MacRorie Date: Mon, 23 Mar 2015 19:21:37 +0000 Subject: [PATCH 028/999] Cleanup redundant else statements find via golint #11602 Signed-off-by: George MacRorie --- builder/parser/line_parsers.go | 19 ++++++++++--------- builder/shell_parser.go | 13 ++++++------- integration-cli/docker_utils.go | 3 +-- opts/opts.go | 3 +-- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/builder/parser/line_parsers.go b/builder/parser/line_parsers.go index 3026a0b06..6e284d6fc 100644 --- a/builder/parser/line_parsers.go +++ b/builder/parser/line_parsers.go @@ -239,17 +239,18 @@ func parseJSON(rest string) (*Node, map[string]bool, error) { var top, prev *Node for _, str := range myJson { - if s, ok := str.(string); !ok { + s, ok := str.(string) + if !ok { return nil, nil, errDockerfileNotStringArray - } else { - node := &Node{Value: s} - if prev == nil { - top = node - } else { - prev.Next = node - } - prev = node } + + node := &Node{Value: s} + if prev == nil { + top = node + } else { + prev.Next = node + } + prev = node } return top, map[string]bool{"json": true}, nil diff --git a/builder/shell_parser.go b/builder/shell_parser.go index b8c746773..d086645eb 100644 --- a/builder/shell_parser.go +++ b/builder/shell_parser.go @@ -158,14 +158,13 @@ func (sw *shellWord) processDollar() (string, error) { return sw.getEnv(name), nil } return "", fmt.Errorf("Unsupported ${} substitution: %s", sw.word) - } else { - // $xxx case - name := sw.processName() - if name == "" { - return "$", nil - } - return sw.getEnv(name), nil } + // $xxx case + name := sw.processName() + if name == "" { + return "$", nil + } + return sw.getEnv(name), nil } func (sw *shellWord) processName() string { diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 10cc6c921..e0b9bacc4 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -395,9 +395,8 @@ func getSliceOfPausedContainers() ([]string, error) { if err == nil { slice := strings.Split(strings.TrimSpace(out), "\n") return slice, err - } else { - return []string{out}, err } + return []string{out}, err } func unpauseContainer(container string) error { diff --git a/opts/opts.go b/opts/opts.go index e867c0a21..df9decf61 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -192,9 +192,8 @@ func ValidateMACAddress(val string) (string, error) { _, err := net.ParseMAC(strings.TrimSpace(val)) if err != nil { return "", err - } else { - return val, nil } + return val, nil } // Validates domain for resolvconf search configuration. From 12576798769081823a3f660b46290da808630616 Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Mon, 23 Mar 2015 20:05:26 +0000 Subject: [PATCH 029/999] Refactor global portallocator state into a global struct Signed-off-by: Paul Bellamy --- .../portallocator/portallocator.go | 61 ++++++++----- .../portallocator/portallocator_test.go | 90 +++++++++---------- 2 files changed, 83 insertions(+), 68 deletions(-) diff --git a/daemon/networkdriver/portallocator/portallocator.go b/daemon/networkdriver/portallocator/portallocator.go index da9f98739..42540b407 100644 --- a/daemon/networkdriver/portallocator/portallocator.go +++ b/daemon/networkdriver/portallocator/portallocator.go @@ -50,12 +50,21 @@ var ( ) var ( - mutex sync.Mutex - - defaultIP = net.ParseIP("0.0.0.0") - globalMap = ipMapping{} + defaultIP = net.ParseIP("0.0.0.0") + defaultPortAllocator = New() ) +type PortAllocator struct { + mutex sync.Mutex + ipMap ipMapping +} + +func New() *PortAllocator { + return &PortAllocator{ + ipMap: ipMapping{}, + } +} + type ErrPortAlreadyAllocated struct { ip string port int @@ -109,12 +118,9 @@ func (e ErrPortAlreadyAllocated) Error() string { return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port) } -// RequestPort requests new port from global ports pool for specified ip and proto. -// If port is 0 it returns first free port. Otherwise it cheks port availability -// in pool and return that port or error if port is already busy. -func RequestPort(ip net.IP, proto string, port int) (int, error) { - mutex.Lock() - defer mutex.Unlock() +func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) { + p.mutex.Lock() + defer p.mutex.Unlock() if proto != "tcp" && proto != "udp" { return 0, ErrUnknownProtocol @@ -124,10 +130,10 @@ func RequestPort(ip net.IP, proto string, port int) (int, error) { ip = defaultIP } ipstr := ip.String() - protomap, ok := globalMap[ipstr] + protomap, ok := p.ipMap[ipstr] if !ok { protomap = newProtoMap() - globalMap[ipstr] = protomap + p.ipMap[ipstr] = protomap } mapping := protomap[proto] if port > 0 { @@ -145,15 +151,22 @@ func RequestPort(ip net.IP, proto string, port int) (int, error) { return port, nil } +// RequestPort requests new port from global ports pool for specified ip and proto. +// If port is 0 it returns first free port. Otherwise it cheks port availability +// in pool and return that port or error if port is already busy. +func RequestPort(ip net.IP, proto string, port int) (int, error) { + return defaultPortAllocator.RequestPort(ip, proto, port) +} + // ReleasePort releases port from global ports pool for specified ip and proto. -func ReleasePort(ip net.IP, proto string, port int) error { - mutex.Lock() - defer mutex.Unlock() +func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error { + p.mutex.Lock() + defer p.mutex.Unlock() if ip == nil { ip = defaultIP } - protomap, ok := globalMap[ip.String()] + protomap, ok := p.ipMap[ip.String()] if !ok { return nil } @@ -161,14 +174,22 @@ func ReleasePort(ip net.IP, proto string, port int) error { return nil } +func ReleasePort(ip net.IP, proto string, port int) error { + return defaultPortAllocator.ReleasePort(ip, proto, port) +} + // ReleaseAll releases all ports for all ips. -func ReleaseAll() error { - mutex.Lock() - globalMap = ipMapping{} - mutex.Unlock() +func (p *PortAllocator) ReleaseAll() error { + p.mutex.Lock() + p.ipMap = ipMapping{} + p.mutex.Unlock() return nil } +func ReleaseAll() error { + return defaultPortAllocator.ReleaseAll() +} + func (pm *portMap) findPort() (int, error) { port := pm.last for i := 0; i <= endPortRange-beginPortRange; i++ { diff --git a/daemon/networkdriver/portallocator/portallocator_test.go b/daemon/networkdriver/portallocator/portallocator_test.go index bac558fa4..f6f122bbd 100644 --- a/daemon/networkdriver/portallocator/portallocator_test.go +++ b/daemon/networkdriver/portallocator/portallocator_test.go @@ -10,14 +10,10 @@ func init() { endPortRange = DefaultPortRangeEnd } -func reset() { - ReleaseAll() -} - func TestRequestNewPort(t *testing.T) { - defer reset() + p := New() - port, err := RequestPort(defaultIP, "tcp", 0) + port, err := p.RequestPort(defaultIP, "tcp", 0) if err != nil { t.Fatal(err) } @@ -28,9 +24,9 @@ func TestRequestNewPort(t *testing.T) { } func TestRequestSpecificPort(t *testing.T) { - defer reset() + p := New() - port, err := RequestPort(defaultIP, "tcp", 5000) + port, err := p.RequestPort(defaultIP, "tcp", 5000) if err != nil { t.Fatal(err) } @@ -40,9 +36,9 @@ func TestRequestSpecificPort(t *testing.T) { } func TestReleasePort(t *testing.T) { - defer reset() + p := New() - port, err := RequestPort(defaultIP, "tcp", 5000) + port, err := p.RequestPort(defaultIP, "tcp", 5000) if err != nil { t.Fatal(err) } @@ -50,15 +46,15 @@ func TestReleasePort(t *testing.T) { t.Fatalf("Expected port 5000 got %d", port) } - if err := ReleasePort(defaultIP, "tcp", 5000); err != nil { + if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil { t.Fatal(err) } } func TestReuseReleasedPort(t *testing.T) { - defer reset() + p := New() - port, err := RequestPort(defaultIP, "tcp", 5000) + port, err := p.RequestPort(defaultIP, "tcp", 5000) if err != nil { t.Fatal(err) } @@ -66,20 +62,20 @@ func TestReuseReleasedPort(t *testing.T) { t.Fatalf("Expected port 5000 got %d", port) } - if err := ReleasePort(defaultIP, "tcp", 5000); err != nil { + if err := p.ReleasePort(defaultIP, "tcp", 5000); err != nil { t.Fatal(err) } - port, err = RequestPort(defaultIP, "tcp", 5000) + port, err = p.RequestPort(defaultIP, "tcp", 5000) if err != nil { t.Fatal(err) } } func TestReleaseUnreadledPort(t *testing.T) { - defer reset() + p := New() - port, err := RequestPort(defaultIP, "tcp", 5000) + port, err := p.RequestPort(defaultIP, "tcp", 5000) if err != nil { t.Fatal(err) } @@ -87,7 +83,7 @@ func TestReleaseUnreadledPort(t *testing.T) { t.Fatalf("Expected port 5000 got %d", port) } - port, err = RequestPort(defaultIP, "tcp", 5000) + port, err = p.RequestPort(defaultIP, "tcp", 5000) switch err.(type) { case ErrPortAlreadyAllocated: @@ -97,18 +93,16 @@ func TestReleaseUnreadledPort(t *testing.T) { } func TestUnknowProtocol(t *testing.T) { - defer reset() - - if _, err := RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol { + if _, err := New().RequestPort(defaultIP, "tcpp", 0); err != ErrUnknownProtocol { t.Fatalf("Expected error %s got %s", ErrUnknownProtocol, err) } } func TestAllocateAllPorts(t *testing.T) { - defer reset() + p := New() for i := 0; i <= endPortRange-beginPortRange; i++ { - port, err := RequestPort(defaultIP, "tcp", 0) + port, err := p.RequestPort(defaultIP, "tcp", 0) if err != nil { t.Fatal(err) } @@ -118,21 +112,21 @@ func TestAllocateAllPorts(t *testing.T) { } } - if _, err := RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated { + if _, err := p.RequestPort(defaultIP, "tcp", 0); err != ErrAllPortsAllocated { t.Fatalf("Expected error %s got %s", ErrAllPortsAllocated, err) } - _, err := RequestPort(defaultIP, "udp", 0) + _, err := p.RequestPort(defaultIP, "udp", 0) 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 { + if err := p.ReleasePort(defaultIP, "tcp", port); err != nil { t.Fatal(err) } - newPort, err := RequestPort(defaultIP, "tcp", 0) + newPort, err := p.RequestPort(defaultIP, "tcp", 0) if err != nil { t.Fatal(err) } @@ -142,10 +136,10 @@ func TestAllocateAllPorts(t *testing.T) { // now pm.last == newPort, release it so that it's the only free port of // the range, and ensure we get it back - if err := ReleasePort(defaultIP, "tcp", newPort); err != nil { + if err := p.ReleasePort(defaultIP, "tcp", newPort); err != nil { t.Fatal(err) } - port, err = RequestPort(defaultIP, "tcp", 0) + port, err = p.RequestPort(defaultIP, "tcp", 0) if err != nil { t.Fatal(err) } @@ -155,11 +149,11 @@ func TestAllocateAllPorts(t *testing.T) { } func BenchmarkAllocatePorts(b *testing.B) { - defer reset() + p := New() for i := 0; i < b.N; i++ { for i := 0; i <= endPortRange-beginPortRange; i++ { - port, err := RequestPort(defaultIP, "tcp", 0) + port, err := p.RequestPort(defaultIP, "tcp", 0) if err != nil { b.Fatal(err) } @@ -168,21 +162,21 @@ func BenchmarkAllocatePorts(b *testing.B) { b.Fatalf("Expected port %d got %d", expected, port) } } - reset() + p.ReleaseAll() } } func TestPortAllocation(t *testing.T) { - defer reset() + p := New() ip := net.ParseIP("192.168.0.1") ip2 := net.ParseIP("192.168.0.2") - if port, err := RequestPort(ip, "tcp", 80); err != nil { + if port, err := p.RequestPort(ip, "tcp", 80); err != nil { t.Fatal(err) } else if port != 80 { t.Fatalf("Acquire(80) should return 80, not %d", port) } - port, err := RequestPort(ip, "tcp", 0) + port, err := p.RequestPort(ip, "tcp", 0) if err != nil { t.Fatal(err) } @@ -190,41 +184,41 @@ func TestPortAllocation(t *testing.T) { t.Fatalf("Acquire(0) should return a non-zero port") } - if _, err := RequestPort(ip, "tcp", port); err == nil { + if _, err := p.RequestPort(ip, "tcp", port); err == nil { t.Fatalf("Acquiring a port already in use should return an error") } - if newPort, err := RequestPort(ip, "tcp", 0); err != nil { + if newPort, err := p.RequestPort(ip, "tcp", 0); err != nil { t.Fatal(err) } else if newPort == port { t.Fatalf("Acquire(0) allocated the same port twice: %d", port) } - if _, err := RequestPort(ip, "tcp", 80); err == nil { + if _, err := p.RequestPort(ip, "tcp", 80); err == nil { t.Fatalf("Acquiring a port already in use should return an error") } - if _, err := RequestPort(ip2, "tcp", 80); err != nil { + if _, err := p.RequestPort(ip2, "tcp", 80); err != nil { t.Fatalf("It should be possible to allocate the same port on a different interface") } - if _, err := RequestPort(ip2, "tcp", 80); err == nil { + if _, err := p.RequestPort(ip2, "tcp", 80); err == nil { t.Fatalf("Acquiring a port already in use should return an error") } - if err := ReleasePort(ip, "tcp", 80); err != nil { + if err := p.ReleasePort(ip, "tcp", 80); err != nil { t.Fatal(err) } - if _, err := RequestPort(ip, "tcp", 80); err != nil { + if _, err := p.RequestPort(ip, "tcp", 80); err != nil { t.Fatal(err) } - port, err = RequestPort(ip, "tcp", 0) + port, err = p.RequestPort(ip, "tcp", 0) if err != nil { t.Fatal(err) } - port2, err := RequestPort(ip, "tcp", port+1) + port2, err := p.RequestPort(ip, "tcp", port+1) if err != nil { t.Fatal(err) } - port3, err := RequestPort(ip, "tcp", 0) + port3, err := p.RequestPort(ip, "tcp", 0) if err != nil { t.Fatal(err) } @@ -234,15 +228,15 @@ func TestPortAllocation(t *testing.T) { } func TestNoDuplicateBPR(t *testing.T) { - defer reset() + p := New() - if port, err := RequestPort(defaultIP, "tcp", beginPortRange); err != nil { + if port, err := p.RequestPort(defaultIP, "tcp", beginPortRange); err != nil { t.Fatal(err) } else if port != beginPortRange { t.Fatalf("Expected port %d got %d", beginPortRange, port) } - if port, err := RequestPort(defaultIP, "tcp", 0); err != nil { + if port, err := p.RequestPort(defaultIP, "tcp", 0); err != nil { t.Fatal(err) } else if port == beginPortRange { t.Fatalf("Acquire(0) allocated the same port twice: %d", port) From a0cd0045282f21215d7845a987ec6292aa92a685 Mon Sep 17 00:00:00 2001 From: Frank Herrmann Date: Mon, 23 Mar 2015 20:10:38 +0000 Subject: [PATCH 030/999] Add builder/evaluator comments for the package in godoc-style fixes #11617 Signed-off-by: Frank Herrmann --- builder/evaluator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/evaluator.go b/builder/evaluator.go index b74c1ba6a..49b7272f0 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -1,4 +1,4 @@ -// builder is the evaluation step in the Dockerfile parse/evaluate pipeline. +// Package builder is the evaluation step in the Dockerfile parse/evaluate pipeline. // // It incorporates a dispatch table based on the parser.Node values (see the // parser package for more information) that are yielded from the parser itself. From e321ec980708b052fd788b41af97f875630cda9c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 23 Mar 2015 11:49:02 -0700 Subject: [PATCH 031/999] Update libcontainer to fd0087d3acdc4c5865de1829d4a Signed-off-by: Michael Crosby --- hack/vendor.sh | 2 +- .../docker/libcontainer/cgroups/fs/apply_raw.go | 7 +++---- .../github.com/docker/libcontainer/cgroups/fs/blkio.go | 8 ++------ .../src/github.com/docker/libcontainer/cgroups/fs/cpu.go | 6 +----- .../github.com/docker/libcontainer/cgroups/fs/devices.go | 6 +----- .../github.com/docker/libcontainer/cgroups/fs/freezer.go | 8 ++------ .../github.com/docker/libcontainer/cgroups/fs/memory.go | 9 +++------ .../docker/libcontainer/cgroups/systemd/apply_systemd.go | 2 +- .../docker/libcontainer/netlink/netlink_linux.go | 4 ++-- 9 files changed, 16 insertions(+), 36 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 8732224bf..f6422ccac 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -75,7 +75,7 @@ rm -rf src/github.com/docker/distribution mkdir -p src/github.com/docker/distribution mv tmp-digest src/github.com/docker/distribution/digest -clone git github.com/docker/libcontainer 4a72e540feb67091156b907c4700e580a99f5a9d +clone git github.com/docker/libcontainer fd0087d3acdc4c5865de1829d4accee5e3ebb658 # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) rm -rf src/github.com/docker/libcontainer/vendor eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')" diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go index f6c0d7d59..5cb8467c7 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go @@ -99,12 +99,11 @@ func (m *Manager) Apply(pid int) error { // created then join consists of writing the process pids to cgroup.procs p, err := d.path(name) if err != nil { + if cgroups.IsNotFound(err) { + continue + } return err } - if !cgroups.PathExists(p) { - continue - } - paths[name] = p } m.Paths = paths diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/blkio.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/blkio.go index 01da5d7fc..8e132643b 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/blkio.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/blkio.go @@ -17,12 +17,8 @@ type BlkioGroup struct { func (s *BlkioGroup) Apply(d *data) error { dir, err := d.join("blkio") - if err != nil { - if cgroups.IsNotFound(err) { - return nil - } else { - return err - } + if err != nil && !cgroups.IsNotFound(err) { + return err } if err := s.Set(dir, d.c); err != nil { diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/cpu.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/cpu.go index 42386fd84..1fbf7b154 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/cpu.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/cpu.go @@ -18,11 +18,7 @@ func (s *CpuGroup) Apply(d *data) error { // on a container basis dir, err := d.join("cpu") if err != nil { - if cgroups.IsNotFound(err) { - return nil - } else { - return err - } + return err } if err := s.Set(dir, d.c); err != nil { diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/devices.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/devices.go index fab8323e9..16e00b1c7 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/devices.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/devices.go @@ -11,11 +11,7 @@ type DevicesGroup struct { func (s *DevicesGroup) Apply(d *data) error { dir, err := d.join("devices") if err != nil { - if cgroups.IsNotFound(err) { - return nil - } else { - return err - } + return err } if err := s.Set(dir, d.c); err != nil { diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/freezer.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/freezer.go index 5e08e0530..fc8241d1b 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/freezer.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/freezer.go @@ -13,12 +13,8 @@ type FreezerGroup struct { func (s *FreezerGroup) Apply(d *data) error { dir, err := d.join("freezer") - if err != nil { - if cgroups.IsNotFound(err) { - return nil - } else { - return err - } + if err != nil && !cgroups.IsNotFound(err) { + return err } if err := s.Set(dir, d.c); err != nil { diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory.go index 68e930fdc..b99f81687 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory.go @@ -16,12 +16,9 @@ type MemoryGroup struct { func (s *MemoryGroup) Apply(d *data) error { dir, err := d.join("memory") - if err != nil { - if cgroups.IsNotFound(err) { - return nil - } else { - return err - } + // only return an error for memory if it was specified + if err != nil && (d.c.Memory != 0 || d.c.MemoryReservation != 0 || d.c.MemorySwap != 0) { + return err } defer func() { if err != nil { diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go index f4358e1a6..f35364069 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go @@ -91,7 +91,7 @@ func UseSystemd() bool { ddf := newProp("DefaultDependencies", false) if _, err := theConn.StartTransientUnit("docker-systemd-test-default-dependencies.scope", "replace", ddf); err != nil { if dbusError, ok := err.(dbus.Error); ok { - if dbusError.Name == "org.freedesktop.DBus.Error.PropertyReadOnly" { + if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") { hasTransientDefaultDependencies = false } } diff --git a/vendor/src/github.com/docker/libcontainer/netlink/netlink_linux.go b/vendor/src/github.com/docker/libcontainer/netlink/netlink_linux.go index 3ecb81fb7..c438ec300 100644 --- a/vendor/src/github.com/docker/libcontainer/netlink/netlink_linux.go +++ b/vendor/src/github.com/docker/libcontainer/netlink/netlink_linux.go @@ -659,7 +659,7 @@ func networkSetNsAction(iface *net.Interface, rtattr *RtAttr) error { } // Move a particular network interface to a particular network namespace -// specified by PID. This is idential to running: ip link set dev $name netns $pid +// specified by PID. This is identical to running: ip link set dev $name netns $pid func NetworkSetNsPid(iface *net.Interface, nspid int) error { data := uint32Attr(syscall.IFLA_NET_NS_PID, uint32(nspid)) return networkSetNsAction(iface, data) @@ -673,7 +673,7 @@ func NetworkSetNsFd(iface *net.Interface, fd int) error { return networkSetNsAction(iface, data) } -// Rname a particular interface to a different name +// Rename a particular interface to a different name // !!! Note that you can't rename an active interface. You need to bring it down before renaming it. // This is identical to running: ip link set dev ${oldName} name ${newName} func NetworkChangeName(iface *net.Interface, newName string) error { From 221e9624e3a7a7a9b81ffda9930657352080e32d Mon Sep 17 00:00:00 2001 From: Meaglith Ma Date: Thu, 12 Mar 2015 03:45:01 +0800 Subject: [PATCH 032/999] Fix decode tags value error when call get /v2//tags/list in registry api v2. Signed-off-by: Meaglith Ma --- registry/session_v2.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/registry/session_v2.go b/registry/session_v2.go index 833abeed6..ed8ce061e 100644 --- a/registry/session_v2.go +++ b/registry/session_v2.go @@ -352,8 +352,8 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, si } type remoteTags struct { - name string - tags []string + Name string + Tags []string } // Given a repository name, returns a json array of string tags @@ -393,5 +393,5 @@ func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestA if err != nil { return nil, fmt.Errorf("Error while decoding the http response: %s", err) } - return remote.tags, nil + return remote.Tags, nil } From 4925d98d1f638e9439e19b6c89c608a64a281a39 Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Mon, 23 Mar 2015 14:23:47 -0700 Subject: [PATCH 033/999] Add struct tags on v2 remote tags struct Signed-off-by: Derek McGowan (github: dmcgowan) --- registry/session_v2.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/registry/session_v2.go b/registry/session_v2.go index ed8ce061e..22f39317b 100644 --- a/registry/session_v2.go +++ b/registry/session_v2.go @@ -352,8 +352,8 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, si } type remoteTags struct { - Name string - Tags []string + Name string `json:"name"` + Tags []string `json:"tags"` } // Given a repository name, returns a json array of string tags From bfc748221b15a0f6e42a6ab520ed6e31fa9e90c8 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Mon, 23 Mar 2015 15:16:13 -0700 Subject: [PATCH 034/999] Cleanup "hello-world" image in build tests Signed-off-by: Arnaud Porterie --- integration-cli/docker_cli_build_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 239dce6e8..9a8ee69e5 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5487,7 +5487,7 @@ func TestBuildRUNoneJSON(t *testing.T) { name := "testbuildrunonejson" defer deleteAllContainers() - defer deleteImages(name) + defer deleteImages(name, "hello-world") ctx, err := fakeContext(`FROM hello-world:frozen RUN [ "/hello" ]`, map[string]string{}) @@ -5513,7 +5513,7 @@ RUN [ "/hello" ]`, map[string]string{}) func TestBuildResourceConstraintsAreUsed(t *testing.T) { name := "testbuildresourceconstraints" defer deleteAllContainers() - defer deleteImages(name) + defer deleteImages(name, "hello-world") ctx, err := fakeContext(` FROM hello-world:frozen From f2c7b4d7437443d469b2247362835af02ccef385 Mon Sep 17 00:00:00 2001 From: Anton Tiurin Date: Tue, 24 Mar 2015 01:32:33 +0300 Subject: [PATCH 035/999] Syslog.Log - Remove redundant cast of msg.Line []byte to string as it's a fmt.Sprintf responsibility. Signed-off-by: Anton Tiurin --- daemon/logger/syslog/syslog.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/logger/syslog/syslog.go b/daemon/logger/syslog/syslog.go index bbd209090..8cd50ae9e 100644 --- a/daemon/logger/syslog/syslog.go +++ b/daemon/logger/syslog/syslog.go @@ -28,7 +28,7 @@ func New(tag string) (logger.Logger, error) { } func (s *Syslog) Log(msg *logger.Message) error { - logMessage := fmt.Sprintf("%s: %s", s.tag, string(msg.Line)) + logMessage := fmt.Sprintf("%s: %s", s.tag, msg.Line) if msg.Source == "stderr" { return s.writer.Err(logMessage) } From 5d70a97b1fff8286220d2bef9ceb248401f046d1 Mon Sep 17 00:00:00 2001 From: Srini Brahmaroutu Date: Thu, 26 Feb 2015 19:20:32 +0000 Subject: [PATCH 036/999] Fix the TestPullImageFromCentralRegistry to skip and add local v1 registry test when net=none Closes #10966 Signed-off-by: Srini Brahmaroutu --- Dockerfile | 3 ++ hack/make/.ensure-registry | 10 ++++++ hack/make/.integration-daemon-start | 1 + hack/make/test-integration-cli | 1 + integration-cli/docker_cli_pull_test.go | 29 +++++++++++++++ integration-cli/docker_test_vars.go | 3 +- integration-cli/registry.go | 47 +++++++++++++++++++++++++ 7 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 hack/make/.ensure-registry diff --git a/Dockerfile b/Dockerfile index b06407613..7d14be824 100644 --- a/Dockerfile +++ b/Dockerfile @@ -164,6 +164,9 @@ RUN set -x \ && (cd /go/src/github.com/BurntSushi/toml && git checkout -q $TOMLV_COMMIT) \ && go install -v github.com/BurntSushi/toml/cmd/tomlv +COPY contrib/download-frozen-image.sh /go/src/github.com/docker/docker/contrib/ +RUN ./contrib/download-frozen-image.sh ./integration-cli/registry registry + # Wrap all commands in the "docker-in-docker" script to allow nested containers ENTRYPOINT ["hack/dind"] diff --git a/hack/make/.ensure-registry b/hack/make/.ensure-registry new file mode 100644 index 000000000..f4dec2ee4 --- /dev/null +++ b/hack/make/.ensure-registry @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +if ! docker inspect registry > /dev/null; then + if [ -d /docker-registry ]; then + ( set -x; docker build -t registry /docker-registry ) + else + ( set -x; tar -cC integration-cli/registry . | docker load & ) + fi +fi diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start index 570c6c7a9..e4f9762aa 100644 --- a/hack/make/.integration-daemon-start +++ b/hack/make/.integration-daemon-start @@ -19,6 +19,7 @@ if [ -z "$DOCKER_TEST_HOST" ]; then export DOCKER_HOST="unix://$(cd "$DEST" && pwd)/docker.sock" # "pwd" tricks to make sure $DEST is an absolute path, not a relative one ( set -x; exec \ docker --daemon --debug \ + --insecure-registry 0.0.0.0:5000 \ --host "$DOCKER_HOST" \ --storage-driver "$DOCKER_GRAPHDRIVER" \ --exec-driver "$DOCKER_EXECDRIVER" \ diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli index 3ef41d919..23058ccfd 100644 --- a/hack/make/test-integration-cli +++ b/hack/make/test-integration-cli @@ -18,6 +18,7 @@ bundle_test_integration_cli() { if ! { source "$(dirname "$BASH_SOURCE")/.ensure-frozen-images" source "$(dirname "$BASH_SOURCE")/.ensure-httpserver" + source "$(dirname "$BASH_SOURCE")/.ensure-registry" source "$(dirname "$BASH_SOURCE")/.ensure-emptyfs" bundle_test_integration_cli diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index 39b0eae3f..efecbdf01 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -85,6 +85,8 @@ func TestPullVerified(t *testing.T) { // pulling an image from the central registry should work func TestPullImageFromCentralRegistry(t *testing.T) { + testRequires(t, Network) + defer deleteImages("hello-world") pullCmd := exec.Command(dockerBinary, "pull", "hello-world") @@ -94,6 +96,33 @@ func TestPullImageFromCentralRegistry(t *testing.T) { logDone("pull - pull hello-world") } +// pulling an image from the local registry should work +func TestPullImageFromlocalRegistry(t *testing.T) { + defer deleteAllContainers() + + if err := startRegistryV1(); err != nil { + t.Fatal(err) + } + repoName := privateV1RegistryURL + defer deleteImages(repoName) + + repo := fmt.Sprintf("%v/%v:%v", repoName, "busybox", "latest") + if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repo)); err != nil { + t.Fatalf("Failed to tag image %v: error %v, output %q", repo, err, out) + } + defer deleteImages(repo) + + if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "push", repo)); err != nil { + t.Fatalf("Failed to push image %v: error %v, output %q", repo, err, string(out)) + } + + pullCmd := exec.Command(dockerBinary, "pull", repo) + if out, _, err := runCommandWithOutput(pullCmd); err != nil { + t.Fatalf("pulling the hello-world image from the registry has failed: %s, %v", out, err) + } + logDone("pull - pull local hello-world") +} + // 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") diff --git a/integration-cli/docker_test_vars.go b/integration-cli/docker_test_vars.go index ff2ec7406..a46b2ce64 100644 --- a/integration-cli/docker_test_vars.go +++ b/integration-cli/docker_test_vars.go @@ -14,7 +14,8 @@ var ( registryImageName = "registry" // the private registry to use for tests - privateRegistryURL = "127.0.0.1:5000" + privateRegistryURL = "127.0.0.1:5000" + privateV1RegistryURL = "0.0.0.0:5000" dockerBasePath = "/var/lib/docker" execDriverPath = dockerBasePath + "/execdriver/native" diff --git a/integration-cli/registry.go b/integration-cli/registry.go index 8290e710f..32ed5a7b8 100644 --- a/integration-cli/registry.go +++ b/integration-cli/registry.go @@ -7,7 +7,9 @@ import ( "os" "os/exec" "path/filepath" + "strings" "testing" + "time" ) const v2binary = "registry-v2" @@ -69,3 +71,48 @@ func (r *testRegistryV2) Close() { r.cmd.Process.Kill() os.RemoveAll(r.dir) } + +func pingV1(ip string) error { + // We always ping through HTTP for our test registry. + resp, err := http.Get(fmt.Sprintf("http://%s/v1/search", ip)) + if err != nil { + return err + } + if resp.StatusCode != 200 { + return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode) + } + return nil +} + +func startRegistryV1() error { + //wait for registry image to be available + for i := 0; i < 10; i++ { + imagesCmd := exec.Command(dockerBinary, "images") + out, _, err := runCommandWithOutput(imagesCmd) + if err != nil { + return err + } + if strings.Contains(out, "registry") { + break + } + time.Sleep(60000 * time.Millisecond) + if i == 10 { + fmt.Errorf("No registry image is found to start the regictry V1 services") + } + } + + if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "regserver", "-d", "-p", "5000:5000", "registry", "docker-registry")); err != nil { + fmt.Errorf("Failed to start registry: error %v, output %q", err, out) + } + ip := privateV1RegistryURL + //wait until registry server is available + for i := 0; i < 10; i++ { + if err := pingV1(ip); err == nil { + return nil + } else if i == 10 && err != nil { + return err + } + time.Sleep(2000 * time.Millisecond) + } + return nil +} From fe1f5ac77cda335ee773956cc8683a0c31791c3c Mon Sep 17 00:00:00 2001 From: Natalie Parker Date: Mon, 23 Mar 2015 20:15:29 +0000 Subject: [PATCH 037/999] Added missing code example in the RM command reference of command to delete all stopped containers Signed-off-by: Natalie Parker --- docs/sources/reference/commandline/cli.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 6c3eb1c8d..30a8a6b42 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1661,6 +1661,8 @@ containers removing all network communication. The main process inside the container referenced under the link `/redis` will receive `SIGKILL`, then the container will be removed. + $ docker rm $(docker ps -a -q) + This command will delete all stopped containers. The command `docker ps -a -q` will return all existing container IDs and pass them to the `rm` command which will delete them. Any running containers will not be From 0c3d2f6f9658ae9b1e7c7cc6f7fda730d4b04898 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 23 Mar 2015 22:03:54 +0100 Subject: [PATCH 038/999] Return ContainerExecCreateResponse from container exec start API endpoint, Fixes #11613 Signed-off-by: Antonio Murdaca --- api/client/commands.go | 9 ++++++--- api/server/server.go | 17 +++++++++++++---- api/types/types.go | 9 +++++++++ 3 files changed, 28 insertions(+), 7 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 676db9dc9..a7cf9122f 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -2677,12 +2677,15 @@ func (cli *DockerCli) CmdExec(args ...string) error { return err } - var execResult engine.Env - if err := execResult.Decode(stream); err != nil { + var response types.ContainerExecCreateResponse + if err := json.NewDecoder(stream).Decode(&response); err != nil { return err } + for _, warning := range response.Warnings { + fmt.Fprintf(cli.err, "WARNING: %s\n", warning) + } - execID := execResult.Get("Id") + execID := response.ID if execID == "" { fmt.Fprintf(cli.out, "exec ID empty") diff --git a/api/server/server.go b/api/server/server.go index a1c57a8e4..1b9c5562f 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1155,10 +1155,11 @@ func postContainerExecCreate(eng *engine.Engine, version version.Version, w http return nil } var ( - out engine.Env name = vars["name"] job = eng.Job("execCreate", name) stdoutBuffer = bytes.NewBuffer(nil) + outWarnings []string + warnings = bytes.NewBuffer(nil) ) if err := job.DecodeEnv(r.Body); err != nil { @@ -1166,15 +1167,23 @@ func postContainerExecCreate(eng *engine.Engine, version version.Version, w http } job.Stdout.Add(stdoutBuffer) + // Read warnings from stderr + job.Stderr.Add(warnings) // Register an instance of Exec in container. if err := job.Run(); err != nil { fmt.Fprintf(os.Stderr, "Error setting up exec command in container %s: %s\n", name, err) return err } - // Return the ID - out.Set("Id", engine.Tail(stdoutBuffer, 1)) + // Parse warnings from stderr + scanner := bufio.NewScanner(warnings) + for scanner.Scan() { + outWarnings = append(outWarnings, scanner.Text()) + } - return writeJSONEnv(w, http.StatusCreated, out) + return writeJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{ + ID: engine.Tail(stdoutBuffer, 1), + Warnings: outWarnings, + }) } // TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. diff --git a/api/types/types.go b/api/types/types.go index f1b1d041e..5531135b1 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -9,3 +9,12 @@ type ContainerCreateResponse struct { // Warnings are any warnings encountered during the creation of the container. Warnings []string `json:"Warnings"` } + +// POST /containers/{name:.*}/exec +type ContainerExecCreateResponse struct { + // ID is the exec ID. + ID string `json:"Id"` + + // Warnings are any warnings encountered during the execution of the command. + Warnings []string `json:"Warnings"` +} From 841692ff864c0d71347607eb8e4fbcf826d859c0 Mon Sep 17 00:00:00 2001 From: Jamshid Afshar Date: Mon, 23 Mar 2015 19:00:05 -0500 Subject: [PATCH 039/999] correcting git fetch command Signed-off-by: Jamshid Afshar --- docs/sources/project/find-an-issue.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/project/find-an-issue.md b/docs/sources/project/find-an-issue.md index 0a36c8833..2b3396e6e 100644 --- a/docs/sources/project/find-an-issue.md +++ b/docs/sources/project/find-an-issue.md @@ -166,7 +166,7 @@ To sync your repository: 5. Fetch all the changes from the `upstream/master` branch. - $ git fetch upstream/master + $ git fetch upstream remote: Counting objects: 141, done. remote: Compressing objects: 100% (29/29), done. remote: Total 141 (delta 52), reused 46 (delta 46), pack-reused 66 From 3d28fc7d1c92456b226e1169bf3f44dcd9154b71 Mon Sep 17 00:00:00 2001 From: John Willis Date: Mon, 23 Mar 2015 21:00:02 -0400 Subject: [PATCH 040/999] #11465 Add additional doc for locagi registries onn pull command Signed-off-by: John Willis --- docs/sources/reference/commandline/cli.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 30a8a6b42..2c00b4f21 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1569,7 +1569,7 @@ This shows all the containers that have exited with status of '0' ## pull - Usage: docker pull [OPTIONS] NAME[:TAG] + Usage: docker pull [OPTIONS] NAME[:TAG] | [REGISTRY_HOST[:REGISTRY_PORT]/]NAME[:TAG] Pull an image or a repository from the registry @@ -1605,6 +1605,7 @@ use `docker pull`: $ sudo docker pull registry.hub.docker.com/debian # manually specifies the path to the default Docker registry. This could # be replaced with the path to a local registry to pull from another source. + # sudo docker pull myhub.com:8080/test-image ## push From dabd8a02aeff8b2122efa0b55de4ba52c429f936 Mon Sep 17 00:00:00 2001 From: Alena Prokharchyk Date: Mon, 23 Mar 2015 18:46:24 -0700 Subject: [PATCH 041/999] Removed unused "mutex" field fixes #11659 Signed-off-by: Alena Prokharchyk --- daemon/logger/syslog/syslog.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/daemon/logger/syslog/syslog.go b/daemon/logger/syslog/syslog.go index 8cd50ae9e..afd3dacbb 100644 --- a/daemon/logger/syslog/syslog.go +++ b/daemon/logger/syslog/syslog.go @@ -5,7 +5,6 @@ import ( "log/syslog" "os" "path" - "sync" "github.com/docker/docker/daemon/logger" ) @@ -13,7 +12,6 @@ import ( type Syslog struct { writer *syslog.Writer tag string - mu sync.Mutex } func New(tag string) (logger.Logger, error) { From b21751da907cb2889aa9ca95ebf6c4c66e6131a6 Mon Sep 17 00:00:00 2001 From: Chris Khoo Date: Mon, 23 Mar 2015 19:21:31 -0700 Subject: [PATCH 042/999] fix broken prerequisite link Signed-off-by: Chris Khoo --- docs/sources/project/set-up-dev-env.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/project/set-up-dev-env.md b/docs/sources/project/set-up-dev-env.md index 637eef6f5..9b767ad64 100644 --- a/docs/sources/project/set-up-dev-env.md +++ b/docs/sources/project/set-up-dev-env.md @@ -15,7 +15,7 @@ You use the `docker` repository and its `Dockerfile` to create a Docker image, run a Docker container, and develop code in the container. Docker itself builds, tests, and releases new Docker versions using this container. -If you followed the procedures that +If you followed the procedures that set up the prerequisites, you should have a fork of the `docker/docker` repository. You also created a branch called `dry-run-test`. In this section, you continue working with your fork on this branch. From 6dba2d01b5198b43a2fab80176cf2888656c6b56 Mon Sep 17 00:00:00 2001 From: willhf Date: Mon, 23 Mar 2015 19:50:33 -0700 Subject: [PATCH 043/999] Add test for net=container and links Signed-off-by: willhf --- runconfig/parse.go | 2 +- runconfig/parse_test.go | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/runconfig/parse.go b/runconfig/parse.go index ccd8056cf..cf4d4003a 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -125,7 +125,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe return nil, nil, cmd, ErrConflictHostNetworkAndLinks } - if *flNetMode == "container" && flLinks.Len() > 0 { + if strings.HasPrefix(*flNetMode, "container") && flLinks.Len() > 0 { return nil, nil, cmd, ErrConflictContainerNetworkAndLinks } diff --git a/runconfig/parse_test.go b/runconfig/parse_test.go index cd90dc3a9..6c0a1cfc6 100644 --- a/runconfig/parse_test.go +++ b/runconfig/parse_test.go @@ -57,3 +57,9 @@ func TestNetHostname(t *testing.T) { t.Fatalf("Expected error ErrConflictNetworkHostname, got: %s", err) } } + +func TestConflictContainerNetworkAndLinks(t *testing.T) { + if _, _, _, err := parseRun([]string{"--net=container:other", "--link=zip:zap", "img", "cmd"}); err != ErrConflictContainerNetworkAndLinks { + t.Fatalf("Expected error ErrConflictContainerNetworkAndLinks, got: %s", err) + } +} From ef0275c66c420944dd3de4647d2d25b78f8f1b5a Mon Sep 17 00:00:00 2001 From: Mark West Date: Mon, 23 Mar 2015 20:16:40 -0700 Subject: [PATCH 044/999] RE: Issue #6114. Updated docs to reflect docker inpsect for volumes Signed-off-by: Mark West --- docs/sources/userguide/dockervolumes.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/sources/userguide/dockervolumes.md b/docs/sources/userguide/dockervolumes.md index d53322465..af4a7297f 100644 --- a/docs/sources/userguide/dockervolumes.md +++ b/docs/sources/userguide/dockervolumes.md @@ -52,6 +52,27 @@ This will create a new volume inside a container at `/webapp`. > You can also use the `VOLUME` instruction in a `Dockerfile` to add one or > more new volumes to any container created from that image. +### Locating a volume + +You can locate the volume on the host by utilizing the 'docker inspect' command. + + $ docker inspect web + +The output will provide details on the container configurations including the +volumes. The output should look something similar to the following: + + ... + "Volumes": { + "/webapp": "/var/lib/docker/volumes/fac362...80535" + }, + "VolumesRW": { + "/webapp": true + } + ... + +You will notice in the above 'Volumes' is specifying the location on the host and +'VolumesRW' is specifying that the volume is read/write. + ### Mount a Host Directory as a Data Volume In addition to creating a volume using the `-v` flag you can also mount a From c136591f40c34de51bbb0691ae69f393ff8b2f0e Mon Sep 17 00:00:00 2001 From: Avi Das Date: Mon, 23 Mar 2015 20:30:09 -0500 Subject: [PATCH 045/999] Add comments to api/common constants, closes #11583 Signed-off-by: Avi Das --- api/common.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/api/common.go b/api/common.go index f6a0bc488..a0f44e860 100644 --- a/api/common.go +++ b/api/common.go @@ -14,11 +14,12 @@ import ( "github.com/docker/libtrust" ) +// Common constants for daemon and client. const ( - APIVERSION version.Version = "1.18" - DEFAULTHTTPHOST = "127.0.0.1" - DEFAULTUNIXSOCKET = "/var/run/docker.sock" - DefaultDockerfileName string = "Dockerfile" + APIVERSION version.Version = "1.18" // Current REST API version + DEFAULTHTTPHOST = "127.0.0.1" // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080 + DEFAULTUNIXSOCKET = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket + DefaultDockerfileName string = "Dockerfile" // Default filename with Docker commands, read by docker build ) func ValidateHost(val string) (string, error) { From 6fa6b5bcbb707a1fe709c5272035b79635c29a78 Mon Sep 17 00:00:00 2001 From: Swapnil Daingade Date: Mon, 23 Mar 2015 23:42:26 -0400 Subject: [PATCH 046/999] Fixes error #11683 Signed-off-by: Swapnil Daingade --- docs/sources/userguide/usingdocker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/userguide/usingdocker.md b/docs/sources/userguide/usingdocker.md index 8d57def4e..fd5f52a37 100644 --- a/docs/sources/userguide/usingdocker.md +++ b/docs/sources/userguide/usingdocker.md @@ -298,7 +298,7 @@ and won't need it again. So let's remove it using the `docker rm` command. Error: Impossible to remove a running container, please stop it first or use -f 2014/05/24 08:12:56 Error: failed to remove one or more containers -What's happened? We can't actually remove a running container. This protects +What happened? We can't actually remove a running container. This protects you from accidentally removing a running container you might need. Let's try this again by stopping the container first. From a57d7c5c796a1affcfa05ee6e7ea586750a9292f Mon Sep 17 00:00:00 2001 From: Dan Anolik Date: Mon, 23 Mar 2015 20:47:04 -0700 Subject: [PATCH 047/999] Added documentation for specifying groupname or GID for commands. Also clarified used of the possible use of multiple USER commands in a Dockerfile. Signed-off-by: Dan Anolik --- docs/man/Dockerfile.5.md | 11 +++++++++-- docs/man/docker-run.1.md | 7 ++++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/docs/man/Dockerfile.5.md b/docs/man/Dockerfile.5.md index 7f884888e..0ec54a8c9 100644 --- a/docs/man/Dockerfile.5.md +++ b/docs/man/Dockerfile.5.md @@ -273,8 +273,15 @@ A Dockerfile is similar to a Makefile. **USER** -- `USER daemon` - The **USER** instruction sets the username or UID that is used when running the - image. + Sets the username or UID used for running subsequent commands. + + The **USER** instruction can optionally be used to set the group or GID. The + followings examples are all valid: + USER [user | user:group | uid | uid:gid | user:gid | uid:group ] + + Until the **USER** instruction is set, instructions will be run as root. The USER + instruction can be used any number of times in a Dockerfile, and will only affect + subsequent commands. **WRKDIR** -- `WORKDIR /path/to/workdir` diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 1831237de..9ac571738 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -341,7 +341,12 @@ The **-t** option is incompatible with a redirection of the docker client standard input. **-u**, **--user**="" - Username or UID + Sets the username or UID used and optionally the groupname or GID for the specified command. + + The followings examples are all valid: + --user [user | user:group | uid | uid:gid | user:gid | uid:group ] + + Without this argument the command will be run as root in the container. **-v**, **--volume**=[] Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container) From df98ce0a28c6d38757305dccc1acb4b8989e6aff Mon Sep 17 00:00:00 2001 From: Chris Khoo Date: Mon, 23 Mar 2015 20:58:51 -0700 Subject: [PATCH 048/999] fix test-and-docs typo "do" to "due" Signed-off-by: Chris Khoo --- docs/sources/project/test-and-docs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/project/test-and-docs.md b/docs/sources/project/test-and-docs.md index d586ea2c3..cef3cae8e 100644 --- a/docs/sources/project/test-and-docs.md +++ b/docs/sources/project/test-and-docs.md @@ -169,7 +169,7 @@ To run the same test inside your Docker development container, you do this: root@5f8630b873fe:/go/src/github.com/docker/docker# TESTFLAGS='-run ^TestBuild$' hack/make.sh -## If test under Boot2Docker fail do to space errors +## If tests under Boot2Docker fail due to disk space errors Running the tests requires about 2GB of memory. If you are running your container on bare metal, that is you are not running with Boot2Docker, your From 0f9c20fe688e0bc093f631b7d31badc4bb750bd7 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Tue, 24 Mar 2015 13:45:16 +0800 Subject: [PATCH 049/999] docs: add memory and swap memory usage examples fix: https://github.com/docker/docker/issues/11629 Signed-off-by: Qiang Huang --- docs/sources/reference/run.md | 45 +++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 052a35823..96e826b7e 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -426,23 +426,23 @@ the `--security-opt` flag. For example, you can specify the MCS/MLS level, a requirement for MLS systems. Specifying the level in the following command allows you to share the same content between containers. - # docker run --security-opt label:level:s0:c100,c200 -i -t fedora bash + $ sudo docker run --security-opt label:level:s0:c100,c200 -i -t fedora bash An MLS example might be: - # docker run --security-opt label:level:TopSecret -i -t rhel7 bash + $ sudo docker run --security-opt label:level:TopSecret -i -t rhel7 bash To disable the security labeling for this container versus running with the `--permissive` flag, use the following command: - # docker run --security-opt label:disable -i -t fedora bash + $ sudo docker run --security-opt label:disable -i -t fedora bash If you want a tighter security policy on the processes within a container, you can specify an alternate type for the container. You could run a container that is only allowed to listen on Apache ports by executing the following command: - # docker run --security-opt label:type:svirt_apache_t -i -t centos bash + $ sudo docker run --security-opt label:type:svirt_apache_t -i -t centos bash Note: @@ -455,7 +455,7 @@ container: -m="": Memory limit (format: , where unit = b, k, m or g) -memory-swap="": Total memory limit (memory + swap, format: , where unit = b, k, m or g) - -c, --cpu-shares=0 CPU shares (relative weight) + -c, --cpu-shares=0: CPU shares (relative weight) ### Memory constraints @@ -507,6 +507,31 @@ We have four ways to set memory usage: +Examples: + + $ sudo docker run -ti ubuntu:14.04 /bin/bash + +We set nothing about memory, this means the processes in the container can use +as much memory and swap memory as they need. + + $ sudo docker run -ti -m 300M --memory-swap -1 ubuntu:14.04 /bin/bash + +We set memory limit and disabled swap memory limit, this means the processes in +the container can use 300M memory and as much swap memory as they need (if the +host supports swap memory). + + $ sudo docker run -ti -m 300M ubuntu:14.04 /bin/bash + +We set memory limit only, this means the processes in the container can use +300M memory and 300M swap memory, by default, the total virtual memory size +(--memory-swap) will be set as double of memory, in this case, memory + swap +would be 2*300M, so processes can use 300M swap memory as well. + + $ sudo docker run -ti -m 300M --memory-swap 1G ubuntu:14.04 /bin/bash + +We set both memory and swap memory, so the processes in the container can use +300M memory and 700M swap memory. + ### CPU share constraint By default, all containers get the same proportion of CPU cycles. This proportion @@ -598,18 +623,18 @@ operator wants to have all capabilities but `MKNOD` they could use: For interacting with the network stack, instead of using `--privileged` they should use `--cap-add=NET_ADMIN` to modify the network interfaces. - $ docker run -t -i --rm ubuntu:14.04 ip link add dummy0 type dummy + $ sudo docker run -t -i --rm ubuntu:14.04 ip link add dummy0 type dummy RTNETLINK answers: Operation not permitted - $ docker run -t -i --rm --cap-add=NET_ADMIN ubuntu:14.04 ip link add dummy0 type dummy + $ sudo docker run -t -i --rm --cap-add=NET_ADMIN ubuntu:14.04 ip link add dummy0 type dummy To mount a FUSE based filesystem, you need to combine both `--cap-add` and `--device`: - $ docker run --rm -it --cap-add SYS_ADMIN sshfs sshfs sven@10.10.10.20:/home/sven /mnt + $ sudo docker run --rm -it --cap-add SYS_ADMIN sshfs sshfs sven@10.10.10.20:/home/sven /mnt fuse: failed to open /dev/fuse: Operation not permitted - $ docker run --rm -it --device /dev/fuse sshfs sshfs sven@10.10.10.20:/home/sven /mnt + $ sudo docker run --rm -it --device /dev/fuse sshfs sshfs sven@10.10.10.20:/home/sven /mnt fusermount: mount failed: Operation not permitted - $ docker run --rm -it --cap-add SYS_ADMIN --device /dev/fuse sshfs + $ sudo docker run --rm -it --cap-add SYS_ADMIN --device /dev/fuse sshfs # sshfs sven@10.10.10.20:/home/sven /mnt The authenticity of host '10.10.10.20 (10.10.10.20)' can't be established. ECDSA key fingerprint is 25:34:85:75:25:b0:17:46:05:19:04:93:b5:dd:5f:c6. From ed0d2ac3b799b8f7b2508176e65bfe8583881c83 Mon Sep 17 00:00:00 2001 From: Jesse Dearing Date: Mon, 23 Mar 2015 20:48:12 -0700 Subject: [PATCH 050/999] Add documentation about the semantics of `docker login` Fixes #10550 Signed-off-by: Jesse Dearing --- docs/man/docker-login.1.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/man/docker-login.1.md b/docs/man/docker-login.1.md index e3614cce4..5ff9403a8 100644 --- a/docs/man/docker-login.1.md +++ b/docs/man/docker-login.1.md @@ -17,6 +17,9 @@ Register or Login to a docker registry server, if no server is specified "https://index.docker.io/v1/" is the default. If you want to login to a private registry you can specify this by adding the server name. +This stores encoded credentials in `$HOME/.dockercfg` on Linux or `%USERPROFILE%/.dockercfg` +on Windows. + # OPTIONS **-e**, **--email**="" Email From babd1b3e1fd7be7674f6e96f264b1b841aeba3b9 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 23 Mar 2015 23:32:50 +0100 Subject: [PATCH 051/999] Return AuthResponse from postAuth api endpoint, Fixes #11607 Signed-off-by: Antonio Murdaca --- api/client/commands.go | 11 ++++++----- api/server/server.go | 4 +++- api/types/types.go | 6 ++++++ 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/api/client/commands.go b/api/client/commands.go index 5689dfd2c..d5dfb2a37 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -450,17 +450,18 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if err != nil { return err } - var out2 engine.Env - err = out2.Decode(stream) - if err != nil { + + var response types.AuthResponse + if err := json.NewDecoder(stream).Decode(response); err != nil { cli.configFile, _ = registry.LoadConfig(homedir.Get()) return err } + registry.SaveConfig(cli.configFile) fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s.\n", path.Join(homedir.Get(), registry.CONFIGFILE)) - if out2.Get("Status") != "" { - fmt.Fprintf(cli.out, "%s\n", out2.Get("Status")) + if response.Status != "" { + fmt.Fprintf(cli.out, "%s\n", response.Status) } return nil } diff --git a/api/server/server.go b/api/server/server.go index 1b9c5562f..a11a4fd9f 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -192,7 +192,9 @@ func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter if status := engine.Tail(stdoutBuffer, 1); status != "" { var env engine.Env env.Set("Status", status) - return writeJSONEnv(w, http.StatusOK, env) + return writeJSON(w, http.StatusOK, &types.AuthResponse{ + Status: status, + }) } w.WriteHeader(http.StatusNoContent) return nil diff --git a/api/types/types.go b/api/types/types.go index 5531135b1..21dba7729 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -18,3 +18,9 @@ type ContainerExecCreateResponse struct { // Warnings are any warnings encountered during the execution of the command. Warnings []string `json:"Warnings"` } + +// POST /auth +type AuthResponse struct { + // Status is the authentication status + Status string `json:"Status"` +} From a5cbb5c3aec0d5c717581b2b60b9ae89b2c6fd05 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Tue, 24 Mar 2015 18:48:08 +0800 Subject: [PATCH 052/999] add cpuset and examples to run.md Signed-off-by: Qiang Huang --- docs/sources/reference/run.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 96e826b7e..69147b2e1 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -448,14 +448,15 @@ Note: You would have to write policy defining a `svirt_apache_t` type. -## Runtime constraints on CPU and memory +## Runtime constraints on resources The operator can also adjust the performance parameters of the container: - -m="": Memory limit (format: , where unit = b, k, m or g) + -m, --memory="": Memory limit (format: , where unit = b, k, m or g) -memory-swap="": Total memory limit (memory + swap, format: , where unit = b, k, m or g) -c, --cpu-shares=0: CPU shares (relative weight) + --cpuset-cpus="": CPUs in which to allow execution (0-3, 0,1) ### Memory constraints @@ -567,6 +568,20 @@ division of CPU shares: 101 {C1} 1 100% of CPU1 102 {C1} 2 100% of CPU2 +### Cpuset constraint + +We can set cpus in which to allow execution for containers. + +Examples: + + $ sudo docker run -ti --cpuset-cpus="1,3" ubuntu:14.04 /bin/bash + +This means processes in container can be executed on cpu 1 and cpu 3. + + $ sudo docker run -ti --cpuset-cpus="0-2" ubuntu:14.04 /bin/bash + +This means processes in container can be executed on cpu 0, cpu 1 and cpu 2. + ## Runtime privilege, Linux capabilities, and LXC configuration --cap-add: Add Linux capabilities From 87df5ab41b3f9b2bdd8682f4afd41dac78f7e96d Mon Sep 17 00:00:00 2001 From: Paul Bellamy Date: Tue, 24 Mar 2015 10:29:30 +0000 Subject: [PATCH 053/999] Refactor global portallocator and portmapper state Continuation of: #11660, working on issue #11626. Wrapped portmapper global state into a struct. Now portallocator and portmapper have no global state (except configuration, and a default instance). Unfortunately, removing the global default instances will break ```api/server/server.go:1539```, and ```daemon/daemon.go:832```, which both call the global portallocator directly. Fixing that would be a much bigger change, so for now, have postponed that. Signed-off-by: Paul Bellamy --- .../portallocator/portallocator.go | 26 +++--- daemon/networkdriver/portmapper/mapper.go | 80 ++++++++++++------- .../networkdriver/portmapper/mapper_test.go | 37 ++++----- 3 files changed, 76 insertions(+), 67 deletions(-) diff --git a/daemon/networkdriver/portallocator/portallocator.go b/daemon/networkdriver/portallocator/portallocator.go index a7c183d9d..01533419b 100644 --- a/daemon/networkdriver/portallocator/portallocator.go +++ b/daemon/networkdriver/portallocator/portallocator.go @@ -50,8 +50,12 @@ var ( ) var ( - defaultIP = net.ParseIP("0.0.0.0") - defaultPortAllocator = New() + defaultIP = net.ParseIP("0.0.0.0") + + DefaultPortAllocator = New() + RequestPort = DefaultPortAllocator.RequestPort + ReleasePort = DefaultPortAllocator.ReleasePort + ReleaseAll = DefaultPortAllocator.ReleaseAll ) type PortAllocator struct { @@ -119,6 +123,9 @@ func (e ErrPortAlreadyAllocated) Error() string { return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port) } +// RequestPort requests new port from global ports pool for specified ip and proto. +// If port is 0 it returns first free port. Otherwise it cheks port availability +// in pool and return that port or error if port is already busy. func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, error) { p.mutex.Lock() defer p.mutex.Unlock() @@ -152,13 +159,6 @@ func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, err return port, nil } -// RequestPort requests new port from global ports pool for specified ip and proto. -// If port is 0 it returns first free port. Otherwise it cheks port availability -// in pool and return that port or error if port is already busy. -func RequestPort(ip net.IP, proto string, port int) (int, error) { - return defaultPortAllocator.RequestPort(ip, proto, port) -} - // ReleasePort releases port from global ports pool for specified ip and proto. func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error { p.mutex.Lock() @@ -175,10 +175,6 @@ func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error { return nil } -func ReleasePort(ip net.IP, proto string, port int) error { - return defaultPortAllocator.ReleasePort(ip, proto, port) -} - // ReleaseAll releases all ports for all ips. func (p *PortAllocator) ReleaseAll() error { p.mutex.Lock() @@ -187,10 +183,6 @@ func (p *PortAllocator) ReleaseAll() error { return nil } -func ReleaseAll() error { - return defaultPortAllocator.ReleaseAll() -} - func (pm *portMap) findPort() (int, error) { port := pm.last for i := 0; i <= endPortRange-beginPortRange; i++ { diff --git a/daemon/networkdriver/portmapper/mapper.go b/daemon/networkdriver/portmapper/mapper.go index 9f2ca5a75..74b329e2f 100644 --- a/daemon/networkdriver/portmapper/mapper.go +++ b/daemon/networkdriver/portmapper/mapper.go @@ -19,13 +19,12 @@ type mapping struct { } var ( - chain *iptables.Chain - lock sync.Mutex - - // udp:ip:port - currentMappings = make(map[string]*mapping) - NewProxy = NewProxyCommand + + DefaultPortMapper = NewWithPortAllocator(portallocator.DefaultPortAllocator) + SetIptablesChain = DefaultPortMapper.SetIptablesChain + Map = DefaultPortMapper.Map + Unmap = DefaultPortMapper.Unmap ) var ( @@ -34,13 +33,34 @@ var ( ErrPortNotMapped = errors.New("port is not mapped") ) -func SetIptablesChain(c *iptables.Chain) { - chain = c +type PortMapper struct { + chain *iptables.Chain + + // udp:ip:port + currentMappings map[string]*mapping + lock sync.Mutex + + allocator *portallocator.PortAllocator } -func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) { - lock.Lock() - defer lock.Unlock() +func New() *PortMapper { + return NewWithPortAllocator(portallocator.New()) +} + +func NewWithPortAllocator(allocator *portallocator.PortAllocator) *PortMapper { + return &PortMapper{ + currentMappings: make(map[string]*mapping), + allocator: allocator, + } +} + +func (pm *PortMapper) SetIptablesChain(c *iptables.Chain) { + pm.chain = c +} + +func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) { + pm.lock.Lock() + defer pm.lock.Unlock() var ( m *mapping @@ -52,7 +72,7 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er switch container.(type) { case *net.TCPAddr: proto = "tcp" - if allocatedHostPort, err = portallocator.RequestPort(hostIP, proto, hostPort); err != nil { + if allocatedHostPort, err = pm.allocator.RequestPort(hostIP, proto, hostPort); err != nil { return nil, err } @@ -65,7 +85,7 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port) case *net.UDPAddr: proto = "udp" - if allocatedHostPort, err = portallocator.RequestPort(hostIP, proto, hostPort); err != nil { + if allocatedHostPort, err = pm.allocator.RequestPort(hostIP, proto, hostPort); err != nil { return nil, err } @@ -83,25 +103,25 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er // release the allocated port on any further error during return. defer func() { if err != nil { - portallocator.ReleasePort(hostIP, proto, allocatedHostPort) + pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort) } }() key := getKey(m.host) - if _, exists := currentMappings[key]; exists { + if _, exists := pm.currentMappings[key]; exists { return nil, ErrPortMappedForIP } containerIP, containerPort := getIPAndPort(m.container) - if err := forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil { + if err := pm.forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil { return nil, err } cleanup := func() error { // need to undo the iptables rules before we return proxy.Stop() - forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort) - if err := portallocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil { + pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort) + if err := pm.allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil { return err } @@ -115,35 +135,35 @@ func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err er return nil, err } m.userlandProxy = proxy - currentMappings[key] = m + pm.currentMappings[key] = m return m.host, nil } -func Unmap(host net.Addr) error { - lock.Lock() - defer lock.Unlock() +func (pm *PortMapper) Unmap(host net.Addr) error { + pm.lock.Lock() + defer pm.lock.Unlock() key := getKey(host) - data, exists := currentMappings[key] + data, exists := pm.currentMappings[key] if !exists { return ErrPortNotMapped } data.userlandProxy.Stop() - delete(currentMappings, key) + delete(pm.currentMappings, key) containerIP, containerPort := getIPAndPort(data.container) hostIP, hostPort := getIPAndPort(data.host) - if err := forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { + if err := pm.forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { log.Errorf("Error on iptables delete: %s", err) } switch a := host.(type) { case *net.TCPAddr: - return portallocator.ReleasePort(a.IP, "tcp", a.Port) + return pm.allocator.ReleasePort(a.IP, "tcp", a.Port) case *net.UDPAddr: - return portallocator.ReleasePort(a.IP, "udp", a.Port) + return pm.allocator.ReleasePort(a.IP, "udp", a.Port) } return nil } @@ -168,9 +188,9 @@ func getIPAndPort(a net.Addr) (net.IP, int) { return nil, 0 } -func forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error { - if chain == nil { +func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error { + if pm.chain == nil { return nil } - return chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort) + return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort) } diff --git a/daemon/networkdriver/portmapper/mapper_test.go b/daemon/networkdriver/portmapper/mapper_test.go index fa7bdecdb..4082a6002 100644 --- a/daemon/networkdriver/portmapper/mapper_test.go +++ b/daemon/networkdriver/portmapper/mapper_test.go @@ -13,30 +13,26 @@ func init() { NewProxy = NewMockProxyCommand } -func reset() { - chain = nil - currentMappings = make(map[string]*mapping) -} - func TestSetIptablesChain(t *testing.T) { - defer reset() + pm := New() c := &iptables.Chain{ Name: "TEST", Bridge: "192.168.1.1", } - if chain != nil { + if pm.chain != nil { t.Fatal("chain should be nil at init") } - SetIptablesChain(c) - if chain == nil { + pm.SetIptablesChain(c) + if pm.chain == nil { t.Fatal("chain should not be nil after set") } } func TestMapPorts(t *testing.T) { + pm := New() dstIp1 := net.ParseIP("192.168.0.1") dstIp2 := net.ParseIP("192.168.0.2") dstAddr1 := &net.TCPAddr{IP: dstIp1, Port: 80} @@ -49,34 +45,34 @@ func TestMapPorts(t *testing.T) { return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String()) } - if host, err := Map(srcAddr1, dstIp1, 80); err != nil { + if host, err := pm.Map(srcAddr1, dstIp1, 80); err != nil { t.Fatalf("Failed to allocate port: %s", err) } else if !addrEqual(dstAddr1, host) { t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s", dstAddr1.String(), dstAddr1.Network(), host.String(), host.Network()) } - if _, err := Map(srcAddr1, dstIp1, 80); err == nil { + if _, err := pm.Map(srcAddr1, dstIp1, 80); err == nil { t.Fatalf("Port is in use - mapping should have failed") } - if _, err := Map(srcAddr2, dstIp1, 80); err == nil { + if _, err := pm.Map(srcAddr2, dstIp1, 80); err == nil { t.Fatalf("Port is in use - mapping should have failed") } - if _, err := Map(srcAddr2, dstIp2, 80); err != nil { + if _, err := pm.Map(srcAddr2, dstIp2, 80); err != nil { t.Fatalf("Failed to allocate port: %s", err) } - if Unmap(dstAddr1) != nil { + if pm.Unmap(dstAddr1) != nil { t.Fatalf("Failed to release port") } - if Unmap(dstAddr2) != nil { + if pm.Unmap(dstAddr2) != nil { t.Fatalf("Failed to release port") } - if Unmap(dstAddr2) == nil { + if pm.Unmap(dstAddr2) == nil { t.Fatalf("Port already released, but no error reported") } } @@ -115,6 +111,7 @@ func TestGetUDPIPAndPort(t *testing.T) { } func TestMapAllPortsSingleInterface(t *testing.T) { + pm := New() dstIp1 := net.ParseIP("0.0.0.0") srcAddr1 := &net.TCPAddr{Port: 1080, IP: net.ParseIP("172.16.0.1")} @@ -124,26 +121,26 @@ func TestMapAllPortsSingleInterface(t *testing.T) { defer func() { for _, val := range hosts { - Unmap(val) + pm.Unmap(val) } }() for i := 0; i < 10; i++ { start, end := portallocator.PortRange() for i := start; i < end; i++ { - if host, err = Map(srcAddr1, dstIp1, 0); err != nil { + if host, err = pm.Map(srcAddr1, dstIp1, 0); err != nil { t.Fatal(err) } hosts = append(hosts, host) } - if _, err := Map(srcAddr1, dstIp1, start); err == nil { + if _, err := pm.Map(srcAddr1, dstIp1, start); err == nil { t.Fatalf("Port %d should be bound but is not", start) } for _, val := range hosts { - if err := Unmap(val); err != nil { + if err := pm.Unmap(val); err != nil { t.Fatal(err) } } From 96d8c3584cedddfc69c01bda3f512d495b21ac47 Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Wed, 25 Mar 2015 00:46:22 +0800 Subject: [PATCH 054/999] Fix minor typo Fix minor typo and make the comments of version-comparison functions uniform. Signed-off-by: Hu Keping --- pkg/version/version.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/version/version.go b/pkg/version/version.go index cc802a654..bd5ec7a83 100644 --- a/pkg/version/version.go +++ b/pkg/version/version.go @@ -37,7 +37,7 @@ func (v Version) compareTo(other Version) int { return 0 } -// LessThan checks if a version is less than another version +// LessThan checks if a version is less than another func (v Version) LessThan(other Version) bool { return v.compareTo(other) == -1 } @@ -47,12 +47,12 @@ func (v Version) LessThanOrEqualTo(other Version) bool { return v.compareTo(other) <= 0 } -// GreaterThan checks if a version is greater than another one +// GreaterThan checks if a version is greater than another func (v Version) GreaterThan(other Version) bool { return v.compareTo(other) == 1 } -// GreaterThanOrEqualTo checks ia version is greater than or equal to another +// GreaterThanOrEqualTo checks if a version is greater than or equal to another func (v Version) GreaterThanOrEqualTo(other Version) bool { return v.compareTo(other) >= 0 } From 7d707360159852dde4e75fc5d4778c72abc44a03 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Tue, 24 Mar 2015 20:15:16 +0800 Subject: [PATCH 055/999] Add some run option to bash completion Signed-off-by: Lei Jitang --- contrib/completion/bash/docker | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index ca874bc10..6b1e62f91 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -734,6 +734,7 @@ _docker_run() { --attach -a --cap-add --cap-drop + --cgroup-parent --cidfile --cpuset --cpu-shares -c @@ -746,7 +747,10 @@ _docker_run() { --expose --hostname -h --ipc + --label -l + --label-file --link + --log-driver --lxc-conf --mac-address --memory -m @@ -798,7 +802,7 @@ _docker_run() { __docker_capabilities return ;; - --cidfile|--env-file) + --cidfile|--cgroup-parent|--env-file|--label-file) _filedir return ;; @@ -850,6 +854,10 @@ _docker_run() { esac return ;; + --log-driver) + COMPREPLY=( $( compgen -W "json-file syslog none" -- "$cur") ) + return + ;; --net) case "$cur" in container:*) From 2770a88413a98fcb3dd6e36e91c5041e5fe082eb Mon Sep 17 00:00:00 2001 From: Frank Herrmann Date: Tue, 24 Mar 2015 16:27:09 +0000 Subject: [PATCH 056/999] add tests for pkg/httputils closes #11597 Signed-off-by: Frank Herrmann --- pkg/httputils/resumablerequestreader_test.go | 83 ++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 pkg/httputils/resumablerequestreader_test.go diff --git a/pkg/httputils/resumablerequestreader_test.go b/pkg/httputils/resumablerequestreader_test.go new file mode 100644 index 000000000..35338600d --- /dev/null +++ b/pkg/httputils/resumablerequestreader_test.go @@ -0,0 +1,83 @@ +package httputils + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "strings" + "testing" +) + +func TestResumableRequestReader(t *testing.T) { + + srvtxt := "some response text data" + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, srvtxt) + })) + defer ts.Close() + + var req *http.Request + req, err := http.NewRequest("GET", ts.URL, nil) + if err != nil { + t.Fatal(err) + } + + client := &http.Client{} + retries := uint32(5) + imgSize := int64(len(srvtxt)) + + resreq := ResumableRequestReader(client, req, retries, imgSize) + defer resreq.Close() + + data, err := ioutil.ReadAll(resreq) + if err != nil { + t.Fatal(err) + } + + resstr := strings.TrimSuffix(string(data), "\n") + + if resstr != srvtxt { + t.Errorf("resstr != srvtxt") + } +} + +func TestResumableRequestReaderWithInitialResponse(t *testing.T) { + + srvtxt := "some response text data" + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, srvtxt) + })) + defer ts.Close() + + var req *http.Request + req, err := http.NewRequest("GET", ts.URL, nil) + if err != nil { + t.Fatal(err) + } + + client := &http.Client{} + retries := uint32(5) + imgSize := int64(len(srvtxt)) + + res, err := client.Do(req) + if err != nil { + t.Fatal(err) + } + + resreq := ResumableRequestReaderWithInitialResponse(client, req, retries, imgSize, res) + defer resreq.Close() + + data, err := ioutil.ReadAll(resreq) + if err != nil { + t.Fatal(err) + } + + resstr := strings.TrimSuffix(string(data), "\n") + + if resstr != srvtxt { + t.Errorf("resstr != srvtxt") + } +} From 4433b4c19e7eb63937e211cc0684560800799e68 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Tue, 24 Mar 2015 09:28:02 -0700 Subject: [PATCH 057/999] Sort .gitignore content Signed-off-by: Arnaud Porterie --- .gitignore | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 9696b9a30..cd641d8ff 100644 --- a/.gitignore +++ b/.gitignore @@ -1,33 +1,33 @@ # Docker project generated files to ignore # if you want to ignore files created by your editor/tools, # please consider a global .gitignore https://help.github.com/articles/ignoring-files -.vagrant* -bin -docker/docker *.exe -.*.swp -a.out *.orig -build_src -.flymake* -.idea +*.test +.*.swp .DS_Store +.bashrc +.dotcloud +.flymake* +.git/ +.gopath/ +.hg/ +.idea +.vagrant* +Vagrantfile +a.out +autogen/ +bin +build_src +bundles/ +docker/docker +docs/AWS_S3_BUCKET +docs/GITCOMMIT +docs/GIT_BRANCH +docs/VERSION docs/_build docs/_static docs/_templates -.gopath/ -.dotcloud -*.test -bundles/ -.hg/ -.git/ -vendor/pkg/ -pyenv -Vagrantfile -docs/AWS_S3_BUCKET -docs/GIT_BRANCH -docs/VERSION -docs/GITCOMMIT docs/changed-files -autogen/ -.bashrc +pyenv +vendor/pkg/ From b80fae735684406d848b16a0f148a746e17ed25f Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 24 Mar 2015 12:25:26 +0100 Subject: [PATCH 058/999] Refactor pkg/common, Fixes #11599 Signed-off-by: Antonio Murdaca --- api/client/commands.go | 18 +++---- builder/evaluator.go | 6 +-- builder/internals.go | 8 +-- daemon/container.go | 4 +- daemon/daemon.go | 8 +-- daemon/exec.go | 4 +- daemon/graphdriver/aufs/aufs.go | 4 +- daemon/image_delete.go | 8 +-- daemon/monitor.go | 4 +- engine/engine.go | 4 +- engine/env_test.go | 18 ++----- graph/graph.go | 8 +-- graph/pull.go | 54 +++++++++---------- graph/push.go | 26 ++++----- graph/tags.go | 6 +-- integration-cli/docker_cli_build_test.go | 3 +- integration-cli/docker_cli_images_test.go | 4 +- integration-cli/docker_cli_tag_test.go | 4 +- integration-cli/docker_utils.go | 5 +- integration-cli/utils.go | 15 +----- integration/commands_test.go | 4 +- integration/graph_test.go | 27 +++++----- integration/runtime_test.go | 4 +- pkg/stringid/README.md | 1 + .../randomid.go => stringid/stringid.go} | 11 +--- .../stringid_test.go} | 44 ++++----------- pkg/stringutils/README.md | 1 + pkg/stringutils/stringutils.go | 43 +++++++++++++++ pkg/stringutils/stringutils_test.go | 25 +++++++++ pkg/truncindex/truncindex_test.go | 32 +++++------ utils/utils.go | 4 +- volumes/repository.go | 4 +- 32 files changed, 215 insertions(+), 196 deletions(-) create mode 100644 pkg/stringid/README.md rename pkg/{common/randomid.go => stringid/stringid.go} (83%) rename pkg/{common/randomid_test.go => stringid/stringid_test.go} (58%) create mode 100644 pkg/stringutils/README.md create mode 100644 pkg/stringutils/stringutils.go create mode 100644 pkg/stringutils/stringutils_test.go diff --git a/api/client/commands.go b/api/client/commands.go index 5689dfd2c..065322a6e 100644 --- a/api/client/commands.go +++ b/api/client/commands.go @@ -33,7 +33,6 @@ import ( "github.com/docker/docker/nat" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" @@ -43,6 +42,7 @@ import ( "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/resolvconf" "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/term" "github.com/docker/docker/pkg/timeutils" @@ -1165,7 +1165,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { if *noTrunc { fmt.Fprintf(w, "%s\t", outID) } else { - fmt.Fprintf(w, "%s\t", common.TruncateID(outID)) + fmt.Fprintf(w, "%s\t", stringid.TruncateID(outID)) } fmt.Fprintf(w, "%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0)))) @@ -1180,7 +1180,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { if *noTrunc { fmt.Fprintln(w, outID) } else { - fmt.Fprintln(w, common.TruncateID(outID)) + fmt.Fprintln(w, stringid.TruncateID(outID)) } } } @@ -1479,7 +1479,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { } if matchName != "" { - if matchName == image.Get("Id") || matchName == common.TruncateID(image.Get("Id")) { + if matchName == image.Get("Id") || matchName == stringid.TruncateID(image.Get("Id")) { startImage = image } @@ -1549,7 +1549,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { for _, out := range outs.Data { outID := out.Get("Id") if !*noTrunc { - outID = common.TruncateID(outID) + outID = stringid.TruncateID(outID) } repoTags := out.GetList("RepoTags") @@ -1629,8 +1629,8 @@ func (cli *DockerCli) printVizNode(noTrunc bool, image *engine.Env, prefix strin imageID = image.Get("Id") parentID = image.Get("ParentId") } else { - imageID = common.TruncateID(image.Get("Id")) - parentID = common.TruncateID(image.Get("ParentId")) + imageID = stringid.TruncateID(image.Get("Id")) + parentID = stringid.TruncateID(image.Get("ParentId")) } if parentID == "" { fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) @@ -1649,7 +1649,7 @@ func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix stri if noTrunc { imageID = image.Get("Id") } else { - imageID = common.TruncateID(image.Get("Id")) + imageID = stringid.TruncateID(image.Get("Id")) } fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(float64(image.GetInt64("VirtualSize")))) @@ -1757,7 +1757,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { outID := out.Get("Id") if !*noTrunc { - outID = common.TruncateID(outID) + outID = stringid.TruncateID(outID) } if *quiet { diff --git a/builder/evaluator.go b/builder/evaluator.go index 5e353c02b..ec568d1fc 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -32,8 +32,8 @@ import ( "github.com/docker/docker/builder/parser" "github.com/docker/docker/daemon" "github.com/docker/docker/engine" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/fileutils" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/registry" @@ -177,7 +177,7 @@ func (b *Builder) Run(context io.Reader) (string, error) { } return "", err } - fmt.Fprintf(b.OutStream, " ---> %s\n", common.TruncateID(b.image)) + fmt.Fprintf(b.OutStream, " ---> %s\n", stringid.TruncateID(b.image)) if b.Remove { b.clearTmp() } @@ -187,7 +187,7 @@ func (b *Builder) Run(context io.Reader) (string, error) { return "", fmt.Errorf("No image was generated. Is your Dockerfile empty?") } - fmt.Fprintf(b.OutStream, "Successfully built %s\n", common.TruncateID(b.image)) + fmt.Fprintf(b.OutStream, "Successfully built %s\n", stringid.TruncateID(b.image)) return b.image, nil } diff --git a/builder/internals.go b/builder/internals.go index 7c22b47b2..e7e792aa0 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -25,10 +25,10 @@ import ( imagepkg "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/progressreader" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/tarsum" @@ -557,7 +557,7 @@ func (b *Builder) create() (*daemon.Container, error) { } b.TmpContainers[c.ID] = struct{}{} - fmt.Fprintf(b.OutStream, " ---> Running in %s\n", common.TruncateID(c.ID)) + fmt.Fprintf(b.OutStream, " ---> Running in %s\n", stringid.TruncateID(c.ID)) if len(config.Cmd) > 0 { // override the entry point that may have been picked up from the base image @@ -753,11 +753,11 @@ func (b *Builder) clearTmp() { } if err := b.Daemon.Rm(tmp); err != nil { - fmt.Fprintf(b.OutStream, "Error removing intermediate container %s: %v\n", common.TruncateID(c), err) + fmt.Fprintf(b.OutStream, "Error removing intermediate container %s: %v\n", stringid.TruncateID(c), err) return } b.Daemon.DeleteVolumes(tmp.VolumePaths()) delete(b.TmpContainers, c) - fmt.Fprintf(b.OutStream, "Removing intermediate container %s\n", common.TruncateID(c)) + fmt.Fprintf(b.OutStream, "Removing intermediate container %s\n", stringid.TruncateID(c)) } } diff --git a/daemon/container.go b/daemon/container.go index db622334a..3f16ab269 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -30,12 +30,12 @@ import ( "github.com/docker/docker/nat" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/broadcastwriter" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/directory" "github.com/docker/docker/pkg/etchosts" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/resolvconf" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/runconfig" @@ -739,7 +739,7 @@ func (container *Container) Kill() error { if _, err := container.WaitStop(10 * time.Second); err != nil { // Ensure that we don't kill ourselves if pid := container.GetPid(); pid != 0 { - log.Infof("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", common.TruncateID(container.ID)) + log.Infof("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", stringid.TruncateID(container.ID)) if err := syscall.Kill(pid, 9); err != nil { if err != syscall.ESRCH { return err diff --git a/daemon/daemon.go b/daemon/daemon.go index 6a27a085a..b2c156191 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -31,13 +31,13 @@ import ( "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/broadcastwriter" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/graphdb" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/namesgenerator" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/kernel" "github.com/docker/docker/pkg/resolvconf" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/sysinfo" "github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/runconfig" @@ -517,7 +517,7 @@ func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *image. func (daemon *Daemon) generateIdAndName(name string) (string, string, error) { var ( err error - id = common.GenerateRandomID() + id = stringid.GenerateRandomID() ) if name == "" { @@ -562,7 +562,7 @@ func (daemon *Daemon) reserveName(id, name string) (string, error) { nameAsKnownByUser := strings.TrimPrefix(name, "/") return "", fmt.Errorf( "Conflict. The name %q is already in use by container %s. You have to delete (or rename) that container to be able to reuse that name.", nameAsKnownByUser, - common.TruncateID(conflictingContainer.ID)) + stringid.TruncateID(conflictingContainer.ID)) } } return name, nil @@ -585,7 +585,7 @@ func (daemon *Daemon) generateNewName(id string) (string, error) { return name, nil } - name = "/" + common.TruncateID(id) + name = "/" + stringid.TruncateID(id) if _, err := daemon.containerGraph.Set(name, id); err != nil { return "", err } diff --git a/daemon/exec.go b/daemon/exec.go index 3af40ac01..c2e00e08d 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -12,9 +12,9 @@ import ( "github.com/docker/docker/daemon/execdriver/lxc" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/broadcastwriter" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/promise" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" ) @@ -141,7 +141,7 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status { } execConfig := &execConfig{ - ID: common.GenerateRandomID(), + ID: stringid.GenerateRandomID(), OpenStdin: config.AttachStdin, OpenStdout: config.AttachStdout, OpenStderr: config.AttachStderr, diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index 4d0c71c7e..bc4b5c081 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -34,9 +34,9 @@ import ( "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/directory" mountpk "github.com/docker/docker/pkg/mount" + "github.com/docker/docker/pkg/stringid" "github.com/docker/libcontainer/label" ) @@ -405,7 +405,7 @@ func (a *Driver) Cleanup() error { for _, id := range ids { if err := a.unmount(id); err != nil { - log.Errorf("Unmounting %s: %s", common.TruncateID(id), err) + log.Errorf("Unmounting %s: %s", stringid.TruncateID(id), err) } } diff --git a/daemon/image_delete.go b/daemon/image_delete.go index 0c0a534cf..092c1082d 100644 --- a/daemon/image_delete.go +++ b/daemon/image_delete.go @@ -7,8 +7,8 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/image" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/utils" ) @@ -148,11 +148,11 @@ func (daemon *Daemon) canDeleteImage(imgID string, force bool) error { if imgID == p.ID { if container.IsRunning() { if force { - return fmt.Errorf("Conflict, cannot force delete %s because the running container %s is using it, stop it and retry", common.TruncateID(imgID), common.TruncateID(container.ID)) + return fmt.Errorf("Conflict, cannot force delete %s because the running container %s is using it, stop it and retry", stringid.TruncateID(imgID), stringid.TruncateID(container.ID)) } - return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it, stop it and use -f to force", common.TruncateID(imgID), common.TruncateID(container.ID)) + return fmt.Errorf("Conflict, cannot delete %s because the running container %s is using it, stop it and use -f to force", stringid.TruncateID(imgID), stringid.TruncateID(container.ID)) } else if !force { - return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it, use -f to force", common.TruncateID(imgID), common.TruncateID(container.ID)) + return fmt.Errorf("Conflict, cannot delete %s because the container %s is using it, use -f to force", stringid.TruncateID(imgID), stringid.TruncateID(container.ID)) } } return nil diff --git a/daemon/monitor.go b/daemon/monitor.go index 7c18b7a38..abe7dea2b 100644 --- a/daemon/monitor.go +++ b/daemon/monitor.go @@ -8,7 +8,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/pkg/common" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" ) @@ -230,7 +230,7 @@ func (m *containerMonitor) shouldRestart(exitCode int) bool { // the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count if max := m.restartPolicy.MaximumRetryCount; max != 0 && m.failureCount > max { log.Debugf("stopping restart of container %s because maximum failure could of %d has been reached", - common.TruncateID(m.container.ID), max) + stringid.TruncateID(m.container.ID), max) return false } diff --git a/engine/engine.go b/engine/engine.go index 60532349a..5155e2774 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -10,8 +10,8 @@ import ( "sync" "time" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/ioutils" + "github.com/docker/docker/pkg/stringutils" ) // Installer is a standard interface for objects which can "install" themselves @@ -78,7 +78,7 @@ func (eng *Engine) RegisterCatchall(catchall Handler) { func New() *Engine { eng := &Engine{ handlers: make(map[string]Handler), - id: common.RandomString(), + id: stringutils.GenerateRandomString(), Stdout: os.Stdout, Stderr: os.Stderr, Stdin: os.Stdin, diff --git a/engine/env_test.go b/engine/env_test.go index 5182783bb..11e930cad 100644 --- a/engine/env_test.go +++ b/engine/env_test.go @@ -3,24 +3,12 @@ package engine import ( "bytes" "encoding/json" - "math/rand" "testing" "time" + + "github.com/docker/docker/pkg/stringutils" ) -const chars = "abcdefghijklmnopqrstuvwxyz" + - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + - "~!@#$%^&*()-_+={}[]\\|<,>.?/\"';:` " - -// RandomString returns random string of specified length -func RandomString(length int) string { - res := make([]byte, length) - for i := 0; i < length; i++ { - res[i] = chars[rand.Intn(len(chars))] - } - return string(res) -} - func TestEnvLenZero(t *testing.T) { env := &Env{} if env.Len() != 0 { @@ -197,7 +185,7 @@ func TestMultiMap(t *testing.T) { func testMap(l int) [][2]string { res := make([][2]string, l) for i := 0; i < l; i++ { - t := [2]string{RandomString(5), RandomString(20)} + t := [2]string{stringutils.GenerateRandomAsciiString(5), stringutils.GenerateRandomAsciiString(20)} res[i] = t } return res diff --git a/graph/graph.go b/graph/graph.go index ecb52a0c5..0aaf8b361 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -17,8 +17,8 @@ import ( "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/progressreader" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" @@ -118,7 +118,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, containerID, containerImage, comment, author string, containerConfig, config *runconfig.Config) (*image.Image, error) { img := &image.Image{ - ID: common.GenerateRandomID(), + ID: stringid.GenerateRandomID(), Comment: comment, Created: time.Now().UTC(), DockerVersion: dockerversion.VERSION, @@ -217,7 +217,7 @@ func (graph *Graph) TempLayerArchive(id string, sf *utils.StreamFormatter, outpu Formatter: sf, Size: 0, NewLines: false, - ID: common.TruncateID(id), + ID: stringid.TruncateID(id), Action: "Buffering to disk", }) defer progressReader.Close() @@ -226,7 +226,7 @@ func (graph *Graph) TempLayerArchive(id string, sf *utils.StreamFormatter, outpu // 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", common.GenerateRandomID()) + dir := path.Join(graph.Root, "_tmp", stringid.GenerateRandomID()) if err := os.MkdirAll(dir, 0700); err != nil { return "", err } diff --git a/graph/pull.go b/graph/pull.go index adad6f323..8a0f0eba0 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -14,8 +14,8 @@ import ( "github.com/docker/distribution/digest" "github.com/docker/docker/engine" "github.com/docker/docker/image" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/progressreader" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/registry" "github.com/docker/docker/utils" ) @@ -172,9 +172,9 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * // ensure no two downloads of the same image happen at the same time if c, err := s.poolAdd("pull", "img:"+img.ID); err != nil { if c != nil { - out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Layer already being pulled by another client. Waiting.", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Layer already being pulled by another client. Waiting.", nil)) <-c - out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Download complete", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil)) } else { log.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err) } @@ -185,12 +185,12 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * } defer s.poolRemove("pull", "img:"+img.ID) - out.Write(sf.FormatProgress(common.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, repoInfo.CanonicalName), nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, repoInfo.CanonicalName), nil)) success := false var lastErr, err error var is_downloaded bool for _, ep := range repoInfo.Index.Mirrors { - out.Write(sf.FormatProgress(common.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, repoInfo.CanonicalName, ep), nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, repoInfo.CanonicalName, ep), nil)) if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { // Don't report errors when pulling from mirrors. log.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err) @@ -202,12 +202,12 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * } if !success { for _, ep := range repoData.Endpoints { - out.Write(sf.FormatProgress(common.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, repoInfo.CanonicalName, ep), nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, repoInfo.CanonicalName, ep), nil)) if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { // It's not ideal that only the last error is returned, it would be better to concatenate the errors. // As the error is also given to the output stream the user will see the error. lastErr = err - out.Write(sf.FormatProgress(common.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err), nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err), nil)) continue } layers_downloaded = layers_downloaded || is_downloaded @@ -217,13 +217,13 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * } if !success { err := fmt.Errorf("Error pulling image (%s) from %s, %v", img.Tag, repoInfo.CanonicalName, lastErr) - out.Write(sf.FormatProgress(common.TruncateID(img.ID), err.Error(), nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), err.Error(), nil)) if parallel { errors <- err return } } - out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Download complete", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil)) if parallel { errors <- nil @@ -270,7 +270,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint if err != nil { return false, err } - out.Write(sf.FormatProgress(common.TruncateID(imgID), "Pulling dependent layers", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(imgID), "Pulling dependent layers", nil)) // FIXME: Try to stream the images? // FIXME: Launch the getRemoteImage() in goroutines @@ -286,7 +286,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint defer s.poolRemove("pull", "layer:"+id) if !s.graph.Exists(id) { - out.Write(sf.FormatProgress(common.TruncateID(id), "Pulling metadata", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(id), "Pulling metadata", nil)) var ( imgJSON []byte imgSize int @@ -297,7 +297,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint for j := 1; j <= retries; j++ { imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token) if err != nil && j == retries { - out.Write(sf.FormatProgress(common.TruncateID(id), "Error pulling dependent layers", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil)) return layers_downloaded, err } else if err != nil { time.Sleep(time.Duration(j) * 500 * time.Millisecond) @@ -306,7 +306,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint img, err = image.NewImgJSON(imgJSON) layers_downloaded = true if err != nil && j == retries { - out.Write(sf.FormatProgress(common.TruncateID(id), "Error pulling dependent layers", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil)) return layers_downloaded, fmt.Errorf("Failed to parse json: %s", err) } else if err != nil { time.Sleep(time.Duration(j) * 500 * time.Millisecond) @@ -322,7 +322,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint if j > 1 { status = fmt.Sprintf("Pulling fs layer [retries: %d]", j) } - out.Write(sf.FormatProgress(common.TruncateID(id), status, nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(id), status, nil)) layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token, int64(imgSize)) if uerr, ok := err.(*url.Error); ok { err = uerr.Err @@ -331,7 +331,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint time.Sleep(time.Duration(j) * 500 * time.Millisecond) continue } else if err != nil { - out.Write(sf.FormatProgress(common.TruncateID(id), "Error pulling dependent layers", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil)) return layers_downloaded, err } layers_downloaded = true @@ -344,21 +344,21 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint Formatter: sf, Size: imgSize, NewLines: false, - ID: common.TruncateID(id), + ID: stringid.TruncateID(id), Action: "Downloading", })) if terr, ok := err.(net.Error); ok && terr.Timeout() && j < retries { time.Sleep(time.Duration(j) * 500 * time.Millisecond) continue } else if err != nil { - out.Write(sf.FormatProgress(common.TruncateID(id), "Error downloading dependent layers", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error downloading dependent layers", nil)) return layers_downloaded, err } else { break } } } - out.Write(sf.FormatProgress(common.TruncateID(id), "Download complete", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(id), "Download complete", nil)) } return layers_downloaded, nil } @@ -478,16 +478,16 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri } downloads[i].digest = dgst - out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Pulling fs layer", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Pulling fs layer", nil)) downloadFunc := func(di *downloadInfo) error { log.Debugf("pulling blob %q to V1 img %s", sumStr, img.ID) if c, err := s.poolAdd("pull", "img:"+img.ID); err != nil { if c != nil { - out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Layer already being pulled by another client. Waiting.", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Layer already being pulled by another client. Waiting.", nil)) <-c - out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Download complete", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil)) } else { log.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err) } @@ -515,20 +515,20 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri Formatter: sf, Size: int(l), NewLines: false, - ID: common.TruncateID(img.ID), + ID: stringid.TruncateID(img.ID), Action: "Downloading", })); err != nil { return fmt.Errorf("unable to copy v2 image blob data: %s", err) } - out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Verifying Checksum", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Verifying Checksum", nil)) if !verifier.Verified() { log.Infof("Image verification failed: checksum mismatch for %q", di.digest.String()) verified = false } - out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Download complete", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil)) log.Debugf("Downloaded %s to tempfile %s", img.ID, tmpFile.Name()) di.tmpFile = tmpFile @@ -574,7 +574,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri Out: out, Formatter: sf, Size: int(d.length), - ID: common.TruncateID(d.img.ID), + ID: stringid.TruncateID(d.img.ID), Action: "Extracting", })) if err != nil { @@ -583,10 +583,10 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri // FIXME: Pool release here for parallel tag pull (ensures any downloads block until fully extracted) } - out.Write(sf.FormatProgress(common.TruncateID(d.img.ID), "Pull complete", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(d.img.ID), "Pull complete", nil)) tagUpdated = true } else { - out.Write(sf.FormatProgress(common.TruncateID(d.img.ID), "Already exists", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(d.img.ID), "Already exists", nil)) } } diff --git a/graph/push.go b/graph/push.go index f86df6d0b..6085113f8 100644 --- a/graph/push.go +++ b/graph/push.go @@ -16,8 +16,8 @@ import ( "github.com/docker/distribution/digest" "github.com/docker/docker/engine" "github.com/docker/docker/image" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/progressreader" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" @@ -139,7 +139,7 @@ func lookupImageOnEndpoint(wg *sync.WaitGroup, r *registry.Session, out io.Write imagesToPush <- image.id continue } - out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", common.TruncateID(image.id))) + out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", stringid.TruncateID(image.id))) } } @@ -191,7 +191,7 @@ func (s *TagStore) pushImageToEndpoint(endpoint string, out io.Writer, remoteNam } } for _, tag := range tags[id] { - out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", common.TruncateID(id), endpoint+"repositories/"+remoteName+"/tags/"+tag)) + out.Write(sf.FormatStatus("", "Pushing tag for rev [%s] on {%s}", stringid.TruncateID(id), endpoint+"repositories/"+remoteName+"/tags/"+tag)) if err := r.PushRegistryTag(remoteName, id, tag, endpoint, repo.Tokens); err != nil { return err } @@ -244,7 +244,7 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin if err != nil { return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err) } - out.Write(sf.FormatProgress(common.TruncateID(imgID), "Pushing", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(imgID), "Pushing", nil)) imgData := ®istry.ImgData{ ID: imgID, @@ -253,7 +253,7 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin // Send the json if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil { if err == registry.ErrAlreadyExists { - out.Write(sf.FormatProgress(common.TruncateID(imgData.ID), "Image already pushed, skipping", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image already pushed, skipping", nil)) return "", nil } return "", err @@ -275,7 +275,7 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin Formatter: sf, Size: int(layerData.Size), NewLines: false, - ID: common.TruncateID(imgData.ID), + ID: stringid.TruncateID(imgData.ID), Action: "Pushing", }), ep, token, jsonRaw) if err != nil { @@ -288,7 +288,7 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin return "", err } - out.Write(sf.FormatProgress(common.TruncateID(imgData.ID), "Image successfully pushed", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(imgData.ID), "Image successfully pushed", nil)) return imgData.Checksum, nil } @@ -385,7 +385,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o // Call mount blob exists, err = r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, sumParts[0], sumParts[1], auth) if err != nil { - out.Write(sf.FormatProgress(common.TruncateID(layer.ID), "Image push failed", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(layer.ID), "Image push failed", nil)) return err } } @@ -400,7 +400,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o checksum = cs } } else { - out.Write(sf.FormatProgress(common.TruncateID(layer.ID), "Image already exists", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(layer.ID), "Image already exists", nil)) } m.FSLayers[i] = ®istry.FSLayer{BlobSum: checksum} m.History[i] = ®istry.ManifestHistory{V1Compatibility: string(jsonData)} @@ -443,7 +443,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o // PushV2Image pushes the image content to the v2 registry, first buffering the contents to disk func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *registry.Endpoint, imageName string, sf *utils.StreamFormatter, out io.Writer, auth *registry.RequestAuthorization) (string, error) { - out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Buffering to Disk", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Buffering to Disk", nil)) image, err := s.graph.Get(img.ID) if err != nil { @@ -481,13 +481,13 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint * Formatter: sf, Size: int(size), NewLines: false, - ID: common.TruncateID(img.ID), + ID: stringid.TruncateID(img.ID), Action: "Pushing", }), auth); err != nil { - out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Image push failed", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Image push failed", nil)) return "", err } - out.Write(sf.FormatProgress(common.TruncateID(img.ID), "Image successfully pushed", nil)) + out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Image successfully pushed", nil)) return dgst.String(), nil } diff --git a/graph/tags.go b/graph/tags.go index 5d26b8cfb..87c045b82 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -13,8 +13,8 @@ import ( "sync" "github.com/docker/docker/image" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/registry" "github.com/docker/docker/utils" "github.com/docker/libtrust" @@ -163,7 +163,7 @@ func (store *TagStore) ImageName(id string) string { if names, exists := store.ByID()[id]; exists && len(names) > 0 { return names[0] } - return common.TruncateID(id) + return stringid.TruncateID(id) } func (store *TagStore) DeleteAll(id string) error { @@ -331,7 +331,7 @@ func (store *TagStore) GetRepoRefs() map[string][]string { for name, repository := range store.Repositories { for tag, id := range repository { - shortID := common.TruncateID(id) + shortID := stringid.TruncateID(id) reporefs[shortID] = append(reporefs[shortID], utils.ImageReference(name, tag)) } } diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 9a8ee69e5..954c0e5d9 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -22,6 +22,7 @@ import ( "github.com/docker/docker/builder/command" "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/stringutils" ) func TestBuildJSONEmptyRun(t *testing.T) { @@ -4420,7 +4421,7 @@ func TestBuildOnBuildOutput(t *testing.T) { } func TestBuildInvalidTag(t *testing.T) { - name := "abcd:" + makeRandomString(200) + name := "abcd:" + stringutils.GenerateRandomAlphaOnlyString(200) defer deleteImages(name) _, out, err := buildImageWithOut(name, "FROM scratch\nMAINTAINER quux\n", true) // if the error doesnt check for illegal tag name, or the image is built diff --git a/integration-cli/docker_cli_images_test.go b/integration-cli/docker_cli_images_test.go index 694971191..28b091efd 100644 --- a/integration-cli/docker_cli_images_test.go +++ b/integration-cli/docker_cli_images_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - "github.com/docker/docker/pkg/common" + "github.com/docker/docker/pkg/stringid" ) func TestImagesEnsureImageIsListed(t *testing.T) { @@ -196,7 +196,7 @@ func TestImagesEnsureDanglingImageOnlyListedOnce(t *testing.T) { if err != nil { t.Fatalf("error tagging foobox: %s", err) } - imageId := common.TruncateID(strings.TrimSpace(out)) + imageId := stringid.TruncateID(strings.TrimSpace(out)) defer deleteImages(imageId) // overwrite the tag, making the previous image dangling diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go index 081e239c3..b181e2177 100644 --- a/integration-cli/docker_cli_tag_test.go +++ b/integration-cli/docker_cli_tag_test.go @@ -5,6 +5,8 @@ import ( "os/exec" "strings" "testing" + + "github.com/docker/docker/pkg/stringutils" ) // tagging a named image in a new unprefixed repo should work @@ -59,7 +61,7 @@ func TestTagInvalidUnprefixedRepo(t *testing.T) { // ensure we don't allow the use of invalid tags; these tag operations should fail func TestTagInvalidPrefixedRepo(t *testing.T) { - long_tag := makeRandomString(121) + long_tag := stringutils.GenerateRandomAlphaOnlyString(121) invalidTags := []string{"repo:fo$z$", "repo:Foo@3cc", "repo:Foo$3", "repo:Foo*3", "repo:Fo^3", "repo:Foo!3", "repo:%goodbye", "repo:#hashtagit", "repo:F)xcz(", "repo:-foo", "repo:..", long_tag} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index e0b9bacc4..7abdc54dc 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -22,6 +22,7 @@ import ( "time" "github.com/docker/docker/api" + "github.com/docker/docker/pkg/stringutils" ) // Daemon represents a Docker daemon for the testing framework. @@ -695,8 +696,8 @@ func (f *remoteFileServer) Close() error { func newRemoteFileServer(ctx *FakeContext) (*remoteFileServer, error) { var ( - image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(makeRandomString(10))) - container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(makeRandomString(10))) + image = fmt.Sprintf("fileserver-img-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10))) + container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(stringutils.GenerateRandomAlphaOnlyString(10))) ) // Build the image diff --git a/integration-cli/utils.go b/integration-cli/utils.go index 85e6f1ccd..d6b65acc0 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -6,7 +6,6 @@ import ( "errors" "fmt" "io" - "math/rand" "net/http" "net/http/httptest" "os" @@ -17,6 +16,7 @@ import ( "syscall" "time" + "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) @@ -301,21 +301,10 @@ func copyWithCP(source, target string) error { return nil } -func makeRandomString(n int) string { - // make a really long string - letters := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") - b := make([]byte, n) - r := rand.New(rand.NewSource(time.Now().UTC().UnixNano())) - for i := range b { - b[i] = letters[r.Intn(len(letters))] - } - return string(b) -} - // randomUnixTmpDirPath provides a temporary unix path with rand string appended. // does not create or checks if it exists. func randomUnixTmpDirPath(s string) string { - return path.Join("/tmp", fmt.Sprintf("%s.%s", s, makeRandomString(10))) + return path.Join("/tmp", fmt.Sprintf("%s.%s", s, stringutils.GenerateRandomAlphaOnlyString(10))) } // Reads chunkSize bytes from reader after every interval. diff --git a/integration/commands_test.go b/integration/commands_test.go index 6c6ad0e71..f748efd6d 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -12,7 +12,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" "github.com/docker/docker/daemon" - "github.com/docker/docker/pkg/common" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/term" "github.com/kr/pty" ) @@ -286,7 +286,7 @@ func TestAttachDetachTruncatedID(t *testing.T) { ch := make(chan struct{}) go func() { defer close(ch) - if err := cli.CmdAttach(common.TruncateID(container.ID)); err != nil { + if err := cli.CmdAttach(stringid.TruncateID(container.ID)); err != nil { if err != io.ErrClosedPipe { t.Fatal(err) } diff --git a/integration/graph_test.go b/integration/graph_test.go index 8518fae8e..a48115455 100644 --- a/integration/graph_test.go +++ b/integration/graph_test.go @@ -2,19 +2,20 @@ package docker import ( "errors" - "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/daemon/graphdriver" - "github.com/docker/docker/graph" - "github.com/docker/docker/image" - "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/common" - "github.com/docker/docker/utils" "io" "io/ioutil" "os" "path" "testing" "time" + + "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/daemon/graphdriver" + "github.com/docker/docker/graph" + "github.com/docker/docker/image" + "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/utils" ) func TestMount(t *testing.T) { @@ -70,7 +71,7 @@ func TestInterruptedRegister(t *testing.T) { defer nukeGraph(graph) badArchive, w := io.Pipe() // Use a pipe reader as a fake archive which never yields data image := &image.Image{ - ID: common.GenerateRandomID(), + ID: stringid.GenerateRandomID(), Comment: "testing", Created: time.Now(), } @@ -130,7 +131,7 @@ func TestRegister(t *testing.T) { t.Fatal(err) } image := &image.Image{ - ID: common.GenerateRandomID(), + ID: stringid.GenerateRandomID(), Comment: "testing", Created: time.Now(), } @@ -160,7 +161,7 @@ func TestDeletePrefix(t *testing.T) { graph, _ := tempGraph(t) defer nukeGraph(graph) img := createTestImage(graph, t) - if err := graph.Delete(common.TruncateID(img.ID)); err != nil { + if err := graph.Delete(stringid.TruncateID(img.ID)); err != nil { t.Fatal(err) } assertNImages(graph, t, 0) @@ -246,19 +247,19 @@ func TestByParent(t *testing.T) { graph, _ := tempGraph(t) defer nukeGraph(graph) parentImage := &image.Image{ - ID: common.GenerateRandomID(), + ID: stringid.GenerateRandomID(), Comment: "parent", Created: time.Now(), Parent: "", } childImage1 := &image.Image{ - ID: common.GenerateRandomID(), + ID: stringid.GenerateRandomID(), Comment: "child1", Created: time.Now(), Parent: parentImage.ID, } childImage2 := &image.Image{ - ID: common.GenerateRandomID(), + ID: stringid.GenerateRandomID(), Comment: "child2", Created: time.Now(), Parent: parentImage.ID, diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 153c38562..4c1632771 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -22,9 +22,9 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/nat" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/reexec" + "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -306,7 +306,7 @@ func TestDaemonCreate(t *testing.T) { &runconfig.HostConfig{}, "conflictname", ) - if _, _, err := daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID, Cmd: []string{"ls", "-al"}}, &runconfig.HostConfig{}, testContainer.Name); err == nil || !strings.Contains(err.Error(), common.TruncateID(testContainer.ID)) { + if _, _, err := daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID, Cmd: []string{"ls", "-al"}}, &runconfig.HostConfig{}, testContainer.Name); err == nil || !strings.Contains(err.Error(), stringid.TruncateID(testContainer.ID)) { t.Fatalf("Name conflict error doesn't include the correct short id. Message was: %v", err) } diff --git a/pkg/stringid/README.md b/pkg/stringid/README.md new file mode 100644 index 000000000..37a5098fd --- /dev/null +++ b/pkg/stringid/README.md @@ -0,0 +1 @@ +This package provides helper functions for dealing with string identifiers diff --git a/pkg/common/randomid.go b/pkg/stringid/stringid.go similarity index 83% rename from pkg/common/randomid.go rename to pkg/stringid/stringid.go index 5c6d5920e..bf39df9b7 100644 --- a/pkg/common/randomid.go +++ b/pkg/stringid/stringid.go @@ -1,4 +1,4 @@ -package common +package stringid import ( "crypto/rand" @@ -36,12 +36,3 @@ func GenerateRandomID() string { return value } } - -func RandomString() string { - id := make([]byte, 32) - - if _, err := io.ReadFull(rand.Reader, id); err != nil { - panic(err) // This shouldn't happen - } - return hex.EncodeToString(id) -} diff --git a/pkg/common/randomid_test.go b/pkg/stringid/stringid_test.go similarity index 58% rename from pkg/common/randomid_test.go rename to pkg/stringid/stringid_test.go index 1dba41254..21f8f8a2f 100644 --- a/pkg/common/randomid_test.go +++ b/pkg/stringid/stringid_test.go @@ -1,8 +1,14 @@ -package common +package stringid -import ( - "testing" -) +import "testing" + +func TestGenerateRandomID(t *testing.T) { + id := GenerateRandomID() + + if len(id) != 64 { + t.Fatalf("Id returned is incorrect: %s", id) + } +} func TestShortenId(t *testing.T) { id := GenerateRandomID() @@ -27,33 +33,3 @@ func TestShortenIdInvalid(t *testing.T) { t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID) } } - -func TestGenerateRandomID(t *testing.T) { - id := GenerateRandomID() - - if len(id) != 64 { - t.Fatalf("Id returned is incorrect: %s", id) - } -} - -func TestRandomString(t *testing.T) { - id := RandomString() - if len(id) != 64 { - t.Fatalf("Id returned is incorrect: %s", id) - } -} - -func TestRandomStringUniqueness(t *testing.T) { - repeats := 25 - set := make(map[string]struct{}, repeats) - for i := 0; i < repeats; i = i + 1 { - id := RandomString() - if len(id) != 64 { - t.Fatalf("Id returned is incorrect: %s", id) - } - if _, ok := set[id]; ok { - t.Fatalf("Random number is repeated") - } - set[id] = struct{}{} - } -} diff --git a/pkg/stringutils/README.md b/pkg/stringutils/README.md new file mode 100644 index 000000000..b3e454573 --- /dev/null +++ b/pkg/stringutils/README.md @@ -0,0 +1 @@ +This package provides helper functions for dealing with strings diff --git a/pkg/stringutils/stringutils.go b/pkg/stringutils/stringutils.go new file mode 100644 index 000000000..bcb0ece57 --- /dev/null +++ b/pkg/stringutils/stringutils.go @@ -0,0 +1,43 @@ +package stringutils + +import ( + "crypto/rand" + "encoding/hex" + "io" + mathrand "math/rand" + "time" +) + +// Generate 32 chars random string +func GenerateRandomString() string { + id := make([]byte, 32) + + if _, err := io.ReadFull(rand.Reader, id); err != nil { + panic(err) // This shouldn't happen + } + return hex.EncodeToString(id) +} + +// Generate alpha only random stirng with length n +func GenerateRandomAlphaOnlyString(n int) string { + // make a really long string + letters := []byte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + b := make([]byte, n) + r := mathrand.New(mathrand.NewSource(time.Now().UTC().UnixNano())) + for i := range b { + b[i] = letters[r.Intn(len(letters))] + } + return string(b) +} + +// Generate Ascii random stirng with length n +func GenerateRandomAsciiString(n int) string { + chars := "abcdefghijklmnopqrstuvwxyz" + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "~!@#$%^&*()-_+={}[]\\|<,>.?/\"';:` " + res := make([]byte, n) + for i := 0; i < n; i++ { + res[i] = chars[mathrand.Intn(len(chars))] + } + return string(res) +} diff --git a/pkg/stringutils/stringutils_test.go b/pkg/stringutils/stringutils_test.go new file mode 100644 index 000000000..60b848ff5 --- /dev/null +++ b/pkg/stringutils/stringutils_test.go @@ -0,0 +1,25 @@ +package stringutils + +import "testing" + +func TestRandomString(t *testing.T) { + str := GenerateRandomString() + if len(str) != 64 { + t.Fatalf("Id returned is incorrect: %s", str) + } +} + +func TestRandomStringUniqueness(t *testing.T) { + repeats := 25 + set := make(map[string]struct{}, repeats) + for i := 0; i < repeats; i = i + 1 { + str := GenerateRandomString() + if len(str) != 64 { + t.Fatalf("Id returned is incorrect: %s", str) + } + if _, ok := set[str]; ok { + t.Fatalf("Random number is repeated") + } + set[str] = struct{}{} + } +} diff --git a/pkg/truncindex/truncindex_test.go b/pkg/truncindex/truncindex_test.go index 928653428..f46a662d7 100644 --- a/pkg/truncindex/truncindex_test.go +++ b/pkg/truncindex/truncindex_test.go @@ -4,7 +4,7 @@ import ( "math/rand" "testing" - "github.com/docker/docker/pkg/common" + "github.com/docker/docker/pkg/stringid" ) // Test the behavior of TruncIndex, an index for querying IDs from a non-conflicting prefix. @@ -111,7 +111,7 @@ func assertIndexGet(t *testing.T, index *TruncIndex, input, expectedResult strin func BenchmarkTruncIndexAdd100(b *testing.B) { var testSet []string for i := 0; i < 100; i++ { - testSet = append(testSet, common.GenerateRandomID()) + testSet = append(testSet, stringid.GenerateRandomID()) } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -127,7 +127,7 @@ func BenchmarkTruncIndexAdd100(b *testing.B) { func BenchmarkTruncIndexAdd250(b *testing.B) { var testSet []string for i := 0; i < 250; i++ { - testSet = append(testSet, common.GenerateRandomID()) + testSet = append(testSet, stringid.GenerateRandomID()) } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -143,7 +143,7 @@ func BenchmarkTruncIndexAdd250(b *testing.B) { func BenchmarkTruncIndexAdd500(b *testing.B) { var testSet []string for i := 0; i < 500; i++ { - testSet = append(testSet, common.GenerateRandomID()) + testSet = append(testSet, stringid.GenerateRandomID()) } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -160,7 +160,7 @@ func BenchmarkTruncIndexGet100(b *testing.B) { var testSet []string var testKeys []string for i := 0; i < 100; i++ { - testSet = append(testSet, common.GenerateRandomID()) + testSet = append(testSet, stringid.GenerateRandomID()) } index := NewTruncIndex([]string{}) for _, id := range testSet { @@ -184,7 +184,7 @@ func BenchmarkTruncIndexGet250(b *testing.B) { var testSet []string var testKeys []string for i := 0; i < 250; i++ { - testSet = append(testSet, common.GenerateRandomID()) + testSet = append(testSet, stringid.GenerateRandomID()) } index := NewTruncIndex([]string{}) for _, id := range testSet { @@ -208,7 +208,7 @@ func BenchmarkTruncIndexGet500(b *testing.B) { var testSet []string var testKeys []string for i := 0; i < 500; i++ { - testSet = append(testSet, common.GenerateRandomID()) + testSet = append(testSet, stringid.GenerateRandomID()) } index := NewTruncIndex([]string{}) for _, id := range testSet { @@ -231,7 +231,7 @@ func BenchmarkTruncIndexGet500(b *testing.B) { func BenchmarkTruncIndexDelete100(b *testing.B) { var testSet []string for i := 0; i < 100; i++ { - testSet = append(testSet, common.GenerateRandomID()) + testSet = append(testSet, stringid.GenerateRandomID()) } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -254,7 +254,7 @@ func BenchmarkTruncIndexDelete100(b *testing.B) { func BenchmarkTruncIndexDelete250(b *testing.B) { var testSet []string for i := 0; i < 250; i++ { - testSet = append(testSet, common.GenerateRandomID()) + testSet = append(testSet, stringid.GenerateRandomID()) } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -277,7 +277,7 @@ func BenchmarkTruncIndexDelete250(b *testing.B) { func BenchmarkTruncIndexDelete500(b *testing.B) { var testSet []string for i := 0; i < 500; i++ { - testSet = append(testSet, common.GenerateRandomID()) + testSet = append(testSet, stringid.GenerateRandomID()) } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -300,7 +300,7 @@ func BenchmarkTruncIndexDelete500(b *testing.B) { func BenchmarkTruncIndexNew100(b *testing.B) { var testSet []string for i := 0; i < 100; i++ { - testSet = append(testSet, common.GenerateRandomID()) + testSet = append(testSet, stringid.GenerateRandomID()) } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -311,7 +311,7 @@ func BenchmarkTruncIndexNew100(b *testing.B) { func BenchmarkTruncIndexNew250(b *testing.B) { var testSet []string for i := 0; i < 250; i++ { - testSet = append(testSet, common.GenerateRandomID()) + testSet = append(testSet, stringid.GenerateRandomID()) } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -322,7 +322,7 @@ func BenchmarkTruncIndexNew250(b *testing.B) { func BenchmarkTruncIndexNew500(b *testing.B) { var testSet []string for i := 0; i < 500; i++ { - testSet = append(testSet, common.GenerateRandomID()) + testSet = append(testSet, stringid.GenerateRandomID()) } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -334,7 +334,7 @@ func BenchmarkTruncIndexAddGet100(b *testing.B) { var testSet []string var testKeys []string for i := 0; i < 500; i++ { - id := common.GenerateRandomID() + id := stringid.GenerateRandomID() testSet = append(testSet, id) l := rand.Intn(12) + 12 testKeys = append(testKeys, id[:l]) @@ -359,7 +359,7 @@ func BenchmarkTruncIndexAddGet250(b *testing.B) { var testSet []string var testKeys []string for i := 0; i < 500; i++ { - id := common.GenerateRandomID() + id := stringid.GenerateRandomID() testSet = append(testSet, id) l := rand.Intn(12) + 12 testKeys = append(testKeys, id[:l]) @@ -384,7 +384,7 @@ func BenchmarkTruncIndexAddGet500(b *testing.B) { var testSet []string var testKeys []string for i := 0; i < 500; i++ { - id := common.GenerateRandomID() + id := stringid.GenerateRandomID() testSet = append(testSet, id) l := rand.Intn(12) + 12 testKeys = append(testKeys, id[:l]) diff --git a/utils/utils.go b/utils/utils.go index 540ae6f57..d5ebb68c9 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -21,9 +21,9 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/common" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/ioutils" + "github.com/docker/docker/pkg/stringutils" ) type KeyValuePair struct { @@ -312,7 +312,7 @@ var globalTestID string // new directory. func TestDirectory(templateDir string) (dir string, err error) { if globalTestID == "" { - globalTestID = common.RandomString()[:4] + globalTestID = stringutils.GenerateRandomString()[:4] } prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, GetCallerName(2)) if prefix == "" { diff --git a/volumes/repository.go b/volumes/repository.go index dbd7a5f55..ee555e401 100644 --- a/volumes/repository.go +++ b/volumes/repository.go @@ -9,7 +9,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/graphdriver" - "github.com/docker/docker/pkg/common" + "github.com/docker/docker/pkg/stringid" ) type Repository struct { @@ -43,7 +43,7 @@ func (r *Repository) newVolume(path string, writable bool) (*Volume, error) { var ( isBindMount bool err error - id = common.GenerateRandomID() + id = stringid.GenerateRandomID() ) if path != "" { isBindMount = true From 557cca536f82a25f110399fcd2964e07f5d51dcf Mon Sep 17 00:00:00 2001 From: Arthur Barr Date: Tue, 24 Mar 2015 07:12:59 +0000 Subject: [PATCH 059/999] Fix #11589 by adding README and comments to exported functions Signed-off-by: Arthur Barr --- pkg/resolvconf/README.md | 1 + pkg/resolvconf/resolvconf.go | 15 ++++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) create mode 100644 pkg/resolvconf/README.md diff --git a/pkg/resolvconf/README.md b/pkg/resolvconf/README.md new file mode 100644 index 000000000..cdda554ba --- /dev/null +++ b/pkg/resolvconf/README.md @@ -0,0 +1 @@ +Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf diff --git a/pkg/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go index 61f92d9ae..d6f0f7a95 100644 --- a/pkg/resolvconf/resolvconf.go +++ b/pkg/resolvconf/resolvconf.go @@ -1,3 +1,4 @@ +// Package resolvconf provides utility code to query and update DNS configuration in /etc/resolv.conf package resolvconf import ( @@ -38,6 +39,7 @@ var lastModified struct { contents []byte } +// Get returns the contents of /etc/resolv.conf func Get() ([]byte, error) { resolv, err := ioutil.ReadFile("/etc/resolv.conf") if err != nil { @@ -46,7 +48,7 @@ func Get() ([]byte, error) { return resolv, nil } -// Retrieves the host /etc/resolv.conf file, checks against the last hash +// GetIfChanged retrieves the host /etc/resolv.conf file, checks against the last hash // and, if modified since last check, returns the bytes and new hash. // This feature is used by the resolv.conf updater for containers func GetIfChanged() ([]byte, string, error) { @@ -70,7 +72,7 @@ func GetIfChanged() ([]byte, string, error) { return nil, "", nil } -// retrieve the last used contents and hash of the host resolv.conf +// GetLastModified retrieves the last used contents and hash of the host resolv.conf. // Used by containers updating on restart func GetLastModified() ([]byte, string) { lastModified.Lock() @@ -79,14 +81,14 @@ func GetLastModified() ([]byte, string) { return lastModified.contents, lastModified.sha256 } -// FilterResolvDns has two main jobs: +// FilterResolvDns cleans up the config in resolvConf. It has two main jobs: // 1. It looks for localhost (127.*|::1) entries in the provided // resolv.conf, removing local nameserver entries, and, if the resulting // cleaned config has no defined nameservers left, adds default DNS entries // 2. Given the caller provides the enable/disable state of IPv6, the filter // code will remove all IPv6 nameservers if it is not enabled for containers // -// It also returns a boolean to notify the caller if changes were made at all +// It returns a boolean to notify the caller if changes were made at all func FilterResolvDns(resolvConf []byte, ipv6Enabled bool) ([]byte, bool) { changed := false cleanedResolvConf := localhostNSRegexp.ReplaceAll(resolvConf, []byte{}) @@ -126,7 +128,7 @@ func getLines(input []byte, commentMarker []byte) [][]byte { return output } -// returns true if the IP string matches the localhost IP regular expression. +// IsLocalhost returns true if ip matches the localhost IP regular expression. // Used for determining if nameserver settings are being passed which are // localhost addresses func IsLocalhost(ip string) bool { @@ -171,6 +173,9 @@ func GetSearchDomains(resolvConf []byte) []string { return domains } +// Build writes a configuration file to path containing a "nameserver" entry +// for every element in dns, and a "search" entry for every element in +// dnsSearch. func Build(path string, dns, dnsSearch []string) error { content := bytes.NewBuffer(nil) for _, dns := range dns { From 1e788ec9855058913de6ac8a2be7acee0f001954 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 24 Mar 2015 10:47:30 -0700 Subject: [PATCH 060/999] Use /var/run/docker as root for execdriver Signed-off-by: Alexander Morozov --- daemon/daemon.go | 3 ++- daemon/execdriver/native/driver.go | 1 - integration-cli/docker_test_vars.go | 4 +++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 6a27a085a..453e2e027 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1012,7 +1012,8 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) } sysInfo := sysinfo.New(false) - ed, err := execdrivers.NewDriver(config.ExecDriver, config.Root, sysInitPath, sysInfo) + const runDir = "/var/run/docker" + ed, err := execdrivers.NewDriver(config.ExecDriver, runDir, sysInitPath, sysInfo) if err != nil { return nil, err } diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 99019d0f8..46097a8cc 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -64,7 +64,6 @@ func NewDriver(root, initPath string) (*driver, error) { root, cgm, libcontainer.InitPath(reexec.Self(), DriverName), - libcontainer.TmpfsRoot, ) if err != nil { return nil, err diff --git a/integration-cli/docker_test_vars.go b/integration-cli/docker_test_vars.go index ff2ec7406..9cb28b274 100644 --- a/integration-cli/docker_test_vars.go +++ b/integration-cli/docker_test_vars.go @@ -17,11 +17,13 @@ var ( privateRegistryURL = "127.0.0.1:5000" dockerBasePath = "/var/lib/docker" - execDriverPath = dockerBasePath + "/execdriver/native" volumesConfigPath = dockerBasePath + "/volumes" volumesStoragePath = dockerBasePath + "/vfs/dir" containerStoragePath = dockerBasePath + "/containers" + runtimePath = "/var/run/docker" + execDriverPath = runtimePath + "/execdriver/native" + workingDirectory string ) From 6de806f348cf802e80a729f390bab777df146ca7 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Tue, 24 Mar 2015 11:25:26 -0700 Subject: [PATCH 061/999] Adding in comments from party Signed-off-by: Mary Anthony --- docs/sources/project/set-up-dev-env.md | 20 +++++++++++++++----- docs/sources/project/work-issue.md | 6 ++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/docs/sources/project/set-up-dev-env.md b/docs/sources/project/set-up-dev-env.md index 9b767ad64..0629822b9 100644 --- a/docs/sources/project/set-up-dev-env.md +++ b/docs/sources/project/set-up-dev-env.md @@ -15,8 +15,8 @@ You use the `docker` repository and its `Dockerfile` to create a Docker image, run a Docker container, and develop code in the container. Docker itself builds, tests, and releases new Docker versions using this container. -If you followed the procedures that -set up the prerequisites, you should have a fork of the `docker/docker` +If you followed the procedures that +set up Git for contributing, you should have a fork of the `docker/docker` repository. You also created a branch called `dry-run-test`. In this section, you continue working with your fork on this branch. @@ -97,10 +97,17 @@ environment. 3. Change into the root of your forked repository. $ cd ~/repos/docker-fork + + If you are following along with this guide, you created a `dry-run-test` + branch when you set up Git for + contributing 4. Ensure you are on your `dry-run-test` branch. $ git checkout dry-run-test + + If you get a message that the branch doesn't exist, add the `-b` flag so the + command both creates the branch and checks it out. 5. Compile your development environment container into an image. @@ -232,7 +239,8 @@ build and run a `docker` binary in your container. You will create one in the next steps. -4. From the `/go/src/github.com/docker/docker` directory make a `docker` binary with the `make.sh` script. +4. From the `/go/src/github.com/docker/docker` directory make a `docker` binary +with the `make.sh` script. root@5f8630b873fe:/go/src/github.com/docker/docker# hack/make.sh binary @@ -357,7 +365,8 @@ container. Your location will be different because it reflects your environment. -3. Create a container using `dry-run-test` but this time mount your repository onto the `/go` directory inside the container. +3. Create a container using `dry-run-test` but this time mount your repository +onto the `/go` directory inside the container. $ docker run --privileged --rm -ti -v `pwd`:/go/src/github.com/docker/docker dry-run-test /bin/bash @@ -408,4 +417,5 @@ container. Congratulations, you have successfully achieved Docker inception. At this point, you've set up your development environment and verified almost all the essential processes you need to contribute. Of course, before you start contributing, -[you'll need to learn one more piece of the development environment, the test framework](/project/test-and-docs/). +[you'll need to learn one more piece of the development environment, the test +framework](/project/test-and-docs/). diff --git a/docs/sources/project/work-issue.md b/docs/sources/project/work-issue.md index 190cec055..561bd231f 100644 --- a/docs/sources/project/work-issue.md +++ b/docs/sources/project/work-issue.md @@ -30,8 +30,10 @@ Follow this workflow as you work: source into a development container and iterate that way. For documentation alone, you can work on your local host. - Review if you forgot the details - of working with a container. + Make sure you don't change files in the `vendor` directory and its + subdirectories; they contain third-party dependency code. Review if you forgot the details of + working with a container. 3. Test your changes as you work. From 7e95b13460a58db75630d2d795482f39c68762c2 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 23 Mar 2015 20:20:10 -0700 Subject: [PATCH 062/999] Use a structure to keep the allocated ips pool. Fixes #11624. Signed-off-by: David Calavera --- daemon/networkdriver/bridge/driver.go | 15 +- daemon/networkdriver/ipallocator/allocator.go | 43 +++-- .../ipallocator/allocator_test.go | 181 +++++++++--------- 3 files changed, 128 insertions(+), 111 deletions(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index aa139b9a3..96a81e7e8 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -80,6 +80,7 @@ var ( defaultBindingIP = net.ParseIP("0.0.0.0") currentInterfaces = ifaces{c: make(map[string]*networkInterface)} + ipAllocator = ipallocator.New() ) func InitDriver(job *engine.Job) engine.Status { @@ -244,7 +245,7 @@ func InitDriver(job *engine.Job) engine.Status { return job.Error(err) } log.Debugf("Subnet: %v", subnet) - if err := ipallocator.RegisterSubnet(bridgeIPv4Network, subnet); err != nil { + if err := ipAllocator.RegisterSubnet(bridgeIPv4Network, subnet); err != nil { return job.Error(err) } } @@ -255,14 +256,14 @@ func InitDriver(job *engine.Job) engine.Status { return job.Error(err) } log.Debugf("Subnet: %v", subnet) - if err := ipallocator.RegisterSubnet(subnet, subnet); err != nil { + if err := ipAllocator.RegisterSubnet(subnet, subnet); err != nil { return job.Error(err) } globalIPv6Network = subnet } // Block BridgeIP in IP allocator - ipallocator.RequestIP(bridgeIPv4Network, bridgeIPv4Network.IP) + ipAllocator.RequestIP(bridgeIPv4Network, bridgeIPv4Network.IP) // https://github.com/docker/docker/issues/2768 job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", bridgeIPv4Network.IP) @@ -509,7 +510,7 @@ func Allocate(job *engine.Job) engine.Status { globalIPv6 net.IP ) - ip, err = ipallocator.RequestIP(bridgeIPv4Network, requestedIP) + ip, err = ipAllocator.RequestIP(bridgeIPv4Network, requestedIP) if err != nil { return job.Error(err) } @@ -530,7 +531,7 @@ func Allocate(job *engine.Job) engine.Status { } } - globalIPv6, err = ipallocator.RequestIP(globalIPv6Network, requestedIPv6) + globalIPv6, err = ipAllocator.RequestIP(globalIPv6Network, requestedIPv6) if err != nil { log.Errorf("Allocator: RequestIP v6: %v", err) return job.Error(err) @@ -591,11 +592,11 @@ func Release(job *engine.Job) engine.Status { } } - if err := ipallocator.ReleaseIP(bridgeIPv4Network, containerInterface.IP); err != nil { + if err := ipAllocator.ReleaseIP(bridgeIPv4Network, containerInterface.IP); err != nil { log.Infof("Unable to release IPv4 %s", err) } if globalIPv6Network != nil { - if err := ipallocator.ReleaseIP(globalIPv6Network, containerInterface.IPv6); err != nil { + if err := ipAllocator.ReleaseIP(globalIPv6Network, containerInterface.IPv6); err != nil { log.Infof("Unable to release IPv6 %s", err) } } diff --git a/daemon/networkdriver/ipallocator/allocator.go b/daemon/networkdriver/ipallocator/allocator.go index a728d1bac..628500d0d 100644 --- a/daemon/networkdriver/ipallocator/allocator.go +++ b/daemon/networkdriver/ipallocator/allocator.go @@ -41,19 +41,24 @@ var ( ErrBadSubnet = errors.New("network does not contain specified subnet") ) -var ( - lock = sync.Mutex{} - allocatedIPs = networkSet{} -) +type IPAllocator struct { + allocatedIPs networkSet + mutex sync.Mutex +} + +func New() *IPAllocator { + return &IPAllocator{networkSet{}, sync.Mutex{}} +} // RegisterSubnet registers network in global allocator with bounds // defined by subnet. If you want to use network range you must call // this method before first RequestIP, otherwise full network range will be used -func RegisterSubnet(network *net.IPNet, subnet *net.IPNet) error { - lock.Lock() - defer lock.Unlock() +func (a *IPAllocator) RegisterSubnet(network *net.IPNet, subnet *net.IPNet) error { + a.mutex.Lock() + defer a.mutex.Unlock() + key := network.String() - if _, ok := allocatedIPs[key]; ok { + if _, ok := a.allocatedIPs[key]; ok { return ErrNetworkAlreadyRegistered } n := newAllocatedMap(network) @@ -68,7 +73,7 @@ func RegisterSubnet(network *net.IPNet, subnet *net.IPNet) error { n.begin.Set(begin) n.end.Set(end) n.last.Sub(begin, big.NewInt(1)) - allocatedIPs[key] = n + a.allocatedIPs[key] = n return nil } @@ -76,14 +81,15 @@ func RegisterSubnet(network *net.IPNet, subnet *net.IPNet) error { // will return the next available ip if the ip provided is nil. If the // ip provided is not nil it will validate that the provided ip is available // for use or return an error -func RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) { - lock.Lock() - defer lock.Unlock() +func (a *IPAllocator) RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) { + a.mutex.Lock() + defer a.mutex.Unlock() + key := network.String() - allocated, ok := allocatedIPs[key] + allocated, ok := a.allocatedIPs[key] if !ok { allocated = newAllocatedMap(network) - allocatedIPs[key] = allocated + a.allocatedIPs[key] = allocated } if ip == nil { @@ -94,10 +100,11 @@ func RequestIP(network *net.IPNet, ip net.IP) (net.IP, error) { // ReleaseIP adds the provided ip back into the pool of // available ips to be returned for use. -func ReleaseIP(network *net.IPNet, ip net.IP) error { - lock.Lock() - defer lock.Unlock() - if allocated, exists := allocatedIPs[network.String()]; exists { +func (a *IPAllocator) ReleaseIP(network *net.IPNet, ip net.IP) error { + a.mutex.Lock() + defer a.mutex.Unlock() + + if allocated, exists := a.allocatedIPs[network.String()]; exists { delete(allocated.p, ip.String()) } return nil diff --git a/daemon/networkdriver/ipallocator/allocator_test.go b/daemon/networkdriver/ipallocator/allocator_test.go index 8e0d8fdca..fffe6e338 100644 --- a/daemon/networkdriver/ipallocator/allocator_test.go +++ b/daemon/networkdriver/ipallocator/allocator_test.go @@ -7,10 +7,6 @@ import ( "testing" ) -func reset() { - allocatedIPs = networkSet{} -} - func TestConversion(t *testing.T) { ip := net.ParseIP("127.0.0.1") i := ipToBigInt(ip) @@ -52,7 +48,8 @@ func TestConversionIPv6(t *testing.T) { } func TestRequestNewIps(t *testing.T) { - defer reset() + a := New() + network := &net.IPNet{ IP: []byte{192, 168, 0, 1}, Mask: []byte{255, 255, 255, 0}, @@ -62,7 +59,7 @@ func TestRequestNewIps(t *testing.T) { var err error for i := 1; i < 10; i++ { - ip, err = RequestIP(network, nil) + ip, err = a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -72,10 +69,10 @@ func TestRequestNewIps(t *testing.T) { } } value := bigIntToIP(big.NewInt(0).Add(ipToBigInt(ip), big.NewInt(1))).String() - if err := ReleaseIP(network, ip); err != nil { + if err := a.ReleaseIP(network, ip); err != nil { t.Fatal(err) } - ip, err = RequestIP(network, nil) + ip, err = a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -85,7 +82,8 @@ func TestRequestNewIps(t *testing.T) { } func TestRequestNewIpV6(t *testing.T) { - defer reset() + a := New() + network := &net.IPNet{ IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask @@ -94,7 +92,7 @@ func TestRequestNewIpV6(t *testing.T) { var ip net.IP var err error for i := 1; i < 10; i++ { - ip, err = RequestIP(network, nil) + ip, err = a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -104,10 +102,10 @@ func TestRequestNewIpV6(t *testing.T) { } } value := bigIntToIP(big.NewInt(0).Add(ipToBigInt(ip), big.NewInt(1))).String() - if err := ReleaseIP(network, ip); err != nil { + if err := a.ReleaseIP(network, ip); err != nil { t.Fatal(err) } - ip, err = RequestIP(network, nil) + ip, err = a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -117,68 +115,70 @@ func TestRequestNewIpV6(t *testing.T) { } func TestReleaseIp(t *testing.T) { - defer reset() + a := New() + network := &net.IPNet{ IP: []byte{192, 168, 0, 1}, Mask: []byte{255, 255, 255, 0}, } - ip, err := RequestIP(network, nil) + ip, err := a.RequestIP(network, nil) if err != nil { t.Fatal(err) } - if err := ReleaseIP(network, ip); err != nil { + if err := a.ReleaseIP(network, ip); err != nil { t.Fatal(err) } } func TestReleaseIpV6(t *testing.T) { - defer reset() + a := New() + network := &net.IPNet{ IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask } - ip, err := RequestIP(network, nil) + ip, err := a.RequestIP(network, nil) if err != nil { t.Fatal(err) } - if err := ReleaseIP(network, ip); err != nil { + if err := a.ReleaseIP(network, ip); err != nil { t.Fatal(err) } } func TestGetReleasedIp(t *testing.T) { - defer reset() + a := New() network := &net.IPNet{ IP: []byte{192, 168, 0, 1}, Mask: []byte{255, 255, 255, 0}, } - ip, err := RequestIP(network, nil) + ip, err := a.RequestIP(network, nil) if err != nil { t.Fatal(err) } value := ip.String() - if err := ReleaseIP(network, ip); err != nil { + if err := a.ReleaseIP(network, ip); err != nil { t.Fatal(err) } for i := 0; i < 253; i++ { - _, err = RequestIP(network, nil) + _, err = a.RequestIP(network, nil) if err != nil { t.Fatal(err) } - err = ReleaseIP(network, ip) + err = a.ReleaseIP(network, ip) if err != nil { t.Fatal(err) } } - ip, err = RequestIP(network, nil) + ip, err = a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -189,34 +189,35 @@ func TestGetReleasedIp(t *testing.T) { } func TestGetReleasedIpV6(t *testing.T) { - defer reset() + a := New() + network := &net.IPNet{ IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 0}, } - ip, err := RequestIP(network, nil) + ip, err := a.RequestIP(network, nil) if err != nil { t.Fatal(err) } value := ip.String() - if err := ReleaseIP(network, ip); err != nil { + if err := a.ReleaseIP(network, ip); err != nil { t.Fatal(err) } for i := 0; i < 253; i++ { - _, err = RequestIP(network, nil) + _, err = a.RequestIP(network, nil) if err != nil { t.Fatal(err) } - err = ReleaseIP(network, ip) + err = a.ReleaseIP(network, ip) if err != nil { t.Fatal(err) } } - ip, err = RequestIP(network, nil) + ip, err = a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -227,7 +228,8 @@ func TestGetReleasedIpV6(t *testing.T) { } func TestRequestSpecificIp(t *testing.T) { - defer reset() + a := New() + network := &net.IPNet{ IP: []byte{192, 168, 0, 1}, Mask: []byte{255, 255, 255, 224}, @@ -236,23 +238,24 @@ func TestRequestSpecificIp(t *testing.T) { ip := net.ParseIP("192.168.0.5") // Request a "good" IP. - if _, err := RequestIP(network, ip); err != nil { + if _, err := a.RequestIP(network, ip); err != nil { t.Fatal(err) } // Request the same IP again. - if _, err := RequestIP(network, ip); err != ErrIPAlreadyAllocated { + if _, err := a.RequestIP(network, ip); err != ErrIPAlreadyAllocated { t.Fatalf("Got the same IP twice: %#v", err) } // Request an out of range IP. - if _, err := RequestIP(network, net.ParseIP("192.168.0.42")); err != ErrIPOutOfRange { + if _, err := a.RequestIP(network, net.ParseIP("192.168.0.42")); err != ErrIPOutOfRange { t.Fatalf("Got an out of range IP: %#v", err) } } func TestRequestSpecificIpV6(t *testing.T) { - defer reset() + a := New() + network := &net.IPNet{ IP: []byte{0x2a, 0x00, 0x14, 0x50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, Mask: []byte{255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0}, // /64 netmask @@ -261,22 +264,24 @@ func TestRequestSpecificIpV6(t *testing.T) { ip := net.ParseIP("2a00:1450::5") // Request a "good" IP. - if _, err := RequestIP(network, ip); err != nil { + if _, err := a.RequestIP(network, ip); err != nil { t.Fatal(err) } // Request the same IP again. - if _, err := RequestIP(network, ip); err != ErrIPAlreadyAllocated { + if _, err := a.RequestIP(network, ip); err != ErrIPAlreadyAllocated { t.Fatalf("Got the same IP twice: %#v", err) } // Request an out of range IP. - if _, err := RequestIP(network, net.ParseIP("2a00:1500::1")); err != ErrIPOutOfRange { + if _, err := a.RequestIP(network, net.ParseIP("2a00:1500::1")); err != ErrIPOutOfRange { t.Fatalf("Got an out of range IP: %#v", err) } } func TestIPAllocator(t *testing.T) { + a := New() + expectedIPs := []net.IP{ 0: net.IPv4(127, 0, 0, 1), 1: net.IPv4(127, 0, 0, 2), @@ -296,7 +301,7 @@ func TestIPAllocator(t *testing.T) { // Check that we get 6 IPs, from 127.0.0.1–127.0.0.6, in that // order. for i := 0; i < 6; i++ { - ip, err := RequestIP(network, nil) + ip, err := a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -332,25 +337,25 @@ func TestIPAllocator(t *testing.T) { // ↑ // Check that there are no more IPs - ip, err := RequestIP(network, nil) + ip, err := a.RequestIP(network, nil) if err == nil { t.Fatalf("There shouldn't be any IP addresses at this point, got %s\n", ip) } // Release some IPs in non-sequential order - if err := ReleaseIP(network, expectedIPs[3]); err != nil { + if err := a.ReleaseIP(network, expectedIPs[3]); err != nil { t.Fatal(err) } // 1(u) - 2(u) - 3(u) - 4(f) - 5(u) - 6(u) // ↑ - if err := ReleaseIP(network, expectedIPs[2]); err != nil { + if err := a.ReleaseIP(network, expectedIPs[2]); err != nil { t.Fatal(err) } // 1(u) - 2(u) - 3(f) - 4(f) - 5(u) - 6(u) // ↑ - if err := ReleaseIP(network, expectedIPs[4]); err != nil { + if err := a.ReleaseIP(network, expectedIPs[4]); err != nil { t.Fatal(err) } // 1(u) - 2(u) - 3(f) - 4(f) - 5(f) - 6(u) @@ -360,7 +365,7 @@ func TestIPAllocator(t *testing.T) { // with the first released IP newIPs := make([]net.IP, 3) for i := 0; i < 3; i++ { - ip, err := RequestIP(network, nil) + ip, err := a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -371,14 +376,15 @@ func TestIPAllocator(t *testing.T) { assertIPEquals(t, expectedIPs[3], newIPs[1]) assertIPEquals(t, expectedIPs[4], newIPs[2]) - _, err = RequestIP(network, nil) + _, err = a.RequestIP(network, nil) if err == nil { t.Fatal("There shouldn't be any IP addresses at this point") } } func TestAllocateFirstIP(t *testing.T) { - defer reset() + a := New() + network := &net.IPNet{ IP: []byte{192, 168, 0, 0}, Mask: []byte{255, 255, 255, 0}, @@ -387,7 +393,7 @@ func TestAllocateFirstIP(t *testing.T) { firstIP := network.IP.To4().Mask(network.Mask) first := big.NewInt(0).Add(ipToBigInt(firstIP), big.NewInt(1)) - ip, err := RequestIP(network, nil) + ip, err := a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -399,7 +405,8 @@ func TestAllocateFirstIP(t *testing.T) { } func TestAllocateAllIps(t *testing.T) { - defer reset() + a := New() + network := &net.IPNet{ IP: []byte{192, 168, 0, 1}, Mask: []byte{255, 255, 255, 0}, @@ -412,7 +419,7 @@ func TestAllocateAllIps(t *testing.T) { ) for err == nil { - current, err = RequestIP(network, nil) + current, err = a.RequestIP(network, nil) if isFirst { first = current isFirst = false @@ -423,15 +430,15 @@ func TestAllocateAllIps(t *testing.T) { t.Fatal(err) } - if _, err := RequestIP(network, nil); err != ErrNoAvailableIPs { + if _, err := a.RequestIP(network, nil); err != ErrNoAvailableIPs { t.Fatal(err) } - if err := ReleaseIP(network, first); err != nil { + if err := a.ReleaseIP(network, first); err != nil { t.Fatal(err) } - again, err := RequestIP(network, nil) + again, err := a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -439,17 +446,17 @@ func TestAllocateAllIps(t *testing.T) { assertIPEquals(t, first, again) // ensure that alloc.last == alloc.begin won't result in dead loop - if _, err := RequestIP(network, nil); err != ErrNoAvailableIPs { + if _, err := a.RequestIP(network, nil); err != ErrNoAvailableIPs { t.Fatal(err) } // Test by making alloc.last the only free ip and ensure we get it back // #1. first of the range, (alloc.last == ipToInt(first) already) - if err := ReleaseIP(network, first); err != nil { + if err := a.ReleaseIP(network, first); err != nil { t.Fatal(err) } - ret, err := RequestIP(network, nil) + ret, err := a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -458,9 +465,9 @@ func TestAllocateAllIps(t *testing.T) { // #2. last of the range, note that current is the last one last := net.IPv4(192, 168, 0, 254) - setLastTo(t, network, last) + setLastTo(t, a, network, last) - ret, err = RequestIP(network, nil) + ret, err = a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -469,9 +476,9 @@ func TestAllocateAllIps(t *testing.T) { // #3. middle of the range mid := net.IPv4(192, 168, 0, 7) - setLastTo(t, network, mid) + setLastTo(t, a, network, mid) - ret, err = RequestIP(network, nil) + ret, err = a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -481,25 +488,25 @@ func TestAllocateAllIps(t *testing.T) { // make sure the pool is full when calling setLastTo. // we don't cheat here -func setLastTo(t *testing.T, network *net.IPNet, ip net.IP) { - if err := ReleaseIP(network, ip); err != nil { +func setLastTo(t *testing.T, a *IPAllocator, network *net.IPNet, ip net.IP) { + if err := a.ReleaseIP(network, ip); err != nil { t.Fatal(err) } - ret, err := RequestIP(network, nil) + ret, err := a.RequestIP(network, nil) if err != nil { t.Fatal(err) } assertIPEquals(t, ip, ret) - if err := ReleaseIP(network, ip); err != nil { + if err := a.ReleaseIP(network, ip); err != nil { t.Fatal(err) } } func TestAllocateDifferentSubnets(t *testing.T) { - defer reset() + a := New() network1 := &net.IPNet{ IP: []byte{192, 168, 0, 1}, Mask: []byte{255, 255, 255, 0}, @@ -528,39 +535,39 @@ func TestAllocateDifferentSubnets(t *testing.T) { 8: net.ParseIP("2a00:1632::2"), } - ip11, err := RequestIP(network1, nil) + ip11, err := a.RequestIP(network1, nil) if err != nil { t.Fatal(err) } - ip12, err := RequestIP(network1, nil) + ip12, err := a.RequestIP(network1, nil) if err != nil { t.Fatal(err) } - ip21, err := RequestIP(network2, nil) + ip21, err := a.RequestIP(network2, nil) if err != nil { t.Fatal(err) } - ip22, err := RequestIP(network2, nil) + ip22, err := a.RequestIP(network2, nil) if err != nil { t.Fatal(err) } - ip31, err := RequestIP(network3, nil) + ip31, err := a.RequestIP(network3, nil) if err != nil { t.Fatal(err) } - ip32, err := RequestIP(network3, nil) + ip32, err := a.RequestIP(network3, nil) if err != nil { t.Fatal(err) } - ip33, err := RequestIP(network3, nil) + ip33, err := a.RequestIP(network3, nil) if err != nil { t.Fatal(err) } - ip41, err := RequestIP(network4, nil) + ip41, err := a.RequestIP(network4, nil) if err != nil { t.Fatal(err) } - ip42, err := RequestIP(network4, nil) + ip42, err := a.RequestIP(network4, nil) if err != nil { t.Fatal(err) } @@ -576,7 +583,7 @@ func TestAllocateDifferentSubnets(t *testing.T) { } func TestRegisterBadTwice(t *testing.T) { - defer reset() + a := New() network := &net.IPNet{ IP: []byte{192, 168, 1, 1}, Mask: []byte{255, 255, 255, 0}, @@ -586,20 +593,20 @@ func TestRegisterBadTwice(t *testing.T) { Mask: []byte{255, 255, 255, 248}, } - if err := RegisterSubnet(network, subnet); err != nil { + if err := a.RegisterSubnet(network, subnet); err != nil { t.Fatal(err) } subnet = &net.IPNet{ IP: []byte{192, 168, 1, 16}, Mask: []byte{255, 255, 255, 248}, } - if err := RegisterSubnet(network, subnet); err != ErrNetworkAlreadyRegistered { + if err := a.RegisterSubnet(network, subnet); err != ErrNetworkAlreadyRegistered { t.Fatalf("Expecteded ErrNetworkAlreadyRegistered error, got %v", err) } } func TestRegisterBadRange(t *testing.T) { - defer reset() + a := New() network := &net.IPNet{ IP: []byte{192, 168, 1, 1}, Mask: []byte{255, 255, 255, 0}, @@ -608,13 +615,13 @@ func TestRegisterBadRange(t *testing.T) { IP: []byte{192, 168, 1, 1}, Mask: []byte{255, 255, 0, 0}, } - if err := RegisterSubnet(network, subnet); err != ErrBadSubnet { + if err := a.RegisterSubnet(network, subnet); err != ErrBadSubnet { t.Fatalf("Expected ErrBadSubnet error, got %v", err) } } func TestAllocateFromRange(t *testing.T) { - defer reset() + a := New() network := &net.IPNet{ IP: []byte{192, 168, 0, 1}, Mask: []byte{255, 255, 255, 0}, @@ -625,7 +632,7 @@ func TestAllocateFromRange(t *testing.T) { Mask: []byte{255, 255, 255, 248}, } - if err := RegisterSubnet(network, subnet); err != nil { + if err := a.RegisterSubnet(network, subnet); err != nil { t.Fatal(err) } expectedIPs := []net.IP{ @@ -637,19 +644,19 @@ func TestAllocateFromRange(t *testing.T) { 5: net.IPv4(192, 168, 0, 14), } for _, ip := range expectedIPs { - rip, err := RequestIP(network, nil) + rip, err := a.RequestIP(network, nil) if err != nil { t.Fatal(err) } assertIPEquals(t, ip, rip) } - if _, err := RequestIP(network, nil); err != ErrNoAvailableIPs { + if _, err := a.RequestIP(network, nil); err != ErrNoAvailableIPs { t.Fatalf("Expected ErrNoAvailableIPs error, got %v", err) } for _, ip := range expectedIPs { - ReleaseIP(network, ip) - rip, err := RequestIP(network, nil) + a.ReleaseIP(network, ip) + rip, err := a.RequestIP(network, nil) if err != nil { t.Fatal(err) } @@ -669,13 +676,15 @@ func BenchmarkRequestIP(b *testing.B) { Mask: []byte{255, 255, 255, 0}, } b.ResetTimer() + for i := 0; i < b.N; i++ { + a := New() + for j := 0; j < 253; j++ { - _, err := RequestIP(network, nil) + _, err := a.RequestIP(network, nil) if err != nil { b.Fatal(err) } } - reset() } } From fc325274e84a0c16b758e3d6b121c9af1150cce2 Mon Sep 17 00:00:00 2001 From: Anton Tiurin Date: Wed, 25 Mar 2015 00:11:04 +0300 Subject: [PATCH 063/999] History.Swap Use parallel assignment to swap elements, as it's a more idiomatic way for golang than using a temp variable. Signed-off-by: Anton Tiurin --- daemon/history.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/daemon/history.go b/daemon/history.go index 0b125ad2b..f71750872 100644 --- a/daemon/history.go +++ b/daemon/history.go @@ -19,9 +19,7 @@ func (history *History) Less(i, j int) bool { func (history *History) Swap(i, j int) { containers := *history - tmp := containers[i] - containers[i] = containers[j] - containers[j] = tmp + containers[i], containers[j] = containers[j], containers[i] } func (history *History) Add(container *Container) { From a153e80f72274c05589145011c16cc1d7f8ee75f Mon Sep 17 00:00:00 2001 From: jimmyxian Date: Tue, 17 Mar 2015 19:37:25 +0800 Subject: [PATCH 064/999] fix docker ps help message Signed-off-by: Xian Chaobo --- docs/sources/reference/commandline/cli.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 1f61432b5..57bda5d47 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1497,6 +1497,8 @@ The filtering flag (`-f` or `--filter)` format is a `key=value` pair. If there i than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`) Current filters: + * id (container's id) + * name (container's name) * exited (int - the code of exited containers. Only useful with '--all') * status (restarting|running|paused|exited) From 08d75bc450b0927966ef9c4bebe42256cf4a64ac Mon Sep 17 00:00:00 2001 From: Matthew Mayer Date: Tue, 24 Mar 2015 14:56:45 -0700 Subject: [PATCH 065/999] Squashed commit of the following: commit d379f7645026001ce57fd6421c819f6c7df77964 Author: Matthew Mayer Date: Mon Mar 23 22:13:06 2015 -0700 Removes unused imports. Signed-off-by: Matthew Mayer commit 6e1f77c7f1566c8719087d88fbe06bade122691c Author: Matthew Mayer Date: Mon Mar 23 20:41:16 2015 -0700 Removes bind dir creation in daemon start. Signed-off-by: Matthew Mayer Signed-off-by: Matthew Mayer --- daemon/start.go | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/daemon/start.go b/daemon/start.go index e51ada22a..381f09f7f 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -1,10 +1,6 @@ package daemon import ( - "fmt" - "os" - "strings" - "github.com/docker/docker/engine" "github.com/docker/docker/runconfig" ) @@ -54,22 +50,6 @@ func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig. return err } - // FIXME: this should be handled by the volume subsystem - // Validate the HostConfig binds. Make sure that: - // the source exists - for _, bind := range hostConfig.Binds { - splitBind := strings.Split(bind, ":") - source := splitBind[0] - - // ensure the source exists on the host - _, err := os.Stat(source) - if err != nil && os.IsNotExist(err) { - err = os.MkdirAll(source, 0755) - if err != nil { - return fmt.Errorf("Could not create local directory '%s' for bind mount: %v!", source, err) - } - } - } // Register any links from the host config before starting the container if err := daemon.RegisterLinks(container, hostConfig); err != nil { return err From 246cab90f216e68c25178ffd19756ab864b809d0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 24 Mar 2015 15:53:23 -0700 Subject: [PATCH 066/999] Mkdir for lxc root dir before setup of symlink Signed-off-by: Michael Crosby --- daemon/execdriver/lxc/driver.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index f45c21445..a9c214c21 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -49,6 +49,9 @@ type activeContainer struct { } func NewDriver(root, initPath string, apparmor bool) (*driver, error) { + if err := os.MkdirAll(root, 0700); err != nil { + return nil, err + } // setup unconfined symlink if err := linkLxcStart(root); err != nil { return nil, err From 772833274fd84b3c960ccab14258b7e5a00b18cd Mon Sep 17 00:00:00 2001 From: Rick Wieman Date: Wed, 25 Feb 2015 19:33:01 +0100 Subject: [PATCH 067/999] Moved pidfile from utils to pkg Fixes #10958 by moving utils.daemon to pkg.pidfile. Test cases were also added. Updated the daemon to use the new pidfile. Signed-off-by: Rick Wieman --- daemon/daemon.go | 6 +++-- pkg/pidfile/pidfile.go | 44 +++++++++++++++++++++++++++++++++++++ pkg/pidfile/pidfile_test.go | 32 +++++++++++++++++++++++++++ utils/daemon.go | 36 ------------------------------ 4 files changed, 80 insertions(+), 38 deletions(-) create mode 100644 pkg/pidfile/pidfile.go create mode 100644 pkg/pidfile/pidfile_test.go delete mode 100644 utils/daemon.go diff --git a/daemon/daemon.go b/daemon/daemon.go index a437fac1b..434b78a33 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -36,6 +36,7 @@ import ( "github.com/docker/docker/pkg/namesgenerator" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/kernel" + "github.com/docker/docker/pkg/pidfile" "github.com/docker/docker/pkg/resolvconf" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/sysinfo" @@ -836,12 +837,13 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) // Claim the pidfile first, to avoid any and all unexpected race conditions. // Some of the init doesn't need a pidfile lock - but let's not try to be smart. if config.Pidfile != "" { - if err := utils.CreatePidFile(config.Pidfile); err != nil { + file, err := pidfile.New(config.Pidfile) + if err != nil { return nil, err } eng.OnShutdown(func() { // Always release the pidfile last, just in case - utils.RemovePidFile(config.Pidfile) + file.Remove() }) } diff --git a/pkg/pidfile/pidfile.go b/pkg/pidfile/pidfile.go new file mode 100644 index 000000000..21a543879 --- /dev/null +++ b/pkg/pidfile/pidfile.go @@ -0,0 +1,44 @@ +package pidfile + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" + "strconv" +) + +type PidFile struct { + path string +} + +func checkPidFileAlreadyExists(path string) error { + if pidString, err := ioutil.ReadFile(path); err == nil { + if pid, err := strconv.Atoi(string(pidString)); err == nil { + if _, err := os.Stat(filepath.Join("/proc", string(pid))); err == nil { + return fmt.Errorf("pid file found, ensure docker is not running or delete %s", path) + } + } + } + return nil +} + +func New(path string) (file *PidFile, err error) { + if err := checkPidFileAlreadyExists(path); err != nil { + return nil, err + } + + file = &PidFile{path: path} + err = ioutil.WriteFile(path, []byte(fmt.Sprintf("%d", os.Getpid())), 0644) + + return file, err +} + +func (file PidFile) Remove() error { + if err := os.Remove(file.path); err != nil { + log.Printf("Error removing %s: %s", file.path, err) + return err + } + return nil +} diff --git a/pkg/pidfile/pidfile_test.go b/pkg/pidfile/pidfile_test.go new file mode 100644 index 000000000..6ed9cfc38 --- /dev/null +++ b/pkg/pidfile/pidfile_test.go @@ -0,0 +1,32 @@ +package pidfile + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +func TestNewAndRemove(t *testing.T) { + dir, err := ioutil.TempDir(os.TempDir(), "test-pidfile") + if err != nil { + t.Fatal("Could not create test directory") + } + + file, err := New(filepath.Join(dir, "testfile")) + if err != nil { + t.Fatal("Could not create test file", err) + } + + if err := file.Remove(); err != nil { + t.Fatal("Could not delete created test file") + } +} + +func TestRemoveInvalidPath(t *testing.T) { + file := PidFile{path: filepath.Join("foo", "bar")} + + if err := file.Remove(); err == nil { + t.Fatal("Non-existing file doesn't give an error on delete") + } +} diff --git a/utils/daemon.go b/utils/daemon.go deleted file mode 100644 index 871122ed5..000000000 --- a/utils/daemon.go +++ /dev/null @@ -1,36 +0,0 @@ -package utils - -import ( - "fmt" - "io/ioutil" - "log" - "os" - "strconv" -) - -func CreatePidFile(pidfile string) error { - if pidString, err := ioutil.ReadFile(pidfile); err == nil { - pid, err := strconv.Atoi(string(pidString)) - if err == nil { - if _, err := os.Stat(fmt.Sprintf("/proc/%d/", pid)); err == nil { - return fmt.Errorf("pid file found, ensure docker is not running or delete %s", pidfile) - } - } - } - - file, err := os.Create(pidfile) - if err != nil { - return err - } - - defer file.Close() - - _, err = fmt.Fprintf(file, "%d", os.Getpid()) - return err -} - -func RemovePidFile(pidfile string) { - if err := os.Remove(pidfile); err != nil { - log.Printf("Error removing %s: %s", pidfile, err) - } -} From 8900ae2928cea8f4b5d52ff68253cad2504edd6c Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Tue, 24 Mar 2015 16:27:35 -0700 Subject: [PATCH 068/999] Revert all but TestPullImageFromCentralRegistry changes Signed-off-by: Arnaud Porterie --- Dockerfile | 3 -- hack/make/.ensure-registry | 10 ------ hack/make/.integration-daemon-start | 1 - hack/make/test-integration-cli | 1 - integration-cli/docker_cli_pull_test.go | 27 -------------- integration-cli/docker_test_vars.go | 3 +- integration-cli/registry.go | 47 ------------------------- 7 files changed, 1 insertion(+), 91 deletions(-) delete mode 100644 hack/make/.ensure-registry diff --git a/Dockerfile b/Dockerfile index 7d14be824..b06407613 100644 --- a/Dockerfile +++ b/Dockerfile @@ -164,9 +164,6 @@ RUN set -x \ && (cd /go/src/github.com/BurntSushi/toml && git checkout -q $TOMLV_COMMIT) \ && go install -v github.com/BurntSushi/toml/cmd/tomlv -COPY contrib/download-frozen-image.sh /go/src/github.com/docker/docker/contrib/ -RUN ./contrib/download-frozen-image.sh ./integration-cli/registry registry - # Wrap all commands in the "docker-in-docker" script to allow nested containers ENTRYPOINT ["hack/dind"] diff --git a/hack/make/.ensure-registry b/hack/make/.ensure-registry deleted file mode 100644 index f4dec2ee4..000000000 --- a/hack/make/.ensure-registry +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -set -e - -if ! docker inspect registry > /dev/null; then - if [ -d /docker-registry ]; then - ( set -x; docker build -t registry /docker-registry ) - else - ( set -x; tar -cC integration-cli/registry . | docker load & ) - fi -fi diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start index e4f9762aa..570c6c7a9 100644 --- a/hack/make/.integration-daemon-start +++ b/hack/make/.integration-daemon-start @@ -19,7 +19,6 @@ if [ -z "$DOCKER_TEST_HOST" ]; then export DOCKER_HOST="unix://$(cd "$DEST" && pwd)/docker.sock" # "pwd" tricks to make sure $DEST is an absolute path, not a relative one ( set -x; exec \ docker --daemon --debug \ - --insecure-registry 0.0.0.0:5000 \ --host "$DOCKER_HOST" \ --storage-driver "$DOCKER_GRAPHDRIVER" \ --exec-driver "$DOCKER_EXECDRIVER" \ diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli index 23058ccfd..3ef41d919 100644 --- a/hack/make/test-integration-cli +++ b/hack/make/test-integration-cli @@ -18,7 +18,6 @@ bundle_test_integration_cli() { if ! { source "$(dirname "$BASH_SOURCE")/.ensure-frozen-images" source "$(dirname "$BASH_SOURCE")/.ensure-httpserver" - source "$(dirname "$BASH_SOURCE")/.ensure-registry" source "$(dirname "$BASH_SOURCE")/.ensure-emptyfs" bundle_test_integration_cli diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index efecbdf01..6e5ddb840 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -96,33 +96,6 @@ func TestPullImageFromCentralRegistry(t *testing.T) { logDone("pull - pull hello-world") } -// pulling an image from the local registry should work -func TestPullImageFromlocalRegistry(t *testing.T) { - defer deleteAllContainers() - - if err := startRegistryV1(); err != nil { - t.Fatal(err) - } - repoName := privateV1RegistryURL - defer deleteImages(repoName) - - repo := fmt.Sprintf("%v/%v:%v", repoName, "busybox", "latest") - if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repo)); err != nil { - t.Fatalf("Failed to tag image %v: error %v, output %q", repo, err, out) - } - defer deleteImages(repo) - - if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "push", repo)); err != nil { - t.Fatalf("Failed to push image %v: error %v, output %q", repo, err, string(out)) - } - - pullCmd := exec.Command(dockerBinary, "pull", repo) - if out, _, err := runCommandWithOutput(pullCmd); err != nil { - t.Fatalf("pulling the hello-world image from the registry has failed: %s, %v", out, err) - } - logDone("pull - pull local hello-world") -} - // 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") diff --git a/integration-cli/docker_test_vars.go b/integration-cli/docker_test_vars.go index a46b2ce64..ff2ec7406 100644 --- a/integration-cli/docker_test_vars.go +++ b/integration-cli/docker_test_vars.go @@ -14,8 +14,7 @@ var ( registryImageName = "registry" // the private registry to use for tests - privateRegistryURL = "127.0.0.1:5000" - privateV1RegistryURL = "0.0.0.0:5000" + privateRegistryURL = "127.0.0.1:5000" dockerBasePath = "/var/lib/docker" execDriverPath = dockerBasePath + "/execdriver/native" diff --git a/integration-cli/registry.go b/integration-cli/registry.go index 32ed5a7b8..8290e710f 100644 --- a/integration-cli/registry.go +++ b/integration-cli/registry.go @@ -7,9 +7,7 @@ import ( "os" "os/exec" "path/filepath" - "strings" "testing" - "time" ) const v2binary = "registry-v2" @@ -71,48 +69,3 @@ func (r *testRegistryV2) Close() { r.cmd.Process.Kill() os.RemoveAll(r.dir) } - -func pingV1(ip string) error { - // We always ping through HTTP for our test registry. - resp, err := http.Get(fmt.Sprintf("http://%s/v1/search", ip)) - if err != nil { - return err - } - if resp.StatusCode != 200 { - return fmt.Errorf("registry ping replied with an unexpected status code %d", resp.StatusCode) - } - return nil -} - -func startRegistryV1() error { - //wait for registry image to be available - for i := 0; i < 10; i++ { - imagesCmd := exec.Command(dockerBinary, "images") - out, _, err := runCommandWithOutput(imagesCmd) - if err != nil { - return err - } - if strings.Contains(out, "registry") { - break - } - time.Sleep(60000 * time.Millisecond) - if i == 10 { - fmt.Errorf("No registry image is found to start the regictry V1 services") - } - } - - if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "regserver", "-d", "-p", "5000:5000", "registry", "docker-registry")); err != nil { - fmt.Errorf("Failed to start registry: error %v, output %q", err, out) - } - ip := privateV1RegistryURL - //wait until registry server is available - for i := 0; i < 10; i++ { - if err := pingV1(ip); err == nil { - return nil - } else if i == 10 && err != nil { - return err - } - time.Sleep(2000 * time.Millisecond) - } - return nil -} From ec5e22efe3fb88f2fa2eb5e9a37161940f86bcfa Mon Sep 17 00:00:00 2001 From: Jimmy Puckett Date: Tue, 24 Mar 2015 21:09:25 -0400 Subject: [PATCH 069/999] Changing bitflag checking style to preferred style. Fixes #11668 Signed-off-by: Jimmy Puckett --- daemon/daemon.go | 4 ++-- daemon/graphdriver/aufs/migrate.go | 2 +- pkg/archive/archive_unix.go | 4 ++-- pkg/archive/changes.go | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index ebb43e248..f8b458287 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -445,8 +445,8 @@ func (daemon *Daemon) setupResolvconfWatcher() error { select { case event := <-watcher.Events: if event.Name == "/etc/resolv.conf" && - (event.Op&fsnotify.Write == fsnotify.Write || - event.Op&fsnotify.Create == fsnotify.Create) { + (event.Op&fsnotify.Write != 0 || + event.Op&fsnotify.Create != 0) { // verify a real change happened before we go further--a file write may have happened // without an actual change to the file updatedResolvConf, newResolvConfHash, err := resolvconf.GetIfChanged() diff --git a/daemon/graphdriver/aufs/migrate.go b/daemon/graphdriver/aufs/migrate.go index dda7cb739..dd61098e8 100644 --- a/daemon/graphdriver/aufs/migrate.go +++ b/daemon/graphdriver/aufs/migrate.go @@ -162,7 +162,7 @@ func tryRelocate(oldPath, newPath string) error { } // If the destination is a symlink then we already tried to relocate once before // and it failed so we delete it and try to remove - if s != nil && s.Mode()&os.ModeSymlink == os.ModeSymlink { + if s != nil && s.Mode()&os.ModeSymlink != 0 { if err := os.RemoveAll(newPath); err != nil { return err } diff --git a/pkg/archive/archive_unix.go b/pkg/archive/archive_unix.go index cbce65e31..82c9a82c1 100644 --- a/pkg/archive/archive_unix.go +++ b/pkg/archive/archive_unix.go @@ -36,8 +36,8 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st inode = uint64(s.Ino) // Currently go does not fil in the major/minors - if s.Mode&syscall.S_IFBLK == syscall.S_IFBLK || - s.Mode&syscall.S_IFCHR == syscall.S_IFCHR { + if s.Mode&syscall.S_IFBLK != 0 || + s.Mode&syscall.S_IFCHR != 0 { hdr.Devmajor = int64(major(uint64(s.Rdev))) hdr.Devminor = int64(minor(uint64(s.Rdev))) } diff --git a/pkg/archive/changes.go b/pkg/archive/changes.go index c3cb4ebe0..96aff36a3 100644 --- a/pkg/archive/changes.go +++ b/pkg/archive/changes.go @@ -176,7 +176,7 @@ func (info *FileInfo) path() string { } func (info *FileInfo) isDir() bool { - return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR == syscall.S_IFDIR + return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR != 0 } func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) { From e8e60befd6c3b4feac975a84cd907659788fabc1 Mon Sep 17 00:00:00 2001 From: Jimmy Puckett Date: Tue, 24 Mar 2015 21:10:07 -0400 Subject: [PATCH 070/999] Code simplification that @tiborvass requested Signed-off-by: Jimmy Puckett --- daemon/daemon.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index f8b458287..52b7dd8c2 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -445,8 +445,7 @@ func (daemon *Daemon) setupResolvconfWatcher() error { select { case event := <-watcher.Events: if event.Name == "/etc/resolv.conf" && - (event.Op&fsnotify.Write != 0 || - event.Op&fsnotify.Create != 0) { + (event.Op & (fsnotify.Write | fsnotify.Create) != 0) { // verify a real change happened before we go further--a file write may have happened // without an actual change to the file updatedResolvConf, newResolvConfHash, err := resolvconf.GetIfChanged() From a08048d5c835f1558fbdbac2f7d833552e13d979 Mon Sep 17 00:00:00 2001 From: Yestin Sun Date: Tue, 24 Mar 2015 18:20:20 -0700 Subject: [PATCH 071/999] Add more tests for pkg/chrootarchive Fixes issue #11601 Change-Id: Ifc1dbcc59cc4dc581ed43fc8fbe43fbaec4ccad0 Signed-off-by: Yestin Sun --- pkg/chrootarchive/archive_test.go | 145 ++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/pkg/chrootarchive/archive_test.go b/pkg/chrootarchive/archive_test.go index fb4c5c4e4..45397d38f 100644 --- a/pkg/chrootarchive/archive_test.go +++ b/pkg/chrootarchive/archive_test.go @@ -1,9 +1,12 @@ package chrootarchive import ( + "bytes" + "fmt" "io" "io/ioutil" "os" + "path" "path/filepath" "testing" "time" @@ -45,6 +48,148 @@ func TestChrootTarUntar(t *testing.T) { } } +func TestChrootUntarEmptyArchive(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarEmptyArchive") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + if err := Untar(nil, tmpdir, nil); err == nil { + t.Fatal("expected error on empty archive") + } +} + +func prepareSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) { + fileData := []byte("fooo") + for n := 0; n < numberOfFiles; n++ { + fileName := fmt.Sprintf("file-%d", n) + if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil { + return 0, err + } + if makeLinks { + if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil { + return 0, err + } + } + } + totalSize := numberOfFiles * len(fileData) + return totalSize, nil +} + +func TestChrootTarUntarWithSoftLink(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "docker-TestChrootTarUntarWithSoftLink") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + src := filepath.Join(tmpdir, "src") + if err := os.MkdirAll(src, 0700); err != nil { + t.Fatal(err) + } + if _, err := prepareSourceDirectory(10, src, true); err != nil { + t.Fatal(err) + } + dest := filepath.Join(tmpdir, "dest") + if err := TarUntar(src, dest); err != nil { + t.Fatal(err) + } +} + +func TestChrootCopyWithTar(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "docker-TestChrootCopyWithTar") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + src := filepath.Join(tmpdir, "src") + if err := os.MkdirAll(src, 0700); err != nil { + t.Fatal(err) + } + if _, err := prepareSourceDirectory(10, src, true); err != nil { + t.Fatal(err) + } + dest := filepath.Join(tmpdir, "dest") + // Copy directory + if err := CopyWithTar(src, dest); err != nil { + t.Fatal(err) + } + // Copy file + srcfile := filepath.Join(src, "file-1") + if err := CopyWithTar(srcfile, dest); err != nil { + t.Fatal(err) + } + // Copy symbolic link + linkfile := filepath.Join(src, "file-1-link") + if err := CopyWithTar(linkfile, dest); err != nil { + t.Fatal(err) + } +} + +func TestChrootCopyFileWithTar(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "docker-TestChrootCopyFileWithTar") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + src := filepath.Join(tmpdir, "src") + if err := os.MkdirAll(src, 0700); err != nil { + t.Fatal(err) + } + if _, err := prepareSourceDirectory(10, src, true); err != nil { + t.Fatal(err) + } + dest := filepath.Join(tmpdir, "dest") + // Copy directory + if err := CopyFileWithTar(src, dest); err == nil { + t.Fatal("Expected error on copying directory") + } + // Copy file + srcfile := filepath.Join(src, "file-1") + if err := CopyFileWithTar(srcfile, dest); err != nil { + t.Fatal(err) + } + // Copy symbolic link + linkfile := filepath.Join(src, "file-1-link") + if err := CopyFileWithTar(linkfile, dest); err != nil { + t.Fatal(err) + } +} + +func TestChrootUntarPath(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarPath") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + src := filepath.Join(tmpdir, "src") + if err := os.MkdirAll(src, 0700); err != nil { + t.Fatal(err) + } + if _, err := prepareSourceDirectory(10, src, true); err != nil { + t.Fatal(err) + } + dest := filepath.Join(tmpdir, "dest") + // Untar a directory + if err := UntarPath(src, dest); err == nil { + t.Fatal("Expected error on untaring a directory") + } + + // Untar a tar file + stream, err := archive.Tar(src, archive.Uncompressed) + if err != nil { + t.Fatal(err) + } + buf := new(bytes.Buffer) + buf.ReadFrom(stream) + tarfile := filepath.Join(tmpdir, "src.tar") + if err := ioutil.WriteFile(tarfile, buf.Bytes(), 0644); err != nil { + t.Fatal(err) + } + if err := UntarPath(tarfile, dest); err != nil { + t.Fatal(err) + } +} + type slowEmptyTarReader struct { size int offset int From 0d2190e6794df81f3cf3b84707ce3abdf5843100 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Tue, 24 Mar 2015 10:08:37 -0700 Subject: [PATCH 072/999] Add some info about what environment variables are available Having the list in one spot makes it easier for people to see what's avaiable instead of having to scan all of the docs and extract the info. Signed-off-by: Doug Davis --- docs/sources/reference/commandline/cli.md | 40 ++++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 6c3eb1c8d..bd22916d8 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -17,6 +17,30 @@ or execute `docker help`: ... +## Environment Variables + +For easy reference, the following list of environment variables are supported +by the `docker` command line: + +* `DOCKER_CERT_PATH` The location of your authentication keys. +* `DOCKER_DRIVER` The graph driver to use. +* `DOCKER_HOST` Daemon socket to connect to. +* `DOCKER_NOWARN_KERNEL_VERSION` Prevent warnings that your Linux kernel is unsuitable for Docker. +* `DOCKER_RAMDISK` If set this will disable 'pivot_root'. +* `DOCKER_TLS_VERIFY` When set Docker uses TLS and verifies the remote. +* `DOCKER_TMPDIR` Location for temporary Docker files. + +Because Docker is developed using 'Go', you can also use any environment +variables used by the 'Go' runtime. In particular, you may find these useful: + +* `HTTP_PROXY` +* `HTTPS_PROXY` +* `NO_PROXY` + +These Go environment variables are case-insensitive. See the +[Go specification](http://golang.org/pkg/net/http/) for details on these +variables. + ## Help To list the help on any command just execute the command, followed by the `--help` option. @@ -539,7 +563,7 @@ Instead of specifying a context, you can pass a single Dockerfile in the docker build - < Dockerfile If you use STDIN or specify a `URL`, the system places the contents into a -file called `Dockerfile`, and any `-f`, `--file` option is ignored. In this +file called `Dockerfile`, and any `-f`, `--file` option is ignored. In this scenario, there is no context. ### Return code @@ -795,7 +819,7 @@ relative to the root of the container's filesystem. Usage: docker cp CONTAINER:PATH HOSTDIR|- - Copy files/folders from the PATH to the HOSTDIR. + Copy files/folders from the PATH to the HOSTDIR. ## create @@ -1530,7 +1554,7 @@ just a specific mapping: --before="" Show only container created before Id or Name -f, --filter=[] Filter output based on conditions provided -l, --latest=false Show the latest created container, include non-running - -n=-1 Show n last created containers, include non-running + -n=-1 Show n last created containers, include non-running --no-trunc=false Don't truncate output -q, --quiet=false Only display numeric IDs -s, --size=false Display total file sizes @@ -1946,7 +1970,7 @@ format: com.example.label2=another\ label com.example.label3 -You can load multiple label-files by supplying multiple `--label-file` flags. +You can load multiple label-files by supplying multiple `--label-file` flags. For additional information on working with labels, see [*Labels - custom metadata in Docker*](/userguide/labels-custom-metadata/) in the Docker User @@ -2060,7 +2084,7 @@ application change: #### Restart Policies -Use Docker's `--restart` to specify a container's *restart policy*. A restart +Use Docker's `--restart` to specify a container's *restart policy*. A restart policy controls whether the Docker daemon restarts a container after exit. Docker supports the following restart policies: @@ -2075,7 +2099,7 @@ Docker supports the following restart policies: no - Do not automatically restart the container when it exits. This is the + Do not automatically restart the container when it exits. This is the default. @@ -2087,7 +2111,7 @@ Docker supports the following restart policies: Restart only if the container exits with a non-zero exit status. - Optionally, limit the number of restart retries the Docker + Optionally, limit the number of restart retries the Docker daemon attempts. @@ -2107,7 +2131,7 @@ Docker supports the following restart policies: This will run the `redis` container with a restart policy of **always** so that if the container exits, Docker will restart it. -More detailed information on restart policies can be found in the +More detailed information on restart policies can be found in the [Restart Policies (--restart)](/reference/run/#restart-policies-restart) section of the Docker run reference page. From b6b8032a1759905adbb68355e994b0405054bcc0 Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Wed, 25 Mar 2015 03:09:45 +0000 Subject: [PATCH 073/999] listenbuffer: add test Signed-off-by: Simon Eskildsen --- pkg/listenbuffer/listen_buffer_test.go | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 pkg/listenbuffer/listen_buffer_test.go diff --git a/pkg/listenbuffer/listen_buffer_test.go b/pkg/listenbuffer/listen_buffer_test.go new file mode 100644 index 000000000..6ffd2f798 --- /dev/null +++ b/pkg/listenbuffer/listen_buffer_test.go @@ -0,0 +1,41 @@ +package listenbuffer + +import ( + "io/ioutil" + "net" + "testing" +) + +func TestListenBufferAllowsAcceptingWhenActivated(t *testing.T) { + lock := make(chan struct{}) + buffer, err := NewListenBuffer("tcp", "", lock) + if err != nil { + t.Fatal("Unable to create listen buffer: ", err) + } + + go func() { + conn, err := net.Dial("tcp", buffer.Addr().String()) + if err != nil { + t.Fatal("Client failed to establish connection to server: ", err) + } + + conn.Write([]byte("ping")) + conn.Close() + }() + + close(lock) + + client, err := buffer.Accept() + if err != nil { + t.Fatal("Failed to accept client: ", err) + } + + response, err := ioutil.ReadAll(client) + if err != nil { + t.Fatal("Failed to read from client: ", err) + } + + if string(response) != "ping" { + t.Fatal("Expected to receive ping from client, received: ", string(response)) + } +} From 67bd859481a9d5c7a2ccf4c593e65d473ab3f106 Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Wed, 25 Mar 2015 03:09:36 +0000 Subject: [PATCH 074/999] listenbuffer: add docs Signed-off-by: Simon Eskildsen --- pkg/listenbuffer/README.md | 27 +++++++++++++++++++++++ pkg/listenbuffer/buffer.go | 44 ++++++++++++++++++++++++++++++++------ 2 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 pkg/listenbuffer/README.md diff --git a/pkg/listenbuffer/README.md b/pkg/listenbuffer/README.md new file mode 100644 index 000000000..227350981 --- /dev/null +++ b/pkg/listenbuffer/README.md @@ -0,0 +1,27 @@ +# listenbuffer + +listenbuffer uses the kernel's listening backlog functionality to queue +connections, allowing applications to start listening immediately and handle +connections later. This is signaled by closing the activation channel passed to +the constructor. + +The maximum amount of queued connections depends on the configuration of your +kernel (typically called SOMAXXCON) and cannot be configured in Go with the +net package. See `src/net/sock_platform.go` in the Go tree or consult your +kernel's manual. + + activator := make(chan struct{}) + buffer, err := NewListenBuffer("tcp", "localhost:4000", activator) + if err != nil { + panic(err) + } + + // will block until activator has been closed or is sent an event + client, err := buffer.Accept() + +Somewhere else in your application once it's been booted: + + close(activator) + +`buffer.Accept()` will return the first client in the kernel listening queue, or +continue to block until a client connects or an error occurs. diff --git a/pkg/listenbuffer/buffer.go b/pkg/listenbuffer/buffer.go index 17572c8a0..6e3656d2c 100644 --- a/pkg/listenbuffer/buffer.go +++ b/pkg/listenbuffer/buffer.go @@ -1,13 +1,37 @@ /* - Package to allow go applications to immediately start - listening on a socket, unix, tcp, udp but hold connections - until the application has booted and is ready to accept them +listenbuffer uses the kernel's listening backlog functionality to queue +connections, allowing applications to start listening immediately and handle +connections later. This is signaled by closing the activation channel passed to +the constructor. + +The maximum amount of queued connections depends on the configuration of your +kernel (typically called SOMAXXCON) and cannot be configured in Go with the +net package. See `src/net/sock_platform.go` in the Go tree or consult your +kernel's manual. + + activator := make(chan struct{}) + buffer, err := NewListenBuffer("tcp", "localhost:4000", activator) + if err != nil { + panic(err) + } + + // will block until activator has been closed or is sent an event + client, err := buffer.Accept() + +Somewhere else in your application once it's been booted: + + close(activator) + +`buffer.Accept()` will return the first client in the kernel listening queue, or +continue to block until a client connects or an error occurs. */ package listenbuffer import "net" -// NewListenBuffer returns a listener listening on addr with the protocol. +// NewListenBuffer returns a net.Listener listening on addr with the protocol +// passed. The channel passed is used to activate the listenbuffer when the +// caller is ready to accept connections. func NewListenBuffer(proto, addr string, activate chan struct{}) (net.Listener, error) { wrapped, err := net.Listen(proto, addr) if err != nil { @@ -20,20 +44,26 @@ func NewListenBuffer(proto, addr string, activate chan struct{}) (net.Listener, }, nil } +// defaultListener is the buffered wrapper around the net.Listener type defaultListener struct { - wrapped net.Listener // the real listener to wrap - ready bool // is the listner ready to start accpeting connections - activate chan struct{} + wrapped net.Listener // The net.Listener wrapped by listenbuffer + ready bool // Whether the listenbuffer has been activated + activate chan struct{} // Channel to control activation of the listenbuffer } +// Close closes the wrapped socket. func (l *defaultListener) Close() error { return l.wrapped.Close() } +// Addr returns the listening address of the wrapped socket. func (l *defaultListener) Addr() net.Addr { return l.wrapped.Addr() } +// Accept returns a client connection on the wrapped socket if the listen buffer +// has been activated. To active the listenbuffer the activation channel passed +// to NewListenBuffer must have been closed or sent an event. func (l *defaultListener) Accept() (net.Conn, error) { // if the listen has been told it is ready then we can go ahead and // start returning connections From fbd47969a86793c4e87e2da1abea1f806ba0a526 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Tue, 24 Mar 2015 20:56:26 -0700 Subject: [PATCH 075/999] TestBuildCancelationKillsSleep send exec cmd to stdout and makes the testing output ugly. This hides the output since it not used. Signed-off-by: Doug Davis --- integration-cli/docker_cli_build_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 954c0e5d9..5b8c3e42d 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -2031,7 +2031,6 @@ func TestBuildCancelationKillsSleep(t *testing.T) { buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".") buildCmd.Dir = ctx.Dir - buildCmd.Stdout = os.Stdout err = buildCmd.Start() if err != nil { From 58690c9cca5995035b721ed6f1337b0ddb932d7a Mon Sep 17 00:00:00 2001 From: Joey Gibson Date: Tue, 24 Mar 2015 23:57:23 -0400 Subject: [PATCH 076/999] api/client - The code for all cli commands are in one file #11610 Signed-off-by: Joey Gibson --- api/client/attach.go | 85 ++ api/client/build.go | 302 +++++ api/client/commands.go | 2938 ---------------------------------------- api/client/commit.go | 76 ++ api/client/cp.go | 54 + api/client/create.go | 153 +++ api/client/diff.go | 41 + api/client/events.go | 68 + api/client/exec.go | 126 ++ api/client/export.go | 49 + api/client/help.go | 32 + api/client/history.go | 65 + api/client/images.go | 271 ++++ api/client/import.go | 54 + api/client/info.go | 144 ++ api/client/inspect.go | 95 ++ api/client/kill.go | 28 + api/client/load.go | 32 + api/client/login.go | 138 ++ api/client/logout.go | 34 + api/client/logs.go | 53 + api/client/pause.go | 25 + api/client/port.go | 60 + api/client/ps.go | 175 +++ api/client/pull.go | 77 ++ api/client/push.go | 76 ++ api/client/rename.go | 23 + api/client/restart.go | 33 + api/client/rm.go | 43 + api/client/rmi.go | 54 + api/client/run.go | 245 ++++ api/client/save.go | 48 + api/client/search.go | 60 + api/client/start.go | 160 +++ api/client/stats.go | 177 +++ api/client/stop.go | 33 + api/client/tag.go | 39 + api/client/top.go | 44 + api/client/unpause.go | 25 + api/client/version.go | 56 + api/client/wait.go | 28 + 41 files changed, 3381 insertions(+), 2938 deletions(-) create mode 100644 api/client/attach.go create mode 100644 api/client/build.go delete mode 100644 api/client/commands.go create mode 100644 api/client/commit.go create mode 100644 api/client/cp.go create mode 100644 api/client/create.go create mode 100644 api/client/diff.go create mode 100644 api/client/events.go create mode 100644 api/client/exec.go create mode 100644 api/client/export.go create mode 100644 api/client/help.go create mode 100644 api/client/history.go create mode 100644 api/client/images.go create mode 100644 api/client/import.go create mode 100644 api/client/info.go create mode 100644 api/client/inspect.go create mode 100644 api/client/kill.go create mode 100644 api/client/load.go create mode 100644 api/client/login.go create mode 100644 api/client/logout.go create mode 100644 api/client/logs.go create mode 100644 api/client/pause.go create mode 100644 api/client/port.go create mode 100644 api/client/ps.go create mode 100644 api/client/pull.go create mode 100644 api/client/push.go create mode 100644 api/client/rename.go create mode 100644 api/client/restart.go create mode 100644 api/client/rm.go create mode 100644 api/client/rmi.go create mode 100644 api/client/run.go create mode 100644 api/client/save.go create mode 100644 api/client/search.go create mode 100644 api/client/start.go create mode 100644 api/client/stats.go create mode 100644 api/client/stop.go create mode 100644 api/client/tag.go create mode 100644 api/client/top.go create mode 100644 api/client/unpause.go create mode 100644 api/client/version.go create mode 100644 api/client/wait.go diff --git a/api/client/attach.go b/api/client/attach.go new file mode 100644 index 000000000..e9232a104 --- /dev/null +++ b/api/client/attach.go @@ -0,0 +1,85 @@ +package client + +import ( + "fmt" + "io" + "net/url" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/engine" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdAttach(args ...string) error { + var ( + cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container", true) + noStdin = cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN") + proxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process") + ) + cmd.Require(flag.Exact, 1) + + utils.ParseFlags(cmd, args, true) + name := cmd.Arg(0) + + stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) + if err != nil { + return err + } + + env := engine.Env{} + if err := env.Decode(stream); err != nil { + return err + } + + if !env.GetSubEnv("State").GetBool("Running") { + return fmt.Errorf("You cannot attach to a stopped container, start it first") + } + + var ( + config = env.GetSubEnv("Config") + tty = config.GetBool("Tty") + ) + + if err := cli.CheckTtyInput(!*noStdin, tty); err != nil { + return err + } + + if tty && cli.isTerminalOut { + if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { + log.Debugf("Error monitoring TTY size: %s", err) + } + } + + var in io.ReadCloser + + v := url.Values{} + v.Set("stream", "1") + if !*noStdin && config.GetBool("OpenStdin") { + v.Set("stdin", "1") + in = cli.in + } + + v.Set("stdout", "1") + v.Set("stderr", "1") + + if *proxy && !tty { + sigc := cli.forwardAllSignals(cmd.Arg(0)) + defer signal.StopCatch(sigc) + } + + if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil); err != nil { + return err + } + + _, status, err := getExitCode(cli, cmd.Arg(0)) + if err != nil { + return err + } + if status != 0 { + return &utils.StatusError{StatusCode: status} + } + + return nil +} diff --git a/api/client/build.go b/api/client/build.go new file mode 100644 index 000000000..91446132a --- /dev/null +++ b/api/client/build.go @@ -0,0 +1,302 @@ +package client + +import ( + "bufio" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "io/ioutil" + "net/http" + "net/url" + "os" + "os/exec" + "path" + "path/filepath" + "runtime" + "strconv" + "strings" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/api" + "github.com/docker/docker/graph" + "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/fileutils" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/progressreader" + "github.com/docker/docker/pkg/symlink" + "github.com/docker/docker/pkg/units" + "github.com/docker/docker/pkg/urlutil" + "github.com/docker/docker/registry" + "github.com/docker/docker/utils" +) + +const ( + tarHeaderSize = 512 +) + +func (cli *DockerCli) CmdBuild(args ...string) error { + cmd := cli.Subcmd("build", "PATH | URL | -", "Build a new image from the source code at PATH", true) + tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image") + suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers") + noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image") + rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build") + forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers") + pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image") + dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')") + flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit") + flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap") + flCpuShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") + flCpuSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") + + cmd.Require(flag.Exact, 1) + + utils.ParseFlags(cmd, args, true) + + var ( + context archive.Archive + isRemote bool + err error + ) + + _, err = exec.LookPath("git") + hasGit := err == nil + if cmd.Arg(0) == "-" { + // As a special case, 'docker build -' will build from either an empty context with the + // contents of stdin as a Dockerfile, or a tar-ed context from stdin. + buf := bufio.NewReader(cli.in) + magic, err := buf.Peek(tarHeaderSize) + if err != nil && err != io.EOF { + return fmt.Errorf("failed to peek context header from STDIN: %v", err) + } + if !archive.IsArchive(magic) { + dockerfile, err := ioutil.ReadAll(buf) + if err != nil { + return fmt.Errorf("failed to read Dockerfile from STDIN: %v", err) + } + + // -f option has no meaning when we're reading it from stdin, + // so just use our default Dockerfile name + *dockerfileName = api.DefaultDockerfileName + context, err = archive.Generate(*dockerfileName, string(dockerfile)) + } else { + context = ioutil.NopCloser(buf) + } + } else if urlutil.IsURL(cmd.Arg(0)) && (!urlutil.IsGitURL(cmd.Arg(0)) || !hasGit) { + isRemote = true + } else { + root := cmd.Arg(0) + if urlutil.IsGitURL(root) { + remoteURL := cmd.Arg(0) + if !urlutil.IsGitTransport(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 + } + + absRoot, err := filepath.Abs(root) + if err != nil { + return err + } + + filename := *dockerfileName // path to Dockerfile + + if *dockerfileName == "" { + // No -f/--file was specified so use the default + *dockerfileName = api.DefaultDockerfileName + filename = filepath.Join(absRoot, *dockerfileName) + + // Just to be nice ;-) look for 'dockerfile' too but only + // use it if we found it, otherwise ignore this check + if _, err = os.Lstat(filename); os.IsNotExist(err) { + tmpFN := path.Join(absRoot, strings.ToLower(*dockerfileName)) + if _, err = os.Lstat(tmpFN); err == nil { + *dockerfileName = strings.ToLower(*dockerfileName) + filename = tmpFN + } + } + } + + origDockerfile := *dockerfileName // used for error msg + if filename, err = filepath.Abs(filename); err != nil { + return err + } + + // Verify that 'filename' is within the build context + filename, err = symlink.FollowSymlinkInScope(filename, absRoot) + if err != nil { + return fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", origDockerfile, root) + } + + // Now reset the dockerfileName to be relative to the build context + *dockerfileName, err = filepath.Rel(absRoot, filename) + if err != nil { + return err + } + // And canonicalize dockerfile name to a platform-independent one + *dockerfileName, err = archive.CanonicalTarNameForPath(*dockerfileName) + if err != nil { + return fmt.Errorf("Cannot canonicalize dockerfile path %s: %v", dockerfileName, err) + } + + if _, err = os.Lstat(filename); os.IsNotExist(err) { + return fmt.Errorf("Cannot locate Dockerfile: %s", origDockerfile) + } + var includes = []string{"."} + + excludes, err := utils.ReadDockerIgnore(path.Join(root, ".dockerignore")) + if err != nil { + return err + } + + // If .dockerignore mentions .dockerignore or the Dockerfile + // then make sure we send both files over to the daemon + // because Dockerfile is, obviously, needed no matter what, and + // .dockerignore is needed to know if either one needs to be + // removed. The deamon will remove them for us, if needed, after it + // parses the Dockerfile. + keepThem1, _ := fileutils.Matches(".dockerignore", excludes) + keepThem2, _ := fileutils.Matches(*dockerfileName, excludes) + if keepThem1 || keepThem2 { + includes = append(includes, ".dockerignore", *dockerfileName) + } + + if err = utils.ValidateContextDirectory(root, excludes); err != nil { + return fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err) + } + options := &archive.TarOptions{ + Compression: archive.Uncompressed, + ExcludePatterns: excludes, + IncludeFiles: includes, + } + context, err = archive.TarWithOptions(root, options) + if err != nil { + return err + } + } + + // windows: show error message about modified file permissions + // FIXME: this is not a valid warning when the daemon is running windows. should be removed once docker engine for windows can build. + if runtime.GOOS == "windows" { + log.Warn(`SECURITY WARNING: You are building a Docker image from Windows against a Linux Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`) + } + + var body io.Reader + // Setup an upload progress bar + // FIXME: ProgressReader shouldn't be this annoying to use + if context != nil { + sf := utils.NewStreamFormatter(false) + body = progressreader.New(progressreader.Config{ + In: context, + Out: cli.out, + Formatter: sf, + NewLines: true, + ID: "", + Action: "Sending build context to Docker daemon", + }) + } + + var memory int64 + if *flMemoryString != "" { + parsedMemory, err := units.RAMInBytes(*flMemoryString) + if err != nil { + return err + } + memory = parsedMemory + } + + var memorySwap int64 + if *flMemorySwap != "" { + if *flMemorySwap == "-1" { + memorySwap = -1 + } else { + parsedMemorySwap, err := units.RAMInBytes(*flMemorySwap) + if err != nil { + return err + } + memorySwap = parsedMemorySwap + } + } + // Send the build context + v := &url.Values{} + + //Check if the given image name can be resolved + if *tag != "" { + repository, tag := parsers.ParseRepositoryTag(*tag) + if err := registry.ValidateRepositoryName(repository); err != nil { + return err + } + if len(tag) > 0 { + if err := graph.ValidateTagName(tag); err != nil { + return err + } + } + } + + v.Set("t", *tag) + + if *suppressOutput { + v.Set("q", "1") + } + if isRemote { + v.Set("remote", cmd.Arg(0)) + } + if *noCache { + v.Set("nocache", "1") + } + if *rm { + v.Set("rm", "1") + } else { + v.Set("rm", "0") + } + + if *forceRm { + v.Set("forcerm", "1") + } + + if *pull { + v.Set("pull", "1") + } + + v.Set("cpusetcpus", *flCpuSetCpus) + v.Set("cpushares", strconv.FormatInt(*flCpuShares, 10)) + v.Set("memory", strconv.FormatInt(memory, 10)) + v.Set("memswap", strconv.FormatInt(memorySwap, 10)) + + v.Set("dockerfile", *dockerfileName) + + cli.LoadConfigFile() + + headers := http.Header(make(map[string][]string)) + buf, err := json.Marshal(cli.configFile) + if err != nil { + return err + } + headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) + + if context != nil { + headers.Set("Content-Type", "application/tar") + } + err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), body, cli.out, headers) + if jerr, ok := err.(*utils.JSONError); ok { + // If no error code is set, default to 1 + if jerr.Code == 0 { + jerr.Code = 1 + } + return &utils.StatusError{Status: jerr.Message, StatusCode: jerr.Code} + } + return err +} diff --git a/api/client/commands.go b/api/client/commands.go deleted file mode 100644 index 46a169da8..000000000 --- a/api/client/commands.go +++ /dev/null @@ -1,2938 +0,0 @@ -package client - -import ( - "bufio" - "bytes" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/url" - "os" - "os/exec" - "path" - "path/filepath" - "runtime" - "sort" - "strconv" - "strings" - "sync" - "text/tabwriter" - "text/template" - "time" - - log "github.com/Sirupsen/logrus" - "github.com/docker/docker/api" - "github.com/docker/docker/api/types" - "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/engine" - "github.com/docker/docker/graph" - "github.com/docker/docker/nat" - "github.com/docker/docker/opts" - "github.com/docker/docker/pkg/archive" - "github.com/docker/docker/pkg/fileutils" - "github.com/docker/docker/pkg/homedir" - flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/pkg/parsers" - "github.com/docker/docker/pkg/parsers/filters" - "github.com/docker/docker/pkg/progressreader" - "github.com/docker/docker/pkg/promise" - "github.com/docker/docker/pkg/resolvconf" - "github.com/docker/docker/pkg/signal" - "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/pkg/symlink" - "github.com/docker/docker/pkg/term" - "github.com/docker/docker/pkg/timeutils" - "github.com/docker/docker/pkg/units" - "github.com/docker/docker/pkg/urlutil" - "github.com/docker/docker/registry" - "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" -) - -const ( - tarHeaderSize = 512 -) - -func (cli *DockerCli) CmdHelp(args ...string) error { - if len(args) > 1 { - method, exists := cli.getMethod(args[:2]...) - if exists { - method("--help") - return nil - } - } - if len(args) > 0 { - method, exists := cli.getMethod(args[0]) - if !exists { - fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0]) - os.Exit(1) - } else { - method("--help") - return nil - } - } - - flag.Usage() - - return nil -} - -func (cli *DockerCli) CmdBuild(args ...string) error { - cmd := cli.Subcmd("build", "PATH | URL | -", "Build a new image from the source code at PATH", true) - tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image") - suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers") - noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image") - rm := cmd.Bool([]string{"#rm", "-rm"}, true, "Remove intermediate containers after a successful build") - forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers") - pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image") - dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')") - flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit") - flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap") - flCpuShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") - flCpuSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") - - cmd.Require(flag.Exact, 1) - - utils.ParseFlags(cmd, args, true) - - var ( - context archive.Archive - isRemote bool - err error - ) - - _, err = exec.LookPath("git") - hasGit := err == nil - if cmd.Arg(0) == "-" { - // As a special case, 'docker build -' will build from either an empty context with the - // contents of stdin as a Dockerfile, or a tar-ed context from stdin. - buf := bufio.NewReader(cli.in) - magic, err := buf.Peek(tarHeaderSize) - if err != nil && err != io.EOF { - return fmt.Errorf("failed to peek context header from STDIN: %v", err) - } - if !archive.IsArchive(magic) { - dockerfile, err := ioutil.ReadAll(buf) - if err != nil { - return fmt.Errorf("failed to read Dockerfile from STDIN: %v", err) - } - - // -f option has no meaning when we're reading it from stdin, - // so just use our default Dockerfile name - *dockerfileName = api.DefaultDockerfileName - context, err = archive.Generate(*dockerfileName, string(dockerfile)) - } else { - context = ioutil.NopCloser(buf) - } - } else if urlutil.IsURL(cmd.Arg(0)) && (!urlutil.IsGitURL(cmd.Arg(0)) || !hasGit) { - isRemote = true - } else { - root := cmd.Arg(0) - if urlutil.IsGitURL(root) { - remoteURL := cmd.Arg(0) - if !urlutil.IsGitTransport(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 - } - - absRoot, err := filepath.Abs(root) - if err != nil { - return err - } - - filename := *dockerfileName // path to Dockerfile - - if *dockerfileName == "" { - // No -f/--file was specified so use the default - *dockerfileName = api.DefaultDockerfileName - filename = filepath.Join(absRoot, *dockerfileName) - - // Just to be nice ;-) look for 'dockerfile' too but only - // use it if we found it, otherwise ignore this check - if _, err = os.Lstat(filename); os.IsNotExist(err) { - tmpFN := path.Join(absRoot, strings.ToLower(*dockerfileName)) - if _, err = os.Lstat(tmpFN); err == nil { - *dockerfileName = strings.ToLower(*dockerfileName) - filename = tmpFN - } - } - } - - origDockerfile := *dockerfileName // used for error msg - if filename, err = filepath.Abs(filename); err != nil { - return err - } - - // Verify that 'filename' is within the build context - filename, err = symlink.FollowSymlinkInScope(filename, absRoot) - if err != nil { - return fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", origDockerfile, root) - } - - // Now reset the dockerfileName to be relative to the build context - *dockerfileName, err = filepath.Rel(absRoot, filename) - if err != nil { - return err - } - // And canonicalize dockerfile name to a platform-independent one - *dockerfileName, err = archive.CanonicalTarNameForPath(*dockerfileName) - if err != nil { - return fmt.Errorf("Cannot canonicalize dockerfile path %s: %v", dockerfileName, err) - } - - if _, err = os.Lstat(filename); os.IsNotExist(err) { - return fmt.Errorf("Cannot locate Dockerfile: %s", origDockerfile) - } - var includes = []string{"."} - - excludes, err := utils.ReadDockerIgnore(path.Join(root, ".dockerignore")) - if err != nil { - return err - } - - // If .dockerignore mentions .dockerignore or the Dockerfile - // then make sure we send both files over to the daemon - // because Dockerfile is, obviously, needed no matter what, and - // .dockerignore is needed to know if either one needs to be - // removed. The deamon will remove them for us, if needed, after it - // parses the Dockerfile. - keepThem1, _ := fileutils.Matches(".dockerignore", excludes) - keepThem2, _ := fileutils.Matches(*dockerfileName, excludes) - if keepThem1 || keepThem2 { - includes = append(includes, ".dockerignore", *dockerfileName) - } - - if err = utils.ValidateContextDirectory(root, excludes); err != nil { - return fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err) - } - options := &archive.TarOptions{ - Compression: archive.Uncompressed, - ExcludePatterns: excludes, - IncludeFiles: includes, - } - context, err = archive.TarWithOptions(root, options) - if err != nil { - return err - } - } - - // windows: show error message about modified file permissions - // FIXME: this is not a valid warning when the daemon is running windows. should be removed once docker engine for windows can build. - if runtime.GOOS == "windows" { - log.Warn(`SECURITY WARNING: You are building a Docker image from Windows against a Linux Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`) - } - - var body io.Reader - // Setup an upload progress bar - // FIXME: ProgressReader shouldn't be this annoying to use - if context != nil { - sf := utils.NewStreamFormatter(false) - body = progressreader.New(progressreader.Config{ - In: context, - Out: cli.out, - Formatter: sf, - NewLines: true, - ID: "", - Action: "Sending build context to Docker daemon", - }) - } - - var memory int64 - if *flMemoryString != "" { - parsedMemory, err := units.RAMInBytes(*flMemoryString) - if err != nil { - return err - } - memory = parsedMemory - } - - var memorySwap int64 - if *flMemorySwap != "" { - if *flMemorySwap == "-1" { - memorySwap = -1 - } else { - parsedMemorySwap, err := units.RAMInBytes(*flMemorySwap) - if err != nil { - return err - } - memorySwap = parsedMemorySwap - } - } - // Send the build context - v := &url.Values{} - - //Check if the given image name can be resolved - if *tag != "" { - repository, tag := parsers.ParseRepositoryTag(*tag) - if err := registry.ValidateRepositoryName(repository); err != nil { - return err - } - if len(tag) > 0 { - if err := graph.ValidateTagName(tag); err != nil { - return err - } - } - } - - v.Set("t", *tag) - - if *suppressOutput { - v.Set("q", "1") - } - if isRemote { - v.Set("remote", cmd.Arg(0)) - } - if *noCache { - v.Set("nocache", "1") - } - if *rm { - v.Set("rm", "1") - } else { - v.Set("rm", "0") - } - - if *forceRm { - v.Set("forcerm", "1") - } - - if *pull { - v.Set("pull", "1") - } - - v.Set("cpusetcpus", *flCpuSetCpus) - v.Set("cpushares", strconv.FormatInt(*flCpuShares, 10)) - v.Set("memory", strconv.FormatInt(memory, 10)) - v.Set("memswap", strconv.FormatInt(memorySwap, 10)) - - v.Set("dockerfile", *dockerfileName) - - cli.LoadConfigFile() - - headers := http.Header(make(map[string][]string)) - buf, err := json.Marshal(cli.configFile) - if err != nil { - return err - } - headers.Add("X-Registry-Config", base64.URLEncoding.EncodeToString(buf)) - - if context != nil { - headers.Set("Content-Type", "application/tar") - } - err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), body, cli.out, headers) - if jerr, ok := err.(*utils.JSONError); ok { - // If no error code is set, default to 1 - if jerr.Code == 0 { - jerr.Code = 1 - } - return &utils.StatusError{Status: jerr.Message, StatusCode: jerr.Code} - } - return err -} - -// 'docker login': login / register a user to registry service. -func (cli *DockerCli) CmdLogin(args ...string) error { - cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true) - cmd.Require(flag.Max, 1) - - var username, password, email string - - cmd.StringVar(&username, []string{"u", "-username"}, "", "Username") - cmd.StringVar(&password, []string{"p", "-password"}, "", "Password") - cmd.StringVar(&email, []string{"e", "-email"}, "", "Email") - - utils.ParseFlags(cmd, args, true) - - serverAddress := registry.IndexServerAddress() - if len(cmd.Args()) > 0 { - serverAddress = cmd.Arg(0) - } - - promptDefault := func(prompt string, configDefault string) { - if configDefault == "" { - fmt.Fprintf(cli.out, "%s: ", prompt) - } else { - fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault) - } - } - - readInput := func(in io.Reader, out io.Writer) string { - reader := bufio.NewReader(in) - line, _, err := reader.ReadLine() - if err != nil { - fmt.Fprintln(out, err.Error()) - os.Exit(1) - } - return string(line) - } - - cli.LoadConfigFile() - authconfig, ok := cli.configFile.Configs[serverAddress] - if !ok { - authconfig = registry.AuthConfig{} - } - - if username == "" { - promptDefault("Username", authconfig.Username) - username = readInput(cli.in, cli.out) - username = strings.Trim(username, " ") - if username == "" { - username = authconfig.Username - } - } - // Assume that a different username means they may not want to use - // the password or email from the config file, so prompt them - if username != authconfig.Username { - if password == "" { - oldState, err := term.SaveState(cli.inFd) - if err != nil { - return err - } - fmt.Fprintf(cli.out, "Password: ") - term.DisableEcho(cli.inFd, oldState) - - password = readInput(cli.in, cli.out) - fmt.Fprint(cli.out, "\n") - - term.RestoreTerminal(cli.inFd, oldState) - if password == "" { - return fmt.Errorf("Error : Password Required") - } - } - - if email == "" { - promptDefault("Email", authconfig.Email) - email = readInput(cli.in, cli.out) - if email == "" { - email = authconfig.Email - } - } - } else { - // However, if they don't override the username use the - // password or email from the cmd line if specified. IOW, allow - // then to change/override them. And if not specified, just - // use what's in the config file - if password == "" { - password = authconfig.Password - } - if email == "" { - email = authconfig.Email - } - } - authconfig.Username = username - authconfig.Password = password - authconfig.Email = email - authconfig.ServerAddress = serverAddress - cli.configFile.Configs[serverAddress] = authconfig - - stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false) - if statusCode == 401 { - delete(cli.configFile.Configs, serverAddress) - registry.SaveConfig(cli.configFile) - return err - } - if err != nil { - return err - } - - var response types.AuthResponse - if err := json.NewDecoder(stream).Decode(response); err != nil { - cli.configFile, _ = registry.LoadConfig(homedir.Get()) - return err - } - - registry.SaveConfig(cli.configFile) - fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s.\n", path.Join(homedir.Get(), registry.CONFIGFILE)) - - if response.Status != "" { - fmt.Fprintf(cli.out, "%s\n", response.Status) - } - return nil -} - -// log out from a Docker registry -func (cli *DockerCli) CmdLogout(args ...string) error { - cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true) - cmd.Require(flag.Max, 1) - - utils.ParseFlags(cmd, args, false) - serverAddress := registry.IndexServerAddress() - if len(cmd.Args()) > 0 { - serverAddress = cmd.Arg(0) - } - - cli.LoadConfigFile() - if _, ok := cli.configFile.Configs[serverAddress]; !ok { - fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress) - } else { - fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress) - delete(cli.configFile.Configs, serverAddress) - - if err := registry.SaveConfig(cli.configFile); err != nil { - return fmt.Errorf("Failed to save docker config: %v", err) - } - } - return nil -} - -// 'docker wait': block until a container stops -func (cli *DockerCli) CmdWait(args ...string) error { - cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.", true) - cmd.Require(flag.Min, 1) - - utils.ParseFlags(cmd, args, true) - - var encounteredError error - for _, name := range cmd.Args() { - status, err := waitForExit(cli, name) - if err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to wait one or more containers") - } else { - fmt.Fprintf(cli.out, "%d\n", status) - } - } - return encounteredError -} - -// 'docker version': show version information -func (cli *DockerCli) CmdVersion(args ...string) error { - cmd := cli.Subcmd("version", "", "Show the Docker version information.", true) - cmd.Require(flag.Exact, 0) - - utils.ParseFlags(cmd, args, false) - - 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", runtime.Version()) - if dockerversion.GITCOMMIT != "" { - fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT) - } - fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH) - - body, _, err := readBody(cli.call("GET", "/version", nil, false)) - if err != nil { - return err - } - - out := engine.NewOutput() - remoteVersion, err := out.AddEnv() - if err != nil { - log.Errorf("Error reading remote version: %s", err) - return err - } - if _, err := out.Write(body); err != nil { - log.Errorf("Error reading remote version: %s", err) - return err - } - 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, "Go version (server): %s\n", remoteVersion.Get("GoVersion")) - fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit")) - fmt.Fprintf(cli.out, "OS/Arch (server): %s/%s\n", remoteVersion.Get("Os"), remoteVersion.Get("Arch")) - return nil -} - -// 'docker info': display system-wide information. -func (cli *DockerCli) CmdInfo(args ...string) error { - cmd := cli.Subcmd("info", "", "Display system-wide information", true) - cmd.Require(flag.Exact, 0) - utils.ParseFlags(cmd, args, false) - - body, _, err := readBody(cli.call("GET", "/info", nil, false)) - if err != nil { - return err - } - - out := engine.NewOutput() - remoteInfo, err := out.AddEnv() - if err != nil { - return err - } - - if _, err := out.Write(body); err != nil { - log.Errorf("Error reading remote info: %s", err) - return err - } - out.Close() - - if remoteInfo.Exists("Containers") { - fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers")) - } - if remoteInfo.Exists("Images") { - fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images")) - } - if remoteInfo.Exists("Driver") { - fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver")) - } - if remoteInfo.Exists("DriverStatus") { - var driverStatus [][2]string - if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil { - return err - } - for _, pair := range driverStatus { - fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) - } - } - if remoteInfo.Exists("ExecutionDriver") { - fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver")) - } - if remoteInfo.Exists("KernelVersion") { - fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion")) - } - if remoteInfo.Exists("OperatingSystem") { - fmt.Fprintf(cli.out, "Operating System: %s\n", remoteInfo.Get("OperatingSystem")) - } - if remoteInfo.Exists("NCPU") { - fmt.Fprintf(cli.out, "CPUs: %d\n", remoteInfo.GetInt("NCPU")) - } - if remoteInfo.Exists("MemTotal") { - fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(remoteInfo.GetInt64("MemTotal")))) - } - if remoteInfo.Exists("Name") { - fmt.Fprintf(cli.out, "Name: %s\n", remoteInfo.Get("Name")) - } - if remoteInfo.Exists("ID") { - fmt.Fprintf(cli.out, "ID: %s\n", remoteInfo.Get("ID")) - } - - if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" { - if remoteInfo.Exists("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") != "") - if remoteInfo.Exists("NFd") { - fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd")) - } - if remoteInfo.Exists("NGoroutines") { - fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines")) - } - if remoteInfo.Exists("SystemTime") { - t, err := remoteInfo.GetTime("SystemTime") - if err != nil { - log.Errorf("Error reading system time: %v", err) - } else { - fmt.Fprintf(cli.out, "System Time: %s\n", t.Format(time.UnixDate)) - } - } - if remoteInfo.Exists("NEventsListener") { - fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener")) - } - if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" { - fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1) - } - if initPath := remoteInfo.Get("InitPath"); initPath != "" { - fmt.Fprintf(cli.out, "Init Path: %s\n", initPath) - } - if root := remoteInfo.Get("DockerRootDir"); root != "" { - fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", root) - } - } - if remoteInfo.Exists("HttpProxy") { - fmt.Fprintf(cli.out, "Http Proxy: %s\n", remoteInfo.Get("HttpProxy")) - } - if remoteInfo.Exists("HttpsProxy") { - fmt.Fprintf(cli.out, "Https Proxy: %s\n", remoteInfo.Get("HttpsProxy")) - } - if remoteInfo.Exists("NoProxy") { - fmt.Fprintf(cli.out, "No Proxy: %s\n", remoteInfo.Get("NoProxy")) - } - if len(remoteInfo.GetList("IndexServerAddress")) != 0 { - cli.LoadConfigFile() - u := cli.configFile.Configs[remoteInfo.Get("IndexServerAddress")].Username - if len(u) > 0 { - fmt.Fprintf(cli.out, "Username: %v\n", u) - fmt.Fprintf(cli.out, "Registry: %v\n", remoteInfo.GetList("IndexServerAddress")) - } - } - if remoteInfo.Exists("MemoryLimit") && !remoteInfo.GetBool("MemoryLimit") { - fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") - } - if remoteInfo.Exists("SwapLimit") && !remoteInfo.GetBool("SwapLimit") { - fmt.Fprintf(cli.err, "WARNING: No swap limit support\n") - } - if remoteInfo.Exists("IPv4Forwarding") && !remoteInfo.GetBool("IPv4Forwarding") { - fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n") - } - if remoteInfo.Exists("Labels") { - fmt.Fprintln(cli.out, "Labels:") - for _, attribute := range remoteInfo.GetList("Labels") { - fmt.Fprintf(cli.out, " %s\n", attribute) - } - } - - return nil -} - -func (cli *DockerCli) CmdStop(args ...string) error { - cmd := cli.Subcmd("stop", "CONTAINER [CONTAINER...]", "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true) - nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it") - cmd.Require(flag.Min, 1) - - utils.ParseFlags(cmd, args, true) - - v := url.Values{} - v.Set("t", strconv.Itoa(*nSeconds)) - - var encounteredError error - for _, name := range cmd.Args() { - _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, false)) - if err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to stop one or more containers") - } else { - fmt.Fprintf(cli.out, "%s\n", name) - } - } - return encounteredError -} - -func (cli *DockerCli) CmdRestart(args ...string) error { - cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container", true) - nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container") - cmd.Require(flag.Min, 1) - - utils.ParseFlags(cmd, args, true) - - v := url.Values{} - v.Set("t", strconv.Itoa(*nSeconds)) - - var encounteredError error - for _, name := range cmd.Args() { - _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false)) - if err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to restart one or more containers") - } else { - fmt.Fprintf(cli.out, "%s\n", name) - } - } - return encounteredError -} - -func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { - sigc := make(chan os.Signal, 128) - signal.CatchAll(sigc) - go func() { - for s := range sigc { - if s == signal.SIGCHLD { - continue - } - var sig string - for sigStr, sigN := range signal.SignalMap { - if sigN == s { - sig = sigStr - break - } - } - if sig == "" { - log.Errorf("Unsupported signal: %v. Discarding.", s) - } - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil { - log.Debugf("Error sending signal: %s", err) - } - } - }() - return sigc -} - -func (cli *DockerCli) CmdStart(args ...string) error { - var ( - cErr chan error - tty bool - - cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Start one or more stopped containers", true) - attach = cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals") - openStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN") - ) - - cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) - - if *attach || *openStdin { - if cmd.NArg() > 1 { - return fmt.Errorf("You cannot start and attach multiple containers at once.") - } - - stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false) - if err != nil { - return err - } - - env := engine.Env{} - if err := env.Decode(stream); err != nil { - return err - } - config := env.GetSubEnv("Config") - tty = config.GetBool("Tty") - - if !tty { - sigc := cli.forwardAllSignals(cmd.Arg(0)) - defer signal.StopCatch(sigc) - } - - var in io.ReadCloser - - v := url.Values{} - v.Set("stream", "1") - - if *openStdin && config.GetBool("OpenStdin") { - v.Set("stdin", "1") - in = cli.in - } - - v.Set("stdout", "1") - v.Set("stderr", "1") - - hijacked := make(chan io.Closer) - // Block the return until the chan gets closed - defer func() { - log.Debugf("CmdStart() returned, defer waiting for hijack to finish.") - if _, ok := <-hijacked; ok { - log.Errorf("Hijack did not finish (chan still open)") - } - cli.in.Close() - }() - cErr = promise.Go(func() error { - return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil) - }) - - // Acknowledge the hijack before starting - select { - case closer := <-hijacked: - // Make sure that the hijack gets closed when returning (results - // in closing the hijack chan and freeing server's goroutines) - if closer != nil { - defer closer.Close() - } - case err := <-cErr: - if err != nil { - return err - } - } - } - - var encounteredError error - for _, name := range cmd.Args() { - _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false)) - if err != nil { - if !*attach && !*openStdin { - // attach and openStdin is false means it could be starting multiple containers - // when a container start failed, show the error message and start next - fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to start one or more containers") - } else { - encounteredError = err - } - } else { - if !*attach && !*openStdin { - fmt.Fprintf(cli.out, "%s\n", name) - } - } - } - - if encounteredError != nil { - return encounteredError - } - - if *openStdin || *attach { - if tty && cli.isTerminalOut { - if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { - log.Errorf("Error monitoring TTY size: %s", err) - } - } - if attchErr := <-cErr; attchErr != nil { - return attchErr - } - _, status, err := getExitCode(cli, cmd.Arg(0)) - if err != nil { - return err - } - if status != 0 { - return &utils.StatusError{StatusCode: status} - } - } - return nil -} - -func (cli *DockerCli) CmdUnpause(args ...string) error { - cmd := cli.Subcmd("unpause", "CONTAINER [CONTAINER...]", "Unpause all processes within a container", true) - cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, false) - - var encounteredError error - for _, name := range cmd.Args() { - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, false)); err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name) - } else { - fmt.Fprintf(cli.out, "%s\n", name) - } - } - return encounteredError -} - -func (cli *DockerCli) CmdPause(args ...string) error { - cmd := cli.Subcmd("pause", "CONTAINER [CONTAINER...]", "Pause all processes within a container", true) - cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, false) - - var encounteredError error - for _, name := range cmd.Args() { - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, false)); err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to pause container named %s", name) - } else { - fmt.Fprintf(cli.out, "%s\n", name) - } - } - return encounteredError -} - -func (cli *DockerCli) CmdRename(args ...string) error { - cmd := cli.Subcmd("rename", "OLD_NAME NEW_NAME", "Rename a container", true) - if err := cmd.Parse(args); err != nil { - return nil - } - - if cmd.NArg() != 2 { - cmd.Usage() - return nil - } - old_name := cmd.Arg(0) - new_name := cmd.Arg(1) - - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", old_name, new_name), nil, false)); err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - return fmt.Errorf("Error: failed to rename container named %s", old_name) - } - return nil -} - -func (cli *DockerCli) CmdInspect(args ...string) error { - cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true) - tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template") - cmd.Require(flag.Min, 1) - - utils.ParseFlags(cmd, args, true) - - var tmpl *template.Template - if *tmplStr != "" { - var err error - if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil { - fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) - return &utils.StatusError{StatusCode: 64, - Status: "Template parsing error: " + err.Error()} - } - } - - indented := new(bytes.Buffer) - indented.WriteByte('[') - status := 0 - - for _, name := range cmd.Args() { - obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false)) - if err != nil { - if strings.Contains(err.Error(), "Too many") { - fmt.Fprintf(cli.err, "Error: %v", err) - status = 1 - continue - } - - obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, false)) - if err != nil { - if strings.Contains(err.Error(), "No such") { - fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name) - } else { - fmt.Fprintf(cli.err, "%s", err) - } - status = 1 - continue - } - } - - if tmpl == nil { - if err = json.Indent(indented, obj, "", " "); err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - status = 1 - continue - } - } else { - // Has template, will render - var value interface{} - if err := json.Unmarshal(obj, &value); err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - status = 1 - continue - } - if err := tmpl.Execute(cli.out, value); err != nil { - return err - } - cli.out.Write([]byte{'\n'}) - } - indented.WriteString(",") - } - - if indented.Len() > 1 { - // Remove trailing ',' - indented.Truncate(indented.Len() - 1) - } - indented.WriteString("]\n") - - if tmpl == nil { - if _, err := io.Copy(cli.out, indented); err != nil { - return err - } - } - - if status != 0 { - return &utils.StatusError{StatusCode: status} - } - return nil -} - -func (cli *DockerCli) CmdTop(args ...string) error { - cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Display the running processes of a container", true) - cmd.Require(flag.Min, 1) - - utils.ParseFlags(cmd, args, true) - - val := url.Values{} - if cmd.NArg() > 1 { - val.Set("ps_args", strings.Join(cmd.Args()[1:], " ")) - } - - stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false) - if err != nil { - return err - } - var procs engine.Env - if err := procs.Decode(stream); err != nil { - return err - } - w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - fmt.Fprintln(w, strings.Join(procs.GetList("Titles"), "\t")) - processes := [][]string{} - if err := procs.GetJson("Processes", &processes); err != nil { - return err - } - for _, proc := range processes { - fmt.Fprintln(w, strings.Join(proc, "\t")) - } - w.Flush() - return nil -} - -func (cli *DockerCli) CmdPort(args ...string) error { - cmd := cli.Subcmd("port", "CONTAINER [PRIVATE_PORT[/PROTO]]", "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true) - cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) - - stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false) - if err != nil { - return err - } - - env := engine.Env{} - if err := env.Decode(stream); err != nil { - return err - } - ports := nat.PortMap{} - if err := env.GetSubEnv("NetworkSettings").GetJson("Ports", &ports); err != nil { - return err - } - - if cmd.NArg() == 2 { - var ( - port = cmd.Arg(1) - proto = "tcp" - parts = strings.SplitN(port, "/", 2) - ) - - if len(parts) == 2 && len(parts[1]) != 0 { - port = parts[0] - proto = parts[1] - } - natPort := port + "/" + proto - if frontends, exists := ports[nat.Port(port+"/"+proto)]; exists && frontends != nil { - for _, frontend := range frontends { - fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) - } - return nil - } - return fmt.Errorf("Error: No public port '%s' published for %s", natPort, cmd.Arg(0)) - } - - for from, frontends := range ports { - for _, frontend := range frontends { - fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIp, frontend.HostPort) - } - } - - return nil -} - -// '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", true) - force = cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image") - noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents") - ) - cmd.Require(flag.Min, 1) - - utils.ParseFlags(cmd, args, true) - - v := url.Values{} - if *force { - v.Set("force", "1") - } - if *noprune { - v.Set("noprune", "1") - } - - var encounteredError error - for _, name := range cmd.Args() { - body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false)) - if err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to remove one or more images") - } else { - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to remove one or more images") - continue - } - for _, out := range outs.Data { - if out.Get("Deleted") != "" { - fmt.Fprintf(cli.out, "Deleted: %s\n", out.Get("Deleted")) - } else { - fmt.Fprintf(cli.out, "Untagged: %s\n", out.Get("Untagged")) - } - } - } - } - return encounteredError -} - -func (cli *DockerCli) CmdHistory(args ...string) error { - cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image", true) - quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") - noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") - cmd.Require(flag.Exact, 1) - - utils.ParseFlags(cmd, args, true) - - body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false)) - if err != nil { - return err - } - - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { - return err - } - - w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - if !*quiet { - fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE") - } - - for _, out := range outs.Data { - outID := out.Get("Id") - if !*quiet { - if *noTrunc { - fmt.Fprintf(w, "%s\t", outID) - } else { - fmt.Fprintf(w, "%s\t", stringid.TruncateID(outID)) - } - - fmt.Fprintf(w, "%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0)))) - - if *noTrunc { - fmt.Fprintf(w, "%s\t", out.Get("CreatedBy")) - } else { - fmt.Fprintf(w, "%s\t", utils.Trunc(out.Get("CreatedBy"), 45)) - } - fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("Size")))) - } else { - if *noTrunc { - fmt.Fprintln(w, outID) - } else { - fmt.Fprintln(w, stringid.TruncateID(outID)) - } - } - } - w.Flush() - return nil -} - -func (cli *DockerCli) CmdRm(args ...string) error { - cmd := cli.Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove one or more containers", true) - v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container") - link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link") - force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)") - cmd.Require(flag.Min, 1) - - utils.ParseFlags(cmd, args, true) - - val := url.Values{} - if *v { - val.Set("v", "1") - } - if *link { - val.Set("link", "1") - } - - if *force { - val.Set("force", "1") - } - - var encounteredError error - for _, name := range cmd.Args() { - _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, false)) - if err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to remove one or more containers") - } else { - fmt.Fprintf(cli.out, "%s\n", name) - } - } - return encounteredError -} - -// 'docker kill NAME' kills a running container -func (cli *DockerCli) CmdKill(args ...string) error { - cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal", true) - signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container") - cmd.Require(flag.Min, 1) - - utils.ParseFlags(cmd, args, true) - - var encounteredError error - for _, name := range cmd.Args() { - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to kill one or more containers") - } else { - fmt.Fprintf(cli.out, "%s\n", name) - } - } - return encounteredError -} - -func (cli *DockerCli) CmdImport(args ...string) error { - cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true) - flChanges := opts.NewListOpts(nil) - cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image") - cmd.Require(flag.Min, 1) - - utils.ParseFlags(cmd, args, true) - - var ( - v = url.Values{} - src = cmd.Arg(0) - repository = cmd.Arg(1) - ) - - v.Set("fromSrc", src) - v.Set("repo", repository) - for _, change := range flChanges.GetAll() { - v.Add("changes", change) - } - if cmd.NArg() == 3 { - fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' has been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n") - v.Set("tag", cmd.Arg(2)) - } - - if repository != "" { - //Check if the given image name can be resolved - repo, _ := parsers.ParseRepositoryTag(repository) - if err := registry.ValidateRepositoryName(repo); err != nil { - return err - } - } - - var in io.Reader - - if src == "-" { - in = cli.in - } - - return cli.stream("POST", "/images/create?"+v.Encode(), in, cli.out, nil) -} - -func (cli *DockerCli) CmdPush(args ...string) error { - cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry", true) - cmd.Require(flag.Exact, 1) - - utils.ParseFlags(cmd, args, true) - - name := cmd.Arg(0) - - cli.LoadConfigFile() - - remote, tag := parsers.ParseRepositoryTag(name) - - // Resolve the Repository name from fqn to RepositoryInfo - repoInfo, err := registry.ParseRepositoryInfo(remote) - if err != nil { - return err - } - // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) - // If we're not using a custom registry, we know the restrictions - // applied to repository names and can warn the user in advance. - // Custom repositories can have different rules, and we must also - // allow pushing by image ID. - if repoInfo.Official { - username := authConfig.Username - if username == "" { - username = "" - } - return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to / (ex: %s/%s)", username, repoInfo.LocalName) - } - - v := url.Values{} - v.Set("tag", tag) - - push := func(authConfig registry.AuthConfig) error { - buf, err := json.Marshal(authConfig) - if err != nil { - return err - } - registryAuthHeader := []string{ - base64.URLEncoding.EncodeToString(buf), - } - - return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{ - "X-Registry-Auth": registryAuthHeader, - }) - } - - if err := push(authConfig); err != nil { - if strings.Contains(err.Error(), "Status 401") { - fmt.Fprintln(cli.out, "\nPlease login prior to push:") - if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil { - return err - } - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) - return push(authConfig) - } - return err - } - return nil -} - -func (cli *DockerCli) CmdPull(args ...string) error { - cmd := cli.Subcmd("pull", "NAME[:TAG|@DIGEST]", "Pull an image or a repository from the registry", true) - allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository") - cmd.Require(flag.Exact, 1) - - utils.ParseFlags(cmd, args, true) - - var ( - v = url.Values{} - remote = cmd.Arg(0) - newRemote = remote - ) - taglessRemote, tag := parsers.ParseRepositoryTag(remote) - if tag == "" && !*allTags { - newRemote = utils.ImageReference(taglessRemote, graph.DEFAULTTAG) - } - if tag != "" && *allTags { - return fmt.Errorf("tag can't be used with --all-tags/-a") - } - - v.Set("fromImage", newRemote) - - // Resolve the Repository name from fqn to RepositoryInfo - repoInfo, err := registry.ParseRepositoryInfo(taglessRemote) - if err != nil { - return err - } - - cli.LoadConfigFile() - - // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) - - pull := func(authConfig registry.AuthConfig) error { - buf, err := json.Marshal(authConfig) - if err != nil { - return err - } - registryAuthHeader := []string{ - base64.URLEncoding.EncodeToString(buf), - } - - return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{ - "X-Registry-Auth": registryAuthHeader, - }) - } - - if err := pull(authConfig); err != nil { - if strings.Contains(err.Error(), "Status 401") { - fmt.Fprintln(cli.out, "\nPlease login prior to pull:") - if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil { - return err - } - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) - return pull(authConfig) - } - return err - } - - return nil -} - -func (cli *DockerCli) CmdImages(args ...string) error { - cmd := cli.Subcmd("images", "[REPOSITORY]", "List images", true) - quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") - all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)") - noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") - showDigests := cmd.Bool([]string{"-digests"}, false, "Show digests") - // 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") - - flFilter := opts.NewListOpts(nil) - cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") - cmd.Require(flag.Max, 1) - - utils.ParseFlags(cmd, args, true) - - // Consolidate all filter flags, and sanity check them early. - // They'll get process in the daemon/server. - imageFilterArgs := filters.Args{} - for _, f := range flFilter.GetAll() { - var err error - imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs) - if err != nil { - return err - } - } - - matchName := cmd.Arg(0) - // FIXME: --viz and --tree are deprecated. Remove them in a future version. - if *flViz || *flTree { - v := url.Values{ - "all": []string{"1"}, - } - if len(imageFilterArgs) > 0 { - filterJson, err := filters.ToParam(imageFilterArgs) - if err != nil { - return err - } - v.Set("filters", filterJson) - } - - body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) - if err != nil { - return err - } - - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { - return err - } - - var ( - printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string) - startImage *engine.Env - - roots = engine.NewTable("Created", outs.Len()) - byParent = make(map[string]*engine.Table) - ) - - for _, image := range outs.Data { - if image.Get("ParentId") == "" { - roots.Add(image) - } else { - if children, exists := byParent[image.Get("ParentId")]; exists { - children.Add(image) - } else { - byParent[image.Get("ParentId")] = engine.NewTable("Created", 1) - byParent[image.Get("ParentId")].Add(image) - } - } - - if matchName != "" { - if matchName == image.Get("Id") || matchName == stringid.TruncateID(image.Get("Id")) { - startImage = image - } - - for _, repotag := range image.GetList("RepoTags") { - if repotag == matchName { - startImage = image - } - } - } - } - - if *flViz { - fmt.Fprintf(cli.out, "digraph docker {\n") - printNode = (*DockerCli).printVizNode - } else { - printNode = (*DockerCli).printTreeNode - } - - if startImage != nil { - root := engine.NewTable("Created", 1) - root.Add(startImage) - cli.WalkTree(*noTrunc, root, byParent, "", printNode) - } else if matchName == "" { - cli.WalkTree(*noTrunc, roots, byParent, "", printNode) - } - if *flViz { - fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") - } - } else { - v := url.Values{} - if len(imageFilterArgs) > 0 { - filterJson, err := filters.ToParam(imageFilterArgs) - if err != nil { - return err - } - v.Set("filters", filterJson) - } - - if cmd.NArg() == 1 { - // FIXME rename this parameter, to not be confused with the filters flag - v.Set("filter", matchName) - } - if *all { - v.Set("all", "1") - } - - body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) - - if err != nil { - return err - } - - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { - return err - } - - w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - if !*quiet { - if *showDigests { - fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE") - } else { - fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") - } - } - - for _, out := range outs.Data { - outID := out.Get("Id") - if !*noTrunc { - outID = stringid.TruncateID(outID) - } - - repoTags := out.GetList("RepoTags") - repoDigests := out.GetList("RepoDigests") - - if len(repoTags) == 1 && repoTags[0] == ":" && len(repoDigests) == 1 && repoDigests[0] == "@" { - // dangling image - clear out either repoTags or repoDigsts so we only show it once below - repoDigests = []string{} - } - - // combine the tags and digests lists - tagsAndDigests := append(repoTags, repoDigests...) - for _, repoAndRef := range tagsAndDigests { - repo, ref := parsers.ParseRepositoryTag(repoAndRef) - // default tag and digest to none - if there's a value, it'll be set below - tag := "" - digest := "" - if utils.DigestReference(ref) { - digest = ref - } else { - tag = ref - } - - if !*quiet { - if *showDigests { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) - } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) - } - } else { - fmt.Fprintln(w, outID) - } - } - } - - if !*quiet { - w.Flush() - } - } - 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 { - for index, image := range images.Data { - if index+1 == length { - printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.Get("Id")]; exists { - cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) - } - } else { - printNode(cli, noTrunc, image, prefix+"\u251C─") - if subimages, exists := byParent[image.Get("Id")]; exists { - cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) - } - } - } - } else { - for _, image := range images.Data { - printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.Get("Id")]; exists { - cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) - } - } - } -} - -// 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 - parentID string - ) - if noTrunc { - imageID = image.Get("Id") - parentID = image.Get("ParentId") - } else { - imageID = stringid.TruncateID(image.Get("Id")) - parentID = stringid.TruncateID(image.Get("ParentId")) - } - if parentID == "" { - fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) - } else { - fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", parentID, imageID) - } - if image.GetList("RepoTags")[0] != ":" { - fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", - imageID, imageID, strings.Join(image.GetList("RepoTags"), "\\n")) - } -} - -// 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 { - imageID = image.Get("Id") - } else { - imageID = stringid.TruncateID(image.Get("Id")) - } - - fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(float64(image.GetInt64("VirtualSize")))) - if image.GetList("RepoTags")[0] != ":" { - fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.GetList("RepoTags"), ", ")) - } else { - fmt.Fprint(cli.out, "\n") - } -} - -func (cli *DockerCli) CmdPs(args ...string) error { - var ( - err error - - psFilterArgs = filters.Args{} - v = url.Values{} - - cmd = cli.Subcmd("ps", "", "List containers", true) - quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs") - size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes") - all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)") - noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") - nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show the latest created container, include non-running") - since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running") - before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name") - last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running") - flFilter = opts.NewListOpts(nil) - ) - cmd.Require(flag.Exact, 0) - - cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") - - utils.ParseFlags(cmd, args, true) - if *last == -1 && *nLatest { - *last = 1 - } - - if *all { - v.Set("all", "1") - } - - if *last != -1 { - v.Set("limit", strconv.Itoa(*last)) - } - - if *since != "" { - v.Set("since", *since) - } - - if *before != "" { - v.Set("before", *before) - } - - if *size { - v.Set("size", "1") - } - - // Consolidate all filter flags, and sanity check them. - // They'll get processed in the daemon/server. - for _, f := range flFilter.GetAll() { - if psFilterArgs, err = filters.ParseFlag(f, psFilterArgs); err != nil { - return err - } - } - - if len(psFilterArgs) > 0 { - filterJson, err := filters.ToParam(psFilterArgs) - if err != nil { - return err - } - - v.Set("filters", filterJson) - } - - body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false)) - if err != nil { - return err - } - - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { - return err - } - - w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - if !*quiet { - fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES") - - if *size { - fmt.Fprintln(w, "\tSIZE") - } else { - fmt.Fprint(w, "\n") - } - } - - stripNamePrefix := func(ss []string) []string { - for i, s := range ss { - ss[i] = s[1:] - } - - return ss - } - - for _, out := range outs.Data { - outID := out.Get("Id") - - if !*noTrunc { - outID = stringid.TruncateID(outID) - } - - if *quiet { - fmt.Fprintln(w, outID) - - continue - } - - var ( - outNames = stripNamePrefix(out.GetList("Names")) - outCommand = strconv.Quote(out.Get("Command")) - ports = engine.NewTable("", 0) - ) - - if !*noTrunc { - outCommand = utils.Trunc(outCommand, 20) - - // only display the default name for the container with notrunc is passed - for _, name := range outNames { - if len(strings.Split(name, "/")) == 1 { - outNames = []string{name} - - break - } - } - } - - ports.ReadListFrom([]byte(out.Get("Ports"))) - - image := out.Get("Image") - if image == "" { - image = "" - } - - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, image, outCommand, - units.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", units.HumanSize(float64(out.GetInt64("SizeRw"))), units.HumanSize(float64(out.GetInt64("SizeRootFs")))) - } else { - fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("SizeRw")))) - } - - continue - } - - fmt.Fprint(w, "\n") - } - - if !*quiet { - w.Flush() - } - - return nil -} - -func (cli *DockerCli) CmdCommit(args ...string) error { - cmd := cli.Subcmd("commit", "CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes", true) - flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit") - flComment := cmd.String([]string{"m", "-message"}, "", "Commit message") - flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith \")") - flChanges := opts.NewListOpts(nil) - cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image") - // 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") - cmd.Require(flag.Max, 2) - cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) - - var ( - name = cmd.Arg(0) - repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) - ) - - //Check if the given image name can be resolved - if repository != "" { - if err := registry.ValidateRepositoryName(repository); err != nil { - return err - } - } - - v := url.Values{} - v.Set("container", name) - v.Set("repo", repository) - v.Set("tag", tag) - v.Set("comment", *flComment) - v.Set("author", *flAuthor) - for _, change := range flChanges.GetAll() { - v.Add("changes", change) - } - - if *flPause != true { - v.Set("pause", "0") - } - - var ( - config *runconfig.Config - env engine.Env - ) - if *flConfig != "" { - config = &runconfig.Config{} - if err := json.Unmarshal([]byte(*flConfig), config); err != nil { - return err - } - } - stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false) - if err != nil { - return err - } - if err := env.Decode(stream); err != nil { - return err - } - - fmt.Fprintf(cli.out, "%s\n", env.Get("Id")) - return nil -} - -func (cli *DockerCli) CmdEvents(args ...string) error { - cmd := cli.Subcmd("events", "", "Get real time events from the server", true) - since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp") - until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp") - flFilter := opts.NewListOpts(nil) - cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") - cmd.Require(flag.Exact, 0) - - utils.ParseFlags(cmd, args, true) - - var ( - v = url.Values{} - loc = time.FixedZone(time.Now().Zone()) - eventFilterArgs = filters.Args{} - ) - - // Consolidate all filter flags, and sanity check them early. - // They'll get process in the daemon/server. - for _, f := range flFilter.GetAll() { - var err error - eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs) - if err != nil { - return err - } - } - var setTime = func(key, value string) { - format := timeutils.RFC3339NanoFixed - if len(value) < len(format) { - format = format[:len(value)] - } - if t, err := time.ParseInLocation(format, value, loc); err == nil { - v.Set(key, strconv.FormatInt(t.Unix(), 10)) - } else { - v.Set(key, value) - } - } - if *since != "" { - setTime("since", *since) - } - if *until != "" { - setTime("until", *until) - } - if len(eventFilterArgs) > 0 { - filterJson, err := filters.ToParam(eventFilterArgs) - if err != nil { - return err - } - v.Set("filters", filterJson) - } - if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil { - return err - } - return nil -} - -func (cli *DockerCli) CmdExport(args ...string) error { - cmd := cli.Subcmd("export", "CONTAINER", "Export a filesystem as a tar archive (streamed to STDOUT by default)", true) - outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT") - cmd.Require(flag.Exact, 1) - - utils.ParseFlags(cmd, args, true) - - var ( - output io.Writer = cli.out - err error - ) - if *outfile != "" { - output, err = os.Create(*outfile) - if err != nil { - return err - } - } else if cli.isTerminalOut { - return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.") - } - - if len(cmd.Args()) == 1 { - image := cmd.Arg(0) - if err := cli.stream("GET", "/containers/"+image+"/export", nil, output, nil); err != nil { - return err - } - } else { - v := url.Values{} - for _, arg := range cmd.Args() { - v.Add("names", arg) - } - if err := cli.stream("GET", "/containers/get?"+v.Encode(), nil, output, nil); err != nil { - return err - } - } - - return nil -} - -func (cli *DockerCli) CmdDiff(args ...string) error { - cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem", true) - cmd.Require(flag.Exact, 1) - - utils.ParseFlags(cmd, args, true) - - body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false)) - - if err != nil { - return err - } - - outs := engine.NewTable("", 0) - if _, err := outs.ReadListFrom(body); err != nil { - return err - } - for _, change := range outs.Data { - var kind string - switch change.GetInt("Kind") { - case archive.ChangeModify: - kind = "C" - case archive.ChangeAdd: - kind = "A" - case archive.ChangeDelete: - kind = "D" - } - fmt.Fprintf(cli.out, "%s %s\n", kind, change.Get("Path")) - } - return nil -} - -func (cli *DockerCli) CmdLogs(args ...string) error { - var ( - cmd = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", true) - follow = cmd.Bool([]string{"f", "-follow"}, false, "Follow log output") - times = cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps") - tail = cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs") - ) - cmd.Require(flag.Exact, 1) - - utils.ParseFlags(cmd, args, true) - - name := cmd.Arg(0) - - stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) - if err != nil { - return err - } - - env := engine.Env{} - if err := env.Decode(stream); err != nil { - return err - } - - if env.GetSubEnv("HostConfig").GetSubEnv("LogConfig").Get("Type") != "json-file" { - return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver") - } - - v := url.Values{} - v.Set("stdout", "1") - v.Set("stderr", "1") - - if *times { - v.Set("timestamps", "1") - } - - if *follow { - v.Set("follow", "1") - } - v.Set("tail", *tail) - - return cli.streamHelper("GET", "/containers/"+name+"/logs?"+v.Encode(), env.GetSubEnv("Config").GetBool("Tty"), nil, cli.out, cli.err, nil) -} - -func (cli *DockerCli) CmdAttach(args ...string) error { - var ( - cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container", true) - noStdin = cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN") - proxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process") - ) - cmd.Require(flag.Exact, 1) - - utils.ParseFlags(cmd, args, true) - name := cmd.Arg(0) - - stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) - if err != nil { - return err - } - - env := engine.Env{} - if err := env.Decode(stream); err != nil { - return err - } - - if !env.GetSubEnv("State").GetBool("Running") { - return fmt.Errorf("You cannot attach to a stopped container, start it first") - } - - var ( - config = env.GetSubEnv("Config") - tty = config.GetBool("Tty") - ) - - if err := cli.CheckTtyInput(!*noStdin, tty); err != nil { - return err - } - - if tty && cli.isTerminalOut { - if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { - log.Debugf("Error monitoring TTY size: %s", err) - } - } - - var in io.ReadCloser - - v := url.Values{} - v.Set("stream", "1") - if !*noStdin && config.GetBool("OpenStdin") { - v.Set("stdin", "1") - in = cli.in - } - - v.Set("stdout", "1") - v.Set("stderr", "1") - - if *proxy && !tty { - sigc := cli.forwardAllSignals(cmd.Arg(0)) - defer signal.StopCatch(sigc) - } - - if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil); err != nil { - return err - } - - _, status, err := getExitCode(cli, cmd.Arg(0)) - if err != nil { - return err - } - if status != 0 { - return &utils.StatusError{StatusCode: status} - } - - return nil -} - -func (cli *DockerCli) CmdSearch(args ...string) error { - cmd := cli.Subcmd("search", "TERM", "Search the Docker Hub for images", true) - noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") - trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds") - automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds") - stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least x stars") - cmd.Require(flag.Exact, 1) - - utils.ParseFlags(cmd, args, true) - - v := url.Values{} - v.Set("term", cmd.Arg(0)) - - body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true)) - - if err != nil { - return err - } - outs := engine.NewTable("star_count", 0) - if _, err := outs.ReadListFrom(body); err != nil { - return err - } - w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) - fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") - for _, out := range outs.Data { - if ((*automated || *trusted) && (!out.GetBool("is_trusted") && !out.GetBool("is_automated"))) || (*stars > out.GetInt("star_count")) { - continue - } - desc := strings.Replace(out.Get("description"), "\n", " ", -1) - desc = strings.Replace(desc, "\r", " ", -1) - if !*noTrunc && len(desc) > 45 { - desc = utils.Trunc(desc, 42) + "..." - } - fmt.Fprintf(w, "%s\t%s\t%d\t", out.Get("name"), desc, out.GetInt("star_count")) - if out.GetBool("is_official") { - fmt.Fprint(w, "[OK]") - - } - fmt.Fprint(w, "\t") - if out.GetBool("is_automated") || out.GetBool("is_trusted") { - fmt.Fprint(w, "[OK]") - } - fmt.Fprint(w, "\n") - } - w.Flush() - return nil -} - -// Ports type - Used to parse multiple -p flags -type ports []int - -func (cli *DockerCli) CmdTag(args ...string) error { - cmd := cli.Subcmd("tag", "IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository", true) - force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force") - cmd.Require(flag.Exact, 2) - - utils.ParseFlags(cmd, args, true) - - var ( - repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) - v = url.Values{} - ) - - //Check if the given image name can be resolved - if err := registry.ValidateRepositoryName(repository); err != nil { - return err - } - v.Set("repo", repository) - v.Set("tag", tag) - - if *force { - v.Set("force", "1") - } - - if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false)); err != nil { - return err - } - return nil -} - -func (cli *DockerCli) pullImage(image string) error { - return cli.pullImageCustomOut(image, cli.out) -} - -func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error { - v := url.Values{} - repos, tag := parsers.ParseRepositoryTag(image) - // pull only the image tagged 'latest' if no tag was specified - if tag == "" { - tag = graph.DEFAULTTAG - } - v.Set("fromImage", repos) - v.Set("tag", tag) - - // Resolve the Repository name from fqn to RepositoryInfo - repoInfo, err := registry.ParseRepositoryInfo(repos) - if err != nil { - return err - } - - // Load the auth config file, to be able to pull the image - cli.LoadConfigFile() - - // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) - buf, err := json.Marshal(authConfig) - if err != nil { - return err - } - - registryAuthHeader := []string{ - base64.URLEncoding.EncodeToString(buf), - } - if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, out, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil { - return err - } - return nil -} - -type cidFile struct { - path string - file *os.File - written bool -} - -func newCIDFile(path string) (*cidFile, error) { - if _, err := os.Stat(path); err == nil { - return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path) - } - - f, err := os.Create(path) - if err != nil { - return nil, fmt.Errorf("Failed to create the container ID file: %s", err) - } - - return &cidFile{path: path, file: f}, nil -} - -func (cid *cidFile) Close() error { - cid.file.Close() - - if !cid.written { - if err := os.Remove(cid.path); err != nil { - return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err) - } - } - - return nil -} - -func (cid *cidFile) Write(id string) error { - if _, err := cid.file.Write([]byte(id)); err != nil { - return fmt.Errorf("Failed to write the container ID to the file: %s", err) - } - cid.written = true - return nil -} - -func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) { - containerValues := url.Values{} - if name != "" { - containerValues.Set("name", name) - } - - mergedConfig := runconfig.MergeConfigs(config, hostConfig) - - var containerIDFile *cidFile - if cidfile != "" { - var err error - if containerIDFile, err = newCIDFile(cidfile); err != nil { - return nil, err - } - defer containerIDFile.Close() - } - - //create the container - stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false) - //if image not found try to pull it - if statusCode == 404 { - repo, tag := parsers.ParseRepositoryTag(config.Image) - if tag == "" { - tag = graph.DEFAULTTAG - } - fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", utils.ImageReference(repo, tag)) - - // we don't want to write to stdout anything apart from container.ID - if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil { - return nil, err - } - // Retry - if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false); err != nil { - return nil, err - } - } else if err != nil { - return nil, err - } - - var response types.ContainerCreateResponse - if err := json.NewDecoder(stream).Decode(&response); err != nil { - return nil, err - } - for _, warning := range response.Warnings { - fmt.Fprintf(cli.err, "WARNING: %s\n", warning) - } - if containerIDFile != nil { - if err = containerIDFile.Write(response.ID); err != nil { - return nil, err - } - } - return &response, nil -} - -func (cli *DockerCli) CmdCreate(args ...string) error { - cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container", true) - - // These are flags not stored in Config/HostConfig - var ( - flName = cmd.String([]string{"-name"}, "", "Assign a name to the container") - ) - - config, hostConfig, cmd, err := runconfig.Parse(cmd, args) - if err != nil { - utils.ReportError(cmd, err.Error(), true) - } - if config.Image == "" { - cmd.Usage() - return nil - } - response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) - if err != nil { - return err - } - fmt.Fprintf(cli.out, "%s\n", response.ID) - return nil -} - -func (cli *DockerCli) CmdRun(args ...string) error { - // FIXME: just use runconfig.Parse already - cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container", true) - - // These are flags not stored in Config/HostConfig - var ( - flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits") - flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID") - flSigProxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process") - flName = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container") - flAttach *opts.ListOpts - - ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") - ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm") - ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d") - ) - - config, hostConfig, cmd, err := runconfig.Parse(cmd, args) - // just in case the Parse does not exit - if err != nil { - utils.ReportError(cmd, err.Error(), true) - } - - if len(hostConfig.Dns) > 0 { - // check the DNS settings passed via --dns against - // localhost regexp to warn if they are trying to - // set a DNS to a localhost address - for _, dnsIP := range hostConfig.Dns { - if resolvconf.IsLocalhost(dnsIP) { - fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) - break - } - } - } - if config.Image == "" { - cmd.Usage() - return nil - } - - if !*flDetach { - if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil { - return err - } - } else { - if fl := cmd.Lookup("-attach"); fl != nil { - flAttach = fl.Value.(*opts.ListOpts) - if flAttach.Len() != 0 { - return ErrConflictAttachDetach - } - } - if *flAutoRemove { - return ErrConflictDetachAutoRemove - } - - config.AttachStdin = false - config.AttachStdout = false - config.AttachStderr = false - config.StdinOnce = false - } - - // Disable flSigProxy when in TTY mode - sigProxy := *flSigProxy - if config.Tty { - sigProxy = false - } - - createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) - if err != nil { - return err - } - if sigProxy { - sigc := cli.forwardAllSignals(createResponse.ID) - defer signal.StopCatch(sigc) - } - var ( - waitDisplayId chan struct{} - errCh chan error - ) - if !config.AttachStdout && !config.AttachStderr { - // Make this asynchronous to allow the client to write to stdin before having to read the ID - waitDisplayId = make(chan struct{}) - go func() { - defer close(waitDisplayId) - fmt.Fprintf(cli.out, "%s\n", createResponse.ID) - }() - } - if *flAutoRemove && (hostConfig.RestartPolicy.Name == "always" || hostConfig.RestartPolicy.Name == "on-failure") { - return ErrConflictRestartPolicyAndAutoRemove - } - // We need to instantiate the chan because the select needs it. It can - // be closed but can't be uninitialized. - hijacked := make(chan io.Closer) - // Block the return until the chan gets closed - defer func() { - log.Debugf("End of CmdRun(), Waiting for hijack to finish.") - if _, ok := <-hijacked; ok { - log.Errorf("Hijack did not finish (chan still open)") - } - }() - if config.AttachStdin || config.AttachStdout || config.AttachStderr { - var ( - out, stderr io.Writer - in io.ReadCloser - v = url.Values{} - ) - v.Set("stream", "1") - if config.AttachStdin { - v.Set("stdin", "1") - in = cli.in - } - if config.AttachStdout { - v.Set("stdout", "1") - out = cli.out - } - if config.AttachStderr { - v.Set("stderr", "1") - if config.Tty { - stderr = cli.out - } else { - stderr = cli.err - } - } - errCh = promise.Go(func() error { - return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil) - }) - } else { - close(hijacked) - } - // Acknowledge the hijack before starting - select { - case closer := <-hijacked: - // Make sure that the hijack gets closed when returning (results - // in closing the hijack chan and freeing server's goroutines) - if closer != nil { - defer closer.Close() - } - case err := <-errCh: - if err != nil { - log.Debugf("Error hijack: %s", err) - return err - } - } - - defer func() { - if *flAutoRemove { - if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, false)); err != nil { - log.Errorf("Error deleting container: %s", err) - } - } - }() - - //start the container - if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, false)); err != nil { - return err - } - - if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut { - if err := cli.monitorTtySize(createResponse.ID, false); err != nil { - log.Errorf("Error monitoring TTY size: %s", err) - } - } - - if errCh != nil { - if err := <-errCh; err != nil { - log.Debugf("Error hijack: %s", err) - return err - } - } - - // Detached mode: wait for the id to be displayed and return. - if !config.AttachStdout && !config.AttachStderr { - // Detached mode - <-waitDisplayId - return nil - } - - var status int - - // Attached mode - if *flAutoRemove { - // Autoremove: wait for the container to finish, retrieve - // the exit code and remove the container - if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, false)); err != nil { - return err - } - if _, status, err = getExitCode(cli, createResponse.ID); err != nil { - return err - } - } else { - // No Autoremove: Simply retrieve the exit code - if !config.Tty { - // In non-TTY mode, we can't detach, so we must wait for container exit - if status, err = waitForExit(cli, createResponse.ID); err != nil { - return err - } - } else { - // In TTY mode, there is a race: if the process dies too slowly, the state could - // be updated after the getExitCode call and result in the wrong exit code being reported - if _, status, err = getExitCode(cli, createResponse.ID); err != nil { - return err - } - } - } - if status != 0 { - return &utils.StatusError{StatusCode: status} - } - return nil -} - -func (cli *DockerCli) CmdCp(args ...string) error { - cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTDIR|-", "Copy files/folders from a PATH on the container to a HOSTDIR on the host\nrunning the command. Use '-' to write the data\nas a tar file to STDOUT.", true) - cmd.Require(flag.Exact, 2) - - utils.ParseFlags(cmd, args, true) - - var copyData engine.Env - info := strings.Split(cmd.Arg(0), ":") - - if len(info) != 2 { - return fmt.Errorf("Error: Path not specified") - } - - copyData.Set("Resource", info[1]) - copyData.Set("HostPath", cmd.Arg(1)) - - stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false) - if stream != nil { - defer stream.Close() - } - if statusCode == 404 { - return fmt.Errorf("No such container: %v", info[0]) - } - if err != nil { - return err - } - - if statusCode == 200 { - dest := copyData.Get("HostPath") - - if dest == "-" { - _, err = io.Copy(cli.out, stream) - } else { - err = archive.Untar(stream, dest, &archive.TarOptions{NoLchown: true}) - } - if err != nil { - return err - } - } - return nil -} - -func (cli *DockerCli) CmdSave(args ...string) error { - cmd := cli.Subcmd("save", "IMAGE [IMAGE...]", "Save an image(s) to a tar archive (streamed to STDOUT by default)", true) - outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT") - cmd.Require(flag.Min, 1) - - utils.ParseFlags(cmd, args, true) - - var ( - output io.Writer = cli.out - err error - ) - if *outfile != "" { - output, err = os.Create(*outfile) - if err != nil { - return err - } - } else if cli.isTerminalOut { - return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.") - } - - if len(cmd.Args()) == 1 { - image := cmd.Arg(0) - if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil { - return err - } - } else { - v := url.Values{} - for _, arg := range cmd.Args() { - v.Add("names", arg) - } - if err := cli.stream("GET", "/images/get?"+v.Encode(), nil, output, nil); err != nil { - return err - } - } - return nil -} - -func (cli *DockerCli) CmdLoad(args ...string) error { - cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN", true) - infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN") - cmd.Require(flag.Exact, 0) - - utils.ParseFlags(cmd, args, true) - - 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 -} - -func (cli *DockerCli) CmdExec(args ...string) error { - cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in a running container", true) - - execConfig, err := runconfig.ParseExec(cmd, args) - // just in case the ParseExec does not exit - if execConfig.Container == "" || err != nil { - return &utils.StatusError{StatusCode: 1} - } - - stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false) - if err != nil { - return err - } - - var response types.ContainerExecCreateResponse - if err := json.NewDecoder(stream).Decode(&response); err != nil { - return err - } - for _, warning := range response.Warnings { - fmt.Fprintf(cli.err, "WARNING: %s\n", warning) - } - - execID := response.ID - - if execID == "" { - fmt.Fprintf(cli.out, "exec ID empty") - return nil - } - - if !execConfig.Detach { - if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil { - return err - } - } else { - if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil { - return err - } - // For now don't print this - wait for when we support exec wait() - // fmt.Fprintf(cli.out, "%s\n", execID) - return nil - } - - // Interactive exec requested. - var ( - out, stderr io.Writer - in io.ReadCloser - hijacked = make(chan io.Closer) - errCh chan error - ) - - // Block the return until the chan gets closed - defer func() { - log.Debugf("End of CmdExec(), Waiting for hijack to finish.") - if _, ok := <-hijacked; ok { - log.Errorf("Hijack did not finish (chan still open)") - } - }() - - if execConfig.AttachStdin { - in = cli.in - } - if execConfig.AttachStdout { - out = cli.out - } - if execConfig.AttachStderr { - if execConfig.Tty { - stderr = cli.out - } else { - stderr = cli.err - } - } - errCh = promise.Go(func() error { - return cli.hijack("POST", "/exec/"+execID+"/start", execConfig.Tty, in, out, stderr, hijacked, execConfig) - }) - - // Acknowledge the hijack before starting - select { - case closer := <-hijacked: - // Make sure that hijack gets closed when returning. (result - // in closing hijack chan and freeing server's goroutines. - if closer != nil { - defer closer.Close() - } - case err := <-errCh: - if err != nil { - log.Debugf("Error hijack: %s", err) - return err - } - } - - if execConfig.Tty && cli.isTerminalIn { - if err := cli.monitorTtySize(execID, true); err != nil { - log.Errorf("Error monitoring TTY size: %s", err) - } - } - - if err := <-errCh; err != nil { - log.Debugf("Error hijack: %s", err) - return err - } - - var status int - if _, status, err = getExecExitCode(cli, execID); err != nil { - return err - } - - if status != 0 { - return &utils.StatusError{StatusCode: status} - } - - return nil -} - -type containerStats struct { - Name string - CpuPercentage float64 - Memory float64 - MemoryLimit float64 - MemoryPercentage float64 - NetworkRx float64 - NetworkTx float64 - mu sync.RWMutex - err error -} - -func (s *containerStats) Collect(cli *DockerCli) { - stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, false) - if err != nil { - s.err = err - return - } - defer stream.Close() - var ( - previousCpu uint64 - previousSystem uint64 - start = true - dec = json.NewDecoder(stream) - u = make(chan error, 1) - ) - go func() { - for { - var v *types.Stats - if err := dec.Decode(&v); err != nil { - u <- err - return - } - var ( - memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 - cpuPercent = 0.0 - ) - if !start { - cpuPercent = calculateCpuPercent(previousCpu, previousSystem, v) - } - start = false - s.mu.Lock() - s.CpuPercentage = cpuPercent - s.Memory = float64(v.MemoryStats.Usage) - s.MemoryLimit = float64(v.MemoryStats.Limit) - s.MemoryPercentage = memPercent - s.NetworkRx = float64(v.Network.RxBytes) - s.NetworkTx = float64(v.Network.TxBytes) - s.mu.Unlock() - previousCpu = v.CpuStats.CpuUsage.TotalUsage - previousSystem = v.CpuStats.SystemUsage - u <- nil - } - }() - for { - select { - case <-time.After(2 * time.Second): - // zero out the values if we have not received an update within - // the specified duration. - s.mu.Lock() - s.CpuPercentage = 0 - s.Memory = 0 - s.MemoryPercentage = 0 - s.mu.Unlock() - case err := <-u: - if err != nil { - s.mu.Lock() - s.err = err - s.mu.Unlock() - return - } - } - } -} - -func (s *containerStats) Display(w io.Writer) error { - s.mu.RLock() - defer s.mu.RUnlock() - if s.err != nil { - return s.err - } - fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n", - s.Name, - s.CpuPercentage, - units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), - s.MemoryPercentage, - units.BytesSize(s.NetworkRx), units.BytesSize(s.NetworkTx)) - return nil -} - -func (cli *DockerCli) CmdStats(args ...string) error { - cmd := cli.Subcmd("stats", "CONTAINER [CONTAINER...]", "Display a live stream of one or more containers' resource usage statistics", true) - cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) - - names := cmd.Args() - sort.Strings(names) - var ( - cStats []*containerStats - w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - ) - printHeader := func() { - fmt.Fprint(cli.out, "\033[2J") - fmt.Fprint(cli.out, "\033[H") - fmt.Fprintln(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O") - } - for _, n := range names { - s := &containerStats{Name: n} - cStats = append(cStats, s) - go s.Collect(cli) - } - // do a quick pause so that any failed connections for containers that do not exist are able to be - // evicted before we display the initial or default values. - time.Sleep(500 * time.Millisecond) - var errs []string - for _, c := range cStats { - c.mu.Lock() - if c.err != nil { - errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err)) - } - c.mu.Unlock() - } - if len(errs) > 0 { - return fmt.Errorf("%s", strings.Join(errs, ", ")) - } - for _ = range time.Tick(500 * time.Millisecond) { - printHeader() - toRemove := []int{} - for i, s := range cStats { - if err := s.Display(w); err != nil { - toRemove = append(toRemove, i) - } - } - for j := len(toRemove) - 1; j >= 0; j-- { - i := toRemove[j] - cStats = append(cStats[:i], cStats[i+1:]...) - } - if len(cStats) == 0 { - return nil - } - w.Flush() - } - return nil -} - -func calculateCpuPercent(previousCpu, previousSystem uint64, v *types.Stats) float64 { - var ( - cpuPercent = 0.0 - // calculate the change for the cpu usage of the container in between readings - cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCpu) - // calculate the change for the entire system between readings - systemDelta = float64(v.CpuStats.SystemUsage - previousSystem) - ) - - if systemDelta > 0.0 && cpuDelta > 0.0 { - cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CpuStats.CpuUsage.PercpuUsage)) * 100.0 - } - return cpuPercent -} diff --git a/api/client/commit.go b/api/client/commit.go new file mode 100644 index 000000000..c532fab0d --- /dev/null +++ b/api/client/commit.go @@ -0,0 +1,76 @@ +package client + +import ( + "encoding/json" + "fmt" + "net/url" + + "github.com/docker/docker/engine" + "github.com/docker/docker/opts" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/registry" + "github.com/docker/docker/runconfig" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdCommit(args ...string) error { + cmd := cli.Subcmd("commit", "CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes", true) + flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit") + flComment := cmd.String([]string{"m", "-message"}, "", "Commit message") + flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith \")") + flChanges := opts.NewListOpts(nil) + cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image") + // 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") + cmd.Require(flag.Max, 2) + cmd.Require(flag.Min, 1) + utils.ParseFlags(cmd, args, true) + + var ( + name = cmd.Arg(0) + repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) + ) + + //Check if the given image name can be resolved + if repository != "" { + if err := registry.ValidateRepositoryName(repository); err != nil { + return err + } + } + + v := url.Values{} + v.Set("container", name) + v.Set("repo", repository) + v.Set("tag", tag) + v.Set("comment", *flComment) + v.Set("author", *flAuthor) + for _, change := range flChanges.GetAll() { + v.Add("changes", change) + } + + if *flPause != true { + v.Set("pause", "0") + } + + var ( + config *runconfig.Config + env engine.Env + ) + if *flConfig != "" { + config = &runconfig.Config{} + if err := json.Unmarshal([]byte(*flConfig), config); err != nil { + return err + } + } + stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false) + if err != nil { + return err + } + if err := env.Decode(stream); err != nil { + return err + } + + fmt.Fprintf(cli.out, "%s\n", env.Get("Id")) + return nil +} diff --git a/api/client/cp.go b/api/client/cp.go new file mode 100644 index 000000000..f2bedb0b1 --- /dev/null +++ b/api/client/cp.go @@ -0,0 +1,54 @@ +package client + +import ( + "fmt" + "io" + "strings" + + "github.com/docker/docker/engine" + "github.com/docker/docker/pkg/archive" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdCp(args ...string) error { + cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTDIR|-", "Copy files/folders from a PATH on the container to a HOSTDIR on the host\nrunning the command. Use '-' to write the data\nas a tar file to STDOUT.", true) + cmd.Require(flag.Exact, 2) + + utils.ParseFlags(cmd, args, true) + + var copyData engine.Env + info := strings.Split(cmd.Arg(0), ":") + + if len(info) != 2 { + return fmt.Errorf("Error: Path not specified") + } + + copyData.Set("Resource", info[1]) + copyData.Set("HostPath", cmd.Arg(1)) + + stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false) + if stream != nil { + defer stream.Close() + } + if statusCode == 404 { + return fmt.Errorf("No such container: %v", info[0]) + } + if err != nil { + return err + } + + if statusCode == 200 { + dest := copyData.Get("HostPath") + + if dest == "-" { + _, err = io.Copy(cli.out, stream) + } else { + err = archive.Untar(stream, dest, &archive.TarOptions{NoLchown: true}) + } + if err != nil { + return err + } + } + return nil +} diff --git a/api/client/create.go b/api/client/create.go new file mode 100644 index 000000000..1adf43b9e --- /dev/null +++ b/api/client/create.go @@ -0,0 +1,153 @@ +package client + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/url" + "os" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/graph" + "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/registry" + "github.com/docker/docker/runconfig" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) pullImage(image string) error { + return cli.pullImageCustomOut(image, cli.out) +} + +func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error { + v := url.Values{} + repos, tag := parsers.ParseRepositoryTag(image) + // pull only the image tagged 'latest' if no tag was specified + if tag == "" { + tag = graph.DEFAULTTAG + } + v.Set("fromImage", repos) + v.Set("tag", tag) + + // Resolve the Repository name from fqn to RepositoryInfo + repoInfo, err := registry.ParseRepositoryInfo(repos) + if err != nil { + return err + } + + // Load the auth config file, to be able to pull the image + cli.LoadConfigFile() + + // Resolve the Auth config relevant for this server + authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) + buf, err := json.Marshal(authConfig) + if err != nil { + return err + } + + registryAuthHeader := []string{ + base64.URLEncoding.EncodeToString(buf), + } + if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, out, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil { + return err + } + return nil +} + +type cidFile struct { + path string + file *os.File + written bool +} + +func newCIDFile(path string) (*cidFile, error) { + if _, err := os.Stat(path); err == nil { + return nil, fmt.Errorf("Container ID file found, make sure the other container isn't running or delete %s", path) + } + + f, err := os.Create(path) + if err != nil { + return nil, fmt.Errorf("Failed to create the container ID file: %s", err) + } + + return &cidFile{path: path, file: f}, nil +} + +func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runconfig.HostConfig, cidfile, name string) (*types.ContainerCreateResponse, error) { + containerValues := url.Values{} + if name != "" { + containerValues.Set("name", name) + } + + mergedConfig := runconfig.MergeConfigs(config, hostConfig) + + var containerIDFile *cidFile + if cidfile != "" { + var err error + if containerIDFile, err = newCIDFile(cidfile); err != nil { + return nil, err + } + defer containerIDFile.Close() + } + + //create the container + stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false) + //if image not found try to pull it + if statusCode == 404 { + repo, tag := parsers.ParseRepositoryTag(config.Image) + if tag == "" { + tag = graph.DEFAULTTAG + } + fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", utils.ImageReference(repo, tag)) + + // we don't want to write to stdout anything apart from container.ID + if err = cli.pullImageCustomOut(config.Image, cli.err); err != nil { + return nil, err + } + // Retry + if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false); err != nil { + return nil, err + } + } else if err != nil { + return nil, err + } + + var response types.ContainerCreateResponse + if err := json.NewDecoder(stream).Decode(&response); err != nil { + return nil, err + } + for _, warning := range response.Warnings { + fmt.Fprintf(cli.err, "WARNING: %s\n", warning) + } + if containerIDFile != nil { + if err = containerIDFile.Write(response.ID); err != nil { + return nil, err + } + } + return &response, nil +} + +func (cli *DockerCli) CmdCreate(args ...string) error { + cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container", true) + + // These are flags not stored in Config/HostConfig + var ( + flName = cmd.String([]string{"-name"}, "", "Assign a name to the container") + ) + + config, hostConfig, cmd, err := runconfig.Parse(cmd, args) + if err != nil { + utils.ReportError(cmd, err.Error(), true) + } + if config.Image == "" { + cmd.Usage() + return nil + } + response, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) + if err != nil { + return err + } + fmt.Fprintf(cli.out, "%s\n", response.ID) + return nil +} diff --git a/api/client/diff.go b/api/client/diff.go new file mode 100644 index 000000000..f82e9c1b2 --- /dev/null +++ b/api/client/diff.go @@ -0,0 +1,41 @@ +package client + +import ( + "fmt" + + "github.com/docker/docker/engine" + "github.com/docker/docker/pkg/archive" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdDiff(args ...string) error { + cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem", true) + cmd.Require(flag.Exact, 1) + + utils.ParseFlags(cmd, args, true) + + body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false)) + + if err != nil { + return err + } + + outs := engine.NewTable("", 0) + if _, err := outs.ReadListFrom(body); err != nil { + return err + } + for _, change := range outs.Data { + var kind string + switch change.GetInt("Kind") { + case archive.ChangeModify: + kind = "C" + case archive.ChangeAdd: + kind = "A" + case archive.ChangeDelete: + kind = "D" + } + fmt.Fprintf(cli.out, "%s %s\n", kind, change.Get("Path")) + } + return nil +} diff --git a/api/client/events.go b/api/client/events.go new file mode 100644 index 000000000..bc39e3cfd --- /dev/null +++ b/api/client/events.go @@ -0,0 +1,68 @@ +package client + +import ( + "net/url" + "strconv" + "time" + + "github.com/docker/docker/opts" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/parsers/filters" + "github.com/docker/docker/pkg/timeutils" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdEvents(args ...string) error { + cmd := cli.Subcmd("events", "", "Get real time events from the server", true) + since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp") + until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp") + flFilter := opts.NewListOpts(nil) + cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") + cmd.Require(flag.Exact, 0) + + utils.ParseFlags(cmd, args, true) + + var ( + v = url.Values{} + loc = time.FixedZone(time.Now().Zone()) + eventFilterArgs = filters.Args{} + ) + + // Consolidate all filter flags, and sanity check them early. + // They'll get process in the daemon/server. + for _, f := range flFilter.GetAll() { + var err error + eventFilterArgs, err = filters.ParseFlag(f, eventFilterArgs) + if err != nil { + return err + } + } + var setTime = func(key, value string) { + format := timeutils.RFC3339NanoFixed + if len(value) < len(format) { + format = format[:len(value)] + } + if t, err := time.ParseInLocation(format, value, loc); err == nil { + v.Set(key, strconv.FormatInt(t.Unix(), 10)) + } else { + v.Set(key, value) + } + } + if *since != "" { + setTime("since", *since) + } + if *until != "" { + setTime("until", *until) + } + if len(eventFilterArgs) > 0 { + filterJson, err := filters.ToParam(eventFilterArgs) + if err != nil { + return err + } + v.Set("filters", filterJson) + } + if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil { + return err + } + return nil +} diff --git a/api/client/exec.go b/api/client/exec.go new file mode 100644 index 000000000..8ededebd1 --- /dev/null +++ b/api/client/exec.go @@ -0,0 +1,126 @@ +package client + +import ( + "encoding/json" + "fmt" + "io" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/promise" + "github.com/docker/docker/runconfig" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdExec(args ...string) error { + cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in a running container", true) + + execConfig, err := runconfig.ParseExec(cmd, args) + // just in case the ParseExec does not exit + if execConfig.Container == "" || err != nil { + return &utils.StatusError{StatusCode: 1} + } + + stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false) + if err != nil { + return err + } + + var response types.ContainerExecCreateResponse + if err := json.NewDecoder(stream).Decode(&response); err != nil { + return err + } + for _, warning := range response.Warnings { + fmt.Fprintf(cli.err, "WARNING: %s\n", warning) + } + + execID := response.ID + + if execID == "" { + fmt.Fprintf(cli.out, "exec ID empty") + return nil + } + + if !execConfig.Detach { + if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil { + return err + } + } else { + if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil { + return err + } + // For now don't print this - wait for when we support exec wait() + // fmt.Fprintf(cli.out, "%s\n", execID) + return nil + } + + // Interactive exec requested. + var ( + out, stderr io.Writer + in io.ReadCloser + hijacked = make(chan io.Closer) + errCh chan error + ) + + // Block the return until the chan gets closed + defer func() { + log.Debugf("End of CmdExec(), Waiting for hijack to finish.") + if _, ok := <-hijacked; ok { + log.Errorf("Hijack did not finish (chan still open)") + } + }() + + if execConfig.AttachStdin { + in = cli.in + } + if execConfig.AttachStdout { + out = cli.out + } + if execConfig.AttachStderr { + if execConfig.Tty { + stderr = cli.out + } else { + stderr = cli.err + } + } + errCh = promise.Go(func() error { + return cli.hijack("POST", "/exec/"+execID+"/start", execConfig.Tty, in, out, stderr, hijacked, execConfig) + }) + + // Acknowledge the hijack before starting + select { + case closer := <-hijacked: + // Make sure that hijack gets closed when returning. (result + // in closing hijack chan and freeing server's goroutines. + if closer != nil { + defer closer.Close() + } + case err := <-errCh: + if err != nil { + log.Debugf("Error hijack: %s", err) + return err + } + } + + if execConfig.Tty && cli.isTerminalIn { + if err := cli.monitorTtySize(execID, true); err != nil { + log.Errorf("Error monitoring TTY size: %s", err) + } + } + + if err := <-errCh; err != nil { + log.Debugf("Error hijack: %s", err) + return err + } + + var status int + if _, status, err = getExecExitCode(cli, execID); err != nil { + return err + } + + if status != 0 { + return &utils.StatusError{StatusCode: status} + } + + return nil +} diff --git a/api/client/export.go b/api/client/export.go new file mode 100644 index 000000000..5b13b0a25 --- /dev/null +++ b/api/client/export.go @@ -0,0 +1,49 @@ +package client + +import ( + "errors" + "io" + "net/url" + "os" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdExport(args ...string) error { + cmd := cli.Subcmd("export", "CONTAINER", "Export a filesystem as a tar archive (streamed to STDOUT by default)", true) + outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT") + cmd.Require(flag.Exact, 1) + + utils.ParseFlags(cmd, args, true) + + var ( + output io.Writer = cli.out + err error + ) + if *outfile != "" { + output, err = os.Create(*outfile) + if err != nil { + return err + } + } else if cli.isTerminalOut { + return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.") + } + + if len(cmd.Args()) == 1 { + image := cmd.Arg(0) + if err := cli.stream("GET", "/containers/"+image+"/export", nil, output, nil); err != nil { + return err + } + } else { + v := url.Values{} + for _, arg := range cmd.Args() { + v.Add("names", arg) + } + if err := cli.stream("GET", "/containers/get?"+v.Encode(), nil, output, nil); err != nil { + return err + } + } + + return nil +} diff --git a/api/client/help.go b/api/client/help.go new file mode 100644 index 000000000..5dea652b0 --- /dev/null +++ b/api/client/help.go @@ -0,0 +1,32 @@ +package client + +import ( + "fmt" + "os" + + flag "github.com/docker/docker/pkg/mflag" +) + +func (cli *DockerCli) CmdHelp(args ...string) error { + if len(args) > 1 { + method, exists := cli.getMethod(args[:2]...) + if exists { + method("--help") + return nil + } + } + if len(args) > 0 { + method, exists := cli.getMethod(args[0]) + if !exists { + fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0]) + os.Exit(1) + } else { + method("--help") + return nil + } + } + + flag.Usage() + + return nil +} diff --git a/api/client/history.go b/api/client/history.go new file mode 100644 index 000000000..98ba68f9a --- /dev/null +++ b/api/client/history.go @@ -0,0 +1,65 @@ +package client + +import ( + "fmt" + "text/tabwriter" + "time" + + "github.com/docker/docker/engine" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/pkg/units" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdHistory(args ...string) error { + cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image", true) + quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") + noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") + cmd.Require(flag.Exact, 1) + + utils.ParseFlags(cmd, args, true) + + body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false)) + if err != nil { + return err + } + + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadListFrom(body); err != nil { + return err + } + + w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) + if !*quiet { + fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE") + } + + for _, out := range outs.Data { + outID := out.Get("Id") + if !*quiet { + if *noTrunc { + fmt.Fprintf(w, "%s\t", outID) + } else { + fmt.Fprintf(w, "%s\t", stringid.TruncateID(outID)) + } + + fmt.Fprintf(w, "%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0)))) + + if *noTrunc { + fmt.Fprintf(w, "%s\t", out.Get("CreatedBy")) + } else { + fmt.Fprintf(w, "%s\t", utils.Trunc(out.Get("CreatedBy"), 45)) + } + fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("Size")))) + } else { + if *noTrunc { + fmt.Fprintln(w, outID) + } else { + fmt.Fprintln(w, stringid.TruncateID(outID)) + } + } + } + w.Flush() + return nil +} diff --git a/api/client/images.go b/api/client/images.go new file mode 100644 index 000000000..d59a0838a --- /dev/null +++ b/api/client/images.go @@ -0,0 +1,271 @@ +package client + +import ( + "fmt" + "net/url" + "strings" + "text/tabwriter" + "time" + + "github.com/docker/docker/engine" + "github.com/docker/docker/opts" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/parsers/filters" + "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/pkg/units" + "github.com/docker/docker/utils" +) + +// 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 { + for index, image := range images.Data { + if index+1 == length { + printNode(cli, noTrunc, image, prefix+"└─") + if subimages, exists := byParent[image.Get("Id")]; exists { + cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) + } + } else { + printNode(cli, noTrunc, image, prefix+"\u251C─") + if subimages, exists := byParent[image.Get("Id")]; exists { + cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) + } + } + } + } else { + for _, image := range images.Data { + printNode(cli, noTrunc, image, prefix+"└─") + if subimages, exists := byParent[image.Get("Id")]; exists { + cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) + } + } + } +} + +// 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 + parentID string + ) + if noTrunc { + imageID = image.Get("Id") + parentID = image.Get("ParentId") + } else { + imageID = stringid.TruncateID(image.Get("Id")) + parentID = stringid.TruncateID(image.Get("ParentId")) + } + if parentID == "" { + fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) + } else { + fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", parentID, imageID) + } + if image.GetList("RepoTags")[0] != ":" { + fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", + imageID, imageID, strings.Join(image.GetList("RepoTags"), "\\n")) + } +} + +// 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 { + imageID = image.Get("Id") + } else { + imageID = stringid.TruncateID(image.Get("Id")) + } + + fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(float64(image.GetInt64("VirtualSize")))) + if image.GetList("RepoTags")[0] != ":" { + fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.GetList("RepoTags"), ", ")) + } else { + fmt.Fprint(cli.out, "\n") + } +} + +func (cli *DockerCli) CmdImages(args ...string) error { + cmd := cli.Subcmd("images", "[REPOSITORY]", "List images", true) + quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") + all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)") + noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") + showDigests := cmd.Bool([]string{"-digests"}, false, "Show digests") + // 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") + + flFilter := opts.NewListOpts(nil) + cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") + cmd.Require(flag.Max, 1) + + utils.ParseFlags(cmd, args, true) + + // Consolidate all filter flags, and sanity check them early. + // They'll get process in the daemon/server. + imageFilterArgs := filters.Args{} + for _, f := range flFilter.GetAll() { + var err error + imageFilterArgs, err = filters.ParseFlag(f, imageFilterArgs) + if err != nil { + return err + } + } + + matchName := cmd.Arg(0) + // FIXME: --viz and --tree are deprecated. Remove them in a future version. + if *flViz || *flTree { + v := url.Values{ + "all": []string{"1"}, + } + if len(imageFilterArgs) > 0 { + filterJson, err := filters.ToParam(imageFilterArgs) + if err != nil { + return err + } + v.Set("filters", filterJson) + } + + body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) + if err != nil { + return err + } + + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadListFrom(body); err != nil { + return err + } + + var ( + printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string) + startImage *engine.Env + + roots = engine.NewTable("Created", outs.Len()) + byParent = make(map[string]*engine.Table) + ) + + for _, image := range outs.Data { + if image.Get("ParentId") == "" { + roots.Add(image) + } else { + if children, exists := byParent[image.Get("ParentId")]; exists { + children.Add(image) + } else { + byParent[image.Get("ParentId")] = engine.NewTable("Created", 1) + byParent[image.Get("ParentId")].Add(image) + } + } + + if matchName != "" { + if matchName == image.Get("Id") || matchName == stringid.TruncateID(image.Get("Id")) { + startImage = image + } + + for _, repotag := range image.GetList("RepoTags") { + if repotag == matchName { + startImage = image + } + } + } + } + + if *flViz { + fmt.Fprintf(cli.out, "digraph docker {\n") + printNode = (*DockerCli).printVizNode + } else { + printNode = (*DockerCli).printTreeNode + } + + if startImage != nil { + root := engine.NewTable("Created", 1) + root.Add(startImage) + cli.WalkTree(*noTrunc, root, byParent, "", printNode) + } else if matchName == "" { + cli.WalkTree(*noTrunc, roots, byParent, "", printNode) + } + if *flViz { + fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") + } + } else { + v := url.Values{} + if len(imageFilterArgs) > 0 { + filterJson, err := filters.ToParam(imageFilterArgs) + if err != nil { + return err + } + v.Set("filters", filterJson) + } + + if cmd.NArg() == 1 { + // FIXME rename this parameter, to not be confused with the filters flag + v.Set("filter", matchName) + } + if *all { + v.Set("all", "1") + } + + body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) + + if err != nil { + return err + } + + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadListFrom(body); err != nil { + return err + } + + w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) + if !*quiet { + if *showDigests { + fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE") + } else { + fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") + } + } + + for _, out := range outs.Data { + outID := out.Get("Id") + if !*noTrunc { + outID = stringid.TruncateID(outID) + } + + repoTags := out.GetList("RepoTags") + repoDigests := out.GetList("RepoDigests") + + if len(repoTags) == 1 && repoTags[0] == ":" && len(repoDigests) == 1 && repoDigests[0] == "@" { + // dangling image - clear out either repoTags or repoDigsts so we only show it once below + repoDigests = []string{} + } + + // combine the tags and digests lists + tagsAndDigests := append(repoTags, repoDigests...) + for _, repoAndRef := range tagsAndDigests { + repo, ref := parsers.ParseRepositoryTag(repoAndRef) + // default tag and digest to none - if there's a value, it'll be set below + tag := "" + digest := "" + if utils.DigestReference(ref) { + digest = ref + } else { + tag = ref + } + + if !*quiet { + if *showDigests { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) + } else { + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) + } + } else { + fmt.Fprintln(w, outID) + } + } + } + + if !*quiet { + w.Flush() + } + } + return nil +} diff --git a/api/client/import.go b/api/client/import.go new file mode 100644 index 000000000..be8e8a6e7 --- /dev/null +++ b/api/client/import.go @@ -0,0 +1,54 @@ +package client + +import ( + "fmt" + "io" + "net/url" + + "github.com/docker/docker/opts" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/registry" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdImport(args ...string) error { + cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true) + flChanges := opts.NewListOpts(nil) + cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image") + cmd.Require(flag.Min, 1) + + utils.ParseFlags(cmd, args, true) + + var ( + v = url.Values{} + src = cmd.Arg(0) + repository = cmd.Arg(1) + ) + + v.Set("fromSrc", src) + v.Set("repo", repository) + for _, change := range flChanges.GetAll() { + v.Add("changes", change) + } + if cmd.NArg() == 3 { + fmt.Fprintf(cli.err, "[DEPRECATED] The format 'URL|- [REPOSITORY [TAG]]' has been deprecated. Please use URL|- [REPOSITORY[:TAG]]\n") + v.Set("tag", cmd.Arg(2)) + } + + if repository != "" { + //Check if the given image name can be resolved + repo, _ := parsers.ParseRepositoryTag(repository) + if err := registry.ValidateRepositoryName(repo); err != nil { + return err + } + } + + var in io.Reader + + if src == "-" { + in = cli.in + } + + return cli.stream("POST", "/images/create?"+v.Encode(), in, cli.out, nil) +} diff --git a/api/client/info.go b/api/client/info.go new file mode 100644 index 000000000..795847f59 --- /dev/null +++ b/api/client/info.go @@ -0,0 +1,144 @@ +package client + +import ( + "fmt" + "os" + "time" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/engine" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/units" + "github.com/docker/docker/utils" +) + +// 'docker info': display system-wide information. +func (cli *DockerCli) CmdInfo(args ...string) error { + cmd := cli.Subcmd("info", "", "Display system-wide information", true) + cmd.Require(flag.Exact, 0) + utils.ParseFlags(cmd, args, false) + + body, _, err := readBody(cli.call("GET", "/info", nil, false)) + if err != nil { + return err + } + + out := engine.NewOutput() + remoteInfo, err := out.AddEnv() + if err != nil { + return err + } + + if _, err := out.Write(body); err != nil { + log.Errorf("Error reading remote info: %s", err) + return err + } + out.Close() + + if remoteInfo.Exists("Containers") { + fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers")) + } + if remoteInfo.Exists("Images") { + fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images")) + } + if remoteInfo.Exists("Driver") { + fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver")) + } + if remoteInfo.Exists("DriverStatus") { + var driverStatus [][2]string + if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil { + return err + } + for _, pair := range driverStatus { + fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) + } + } + if remoteInfo.Exists("ExecutionDriver") { + fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver")) + } + if remoteInfo.Exists("KernelVersion") { + fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion")) + } + if remoteInfo.Exists("OperatingSystem") { + fmt.Fprintf(cli.out, "Operating System: %s\n", remoteInfo.Get("OperatingSystem")) + } + if remoteInfo.Exists("NCPU") { + fmt.Fprintf(cli.out, "CPUs: %d\n", remoteInfo.GetInt("NCPU")) + } + if remoteInfo.Exists("MemTotal") { + fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(remoteInfo.GetInt64("MemTotal")))) + } + if remoteInfo.Exists("Name") { + fmt.Fprintf(cli.out, "Name: %s\n", remoteInfo.Get("Name")) + } + if remoteInfo.Exists("ID") { + fmt.Fprintf(cli.out, "ID: %s\n", remoteInfo.Get("ID")) + } + + if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" { + if remoteInfo.Exists("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") != "") + if remoteInfo.Exists("NFd") { + fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd")) + } + if remoteInfo.Exists("NGoroutines") { + fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines")) + } + if remoteInfo.Exists("SystemTime") { + t, err := remoteInfo.GetTime("SystemTime") + if err != nil { + log.Errorf("Error reading system time: %v", err) + } else { + fmt.Fprintf(cli.out, "System Time: %s\n", t.Format(time.UnixDate)) + } + } + if remoteInfo.Exists("NEventsListener") { + fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener")) + } + if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" { + fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1) + } + if initPath := remoteInfo.Get("InitPath"); initPath != "" { + fmt.Fprintf(cli.out, "Init Path: %s\n", initPath) + } + if root := remoteInfo.Get("DockerRootDir"); root != "" { + fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", root) + } + } + if remoteInfo.Exists("HttpProxy") { + fmt.Fprintf(cli.out, "Http Proxy: %s\n", remoteInfo.Get("HttpProxy")) + } + if remoteInfo.Exists("HttpsProxy") { + fmt.Fprintf(cli.out, "Https Proxy: %s\n", remoteInfo.Get("HttpsProxy")) + } + if remoteInfo.Exists("NoProxy") { + fmt.Fprintf(cli.out, "No Proxy: %s\n", remoteInfo.Get("NoProxy")) + } + if len(remoteInfo.GetList("IndexServerAddress")) != 0 { + cli.LoadConfigFile() + u := cli.configFile.Configs[remoteInfo.Get("IndexServerAddress")].Username + if len(u) > 0 { + fmt.Fprintf(cli.out, "Username: %v\n", u) + fmt.Fprintf(cli.out, "Registry: %v\n", remoteInfo.GetList("IndexServerAddress")) + } + } + if remoteInfo.Exists("MemoryLimit") && !remoteInfo.GetBool("MemoryLimit") { + fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") + } + if remoteInfo.Exists("SwapLimit") && !remoteInfo.GetBool("SwapLimit") { + fmt.Fprintf(cli.err, "WARNING: No swap limit support\n") + } + if remoteInfo.Exists("IPv4Forwarding") && !remoteInfo.GetBool("IPv4Forwarding") { + fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n") + } + if remoteInfo.Exists("Labels") { + fmt.Fprintln(cli.out, "Labels:") + for _, attribute := range remoteInfo.GetList("Labels") { + fmt.Fprintf(cli.out, " %s\n", attribute) + } + } + + return nil +} diff --git a/api/client/inspect.go b/api/client/inspect.go new file mode 100644 index 000000000..f63858981 --- /dev/null +++ b/api/client/inspect.go @@ -0,0 +1,95 @@ +package client + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "strings" + "text/template" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdInspect(args ...string) error { + cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true) + tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template") + cmd.Require(flag.Min, 1) + + utils.ParseFlags(cmd, args, true) + + var tmpl *template.Template + if *tmplStr != "" { + var err error + if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil { + fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) + return &utils.StatusError{StatusCode: 64, + Status: "Template parsing error: " + err.Error()} + } + } + + indented := new(bytes.Buffer) + indented.WriteByte('[') + status := 0 + + for _, name := range cmd.Args() { + obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false)) + if err != nil { + if strings.Contains(err.Error(), "Too many") { + fmt.Fprintf(cli.err, "Error: %v", err) + status = 1 + continue + } + + obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, false)) + if err != nil { + if strings.Contains(err.Error(), "No such") { + fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name) + } else { + fmt.Fprintf(cli.err, "%s", err) + } + status = 1 + continue + } + } + + if tmpl == nil { + if err = json.Indent(indented, obj, "", " "); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + status = 1 + continue + } + } else { + // Has template, will render + var value interface{} + if err := json.Unmarshal(obj, &value); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + status = 1 + continue + } + if err := tmpl.Execute(cli.out, value); err != nil { + return err + } + cli.out.Write([]byte{'\n'}) + } + indented.WriteString(",") + } + + if indented.Len() > 1 { + // Remove trailing ',' + indented.Truncate(indented.Len() - 1) + } + indented.WriteString("]\n") + + if tmpl == nil { + if _, err := io.Copy(cli.out, indented); err != nil { + return err + } + } + + if status != 0 { + return &utils.StatusError{StatusCode: status} + } + return nil +} diff --git a/api/client/kill.go b/api/client/kill.go new file mode 100644 index 000000000..1b8e27d35 --- /dev/null +++ b/api/client/kill.go @@ -0,0 +1,28 @@ +package client + +import ( + "fmt" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +// 'docker kill NAME' kills a running container +func (cli *DockerCli) CmdKill(args ...string) error { + cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal", true) + signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container") + cmd.Require(flag.Min, 1) + + utils.ParseFlags(cmd, args, true) + + var encounteredError error + for _, name := range cmd.Args() { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to kill one or more containers") + } else { + fmt.Fprintf(cli.out, "%s\n", name) + } + } + return encounteredError +} diff --git a/api/client/load.go b/api/client/load.go new file mode 100644 index 000000000..25ef0ab6c --- /dev/null +++ b/api/client/load.go @@ -0,0 +1,32 @@ +package client + +import ( + "io" + "os" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdLoad(args ...string) error { + cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN", true) + infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN") + cmd.Require(flag.Exact, 0) + + utils.ParseFlags(cmd, args, true) + + 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/api/client/login.go b/api/client/login.go new file mode 100644 index 000000000..26a5d6b84 --- /dev/null +++ b/api/client/login.go @@ -0,0 +1,138 @@ +package client + +import ( + "bufio" + "encoding/json" + "fmt" + "io" + "os" + "path" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/homedir" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/term" + "github.com/docker/docker/registry" + "github.com/docker/docker/utils" +) + +// 'docker login': login / register a user to registry service. +func (cli *DockerCli) CmdLogin(args ...string) error { + cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true) + cmd.Require(flag.Max, 1) + + var username, password, email string + + cmd.StringVar(&username, []string{"u", "-username"}, "", "Username") + cmd.StringVar(&password, []string{"p", "-password"}, "", "Password") + cmd.StringVar(&email, []string{"e", "-email"}, "", "Email") + + utils.ParseFlags(cmd, args, true) + + serverAddress := registry.IndexServerAddress() + if len(cmd.Args()) > 0 { + serverAddress = cmd.Arg(0) + } + + promptDefault := func(prompt string, configDefault string) { + if configDefault == "" { + fmt.Fprintf(cli.out, "%s: ", prompt) + } else { + fmt.Fprintf(cli.out, "%s (%s): ", prompt, configDefault) + } + } + + readInput := func(in io.Reader, out io.Writer) string { + reader := bufio.NewReader(in) + line, _, err := reader.ReadLine() + if err != nil { + fmt.Fprintln(out, err.Error()) + os.Exit(1) + } + return string(line) + } + + cli.LoadConfigFile() + authconfig, ok := cli.configFile.Configs[serverAddress] + if !ok { + authconfig = registry.AuthConfig{} + } + + if username == "" { + promptDefault("Username", authconfig.Username) + username = readInput(cli.in, cli.out) + username = strings.Trim(username, " ") + if username == "" { + username = authconfig.Username + } + } + // Assume that a different username means they may not want to use + // the password or email from the config file, so prompt them + if username != authconfig.Username { + if password == "" { + oldState, err := term.SaveState(cli.inFd) + if err != nil { + return err + } + fmt.Fprintf(cli.out, "Password: ") + term.DisableEcho(cli.inFd, oldState) + + password = readInput(cli.in, cli.out) + fmt.Fprint(cli.out, "\n") + + term.RestoreTerminal(cli.inFd, oldState) + if password == "" { + return fmt.Errorf("Error : Password Required") + } + } + + if email == "" { + promptDefault("Email", authconfig.Email) + email = readInput(cli.in, cli.out) + if email == "" { + email = authconfig.Email + } + } + } else { + // However, if they don't override the username use the + // password or email from the cmd line if specified. IOW, allow + // then to change/override them. And if not specified, just + // use what's in the config file + if password == "" { + password = authconfig.Password + } + if email == "" { + email = authconfig.Email + } + } + authconfig.Username = username + authconfig.Password = password + authconfig.Email = email + authconfig.ServerAddress = serverAddress + cli.configFile.Configs[serverAddress] = authconfig + + stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false) + if statusCode == 401 { + delete(cli.configFile.Configs, serverAddress) + registry.SaveConfig(cli.configFile) + return err + } + if err != nil { + return err + } + + var response types.AuthResponse + if err := json.NewDecoder(stream).Decode(response); err != nil { + cli.configFile, _ = registry.LoadConfig(homedir.Get()) + return err + } + + registry.SaveConfig(cli.configFile) + fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s.\n", path.Join(homedir.Get(), registry.CONFIGFILE)) + + if response.Status != "" { + fmt.Fprintf(cli.out, "%s\n", response.Status) + } + return nil +} diff --git a/api/client/logout.go b/api/client/logout.go new file mode 100644 index 000000000..bd135de7f --- /dev/null +++ b/api/client/logout.go @@ -0,0 +1,34 @@ +package client + +import ( + "fmt" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/registry" + "github.com/docker/docker/utils" +) + +// 'docker logout': log out a user from a registry service. +func (cli *DockerCli) CmdLogout(args ...string) error { + cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true) + cmd.Require(flag.Max, 1) + + utils.ParseFlags(cmd, args, false) + serverAddress := registry.IndexServerAddress() + if len(cmd.Args()) > 0 { + serverAddress = cmd.Arg(0) + } + + cli.LoadConfigFile() + if _, ok := cli.configFile.Configs[serverAddress]; !ok { + fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress) + } else { + fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress) + delete(cli.configFile.Configs, serverAddress) + + if err := registry.SaveConfig(cli.configFile); err != nil { + return fmt.Errorf("Failed to save docker config: %v", err) + } + } + return nil +} diff --git a/api/client/logs.go b/api/client/logs.go new file mode 100644 index 000000000..1cb580052 --- /dev/null +++ b/api/client/logs.go @@ -0,0 +1,53 @@ +package client + +import ( + "fmt" + "net/url" + + "github.com/docker/docker/engine" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdLogs(args ...string) error { + var ( + cmd = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", true) + follow = cmd.Bool([]string{"f", "-follow"}, false, "Follow log output") + times = cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps") + tail = cmd.String([]string{"-tail"}, "all", "Number of lines to show from the end of the logs") + ) + cmd.Require(flag.Exact, 1) + + utils.ParseFlags(cmd, args, true) + + name := cmd.Arg(0) + + stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) + if err != nil { + return err + } + + env := engine.Env{} + if err := env.Decode(stream); err != nil { + return err + } + + if env.GetSubEnv("HostConfig").GetSubEnv("LogConfig").Get("Type") != "json-file" { + return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver") + } + + v := url.Values{} + v.Set("stdout", "1") + v.Set("stderr", "1") + + if *times { + v.Set("timestamps", "1") + } + + if *follow { + v.Set("follow", "1") + } + v.Set("tail", *tail) + + return cli.streamHelper("GET", "/containers/"+name+"/logs?"+v.Encode(), env.GetSubEnv("Config").GetBool("Tty"), nil, cli.out, cli.err, nil) +} diff --git a/api/client/pause.go b/api/client/pause.go new file mode 100644 index 000000000..db85368a9 --- /dev/null +++ b/api/client/pause.go @@ -0,0 +1,25 @@ +package client + +import ( + "fmt" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdPause(args ...string) error { + cmd := cli.Subcmd("pause", "CONTAINER [CONTAINER...]", "Pause all processes within a container", true) + cmd.Require(flag.Min, 1) + utils.ParseFlags(cmd, args, false) + + var encounteredError error + for _, name := range cmd.Args() { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, false)); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to pause container named %s", name) + } else { + fmt.Fprintf(cli.out, "%s\n", name) + } + } + return encounteredError +} diff --git a/api/client/port.go b/api/client/port.go new file mode 100644 index 000000000..713fc8b96 --- /dev/null +++ b/api/client/port.go @@ -0,0 +1,60 @@ +package client + +import ( + "fmt" + "strings" + + "github.com/docker/docker/engine" + "github.com/docker/docker/nat" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdPort(args ...string) error { + cmd := cli.Subcmd("port", "CONTAINER [PRIVATE_PORT[/PROTO]]", "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true) + cmd.Require(flag.Min, 1) + utils.ParseFlags(cmd, args, true) + + stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false) + if err != nil { + return err + } + + env := engine.Env{} + if err := env.Decode(stream); err != nil { + return err + } + ports := nat.PortMap{} + if err := env.GetSubEnv("NetworkSettings").GetJson("Ports", &ports); err != nil { + return err + } + + if cmd.NArg() == 2 { + var ( + port = cmd.Arg(1) + proto = "tcp" + parts = strings.SplitN(port, "/", 2) + ) + + if len(parts) == 2 && len(parts[1]) != 0 { + port = parts[0] + proto = parts[1] + } + natPort := port + "/" + proto + if frontends, exists := ports[nat.Port(port+"/"+proto)]; exists && frontends != nil { + for _, frontend := range frontends { + fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) + } + return nil + } + return fmt.Errorf("Error: No public port '%s' published for %s", natPort, cmd.Arg(0)) + } + + for from, frontends := range ports { + for _, frontend := range frontends { + fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIp, frontend.HostPort) + } + } + + return nil +} diff --git a/api/client/ps.go b/api/client/ps.go new file mode 100644 index 000000000..fa9b2b488 --- /dev/null +++ b/api/client/ps.go @@ -0,0 +1,175 @@ +package client + +import ( + "fmt" + "net/url" + "strconv" + "strings" + "text/tabwriter" + "time" + + "github.com/docker/docker/api" + "github.com/docker/docker/engine" + "github.com/docker/docker/opts" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/parsers/filters" + "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/pkg/units" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdPs(args ...string) error { + var ( + err error + + psFilterArgs = filters.Args{} + v = url.Values{} + + cmd = cli.Subcmd("ps", "", "List containers", true) + quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs") + size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes") + all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)") + noTrunc = cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") + nLatest = cmd.Bool([]string{"l", "-latest"}, false, "Show the latest created container, include non-running") + since = cmd.String([]string{"#sinceId", "#-since-id", "-since"}, "", "Show created since Id or Name, include non-running") + before = cmd.String([]string{"#beforeId", "#-before-id", "-before"}, "", "Show only container created before Id or Name") + last = cmd.Int([]string{"n"}, -1, "Show n last created containers, include non-running") + flFilter = opts.NewListOpts(nil) + ) + cmd.Require(flag.Exact, 0) + + cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") + + utils.ParseFlags(cmd, args, true) + if *last == -1 && *nLatest { + *last = 1 + } + + if *all { + v.Set("all", "1") + } + + if *last != -1 { + v.Set("limit", strconv.Itoa(*last)) + } + + if *since != "" { + v.Set("since", *since) + } + + if *before != "" { + v.Set("before", *before) + } + + if *size { + v.Set("size", "1") + } + + // Consolidate all filter flags, and sanity check them. + // They'll get processed in the daemon/server. + for _, f := range flFilter.GetAll() { + if psFilterArgs, err = filters.ParseFlag(f, psFilterArgs); err != nil { + return err + } + } + + if len(psFilterArgs) > 0 { + filterJson, err := filters.ToParam(psFilterArgs) + if err != nil { + return err + } + + v.Set("filters", filterJson) + } + + body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false)) + if err != nil { + return err + } + + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadListFrom(body); err != nil { + return err + } + + w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) + if !*quiet { + fmt.Fprint(w, "CONTAINER ID\tIMAGE\tCOMMAND\tCREATED\tSTATUS\tPORTS\tNAMES") + + if *size { + fmt.Fprintln(w, "\tSIZE") + } else { + fmt.Fprint(w, "\n") + } + } + + stripNamePrefix := func(ss []string) []string { + for i, s := range ss { + ss[i] = s[1:] + } + + return ss + } + + for _, out := range outs.Data { + outID := out.Get("Id") + + if !*noTrunc { + outID = stringid.TruncateID(outID) + } + + if *quiet { + fmt.Fprintln(w, outID) + + continue + } + + var ( + outNames = stripNamePrefix(out.GetList("Names")) + outCommand = strconv.Quote(out.Get("Command")) + ports = engine.NewTable("", 0) + ) + + if !*noTrunc { + outCommand = utils.Trunc(outCommand, 20) + + // only display the default name for the container with notrunc is passed + for _, name := range outNames { + if len(strings.Split(name, "/")) == 1 { + outNames = []string{name} + + break + } + } + } + + ports.ReadListFrom([]byte(out.Get("Ports"))) + + image := out.Get("Image") + if image == "" { + image = "" + } + + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, image, outCommand, + units.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", units.HumanSize(float64(out.GetInt64("SizeRw"))), units.HumanSize(float64(out.GetInt64("SizeRootFs")))) + } else { + fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("SizeRw")))) + } + + continue + } + + fmt.Fprint(w, "\n") + } + + if !*quiet { + w.Flush() + } + + return nil +} diff --git a/api/client/pull.go b/api/client/pull.go new file mode 100644 index 000000000..a27c7cb96 --- /dev/null +++ b/api/client/pull.go @@ -0,0 +1,77 @@ +package client + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/url" + "strings" + + "github.com/docker/docker/graph" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/registry" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdPull(args ...string) error { + cmd := cli.Subcmd("pull", "NAME[:TAG|@DIGEST]", "Pull an image or a repository from the registry", true) + allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository") + cmd.Require(flag.Exact, 1) + + utils.ParseFlags(cmd, args, true) + + var ( + v = url.Values{} + remote = cmd.Arg(0) + newRemote = remote + ) + taglessRemote, tag := parsers.ParseRepositoryTag(remote) + if tag == "" && !*allTags { + newRemote = utils.ImageReference(taglessRemote, graph.DEFAULTTAG) + } + if tag != "" && *allTags { + return fmt.Errorf("tag can't be used with --all-tags/-a") + } + + v.Set("fromImage", newRemote) + + // Resolve the Repository name from fqn to RepositoryInfo + repoInfo, err := registry.ParseRepositoryInfo(taglessRemote) + if err != nil { + return err + } + + cli.LoadConfigFile() + + // Resolve the Auth config relevant for this server + authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) + + pull := func(authConfig registry.AuthConfig) error { + buf, err := json.Marshal(authConfig) + if err != nil { + return err + } + registryAuthHeader := []string{ + base64.URLEncoding.EncodeToString(buf), + } + + return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{ + "X-Registry-Auth": registryAuthHeader, + }) + } + + if err := pull(authConfig); err != nil { + if strings.Contains(err.Error(), "Status 401") { + fmt.Fprintln(cli.out, "\nPlease login prior to pull:") + if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil { + return err + } + authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) + return pull(authConfig) + } + return err + } + + return nil +} diff --git a/api/client/push.go b/api/client/push.go new file mode 100644 index 000000000..92a87ed27 --- /dev/null +++ b/api/client/push.go @@ -0,0 +1,76 @@ +package client + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/url" + "strings" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/registry" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdPush(args ...string) error { + cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry", true) + cmd.Require(flag.Exact, 1) + + utils.ParseFlags(cmd, args, true) + + name := cmd.Arg(0) + + cli.LoadConfigFile() + + remote, tag := parsers.ParseRepositoryTag(name) + + // Resolve the Repository name from fqn to RepositoryInfo + repoInfo, err := registry.ParseRepositoryInfo(remote) + if err != nil { + return err + } + // Resolve the Auth config relevant for this server + authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) + // If we're not using a custom registry, we know the restrictions + // applied to repository names and can warn the user in advance. + // Custom repositories can have different rules, and we must also + // allow pushing by image ID. + if repoInfo.Official { + username := authConfig.Username + if username == "" { + username = "" + } + return fmt.Errorf("You cannot push a \"root\" repository. Please rename your repository to / (ex: %s/%s)", username, repoInfo.LocalName) + } + + v := url.Values{} + v.Set("tag", tag) + + push := func(authConfig registry.AuthConfig) error { + buf, err := json.Marshal(authConfig) + if err != nil { + return err + } + registryAuthHeader := []string{ + base64.URLEncoding.EncodeToString(buf), + } + + return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{ + "X-Registry-Auth": registryAuthHeader, + }) + } + + if err := push(authConfig); err != nil { + if strings.Contains(err.Error(), "Status 401") { + fmt.Fprintln(cli.out, "\nPlease login prior to push:") + if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil { + return err + } + authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) + return push(authConfig) + } + return err + } + return nil +} diff --git a/api/client/rename.go b/api/client/rename.go new file mode 100644 index 000000000..82d2b74c3 --- /dev/null +++ b/api/client/rename.go @@ -0,0 +1,23 @@ +package client + +import "fmt" + +func (cli *DockerCli) CmdRename(args ...string) error { + cmd := cli.Subcmd("rename", "OLD_NAME NEW_NAME", "Rename a container", true) + if err := cmd.Parse(args); err != nil { + return nil + } + + if cmd.NArg() != 2 { + cmd.Usage() + return nil + } + old_name := cmd.Arg(0) + new_name := cmd.Arg(1) + + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", old_name, new_name), nil, false)); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + return fmt.Errorf("Error: failed to rename container named %s", old_name) + } + return nil +} diff --git a/api/client/restart.go b/api/client/restart.go new file mode 100644 index 000000000..90c5b3fab --- /dev/null +++ b/api/client/restart.go @@ -0,0 +1,33 @@ +package client + +import ( + "fmt" + "net/url" + "strconv" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdRestart(args ...string) error { + cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container", true) + nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container") + cmd.Require(flag.Min, 1) + + utils.ParseFlags(cmd, args, true) + + v := url.Values{} + v.Set("t", strconv.Itoa(*nSeconds)) + + var encounteredError error + for _, name := range cmd.Args() { + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false)) + if err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to restart one or more containers") + } else { + fmt.Fprintf(cli.out, "%s\n", name) + } + } + return encounteredError +} diff --git a/api/client/rm.go b/api/client/rm.go new file mode 100644 index 000000000..0764f76b5 --- /dev/null +++ b/api/client/rm.go @@ -0,0 +1,43 @@ +package client + +import ( + "fmt" + "net/url" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdRm(args ...string) error { + cmd := cli.Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove one or more containers", true) + v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container") + link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link") + force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)") + cmd.Require(flag.Min, 1) + + utils.ParseFlags(cmd, args, true) + + val := url.Values{} + if *v { + val.Set("v", "1") + } + if *link { + val.Set("link", "1") + } + + if *force { + val.Set("force", "1") + } + + var encounteredError error + for _, name := range cmd.Args() { + _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, false)) + if err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to remove one or more containers") + } else { + fmt.Fprintf(cli.out, "%s\n", name) + } + } + return encounteredError +} diff --git a/api/client/rmi.go b/api/client/rmi.go new file mode 100644 index 000000000..7c5b4d0af --- /dev/null +++ b/api/client/rmi.go @@ -0,0 +1,54 @@ +package client + +import ( + "fmt" + "net/url" + + "github.com/docker/docker/engine" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +// '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", true) + force = cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image") + noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents") + ) + cmd.Require(flag.Min, 1) + + utils.ParseFlags(cmd, args, true) + + v := url.Values{} + if *force { + v.Set("force", "1") + } + if *noprune { + v.Set("noprune", "1") + } + + var encounteredError error + for _, name := range cmd.Args() { + body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false)) + if err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to remove one or more images") + } else { + outs := engine.NewTable("Created", 0) + if _, err := outs.ReadListFrom(body); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to remove one or more images") + continue + } + for _, out := range outs.Data { + if out.Get("Deleted") != "" { + fmt.Fprintf(cli.out, "Deleted: %s\n", out.Get("Deleted")) + } else { + fmt.Fprintf(cli.out, "Untagged: %s\n", out.Get("Untagged")) + } + } + } + } + return encounteredError +} diff --git a/api/client/run.go b/api/client/run.go new file mode 100644 index 000000000..e00a3d78d --- /dev/null +++ b/api/client/run.go @@ -0,0 +1,245 @@ +package client + +import ( + "fmt" + "io" + "net/url" + "os" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/opts" + "github.com/docker/docker/pkg/promise" + "github.com/docker/docker/pkg/resolvconf" + "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/runconfig" + "github.com/docker/docker/utils" +) + +func (cid *cidFile) Close() error { + cid.file.Close() + + if !cid.written { + if err := os.Remove(cid.path); err != nil { + return fmt.Errorf("failed to remove the CID file '%s': %s \n", cid.path, err) + } + } + + return nil +} + +func (cid *cidFile) Write(id string) error { + if _, err := cid.file.Write([]byte(id)); err != nil { + return fmt.Errorf("Failed to write the container ID to the file: %s", err) + } + cid.written = true + return nil +} + +func (cli *DockerCli) CmdRun(args ...string) error { + // FIXME: just use runconfig.Parse already + cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container", true) + + // These are flags not stored in Config/HostConfig + var ( + flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits") + flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID") + flSigProxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process") + flName = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container") + flAttach *opts.ListOpts + + ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") + ErrConflictRestartPolicyAndAutoRemove = fmt.Errorf("Conflicting options: --restart and --rm") + ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: --rm and -d") + ) + + config, hostConfig, cmd, err := runconfig.Parse(cmd, args) + // just in case the Parse does not exit + if err != nil { + utils.ReportError(cmd, err.Error(), true) + } + + if len(hostConfig.Dns) > 0 { + // check the DNS settings passed via --dns against + // localhost regexp to warn if they are trying to + // set a DNS to a localhost address + for _, dnsIP := range hostConfig.Dns { + if resolvconf.IsLocalhost(dnsIP) { + fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP) + break + } + } + } + if config.Image == "" { + cmd.Usage() + return nil + } + + if !*flDetach { + if err := cli.CheckTtyInput(config.AttachStdin, config.Tty); err != nil { + return err + } + } else { + if fl := cmd.Lookup("-attach"); fl != nil { + flAttach = fl.Value.(*opts.ListOpts) + if flAttach.Len() != 0 { + return ErrConflictAttachDetach + } + } + if *flAutoRemove { + return ErrConflictDetachAutoRemove + } + + config.AttachStdin = false + config.AttachStdout = false + config.AttachStderr = false + config.StdinOnce = false + } + + // Disable flSigProxy when in TTY mode + sigProxy := *flSigProxy + if config.Tty { + sigProxy = false + } + + createResponse, err := cli.createContainer(config, hostConfig, hostConfig.ContainerIDFile, *flName) + if err != nil { + return err + } + if sigProxy { + sigc := cli.forwardAllSignals(createResponse.ID) + defer signal.StopCatch(sigc) + } + var ( + waitDisplayId chan struct{} + errCh chan error + ) + if !config.AttachStdout && !config.AttachStderr { + // Make this asynchronous to allow the client to write to stdin before having to read the ID + waitDisplayId = make(chan struct{}) + go func() { + defer close(waitDisplayId) + fmt.Fprintf(cli.out, "%s\n", createResponse.ID) + }() + } + if *flAutoRemove && (hostConfig.RestartPolicy.Name == "always" || hostConfig.RestartPolicy.Name == "on-failure") { + return ErrConflictRestartPolicyAndAutoRemove + } + // We need to instantiate the chan because the select needs it. It can + // be closed but can't be uninitialized. + hijacked := make(chan io.Closer) + // Block the return until the chan gets closed + defer func() { + log.Debugf("End of CmdRun(), Waiting for hijack to finish.") + if _, ok := <-hijacked; ok { + log.Errorf("Hijack did not finish (chan still open)") + } + }() + if config.AttachStdin || config.AttachStdout || config.AttachStderr { + var ( + out, stderr io.Writer + in io.ReadCloser + v = url.Values{} + ) + v.Set("stream", "1") + if config.AttachStdin { + v.Set("stdin", "1") + in = cli.in + } + if config.AttachStdout { + v.Set("stdout", "1") + out = cli.out + } + if config.AttachStderr { + v.Set("stderr", "1") + if config.Tty { + stderr = cli.out + } else { + stderr = cli.err + } + } + errCh = promise.Go(func() error { + return cli.hijack("POST", "/containers/"+createResponse.ID+"/attach?"+v.Encode(), config.Tty, in, out, stderr, hijacked, nil) + }) + } else { + close(hijacked) + } + // Acknowledge the hijack before starting + select { + case closer := <-hijacked: + // Make sure that the hijack gets closed when returning (results + // in closing the hijack chan and freeing server's goroutines) + if closer != nil { + defer closer.Close() + } + case err := <-errCh: + if err != nil { + log.Debugf("Error hijack: %s", err) + return err + } + } + + defer func() { + if *flAutoRemove { + if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, false)); err != nil { + log.Errorf("Error deleting container: %s", err) + } + } + }() + + //start the container + if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, false)); err != nil { + return err + } + + if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut { + if err := cli.monitorTtySize(createResponse.ID, false); err != nil { + log.Errorf("Error monitoring TTY size: %s", err) + } + } + + if errCh != nil { + if err := <-errCh; err != nil { + log.Debugf("Error hijack: %s", err) + return err + } + } + + // Detached mode: wait for the id to be displayed and return. + if !config.AttachStdout && !config.AttachStderr { + // Detached mode + <-waitDisplayId + return nil + } + + var status int + + // Attached mode + if *flAutoRemove { + // Autoremove: wait for the container to finish, retrieve + // the exit code and remove the container + if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, false)); err != nil { + return err + } + if _, status, err = getExitCode(cli, createResponse.ID); err != nil { + return err + } + } else { + // No Autoremove: Simply retrieve the exit code + if !config.Tty { + // In non-TTY mode, we can't detach, so we must wait for container exit + if status, err = waitForExit(cli, createResponse.ID); err != nil { + return err + } + } else { + // In TTY mode, there is a race: if the process dies too slowly, the state could + // be updated after the getExitCode call and result in the wrong exit code being reported + if _, status, err = getExitCode(cli, createResponse.ID); err != nil { + return err + } + } + } + if status != 0 { + return &utils.StatusError{StatusCode: status} + } + return nil +} diff --git a/api/client/save.go b/api/client/save.go new file mode 100644 index 000000000..8d42218d8 --- /dev/null +++ b/api/client/save.go @@ -0,0 +1,48 @@ +package client + +import ( + "errors" + "io" + "net/url" + "os" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdSave(args ...string) error { + cmd := cli.Subcmd("save", "IMAGE [IMAGE...]", "Save an image(s) to a tar archive (streamed to STDOUT by default)", true) + outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT") + cmd.Require(flag.Min, 1) + + utils.ParseFlags(cmd, args, true) + + var ( + output io.Writer = cli.out + err error + ) + if *outfile != "" { + output, err = os.Create(*outfile) + if err != nil { + return err + } + } else if cli.isTerminalOut { + return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.") + } + + if len(cmd.Args()) == 1 { + image := cmd.Arg(0) + if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil { + return err + } + } else { + v := url.Values{} + for _, arg := range cmd.Args() { + v.Add("names", arg) + } + if err := cli.stream("GET", "/images/get?"+v.Encode(), nil, output, nil); err != nil { + return err + } + } + return nil +} diff --git a/api/client/search.go b/api/client/search.go new file mode 100644 index 000000000..1b43ac991 --- /dev/null +++ b/api/client/search.go @@ -0,0 +1,60 @@ +package client + +import ( + "fmt" + "net/url" + "strings" + "text/tabwriter" + + "github.com/docker/docker/engine" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdSearch(args ...string) error { + cmd := cli.Subcmd("search", "TERM", "Search the Docker Hub for images", true) + noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") + trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds") + automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds") + stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least x stars") + cmd.Require(flag.Exact, 1) + + utils.ParseFlags(cmd, args, true) + + v := url.Values{} + v.Set("term", cmd.Arg(0)) + + body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true)) + + if err != nil { + return err + } + outs := engine.NewTable("star_count", 0) + if _, err := outs.ReadListFrom(body); err != nil { + return err + } + w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) + fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") + for _, out := range outs.Data { + if ((*automated || *trusted) && (!out.GetBool("is_trusted") && !out.GetBool("is_automated"))) || (*stars > out.GetInt("star_count")) { + continue + } + desc := strings.Replace(out.Get("description"), "\n", " ", -1) + desc = strings.Replace(desc, "\r", " ", -1) + if !*noTrunc && len(desc) > 45 { + desc = utils.Trunc(desc, 42) + "..." + } + fmt.Fprintf(w, "%s\t%s\t%d\t", out.Get("name"), desc, out.GetInt("star_count")) + if out.GetBool("is_official") { + fmt.Fprint(w, "[OK]") + + } + fmt.Fprint(w, "\t") + if out.GetBool("is_automated") || out.GetBool("is_trusted") { + fmt.Fprint(w, "[OK]") + } + fmt.Fprint(w, "\n") + } + w.Flush() + return nil +} diff --git a/api/client/start.go b/api/client/start.go new file mode 100644 index 000000000..8d4ea6bb9 --- /dev/null +++ b/api/client/start.go @@ -0,0 +1,160 @@ +package client + +import ( + "fmt" + "io" + "net/url" + "os" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/engine" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/promise" + "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { + sigc := make(chan os.Signal, 128) + signal.CatchAll(sigc) + go func() { + for s := range sigc { + if s == signal.SIGCHLD { + continue + } + var sig string + for sigStr, sigN := range signal.SignalMap { + if sigN == s { + sig = sigStr + break + } + } + if sig == "" { + log.Errorf("Unsupported signal: %v. Discarding.", s) + } + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil { + log.Debugf("Error sending signal: %s", err) + } + } + }() + return sigc +} + +func (cli *DockerCli) CmdStart(args ...string) error { + var ( + cErr chan error + tty bool + + cmd = cli.Subcmd("start", "CONTAINER [CONTAINER...]", "Start one or more stopped containers", true) + attach = cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals") + openStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN") + ) + + cmd.Require(flag.Min, 1) + utils.ParseFlags(cmd, args, true) + + if *attach || *openStdin { + if cmd.NArg() > 1 { + return fmt.Errorf("You cannot start and attach multiple containers at once.") + } + + stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false) + if err != nil { + return err + } + + env := engine.Env{} + if err := env.Decode(stream); err != nil { + return err + } + config := env.GetSubEnv("Config") + tty = config.GetBool("Tty") + + if !tty { + sigc := cli.forwardAllSignals(cmd.Arg(0)) + defer signal.StopCatch(sigc) + } + + var in io.ReadCloser + + v := url.Values{} + v.Set("stream", "1") + + if *openStdin && config.GetBool("OpenStdin") { + v.Set("stdin", "1") + in = cli.in + } + + v.Set("stdout", "1") + v.Set("stderr", "1") + + hijacked := make(chan io.Closer) + // Block the return until the chan gets closed + defer func() { + log.Debugf("CmdStart() returned, defer waiting for hijack to finish.") + if _, ok := <-hijacked; ok { + log.Errorf("Hijack did not finish (chan still open)") + } + cli.in.Close() + }() + cErr = promise.Go(func() error { + return cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, hijacked, nil) + }) + + // Acknowledge the hijack before starting + select { + case closer := <-hijacked: + // Make sure that the hijack gets closed when returning (results + // in closing the hijack chan and freeing server's goroutines) + if closer != nil { + defer closer.Close() + } + case err := <-cErr: + if err != nil { + return err + } + } + } + + var encounteredError error + for _, name := range cmd.Args() { + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false)) + if err != nil { + if !*attach && !*openStdin { + // attach and openStdin is false means it could be starting multiple containers + // when a container start failed, show the error message and start next + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to start one or more containers") + } else { + encounteredError = err + } + } else { + if !*attach && !*openStdin { + fmt.Fprintf(cli.out, "%s\n", name) + } + } + } + + if encounteredError != nil { + return encounteredError + } + + if *openStdin || *attach { + if tty && cli.isTerminalOut { + if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { + log.Errorf("Error monitoring TTY size: %s", err) + } + } + if attchErr := <-cErr; attchErr != nil { + return attchErr + } + _, status, err := getExitCode(cli, cmd.Arg(0)) + if err != nil { + return err + } + if status != 0 { + return &utils.StatusError{StatusCode: status} + } + } + return nil +} diff --git a/api/client/stats.go b/api/client/stats.go new file mode 100644 index 000000000..8999abdb2 --- /dev/null +++ b/api/client/stats.go @@ -0,0 +1,177 @@ +package client + +import ( + "encoding/json" + "fmt" + "io" + "sort" + "strings" + "sync" + "text/tabwriter" + "time" + + "github.com/docker/docker/api/types" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/units" + "github.com/docker/docker/utils" +) + +type containerStats struct { + Name string + CpuPercentage float64 + Memory float64 + MemoryLimit float64 + MemoryPercentage float64 + NetworkRx float64 + NetworkTx float64 + mu sync.RWMutex + err error +} + +func (s *containerStats) Collect(cli *DockerCli) { + stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, false) + if err != nil { + s.err = err + return + } + defer stream.Close() + var ( + previousCpu uint64 + previousSystem uint64 + start = true + dec = json.NewDecoder(stream) + u = make(chan error, 1) + ) + go func() { + for { + var v *types.Stats + if err := dec.Decode(&v); err != nil { + u <- err + return + } + var ( + memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0 + cpuPercent = 0.0 + ) + if !start { + cpuPercent = calculateCpuPercent(previousCpu, previousSystem, v) + } + start = false + s.mu.Lock() + s.CpuPercentage = cpuPercent + s.Memory = float64(v.MemoryStats.Usage) + s.MemoryLimit = float64(v.MemoryStats.Limit) + s.MemoryPercentage = memPercent + s.NetworkRx = float64(v.Network.RxBytes) + s.NetworkTx = float64(v.Network.TxBytes) + s.mu.Unlock() + previousCpu = v.CpuStats.CpuUsage.TotalUsage + previousSystem = v.CpuStats.SystemUsage + u <- nil + } + }() + for { + select { + case <-time.After(2 * time.Second): + // zero out the values if we have not received an update within + // the specified duration. + s.mu.Lock() + s.CpuPercentage = 0 + s.Memory = 0 + s.MemoryPercentage = 0 + s.mu.Unlock() + case err := <-u: + if err != nil { + s.mu.Lock() + s.err = err + s.mu.Unlock() + return + } + } + } +} + +func (s *containerStats) Display(w io.Writer) error { + s.mu.RLock() + defer s.mu.RUnlock() + if s.err != nil { + return s.err + } + fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n", + s.Name, + s.CpuPercentage, + units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), + s.MemoryPercentage, + units.BytesSize(s.NetworkRx), units.BytesSize(s.NetworkTx)) + return nil +} + +func (cli *DockerCli) CmdStats(args ...string) error { + cmd := cli.Subcmd("stats", "CONTAINER [CONTAINER...]", "Display a live stream of one or more containers' resource usage statistics", true) + cmd.Require(flag.Min, 1) + utils.ParseFlags(cmd, args, true) + + names := cmd.Args() + sort.Strings(names) + var ( + cStats []*containerStats + w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) + ) + printHeader := func() { + fmt.Fprint(cli.out, "\033[2J") + fmt.Fprint(cli.out, "\033[H") + fmt.Fprintln(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O") + } + for _, n := range names { + s := &containerStats{Name: n} + cStats = append(cStats, s) + go s.Collect(cli) + } + // do a quick pause so that any failed connections for containers that do not exist are able to be + // evicted before we display the initial or default values. + time.Sleep(500 * time.Millisecond) + var errs []string + for _, c := range cStats { + c.mu.Lock() + if c.err != nil { + errs = append(errs, fmt.Sprintf("%s: %v", c.Name, c.err)) + } + c.mu.Unlock() + } + if len(errs) > 0 { + return fmt.Errorf("%s", strings.Join(errs, ", ")) + } + for _ = range time.Tick(500 * time.Millisecond) { + printHeader() + toRemove := []int{} + for i, s := range cStats { + if err := s.Display(w); err != nil { + toRemove = append(toRemove, i) + } + } + for j := len(toRemove) - 1; j >= 0; j-- { + i := toRemove[j] + cStats = append(cStats[:i], cStats[i+1:]...) + } + if len(cStats) == 0 { + return nil + } + w.Flush() + } + return nil +} + +func calculateCpuPercent(previousCpu, previousSystem uint64, v *types.Stats) float64 { + var ( + cpuPercent = 0.0 + // calculate the change for the cpu usage of the container in between readings + cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCpu) + // calculate the change for the entire system between readings + systemDelta = float64(v.CpuStats.SystemUsage - previousSystem) + ) + + if systemDelta > 0.0 && cpuDelta > 0.0 { + cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CpuStats.CpuUsage.PercpuUsage)) * 100.0 + } + return cpuPercent +} diff --git a/api/client/stop.go b/api/client/stop.go new file mode 100644 index 000000000..aa46a2fea --- /dev/null +++ b/api/client/stop.go @@ -0,0 +1,33 @@ +package client + +import ( + "fmt" + "net/url" + "strconv" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdStop(args ...string) error { + cmd := cli.Subcmd("stop", "CONTAINER [CONTAINER...]", "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true) + nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it") + cmd.Require(flag.Min, 1) + + utils.ParseFlags(cmd, args, true) + + v := url.Values{} + v.Set("t", strconv.Itoa(*nSeconds)) + + var encounteredError error + for _, name := range cmd.Args() { + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, false)) + if err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to stop one or more containers") + } else { + fmt.Fprintf(cli.out, "%s\n", name) + } + } + return encounteredError +} diff --git a/api/client/tag.go b/api/client/tag.go new file mode 100644 index 000000000..701e381e9 --- /dev/null +++ b/api/client/tag.go @@ -0,0 +1,39 @@ +package client + +import ( + "net/url" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/registry" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdTag(args ...string) error { + cmd := cli.Subcmd("tag", "IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository", true) + force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force") + cmd.Require(flag.Exact, 2) + + utils.ParseFlags(cmd, args, true) + + var ( + repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) + v = url.Values{} + ) + + //Check if the given image name can be resolved + if err := registry.ValidateRepositoryName(repository); err != nil { + return err + } + v.Set("repo", repository) + v.Set("tag", tag) + + if *force { + v.Set("force", "1") + } + + if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false)); err != nil { + return err + } + return nil +} diff --git a/api/client/top.go b/api/client/top.go new file mode 100644 index 000000000..8f8837a9a --- /dev/null +++ b/api/client/top.go @@ -0,0 +1,44 @@ +package client + +import ( + "fmt" + "net/url" + "strings" + "text/tabwriter" + + "github.com/docker/docker/engine" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdTop(args ...string) error { + cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Display the running processes of a container", true) + cmd.Require(flag.Min, 1) + + utils.ParseFlags(cmd, args, true) + + val := url.Values{} + if cmd.NArg() > 1 { + val.Set("ps_args", strings.Join(cmd.Args()[1:], " ")) + } + + stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false) + if err != nil { + return err + } + var procs engine.Env + if err := procs.Decode(stream); err != nil { + return err + } + w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) + fmt.Fprintln(w, strings.Join(procs.GetList("Titles"), "\t")) + processes := [][]string{} + if err := procs.GetJson("Processes", &processes); err != nil { + return err + } + for _, proc := range processes { + fmt.Fprintln(w, strings.Join(proc, "\t")) + } + w.Flush() + return nil +} diff --git a/api/client/unpause.go b/api/client/unpause.go new file mode 100644 index 000000000..c40948404 --- /dev/null +++ b/api/client/unpause.go @@ -0,0 +1,25 @@ +package client + +import ( + "fmt" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +func (cli *DockerCli) CmdUnpause(args ...string) error { + cmd := cli.Subcmd("unpause", "CONTAINER [CONTAINER...]", "Unpause all processes within a container", true) + cmd.Require(flag.Min, 1) + utils.ParseFlags(cmd, args, false) + + var encounteredError error + for _, name := range cmd.Args() { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, false)); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name) + } else { + fmt.Fprintf(cli.out, "%s\n", name) + } + } + return encounteredError +} diff --git a/api/client/version.go b/api/client/version.go new file mode 100644 index 000000000..fa996e538 --- /dev/null +++ b/api/client/version.go @@ -0,0 +1,56 @@ +package client + +import ( + "fmt" + "runtime" + + log "github.com/Sirupsen/logrus" + "github.com/docker/docker/api" + "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/engine" + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +// 'docker version': show version information +func (cli *DockerCli) CmdVersion(args ...string) error { + cmd := cli.Subcmd("version", "", "Show the Docker version information.", true) + cmd.Require(flag.Exact, 0) + + utils.ParseFlags(cmd, args, false) + + 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", runtime.Version()) + if dockerversion.GITCOMMIT != "" { + fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT) + } + fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH) + + body, _, err := readBody(cli.call("GET", "/version", nil, false)) + if err != nil { + return err + } + + out := engine.NewOutput() + remoteVersion, err := out.AddEnv() + if err != nil { + log.Errorf("Error reading remote version: %s", err) + return err + } + if _, err := out.Write(body); err != nil { + log.Errorf("Error reading remote version: %s", err) + return err + } + 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, "Go version (server): %s\n", remoteVersion.Get("GoVersion")) + fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit")) + fmt.Fprintf(cli.out, "OS/Arch (server): %s/%s\n", remoteVersion.Get("Os"), remoteVersion.Get("Arch")) + return nil +} diff --git a/api/client/wait.go b/api/client/wait.go new file mode 100644 index 000000000..ca9f713aa --- /dev/null +++ b/api/client/wait.go @@ -0,0 +1,28 @@ +package client + +import ( + "fmt" + + flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/utils" +) + +// 'docker wait': block until a container stops +func (cli *DockerCli) CmdWait(args ...string) error { + cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.", true) + cmd.Require(flag.Min, 1) + + utils.ParseFlags(cmd, args, true) + + var encounteredError error + for _, name := range cmd.Args() { + status, err := waitForExit(cli, name) + if err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + encounteredError = fmt.Errorf("Error: failed to wait one or more containers") + } else { + fmt.Fprintf(cli.out, "%d\n", status) + } + } + return encounteredError +} From 7dce9024947e6d573fc5ad0e2151e07c204c474c Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Tue, 24 Mar 2015 17:11:49 -0700 Subject: [PATCH 077/999] Get rid of panic in stats for lxc Fix containers dir Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) --- daemon/daemon.go | 2 +- daemon/execdriver/execdrivers/execdrivers.go | 4 ++-- daemon/execdriver/lxc/driver.go | 11 ++++++++--- daemon/execdriver/lxc/lxc_template_unit_test.go | 10 +++++----- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 434b78a33..fb3932a0e 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1015,7 +1015,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) sysInfo := sysinfo.New(false) const runDir = "/var/run/docker" - ed, err := execdrivers.NewDriver(config.ExecDriver, runDir, sysInitPath, sysInfo) + ed, err := execdrivers.NewDriver(config.ExecDriver, runDir, config.Root, sysInitPath, sysInfo) if err != nil { return nil, err } diff --git a/daemon/execdriver/execdrivers/execdrivers.go b/daemon/execdriver/execdrivers/execdrivers.go index be3222a8b..f6f97c930 100644 --- a/daemon/execdriver/execdrivers/execdrivers.go +++ b/daemon/execdriver/execdrivers/execdrivers.go @@ -10,13 +10,13 @@ import ( "github.com/docker/docker/pkg/sysinfo" ) -func NewDriver(name, root, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { +func NewDriver(name, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { switch name { case "lxc": // we want to give 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, initPath, sysInfo.AppArmor) + return lxc.NewDriver(root, libPath, initPath, sysInfo.AppArmor) case "native": return native.NewDriver(path.Join(root, "execdriver", "native"), initPath) } diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 353e6acef..55c4ac4e1 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -36,6 +36,7 @@ var ErrExec = errors.New("Unsupported: Exec is not supported by the lxc driver") type driver struct { root string // root path for the driver to use + libPath string initPath string apparmor bool sharedRoot bool @@ -49,7 +50,7 @@ type activeContainer struct { cmd *exec.Cmd } -func NewDriver(root, initPath string, apparmor bool) (*driver, error) { +func NewDriver(root, libPath, initPath string, apparmor bool) (*driver, error) { if err := os.MkdirAll(root, 0700); err != nil { return nil, err } @@ -64,6 +65,7 @@ func NewDriver(root, initPath string, apparmor bool) (*driver, error) { return &driver{ apparmor: apparmor, root: root, + libPath: libPath, initPath: initPath, sharedRoot: rootIsShared(), activeContainers: make(map[string]*activeContainer), @@ -669,7 +671,7 @@ func rootIsShared() bool { } func (d *driver) containerDir(containerId string) string { - return path.Join(d.root, "containers", containerId) + return path.Join(d.libPath, "containers", containerId) } func (d *driver) generateLXCConfig(c *execdriver.Command) (string, error) { @@ -699,7 +701,7 @@ func (d *driver) generateEnvConfig(c *execdriver.Command) error { if err != nil { return err } - p := path.Join(d.root, "containers", c.ID, "config.env") + p := path.Join(d.libPath, "containers", c.ID, "config.env") c.Mounts = append(c.Mounts, execdriver.Mount{ Source: p, Destination: "/.dockerenv", @@ -791,5 +793,8 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo } func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) { + if _, ok := d.activeContainers[id]; !ok { + return nil, fmt.Errorf("%s is not a key in active containers", id) + } return execdriver.Stats(d.containerDir(id), d.activeContainers[id].container.Cgroups.Memory, d.machineMemory) } diff --git a/daemon/execdriver/lxc/lxc_template_unit_test.go b/daemon/execdriver/lxc/lxc_template_unit_test.go index 65e7b6d55..78760f600 100644 --- a/daemon/execdriver/lxc/lxc_template_unit_test.go +++ b/daemon/execdriver/lxc/lxc_template_unit_test.go @@ -39,7 +39,7 @@ func TestLXCConfig(t *testing.T) { cpu = cpuMin + rand.Intn(cpuMax-cpuMin) ) - driver, err := NewDriver(root, "", false) + driver, err := NewDriver(root, root, "", false) if err != nil { t.Fatal(err) } @@ -76,7 +76,7 @@ func TestCustomLxcConfig(t *testing.T) { os.MkdirAll(path.Join(root, "containers", "1"), 0777) - driver, err := NewDriver(root, "", false) + driver, err := NewDriver(root, root, "", false) if err != nil { t.Fatal(err) } @@ -194,7 +194,7 @@ func TestCustomLxcConfigMounts(t *testing.T) { } os.MkdirAll(path.Join(root, "containers", "1"), 0777) - driver, err := NewDriver(root, "", false) + driver, err := NewDriver(root, root, "", false) if err != nil { t.Fatal(err) } @@ -248,7 +248,7 @@ func TestCustomLxcConfigMisc(t *testing.T) { } defer os.RemoveAll(root) os.MkdirAll(path.Join(root, "containers", "1"), 0777) - driver, err := NewDriver(root, "", true) + driver, err := NewDriver(root, root, "", true) if err != nil { t.Fatal(err) @@ -313,7 +313,7 @@ func TestCustomLxcConfigMiscOverride(t *testing.T) { } defer os.RemoveAll(root) os.MkdirAll(path.Join(root, "containers", "1"), 0777) - driver, err := NewDriver(root, "", false) + driver, err := NewDriver(root, root, "", false) if err != nil { t.Fatal(err) } From c959d26d2f413fa93b60174c4e19111eff0a845d Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Tue, 24 Mar 2015 21:00:48 -0700 Subject: [PATCH 078/999] fix 2 integration tests on lxc Docker-DCO-1.1-Signed-off-by: Jessica Frazelle (github: jfrazelle) --- integration-cli/docker_cli_build_test.go | 1 + integration-cli/docker_cli_run_test.go | 1 + 2 files changed, 2 insertions(+) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 954c0e5d9..3e93c08c3 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -1959,6 +1959,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) { name := "testbuildcancelation" defer deleteImages(name) + defer deleteAllContainers() // (Note: one year, will never finish) ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 083e651bf..26cfcccde 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -413,6 +413,7 @@ func TestRunLinkToContainerNetMode(t *testing.T) { } func TestRunModeNetContainerHostname(t *testing.T) { + testRequires(t, ExecSupport) defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-i", "-d", "--name", "parent", "busybox", "top") out, _, err := runCommandWithOutput(cmd) From e479e1c9f7d327f396eebc46d446cf4ee34513f7 Mon Sep 17 00:00:00 2001 From: Anes Hasicic Date: Wed, 25 Mar 2015 09:53:04 +0100 Subject: [PATCH 079/999] Fixed redundant else in GetDeviceStatus Signed-off-by: Anes Hasicic --- daemon/graphdriver/devmapper/deviceset.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 686d72b95..98980cc88 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -1549,14 +1549,16 @@ func (devices *DeviceSet) GetDeviceStatus(hash string) (*DevStatus, error) { return nil, fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err) } - if sizeInSectors, mappedSectors, highestMappedSector, err := devices.deviceStatus(info.DevName()); err != nil { + sizeInSectors, mappedSectors, highestMappedSector, err := devices.deviceStatus(info.DevName()) + + if err != nil { return nil, err - } else { - status.SizeInSectors = sizeInSectors - status.MappedSectors = mappedSectors - status.HighestMappedSector = highestMappedSector } + status.SizeInSectors = sizeInSectors + status.MappedSectors = mappedSectors + status.HighestMappedSector = highestMappedSector + return status, nil } From be5de5bcb8d39515e374219aa95bd2ac855d6918 Mon Sep 17 00:00:00 2001 From: Srini Brahmaroutu Date: Thu, 26 Feb 2015 16:45:48 +0000 Subject: [PATCH 080/999] Fix the events, pull test to use v2 local server Closes #10964 Signed-off-by: Srini Brahmaroutu --- integration-cli/docker_cli_events_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index a74ce15fb..e855ce88f 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -149,6 +149,7 @@ func TestEventsImageUntagDelete(t *testing.T) { func TestEventsImagePull(t *testing.T) { since := daemonTime(t).Unix() + testRequires(t, Network) defer deleteImages("hello-world") From b46beb170fda938c373a67671919c587aeda92db Mon Sep 17 00:00:00 2001 From: Mabin Date: Wed, 25 Mar 2015 23:32:12 +0800 Subject: [PATCH 081/999] Use appropriate function to record logs Signed-off-by: Mabin --- daemon/daemon.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index fb3932a0e..283b906d8 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -294,7 +294,7 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err if !info.IsRunning() { log.Debugf("Container %s was supposed to be running but is not.", container.ID) - log.Debugf("Marking as stopped") + log.Debug("Marking as stopped") container.SetStopped(&execdriver.ExitStatus{ExitCode: -127}) if err := container.ToDisk(); err != nil { @@ -337,7 +337,7 @@ func (daemon *Daemon) restore() error { ) if !debug { - log.Infof("Loading containers: start.") + log.Info("Loading containers: start.") } dir, err := ioutil.ReadDir(daemon.repository) if err != nil { @@ -406,7 +406,7 @@ func (daemon *Daemon) restore() error { // check the restart policy on the containers and restart any container with // the restart policy of "always" if daemon.config.AutoRestart { - log.Debugf("Restarting containers...") + log.Debug("Restarting containers...") for _, container := range registeredContainers { if container.hostConfig.RestartPolicy.Name == "always" || @@ -424,7 +424,7 @@ func (daemon *Daemon) restore() error { if log.GetLevel() == log.InfoLevel { fmt.Println() } - log.Infof("Loading containers: done.") + log.Info("Loading containers: done.") } return nil @@ -465,7 +465,7 @@ func (daemon *Daemon) setupResolvconfWatcher() error { newResolvConfHash = newHash } } - log.Debugf("host network resolv.conf changed--walking container list for updates") + log.Debug("host network resolv.conf changed--walking container list for updates") contList := daemon.containers.List() for _, container := range contList { if err := container.updateResolvConf(updatedResolvConf, newResolvConfHash); err != nil { @@ -926,7 +926,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) return nil, err } - log.Debugf("Creating images graph") + log.Debug("Creating images graph") g, err := graph.NewGraph(path.Join(config.Root, "graph"), driver) if err != nil { return nil, err @@ -947,7 +947,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) return nil, err } - log.Debugf("Creating repository list") + log.Debug("Creating repository list") repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, trustKey) if err != nil { return nil, fmt.Errorf("Couldn't create Tag store: %s", err) @@ -1061,7 +1061,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) func (daemon *Daemon) shutdown() error { group := sync.WaitGroup{} - log.Debugf("starting clean shutdown of all containers...") + log.Debug("starting clean shutdown of all containers...") for _, container := range daemon.List() { c := container if c.IsRunning() { From 1d1230ea32544bcc646a5b79ddf9cc66b70c66cd Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 24 Mar 2015 22:11:45 +0100 Subject: [PATCH 082/999] Fix volume initialize error check, Fixes #11725 Signed-off-by: Antonio Murdaca --- volumes/volume.go | 5 ++++- volumes/volume_test.go | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/volumes/volume.go b/volumes/volume.go index 8041160ce..c5191c48c 100644 --- a/volumes/volume.go +++ b/volumes/volume.go @@ -90,7 +90,10 @@ func (v *Volume) initialize() error { v.lock.Lock() defer v.lock.Unlock() - if _, err := os.Stat(v.Path); err != nil && os.IsNotExist(err) { + if _, err := os.Stat(v.Path); err != nil { + if !os.IsNotExist(err) { + return err + } if err := os.MkdirAll(v.Path, 0755); err != nil { return err } diff --git a/volumes/volume_test.go b/volumes/volume_test.go index 5f3fdcfe6..caf38c8bb 100644 --- a/volumes/volume_test.go +++ b/volumes/volume_test.go @@ -1,6 +1,11 @@ package volumes -import "testing" +import ( + "strings" + "testing" + + "github.com/docker/docker/pkg/stringutils" +) func TestContainers(t *testing.T) { v := &Volume{containers: make(map[string]struct{})} @@ -17,3 +22,34 @@ func TestContainers(t *testing.T) { t.Fatalf("removing container failed") } } + +// os.Stat(v.Path) is returning ErrNotExist, initialize catch it and try to +// mkdir v.Path but it dies and correctly returns the error +func TestInitializeCannotMkdirOnNonExistentPath(t *testing.T) { + v := &Volume{Path: "nonexistentpath"} + + err := v.initialize() + if err == nil { + t.Fatal("Expected not to initialize volume with a non existent path") + } + + if !strings.Contains(err.Error(), "mkdir : no such file or directory") { + t.Fatalf("Expected to get mkdir no such file or directory, got %s", err) + } +} + +// os.Stat(v.Path) is NOT returning ErrNotExist so skip and return error from +// initialize +func TestInitializeCannotStatPathFileNameTooLong(t *testing.T) { + // ENAMETOOLONG + v := &Volume{Path: stringutils.GenerateRandomAlphaOnlyString(300)} + + err := v.initialize() + if err == nil { + t.Fatal("Expected not to initialize volume with a non existent path") + } + + if !strings.Contains(err.Error(), "file name too long") { + t.Fatalf("Expected to get ENAMETOOLONG error, got %s", err) + } +} From 0d65069a175b08015147ecf57a93f7c16ad4e37c Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 25 Mar 2015 08:45:14 -0700 Subject: [PATCH 083/999] Fix login Right now it returns: ``` FATA[0001] json: Unmarshal(non-pointer types.AuthResponse) ``` Signed-off-by: Doug Davis --- api/client/login.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/client/login.go b/api/client/login.go index 26a5d6b84..97311d058 100644 --- a/api/client/login.go +++ b/api/client/login.go @@ -123,7 +123,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } var response types.AuthResponse - if err := json.NewDecoder(stream).Decode(response); err != nil { + if err := json.NewDecoder(stream).Decode(&response); err != nil { cli.configFile, _ = registry.LoadConfig(homedir.Get()) return err } From 0252ad0adc37a34b88fa908ae74a13b940febdcb Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Wed, 25 Mar 2015 09:56:49 -0600 Subject: [PATCH 084/999] Revert "Dealing with trailing whitespaces" The validation script from #10681 is too pedantic, and does not handle well situations like: ``` cat < --- MAINTAINERS | 26 ++++++------ Makefile | 2 +- NOTICE | 4 +- contrib/completion/bash/docker | 6 +-- .../desktop-integration/gparted/Dockerfile | 2 +- contrib/host-integration/manager/systemd | 4 +- contrib/init/sysvinit-redhat/docker.sysconfig | 2 +- contrib/mkimage-debootstrap.sh | 38 ++++++++--------- contrib/report-issue.sh | 42 +++++++++---------- .../examples/postgresql_service.Dockerfile | 4 +- hack/make.sh | 1 - hack/make/.validate | 12 +++--- hack/make/dynbinary | 2 +- hack/make/tgz | 12 +++--- hack/make/validate-spaces | 33 --------------- hack/vendor.sh | 12 +++--- integration-cli/docker_cli_build_test.go | 2 +- integration/fixtures/https/client-cert.pem | 12 +++--- .../fixtures/https/client-rogue-cert.pem | 12 +++--- integration/fixtures/https/server-cert.pem | 14 +++---- .../fixtures/https/server-rogue-cert.pem | 14 +++---- 21 files changed, 111 insertions(+), 145 deletions(-) delete mode 100644 hack/make/validate-spaces diff --git a/MAINTAINERS b/MAINTAINERS index 052b7e783..04951bf45 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -113,7 +113,7 @@ It is the responsibility of the subsystem maintainers to process patches affecti manner. * If the change affects areas of the code which are not part of a subsystem, -or if subsystem maintainers are unable to reach a timely decision, it must be approved by +or if subsystem maintainers are unable to reach a timely decision, it must be approved by the core maintainers. * If the change affects the UI or public APIs, or if it represents a major change in architecture, @@ -200,11 +200,11 @@ for each. 2-code-review = "requires more code changes" 1-design-review = "raises design concerns" 4-merge = "general case" - + # Docs approval [Rules.review.docs-approval] # Changes and additions to docs must be reviewed and approved (LGTM'd) by a minimum of two docs sub-project maintainers. - # If the docs change originates with a docs maintainer, only one additional LGTM is required (since we assume a docs maintainer approves of their own PR). + # If the docs change originates with a docs maintainer, only one additional LGTM is required (since we assume a docs maintainer approves of their own PR). # Merge [Rules.review.states.4-merge] @@ -268,7 +268,7 @@ made through a pull request. # The chief architect is responsible for the overall integrity of the technical architecture # across all subsystems, and the consistency of APIs and UI. - # + # # Changes to UI, public APIs and overall architecture (for example a plugin system) must # be approved by the chief architect. "Chief Architect" = "shykes" @@ -314,7 +314,7 @@ made through a pull request. ] # The chief maintainer is responsible for all aspects of quality for the project including - # code reviews, usability, stability, security, performance, etc. + # code reviews, usability, stability, security, performance, etc. # The most important function of the chief maintainer is to lead by example. On the first # day of a new maintainer, the best advice should be "follow the C.M.'s example and you'll # be fine". @@ -359,9 +359,9 @@ made through a pull request. # has a dedicated group of maintainers, which are dedicated to that subsytem and responsible # for its quality. # This "cellular division" is the primary mechanism for scaling maintenance of the project as it grows. - # + # # The maintainers of each subsytem are responsible for: - # + # # 1. Exposing a clear road map for improving their subsystem. # 2. Deliver prompt feedback and decisions on pull requests affecting their subsystem. # 3. Be available to anyone with questions, bug reports, criticism etc. @@ -371,9 +371,9 @@ made through a pull request. # road map of the project. # # #### How to review patches to your subsystem - # + # # Accepting pull requests: - # + # # - If the pull request appears to be ready to merge, give it a `LGTM`, which # stands for "Looks Good To Me". # - If the pull request has some small problems that need to be changed, make @@ -384,9 +384,9 @@ made through a pull request. # - If the PR only needs a few changes before being merged, any MAINTAINER can # make a replacement PR that incorporates the existing commits and fixes the # problems before a fast track merge. - # + # # Closing pull requests: - # + # # - If a PR appears to be abandoned, after having attempted to contact the # original contributor, then a replacement PR may be made. Once the # replacement PR is made, any contributor may close the original one. @@ -584,12 +584,12 @@ made through a pull request. Name = "Solomon Hykes" Email = "solomon@docker.com" GitHub = "shykes" - + [people.spf13] Name = "Steve Francia" Email = "steve.francia@gmail.com" GitHub = "spf13" - + [people.sven] Name = "Sven Dowideit" Email = "SvenDowideit@home.org.au" diff --git a/Makefile b/Makefile index 952a4e9c1..9bf1b16c9 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ test-docker-py: build $(DOCKER_RUN_DOCKER) hack/make.sh binary test-docker-py validate: build - $(DOCKER_RUN_DOCKER) hack/make.sh validate-gofmt validate-dco validate-toml validate-spaces + $(DOCKER_RUN_DOCKER) hack/make.sh validate-gofmt validate-dco validate-toml shell: build $(DOCKER_RUN_DOCKER) bash diff --git a/NOTICE b/NOTICE index 8e84d0f3b..435ace7f0 100644 --- a/NOTICE +++ b/NOTICE @@ -10,9 +10,9 @@ The following is courtesy of our legal counsel: Use and transfer of Docker may be subject to certain restrictions by the -United States and other governments. +United States and other governments. It is your responsibility to ensure that your use and/or transfer does not -violate applicable laws. +violate applicable laws. For more information, please see http://www.bis.doc.gov diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 35dd4d898..ca874bc10 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -22,7 +22,7 @@ # must have access to the socket for the completions to function correctly # # Note for developers: -# Please arrange options sorted alphabetically by long name with the short +# Please arrange options sorted alphabetically by long name with the short # options immediately following their corresponding long form. # This order should be applied to lists, alternatives and code blocks. @@ -257,8 +257,8 @@ _docker_build() { ;; --file|-f) _filedir - return - ;; + return + ;; esac case "$cur" in diff --git a/contrib/desktop-integration/gparted/Dockerfile b/contrib/desktop-integration/gparted/Dockerfile index 3ddb23208..e76e65897 100644 --- a/contrib/desktop-integration/gparted/Dockerfile +++ b/contrib/desktop-integration/gparted/Dockerfile @@ -3,7 +3,7 @@ # AUTHOR: Jessica Frazelle # COMMENTS: # This file describes how to build a gparted container with all -# dependencies installed. It uses native X11 unix socket. +# dependencies installed. It uses native X11 unix socket. # Tested on Debian Jessie # USAGE: # # Download gparted Dockerfile diff --git a/contrib/host-integration/manager/systemd b/contrib/host-integration/manager/systemd index c1ab34ef0..0431b3ced 100755 --- a/contrib/host-integration/manager/systemd +++ b/contrib/host-integration/manager/systemd @@ -10,11 +10,11 @@ cat <<-EOF Description=$desc Author=$auth After=docker.service - + [Service] ExecStart=/usr/bin/docker start -a $cid ExecStop=/usr/bin/docker stop -t 2 $cid - + [Install] WantedBy=local.target EOF diff --git a/contrib/init/sysvinit-redhat/docker.sysconfig b/contrib/init/sysvinit-redhat/docker.sysconfig index 5f9b7e53e..9c99dd196 100644 --- a/contrib/init/sysvinit-redhat/docker.sysconfig +++ b/contrib/init/sysvinit-redhat/docker.sysconfig @@ -1,5 +1,5 @@ # /etc/sysconfig/docker -# +# # Other arguments to pass to the docker daemon process # These will be parsed by the sysv initscript and appended # to the arguments list passed to docker -d diff --git a/contrib/mkimage-debootstrap.sh b/contrib/mkimage-debootstrap.sh index 412a5ce0a..d9d6aae63 100755 --- a/contrib/mkimage-debootstrap.sh +++ b/contrib/mkimage-debootstrap.sh @@ -14,9 +14,9 @@ justTar= usage() { echo >&2 - + echo >&2 "usage: $0 [options] repo suite [mirror]" - + echo >&2 echo >&2 'options: (not recommended)' echo >&2 " -p set an http_proxy for debootstrap" @@ -26,20 +26,20 @@ usage() { echo >&2 " -s # skip version detection and tagging (ie, precise also tagged as 12.04)" echo >&2 " # note that this will also skip adding universe and/or security/updates to sources.list" echo >&2 " -t # just create a tarball, especially for dockerbrew (uses repo as tarball name)" - + echo >&2 echo >&2 " ie: $0 username/debian squeeze" echo >&2 " $0 username/debian squeeze http://ftp.uk.debian.org/debian/" - + echo >&2 echo >&2 " ie: $0 username/ubuntu precise" echo >&2 " $0 username/ubuntu precise http://mirrors.melbourne.co.uk/ubuntu/" - + echo >&2 echo >&2 " ie: $0 -t precise.tar.bz2 precise" echo >&2 " $0 -t wheezy.tgz wheezy" echo >&2 " $0 -t wheezy-uk.tar.xz wheezy http://ftp.uk.debian.org/debian/" - + echo >&2 } @@ -145,10 +145,10 @@ if [ -z "$strictDebootstrap" ]; then sudo chroot . dpkg-divert --local --rename --add /sbin/initctl sudo ln -sf /bin/true sbin/initctl # see https://github.com/docker/docker/issues/446#issuecomment-16953173 - + # shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB) sudo chroot . apt-get clean - + if strings usr/bin/dpkg | grep -q unsafe-io; then # while we're at it, apt is unnecessarily slow inside containers # this forces dpkg not to call sync() after package extraction and speeds up install @@ -159,7 +159,7 @@ if [ -z "$strictDebootstrap" ]; then # (see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=584254#82), # and ubuntu lucid/10.04 only has 1.15.5.6 fi - + # we want to effectively run "apt-get clean" after every install to keep images small (see output of "apt-get clean -s" for context) { aptGetClean='"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true";' @@ -167,17 +167,17 @@ if [ -z "$strictDebootstrap" ]; then echo "APT::Update::Post-Invoke { ${aptGetClean} };" echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' } | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null - + # and remove the translations, too echo 'Acquire::Languages "none";' | sudo tee etc/apt/apt.conf.d/no-languages > /dev/null - + # helpful undo lines for each the above tweaks (for lack of a better home to keep track of them): # rm /usr/sbin/policy-rc.d # rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl # rm /etc/dpkg/dpkg.cfg.d/02apt-speedup # rm /etc/apt/apt.conf.d/no-cache # rm /etc/apt/apt.conf.d/no-languages - + if [ -z "$skipDetection" ]; then # see also rudimentary platform detection in hack/install.sh lsbDist='' @@ -187,14 +187,14 @@ if [ -z "$strictDebootstrap" ]; then if [ -z "$lsbDist" ] && [ -r etc/debian_version ]; then lsbDist='Debian' fi - + case "$lsbDist" in Debian) # add the updates and security repositories if [ "$suite" != "$debianUnstable" -a "$suite" != 'unstable' ]; then # ${suite}-updates only applies to non-unstable sudo sed -i "p; s/ $suite main$/ ${suite}-updates main/" etc/apt/sources.list - + # same for security updates echo "deb http://security.debian.org/ $suite/updates main" | sudo tee -a etc/apt/sources.list > /dev/null fi @@ -220,7 +220,7 @@ if [ -z "$strictDebootstrap" ]; then ;; esac fi - + # 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 @@ -229,23 +229,23 @@ fi if [ "$justTar" ]; then # create the tarball file so it has the right permissions (ie, not root) touch "$repo" - + # fill the tarball sudo tar --numeric-owner -caf "$repo" . else # create the image (and tag $repo:$suite) sudo tar --numeric-owner -c . | $docker import - $repo:$suite - + # test the image $docker run -i -t $repo:$suite echo success - + if [ -z "$skipDetection" ]; then case "$lsbDist" in Debian) if [ "$suite" = "$debianStable" -o "$suite" = 'stable' ] && [ -r etc/debian_version ]; then # tag latest $docker tag $repo:$suite $repo:latest - + if [ -r etc/debian_version ]; then # tag the specific debian release version (which is only reasonable to tag on debian stable) ver=$(cat etc/debian_version) diff --git a/contrib/report-issue.sh b/contrib/report-issue.sh index cb54f1a5b..5ef2ecee2 100644 --- a/contrib/report-issue.sh +++ b/contrib/report-issue.sh @@ -29,41 +29,41 @@ function template() { # this should always match the template from CONTRIBUTING.md cat <<- EOM Description of problem: - - + + \`docker version\`: `${DOCKER_COMMAND} -D version` - - + + \`docker info\`: `${DOCKER_COMMAND} -D info` - - + + \`uname -a\`: `uname -a` - - + + Environment details (AWS, VirtualBox, physical, etc.): - - + + How reproducible: - - + + Steps to Reproduce: 1. 2. 3. - - + + Actual Results: - - + + Expected Results: - - + + Additional info: - - + + EOM } @@ -81,7 +81,7 @@ echo -ne "Do you use \`sudo\` to call docker? [y|N]: " read -r -n 1 use_sudo echo "" -if [ "x${use_sudo}" = "xy" -o "x${use_sudo}" = "xY" ]; then +if [ "x${use_sudo}" = "xy" -o "x${use_sudo}" = "xY" ]; then export DOCKER_COMMAND="sudo ${DOCKER}" fi diff --git a/docs/sources/examples/postgresql_service.Dockerfile b/docs/sources/examples/postgresql_service.Dockerfile index 740f180f5..9c0c0d4fc 100644 --- a/docs/sources/examples/postgresql_service.Dockerfile +++ b/docs/sources/examples/postgresql_service.Dockerfile @@ -6,7 +6,7 @@ FROM ubuntu 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 +# It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8 # Add PostgreSQL's repository. It contains the most recent stable release @@ -33,7 +33,7 @@ RUN /etc/init.d/postgresql start &&\ createdb -O docker docker # Adjust PostgreSQL configuration so that remote connections to the -# database are possible. +# database are possible. RUN echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/9.3/main/pg_hba.conf # And add ``listen_addresses`` to ``/etc/postgresql/9.3/main/postgresql.conf`` diff --git a/hack/make.sh b/hack/make.sh index 5107de5f8..118d4327f 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -45,7 +45,6 @@ DEFAULT_BUNDLES=( validate-dco validate-gofmt validate-toml - validate-spaces binary diff --git a/hack/make/.validate b/hack/make/.validate index 7397d0fa1..022809154 100644 --- a/hack/make/.validate +++ b/hack/make/.validate @@ -3,23 +3,23 @@ if [ -z "$VALIDATE_UPSTREAM" ]; then # this is kind of an expensive check, so let's not do this twice if we # are running more than one validate bundlescript - + VALIDATE_REPO='https://github.com/docker/docker.git' VALIDATE_BRANCH='master' - + if [ "$TRAVIS" = 'true' -a "$TRAVIS_PULL_REQUEST" != 'false' ]; then VALIDATE_REPO="https://github.com/${TRAVIS_REPO_SLUG}.git" VALIDATE_BRANCH="${TRAVIS_BRANCH}" fi - + VALIDATE_HEAD="$(git rev-parse --verify HEAD)" - + git fetch -q "$VALIDATE_REPO" "refs/heads/$VALIDATE_BRANCH" VALIDATE_UPSTREAM="$(git rev-parse --verify FETCH_HEAD)" - + VALIDATE_COMMIT_LOG="$VALIDATE_UPSTREAM..$VALIDATE_HEAD" VALIDATE_COMMIT_DIFF="$VALIDATE_UPSTREAM...$VALIDATE_HEAD" - + validate_diff() { if [ "$VALIDATE_UPSTREAM" != "$VALIDATE_HEAD" ]; then git diff "$VALIDATE_COMMIT_DIFF" "$@" diff --git a/hack/make/dynbinary b/hack/make/dynbinary index f9b43b0e7..861a19214 100644 --- a/hack/make/dynbinary +++ b/hack/make/dynbinary @@ -5,7 +5,7 @@ DEST=$1 if [ -z "$DOCKER_CLIENTONLY" ]; then source "$(dirname "$BASH_SOURCE")/.dockerinit" - + hash_files "$DEST/dockerinit-$VERSION" else # DOCKER_CLIENTONLY must be truthy, so we don't need to bother with dockerinit :) diff --git a/hack/make/tgz b/hack/make/tgz index fa297e1f5..7234218da 100644 --- a/hack/make/tgz +++ b/hack/make/tgz @@ -18,17 +18,17 @@ for d in "$CROSS/"*/*; do BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION" mkdir -p "$DEST/$GOOS/$GOARCH" TGZ="$DEST/$GOOS/$GOARCH/$BINARY_NAME.tgz" - + mkdir -p "$DEST/build" - + mkdir -p "$DEST/build/usr/local/bin" cp -L "$d/$BINARY_FULLNAME" "$DEST/build/usr/local/bin/docker$BINARY_EXTENSION" - + tar --numeric-owner --owner 0 -C "$DEST/build" -czf "$TGZ" usr - + hash_files "$TGZ" - + rm -rf "$DEST/build" - + echo "Created tgz: $TGZ" done diff --git a/hack/make/validate-spaces b/hack/make/validate-spaces deleted file mode 100644 index a16d6370b..000000000 --- a/hack/make/validate-spaces +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -source "$(dirname "$BASH_SOURCE")/.validate" - -#Ignoring files from vendor/, builder/parser/testfiles*, docs/theme/mkdocs/tipuesearch*, ending with .md and .go -ignoreFiles='^builder/parser/testfiles*|^docs/theme/mkdocs/tipuesearch*|^vendor/|\.md$|\.go$' - -IFS=$'\n' -files=( $(validate_diff --diff-filter=ACMR --name-only | grep -v "$ignoreFiles" || true) ) -unset IFS - -badFiles=() -for f in "${files[@]}"; do - if [ "$(git show "$VALIDATE_HEAD:$f" | grep '[[:space:]]$')" ]; then - badFiles+=( "$f" ) - fi -done - -if [ ${#badFiles[@]} -eq 0 ]; then - echo 'Congratulations! All text files are properly formatted.' -else - { - echo "These files have trailing whitespaces:" - for f in "${badFiles[@]}"; do - echo " - $f" - done - echo - echo 'Please reformat the above files using, for example:' - echo '"ex -sc "'"%s/[[:space:]]*$//g|x"'" file" and commit the result.' - echo - } >&2 - false -fi diff --git a/hack/vendor.sh b/hack/vendor.sh index fa835b410..f6422ccac 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -11,17 +11,17 @@ clone() { vcs=$1 pkg=$2 rev=$3 - + pkg_url=https://$pkg target_dir=src/$pkg - + echo -n "$pkg @ $rev: " - + if [ -d $target_dir ]; then echo -n 'rm old, ' rm -fr $target_dir fi - + echo -n 'clone, ' case $vcs in git) @@ -32,10 +32,10 @@ clone() { hg clone --quiet --updaterev $rev $pkg_url $target_dir ;; esac - + echo -n 'rm VCS, ' ( cd $target_dir && rm -rf .{git,hg} ) - + echo done } diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 73d3f78bd..f81d43181 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -4624,7 +4624,7 @@ func TestBuildExoticShellInterpolation(t *testing.T) { _, err := buildImage(name, ` FROM busybox - + ENV SOME_VAR a.b.c RUN [ "$SOME_VAR" = 'a.b.c' ] diff --git a/integration/fixtures/https/client-cert.pem b/integration/fixtures/https/client-cert.pem index cf891ff9e..c05ed47c2 100644 --- a/integration/fixtures/https/client-cert.pem +++ b/integration/fixtures/https/client-cert.pem @@ -23,20 +23,20 @@ Certificate: 7e:4e:78:7d:0a:9e:8f:42:43 Exponent: 65537 (0x10001) X509v3 extensions: - X509v3 Basic Constraints: + X509v3 Basic Constraints: CA:FALSE - Netscape Comment: + Netscape Comment: Easy-RSA Generated Certificate - X509v3 Subject Key Identifier: + 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: + 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: + X509v3 Extended Key Usage: TLS Web Client Authentication - X509v3 Key Usage: + 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: diff --git a/integration/fixtures/https/client-rogue-cert.pem b/integration/fixtures/https/client-rogue-cert.pem index 039073fbe..21ae4bd57 100644 --- a/integration/fixtures/https/client-rogue-cert.pem +++ b/integration/fixtures/https/client-rogue-cert.pem @@ -23,20 +23,20 @@ Certificate: 1d:7b:6c:7b:be:89:6b:88:8b Exponent: 65537 (0x10001) X509v3 extensions: - X509v3 Basic Constraints: + X509v3 Basic Constraints: CA:FALSE - Netscape Comment: + Netscape Comment: Easy-RSA Generated Certificate - X509v3 Subject Key Identifier: + 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: + 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: + X509v3 Extended Key Usage: TLS Web Client Authentication - X509v3 Key Usage: + 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: diff --git a/integration/fixtures/https/server-cert.pem b/integration/fixtures/https/server-cert.pem index 4ec153ac2..08abfd1a3 100644 --- a/integration/fixtures/https/server-cert.pem +++ b/integration/fixtures/https/server-cert.pem @@ -23,22 +23,22 @@ Certificate: a8:05:32:1e:f9:95:09:14:75 Exponent: 65537 (0x10001) X509v3 extensions: - X509v3 Basic Constraints: + X509v3 Basic Constraints: CA:FALSE - Netscape Cert Type: + Netscape Cert Type: SSL Server - Netscape Comment: + Netscape Comment: Easy-RSA Generated Server Certificate - X509v3 Subject Key Identifier: + 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: + 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: + X509v3 Extended Key Usage: TLS Web Server Authentication - X509v3 Key Usage: + 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: diff --git a/integration/fixtures/https/server-rogue-cert.pem b/integration/fixtures/https/server-rogue-cert.pem index c0fcf5257..28feba665 100644 --- a/integration/fixtures/https/server-rogue-cert.pem +++ b/integration/fixtures/https/server-rogue-cert.pem @@ -23,22 +23,22 @@ Certificate: 9e:02:5c:be:65:98:a4:b4:b5 Exponent: 65537 (0x10001) X509v3 extensions: - X509v3 Basic Constraints: + X509v3 Basic Constraints: CA:FALSE - Netscape Cert Type: + Netscape Cert Type: SSL Server - Netscape Comment: + Netscape Comment: Easy-RSA Generated Server Certificate - X509v3 Subject Key Identifier: + 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: + 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: + X509v3 Extended Key Usage: TLS Web Server Authentication - X509v3 Key Usage: + 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: From ed7907a988fe336711603e38c8b79d673e87c902 Mon Sep 17 00:00:00 2001 From: Xinzi Zhou Date: Wed, 25 Mar 2015 18:10:33 +0800 Subject: [PATCH 085/999] Add missing newline for bash code example Signed-off-by: Zhou Xinzi --- docs/sources/installation/ubuntulinux.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index 85a37d768..a7931c1ff 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -105,7 +105,8 @@ install Docker using the following: If `wget` isn't installed, install it after updating your manager: - $ sudo apt-get update $ sudo apt-get install wget + $ sudo apt-get update + $ sudo apt-get install wget 3. Get the latest Docker package. From 7617ec176d266650b19c2378ccab4aa41e6dc5a2 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 25 Mar 2015 13:38:17 -0400 Subject: [PATCH 086/999] .: remove trailing white spaces blame tibor this one ;-) ``` find . -type f -not -name '*.png' -not -name '*.go' -not -name '*.md' -not -name '*.tar' -not -name '*.pem' -not -path './vendor/*' -not -path './.git/*' -not -path '*/testdata/*' -not -path './docs/*images*' -not -path '*/testfiles/*' -not -path './bundles/*' -not -path './docs/*static*/*' -not -path './docs/*article-img/*' -exec grep -HnEl '[[:space:]]$' {} \; | xargs sed -iE 's/[[:space:]]*$//' ``` Signed-off-by: Vincent Batts --- MAINTAINERS | 26 ++++++------ NOTICE | 4 +- builder/words | 2 +- contrib/completion/bash/docker | 6 +-- .../desktop-integration/gparted/Dockerfile | 2 +- contrib/download-frozen-image.sh | 16 +++---- contrib/host-integration/manager/systemd | 4 +- contrib/init/sysvinit-redhat/docker.sysconfig | 2 +- contrib/mkimage-debootstrap.sh | 38 ++++++++--------- contrib/mkimage/debootstrap | 8 ++-- contrib/report-issue.sh | 42 +++++++++---------- .../Syntaxes/Dockerfile.tmLanguage | 8 ++-- docs/mkdocs.yml | 14 +++---- docs/release.sh | 2 +- .../examples/postgresql_service.Dockerfile | 4 +- hack/make/.validate | 12 +++--- hack/make/dynbinary | 2 +- hack/make/dyngccgo | 2 +- hack/make/tgz | 12 +++--- hack/vendor.sh | 12 +++--- 20 files changed, 109 insertions(+), 109 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 04951bf45..052b7e783 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -113,7 +113,7 @@ It is the responsibility of the subsystem maintainers to process patches affecti manner. * If the change affects areas of the code which are not part of a subsystem, -or if subsystem maintainers are unable to reach a timely decision, it must be approved by +or if subsystem maintainers are unable to reach a timely decision, it must be approved by the core maintainers. * If the change affects the UI or public APIs, or if it represents a major change in architecture, @@ -200,11 +200,11 @@ for each. 2-code-review = "requires more code changes" 1-design-review = "raises design concerns" 4-merge = "general case" - + # Docs approval [Rules.review.docs-approval] # Changes and additions to docs must be reviewed and approved (LGTM'd) by a minimum of two docs sub-project maintainers. - # If the docs change originates with a docs maintainer, only one additional LGTM is required (since we assume a docs maintainer approves of their own PR). + # If the docs change originates with a docs maintainer, only one additional LGTM is required (since we assume a docs maintainer approves of their own PR). # Merge [Rules.review.states.4-merge] @@ -268,7 +268,7 @@ made through a pull request. # The chief architect is responsible for the overall integrity of the technical architecture # across all subsystems, and the consistency of APIs and UI. - # + # # Changes to UI, public APIs and overall architecture (for example a plugin system) must # be approved by the chief architect. "Chief Architect" = "shykes" @@ -314,7 +314,7 @@ made through a pull request. ] # The chief maintainer is responsible for all aspects of quality for the project including - # code reviews, usability, stability, security, performance, etc. + # code reviews, usability, stability, security, performance, etc. # The most important function of the chief maintainer is to lead by example. On the first # day of a new maintainer, the best advice should be "follow the C.M.'s example and you'll # be fine". @@ -359,9 +359,9 @@ made through a pull request. # has a dedicated group of maintainers, which are dedicated to that subsytem and responsible # for its quality. # This "cellular division" is the primary mechanism for scaling maintenance of the project as it grows. - # + # # The maintainers of each subsytem are responsible for: - # + # # 1. Exposing a clear road map for improving their subsystem. # 2. Deliver prompt feedback and decisions on pull requests affecting their subsystem. # 3. Be available to anyone with questions, bug reports, criticism etc. @@ -371,9 +371,9 @@ made through a pull request. # road map of the project. # # #### How to review patches to your subsystem - # + # # Accepting pull requests: - # + # # - If the pull request appears to be ready to merge, give it a `LGTM`, which # stands for "Looks Good To Me". # - If the pull request has some small problems that need to be changed, make @@ -384,9 +384,9 @@ made through a pull request. # - If the PR only needs a few changes before being merged, any MAINTAINER can # make a replacement PR that incorporates the existing commits and fixes the # problems before a fast track merge. - # + # # Closing pull requests: - # + # # - If a PR appears to be abandoned, after having attempted to contact the # original contributor, then a replacement PR may be made. Once the # replacement PR is made, any contributor may close the original one. @@ -584,12 +584,12 @@ made through a pull request. Name = "Solomon Hykes" Email = "solomon@docker.com" GitHub = "shykes" - + [people.spf13] Name = "Steve Francia" Email = "steve.francia@gmail.com" GitHub = "spf13" - + [people.sven] Name = "Sven Dowideit" Email = "SvenDowideit@home.org.au" diff --git a/NOTICE b/NOTICE index 435ace7f0..8e84d0f3b 100644 --- a/NOTICE +++ b/NOTICE @@ -10,9 +10,9 @@ The following is courtesy of our legal counsel: Use and transfer of Docker may be subject to certain restrictions by the -United States and other governments. +United States and other governments. It is your responsibility to ensure that your use and/or transfer does not -violate applicable laws. +violate applicable laws. For more information, please see http://www.bis.doc.gov diff --git a/builder/words b/builder/words index 2148f7253..5cac826a6 100644 --- a/builder/words +++ b/builder/words @@ -15,7 +15,7 @@ hello\\ | hello\ 'hello\' | hello\ "''" | '' $. | $. -$1 | +$1 | he$1x | hex he$.x | he$.x he$pwd. | he. diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index ca874bc10..35dd4d898 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -22,7 +22,7 @@ # must have access to the socket for the completions to function correctly # # Note for developers: -# Please arrange options sorted alphabetically by long name with the short +# Please arrange options sorted alphabetically by long name with the short # options immediately following their corresponding long form. # This order should be applied to lists, alternatives and code blocks. @@ -257,8 +257,8 @@ _docker_build() { ;; --file|-f) _filedir - return - ;; + return + ;; esac case "$cur" in diff --git a/contrib/desktop-integration/gparted/Dockerfile b/contrib/desktop-integration/gparted/Dockerfile index e76e65897..3ddb23208 100644 --- a/contrib/desktop-integration/gparted/Dockerfile +++ b/contrib/desktop-integration/gparted/Dockerfile @@ -3,7 +3,7 @@ # AUTHOR: Jessica Frazelle # COMMENTS: # This file describes how to build a gparted container with all -# dependencies installed. It uses native X11 unix socket. +# dependencies installed. It uses native X11 unix socket. # Tested on Debian Jessie # USAGE: # # Download gparted Dockerfile diff --git a/contrib/download-frozen-image.sh b/contrib/download-frozen-image.sh index b45cba981..8a2bb5012 100755 --- a/contrib/download-frozen-image.sh +++ b/contrib/download-frozen-image.sh @@ -41,39 +41,39 @@ while [ $# -gt 0 ]; do [ "$imageId" != "$tag" ] || imageId= [ "$tag" != "$imageTag" ] || tag='latest' tag="${tag%@*}" - + token="$(curl -sSL -o /dev/null -D- -H 'X-Docker-Token: true' "https://index.docker.io/v1/repositories/$image/images" | tr -d '\r' | awk -F ': *' '$1 == "X-Docker-Token" { print $2 }')" - + if [ -z "$imageId" ]; then imageId="$(curl -sSL -H "Authorization: Token $token" "https://registry-1.docker.io/v1/repositories/$image/tags/$tag")" imageId="${imageId//\"/}" fi - + ancestryJson="$(curl -sSL -H "Authorization: Token $token" "https://registry-1.docker.io/v1/images/$imageId/ancestry")" if [ "${ancestryJson:0:1}" != '[' ]; then echo >&2 "error: /v1/images/$imageId/ancestry returned something unexpected:" echo >&2 " $ancestryJson" exit 1 fi - + IFS=',' ancestry=( ${ancestryJson//[\[\] \"]/} ) unset IFS - + if [ -s "$dir/tags-$image.tmp" ]; then echo -n ', ' >> "$dir/tags-$image.tmp" else images=( "${images[@]}" "$image" ) fi echo -n '"'"$tag"'": "'"$imageId"'"' >> "$dir/tags-$image.tmp" - + echo "Downloading '$imageTag' (${#ancestry[@]} layers)..." for imageId in "${ancestry[@]}"; do mkdir -p "$dir/$imageId" echo '1.0' > "$dir/$imageId/VERSION" - + curl -sSL -H "Authorization: Token $token" "https://registry-1.docker.io/v1/images/$imageId/json" -o "$dir/$imageId/json" - + # TODO figure out why "-C -" doesn't work here # "curl: (33) HTTP server doesn't seem to support byte ranges. Cannot resume." # "HTTP/1.1 416 Requested Range Not Satisfiable" diff --git a/contrib/host-integration/manager/systemd b/contrib/host-integration/manager/systemd index 0431b3ced..c1ab34ef0 100755 --- a/contrib/host-integration/manager/systemd +++ b/contrib/host-integration/manager/systemd @@ -10,11 +10,11 @@ cat <<-EOF Description=$desc Author=$auth After=docker.service - + [Service] ExecStart=/usr/bin/docker start -a $cid ExecStop=/usr/bin/docker stop -t 2 $cid - + [Install] WantedBy=local.target EOF diff --git a/contrib/init/sysvinit-redhat/docker.sysconfig b/contrib/init/sysvinit-redhat/docker.sysconfig index 9c99dd196..5f9b7e53e 100644 --- a/contrib/init/sysvinit-redhat/docker.sysconfig +++ b/contrib/init/sysvinit-redhat/docker.sysconfig @@ -1,5 +1,5 @@ # /etc/sysconfig/docker -# +# # Other arguments to pass to the docker daemon process # These will be parsed by the sysv initscript and appended # to the arguments list passed to docker -d diff --git a/contrib/mkimage-debootstrap.sh b/contrib/mkimage-debootstrap.sh index d9d6aae63..412a5ce0a 100755 --- a/contrib/mkimage-debootstrap.sh +++ b/contrib/mkimage-debootstrap.sh @@ -14,9 +14,9 @@ justTar= usage() { echo >&2 - + echo >&2 "usage: $0 [options] repo suite [mirror]" - + echo >&2 echo >&2 'options: (not recommended)' echo >&2 " -p set an http_proxy for debootstrap" @@ -26,20 +26,20 @@ usage() { echo >&2 " -s # skip version detection and tagging (ie, precise also tagged as 12.04)" echo >&2 " # note that this will also skip adding universe and/or security/updates to sources.list" echo >&2 " -t # just create a tarball, especially for dockerbrew (uses repo as tarball name)" - + echo >&2 echo >&2 " ie: $0 username/debian squeeze" echo >&2 " $0 username/debian squeeze http://ftp.uk.debian.org/debian/" - + echo >&2 echo >&2 " ie: $0 username/ubuntu precise" echo >&2 " $0 username/ubuntu precise http://mirrors.melbourne.co.uk/ubuntu/" - + echo >&2 echo >&2 " ie: $0 -t precise.tar.bz2 precise" echo >&2 " $0 -t wheezy.tgz wheezy" echo >&2 " $0 -t wheezy-uk.tar.xz wheezy http://ftp.uk.debian.org/debian/" - + echo >&2 } @@ -145,10 +145,10 @@ if [ -z "$strictDebootstrap" ]; then sudo chroot . dpkg-divert --local --rename --add /sbin/initctl sudo ln -sf /bin/true sbin/initctl # see https://github.com/docker/docker/issues/446#issuecomment-16953173 - + # shrink the image, since apt makes us fat (wheezy: ~157.5MB vs ~120MB) sudo chroot . apt-get clean - + if strings usr/bin/dpkg | grep -q unsafe-io; then # while we're at it, apt is unnecessarily slow inside containers # this forces dpkg not to call sync() after package extraction and speeds up install @@ -159,7 +159,7 @@ if [ -z "$strictDebootstrap" ]; then # (see http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=584254#82), # and ubuntu lucid/10.04 only has 1.15.5.6 fi - + # we want to effectively run "apt-get clean" after every install to keep images small (see output of "apt-get clean -s" for context) { aptGetClean='"rm -f /var/cache/apt/archives/*.deb /var/cache/apt/archives/partial/*.deb /var/cache/apt/*.bin || true";' @@ -167,17 +167,17 @@ if [ -z "$strictDebootstrap" ]; then echo "APT::Update::Post-Invoke { ${aptGetClean} };" echo 'Dir::Cache::pkgcache ""; Dir::Cache::srcpkgcache "";' } | sudo tee etc/apt/apt.conf.d/no-cache > /dev/null - + # and remove the translations, too echo 'Acquire::Languages "none";' | sudo tee etc/apt/apt.conf.d/no-languages > /dev/null - + # helpful undo lines for each the above tweaks (for lack of a better home to keep track of them): # rm /usr/sbin/policy-rc.d # rm /sbin/initctl; dpkg-divert --rename --remove /sbin/initctl # rm /etc/dpkg/dpkg.cfg.d/02apt-speedup # rm /etc/apt/apt.conf.d/no-cache # rm /etc/apt/apt.conf.d/no-languages - + if [ -z "$skipDetection" ]; then # see also rudimentary platform detection in hack/install.sh lsbDist='' @@ -187,14 +187,14 @@ if [ -z "$strictDebootstrap" ]; then if [ -z "$lsbDist" ] && [ -r etc/debian_version ]; then lsbDist='Debian' fi - + case "$lsbDist" in Debian) # add the updates and security repositories if [ "$suite" != "$debianUnstable" -a "$suite" != 'unstable' ]; then # ${suite}-updates only applies to non-unstable sudo sed -i "p; s/ $suite main$/ ${suite}-updates main/" etc/apt/sources.list - + # same for security updates echo "deb http://security.debian.org/ $suite/updates main" | sudo tee -a etc/apt/sources.list > /dev/null fi @@ -220,7 +220,7 @@ if [ -z "$strictDebootstrap" ]; then ;; esac fi - + # 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 @@ -229,23 +229,23 @@ fi if [ "$justTar" ]; then # create the tarball file so it has the right permissions (ie, not root) touch "$repo" - + # fill the tarball sudo tar --numeric-owner -caf "$repo" . else # create the image (and tag $repo:$suite) sudo tar --numeric-owner -c . | $docker import - $repo:$suite - + # test the image $docker run -i -t $repo:$suite echo success - + if [ -z "$skipDetection" ]; then case "$lsbDist" in Debian) if [ "$suite" = "$debianStable" -o "$suite" = 'stable' ] && [ -r etc/debian_version ]; then # tag latest $docker tag $repo:$suite $repo:latest - + if [ -r etc/debian_version ]; then # tag the specific debian release version (which is only reasonable to tag on debian stable) ver=$(cat etc/debian_version) diff --git a/contrib/mkimage/debootstrap b/contrib/mkimage/debootstrap index 72983d249..9d765582f 100755 --- a/contrib/mkimage/debootstrap +++ b/contrib/mkimage/debootstrap @@ -19,7 +19,7 @@ shift chrootPath="$(type -P chroot)" rootfs_chroot() { # "chroot" doesn't set PATH, so we need to set it explicitly to something our new debootstrap chroot can use appropriately! - + # set PATH and chroot away! PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' \ "$chrootPath" "$rootfsDir" "$@" @@ -220,13 +220,13 @@ fi ( set -x - + # make sure we're fully up-to-date rootfs_chroot sh -xc 'apt-get update && apt-get dist-upgrade -y' - + # delete all the apt list files since they're big and get stale quickly rm -rf "$rootfsDir/var/lib/apt/lists"/* # this forces "apt-get update" in dependent images, which is also good - + mkdir "$rootfsDir/var/lib/apt/lists/partial" # Lucid... "E: Lists directory /var/lib/apt/lists/partial is missing." ) diff --git a/contrib/report-issue.sh b/contrib/report-issue.sh index 5ef2ecee2..cb54f1a5b 100644 --- a/contrib/report-issue.sh +++ b/contrib/report-issue.sh @@ -29,41 +29,41 @@ function template() { # this should always match the template from CONTRIBUTING.md cat <<- EOM Description of problem: - - + + \`docker version\`: `${DOCKER_COMMAND} -D version` - - + + \`docker info\`: `${DOCKER_COMMAND} -D info` - - + + \`uname -a\`: `uname -a` - - + + Environment details (AWS, VirtualBox, physical, etc.): - - + + How reproducible: - - + + Steps to Reproduce: 1. 2. 3. - - + + Actual Results: - - + + Expected Results: - - + + Additional info: - - + + EOM } @@ -81,7 +81,7 @@ echo -ne "Do you use \`sudo\` to call docker? [y|N]: " read -r -n 1 use_sudo echo "" -if [ "x${use_sudo}" = "xy" -o "x${use_sudo}" = "xY" ]; then +if [ "x${use_sudo}" = "xy" -o "x${use_sudo}" = "xY" ]; then export DOCKER_COMMAND="sudo ${DOCKER}" fi diff --git a/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage b/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage index 75efc2e81..c73ae21fa 100644 --- a/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage +++ b/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage @@ -18,12 +18,12 @@ 0 name - keyword.control.dockerfile + keyword.control.dockerfile 1 name - keyword.other.special-method.dockerfile + keyword.other.special-method.dockerfile @@ -35,12 +35,12 @@ 0 name - keyword.operator.dockerfile + keyword.operator.dockerfile 1 name - keyword.other.special-method.dockerfile + keyword.other.special-method.dockerfile diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 6e7be67d2..87bad208e 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -176,15 +176,15 @@ pages: # Project: - ['project/index.md', '**HIDDEN**'] - ['project/who-written-for.md', 'Contributor Guide', 'README first'] -- ['project/software-required.md', 'Contributor Guide', 'Get required software'] -- ['project/set-up-git.md', 'Contributor Guide', 'Configure Git for contributing'] -- ['project/set-up-dev-env.md', 'Contributor Guide', 'Work with a development container'] +- ['project/software-required.md', 'Contributor Guide', 'Get required software'] +- ['project/set-up-git.md', 'Contributor Guide', 'Configure Git for contributing'] +- ['project/set-up-dev-env.md', 'Contributor Guide', 'Work with a development container'] - ['project/test-and-docs.md', 'Contributor Guide', 'Run tests and test documentation'] - ['project/make-a-contribution.md', 'Contributor Guide', 'Understand contribution workflow'] -- ['project/find-an-issue.md', 'Contributor Guide', 'Find an issue'] -- ['project/work-issue.md', 'Contributor Guide', 'Work on an issue'] -- ['project/create-pr.md', 'Contributor Guide', 'Create a pull request'] -- ['project/review-pr.md', 'Contributor Guide', 'Participate in the PR review'] +- ['project/find-an-issue.md', 'Contributor Guide', 'Find an issue'] +- ['project/work-issue.md', 'Contributor Guide', 'Work on an issue'] +- ['project/create-pr.md', 'Contributor Guide', 'Create a pull request'] +- ['project/review-pr.md', 'Contributor Guide', 'Participate in the PR review'] - ['project/advanced-contributing.md', 'Contributor Guide', 'Advanced contributing'] - ['project/get-help.md', 'Contributor Guide', 'Where to get help'] - ['project/coding-style.md', 'Contributor Guide', 'Coding style guide'] diff --git a/docs/release.sh b/docs/release.sh index 7e2ed5f11..09a85016c 100755 --- a/docs/release.sh +++ b/docs/release.sh @@ -5,7 +5,7 @@ set -o pipefail usage() { cat >&2 <<'EOF' -To publish the Docker documentation you need to set your access_key and secret_key in the docs/awsconfig file +To publish the Docker documentation you need to set your access_key and secret_key in the docs/awsconfig file (with the keys in a [profile $AWS_S3_BUCKET] section - so you can have more than one set of keys in your file) and set the AWS_S3_BUCKET env var to the name of your bucket. diff --git a/docs/sources/examples/postgresql_service.Dockerfile b/docs/sources/examples/postgresql_service.Dockerfile index 9c0c0d4fc..740f180f5 100644 --- a/docs/sources/examples/postgresql_service.Dockerfile +++ b/docs/sources/examples/postgresql_service.Dockerfile @@ -6,7 +6,7 @@ FROM ubuntu 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 +# It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8 # Add PostgreSQL's repository. It contains the most recent stable release @@ -33,7 +33,7 @@ RUN /etc/init.d/postgresql start &&\ createdb -O docker docker # Adjust PostgreSQL configuration so that remote connections to the -# database are possible. +# database are possible. RUN echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/9.3/main/pg_hba.conf # And add ``listen_addresses`` to ``/etc/postgresql/9.3/main/postgresql.conf`` diff --git a/hack/make/.validate b/hack/make/.validate index 022809154..7397d0fa1 100644 --- a/hack/make/.validate +++ b/hack/make/.validate @@ -3,23 +3,23 @@ if [ -z "$VALIDATE_UPSTREAM" ]; then # this is kind of an expensive check, so let's not do this twice if we # are running more than one validate bundlescript - + VALIDATE_REPO='https://github.com/docker/docker.git' VALIDATE_BRANCH='master' - + if [ "$TRAVIS" = 'true' -a "$TRAVIS_PULL_REQUEST" != 'false' ]; then VALIDATE_REPO="https://github.com/${TRAVIS_REPO_SLUG}.git" VALIDATE_BRANCH="${TRAVIS_BRANCH}" fi - + VALIDATE_HEAD="$(git rev-parse --verify HEAD)" - + git fetch -q "$VALIDATE_REPO" "refs/heads/$VALIDATE_BRANCH" VALIDATE_UPSTREAM="$(git rev-parse --verify FETCH_HEAD)" - + VALIDATE_COMMIT_LOG="$VALIDATE_UPSTREAM..$VALIDATE_HEAD" VALIDATE_COMMIT_DIFF="$VALIDATE_UPSTREAM...$VALIDATE_HEAD" - + validate_diff() { if [ "$VALIDATE_UPSTREAM" != "$VALIDATE_HEAD" ]; then git diff "$VALIDATE_COMMIT_DIFF" "$@" diff --git a/hack/make/dynbinary b/hack/make/dynbinary index 861a19214..f9b43b0e7 100644 --- a/hack/make/dynbinary +++ b/hack/make/dynbinary @@ -5,7 +5,7 @@ DEST=$1 if [ -z "$DOCKER_CLIENTONLY" ]; then source "$(dirname "$BASH_SOURCE")/.dockerinit" - + hash_files "$DEST/dockerinit-$VERSION" else # DOCKER_CLIENTONLY must be truthy, so we don't need to bother with dockerinit :) diff --git a/hack/make/dyngccgo b/hack/make/dyngccgo index a76e9c5b5..738e1450a 100644 --- a/hack/make/dyngccgo +++ b/hack/make/dyngccgo @@ -5,7 +5,7 @@ DEST=$1 if [ -z "$DOCKER_CLIENTONLY" ]; then source "$(dirname "$BASH_SOURCE")/.dockerinit-gccgo" - + hash_files "$DEST/dockerinit-$VERSION" else # DOCKER_CLIENTONLY must be truthy, so we don't need to bother with dockerinit :) diff --git a/hack/make/tgz b/hack/make/tgz index 7234218da..fa297e1f5 100644 --- a/hack/make/tgz +++ b/hack/make/tgz @@ -18,17 +18,17 @@ for d in "$CROSS/"*/*; do BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION" mkdir -p "$DEST/$GOOS/$GOARCH" TGZ="$DEST/$GOOS/$GOARCH/$BINARY_NAME.tgz" - + mkdir -p "$DEST/build" - + mkdir -p "$DEST/build/usr/local/bin" cp -L "$d/$BINARY_FULLNAME" "$DEST/build/usr/local/bin/docker$BINARY_EXTENSION" - + tar --numeric-owner --owner 0 -C "$DEST/build" -czf "$TGZ" usr - + hash_files "$TGZ" - + rm -rf "$DEST/build" - + echo "Created tgz: $TGZ" done diff --git a/hack/vendor.sh b/hack/vendor.sh index f6422ccac..fa835b410 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -11,17 +11,17 @@ clone() { vcs=$1 pkg=$2 rev=$3 - + pkg_url=https://$pkg target_dir=src/$pkg - + echo -n "$pkg @ $rev: " - + if [ -d $target_dir ]; then echo -n 'rm old, ' rm -fr $target_dir fi - + echo -n 'clone, ' case $vcs in git) @@ -32,10 +32,10 @@ clone() { hg clone --quiet --updaterev $rev $pkg_url $target_dir ;; esac - + echo -n 'rm VCS, ' ( cd $target_dir && rm -rf .{git,hg} ) - + echo done } From b76e300b4cd6ce4446170c7170a2734f7994a6c1 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 25 Mar 2015 13:26:41 -0400 Subject: [PATCH 087/999] btrfs: #ifdef for build version We removed it, because upstream removed it. But now it will be coming back, so work with it either way. Signed-off-by: Vincent Batts --- daemon/graphdriver/btrfs/btrfs.go | 3 +++ daemon/graphdriver/btrfs/version.go | 19 ++++++++++++------- daemon/graphdriver/btrfs/version_none.go | 4 ++++ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/daemon/graphdriver/btrfs/btrfs.go b/daemon/graphdriver/btrfs/btrfs.go index ef51bac83..1830ad4e8 100644 --- a/daemon/graphdriver/btrfs/btrfs.go +++ b/daemon/graphdriver/btrfs/btrfs.go @@ -61,6 +61,9 @@ func (d *Driver) String() string { func (d *Driver) Status() [][2]string { status := [][2]string{} + if bv := BtrfsBuildVersion(); bv != "-" { + status = append(status, [2]string{"Build Version", bv}) + } if lv := BtrfsLibVersion(); lv != -1 { status = append(status, [2]string{"Library Version", fmt.Sprintf("%d", lv)}) } diff --git a/daemon/graphdriver/btrfs/version.go b/daemon/graphdriver/btrfs/version.go index 8a96305b5..25248b151 100644 --- a/daemon/graphdriver/btrfs/version.go +++ b/daemon/graphdriver/btrfs/version.go @@ -5,17 +5,22 @@ package btrfs /* #include -// because around version 3.16, they did not define lib version yet -int my_btrfs_lib_version() { -#ifdef BTRFS_LIB_VERSION - return BTRFS_LIB_VERSION; -#else - return -1; +// around version 3.16, they did not define lib version yet +#ifndef BTRFS_LIB_VERSION +#define BTRFS_LIB_VERSION -1 +#endif + +// upstream had removed it, but now it will be coming back +#ifndef BTRFS_BUILD_VERSION +#define BTRFS_BUILD_VERSION "-" #endif -} */ import "C" +func BtrfsBuildVersion() string { + return string(C.BTRFS_BUILD_VERSION) +} + func BtrfsLibVersion() int { return int(C.BTRFS_LIB_VERSION) } diff --git a/daemon/graphdriver/btrfs/version_none.go b/daemon/graphdriver/btrfs/version_none.go index d191d36f3..b32fc61c0 100644 --- a/daemon/graphdriver/btrfs/version_none.go +++ b/daemon/graphdriver/btrfs/version_none.go @@ -5,6 +5,10 @@ package btrfs // TODO(vbatts) remove this work-around once supported linux distros are on // btrfs utililties of >= 3.16.1 +func BtrfsBuildVersion() string { + return "-" +} + func BtrfsLibVersion() int { return -1 } From 09165e0fe0b2b1b0dd2c9be45a7ca7f53c2fae29 Mon Sep 17 00:00:00 2001 From: YAMADA Tsuyoshi Date: Wed, 25 Mar 2015 23:53:24 +0900 Subject: [PATCH 088/999] removed unnecessary tab from /usr/sbin/policy-rc.d, and /sbin/initctl Signed-off-by: YAMADA Tsuyoshi --- contrib/mkimage/debootstrap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/mkimage/debootstrap b/contrib/mkimage/debootstrap index 72983d249..b7c909733 100755 --- a/contrib/mkimage/debootstrap +++ b/contrib/mkimage/debootstrap @@ -37,7 +37,7 @@ rootfs_chroot() { # prevent init scripts from running during install/update echo >&2 "+ echo exit 101 > '$rootfsDir/usr/sbin/policy-rc.d'" -cat > "$rootfsDir/usr/sbin/policy-rc.d" <<'EOF' +cat > "$rootfsDir/usr/sbin/policy-rc.d" <<-'EOF' #!/bin/sh # For most Docker users, "apt-get install" only happens during "docker build", From b4196f7892f4aeb11318bbd6c2d68227868e27e2 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 25 Mar 2015 11:32:14 -0700 Subject: [PATCH 089/999] Update libcontainer to a6044b701c166fe538fc760f9e2 Signed-off-by: Michael Crosby --- hack/vendor.sh | 2 +- .../libcontainer/cgroups/fs/apply_raw.go | 6 ----- .../docker/libcontainer/init_linux.go | 2 +- .../docker/libcontainer/rootfs_linux.go | 23 ++++++++++--------- .../docker/libcontainer/update-vendor.sh | 2 +- .../capability/capability_linux.go | 6 +---- 6 files changed, 16 insertions(+), 25 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index f6422ccac..ed0983109 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -75,7 +75,7 @@ rm -rf src/github.com/docker/distribution mkdir -p src/github.com/docker/distribution mv tmp-digest src/github.com/docker/distribution/digest -clone git github.com/docker/libcontainer fd0087d3acdc4c5865de1829d4accee5e3ebb658 +clone git github.com/docker/libcontainer a6044b701c166fe538fc760f9e2dcea3d737cd2a # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) rm -rf src/github.com/docker/libcontainer/vendor eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')" diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go index 5cb8467c7..c771245da 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go @@ -173,9 +173,6 @@ func (m *Manager) Freeze(state configs.FreezerState) error { if err != nil { return err } - if !cgroups.PathExists(dir) { - return cgroups.NewNotFoundError("freezer") - } prevState := m.Cgroups.Freezer m.Cgroups.Freezer = state @@ -200,9 +197,6 @@ func (m *Manager) GetPids() ([]int, error) { if err != nil { return nil, err } - if !cgroups.PathExists(dir) { - return nil, cgroups.NewNotFoundError("devices") - } return cgroups.ReadProcsFile(dir) } diff --git a/vendor/src/github.com/docker/libcontainer/init_linux.go b/vendor/src/github.com/docker/libcontainer/init_linux.go index 1c5f6a87e..aa95423e5 100644 --- a/vendor/src/github.com/docker/libcontainer/init_linux.go +++ b/vendor/src/github.com/docker/libcontainer/init_linux.go @@ -91,7 +91,7 @@ func populateProcessEnvironment(env []string) error { // finalizeNamespace drops the caps, sets the correct user // and working dir, and closes any leaked file descriptors -// before execing the command inside the namespace +// before executing the command inside the namespace func finalizeNamespace(config *initConfig) error { // Ensure that all non-standard fds we may have accidentally // inherited are marked close-on-exec so they stay out of the diff --git a/vendor/src/github.com/docker/libcontainer/rootfs_linux.go b/vendor/src/github.com/docker/libcontainer/rootfs_linux.go index 6caa07a0c..ab1a9a5fc 100644 --- a/vendor/src/github.com/docker/libcontainer/rootfs_linux.go +++ b/vendor/src/github.com/docker/libcontainer/rootfs_linux.go @@ -186,7 +186,9 @@ func reOpenDevNull(rootfs string) error { func createDevices(config *configs.Config) error { oldMask := syscall.Umask(0000) for _, node := range config.Devices { - if err := createDeviceNode(config.Rootfs, node); err != nil { + // containers running in a user namespace are not allowed to mknod + // devices so we can just bind mount it from the host. + if err := createDeviceNode(config.Rootfs, node, config.Namespaces.Contains(configs.NEWUSER)); err != nil { syscall.Umask(oldMask) return err } @@ -196,20 +198,13 @@ func createDevices(config *configs.Config) error { } // Creates the device node in the rootfs of the container. -func createDeviceNode(rootfs string, node *configs.Device) error { +func createDeviceNode(rootfs string, node *configs.Device, bind bool) error { dest := filepath.Join(rootfs, node.Path) if err := os.MkdirAll(filepath.Dir(dest), 0755); err != nil { return err } - if err := mknodDevice(dest, node); err != nil { - if os.IsExist(err) { - return nil - } - if err != syscall.EPERM { - return err - } - // containers running in a user namespace are not allowed to mknod - // devices so we can just bind mount it from the host. + + if bind { f, err := os.Create(dest) if err != nil && !os.IsExist(err) { return err @@ -219,6 +214,12 @@ func createDeviceNode(rootfs string, node *configs.Device) error { } return syscall.Mount(node.Path, dest, "bind", syscall.MS_BIND, "") } + if err := mknodDevice(dest, node); err != nil { + if os.IsExist(err) { + return nil + } + return err + } return nil } diff --git a/vendor/src/github.com/docker/libcontainer/update-vendor.sh b/vendor/src/github.com/docker/libcontainer/update-vendor.sh index 12077256e..b68f5d461 100755 --- a/vendor/src/github.com/docker/libcontainer/update-vendor.sh +++ b/vendor/src/github.com/docker/libcontainer/update-vendor.sh @@ -44,6 +44,6 @@ clone git github.com/codegangsta/cli 1.1.0 clone git github.com/coreos/go-systemd v2 clone git github.com/godbus/dbus v2 clone git github.com/Sirupsen/logrus v0.6.6 -clone git github.com/syndtr/gocapability e55e583369 +clone git github.com/syndtr/gocapability 8e4cdcb # intentionally not vendoring Docker itself... that'd be a circle :) diff --git a/vendor/src/github.com/syndtr/gocapability/capability/capability_linux.go b/vendor/src/github.com/syndtr/gocapability/capability/capability_linux.go index 24dc85fa8..3dfcd398d 100644 --- a/vendor/src/github.com/syndtr/gocapability/capability/capability_linux.go +++ b/vendor/src/github.com/syndtr/gocapability/capability/capability_linux.go @@ -417,10 +417,6 @@ func (c *capsV3) Load() (err error) { } func (c *capsV3) Apply(kind CapType) (err error) { - err = initLastCap() - if err != nil { - return - } if kind&BOUNDS == BOUNDS { var data [2]capData err = capget(&c.hdr, &data[0]) @@ -428,7 +424,7 @@ func (c *capsV3) Apply(kind CapType) (err error) { return } if (1< Date: Wed, 25 Mar 2015 11:01:52 -0700 Subject: [PATCH 090/999] Rename Fds to File Descriptors in docker info This makes the docker info more readable. Also change a log line in a test file renaming Fds Signed-off-by: Ankush Agarwal --- api/client/info.go | 2 +- docs/sources/reference/commandline/cli.md | 2 +- docs/sources/userguide/labels-custom-metadata.md | 2 +- integration/z_final_test.go | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/client/info.go b/api/client/info.go index 795847f59..b76516328 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -81,7 +81,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { } fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") if remoteInfo.Exists("NFd") { - fmt.Fprintf(cli.out, "Fds: %d\n", remoteInfo.GetInt("NFd")) + fmt.Fprintf(cli.out, "File Descriptors: %d\n", remoteInfo.GetInt("NFd")) } if remoteInfo.Exists("NGoroutines") { fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines")) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 6d39d4541..a6c1990d0 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1333,7 +1333,7 @@ For example: Total Memory: 2 GiB Debug mode (server): false Debug mode (client): true - Fds: 10 + File Descriptors: 10 Goroutines: 9 System Time: Tue Mar 10 18:38:57 UTC 2015 EventsListeners: 0 diff --git a/docs/sources/userguide/labels-custom-metadata.md b/docs/sources/userguide/labels-custom-metadata.md index 7cf25c060..eb21cc98d 100644 --- a/docs/sources/userguide/labels-custom-metadata.md +++ b/docs/sources/userguide/labels-custom-metadata.md @@ -179,7 +179,7 @@ These labels appear as part of the `docker info` output for the daemon: ID: RC3P:JTCT:32YS:XYSB:YUBG:VFED:AAJZ:W3YW:76XO:D7NN:TEVU:UCRW Debug mode (server): false Debug mode (client): true - Fds: 11 + File Descriptors: 11 Goroutines: 14 EventsListeners: 0 Init Path: /usr/bin/docker diff --git a/integration/z_final_test.go b/integration/z_final_test.go index ad1eb4340..13cd0c3fd 100644 --- a/integration/z_final_test.go +++ b/integration/z_final_test.go @@ -7,11 +7,11 @@ import ( ) func displayFdGoroutines(t *testing.T) { - t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine()) + t.Logf("File Descriptors: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine()) } func TestFinal(t *testing.T) { nuke(globalDaemon) - t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines) + t.Logf("Start File Descriptors: %d, Start Goroutines: %d", startFds, startGoroutines) displayFdGoroutines(t) } From b5d0380108dde9e96d51bb01821860a0799dee0d Mon Sep 17 00:00:00 2001 From: Peggy Li Date: Wed, 25 Mar 2015 10:34:41 -0700 Subject: [PATCH 091/999] Add godoc-style docstrings to Cmd... methods Signed-off-by: Peggy Li --- api/client/attach.go | 3 +++ api/client/build.go | 5 +++++ api/client/cli.go | 2 +- api/client/client.go | 5 +++++ api/client/commit.go | 3 +++ api/client/cp.go | 5 +++++ api/client/create.go | 3 +++ api/client/diff.go | 5 +++++ api/client/events.go | 3 +++ api/client/exec.go | 3 +++ api/client/export.go | 5 +++++ api/client/help.go | 5 +++++ api/client/history.go | 3 +++ api/client/images.go | 3 +++ api/client/import.go | 5 +++++ api/client/info.go | 4 +++- api/client/inspect.go | 3 +++ api/client/kill.go | 4 +++- api/client/load.go | 5 +++++ api/client/login.go | 6 +++++- api/client/logout.go | 6 +++++- api/client/logs.go | 3 +++ api/client/pause.go | 3 +++ api/client/port.go | 4 ++++ api/client/ps.go | 3 +++ api/client/pull.go | 3 +++ api/client/push.go | 3 +++ api/client/rename.go | 3 +++ api/client/restart.go | 3 +++ api/client/rmi.go | 4 +++- api/client/run.go | 3 +++ api/client/save.go | 5 +++++ api/client/search.go | 3 +++ api/client/start.go | 3 +++ api/client/stats.go | 5 +++++ api/client/stop.go | 5 +++++ api/client/tag.go | 3 +++ api/client/top.go | 3 +++ api/client/unpause.go | 3 +++ api/client/version.go | 6 +++++- api/client/wait.go | 6 +++++- 41 files changed, 152 insertions(+), 8 deletions(-) create mode 100644 api/client/client.go diff --git a/api/client/attach.go b/api/client/attach.go index e9232a104..5b8048589 100644 --- a/api/client/attach.go +++ b/api/client/attach.go @@ -12,6 +12,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdAttach attaches to a running container. +// +// Usage: docker attach [OPTIONS] CONTAINER func (cli *DockerCli) CmdAttach(args ...string) error { var ( cmd = cli.Subcmd("attach", "CONTAINER", "Attach to a running container", true) diff --git a/api/client/build.go b/api/client/build.go index 91446132a..78507c39b 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -36,6 +36,11 @@ const ( tarHeaderSize = 512 ) +// CmdBuild builds a new image from the source code at a given path. +// +// If '-' is provided instead of a path or URL, Docker will build an image from either a Dockerfile or tar archive read from STDIN. +// +// Usage: docker build [OPTIONS] PATH | URL | - func (cli *DockerCli) CmdBuild(args ...string) error { cmd := cli.Subcmd("build", "PATH | URL | -", "Build a new image from the source code at PATH", true) tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image") diff --git a/api/client/cli.go b/api/client/cli.go index e0ab4191b..01b5f2e63 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -64,7 +64,7 @@ func (cli *DockerCli) getMethod(args ...string) (func(...string) error, bool) { return method.Interface().(func(...string) error), true } -// Cmd executes the specified command +// Cmd executes the specified command. func (cli *DockerCli) Cmd(args ...string) error { if len(args) > 1 { method, exists := cli.getMethod(args[:2]...) diff --git a/api/client/client.go b/api/client/client.go new file mode 100644 index 000000000..4cfce5f68 --- /dev/null +++ b/api/client/client.go @@ -0,0 +1,5 @@ +// Package client provides a command-line interface for Docker. +// +// Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand. +// See https://docs.docker.com/installation/ for instructions on installing Docker. +package client diff --git a/api/client/commit.go b/api/client/commit.go index c532fab0d..4500f3b42 100644 --- a/api/client/commit.go +++ b/api/client/commit.go @@ -14,6 +14,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdAttach attaches to a running container. +// +// Usage: docker attach [OPTIONS] CONTAINER func (cli *DockerCli) CmdCommit(args ...string) error { cmd := cli.Subcmd("commit", "CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes", true) flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit") diff --git a/api/client/cp.go b/api/client/cp.go index f2bedb0b1..19e050e16 100644 --- a/api/client/cp.go +++ b/api/client/cp.go @@ -11,6 +11,11 @@ import ( "github.com/docker/docker/utils" ) +// CmdCp copies files/folders from a path on the container to a directory on the host running the command. +// +// If HOSTDIR is '-', the data is written as a tar file to STDOUT. +// +// Usage: docker cp CONTAINER:PATH HOSTDIR func (cli *DockerCli) CmdCp(args ...string) error { cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTDIR|-", "Copy files/folders from a PATH on the container to a HOSTDIR on the host\nrunning the command. Use '-' to write the data\nas a tar file to STDOUT.", true) cmd.Require(flag.Exact, 2) diff --git a/api/client/create.go b/api/client/create.go index 1adf43b9e..10b16da8d 100644 --- a/api/client/create.go +++ b/api/client/create.go @@ -128,6 +128,9 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc return &response, nil } +// CmdCreate creates a new container from a given image. +// +// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...] func (cli *DockerCli) CmdCreate(args ...string) error { cmd := cli.Subcmd("create", "IMAGE [COMMAND] [ARG...]", "Create a new container", true) diff --git a/api/client/diff.go b/api/client/diff.go index f82e9c1b2..08f16a712 100644 --- a/api/client/diff.go +++ b/api/client/diff.go @@ -9,6 +9,11 @@ import ( "github.com/docker/docker/utils" ) +// CmdDiff shows changes on a container's filesystem. +// +// Each changed file is printed on a separate line, prefixed with a single character that indicates the status of the file: C (modified), A (added), or D (deleted). +// +// Usage: docker diff CONTAINER func (cli *DockerCli) CmdDiff(args ...string) error { cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem", true) cmd.Require(flag.Exact, 1) diff --git a/api/client/events.go b/api/client/events.go index bc39e3cfd..047c5559b 100644 --- a/api/client/events.go +++ b/api/client/events.go @@ -12,6 +12,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdEvents prints a live stream of real time events from the server. +// +// Usage: docker events [OPTIONS] func (cli *DockerCli) CmdEvents(args ...string) error { cmd := cli.Subcmd("events", "", "Get real time events from the server", true) since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp") diff --git a/api/client/exec.go b/api/client/exec.go index 8ededebd1..51acb3710 100644 --- a/api/client/exec.go +++ b/api/client/exec.go @@ -12,6 +12,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdExec runs a command in a running container. +// +// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...] func (cli *DockerCli) CmdExec(args ...string) error { cmd := cli.Subcmd("exec", "CONTAINER COMMAND [ARG...]", "Run a command in a running container", true) diff --git a/api/client/export.go b/api/client/export.go index 5b13b0a25..dab83490e 100644 --- a/api/client/export.go +++ b/api/client/export.go @@ -10,6 +10,11 @@ import ( "github.com/docker/docker/utils" ) +// CmdExport exports a filesystem as a tar archive. +// +// The tar archive is streamed to STDOUT by default or written to a file. +// +// Usage: docker export [OPTIONS] CONTAINER func (cli *DockerCli) CmdExport(args ...string) error { cmd := cli.Subcmd("export", "CONTAINER", "Export a filesystem as a tar archive (streamed to STDOUT by default)", true) outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT") diff --git a/api/client/help.go b/api/client/help.go index 5dea652b0..2f2d50bbf 100644 --- a/api/client/help.go +++ b/api/client/help.go @@ -7,6 +7,11 @@ import ( flag "github.com/docker/docker/pkg/mflag" ) +// CmdHelp displays information on a Docker command. +// +//If more than one command is specified, information is only shown for the first command. +// +// Usage: docker help COMMAND or docker COMMAND --help func (cli *DockerCli) CmdHelp(args ...string) error { if len(args) > 1 { method, exists := cli.getMethod(args[:2]...) diff --git a/api/client/history.go b/api/client/history.go index 98ba68f9a..b932a4eda 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -12,6 +12,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdHistory shows the history of an image. +// +// Usage: docker history [OPTIONS] IMAGE func (cli *DockerCli) CmdHistory(args ...string) error { cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image", true) quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") diff --git a/api/client/images.go b/api/client/images.go index d59a0838a..1b2dd0cd5 100644 --- a/api/client/images.go +++ b/api/client/images.go @@ -85,6 +85,9 @@ func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix stri } } +// CmdImages lists the images in a specified repository, or all top-level images if no repository is specified. +// +// Usage: docker images [OPTIONS] [REPOSITORY] func (cli *DockerCli) CmdImages(args ...string) error { cmd := cli.Subcmd("images", "[REPOSITORY]", "List images", true) quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") diff --git a/api/client/import.go b/api/client/import.go index be8e8a6e7..4264e1c28 100644 --- a/api/client/import.go +++ b/api/client/import.go @@ -12,6 +12,11 @@ import ( "github.com/docker/docker/utils" ) +// CmdImport creates an empty filesystem image, imports the contents of the tarball into the image, and optionally tags the image. +// +// The URL argument is the address of a tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) file. If the URL is '-', then the tar file is read from STDIN. +// +// Usage: docker import [OPTIONS] URL [REPOSITORY[:TAG]] func (cli *DockerCli) CmdImport(args ...string) error { cmd := cli.Subcmd("import", "URL|- [REPOSITORY[:TAG]]", "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true) flChanges := opts.NewListOpts(nil) diff --git a/api/client/info.go b/api/client/info.go index 795847f59..96ddeadef 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -12,7 +12,9 @@ import ( "github.com/docker/docker/utils" ) -// 'docker info': display system-wide information. +// CmdInfo displays system-wide information. +// +// Usage: docker info func (cli *DockerCli) CmdInfo(args ...string) error { cmd := cli.Subcmd("info", "", "Display system-wide information", true) cmd.Require(flag.Exact, 0) diff --git a/api/client/inspect.go b/api/client/inspect.go index f63858981..2a840c4bc 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -12,6 +12,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdInspect displays low-level information on one or more containers or images. +// +// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...] func (cli *DockerCli) CmdInspect(args ...string) error { cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true) tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template") diff --git a/api/client/kill.go b/api/client/kill.go index 1b8e27d35..0f4536fee 100644 --- a/api/client/kill.go +++ b/api/client/kill.go @@ -7,7 +7,9 @@ import ( "github.com/docker/docker/utils" ) -// 'docker kill NAME' kills a running container +// CmdKill kills one or more running container using SIGKILL or a specified signal. +// +// Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdKill(args ...string) error { cmd := cli.Subcmd("kill", "CONTAINER [CONTAINER...]", "Kill a running container using SIGKILL or a specified signal", true) signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container") diff --git a/api/client/load.go b/api/client/load.go index 25ef0ab6c..e8eb8e251 100644 --- a/api/client/load.go +++ b/api/client/load.go @@ -8,6 +8,11 @@ import ( "github.com/docker/docker/utils" ) +// CmdLoad loads an image from a tar archive. +// +// The tar archive is read from STDIN by default, or from a tar archive file. +// +// Usage: docker load [OPTIONS] func (cli *DockerCli) CmdLoad(args ...string) error { cmd := cli.Subcmd("load", "", "Load an image from a tar archive on STDIN", true) infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN") diff --git a/api/client/login.go b/api/client/login.go index 97311d058..6fe2daba8 100644 --- a/api/client/login.go +++ b/api/client/login.go @@ -17,7 +17,11 @@ import ( "github.com/docker/docker/utils" ) -// 'docker login': login / register a user to registry service. +// CmdLogin logs in or registers a user to a Docker registry service. +// +// If no server is specified, the user will be logged into or registered to the registry's index server. +// +// Usage: docker login SERVER func (cli *DockerCli) CmdLogin(args ...string) error { cmd := cli.Subcmd("login", "[SERVER]", "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true) cmd.Require(flag.Max, 1) diff --git a/api/client/logout.go b/api/client/logout.go index bd135de7f..5d9a77f2c 100644 --- a/api/client/logout.go +++ b/api/client/logout.go @@ -8,7 +8,11 @@ import ( "github.com/docker/docker/utils" ) -// 'docker logout': log out a user from a registry service. +// CmdLogout logs a user out from a Docker registry. +// +// If no server is specified, the user will be logged out from the registry's index server. +// +// Usage: docker logout [SERVER] func (cli *DockerCli) CmdLogout(args ...string) error { cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true) cmd.Require(flag.Max, 1) diff --git a/api/client/logs.go b/api/client/logs.go index 1cb580052..11809d572 100644 --- a/api/client/logs.go +++ b/api/client/logs.go @@ -9,6 +9,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdLogs fetches the logs of a given container. +// +// docker logs [OPTIONS] CONTAINER func (cli *DockerCli) CmdLogs(args ...string) error { var ( cmd = cli.Subcmd("logs", "CONTAINER", "Fetch the logs of a container", true) diff --git a/api/client/pause.go b/api/client/pause.go index db85368a9..a13be5c11 100644 --- a/api/client/pause.go +++ b/api/client/pause.go @@ -7,6 +7,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdPause pauses all processes within one or more containers. +// +// Usage: docker pause CONTAINER [CONTAINER...] func (cli *DockerCli) CmdPause(args ...string) error { cmd := cli.Subcmd("pause", "CONTAINER [CONTAINER...]", "Pause all processes within a container", true) cmd.Require(flag.Min, 1) diff --git a/api/client/port.go b/api/client/port.go index 713fc8b96..cc3da8afe 100644 --- a/api/client/port.go +++ b/api/client/port.go @@ -10,6 +10,10 @@ import ( "github.com/docker/docker/utils" ) +// CmdPort lists port mappings for a container. +// If a private port is specified, it also shows the public-facing port that is NATed to the private port. +// +// Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]] func (cli *DockerCli) CmdPort(args ...string) error { cmd := cli.Subcmd("port", "CONTAINER [PRIVATE_PORT[/PROTO]]", "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true) cmd.Require(flag.Min, 1) diff --git a/api/client/ps.go b/api/client/ps.go index fa9b2b488..28891c984 100644 --- a/api/client/ps.go +++ b/api/client/ps.go @@ -18,6 +18,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdPs outputs a list of Docker containers. +// +// Usage: docker ps [OPTIONS] func (cli *DockerCli) CmdPs(args ...string) error { var ( err error diff --git a/api/client/pull.go b/api/client/pull.go index a27c7cb96..3f42ba2ce 100644 --- a/api/client/pull.go +++ b/api/client/pull.go @@ -14,6 +14,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdPull pulls an image or a repository from the registry. +// +// Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST] func (cli *DockerCli) CmdPull(args ...string) error { cmd := cli.Subcmd("pull", "NAME[:TAG|@DIGEST]", "Pull an image or a repository from the registry", true) allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository") diff --git a/api/client/push.go b/api/client/push.go index 92a87ed27..025cf682c 100644 --- a/api/client/push.go +++ b/api/client/push.go @@ -13,6 +13,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdPush pushes an image or repository to the registry. +// +// Usage: docker push NAME[:TAG] func (cli *DockerCli) CmdPush(args ...string) error { cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry", true) cmd.Require(flag.Exact, 1) diff --git a/api/client/rename.go b/api/client/rename.go index 82d2b74c3..45cf43595 100644 --- a/api/client/rename.go +++ b/api/client/rename.go @@ -2,6 +2,9 @@ package client import "fmt" +// CmdRename renames a container. +// +// Usage: docker rename OLD_NAME NEW_NAME func (cli *DockerCli) CmdRename(args ...string) error { cmd := cli.Subcmd("rename", "OLD_NAME NEW_NAME", "Rename a container", true) if err := cmd.Parse(args); err != nil { diff --git a/api/client/restart.go b/api/client/restart.go index 90c5b3fab..84882f292 100644 --- a/api/client/restart.go +++ b/api/client/restart.go @@ -9,6 +9,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdRestart restarts one or more running containers. +// +// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdRestart(args ...string) error { cmd := cli.Subcmd("restart", "CONTAINER [CONTAINER...]", "Restart a running container", true) nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container") diff --git a/api/client/rmi.go b/api/client/rmi.go index 7c5b4d0af..93e41654b 100644 --- a/api/client/rmi.go +++ b/api/client/rmi.go @@ -9,7 +9,9 @@ import ( "github.com/docker/docker/utils" ) -// 'docker rmi IMAGE' removes all images with the name IMAGE +// CmdRmi removes all images with the specified name(s). +// +// Usage: docker rmi [OPTIONS] IMAGE [IMAGE...] func (cli *DockerCli) CmdRmi(args ...string) error { var ( cmd = cli.Subcmd("rmi", "IMAGE [IMAGE...]", "Remove one or more images", true) diff --git a/api/client/run.go b/api/client/run.go index e00a3d78d..1c37e40dd 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -35,6 +35,9 @@ func (cid *cidFile) Write(id string) error { return nil } +// CmdRun runs a command in a new container. +// +// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] func (cli *DockerCli) CmdRun(args ...string) error { // FIXME: just use runconfig.Parse already cmd := cli.Subcmd("run", "IMAGE [COMMAND] [ARG...]", "Run a command in a new container", true) diff --git a/api/client/save.go b/api/client/save.go index 8d42218d8..e0cdd1c29 100644 --- a/api/client/save.go +++ b/api/client/save.go @@ -10,6 +10,11 @@ import ( "github.com/docker/docker/utils" ) +// CmdSave saves one or more images to a tar archive. +// +// The tar archive is written to STDOUT by default, or written to a file. +// +// Usage: docker save [OPTIONS] IMAGE [IMAGE...] func (cli *DockerCli) CmdSave(args ...string) error { cmd := cli.Subcmd("save", "IMAGE [IMAGE...]", "Save an image(s) to a tar archive (streamed to STDOUT by default)", true) outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT") diff --git a/api/client/search.go b/api/client/search.go index 1b43ac991..21d704841 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -11,6 +11,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdSearch searches the Docker Hub for images. +// +// Usage: docker search [OPTIONS] TERM func (cli *DockerCli) CmdSearch(args ...string) error { cmd := cli.Subcmd("search", "TERM", "Search the Docker Hub for images", true) noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") diff --git a/api/client/start.go b/api/client/start.go index 8d4ea6bb9..ef8f17bd4 100644 --- a/api/client/start.go +++ b/api/client/start.go @@ -40,6 +40,9 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { return sigc } +// CmdStart starts one or more stopped containers. +// +// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdStart(args ...string) error { var ( cErr chan error diff --git a/api/client/stats.go b/api/client/stats.go index 8999abdb2..bdb8ec4b8 100644 --- a/api/client/stats.go +++ b/api/client/stats.go @@ -106,6 +106,11 @@ func (s *containerStats) Display(w io.Writer) error { return nil } +// CmdStats displays a live stream of resource usage statistics for one or more containers. +// +// This shows real-time information on CPU usage, memory usage, and network I/O. +// +// Usage: docker stats CONTAINER [CONTAINER...] func (cli *DockerCli) CmdStats(args ...string) error { cmd := cli.Subcmd("stats", "CONTAINER [CONTAINER...]", "Display a live stream of one or more containers' resource usage statistics", true) cmd.Require(flag.Min, 1) diff --git a/api/client/stop.go b/api/client/stop.go index aa46a2fea..943f8c58a 100644 --- a/api/client/stop.go +++ b/api/client/stop.go @@ -9,6 +9,11 @@ import ( "github.com/docker/docker/utils" ) +// CmdStop stops one or more running containers. +// +// A running container is stopped by first sending SIGTERM and then SIGKILL if the container fails to stop within a grace period (the default is 10 seconds). +// +// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdStop(args ...string) error { cmd := cli.Subcmd("stop", "CONTAINER [CONTAINER...]", "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true) nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it") diff --git a/api/client/tag.go b/api/client/tag.go index 701e381e9..44a32508f 100644 --- a/api/client/tag.go +++ b/api/client/tag.go @@ -9,6 +9,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdTag tags an image into a repository. +// +// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG] func (cli *DockerCli) CmdTag(args ...string) error { cmd := cli.Subcmd("tag", "IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]", "Tag an image into a repository", true) force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force") diff --git a/api/client/top.go b/api/client/top.go index 8f8837a9a..b5129acb9 100644 --- a/api/client/top.go +++ b/api/client/top.go @@ -11,6 +11,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdTop displays the running processes of a container. +// +// Usage: docker top CONTAINER func (cli *DockerCli) CmdTop(args ...string) error { cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Display the running processes of a container", true) cmd.Require(flag.Min, 1) diff --git a/api/client/unpause.go b/api/client/unpause.go index c40948404..c5a66f61b 100644 --- a/api/client/unpause.go +++ b/api/client/unpause.go @@ -7,6 +7,9 @@ import ( "github.com/docker/docker/utils" ) +// CmdUnpause unpauses all processes within a container, for one or more containers. +// +// Usage: docker unpause CONTAINER [CONTAINER...] func (cli *DockerCli) CmdUnpause(args ...string) error { cmd := cli.Subcmd("unpause", "CONTAINER [CONTAINER...]", "Unpause all processes within a container", true) cmd.Require(flag.Min, 1) diff --git a/api/client/version.go b/api/client/version.go index fa996e538..d3752d34e 100644 --- a/api/client/version.go +++ b/api/client/version.go @@ -12,7 +12,11 @@ import ( "github.com/docker/docker/utils" ) -// 'docker version': show version information +// CmdVersion shows Docker version information. +// +// Available version information is shown for: client Docker version, client API version, client Go version, client Git commit, client OS/Arch, server Docker version, server API version, server Go version, server Git commit, and server OS/Arch. +// +// Usage: docker version func (cli *DockerCli) CmdVersion(args ...string) error { cmd := cli.Subcmd("version", "", "Show the Docker version information.", true) cmd.Require(flag.Exact, 0) diff --git a/api/client/wait.go b/api/client/wait.go index ca9f713aa..92c3b05dc 100644 --- a/api/client/wait.go +++ b/api/client/wait.go @@ -7,7 +7,11 @@ import ( "github.com/docker/docker/utils" ) -// 'docker wait': block until a container stops +// CmdWait blocks until a container stops, then prints its exit code. +// +// If more than one container is specified, this will wait synchronously on each container. +// +// Usage: docker wait CONTAINER [CONTAINER...] func (cli *DockerCli) CmdWait(args ...string) error { cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.", true) cmd.Require(flag.Min, 1) From c79b9bab541673af121d829ebc3b29ff1b01efa2 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 25 Mar 2015 08:44:12 +0100 Subject: [PATCH 092/999] Remove engine.Status and replace it with standard go error Signed-off-by: Antonio Murdaca --- api/server/server.go | 12 +-- api/server/server_linux.go | 4 +- api/server/server_unit_test.go | 80 +++++++++---------- builder/job.go | 41 +++++----- builtins/builtins.go | 6 +- daemon/attach.go | 9 ++- daemon/changes.go | 20 ++--- daemon/commit.go | 17 ++-- daemon/copy.go | 13 +-- daemon/create.go | 27 ++++--- daemon/delete.go | 26 +++--- daemon/exec.go | 26 +++--- daemon/export.go | 13 +-- daemon/image_delete.go | 12 +-- daemon/info.go | 12 +-- daemon/inspect.go | 24 +++--- daemon/kill.go | 15 ++-- daemon/list.go | 16 ++-- daemon/logs.go | 14 ++-- daemon/networkdriver/bridge/driver.go | 92 +++++++++++----------- daemon/networkdriver/bridge/driver_test.go | 24 +++--- daemon/pause.go | 22 +++--- daemon/rename.go | 20 +++-- daemon/resize.go | 29 +++---- daemon/restart.go | 12 +-- daemon/start.go | 18 +++-- daemon/stats.go | 8 +- daemon/stop.go | 14 ++-- daemon/top.go | 19 ++--- daemon/wait.go | 9 ++- engine/engine.go | 6 +- engine/engine_test.go | 24 +++--- engine/job.go | 62 ++------------- engine/job_test.go | 44 ++--------- engine/shutdown_test.go | 8 +- events/events.go | 23 +++--- graph/export.go | 23 +++--- graph/history.go | 11 +-- graph/import.go | 19 ++--- graph/list.go | 13 +-- graph/load.go | 22 +++--- graph/load_unsupported.go | 6 +- graph/pull.go | 20 ++--- graph/push.go | 22 +++--- graph/service.go | 46 +++++------ graph/tag.go | 11 ++- graph/viz.go | 9 ++- registry/service.go | 50 ++++++------ trust/service.go | 18 ++--- 49 files changed, 525 insertions(+), 566 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index a11a4fd9f..9c50bfb52 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1097,7 +1097,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite select { case <-finished: case <-closeNotifier.CloseNotify(): - log.Infof("Client disconnected, cancelling job: %v", job) + log.Infof("Client disconnected, cancelling job: %s", job.Name) job.Cancel() } }() @@ -1581,9 +1581,9 @@ type Server interface { // ServeApi loops through all of the protocols sent in to docker and spawns // off a go routine to setup a serving http.Server for each. -func ServeApi(job *engine.Job) engine.Status { +func ServeApi(job *engine.Job) error { if len(job.Args) == 0 { - return job.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) + return fmt.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) } var ( protoAddrs = job.Args @@ -1594,7 +1594,7 @@ func ServeApi(job *engine.Job) engine.Status { for _, protoAddr := range protoAddrs { protoAddrParts := strings.SplitN(protoAddr, "://", 2) if len(protoAddrParts) != 2 { - return job.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) + return fmt.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) } go func() { log.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1]) @@ -1618,9 +1618,9 @@ func ServeApi(job *engine.Job) engine.Status { for i := 0; i < len(protoAddrs); i++ { err := <-chErrors if err != nil { - return job.Error(err) + return err } } - return engine.StatusOK + return nil } diff --git a/api/server/server_linux.go b/api/server/server_linux.go index fff803dda..972f5ff74 100644 --- a/api/server/server_linux.go +++ b/api/server/server_linux.go @@ -90,7 +90,7 @@ func serveFd(addr string, job *engine.Job) error { } // Called through eng.Job("acceptconnections") -func AcceptConnections(job *engine.Job) engine.Status { +func AcceptConnections(job *engine.Job) error { // Tell the init daemon we are accepting requests go systemd.SdNotify("READY=1") @@ -99,5 +99,5 @@ func AcceptConnections(job *engine.Job) engine.Status { close(activationLock) } - return engine.StatusOK + return nil } diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index b5ec7c896..0501bea36 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -63,7 +63,7 @@ func TesthttpError(t *testing.T) { func TestGetVersion(t *testing.T) { eng := engine.New() var called bool - eng.Register("version", func(job *engine.Job) engine.Status { + eng.Register("version", func(job *engine.Job) error { called = true v := &engine.Env{} v.SetJson("Version", "42.1") @@ -72,9 +72,9 @@ func TestGetVersion(t *testing.T) { v.Set("Os", "Linux") v.Set("Arch", "x86_64") if _, err := v.WriteTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil }) r := serveRequest("GET", "/version", nil, eng, t) if !called { @@ -92,15 +92,15 @@ func TestGetVersion(t *testing.T) { func TestGetInfo(t *testing.T) { eng := engine.New() var called bool - eng.Register("info", func(job *engine.Job) engine.Status { + eng.Register("info", func(job *engine.Job) error { 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 err } - return engine.StatusOK + return nil }) r := serveRequest("GET", "/info", nil, eng, t) if !called { @@ -119,13 +119,13 @@ func TestGetInfo(t *testing.T) { func TestGetImagesJSON(t *testing.T) { eng := engine.New() var called bool - eng.Register("images", func(job *engine.Job) engine.Status { + eng.Register("images", func(job *engine.Job) error { called = true v := createEnvFromGetImagesJSONStruct(sampleImage) if _, err := v.WriteTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil }) r := serveRequest("GET", "/images/json", nil, eng, t) if !called { @@ -145,9 +145,9 @@ func TestGetImagesJSON(t *testing.T) { func TestGetImagesJSONFilter(t *testing.T) { eng := engine.New() filter := "nothing" - eng.Register("images", func(job *engine.Job) engine.Status { + eng.Register("images", func(job *engine.Job) error { filter = job.Getenv("filter") - return engine.StatusOK + return nil }) serveRequest("GET", "/images/json?filter=aaaa", nil, eng, t) if filter != "aaaa" { @@ -158,9 +158,9 @@ func TestGetImagesJSONFilter(t *testing.T) { func TestGetImagesJSONFilters(t *testing.T) { eng := engine.New() filter := "nothing" - eng.Register("images", func(job *engine.Job) engine.Status { + eng.Register("images", func(job *engine.Job) error { filter = job.Getenv("filters") - return engine.StatusOK + return nil }) serveRequest("GET", "/images/json?filters=nnnn", nil, eng, t) if filter != "nnnn" { @@ -171,9 +171,9 @@ func TestGetImagesJSONFilters(t *testing.T) { func TestGetImagesJSONAll(t *testing.T) { eng := engine.New() allFilter := "-1" - eng.Register("images", func(job *engine.Job) engine.Status { + eng.Register("images", func(job *engine.Job) error { allFilter = job.Getenv("all") - return engine.StatusOK + return nil }) serveRequest("GET", "/images/json?all=1", nil, eng, t) if allFilter != "1" { @@ -184,14 +184,14 @@ func TestGetImagesJSONAll(t *testing.T) { func TestGetImagesJSONLegacyFormat(t *testing.T) { eng := engine.New() var called bool - eng.Register("images", func(job *engine.Job) engine.Status { + eng.Register("images", func(job *engine.Job) error { called = true outsLegacy := engine.NewTable("Created", 0) outsLegacy.Add(createEnvFromGetImagesJSONStruct(sampleImage)) if _, err := outsLegacy.WriteListTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil }) r := serveRequestUsingVersion("GET", "/images/json", "1.6", nil, eng, t) if !called { @@ -219,7 +219,7 @@ func TestGetContainersByName(t *testing.T) { eng := engine.New() name := "container_name" var called bool - eng.Register("container_inspect", func(job *engine.Job) engine.Status { + eng.Register("container_inspect", func(job *engine.Job) error { called = true if job.Args[0] != name { t.Errorf("name != '%s': %#v", name, job.Args[0]) @@ -232,9 +232,9 @@ func TestGetContainersByName(t *testing.T) { v := &engine.Env{} v.SetBool("dirty", true) if _, err := v.WriteTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil }) r := serveRequest("GET", "/containers/"+name+"/json", nil, eng, t) if !called { @@ -253,7 +253,7 @@ func TestGetContainersByName(t *testing.T) { func TestGetEvents(t *testing.T) { eng := engine.New() var called bool - eng.Register("events", func(job *engine.Job) engine.Status { + eng.Register("events", func(job *engine.Job) error { called = true since := job.Getenv("since") if since != "1" { @@ -267,9 +267,9 @@ func TestGetEvents(t *testing.T) { v.Set("since", since) v.Set("until", until) if _, err := v.WriteTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil }) r := serveRequest("GET", "/events?since=1&until=0", nil, eng, t) if !called { @@ -295,7 +295,7 @@ func TestLogs(t *testing.T) { eng := engine.New() var inspect bool var logs bool - eng.Register("container_inspect", func(job *engine.Job) engine.Status { + eng.Register("container_inspect", func(job *engine.Job) error { inspect = true if len(job.Args) == 0 { t.Fatal("Job arguments is empty") @@ -303,10 +303,10 @@ func TestLogs(t *testing.T) { if job.Args[0] != "test" { t.Fatalf("Container name %s, must be test", job.Args[0]) } - return engine.StatusOK + return nil }) expected := "logs" - eng.Register("logs", func(job *engine.Job) engine.Status { + eng.Register("logs", func(job *engine.Job) error { logs = true if len(job.Args) == 0 { t.Fatal("Job arguments is empty") @@ -331,7 +331,7 @@ func TestLogs(t *testing.T) { t.Fatalf("timestamps %s, must be 1", timestamps) } job.Stdout.Write([]byte(expected)) - return engine.StatusOK + return nil }) r := serveRequest("GET", "/containers/test/logs?follow=1&stdout=1×tamps=1", nil, eng, t) if r.Code != http.StatusOK { @@ -353,7 +353,7 @@ func TestLogsNoStreams(t *testing.T) { eng := engine.New() var inspect bool var logs bool - eng.Register("container_inspect", func(job *engine.Job) engine.Status { + eng.Register("container_inspect", func(job *engine.Job) error { inspect = true if len(job.Args) == 0 { t.Fatal("Job arguments is empty") @@ -361,11 +361,11 @@ func TestLogsNoStreams(t *testing.T) { if job.Args[0] != "test" { t.Fatalf("Container name %s, must be test", job.Args[0]) } - return engine.StatusOK + return nil }) - eng.Register("logs", func(job *engine.Job) engine.Status { + eng.Register("logs", func(job *engine.Job) error { logs = true - return engine.StatusOK + return nil }) r := serveRequest("GET", "/containers/test/logs", nil, eng, t) if r.Code != http.StatusBadRequest { @@ -388,7 +388,7 @@ func TestGetImagesHistory(t *testing.T) { eng := engine.New() imageName := "docker-test-image" var called bool - eng.Register("history", func(job *engine.Job) engine.Status { + eng.Register("history", func(job *engine.Job) error { called = true if len(job.Args) == 0 { t.Fatal("Job arguments is empty") @@ -398,9 +398,9 @@ func TestGetImagesHistory(t *testing.T) { } v := &engine.Env{} if _, err := v.WriteTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil }) r := serveRequest("GET", "/images/"+imageName+"/history", nil, eng, t) if !called { @@ -418,7 +418,7 @@ func TestGetImagesByName(t *testing.T) { eng := engine.New() name := "image_name" var called bool - eng.Register("image_inspect", func(job *engine.Job) engine.Status { + eng.Register("image_inspect", func(job *engine.Job) error { called = true if job.Args[0] != name { t.Fatalf("name != '%s': %#v", name, job.Args[0]) @@ -431,9 +431,9 @@ func TestGetImagesByName(t *testing.T) { v := &engine.Env{} v.SetBool("dirty", true) if _, err := v.WriteTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil }) r := serveRequest("GET", "/images/"+name+"/json", nil, eng, t) if !called { @@ -455,7 +455,7 @@ func TestDeleteContainers(t *testing.T) { eng := engine.New() name := "foo" var called bool - eng.Register("rm", func(job *engine.Job) engine.Status { + eng.Register("rm", func(job *engine.Job) error { called = true if len(job.Args) == 0 { t.Fatalf("Job arguments is empty") @@ -463,7 +463,7 @@ func TestDeleteContainers(t *testing.T) { if job.Args[0] != name { t.Fatalf("name != '%s': %#v", name, job.Args[0]) } - return engine.StatusOK + return nil }) r := serveRequest("DELETE", "/containers/"+name, nil, eng, t) if !called { diff --git a/builder/job.go b/builder/job.go index 59df87e8c..665b268b6 100644 --- a/builder/job.go +++ b/builder/job.go @@ -3,6 +3,7 @@ package builder import ( "bytes" "encoding/json" + "fmt" "io" "io/ioutil" "os" @@ -44,9 +45,9 @@ func (b *BuilderJob) Install() { b.Engine.Register("build_config", b.CmdBuildConfig) } -func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status { +func (b *BuilderJob) CmdBuild(job *engine.Job) error { if len(job.Args) != 0 { - return job.Errorf("Usage: %s\n", job.Name) + return fmt.Errorf("Usage: %s\n", job.Name) } var ( dockerfileName = job.Getenv("dockerfile") @@ -73,11 +74,11 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status { repoName, tag = parsers.ParseRepositoryTag(repoName) if repoName != "" { if err := registry.ValidateRepositoryName(repoName); err != nil { - return job.Error(err) + return err } if len(tag) > 0 { if err := graph.ValidateTagName(tag); err != nil { - return job.Error(err) + return err } } } @@ -90,28 +91,28 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status { } root, err := ioutil.TempDir("", "docker-build-git") if err != nil { - return job.Error(err) + return err } defer os.RemoveAll(root) if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil { - return job.Errorf("Error trying to use git: %s (%s)", err, output) + return fmt.Errorf("Error trying to use git: %s (%s)", err, output) } c, err := archive.Tar(root, archive.Uncompressed) if err != nil { - return job.Error(err) + return err } context = c } else if urlutil.IsURL(remoteURL) { f, err := utils.Download(remoteURL) if err != nil { - return job.Error(err) + return err } defer f.Body.Close() dockerFile, err := ioutil.ReadAll(f.Body) if err != nil { - return job.Error(err) + return err } // When we're downloading just a Dockerfile put it in @@ -120,7 +121,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status { c, err := archive.Generate(dockerfileName, string(dockerFile)) if err != nil { - return job.Error(err) + return err } context = c } @@ -158,18 +159,18 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status { id, err := builder.Run(context) if err != nil { - return job.Error(err) + return err } if repoName != "" { b.Daemon.Repositories().Set(repoName, tag, id, true) } - return engine.StatusOK + return nil } -func (b *BuilderJob) CmdBuildConfig(job *engine.Job) engine.Status { +func (b *BuilderJob) CmdBuildConfig(job *engine.Job) error { if len(job.Args) != 0 { - return job.Errorf("Usage: %s\n", job.Name) + return fmt.Errorf("Usage: %s\n", job.Name) } var ( @@ -178,18 +179,18 @@ func (b *BuilderJob) CmdBuildConfig(job *engine.Job) engine.Status { ) if err := job.GetenvJson("config", &newConfig); err != nil { - return job.Error(err) + return err } ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) if err != nil { - return job.Error(err) + return err } // ensure that the commands are valid for _, n := range ast.Children { if !validCommitCommands[n.Value] { - return job.Errorf("%s is not a valid change command", n.Value) + return fmt.Errorf("%s is not a valid change command", n.Value) } } @@ -204,12 +205,12 @@ func (b *BuilderJob) CmdBuildConfig(job *engine.Job) engine.Status { for i, n := range ast.Children { if err := builder.dispatch(i, n); err != nil { - return job.Error(err) + return err } } if err := json.NewEncoder(job.Stdout).Encode(builder.Config); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } diff --git a/builtins/builtins.go b/builtins/builtins.go index 1bd9362c0..d87bdb87a 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -57,7 +57,7 @@ func daemon(eng *engine.Engine) error { } // builtins jobs independent of any subsystem -func dockerVersion(job *engine.Job) engine.Status { +func dockerVersion(job *engine.Job) error { v := &engine.Env{} v.SetJson("Version", dockerversion.VERSION) v.SetJson("ApiVersion", api.APIVERSION) @@ -69,7 +69,7 @@ func dockerVersion(job *engine.Job) engine.Status { v.Set("KernelVersion", kernelVersion.String()) } if _, err := v.WriteTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } diff --git a/daemon/attach.go b/daemon/attach.go index 967c86387..24d67a7c6 100644 --- a/daemon/attach.go +++ b/daemon/attach.go @@ -2,6 +2,7 @@ package daemon import ( "encoding/json" + "fmt" "io" "os" "sync" @@ -14,9 +15,9 @@ import ( "github.com/docker/docker/utils" ) -func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerAttach(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("Usage: %s CONTAINER\n", job.Name) + return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) } var ( @@ -30,7 +31,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status { container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } //logs @@ -108,7 +109,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) engine.Status { container.WaitStop(-1 * time.Second) } } - return engine.StatusOK + return nil } func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error { diff --git a/daemon/changes.go b/daemon/changes.go index faa432314..aa9baab0a 100644 --- a/daemon/changes.go +++ b/daemon/changes.go @@ -1,37 +1,39 @@ package daemon import ( + "fmt" + "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerChanges(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerChanges(job *engine.Job) error { if n := len(job.Args); n != 1 { - return job.Errorf("Usage: %s CONTAINER", job.Name) + return fmt.Errorf("Usage: %s CONTAINER", job.Name) } name := job.Args[0] - container, error := daemon.Get(name) - if error != nil { - return job.Error(error) + container, err := daemon.Get(name) + if err != nil { + return err } outs := engine.NewTable("", 0) changes, err := container.Changes() if err != nil { - return job.Error(err) + return err } for _, change := range changes { out := &engine.Env{} if err := out.Import(change); err != nil { - return job.Error(err) + return err } outs.Add(out) } if _, err := outs.WriteListTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } diff --git a/daemon/commit.go b/daemon/commit.go index f1496a414..1daf57a4f 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -3,21 +3,22 @@ package daemon import ( "bytes" "encoding/json" + "fmt" "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/runconfig" ) -func (daemon *Daemon) ContainerCommit(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerCommit(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) + return fmt.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) } name := job.Args[0] container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } var ( @@ -33,22 +34,22 @@ func (daemon *Daemon) ContainerCommit(job *engine.Job) engine.Status { buildConfigJob.Setenv("config", job.Getenv("config")) if err := buildConfigJob.Run(); err != nil { - return job.Error(err) + return err } if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil { - return job.Error(err) + return err } if err := runconfig.Merge(&newConfig, config); err != nil { - return job.Error(err) + return err } img, err := daemon.Commit(container, job.Getenv("repo"), job.Getenv("tag"), job.Getenv("comment"), job.Getenv("author"), job.GetenvBool("pause"), &newConfig) if err != nil { - return job.Error(err) + return err } job.Printf("%s\n", img.ID) - return engine.StatusOK + return nil } // Commit creates a new filesystem image from the current state of a container. diff --git a/daemon/copy.go b/daemon/copy.go index d42f450fd..aaa725263 100644 --- a/daemon/copy.go +++ b/daemon/copy.go @@ -1,14 +1,15 @@ package daemon import ( + "fmt" "io" "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerCopy(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerCopy(job *engine.Job) error { if len(job.Args) != 2 { - return job.Errorf("Usage: %s CONTAINER RESOURCE\n", job.Name) + return fmt.Errorf("Usage: %s CONTAINER RESOURCE\n", job.Name) } var ( @@ -18,17 +19,17 @@ func (daemon *Daemon) ContainerCopy(job *engine.Job) engine.Status { container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } data, err := container.Copy(resource) if err != nil { - return job.Error(err) + return err } defer data.Close() if _, err := io.Copy(job.Stdout, data); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } diff --git a/daemon/create.go b/daemon/create.go index 49bc6a7de..a038635ed 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" + log "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/image" @@ -12,36 +13,36 @@ import ( "github.com/docker/libcontainer/label" ) -func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerCreate(job *engine.Job) error { var name string if len(job.Args) == 1 { name = job.Args[0] } else if len(job.Args) > 1 { - return job.Errorf("Usage: %s", job.Name) + return fmt.Errorf("Usage: %s", job.Name) } config := runconfig.ContainerConfigFromJob(job) hostConfig := runconfig.ContainerHostConfigFromJob(job) if len(hostConfig.LxcConf) > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { - return job.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) + return fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) } if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 { - return job.Errorf("Minimum memory limit allowed is 4MB") + return fmt.Errorf("Minimum memory limit allowed is 4MB") } if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit { - job.Errorf("Your kernel does not support memory limit capabilities. Limitation discarded.\n") + log.Printf("Your kernel does not support memory limit capabilities. Limitation discarded.\n") hostConfig.Memory = 0 } if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit { - job.Errorf("Your kernel does not support swap limit capabilities. Limitation discarded.\n") + log.Printf("Your kernel does not support swap limit capabilities. Limitation discarded.\n") hostConfig.MemorySwap = -1 } if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory { - return job.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") + return fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") } if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { - return job.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") + return fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") } container, buildWarnings, err := daemon.Create(config, hostConfig, name) @@ -51,22 +52,22 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) engine.Status { if tag == "" { tag = graph.DEFAULTTAG } - return job.Errorf("No such image: %s (tag: %s)", config.Image, tag) + return fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag) } - return job.Error(err) + return err } if !container.Config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled { - job.Errorf("IPv4 forwarding is disabled.\n") + log.Printf("IPv4 forwarding is disabled.\n") } container.LogEvent("create") job.Printf("%s\n", container.ID) for _, warning := range buildWarnings { - job.Errorf("%s\n", warning) + log.Printf("%s\n", warning) } - return engine.StatusOK + return nil } // Create creates a new container from the given configuration with a given name. diff --git a/daemon/delete.go b/daemon/delete.go index d9e5b88ba..9f31d6d55 100644 --- a/daemon/delete.go +++ b/daemon/delete.go @@ -9,9 +9,9 @@ import ( "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerRm(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) + return fmt.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) } name := job.Args[0] removeVolume := job.GetenvBool("removeVolume") @@ -20,21 +20,23 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status { container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } if removeLink { name, err := GetFullContainerName(name) if err != nil { - job.Error(err) + return err + // TODO: why was just job.Error(err) without return if the function cannot continue w/o container name? + //job.Error(err) } parent, n := path.Split(name) if parent == "/" { - return job.Errorf("Conflict, cannot remove the default name of the container") + return fmt.Errorf("Conflict, cannot remove the default name of the container") } pe := daemon.ContainerGraph().Get(parent) if pe == nil { - return job.Errorf("Cannot get parent %s for name %s", parent, name) + return fmt.Errorf("Cannot get parent %s for name %s", parent, name) } parentContainer, _ := daemon.Get(pe.ID()) @@ -43,9 +45,9 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status { } if err := daemon.ContainerGraph().Delete(name); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } if container != nil { @@ -55,21 +57,21 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) engine.Status { if container.IsRunning() { if forceRemove { if err := container.Kill(); err != nil { - return job.Errorf("Could not kill running container, cannot remove - %v", err) + return fmt.Errorf("Could not kill running container, cannot remove - %v", err) } } else { - return job.Errorf("Conflict, You cannot remove a running container. Stop the container before attempting removal or use -f") + return fmt.Errorf("Conflict, You cannot remove a running container. Stop the container before attempting removal or use -f") } } if err := daemon.Rm(container); err != nil { - return job.Errorf("Cannot destroy container %s: %s", name, err) + return fmt.Errorf("Cannot destroy container %s: %s", name, err) } container.LogEvent("destroy") if removeVolume { daemon.DeleteVolumes(container.VolumePaths()) } } - return engine.StatusOK + return nil } func (daemon *Daemon) DeleteVolumes(volumeIDs map[string]struct{}) { diff --git a/daemon/exec.go b/daemon/exec.go index c2e00e08d..c7d494bb0 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -111,25 +111,25 @@ func (d *Daemon) getActiveContainer(name string) (*Container, error) { return container, nil } -func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status { +func (d *Daemon) ContainerExecCreate(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("Usage: %s [options] container command [args]", job.Name) + return fmt.Errorf("Usage: %s [options] container command [args]", job.Name) } if strings.HasPrefix(d.execDriver.Name(), lxc.DriverName) { - return job.Error(lxc.ErrExec) + return lxc.ErrExec } var name = job.Args[0] container, err := d.getActiveContainer(name) if err != nil { - return job.Error(err) + return err } config, err := runconfig.ExecConfigFromJob(job) if err != nil { - return job.Error(err) + return err } entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd) @@ -157,12 +157,12 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) engine.Status { job.Printf("%s\n", execConfig.ID) - return engine.StatusOK + return nil } -func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status { +func (d *Daemon) ContainerExecStart(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("Usage: %s [options] exec", job.Name) + return fmt.Errorf("Usage: %s [options] exec", job.Name) } var ( @@ -173,7 +173,7 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status { execConfig, err := d.getExecConfig(execName) if err != nil { - return job.Error(err) + return err } func() { @@ -185,7 +185,7 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status { execConfig.Running = true }() if err != nil { - return job.Error(err) + return err } log.Debugf("starting exec command %s in container %s", execConfig.ID, execConfig.Container.ID) @@ -236,14 +236,14 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) engine.Status { select { case err := <-attachErr: if err != nil { - return job.Errorf("attach failed with error: %s", err) + return fmt.Errorf("attach failed with error: %s", err) } break case err := <-execErr: - return job.Error(err) + return err } - return engine.StatusOK + return nil } func (d *Daemon) Exec(c *Container, execConfig *execConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { diff --git a/daemon/export.go b/daemon/export.go index 859c80f9b..b1417b932 100644 --- a/daemon/export.go +++ b/daemon/export.go @@ -1,33 +1,34 @@ package daemon import ( + "fmt" "io" "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerExport(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerExport(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("Usage: %s container_id", job.Name) + return fmt.Errorf("Usage: %s container_id", job.Name) } name := job.Args[0] container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } data, err := container.Export() if err != nil { - return job.Errorf("%s: %s", name, err) + return fmt.Errorf("%s: %s", name, err) } defer data.Close() // Stream the entire contents of the container (basically a volatile snapshot) if _, err := io.Copy(job.Stdout, data); err != nil { - return job.Errorf("%s: %s", name, err) + return fmt.Errorf("%s: %s", name, err) } // FIXME: factor job-specific LogEvent to engine.Job.Run() container.LogEvent("export") - return engine.StatusOK + return nil } diff --git a/daemon/image_delete.go b/daemon/image_delete.go index 092c1082d..1c865c58d 100644 --- a/daemon/image_delete.go +++ b/daemon/image_delete.go @@ -12,21 +12,21 @@ import ( "github.com/docker/docker/utils" ) -func (daemon *Daemon) ImageDelete(job *engine.Job) engine.Status { +func (daemon *Daemon) ImageDelete(job *engine.Job) error { if n := len(job.Args); n != 1 { - return job.Errorf("Usage: %s IMAGE", job.Name) + return fmt.Errorf("Usage: %s IMAGE", job.Name) } imgs := engine.NewTable("", 0) if err := daemon.DeleteImage(job.Eng, job.Args[0], imgs, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil { - return job.Error(err) + return err } if len(imgs.Data) == 0 { - return job.Errorf("Conflict, %s wasn't deleted", job.Args[0]) + return fmt.Errorf("Conflict, %s wasn't deleted", job.Args[0]) } if _, err := imgs.WriteListTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } // FIXME: make this private and use the job instead diff --git a/daemon/info.go b/daemon/info.go index 965c37032..91ac5c6a6 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -15,7 +15,7 @@ import ( "github.com/docker/docker/utils" ) -func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status { +func (daemon *Daemon) CmdInfo(job *engine.Job) error { images, _ := daemon.Graph().Map() var imgcount int if images == nil { @@ -54,16 +54,16 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status { cjob := job.Eng.Job("subscribers_count") env, _ := cjob.Stdout.AddEnv() if err := cjob.Run(); err != nil { - return job.Error(err) + return err } registryJob := job.Eng.Job("registry_config") registryEnv, _ := registryJob.Stdout.AddEnv() if err := registryJob.Run(); err != nil { - return job.Error(err) + return err } registryConfig := registry.ServiceConfig{} if err := registryEnv.GetJson("config", ®istryConfig); err != nil { - return job.Error(err) + return err } v := &engine.Env{} v.SetJson("ID", daemon.ID) @@ -104,7 +104,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) engine.Status { } v.SetList("Labels", daemon.Config().Labels) if _, err := v.WriteTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } diff --git a/daemon/inspect.go b/daemon/inspect.go index 08265795e..73ce2ea8e 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -8,14 +8,14 @@ import ( "github.com/docker/docker/runconfig" ) -func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerInspect(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("usage: %s NAME", job.Name) + return fmt.Errorf("usage: %s NAME", job.Name) } name := job.Args[0] container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } container.Lock() @@ -26,10 +26,10 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status { HostConfig *runconfig.HostConfig }{container, container.hostConfig}) if err != nil { - return job.Error(err) + return err } job.Stdout.Write(b) - return engine.StatusOK + return nil } out := &engine.Env{} @@ -75,25 +75,25 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) engine.Status { container.hostConfig.Links = nil if _, err := out.WriteTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } -func (daemon *Daemon) ContainerExecInspect(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerExecInspect(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("usage: %s ID", job.Name) + return fmt.Errorf("usage: %s ID", job.Name) } id := job.Args[0] eConfig, err := daemon.getExecConfig(id) if err != nil { - return job.Error(err) + return err } b, err := json.Marshal(*eConfig) if err != nil { - return job.Error(err) + return err } job.Stdout.Write(b) - return engine.StatusOK + return nil } diff --git a/daemon/kill.go b/daemon/kill.go index 84094f8fb..56bcad900 100644 --- a/daemon/kill.go +++ b/daemon/kill.go @@ -1,6 +1,7 @@ package daemon import ( + "fmt" "strconv" "strings" "syscall" @@ -13,9 +14,9 @@ import ( // If no signal is given (sig 0), then Kill with SIGKILL and wait // for the container to exit. // If a signal is given, then just send it to the container and return. -func (daemon *Daemon) ContainerKill(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerKill(job *engine.Job) error { if n := len(job.Args); n < 1 || n > 2 { - return job.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name) + return fmt.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name) } var ( name = job.Args[0] @@ -34,27 +35,27 @@ func (daemon *Daemon) ContainerKill(job *engine.Job) engine.Status { } if sig == 0 { - return job.Errorf("Invalid signal: %s", job.Args[1]) + return fmt.Errorf("Invalid signal: %s", job.Args[1]) } } container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } // If no signal is passed, or SIGKILL, perform regular Kill (SIGKILL + wait()) if sig == 0 || syscall.Signal(sig) == syscall.SIGKILL { if err := container.Kill(); err != nil { - return job.Errorf("Cannot kill container %s: %s", name, err) + return fmt.Errorf("Cannot kill container %s: %s", name, err) } container.LogEvent("kill") } else { // Otherwise, just send the requested signal if err := container.KillSig(int(sig)); err != nil { - return job.Errorf("Cannot kill container %s: %s", name, err) + return fmt.Errorf("Cannot kill container %s: %s", name, err) } // FIXME: Add event for signals } - return engine.StatusOK + return nil } diff --git a/daemon/list.go b/daemon/list.go index 130ac0537..3779cc3ec 100644 --- a/daemon/list.go +++ b/daemon/list.go @@ -20,7 +20,7 @@ func (daemon *Daemon) List() []*Container { return daemon.containers.List() } -func (daemon *Daemon) Containers(job *engine.Job) engine.Status { +func (daemon *Daemon) Containers(job *engine.Job) error { var ( foundBefore bool displayed int @@ -36,13 +36,13 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status { psFilters, err := filters.FromParam(job.Getenv("filters")) if err != nil { - return job.Error(err) + return err } if i, ok := psFilters["exited"]; ok { for _, value := range i { code, err := strconv.Atoi(value) if err != nil { - return job.Error(err) + return err } filt_exited = append(filt_exited, code) } @@ -65,14 +65,14 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status { if before != "" { beforeCont, err = daemon.Get(before) if err != nil { - return job.Error(err) + return err } } if since != "" { sinceCont, err = daemon.Get(since) if err != nil { - return job.Error(err) + return err } } @@ -170,14 +170,14 @@ func (daemon *Daemon) Containers(job *engine.Job) engine.Status { for _, container := range daemon.List() { if err := writeCont(container); err != nil { if err != errLast { - return job.Error(err) + return err } break } } outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } diff --git a/daemon/logs.go b/daemon/logs.go index 356d08c5c..14e6aa794 100644 --- a/daemon/logs.go +++ b/daemon/logs.go @@ -16,9 +16,9 @@ import ( "github.com/docker/docker/pkg/timeutils" ) -func (daemon *Daemon) ContainerLogs(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerLogs(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("Usage: %s CONTAINER\n", job.Name) + return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) } var ( @@ -32,7 +32,7 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) engine.Status { format string ) if !(stdout || stderr) { - return job.Errorf("You must choose at least one stream") + return fmt.Errorf("You must choose at least one stream") } if times { format = timeutils.RFC3339NanoFixed @@ -42,10 +42,10 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) engine.Status { } container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } if container.LogDriverType() != "json-file" { - return job.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver") + return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver") } cLog, err := container.ReadLog("json") if err != nil && os.IsNotExist(err) { @@ -83,7 +83,7 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) engine.Status { f := cLog.(*os.File) ls, err := tailfile.TailFile(f, lines) if err != nil { - return job.Error(err) + return err } tmp := bytes.NewBuffer([]byte{}) for _, l := range ls { @@ -148,5 +148,5 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) engine.Status { } } - return engine.StatusOK + return nil } diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index f0fa772bd..61237ebe9 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -83,7 +83,7 @@ var ( ipAllocator = ipallocator.New() ) -func InitDriver(job *engine.Job) engine.Status { +func InitDriver(job *engine.Job) error { var ( networkv4 *net.IPNet networkv6 *net.IPNet @@ -117,17 +117,17 @@ func InitDriver(job *engine.Job) engine.Status { // No Bridge existent, create one // If we're not using the default bridge, fail without trying to create it if !usingDefaultBridge { - return job.Error(err) + return err } // If the iface is not found, try to create it if err := configureBridge(bridgeIP, bridgeIPv6, enableIPv6); err != nil { - return job.Error(err) + return err } addrv4, addrsv6, err = networkdriver.GetIfaceAddr(bridgeIface) if err != nil { - return job.Error(err) + return err } if fixedCIDRv6 != "" { @@ -144,10 +144,10 @@ func InitDriver(job *engine.Job) engine.Status { networkv4 = addrv4.(*net.IPNet) bip, _, err := net.ParseCIDR(bridgeIP) if err != nil { - return job.Error(err) + return err } if !networkv4.IP.Equal(bip) { - return job.Errorf("Bridge ip (%s) does not match existing bridge configuration %s", networkv4.IP, bip) + return fmt.Errorf("Bridge ip (%s) does not match existing bridge configuration %s", networkv4.IP, bip) } } @@ -157,12 +157,12 @@ func InitDriver(job *engine.Job) engine.Status { // the bridge init for IPv6 here, else we will error out below if --ipv6=true if len(addrsv6) == 0 && enableIPv6 { if err := setupIPv6Bridge(bridgeIPv6); err != nil { - return job.Error(err) + return err } // Recheck addresses now that IPv6 is setup on the bridge addrv4, addrsv6, err = networkdriver.GetIfaceAddr(bridgeIface) if err != nil { - return job.Error(err) + return err } } @@ -172,7 +172,7 @@ func InitDriver(job *engine.Job) engine.Status { if enableIPv6 { bip6, _, err := net.ParseCIDR(bridgeIPv6) if err != nil { - return job.Error(err) + return err } found := false for _, addrv6 := range addrsv6 { @@ -183,7 +183,7 @@ func InitDriver(job *engine.Job) engine.Status { } } if !found { - return job.Errorf("Bridge IPv6 does not match existing bridge configuration %s", bip6) + return fmt.Errorf("Bridge IPv6 does not match existing bridge configuration %s", bip6) } } @@ -191,7 +191,7 @@ func InitDriver(job *engine.Job) engine.Status { if enableIPv6 { if len(addrsv6) == 0 { - return job.Error(errors.New("IPv6 enabled but no IPv6 detected")) + return errors.New("IPv6 enabled but no IPv6 detected") } bridgeIPv6Addr = networkv6.IP } @@ -199,7 +199,7 @@ func InitDriver(job *engine.Job) engine.Status { // Configure iptables for link support if enableIPTables { if err := setupIPTables(addrv4, icc, ipMasq); err != nil { - return job.Error(err) + return err } } @@ -207,33 +207,33 @@ func InitDriver(job *engine.Job) engine.Status { if ipForward { // Enable IPv4 forwarding if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil { - job.Logf("WARNING: unable to enable IPv4 forwarding: %s\n", err) + log.Warnf("WARNING: unable to enable IPv4 forwarding: %s\n", err) } if fixedCIDRv6 != "" { // Enable IPv6 forwarding if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/default/forwarding", []byte{'1', '\n'}, 0644); err != nil { - job.Logf("WARNING: unable to enable IPv6 default forwarding: %s\n", err) + log.Warnf("WARNING: unable to enable IPv6 default forwarding: %s\n", err) } if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/all/forwarding", []byte{'1', '\n'}, 0644); err != nil { - job.Logf("WARNING: unable to enable IPv6 all forwarding: %s\n", err) + log.Warnf("WARNING: unable to enable IPv6 all forwarding: %s\n", err) } } } // We can always try removing the iptables if err := iptables.RemoveExistingChain("DOCKER", iptables.Nat); err != nil { - return job.Error(err) + return err } if enableIPTables { _, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Nat) if err != nil { - return job.Error(err) + return err } chain, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter) if err != nil { - return job.Error(err) + return err } portmapper.SetIptablesChain(chain) } @@ -242,22 +242,22 @@ func InitDriver(job *engine.Job) engine.Status { if fixedCIDR != "" { _, subnet, err := net.ParseCIDR(fixedCIDR) if err != nil { - return job.Error(err) + return err } log.Debugf("Subnet: %v", subnet) if err := ipAllocator.RegisterSubnet(bridgeIPv4Network, subnet); err != nil { - return job.Error(err) + return err } } if fixedCIDRv6 != "" { _, subnet, err := net.ParseCIDR(fixedCIDRv6) if err != nil { - return job.Error(err) + return err } log.Debugf("Subnet: %v", subnet) if err := ipAllocator.RegisterSubnet(subnet, subnet); err != nil { - return job.Error(err) + return err } globalIPv6Network = subnet } @@ -275,10 +275,10 @@ func InitDriver(job *engine.Job) engine.Status { "link": LinkContainers, } { if err := job.Eng.Register(name, f); err != nil { - return job.Error(err) + return err } } - return engine.StatusOK + return nil } func setupIPTables(addr net.Addr, icc, ipmasq bool) error { @@ -499,7 +499,7 @@ func linkLocalIPv6FromMac(mac string) (string, error) { } // Allocate a network interface -func Allocate(job *engine.Job) engine.Status { +func Allocate(job *engine.Job) error { var ( ip net.IP mac net.HardwareAddr @@ -512,7 +512,7 @@ func Allocate(job *engine.Job) engine.Status { ip, err = ipAllocator.RequestIP(bridgeIPv4Network, requestedIP) if err != nil { - return job.Error(err) + return err } // If no explicit mac address was given, generate a random one. @@ -534,7 +534,7 @@ func Allocate(job *engine.Job) engine.Status { globalIPv6, err = ipAllocator.RequestIP(globalIPv6Network, requestedIPv6) if err != nil { log.Errorf("Allocator: RequestIP v6: %v", err) - return job.Error(err) + return err } log.Infof("Allocated IPv6 %s", globalIPv6) } @@ -552,7 +552,7 @@ func Allocate(job *engine.Job) engine.Status { // If linklocal IPv6 localIPv6Net, err := linkLocalIPv6FromMac(mac.String()) if err != nil { - return job.Error(err) + return err } localIPv6, _, _ := net.ParseCIDR(localIPv6Net) out.Set("LinkLocalIPv6", localIPv6.String()) @@ -572,18 +572,18 @@ func Allocate(job *engine.Job) engine.Status { out.WriteTo(job.Stdout) - return engine.StatusOK + return nil } // Release an interface for a select ip -func Release(job *engine.Job) engine.Status { +func Release(job *engine.Job) error { var ( id = job.Args[0] containerInterface = currentInterfaces.Get(id) ) if containerInterface == nil { - return job.Errorf("No network information to release for %s", id) + return fmt.Errorf("No network information to release for %s", id) } for _, nat := range containerInterface.PortMappings { @@ -600,11 +600,11 @@ func Release(job *engine.Job) engine.Status { log.Infof("Unable to release IPv6 %s", err) } } - return engine.StatusOK + return nil } // Allocate an external port and map it to the interface -func AllocatePort(job *engine.Job) engine.Status { +func AllocatePort(job *engine.Job) error { var ( err error @@ -620,7 +620,7 @@ func AllocatePort(job *engine.Job) engine.Status { if hostIP != "" { ip = net.ParseIP(hostIP) if ip == nil { - return job.Errorf("Bad parameter: invalid host ip %s", hostIP) + return fmt.Errorf("Bad parameter: invalid host ip %s", hostIP) } } @@ -632,7 +632,7 @@ func AllocatePort(job *engine.Job) engine.Status { case "udp": container = &net.UDPAddr{IP: network.IP, Port: containerPort} default: - return job.Errorf("unsupported address type %s", proto) + return fmt.Errorf("unsupported address type %s", proto) } // @@ -650,14 +650,14 @@ func AllocatePort(job *engine.Job) engine.Status { // There is no point in immediately retrying to map an explicitly // chosen port. if hostPort != 0 { - job.Logf("Failed to allocate and map port %d: %s", hostPort, err) + log.Warnf("Failed to allocate and map port %d: %s", hostPort, err) break } - job.Logf("Failed to allocate and map port: %s, retry: %d", err, i+1) + log.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1) } if err != nil { - return job.Error(err) + return err } network.PortMappings = append(network.PortMappings, host) @@ -672,13 +672,13 @@ func AllocatePort(job *engine.Job) engine.Status { out.SetInt("HostPort", netAddr.Port) } if _, err := out.WriteTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } -func LinkContainers(job *engine.Job) engine.Status { +func LinkContainers(job *engine.Job) error { var ( action = job.Args[0] nfAction iptables.Action @@ -696,24 +696,24 @@ func LinkContainers(job *engine.Job) engine.Status { case "-D": nfAction = iptables.Delete default: - return job.Errorf("Invalid action '%s' specified", action) + return fmt.Errorf("Invalid action '%s' specified", action) } ip1 := net.ParseIP(parentIP) if ip1 == nil { - return job.Errorf("Parent IP '%s' is invalid", parentIP) + return fmt.Errorf("Parent IP '%s' is invalid", parentIP) } ip2 := net.ParseIP(childIP) if ip2 == nil { - return job.Errorf("Child IP '%s' is invalid", childIP) + return fmt.Errorf("Child IP '%s' is invalid", childIP) } chain := iptables.Chain{Name: "DOCKER", Bridge: bridgeIface} for _, p := range ports { port := nat.Port(p) if err := chain.Link(nfAction, ip1, ip2, port.Int(), port.Proto()); !ignoreErrors && err != nil { - return job.Error(err) + return err } } - return engine.StatusOK + return nil } diff --git a/daemon/networkdriver/bridge/driver_test.go b/daemon/networkdriver/bridge/driver_test.go index 8c20dffb8..50b2ff503 100644 --- a/daemon/networkdriver/bridge/driver_test.go +++ b/daemon/networkdriver/bridge/driver_test.go @@ -60,22 +60,22 @@ func TestAllocatePortDetection(t *testing.T) { // Init driver job := eng.Job("initdriver") - if res := InitDriver(job); res != engine.StatusOK { + if res := InitDriver(job); res != nil { t.Fatal("Failed to initialize network driver") } // Allocate interface job = eng.Job("allocate_interface", "container_id") - if res := Allocate(job); res != engine.StatusOK { + if res := Allocate(job); res != nil { t.Fatal("Failed to allocate network interface") } // Allocate same port twice, expect failure on second call job = newPortAllocationJob(eng, freePort) - if res := AllocatePort(job); res != engine.StatusOK { + if res := AllocatePort(job); res != nil { t.Fatal("Failed to find a free port to allocate") } - if res := AllocatePort(job); res == engine.StatusOK { + if res := AllocatePort(job); res == nil { t.Fatal("Duplicate port allocation granted by AllocatePort") } } @@ -88,19 +88,19 @@ func TestHostnameFormatChecking(t *testing.T) { // Init driver job := eng.Job("initdriver") - if res := InitDriver(job); res != engine.StatusOK { + if res := InitDriver(job); res != nil { t.Fatal("Failed to initialize network driver") } // Allocate interface job = eng.Job("allocate_interface", "container_id") - if res := Allocate(job); res != engine.StatusOK { + if res := Allocate(job); res != nil { t.Fatal("Failed to allocate network interface") } // Allocate port with invalid HostIP, expect failure with Bad Request http status job = newPortAllocationJobWithInvalidHostIP(eng, freePort) - if res := AllocatePort(job); res == engine.StatusOK { + if res := AllocatePort(job); res == nil { t.Fatal("Failed to check invalid HostIP") } } @@ -129,11 +129,11 @@ func newInterfaceAllocation(t *testing.T, input engine.Env) (output engine.Env) <-done if input.Exists("expectFail") && input.GetBool("expectFail") { - if res == engine.StatusOK { + if res == nil { t.Fatal("Doesn't fail to allocate network interface") } } else { - if res != engine.StatusOK { + if res != nil { t.Fatal("Failed to allocate network interface") } } @@ -244,13 +244,13 @@ func TestLinkContainers(t *testing.T) { // Init driver job := eng.Job("initdriver") - if res := InitDriver(job); res != engine.StatusOK { + if res := InitDriver(job); res != nil { t.Fatal("Failed to initialize network driver") } // Allocate interface job = eng.Job("allocate_interface", "container_id") - if res := Allocate(job); res != engine.StatusOK { + if res := Allocate(job); res != nil { t.Fatal("Failed to allocate network interface") } @@ -267,7 +267,7 @@ func TestLinkContainers(t *testing.T) { t.Fatal(err) } - if res := LinkContainers(job); res != engine.StatusOK { + if res := LinkContainers(job); res != nil { t.Fatalf("LinkContainers failed") } diff --git a/daemon/pause.go b/daemon/pause.go index af943de10..448c521e4 100644 --- a/daemon/pause.go +++ b/daemon/pause.go @@ -1,37 +1,39 @@ package daemon import ( + "fmt" + "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerPause(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerPause(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("Usage: %s CONTAINER", job.Name) + return fmt.Errorf("Usage: %s CONTAINER", job.Name) } name := job.Args[0] container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } if err := container.Pause(); err != nil { - return job.Errorf("Cannot pause container %s: %s", name, err) + return fmt.Errorf("Cannot pause container %s: %s", name, err) } container.LogEvent("pause") - return engine.StatusOK + return nil } -func (daemon *Daemon) ContainerUnpause(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerUnpause(job *engine.Job) error { if n := len(job.Args); n < 1 || n > 2 { - return job.Errorf("Usage: %s CONTAINER", job.Name) + return fmt.Errorf("Usage: %s CONTAINER", job.Name) } name := job.Args[0] container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } if err := container.Unpause(); err != nil { - return job.Errorf("Cannot unpause container %s: %s", name, err) + return fmt.Errorf("Cannot unpause container %s: %s", name, err) } container.LogEvent("unpause") - return engine.StatusOK + return nil } diff --git a/daemon/rename.go b/daemon/rename.go index 6d8293f12..66e7ac080 100644 --- a/daemon/rename.go +++ b/daemon/rename.go @@ -1,17 +1,21 @@ package daemon -import "github.com/docker/docker/engine" +import ( + "fmt" -func (daemon *Daemon) ContainerRename(job *engine.Job) engine.Status { + "github.com/docker/docker/engine" +) + +func (daemon *Daemon) ContainerRename(job *engine.Job) error { if len(job.Args) != 2 { - return job.Errorf("usage: %s OLD_NAME NEW_NAME", job.Name) + return fmt.Errorf("usage: %s OLD_NAME NEW_NAME", job.Name) } oldName := job.Args[0] newName := job.Args[1] container, err := daemon.Get(oldName) if err != nil { - return job.Error(err) + return err } oldName = container.Name @@ -19,7 +23,7 @@ func (daemon *Daemon) ContainerRename(job *engine.Job) engine.Status { container.Lock() defer container.Unlock() if newName, err = daemon.reserveName(container.ID, newName); err != nil { - return job.Errorf("Error when allocating new name: %s", err) + return fmt.Errorf("Error when allocating new name: %s", err) } container.Name = newName @@ -32,13 +36,13 @@ func (daemon *Daemon) ContainerRename(job *engine.Job) engine.Status { if err := daemon.containerGraph.Delete(oldName); err != nil { undo() - return job.Errorf("Failed to delete container %q: %v", oldName, err) + return fmt.Errorf("Failed to delete container %q: %v", oldName, err) } if err := container.toDisk(); err != nil { undo() - return job.Error(err) + return err } - return engine.StatusOK + return nil } diff --git a/daemon/resize.go b/daemon/resize.go index 860f79eba..fce06753e 100644 --- a/daemon/resize.go +++ b/daemon/resize.go @@ -1,53 +1,54 @@ package daemon import ( + "fmt" "strconv" "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerResize(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerResize(job *engine.Job) error { if len(job.Args) != 3 { - return job.Errorf("Not enough arguments. Usage: %s CONTAINER HEIGHT WIDTH\n", job.Name) + return fmt.Errorf("Not enough arguments. Usage: %s CONTAINER HEIGHT WIDTH\n", job.Name) } name := job.Args[0] height, err := strconv.Atoi(job.Args[1]) if err != nil { - return job.Error(err) + return err } width, err := strconv.Atoi(job.Args[2]) if err != nil { - return job.Error(err) + return err } container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } if err := container.Resize(height, width); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } -func (daemon *Daemon) ContainerExecResize(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerExecResize(job *engine.Job) error { if len(job.Args) != 3 { - return job.Errorf("Not enough arguments. Usage: %s EXEC HEIGHT WIDTH\n", job.Name) + return fmt.Errorf("Not enough arguments. Usage: %s EXEC HEIGHT WIDTH\n", job.Name) } name := job.Args[0] height, err := strconv.Atoi(job.Args[1]) if err != nil { - return job.Error(err) + return err } width, err := strconv.Atoi(job.Args[2]) if err != nil { - return job.Error(err) + return err } execConfig, err := daemon.getExecConfig(name) if err != nil { - return job.Error(err) + return err } if err := execConfig.Resize(height, width); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } diff --git a/daemon/restart.go b/daemon/restart.go index bcde628d3..1bd2f8ca1 100644 --- a/daemon/restart.go +++ b/daemon/restart.go @@ -1,12 +1,14 @@ package daemon import ( + "fmt" + "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerRestart(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerRestart(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("Usage: %s CONTAINER\n", job.Name) + return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) } var ( name = job.Args[0] @@ -17,11 +19,11 @@ func (daemon *Daemon) ContainerRestart(job *engine.Job) engine.Status { } container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } if err := container.Restart(int(t)); err != nil { - return job.Errorf("Cannot restart container %s: %s\n", name, err) + return fmt.Errorf("Cannot restart container %s: %s\n", name, err) } container.LogEvent("restart") - return engine.StatusOK + return nil } diff --git a/daemon/start.go b/daemon/start.go index 381f09f7f..8de67b996 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -1,13 +1,15 @@ package daemon import ( + "fmt" + "github.com/docker/docker/engine" "github.com/docker/docker/runconfig" ) -func (daemon *Daemon) ContainerStart(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerStart(job *engine.Job) error { if len(job.Args) < 1 { - return job.Errorf("Usage: %s container_id", job.Name) + return fmt.Errorf("Usage: %s container_id", job.Name) } var ( name = job.Args[0] @@ -15,15 +17,15 @@ func (daemon *Daemon) ContainerStart(job *engine.Job) engine.Status { container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } if container.IsPaused() { - return job.Errorf("Cannot start a paused container, try unpause instead.") + return fmt.Errorf("Cannot start a paused container, try unpause instead.") } if container.IsRunning() { - return job.Errorf("Container already started") + return fmt.Errorf("Container already started") } // If no environment was set, then no hostconfig was passed. @@ -32,15 +34,15 @@ func (daemon *Daemon) ContainerStart(job *engine.Job) engine.Status { if len(job.Environ()) > 0 { hostConfig := runconfig.ContainerHostConfigFromJob(job) if err := daemon.setHostConfig(container, hostConfig); err != nil { - return job.Error(err) + return err } } if err := container.Start(); err != nil { container.LogEvent("die") - return job.Errorf("Cannot start container %s: %s", name, err) + return fmt.Errorf("Cannot start container %s: %s", name, err) } - return engine.StatusOK + return nil } func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.HostConfig) error { diff --git a/daemon/stats.go b/daemon/stats.go index 85d4a0855..e40788013 100644 --- a/daemon/stats.go +++ b/daemon/stats.go @@ -10,10 +10,10 @@ import ( "github.com/docker/libcontainer/cgroups" ) -func (daemon *Daemon) ContainerStats(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerStats(job *engine.Job) error { updates, err := daemon.SubscribeToContainerStats(job.Args[0]) if err != nil { - return job.Error(err) + return err } enc := json.NewEncoder(job.Stdout) for v := range updates { @@ -25,10 +25,10 @@ func (daemon *Daemon) ContainerStats(job *engine.Job) engine.Status { if err := enc.Encode(ss); err != nil { // TODO: handle the specific broken pipe daemon.UnsubscribeToContainerStats(job.Args[0], updates) - return job.Error(err) + return err } } - return engine.StatusOK + return nil } // convertToAPITypes converts the libcontainer.Stats to the api specific diff --git a/daemon/stop.go b/daemon/stop.go index e2f1d284a..871683be9 100644 --- a/daemon/stop.go +++ b/daemon/stop.go @@ -1,12 +1,14 @@ package daemon import ( + "fmt" + "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerStop(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerStop(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("Usage: %s CONTAINER\n", job.Name) + return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) } var ( name = job.Args[0] @@ -17,14 +19,14 @@ func (daemon *Daemon) ContainerStop(job *engine.Job) engine.Status { } container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } if !container.IsRunning() { - return job.Errorf("Container already stopped") + return fmt.Errorf("Container already stopped") } if err := container.Stop(int(t)); err != nil { - return job.Errorf("Cannot stop container %s: %s\n", name, err) + return fmt.Errorf("Cannot stop container %s: %s\n", name, err) } container.LogEvent("stop") - return engine.StatusOK + return nil } diff --git a/daemon/top.go b/daemon/top.go index 782cc83dc..1e8c39987 100644 --- a/daemon/top.go +++ b/daemon/top.go @@ -1,6 +1,7 @@ package daemon import ( + "fmt" "os/exec" "strconv" "strings" @@ -8,9 +9,9 @@ import ( "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerTop(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerTop(job *engine.Job) error { if len(job.Args) != 1 && len(job.Args) != 2 { - return job.Errorf("Not enough arguments. Usage: %s CONTAINER [PS_ARGS]\n", job.Name) + return fmt.Errorf("Not enough arguments. Usage: %s CONTAINER [PS_ARGS]\n", job.Name) } var ( name = job.Args[0] @@ -23,18 +24,18 @@ func (daemon *Daemon) ContainerTop(job *engine.Job) engine.Status { container, err := daemon.Get(name) if err != nil { - return job.Error(err) + return err } if !container.IsRunning() { - return job.Errorf("Container %s is not running", name) + return fmt.Errorf("Container %s is not running", name) } pids, err := daemon.ExecutionDriver().GetPidsForContainer(container.ID) if err != nil { - return job.Error(err) + return err } output, err := exec.Command("ps", strings.Split(psArgs, " ")...).Output() if err != nil { - return job.Errorf("Error running ps: %s", err) + return fmt.Errorf("Error running ps: %s", err) } lines := strings.Split(string(output), "\n") @@ -49,7 +50,7 @@ func (daemon *Daemon) ContainerTop(job *engine.Job) engine.Status { } } if pidIndex == -1 { - return job.Errorf("Couldn't find PID field in ps output") + return fmt.Errorf("Couldn't find PID field in ps output") } processes := [][]string{} @@ -60,7 +61,7 @@ func (daemon *Daemon) ContainerTop(job *engine.Job) engine.Status { fields := strings.Fields(line) p, err := strconv.Atoi(fields[pidIndex]) if err != nil { - return job.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) + return fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) } for _, pid := range pids { @@ -75,5 +76,5 @@ func (daemon *Daemon) ContainerTop(job *engine.Job) engine.Status { } out.SetJson("Processes", processes) out.WriteTo(job.Stdout) - return engine.StatusOK + return nil } diff --git a/daemon/wait.go b/daemon/wait.go index 7579467a0..5c1f44beb 100644 --- a/daemon/wait.go +++ b/daemon/wait.go @@ -1,21 +1,22 @@ package daemon import ( + "fmt" "time" "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerWait(job *engine.Job) engine.Status { +func (daemon *Daemon) ContainerWait(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("Usage: %s", job.Name) + return fmt.Errorf("Usage: %s", job.Name) } name := job.Args[0] container, err := daemon.Get(name) if err != nil { - return job.Errorf("%s: %v", job.Name, err) + return fmt.Errorf("%s: %v", job.Name, err) } status, _ := container.WaitStop(-1 * time.Second) job.Printf("%d\n", status) - return engine.StatusOK + return nil } diff --git a/engine/engine.go b/engine/engine.go index 5155e2774..1090675df 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -21,7 +21,7 @@ type Installer interface { Install(*Engine) error } -type Handler func(*Job) Status +type Handler func(*Job) error var globalHandlers map[string]Handler @@ -84,11 +84,11 @@ func New() *Engine { Stdin: os.Stdin, Logging: true, } - eng.Register("commands", func(job *Job) Status { + eng.Register("commands", func(job *Job) error { for _, name := range eng.commands() { job.Printf("%s\n", name) } - return StatusOK + return nil }) // Copy existing global handlers for k, v := range globalHandlers { diff --git a/engine/engine_test.go b/engine/engine_test.go index 96c3f0df3..a6ff62c8b 100644 --- a/engine/engine_test.go +++ b/engine/engine_test.go @@ -45,9 +45,9 @@ func TestJob(t *testing.T) { t.Fatalf("job1.handler should be empty") } - h := func(j *Job) Status { + h := func(j *Job) error { j.Printf("%s\n", j.Name) - return 42 + return nil } eng.Register("dummy2", h) @@ -58,7 +58,7 @@ func TestJob(t *testing.T) { t.Fatalf("job2.handler shouldn't be nil") } - if job2.handler(job2) != 42 { + if job2.handler(job2) != nil { t.Fatalf("handler dummy2 was not found in job2") } } @@ -76,7 +76,7 @@ func TestEngineShutdown(t *testing.T) { func TestEngineCommands(t *testing.T) { eng := New() - handler := func(job *Job) Status { return StatusOK } + handler := func(job *Job) error { return nil } eng.Register("foo", handler) eng.Register("bar", handler) eng.Register("echo", handler) @@ -105,9 +105,9 @@ func TestParseJob(t *testing.T) { eng := New() // Verify that the resulting job calls to the right place var called bool - eng.Register("echo", func(job *Job) Status { + eng.Register("echo", func(job *Job) error { called = true - return StatusOK + return nil }) input := "echo DEBUG=1 hello world VERBOSITY=42" job, err := eng.ParseJob(input) @@ -140,9 +140,9 @@ func TestParseJob(t *testing.T) { func TestCatchallEmptyName(t *testing.T) { eng := New() var called bool - eng.RegisterCatchall(func(job *Job) Status { + eng.RegisterCatchall(func(job *Job) error { called = true - return StatusOK + return nil }) err := eng.Job("").Run() if err == nil { @@ -164,7 +164,7 @@ func TestNestedJobSharedOutput(t *testing.T) { wrapOutput bool ) - outerHandler = func(job *Job) Status { + outerHandler = func(job *Job) error { job.Stdout.Write([]byte("outer1")) innerJob := job.Eng.Job("innerJob") @@ -184,13 +184,13 @@ func TestNestedJobSharedOutput(t *testing.T) { // closed output. job.Stdout.Write([]byte(" outer2")) - return StatusOK + return nil } - innerHandler = func(job *Job) Status { + innerHandler = func(job *Job) error { job.Stdout.Write([]byte(" inner")) - return StatusOK + return nil } eng := New() diff --git a/engine/job.go b/engine/job.go index ecb68c3eb..d882d9c9d 100644 --- a/engine/job.go +++ b/engine/job.go @@ -32,7 +32,7 @@ type Job struct { Stderr *Output Stdin *Input handler Handler - status Status + err error end time.Time closeIO bool @@ -43,17 +43,8 @@ type Job struct { cancelOnce sync.Once } -type Status int - -const ( - StatusOK Status = 0 - StatusErr Status = 1 - StatusNotFound Status = 127 -) - // Run executes the job and blocks until the job completes. -// If the job returns a failure status, an error is returned -// which includes the status. +// If the job fails it returns an error func (job *Job) Run() error { if job.Eng.IsShutdown() && !job.GetenvBool("overrideShutdown") { return fmt.Errorf("engine is shutdown") @@ -78,16 +69,16 @@ func (job *Job) Run() error { if job.Eng.Logging { log.Infof("+job %s", job.CallString()) defer func() { - log.Infof("-job %s%s", job.CallString(), job.StatusString()) + // what if err is nil? + log.Infof("-job %s%s", job.CallString(), job.err) }() } var errorMessage = bytes.NewBuffer(nil) job.Stderr.Add(errorMessage) if job.handler == nil { - job.Errorf("%s: command not found", job.Name) - job.status = 127 + job.err = fmt.Errorf("%s: command not found", job.Name) } else { - job.status = job.handler(job) + job.err = job.handler(job) job.end = time.Now() } if job.closeIO { @@ -102,36 +93,14 @@ func (job *Job) Run() error { return err } } - if job.status != 0 { - return fmt.Errorf("%s", Tail(errorMessage, 1)) - } - return nil + return job.err } func (job *Job) CallString() string { return fmt.Sprintf("%s(%s)", job.Name, strings.Join(job.Args, ", ")) } -func (job *Job) StatusString() string { - // If the job hasn't completed, status string is empty - if job.end.IsZero() { - return "" - } - var okerr string - if job.status == StatusOK { - okerr = "OK" - } else { - okerr = "ERR" - } - return fmt.Sprintf(" = %s (%d)", okerr, job.status) -} - -// String returns a human-readable description of `job` -func (job *Job) String() string { - return fmt.Sprintf("%s.%s%s", job.Eng, job.CallString(), job.StatusString()) -} - func (job *Job) Env() *Env { return job.env } @@ -235,23 +204,6 @@ func (job *Job) Printf(format string, args ...interface{}) (n int, err error) { return fmt.Fprintf(job.Stdout, format, args...) } -func (job *Job) Errorf(format string, args ...interface{}) Status { - if format[len(format)-1] != '\n' { - format = format + "\n" - } - fmt.Fprintf(job.Stderr, format, args...) - return StatusErr -} - -func (job *Job) Error(err error) Status { - fmt.Fprintf(job.Stderr, "%s\n", err) - return StatusErr -} - -func (job *Job) StatusCode() int { - return int(job.status) -} - func (job *Job) SetCloseIO(val bool) { job.closeIO = val } diff --git a/engine/job_test.go b/engine/job_test.go index 9f8c76095..76135e6e6 100644 --- a/engine/job_test.go +++ b/engine/job_test.go @@ -2,43 +2,35 @@ package engine import ( "bytes" + "errors" "fmt" "testing" ) -func TestJobStatusOK(t *testing.T) { +func TestJobOK(t *testing.T) { eng := New() - eng.Register("return_ok", func(job *Job) Status { return StatusOK }) + eng.Register("return_ok", func(job *Job) error { return nil }) err := eng.Job("return_ok").Run() if err != nil { t.Fatalf("Expected: err=%v\nReceived: err=%v", nil, err) } } -func TestJobStatusErr(t *testing.T) { +func TestJobErr(t *testing.T) { eng := New() - eng.Register("return_err", func(job *Job) Status { return StatusErr }) + eng.Register("return_err", func(job *Job) error { return errors.New("return_err") }) err := eng.Job("return_err").Run() if err == nil { - t.Fatalf("When a job returns StatusErr, Run() should return an error") - } -} - -func TestJobStatusNotFound(t *testing.T) { - eng := New() - eng.Register("return_not_found", func(job *Job) Status { return StatusNotFound }) - err := eng.Job("return_not_found").Run() - if err == nil { - t.Fatalf("When a job returns StatusNotFound, Run() should return an error") + t.Fatalf("When a job returns error, Run() should return an error") } } func TestJobStdoutString(t *testing.T) { eng := New() // FIXME: test multiple combinations of output and status - eng.Register("say_something_in_stdout", func(job *Job) Status { + eng.Register("say_something_in_stdout", func(job *Job) error { job.Printf("Hello world\n") - return StatusOK + return nil }) job := eng.Job("say_something_in_stdout") @@ -53,23 +45,3 @@ func TestJobStdoutString(t *testing.T) { t.Fatalf("Stdout last line:\nExpected: %v\nReceived: %v", expectedOutput, output) } } - -func TestJobStderrString(t *testing.T) { - eng := New() - // FIXME: test multiple combinations of output and status - eng.Register("say_something_in_stderr", func(job *Job) Status { - job.Errorf("Something might happen\nHere it comes!\nOh no...\nSomething happened\n") - return StatusOK - }) - - job := eng.Job("say_something_in_stderr") - var outputBuffer = bytes.NewBuffer(nil) - job.Stderr.Add(outputBuffer) - if err := job.Run(); err != nil { - t.Fatal(err) - } - var output = Tail(outputBuffer, 1) - if expectedOutput := "Something happened"; output != expectedOutput { - t.Fatalf("Stderr last line:\nExpected: %v\nReceived: %v", expectedOutput, output) - } -} diff --git a/engine/shutdown_test.go b/engine/shutdown_test.go index 13d804926..cde177e39 100644 --- a/engine/shutdown_test.go +++ b/engine/shutdown_test.go @@ -19,9 +19,9 @@ func TestShutdownEmpty(t *testing.T) { func TestShutdownAfterRun(t *testing.T) { eng := New() var called bool - eng.Register("foo", func(job *Job) Status { + eng.Register("foo", func(job *Job) error { called = true - return StatusOK + return nil }) if err := eng.Job("foo").Run(); err != nil { t.Fatal(err) @@ -42,10 +42,10 @@ func TestShutdownDuringRun(t *testing.T) { ) eng := New() var completed bool - eng.Register("foo", func(job *Job) Status { + eng.Register("foo", func(job *Job) error { time.Sleep(jobDelay) completed = true - return StatusOK + return nil }) go eng.Job("foo").Run() time.Sleep(50 * time.Millisecond) diff --git a/events/events.go b/events/events.go index 559bf687e..a093f359b 100644 --- a/events/events.go +++ b/events/events.go @@ -3,6 +3,7 @@ package events import ( "bytes" "encoding/json" + "fmt" "io" "strings" "sync" @@ -45,7 +46,7 @@ func (e *Events) Install(eng *engine.Engine) error { return nil } -func (e *Events) Get(job *engine.Job) engine.Status { +func (e *Events) Get(job *engine.Job) error { var ( since = job.GetenvInt64("since") until = job.GetenvInt64("until") @@ -54,7 +55,7 @@ func (e *Events) Get(job *engine.Job) engine.Status { eventFilters, err := filters.FromParam(job.Getenv("filters")) if err != nil { - return job.Error(err) + return err } // If no until, disable timeout @@ -71,7 +72,7 @@ func (e *Events) Get(job *engine.Job) engine.Status { // Resend every event in the [since, until] time interval. if since != 0 { if err := e.writeCurrent(job, since, until, eventFilters); err != nil { - return job.Error(err) + return err } } @@ -79,31 +80,31 @@ func (e *Events) Get(job *engine.Job) engine.Status { select { case event, ok := <-listener: if !ok { - return engine.StatusOK + return nil } if err := writeEvent(job, event, eventFilters); err != nil { - return job.Error(err) + return err } case <-timeout.C: - return engine.StatusOK + return nil } } } -func (e *Events) Log(job *engine.Job) engine.Status { +func (e *Events) Log(job *engine.Job) error { if len(job.Args) != 3 { - return job.Errorf("usage: %s ACTION ID FROM", job.Name) + return fmt.Errorf("usage: %s ACTION ID FROM", job.Name) } // not waiting for receivers go e.log(job.Args[0], job.Args[1], job.Args[2]) - return engine.StatusOK + return nil } -func (e *Events) SubscribersCount(job *engine.Job) engine.Status { +func (e *Events) SubscribersCount(job *engine.Job) error { ret := &engine.Env{} ret.SetInt("count", e.subscribersCount()) ret.WriteTo(job.Stdout) - return engine.StatusOK + return nil } func writeEvent(job *engine.Job, event *utils.JSONMessage, eventFilters filters.Args) error { diff --git a/graph/export.go b/graph/export.go index 3f7ecd3c4..a4c9278bc 100644 --- a/graph/export.go +++ b/graph/export.go @@ -2,6 +2,7 @@ package graph import ( "encoding/json" + "fmt" "io" "io/ioutil" "os" @@ -19,14 +20,14 @@ import ( // uncompressed tar ball. // name is the set of tags to export. // out is the writer where the images are written to. -func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status { +func (s *TagStore) CmdImageExport(job *engine.Job) error { if len(job.Args) < 1 { - return job.Errorf("Usage: %s IMAGE [IMAGE...]\n", job.Name) + return fmt.Errorf("Usage: %s IMAGE [IMAGE...]\n", job.Name) } // get image json tempdir, err := ioutil.TempDir("", "docker-export-") if err != nil { - return job.Error(err) + return err } defer os.RemoveAll(tempdir) @@ -48,13 +49,13 @@ func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status { for tag, id := range rootRepo { addKey(name, tag, id) if err := s.exportImage(job.Eng, id, tempdir); err != nil { - return job.Error(err) + return err } } } else { img, err := s.LookupImage(name) if err != nil { - return job.Error(err) + return err } if img != nil { @@ -67,13 +68,13 @@ func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status { addKey(repoName, repoTag, img.ID) } if err := s.exportImage(job.Eng, img.ID, tempdir); err != nil { - return job.Error(err) + return err } } else { // this must be an ID that didn't get looked up just right? if err := s.exportImage(job.Eng, name, tempdir); err != nil { - return job.Error(err) + return err } } } @@ -83,7 +84,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status { if len(rootRepoMap) > 0 { rootRepoJson, _ := json.Marshal(rootRepoMap) if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.FileMode(0644)); err != nil { - return job.Error(err) + return err } } else { log.Debugf("There were no repositories to write") @@ -91,15 +92,15 @@ func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status { fs, err := archive.Tar(tempdir, archive.Uncompressed) if err != nil { - return job.Error(err) + return err } defer fs.Close() if _, err := io.Copy(job.Stdout, fs); err != nil { - return job.Error(err) + return err } log.Debugf("End export job: %s", job.Name) - return engine.StatusOK + return nil } // FIXME: this should be a top-level function, not a class method diff --git a/graph/history.go b/graph/history.go index 7f5063e91..719cdf379 100644 --- a/graph/history.go +++ b/graph/history.go @@ -1,6 +1,7 @@ package graph import ( + "fmt" "strings" "github.com/docker/docker/engine" @@ -8,14 +9,14 @@ import ( "github.com/docker/docker/utils" ) -func (s *TagStore) CmdHistory(job *engine.Job) engine.Status { +func (s *TagStore) CmdHistory(job *engine.Job) error { if n := len(job.Args); n != 1 { - return job.Errorf("Usage: %s IMAGE", job.Name) + return fmt.Errorf("Usage: %s IMAGE", job.Name) } name := job.Args[0] foundImage, err := s.LookupImage(name) if err != nil { - return job.Error(err) + return err } lookupMap := make(map[string][]string) @@ -41,7 +42,7 @@ func (s *TagStore) CmdHistory(job *engine.Job) engine.Status { return nil }) if _, err := outs.WriteListTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } diff --git a/graph/import.go b/graph/import.go index 44b1ecbd5..2235fcf6b 100644 --- a/graph/import.go +++ b/graph/import.go @@ -3,6 +3,7 @@ package graph import ( "bytes" "encoding/json" + "fmt" "net/http" "net/url" @@ -14,9 +15,9 @@ import ( "github.com/docker/docker/utils" ) -func (s *TagStore) CmdImport(job *engine.Job) engine.Status { +func (s *TagStore) CmdImport(job *engine.Job) error { if n := len(job.Args); n != 2 && n != 3 { - return job.Errorf("Usage: %s SRC REPO [TAG]", job.Name) + return fmt.Errorf("Usage: %s SRC REPO [TAG]", job.Name) } var ( src = job.Args[0] @@ -37,7 +38,7 @@ func (s *TagStore) CmdImport(job *engine.Job) engine.Status { } else { u, err := url.Parse(src) if err != nil { - return job.Error(err) + return err } if u.Scheme == "" { u.Scheme = "http" @@ -47,7 +48,7 @@ func (s *TagStore) CmdImport(job *engine.Job) engine.Status { job.Stdout.Write(sf.FormatStatus("", "Downloading from %s", u)) resp, err = utils.Download(u.String()) if err != nil { - return job.Error(err) + return err } progressReader := progressreader.New(progressreader.Config{ In: resp.Body, @@ -69,20 +70,20 @@ func (s *TagStore) CmdImport(job *engine.Job) engine.Status { buildConfigJob.Setenv("config", job.Getenv("config")) if err := buildConfigJob.Run(); err != nil { - return job.Error(err) + return err } if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil { - return job.Error(err) + return err } img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, &newConfig) if err != nil { - return job.Error(err) + return err } // Optionally register the image at REPO/TAG if repo != "" { if err := s.Set(repo, tag, img.ID, true); err != nil { - return job.Error(err) + return err } } job.Stdout.Write(sf.FormatStatus("", img.ID)) @@ -93,5 +94,5 @@ func (s *TagStore) CmdImport(job *engine.Job) engine.Status { if err = job.Eng.Job("log", "import", logID, "").Run(); err != nil { log.Errorf("Error logging event 'import' for %s: %s", logID, err) } - return engine.StatusOK + return nil } diff --git a/graph/list.go b/graph/list.go index 9f7bccdfa..8e0d12f64 100644 --- a/graph/list.go +++ b/graph/list.go @@ -1,6 +1,7 @@ package graph import ( + "fmt" "log" "path" "strings" @@ -16,7 +17,7 @@ var acceptedImageFilterTags = map[string]struct{}{ "label": {}, } -func (s *TagStore) CmdImages(job *engine.Job) engine.Status { +func (s *TagStore) CmdImages(job *engine.Job) error { var ( allImages map[string]*image.Image err error @@ -26,11 +27,11 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status { imageFilters, err := filters.FromParam(job.Getenv("filters")) if err != nil { - return job.Error(err) + return err } for name := range imageFilters { if _, ok := acceptedImageFilterTags[name]; !ok { - return job.Errorf("Invalid filter '%s'", name) + return fmt.Errorf("Invalid filter '%s'", name) } } @@ -50,7 +51,7 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status { allImages, err = s.graph.Heads() } if err != nil { - return job.Error(err) + return err } lookup := make(map[string]*engine.Env) s.Lock() @@ -133,7 +134,7 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status { outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } diff --git a/graph/load.go b/graph/load.go index c257e9ef1..08c7bd877 100644 --- a/graph/load.go +++ b/graph/load.go @@ -18,10 +18,10 @@ import ( // Loads a set of images into the repository. This is the complementary of ImageExport. // The input stream is an uncompressed tar ball containing images and metadata. -func (s *TagStore) CmdLoad(job *engine.Job) engine.Status { +func (s *TagStore) CmdLoad(job *engine.Job) error { tmpImageDir, err := ioutil.TempDir("", "docker-import-") if err != nil { - return job.Error(err) + return err } defer os.RemoveAll(tmpImageDir) @@ -30,11 +30,11 @@ func (s *TagStore) CmdLoad(job *engine.Job) engine.Status { ) if err := os.Mkdir(repoDir, os.ModeDir); err != nil { - return job.Error(err) + return err } images, err := s.graph.Map() if err != nil { - return job.Error(err) + return err } excludes := make([]string, len(images)) i := 0 @@ -43,18 +43,18 @@ func (s *TagStore) CmdLoad(job *engine.Job) engine.Status { i++ } if err := chrootarchive.Untar(job.Stdin, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil { - return job.Error(err) + return err } dirs, err := ioutil.ReadDir(repoDir) if err != nil { - return job.Error(err) + return err } for _, d := range dirs { if d.IsDir() { if err := s.recursiveLoad(job.Eng, d.Name(), tmpImageDir); err != nil { - return job.Error(err) + return err } } } @@ -63,21 +63,21 @@ func (s *TagStore) CmdLoad(job *engine.Job) engine.Status { if err == nil { repositories := map[string]Repository{} if err := json.Unmarshal(repositoriesJson, &repositories); err != nil { - return job.Error(err) + return err } for imageName, tagMap := range repositories { for tag, address := range tagMap { if err := s.Set(imageName, tag, address, true); err != nil { - return job.Error(err) + return err } } } } else if !os.IsNotExist(err) { - return job.Error(err) + return err } - return engine.StatusOK + return nil } func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error { diff --git a/graph/load_unsupported.go b/graph/load_unsupported.go index 164e9176a..707534480 100644 --- a/graph/load_unsupported.go +++ b/graph/load_unsupported.go @@ -3,9 +3,11 @@ package graph import ( + "fmt" + "github.com/docker/docker/engine" ) -func (s *TagStore) CmdLoad(job *engine.Job) engine.Status { - return job.Errorf("CmdLoad is not supported on this platform") +func (s *TagStore) CmdLoad(job *engine.Job) error { + return fmt.Errorf("CmdLoad is not supported on this platform") } diff --git a/graph/pull.go b/graph/pull.go index 8a0f0eba0..f359bb70c 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -20,9 +20,9 @@ import ( "github.com/docker/docker/utils" ) -func (s *TagStore) CmdPull(job *engine.Job) engine.Status { +func (s *TagStore) CmdPull(job *engine.Job) error { if n := len(job.Args); n != 1 && n != 2 { - return job.Errorf("Usage: %s IMAGE [TAG|DIGEST]", job.Name) + return fmt.Errorf("Usage: %s IMAGE [TAG|DIGEST]", job.Name) } var ( @@ -36,7 +36,7 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status { // Resolve the Repository name from fqn to RepositoryInfo repoInfo, err := registry.ResolveRepositoryInfo(job, localName) if err != nil { - return job.Error(err) + return err } if len(job.Args) > 1 { @@ -52,21 +52,21 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status { // Another pull of the same repository is already taking place; just wait for it to finish job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", repoInfo.LocalName)) <-c - return engine.StatusOK + return nil } - return job.Error(err) + return err } defer s.poolRemove("pull", utils.ImageReference(repoInfo.LocalName, tag)) log.Debugf("pulling image from host %q with remote name %q", repoInfo.Index.Name, repoInfo.RemoteName) endpoint, err := repoInfo.GetEndpoint() if err != nil { - return job.Error(err) + return err } r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, true) if err != nil { - return job.Error(err) + return err } logName := repoInfo.LocalName @@ -87,7 +87,7 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status { if err = job.Eng.Job("log", "pull", logName, "").Run(); err != nil { log.Errorf("Error logging event 'pull' for %s: %s", logName, err) } - return engine.StatusOK + return nil } else if err != registry.ErrDoesNotExist && err != ErrV2RegistryUnavailable { log.Errorf("Error from V2 registry: %s", err) } @@ -97,14 +97,14 @@ func (s *TagStore) CmdPull(job *engine.Job) engine.Status { log.Debugf("pulling v1 repository with local name %q", repoInfo.LocalName) if err = s.pullRepository(r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err != nil { - return job.Error(err) + return err } if err = job.Eng.Job("log", "pull", logName, "").Run(); err != nil { log.Errorf("Error logging event 'pull' for %s: %s", logName, err) } - return engine.StatusOK + return nil } func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, askedTag string, sf *utils.StreamFormatter, parallel bool) error { diff --git a/graph/push.go b/graph/push.go index 6085113f8..3bbb9676d 100644 --- a/graph/push.go +++ b/graph/push.go @@ -492,9 +492,9 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint * } // FIXME: Allow to interrupt current push when new push of same image is done. -func (s *TagStore) CmdPush(job *engine.Job) engine.Status { +func (s *TagStore) CmdPush(job *engine.Job) error { if n := len(job.Args); n != 1 { - return job.Errorf("Usage: %s IMAGE", job.Name) + return fmt.Errorf("Usage: %s IMAGE", job.Name) } var ( localName = job.Args[0] @@ -506,7 +506,7 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status { // Resolve the Repository name from fqn to RepositoryInfo repoInfo, err := registry.ResolveRepositoryInfo(job, localName) if err != nil { - return job.Error(err) + return err } tag := job.Getenv("tag") @@ -514,18 +514,18 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status { job.GetenvJson("metaHeaders", &metaHeaders) if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil { - return job.Error(err) + return err } defer s.poolRemove("push", repoInfo.LocalName) endpoint, err := repoInfo.GetEndpoint() if err != nil { - return job.Error(err) + return err } r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false) if err != nil { - return job.Error(err) + return err } reposLen := 1 @@ -536,23 +536,23 @@ func (s *TagStore) CmdPush(job *engine.Job) engine.Status { // If it fails, try to get the repository localRepo, exists := s.Repositories[repoInfo.LocalName] if !exists { - return job.Errorf("Repository does not exist: %s", repoInfo.LocalName) + return fmt.Errorf("Repository does not exist: %s", repoInfo.LocalName) } if repoInfo.Index.Official || endpoint.Version == registry.APIVersion2 { err := s.pushV2Repository(r, localRepo, job.Stdout, repoInfo, tag, sf) if err == nil { - return engine.StatusOK + return nil } if err != ErrV2RegistryUnavailable { - return job.Errorf("Error pushing to registry: %s", err) + return fmt.Errorf("Error pushing to registry: %s", err) } } if err := s.pushRepository(r, job.Stdout, repoInfo, localRepo, tag, sf); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } diff --git a/graph/service.go b/graph/service.go index 350ed8cf9..03ae8f4c0 100644 --- a/graph/service.go +++ b/graph/service.go @@ -55,36 +55,36 @@ func (s *TagStore) Install(eng *engine.Engine) error { // That is a requirement of the current registry client implementation, // because a re-encoded json might invalidate the image checksum at // the next upload, even with functionaly identical content. -func (s *TagStore) CmdSet(job *engine.Job) engine.Status { +func (s *TagStore) CmdSet(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("usage: %s NAME", job.Name) + return fmt.Errorf("usage: %s NAME", job.Name) } var ( imgJSON = []byte(job.Getenv("json")) layer = job.Stdin ) if len(imgJSON) == 0 { - return job.Errorf("mandatory key 'json' is not set") + return fmt.Errorf("mandatory key 'json' is not set") } // We have to pass an *image.Image object, even though it will be completely // ignored in favor of the redundant json data. // FIXME: the current prototype of Graph.Register is stupid and redundant. img, err := image.NewImgJSON(imgJSON) if err != nil { - return job.Error(err) + return err } if err := s.graph.Register(img, layer); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } // CmdGet returns information about an image. // If the image doesn't exist, an empty object is returned, to allow // checking for an image's existence. -func (s *TagStore) CmdGet(job *engine.Job) engine.Status { +func (s *TagStore) CmdGet(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("usage: %s NAME", job.Name) + return fmt.Errorf("usage: %s NAME", job.Name) } name := job.Args[0] res := &engine.Env{} @@ -92,7 +92,7 @@ func (s *TagStore) CmdGet(job *engine.Job) engine.Status { // Note: if the image doesn't exist, LookupImage returns // nil, nil. if err != nil { - return job.Error(err) + return err } if img != nil { // We don't directly expose all fields of the Image objects, @@ -116,23 +116,23 @@ func (s *TagStore) CmdGet(job *engine.Job) engine.Status { res.SetJson("Parent", img.Parent) } res.WriteTo(job.Stdout) - return engine.StatusOK + return nil } // CmdLookup return an image encoded in JSON -func (s *TagStore) CmdLookup(job *engine.Job) engine.Status { +func (s *TagStore) CmdLookup(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("usage: %s NAME", job.Name) + return fmt.Errorf("usage: %s NAME", job.Name) } name := job.Args[0] if image, err := s.LookupImage(name); err == nil && image != nil { if job.GetenvBool("raw") { b, err := image.RawJson() if err != nil { - return job.Error(err) + return err } job.Stdout.Write(b) - return engine.StatusOK + return nil } out := &engine.Env{} @@ -150,32 +150,32 @@ func (s *TagStore) CmdLookup(job *engine.Job) engine.Status { out.SetInt64("Size", image.Size) out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size) if _, err = out.WriteTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } - return job.Errorf("No such image: %s", name) + return fmt.Errorf("No such image: %s", name) } // CmdTarLayer return the tarLayer of the image -func (s *TagStore) CmdTarLayer(job *engine.Job) engine.Status { +func (s *TagStore) CmdTarLayer(job *engine.Job) error { if len(job.Args) != 1 { - return job.Errorf("usage: %s NAME", job.Name) + return fmt.Errorf("usage: %s NAME", job.Name) } name := job.Args[0] if image, err := s.LookupImage(name); err == nil && image != nil { fs, err := image.TarLayer() if err != nil { - return job.Error(err) + return err } defer fs.Close() written, err := io.Copy(job.Stdout, fs) if err != nil { - return job.Error(err) + return err } log.Debugf("rendered layer for %s of [%d] size", image.ID, written) - return engine.StatusOK + return nil } - return job.Errorf("No such image: %s", name) + return fmt.Errorf("No such image: %s", name) } diff --git a/graph/tag.go b/graph/tag.go index b33e49d59..c0b269946 100644 --- a/graph/tag.go +++ b/graph/tag.go @@ -1,19 +1,18 @@ package graph import ( + "fmt" + "github.com/docker/docker/engine" ) -func (s *TagStore) CmdTag(job *engine.Job) engine.Status { +func (s *TagStore) CmdTag(job *engine.Job) error { if len(job.Args) != 2 && len(job.Args) != 3 { - return job.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name) + return fmt.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name) } var tag string if len(job.Args) == 3 { tag = job.Args[2] } - if err := s.Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")); err != nil { - return job.Error(err) - } - return engine.StatusOK + return s.Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")) } diff --git a/graph/viz.go b/graph/viz.go index 924c22b6a..0c45caa9e 100644 --- a/graph/viz.go +++ b/graph/viz.go @@ -1,16 +1,17 @@ package graph import ( + "fmt" "strings" "github.com/docker/docker/engine" "github.com/docker/docker/image" ) -func (s *TagStore) CmdViz(job *engine.Job) engine.Status { +func (s *TagStore) CmdViz(job *engine.Job) error { images, _ := s.graph.Map() if images == nil { - return engine.StatusOK + return nil } job.Stdout.Write([]byte("digraph docker {\n")) @@ -21,7 +22,7 @@ func (s *TagStore) CmdViz(job *engine.Job) engine.Status { for _, image := range images { parentImage, err = image.GetParent() if err != nil { - return job.Errorf("Error while getting parent image: %v", err) + return fmt.Errorf("Error while getting parent image: %v", err) } if parentImage != nil { job.Stdout.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n")) @@ -34,5 +35,5 @@ func (s *TagStore) CmdViz(job *engine.Job) engine.Status { job.Stdout.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n")) } job.Stdout.Write([]byte(" base [style=invisible]\n}\n")) - return engine.StatusOK + return nil } diff --git a/registry/service.go b/registry/service.go index 048340224..5daacb2b1 100644 --- a/registry/service.go +++ b/registry/service.go @@ -1,6 +1,8 @@ package registry import ( + "fmt" + log "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" ) @@ -38,7 +40,7 @@ func (s *Service) Install(eng *engine.Engine) error { // Auth contacts the public registry with the provided credentials, // and returns OK if authentication was sucessful. // It can be used to verify the validity of a client's credentials. -func (s *Service) Auth(job *engine.Job) engine.Status { +func (s *Service) Auth(job *engine.Job) error { var ( authConfig = new(AuthConfig) endpoint *Endpoint @@ -56,25 +58,25 @@ func (s *Service) Auth(job *engine.Job) engine.Status { } if index, err = ResolveIndexInfo(job, addr); err != nil { - return job.Error(err) + return err } if endpoint, err = NewEndpoint(index); err != nil { log.Errorf("unable to get new registry endpoint: %s", err) - return job.Error(err) + return err } authConfig.ServerAddress = endpoint.String() if status, err = Login(authConfig, endpoint, HTTPRequestFactory(nil)); err != nil { log.Errorf("unable to login against registry endpoint %s: %s", endpoint, err) - return job.Error(err) + return err } log.Infof("successful registry login for endpoint %s: %s", endpoint, status) job.Printf("%s\n", status) - return engine.StatusOK + return nil } // Search queries the public registry for images matching the specified @@ -93,9 +95,9 @@ func (s *Service) Auth(job *engine.Job) engine.Status { // Results are sent as a collection of structured messages (using engine.Table). // Each result is sent as a separate message. // Results are ordered by number of stars on the public registry. -func (s *Service) Search(job *engine.Job) engine.Status { +func (s *Service) Search(job *engine.Job) error { if n := len(job.Args); n != 1 { - return job.Errorf("Usage: %s TERM", job.Name) + return fmt.Errorf("Usage: %s TERM", job.Name) } var ( term = job.Args[0] @@ -107,20 +109,20 @@ func (s *Service) Search(job *engine.Job) engine.Status { repoInfo, err := ResolveRepositoryInfo(job, term) if err != nil { - return job.Error(err) + return err } // *TODO: Search multiple indexes. endpoint, err := repoInfo.GetEndpoint() if err != nil { - return job.Error(err) + return err } r, err := NewSession(authConfig, HTTPRequestFactory(metaHeaders), endpoint, true) if err != nil { - return job.Error(err) + return err } results, err := r.SearchRepositories(repoInfo.GetSearchTerm()) if err != nil { - return job.Error(err) + return err } outs := engine.NewTable("star_count", 0) for _, result := range results.Results { @@ -130,31 +132,31 @@ func (s *Service) Search(job *engine.Job) engine.Status { } outs.ReverseSort() if _, err := outs.WriteListTo(job.Stdout); err != nil { - return job.Error(err) + return err } - return engine.StatusOK + return nil } // ResolveRepository splits a repository name into its components // and configuration of the associated registry. -func (s *Service) ResolveRepository(job *engine.Job) engine.Status { +func (s *Service) ResolveRepository(job *engine.Job) error { var ( reposName = job.Args[0] ) repoInfo, err := s.Config.NewRepositoryInfo(reposName) if err != nil { - return job.Error(err) + return err } out := engine.Env{} err = out.SetJson("repository", repoInfo) if err != nil { - return job.Error(err) + return err } out.WriteTo(job.Stdout) - return engine.StatusOK + return nil } // Convenience wrapper for calling resolve_repository Job from a running job. @@ -175,24 +177,24 @@ func ResolveRepositoryInfo(jobContext *engine.Job, reposName string) (*Repositor } // ResolveIndex takes indexName and returns index info -func (s *Service) ResolveIndex(job *engine.Job) engine.Status { +func (s *Service) ResolveIndex(job *engine.Job) error { var ( indexName = job.Args[0] ) index, err := s.Config.NewIndexInfo(indexName) if err != nil { - return job.Error(err) + return err } out := engine.Env{} err = out.SetJson("index", index) if err != nil { - return job.Error(err) + return err } out.WriteTo(job.Stdout) - return engine.StatusOK + return nil } // Convenience wrapper for calling resolve_index Job from a running job. @@ -213,13 +215,13 @@ func ResolveIndexInfo(jobContext *engine.Job, indexName string) (*IndexInfo, err } // GetRegistryConfig returns current registry configuration. -func (s *Service) GetRegistryConfig(job *engine.Job) engine.Status { +func (s *Service) GetRegistryConfig(job *engine.Job) error { out := engine.Env{} err := out.SetJson("config", s.Config) if err != nil { - return job.Error(err) + return err } out.WriteTo(job.Stdout) - return engine.StatusOK + return nil } diff --git a/trust/service.go b/trust/service.go index 324a478f1..923537c9c 100644 --- a/trust/service.go +++ b/trust/service.go @@ -21,9 +21,9 @@ func (t *TrustStore) Install(eng *engine.Engine) error { return nil } -func (t *TrustStore) CmdCheckKey(job *engine.Job) engine.Status { +func (t *TrustStore) CmdCheckKey(job *engine.Job) error { if n := len(job.Args); n != 1 { - return job.Errorf("Usage: %s NAMESPACE", job.Name) + return fmt.Errorf("Usage: %s NAMESPACE", job.Name) } var ( namespace = job.Args[0] @@ -31,11 +31,11 @@ func (t *TrustStore) CmdCheckKey(job *engine.Job) engine.Status { ) if keyBytes == "" { - return job.Errorf("Missing PublicKey") + return fmt.Errorf("Missing PublicKey") } pk, err := libtrust.UnmarshalPublicKeyJWK([]byte(keyBytes)) if err != nil { - return job.Errorf("Error unmarshalling public key: %s", err) + return fmt.Errorf("Error unmarshalling public key: %s", err) } permission := uint16(job.GetenvInt("Permission")) @@ -47,13 +47,13 @@ func (t *TrustStore) CmdCheckKey(job *engine.Job) engine.Status { defer t.RUnlock() if t.graph == nil { job.Stdout.Write([]byte("no graph")) - return engine.StatusOK + return nil } // Check if any expired grants verified, err := t.graph.Verify(pk, namespace, permission) if err != nil { - return job.Errorf("Error verifying key to namespace: %s", namespace) + return fmt.Errorf("Error verifying key to namespace: %s", namespace) } if !verified { log.Debugf("Verification failed for %s using key %s", namespace, pk.KeyID()) @@ -64,11 +64,11 @@ func (t *TrustStore) CmdCheckKey(job *engine.Job) engine.Status { job.Stdout.Write([]byte("verified")) } - return engine.StatusOK + return nil } -func (t *TrustStore) CmdUpdateBase(job *engine.Job) engine.Status { +func (t *TrustStore) CmdUpdateBase(job *engine.Job) error { t.fetch() - return engine.StatusOK + return nil } From 1b6065de8f46edc8f36f6b3734fe64175e413dc3 Mon Sep 17 00:00:00 2001 From: Anes Hasicic Date: Wed, 25 Mar 2015 23:34:00 +0100 Subject: [PATCH 093/999] Removed redundant elses Signed-off-by: Anes Hasicic --- pkg/devicemapper/devmapper.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/devicemapper/devmapper.go b/pkg/devicemapper/devmapper.go index f3b55c853..1d45d0588 100644 --- a/pkg/devicemapper/devmapper.go +++ b/pkg/devicemapper/devmapper.go @@ -586,9 +586,10 @@ func CreateDevice(poolName string, deviceId int) error { // Caller wants to know about ErrDeviceIdExists so that it can try with a different device id. if dmSawExist { return ErrDeviceIdExists - } else { - return fmt.Errorf("Error running CreateDevice %s", err) } + + return fmt.Errorf("Error running CreateDevice %s", err) + } return nil } @@ -681,9 +682,10 @@ func CreateSnapDevice(poolName string, deviceId int, baseName string, baseDevice // Caller wants to know about ErrDeviceIdExists so that it can try with a different device id. if dmSawExist { return ErrDeviceIdExists - } else { - return fmt.Errorf("Error running DeviceCreate (createSnapDevice) %s", err) } + + return fmt.Errorf("Error running DeviceCreate (createSnapDevice) %s", err) + } if doSuspend { From 3d7b9e8f30a48bc2e929d8ac3d82f70778e99b59 Mon Sep 17 00:00:00 2001 From: Anes Hasicic Date: Wed, 25 Mar 2015 23:44:32 +0100 Subject: [PATCH 094/999] Fixed redundant else Signed-off-by: Anes Hasicic --- daemon/graphdriver/overlay/overlay.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/graphdriver/overlay/overlay.go b/daemon/graphdriver/overlay/overlay.go index afe12c509..fa5e9a2bf 100644 --- a/daemon/graphdriver/overlay/overlay.go +++ b/daemon/graphdriver/overlay/overlay.go @@ -273,10 +273,10 @@ func (d *Driver) Get(id string, mountLabel string) (string, error) { if mount != nil { mount.count++ return mount.path, nil - } else { - mount = &ActiveMount{count: 1} } + mount = &ActiveMount{count: 1} + dir := d.dir(id) if _, err := os.Stat(dir); err != nil { return "", err From e39646d2e1becb652ff0a8a7d131067f38a94247 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Wed, 25 Mar 2015 15:46:42 -0700 Subject: [PATCH 095/999] Remove unused runconfig.Config.SecurityOpt field Signed-off-by: Arnaud Porterie --- runconfig/config.go | 1 - 1 file changed, 1 deletion(-) diff --git a/runconfig/config.go b/runconfig/config.go index 3e32a1e34..45255e9b0 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -33,7 +33,6 @@ type Config struct { NetworkDisabled bool MacAddress string OnBuild []string - SecurityOpt []string Labels map[string]string } From 6fdb583f3899362db13292ad3cad31fec970d638 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 25 Mar 2015 23:47:38 +0100 Subject: [PATCH 096/999] Fix typo "WRKDIR" -> "WORKDIR" Signed-off-by: Sebastiaan van Stijn --- docs/man/Dockerfile.5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/man/Dockerfile.5.md b/docs/man/Dockerfile.5.md index 0ec54a8c9..dc28d3e03 100644 --- a/docs/man/Dockerfile.5.md +++ b/docs/man/Dockerfile.5.md @@ -283,7 +283,7 @@ A Dockerfile is similar to a Makefile. instruction can be used any number of times in a Dockerfile, and will only affect subsequent commands. -**WRKDIR** +**WORKDIR** -- `WORKDIR /path/to/workdir` The **WORKDIR** instruction sets the working directory for the **RUN**, **CMD**, **ENTRYPOINT**, **COPY** and **ADD** Dockerfile commands that follow it. It can From 5dde99163e7674814f2d9be0bc22c9618fa70b89 Mon Sep 17 00:00:00 2001 From: Jimmy Puckett Date: Wed, 25 Mar 2015 19:24:55 -0400 Subject: [PATCH 097/999] running code formatter as @tiborvass requested Signed-off-by: Jimmy Puckett --- daemon/daemon.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 52b7dd8c2..fe07e5ebc 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -445,7 +445,7 @@ func (daemon *Daemon) setupResolvconfWatcher() error { select { case event := <-watcher.Events: if event.Name == "/etc/resolv.conf" && - (event.Op & (fsnotify.Write | fsnotify.Create) != 0) { + (event.Op&(fsnotify.Write|fsnotify.Create) != 0) { // verify a real change happened before we go further--a file write may have happened // without an actual change to the file updatedResolvConf, newResolvConfHash, err := resolvconf.GetIfChanged() From 61dba98608446f10b516a677753877cc3160173f Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 25 Mar 2015 16:25:13 -0700 Subject: [PATCH 098/999] SecurityOpt parameter is singular, and belongs in HostConfig since API 1.17 Signed-off-by: Joffrey F --- docs/sources/reference/api/docker_remote_api_v1.17.md | 6 +++--- docs/sources/reference/api/docker_remote_api_v1.18.md | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api_v1.17.md b/docs/sources/reference/api/docker_remote_api_v1.17.md index 96887559c..c8b157169 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.17.md +++ b/docs/sources/reference/api/docker_remote_api_v1.17.md @@ -138,7 +138,6 @@ Create a container "ExposedPorts": { "22/tcp": {} }, - "SecurityOpts": [""], "HostConfig": { "Binds": ["/tmp:/tmp"], "Links": ["redis3:redis"], @@ -156,6 +155,7 @@ Create a container "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, "NetworkMode": "bridge", "Devices": [] + "SecurityOpt": [""], } } @@ -201,8 +201,6 @@ Json Parameters: container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` -- **SecurityOpts**: A list of string values to customize labels for MLS - systems, such as SELinux. - **HostConfig** - **Binds** – A list of volume bindings for this container. Each volume binding is a string of the form `container_path` (to create a new @@ -244,6 +242,8 @@ Json Parameters: - **Devices** - A list of devices to add to the container specified in the form `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` + - **SecurityOpt**: A list of string values to customize labels for MLS + systems, such as SELinux. Query Parameters: diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index 2197066d1..321be87e3 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -139,7 +139,6 @@ Create a container "ExposedPorts": { "22/tcp": {} }, - "SecurityOpts": [""], "HostConfig": { "Binds": ["/tmp:/tmp"], "Links": ["redis3:redis"], @@ -163,6 +162,7 @@ Create a container "Devices": [], "Ulimits": [{}], "LogConfig": { "Type": "json-file", Config: {} }, + "SecurityOpt": [""], "CgroupParent": "" } } @@ -211,8 +211,6 @@ Json Parameters: container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` -- **SecurityOpts**: A list of string values to customize labels for MLS - systems, such as SELinux. - **HostConfig** - **Binds** – A list of volume bindings for this container. Each volume binding is a string of the form `container_path` (to create a new @@ -257,6 +255,8 @@ Json Parameters: - **Ulimits** - A list of ulimits to be set in the container, specified as `{ "Name": , "Soft": , "Hard": }`, for example: `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` + - **SecurityOpt**: A list of string values to customize labels for MLS + systems, such as SELinux. - **LogConfig** - Logging configuration to container, format `{ "Type": "", "Config": {"key1": "val1"}} Available types: `json-file`, `syslog`, `none`. From cb168e5622fefc42df1fa0e355aa5922c74329e8 Mon Sep 17 00:00:00 2001 From: Allen Madsen Date: Wed, 25 Mar 2015 19:39:05 -0400 Subject: [PATCH 099/999] Fix (*Ulimit).String() function. Closes #11769. Signed-off-by: Allen Madsen --- pkg/ulimit/ulimit.go | 2 +- pkg/ulimit/ulimit_test.go | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pkg/ulimit/ulimit.go b/pkg/ulimit/ulimit.go index 2375315e3..eb2ae4e8c 100644 --- a/pkg/ulimit/ulimit.go +++ b/pkg/ulimit/ulimit.go @@ -102,5 +102,5 @@ func (u *Ulimit) GetRlimit() (*Rlimit, error) { } func (u *Ulimit) String() string { - return fmt.Sprintf("%s=%s:%s", u.Name, u.Soft, u.Hard) + return fmt.Sprintf("%s=%d:%d", u.Name, u.Soft, u.Hard) } diff --git a/pkg/ulimit/ulimit_test.go b/pkg/ulimit/ulimit_test.go index 419b5e040..593918aa3 100644 --- a/pkg/ulimit/ulimit_test.go +++ b/pkg/ulimit/ulimit_test.go @@ -39,3 +39,10 @@ func TestParseInvalidValueType(t *testing.T) { t.Fatal("expected error on bad value type") } } + +func TestStringOutput(t *testing.T) { + u := &Ulimit{"nofile", 1024, 512} + if s := u.String(); s != "nofile=512:1024" { + t.Fatal("expected String to return nofile=512:1024, but got", s) + } +} From a97ca674f06cc7e70cc6defd617afa7775a8480a Mon Sep 17 00:00:00 2001 From: Allen Madsen Date: Wed, 25 Mar 2015 19:59:40 -0400 Subject: [PATCH 100/999] Add test for successful Ulimit Parse. Signed-off-by: Allen Madsen --- pkg/ulimit/ulimit_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/ulimit/ulimit_test.go b/pkg/ulimit/ulimit_test.go index 419b5e040..271ac25ef 100644 --- a/pkg/ulimit/ulimit_test.go +++ b/pkg/ulimit/ulimit_test.go @@ -2,6 +2,13 @@ package ulimit import "testing" +func TestParseValid(t *testing.T) { + u1 := &Ulimit{"nofile", 1024, 512} + if u2, _ := Parse("nofile=512:1024"); u1 == u2 { + t.Fatalf("expected %s, but got %s", u1.String(), u2.String()) + } +} + func TestParseInvalidLimitType(t *testing.T) { if _, err := Parse("notarealtype=1024:1024"); err == nil { t.Fatalf("expected error on invalid ulimit type") From ba222f7bc89f7d7f3c83b54cb0a3567ca3694f34 Mon Sep 17 00:00:00 2001 From: xamyzhao Date: Wed, 25 Mar 2015 20:22:56 -0400 Subject: [PATCH 101/999] Updated step 5 with Windows instructions so that installation works in Windows Signed-off-by: Amy Zhao Signed-off-by: Amy Zhao --- docs/sources/project/set-up-git.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/project/set-up-git.md b/docs/sources/project/set-up-git.md index 2292d93b3..b76af6b35 100644 --- a/docs/sources/project/set-up-git.md +++ b/docs/sources/project/set-up-git.md @@ -46,7 +46,7 @@ target="_blank">docker/docker repository. that instead. You'll need to convert what you see in the guide to what is appropriate to your tool. -5. Open a terminal window on your local host and change to your home directory. +5. Open a terminal window on your local host and change to your home directory. In Windows, you'll work in your Boot2Docker window instead of Powershell or cmd. $ cd ~ From b4d7b0f8657cd29147dd3215876b9f739ead7097 Mon Sep 17 00:00:00 2001 From: Maxim Kulkin Date: Wed, 25 Mar 2015 15:47:15 -0700 Subject: [PATCH 102/999] Explain advanced contribution workflow more Explain why advanced contribution workflow have to be so complex Signed-off-by: Maxim Kulkin --- docs/sources/project/advanced-contributing.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/sources/project/advanced-contributing.md b/docs/sources/project/advanced-contributing.md index df5756d9d..0c9b5d1ce 100644 --- a/docs/sources/project/advanced-contributing.md +++ b/docs/sources/project/advanced-contributing.md @@ -137,3 +137,16 @@ The following provides greater detail on the process: 14. Acceptance and merge! +## About the Advanced process + +Docker is a large project. Our core team gets a great many design proposals. +Design proposal discussions can span days, weeks, and longer. The number of comments can reach the 100s. +In that situation, following the discussion flow and the decisions reached is crucial. + +Making a pull request with a design proposal simplifies this process: +* you can leave comments on specific design proposal line +* replies around line are easy to track +* as a proposal changes and is updated, pages reset as line items resolve +* Github maintains the entire history + +While proposals in pull requests do not end up merged into a master repository, they provide a convenient tool for managing the design process. From 0cd6c05d8112e9246b734107d54e2855e3d5fec5 Mon Sep 17 00:00:00 2001 From: bobby abbott Date: Tue, 17 Mar 2015 19:18:41 -0700 Subject: [PATCH 103/999] Fixes hacks from progressreader refactor related to #10959 Signed-off-by: bobby abbott --- api/client/build.go | 6 ++-- api/client/utils.go | 4 +-- api/server/server.go | 7 +++-- builder/evaluator.go | 3 +- builder/internals.go | 3 +- builder/job.go | 7 +++-- events/events.go | 14 ++++----- events/events_test.go | 16 +++++----- graph/graph.go | 3 +- graph/import.go | 3 +- graph/pull.go | 13 ++++---- graph/push.go | 15 ++++----- {utils => pkg/jsonmessage}/jsonmessage.go | 2 +- .../jsonmessage}/jsonmessage_test.go | 2 +- pkg/progressreader/progressreader.go | 31 +++---------------- .../streamformatter}/streamformatter.go | 30 +++++++----------- .../streamformatter}/streamformatter_test.go | 9 +++--- utils/utils.go | 3 +- 18 files changed, 77 insertions(+), 94 deletions(-) rename {utils => pkg/jsonmessage}/jsonmessage.go (99%) rename {utils => pkg/jsonmessage}/jsonmessage_test.go (97%) rename {utils => pkg/streamformatter}/streamformatter.go (72%) rename {utils => pkg/streamformatter}/streamformatter_test.go (88%) diff --git a/api/client/build.go b/api/client/build.go index 91446132a..87547fc8a 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -22,9 +22,11 @@ import ( "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/fileutils" + "github.com/docker/docker/pkg/jsonmessage" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/progressreader" + "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/units" "github.com/docker/docker/pkg/urlutil" @@ -198,7 +200,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // Setup an upload progress bar // FIXME: ProgressReader shouldn't be this annoying to use if context != nil { - sf := utils.NewStreamFormatter(false) + sf := streamformatter.NewStreamFormatter(false) body = progressreader.New(progressreader.Config{ In: context, Out: cli.out, @@ -291,7 +293,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { headers.Set("Content-Type", "application/tar") } err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), body, cli.out, headers) - if jerr, ok := err.(*utils.JSONError); ok { + if jerr, ok := err.(*jsonmessage.JSONError); ok { // If no error code is set, default to 1 if jerr.Code == 0 { jerr.Code = 1 diff --git a/api/client/utils.go b/api/client/utils.go index 103bfdec3..65ed2c7c4 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -19,11 +19,11 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/engine" + "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/term" "github.com/docker/docker/registry" - "github.com/docker/docker/utils" ) var ( @@ -164,7 +164,7 @@ func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in } if api.MatchesContentType(resp.Header.Get("Content-Type"), "application/json") { - return utils.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut) + return jsonmessage.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut) } if stdout != nil || stderr != nil { // When TTY is ON, use regular copy diff --git a/api/server/server.go b/api/server/server.go index 9c50bfb52..080d9ad10 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -32,6 +32,7 @@ import ( "github.com/docker/docker/pkg/listenbuffer" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/stdcopy" + "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/version" "github.com/docker/docker/registry" "github.com/docker/docker/utils" @@ -595,7 +596,7 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon if !job.Stdout.Used() { return err } - sf := utils.NewStreamFormatter(version.GreaterThan("1.0")) + sf := streamformatter.NewStreamFormatter(version.GreaterThan("1.0")) w.Write(sf.FormatError(err)) } @@ -680,7 +681,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response if !job.Stdout.Used() { return err } - sf := utils.NewStreamFormatter(version.GreaterThan("1.0")) + sf := streamformatter.NewStreamFormatter(version.GreaterThan("1.0")) w.Write(sf.FormatError(err)) } return nil @@ -1107,7 +1108,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite if !job.Stdout.Used() { return err } - sf := utils.NewStreamFormatter(version.GreaterThanOrEqualTo("1.8")) + sf := streamformatter.NewStreamFormatter(version.GreaterThanOrEqualTo("1.8")) w.Write(sf.FormatError(err)) } return nil diff --git a/builder/evaluator.go b/builder/evaluator.go index ec568d1fc..78c4c12f8 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -33,6 +33,7 @@ import ( "github.com/docker/docker/daemon" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/fileutils" + "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/tarsum" @@ -105,7 +106,7 @@ type Builder struct { // Deprecated, original writer used for ImagePull. To be removed. OutOld io.Writer - StreamFormatter *utils.StreamFormatter + StreamFormatter *streamformatter.StreamFormatter Config *runconfig.Config // runconfig for cmd, run, entrypoint etc. diff --git a/builder/internals.go b/builder/internals.go index e7e792aa0..1c90bf2d5 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -26,6 +26,7 @@ import ( "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/pkg/ioutils" + "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/stringid" @@ -601,7 +602,7 @@ func (b *Builder) run(c *daemon.Container) error { // Wait for it to finish if ret, _ := c.WaitStop(-1 * time.Second); ret != 0 { - err := &utils.JSONError{ + err := &jsonmessage.JSONError{ Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.Config.Cmd, ret), Code: ret, } diff --git a/builder/job.go b/builder/job.go index 665b268b6..89ed52f87 100644 --- a/builder/job.go +++ b/builder/job.go @@ -17,6 +17,7 @@ import ( "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" @@ -127,16 +128,16 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { } defer context.Close() - sf := utils.NewStreamFormatter(job.GetenvBool("json")) + sf := streamformatter.NewStreamFormatter(job.GetenvBool("json")) builder := &Builder{ Daemon: b.Daemon, Engine: b.Engine, - OutStream: &utils.StdoutFormater{ + OutStream: &streamformatter.StdoutFormater{ Writer: job.Stdout, StreamFormatter: sf, }, - ErrStream: &utils.StderrFormater{ + ErrStream: &streamformatter.StderrFormater{ Writer: job.Stdout, StreamFormatter: sf, }, diff --git a/events/events.go b/events/events.go index a093f359b..6940bafbb 100644 --- a/events/events.go +++ b/events/events.go @@ -10,23 +10,23 @@ import ( "time" "github.com/docker/docker/engine" + "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/parsers/filters" - "github.com/docker/docker/utils" ) const eventsLimit = 64 -type listener chan<- *utils.JSONMessage +type listener chan<- *jsonmessage.JSONMessage type Events struct { mu sync.RWMutex - events []*utils.JSONMessage + events []*jsonmessage.JSONMessage subscribers []listener } func New() *Events { return &Events{ - events: make([]*utils.JSONMessage, 0, eventsLimit), + events: make([]*jsonmessage.JSONMessage, 0, eventsLimit), } } @@ -63,7 +63,7 @@ func (e *Events) Get(job *engine.Job) error { timeout.Stop() } - listener := make(chan *utils.JSONMessage) + listener := make(chan *jsonmessage.JSONMessage) e.subscribe(listener) defer e.unsubscribe(listener) @@ -107,7 +107,7 @@ func (e *Events) SubscribersCount(job *engine.Job) error { return nil } -func writeEvent(job *engine.Job, event *utils.JSONMessage, eventFilters filters.Args) error { +func writeEvent(job *engine.Job, event *jsonmessage.JSONMessage, eventFilters filters.Args) error { isFiltered := func(field string, filter []string) bool { if len(filter) == 0 { return false @@ -170,7 +170,7 @@ func (e *Events) subscribersCount() int { func (e *Events) log(action, id, from string) { e.mu.Lock() now := time.Now().UTC().Unix() - jm := &utils.JSONMessage{Status: action, ID: id, From: from, Time: now} + jm := &jsonmessage.JSONMessage{Status: action, ID: id, From: from, Time: now} if len(e.events) == cap(e.events) { // discard oldest event copy(e.events, e.events[1:]) diff --git a/events/events_test.go b/events/events_test.go index d4fc664ba..a232576fe 100644 --- a/events/events_test.go +++ b/events/events_test.go @@ -9,13 +9,13 @@ import ( "time" "github.com/docker/docker/engine" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/jsonmessage" ) func TestEventsPublish(t *testing.T) { e := New() - l1 := make(chan *utils.JSONMessage) - l2 := make(chan *utils.JSONMessage) + l1 := make(chan *jsonmessage.JSONMessage) + l2 := make(chan *jsonmessage.JSONMessage) e.subscribe(l1) e.subscribe(l2) count := e.subscribersCount() @@ -61,7 +61,7 @@ func TestEventsPublish(t *testing.T) { func TestEventsPublishTimeout(t *testing.T) { e := New() - l := make(chan *utils.JSONMessage) + l := make(chan *jsonmessage.JSONMessage) e.subscribe(l) c := make(chan struct{}) @@ -108,9 +108,9 @@ func TestLogEvents(t *testing.T) { } buf = bytes.NewBuffer(buf.Bytes()) dec := json.NewDecoder(buf) - var msgs []utils.JSONMessage + var msgs []jsonmessage.JSONMessage for { - var jm utils.JSONMessage + var jm jsonmessage.JSONMessage if err := dec.Decode(&jm); err != nil { if err == io.EOF { break @@ -138,8 +138,8 @@ func TestEventsCountJob(t *testing.T) { if err := e.Install(eng); err != nil { t.Fatal(err) } - l1 := make(chan *utils.JSONMessage) - l2 := make(chan *utils.JSONMessage) + l1 := make(chan *jsonmessage.JSONMessage) + l2 := make(chan *jsonmessage.JSONMessage) e.subscribe(l1) e.subscribe(l2) job := eng.Job("subscribers_count") diff --git a/graph/graph.go b/graph/graph.go index 0aaf8b361..902018e39 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -18,6 +18,7 @@ import ( "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/progressreader" + "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/runconfig" @@ -198,7 +199,7 @@ func (graph *Graph) Register(img *image.Image, layerData archive.ArchiveReader) // The archive is stored on disk and will be automatically deleted as soon as has been read. // If output is not nil, a human-readable progress bar will be written to it. // FIXME: does this belong in Graph? How about MktempFile, let the caller use it for archives? -func (graph *Graph) TempLayerArchive(id string, sf *utils.StreamFormatter, output io.Writer) (*archive.TempArchive, error) { +func (graph *Graph) TempLayerArchive(id string, sf *streamformatter.StreamFormatter, output io.Writer) (*archive.TempArchive, error) { image, err := graph.Get(id) if err != nil { return nil, err diff --git a/graph/import.go b/graph/import.go index 2235fcf6b..2b3e8bdd6 100644 --- a/graph/import.go +++ b/graph/import.go @@ -11,6 +11,7 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/progressreader" + "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -23,7 +24,7 @@ func (s *TagStore) CmdImport(job *engine.Job) error { src = job.Args[0] repo = job.Args[1] tag string - sf = utils.NewStreamFormatter(job.GetenvBool("json")) + sf = streamformatter.NewStreamFormatter(job.GetenvBool("json")) archive archive.ArchiveReader resp *http.Response stdoutBuffer = bytes.NewBuffer(nil) diff --git a/graph/pull.go b/graph/pull.go index f359bb70c..0a6b2800c 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -15,6 +15,7 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" + "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/registry" "github.com/docker/docker/utils" @@ -28,7 +29,7 @@ func (s *TagStore) CmdPull(job *engine.Job) error { var ( localName = job.Args[0] tag string - sf = utils.NewStreamFormatter(job.GetenvBool("json")) + sf = streamformatter.NewStreamFormatter(job.GetenvBool("json")) authConfig = ®istry.AuthConfig{} metaHeaders map[string][]string ) @@ -107,7 +108,7 @@ func (s *TagStore) CmdPull(job *engine.Job) error { return nil } -func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, askedTag string, sf *utils.StreamFormatter, parallel bool) error { +func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, askedTag string, sf *streamformatter.StreamFormatter, parallel bool) error { out.Write(sf.FormatStatus("", "Pulling repository %s", repoInfo.CanonicalName)) repoData, err := r.GetRepositoryData(repoInfo.RemoteName) @@ -265,7 +266,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * return nil } -func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) (bool, error) { +func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *streamformatter.StreamFormatter) (bool, error) { history, err := r.GetRemoteHistory(imgID, endpoint, token) if err != nil { return false, err @@ -363,7 +364,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint return layers_downloaded, nil } -func WriteStatus(requestedTag string, out io.Writer, sf *utils.StreamFormatter, layers_downloaded bool) { +func WriteStatus(requestedTag string, out io.Writer, sf *streamformatter.StreamFormatter, layers_downloaded bool) { if layers_downloaded { out.Write(sf.FormatStatus("", "Status: Downloaded newer image for %s", requestedTag)) } else { @@ -382,7 +383,7 @@ type downloadInfo struct { err chan error } -func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter, parallel bool) error { +func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, parallel bool) error { endpoint, err := r.V2RegistryEndpoint(repoInfo.Index) if err != nil { if repoInfo.Index.Official { @@ -428,7 +429,7 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out return nil } -func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter, parallel bool, auth *registry.RequestAuthorization) (bool, error) { +func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, parallel bool, auth *registry.RequestAuthorization) (bool, error) { log.Debugf("Pulling tag from V2 registry: %q", tag) manifestBytes, manifestDigest, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth) diff --git a/graph/push.go b/graph/push.go index 3bbb9676d..4bd80f120 100644 --- a/graph/push.go +++ b/graph/push.go @@ -17,6 +17,7 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" + "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" @@ -130,7 +131,7 @@ type imagePushData struct { // lookupImageOnEndpoint checks the specified endpoint to see if an image exists // and if it is absent then it sends the image id to the channel to be pushed. -func lookupImageOnEndpoint(wg *sync.WaitGroup, r *registry.Session, out io.Writer, sf *utils.StreamFormatter, +func lookupImageOnEndpoint(wg *sync.WaitGroup, r *registry.Session, out io.Writer, sf *streamformatter.StreamFormatter, images chan imagePushData, imagesToPush chan string) { defer wg.Done() for image := range images { @@ -144,7 +145,7 @@ func lookupImageOnEndpoint(wg *sync.WaitGroup, r *registry.Session, out io.Write } func (s *TagStore) pushImageToEndpoint(endpoint string, out io.Writer, remoteName string, imageIDs []string, - tags map[string][]string, repo *registry.RepositoryData, sf *utils.StreamFormatter, r *registry.Session) error { + tags map[string][]string, repo *registry.RepositoryData, sf *streamformatter.StreamFormatter, r *registry.Session) error { workerCount := len(imageIDs) // start a maximum of 5 workers to check if images exist on the specified endpoint. if workerCount > 5 { @@ -203,7 +204,7 @@ func (s *TagStore) pushImageToEndpoint(endpoint string, out io.Writer, remoteNam // pushRepository pushes layers that do not already exist on the registry. func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, localRepo map[string]string, - tag string, sf *utils.StreamFormatter) error { + tag string, sf *streamformatter.StreamFormatter) error { log.Debugf("Local repo: %s", localRepo) out = utils.NewWriteFlusher(out) imgList, tags, err := s.getImageList(localRepo, tag) @@ -238,7 +239,7 @@ func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, return err } -func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep string, token []string, sf *utils.StreamFormatter) (checksum string, err error) { +func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep string, token []string, sf *streamformatter.StreamFormatter) (checksum string, err error) { out = utils.NewWriteFlusher(out) jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json")) if err != nil { @@ -292,7 +293,7 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin return imgData.Checksum, nil } -func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *utils.StreamFormatter) error { +func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter) error { endpoint, err := r.V2RegistryEndpoint(repoInfo.Index) if err != nil { if repoInfo.Index.Official { @@ -442,7 +443,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o } // PushV2Image pushes the image content to the v2 registry, first buffering the contents to disk -func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *registry.Endpoint, imageName string, sf *utils.StreamFormatter, out io.Writer, auth *registry.RequestAuthorization) (string, error) { +func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *registry.Endpoint, imageName string, sf *streamformatter.StreamFormatter, out io.Writer, auth *registry.RequestAuthorization) (string, error) { out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Buffering to Disk", nil)) image, err := s.graph.Get(img.ID) @@ -498,7 +499,7 @@ func (s *TagStore) CmdPush(job *engine.Job) error { } var ( localName = job.Args[0] - sf = utils.NewStreamFormatter(job.GetenvBool("json")) + sf = streamformatter.NewStreamFormatter(job.GetenvBool("json")) authConfig = ®istry.AuthConfig{} metaHeaders map[string][]string ) diff --git a/utils/jsonmessage.go b/pkg/jsonmessage/jsonmessage.go similarity index 99% rename from utils/jsonmessage.go rename to pkg/jsonmessage/jsonmessage.go index 74d311271..7db1626e4 100644 --- a/utils/jsonmessage.go +++ b/pkg/jsonmessage/jsonmessage.go @@ -1,4 +1,4 @@ -package utils +package jsonmessage import ( "encoding/json" diff --git a/utils/jsonmessage_test.go b/pkg/jsonmessage/jsonmessage_test.go similarity index 97% rename from utils/jsonmessage_test.go rename to pkg/jsonmessage/jsonmessage_test.go index b9103da1a..4c3f5666b 100644 --- a/utils/jsonmessage_test.go +++ b/pkg/jsonmessage/jsonmessage_test.go @@ -1,4 +1,4 @@ -package utils +package jsonmessage import ( "testing" diff --git a/pkg/progressreader/progressreader.go b/pkg/progressreader/progressreader.go index 730559e9f..e548b0755 100644 --- a/pkg/progressreader/progressreader.go +++ b/pkg/progressreader/progressreader.go @@ -1,37 +1,16 @@ package progressreader import ( + "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/docker/pkg/streamformatter" "io" ) -type StreamFormatter interface { - FormatProg(string, string, interface{}) []byte - FormatStatus(string, string, ...interface{}) []byte - FormatError(error) []byte -} - -type PR_JSONProgress interface { - GetCurrent() int - GetTotal() int -} - -type JSONProg struct { - Current int - Total int -} - -func (j *JSONProg) GetCurrent() int { - return j.Current -} -func (j *JSONProg) GetTotal() int { - return j.Total -} - // Reader with progress bar type Config struct { In io.ReadCloser // Stream to read from Out io.Writer // Where to send progress bar to - Formatter StreamFormatter + Formatter *streamformatter.StreamFormatter Size int Current int LastUpdate int @@ -54,7 +33,7 @@ func (config *Config) Read(p []byte) (n int, err error) { } } if config.Current-config.LastUpdate > updateEvery || err != nil { - config.Out.Write(config.Formatter.FormatProg(config.ID, config.Action, &JSONProg{Current: config.Current, Total: config.Size})) + config.Out.Write(config.Formatter.FormatProgress(config.ID, config.Action, &jsonmessage.JSONProgress{Current: config.Current, Total: config.Size})) config.LastUpdate = config.Current } // Send newline when complete @@ -64,6 +43,6 @@ func (config *Config) Read(p []byte) (n int, err error) { return read, err } func (config *Config) Close() error { - config.Out.Write(config.Formatter.FormatProg(config.ID, config.Action, &JSONProg{Current: config.Current, Total: config.Size})) + config.Out.Write(config.Formatter.FormatProgress(config.ID, config.Action, &jsonmessage.JSONProgress{Current: config.Current, Total: config.Size})) return config.In.Close() } diff --git a/utils/streamformatter.go b/pkg/streamformatter/streamformatter.go similarity index 72% rename from utils/streamformatter.go rename to pkg/streamformatter/streamformatter.go index e5b15f983..383e7adf9 100644 --- a/utils/streamformatter.go +++ b/pkg/streamformatter/streamformatter.go @@ -1,9 +1,9 @@ -package utils +package streamformatter import ( "encoding/json" "fmt" - "github.com/docker/docker/pkg/progressreader" + "github.com/docker/docker/pkg/jsonmessage" "io" ) @@ -21,7 +21,7 @@ var streamNewlineBytes = []byte(streamNewline) func (sf *StreamFormatter) FormatStream(str string) []byte { if sf.json { - b, err := json.Marshal(&JSONMessage{Stream: str}) + b, err := json.Marshal(&jsonmessage.JSONMessage{Stream: str}) if err != nil { return sf.FormatError(err) } @@ -33,7 +33,7 @@ func (sf *StreamFormatter) FormatStream(str string) []byte { func (sf *StreamFormatter) FormatStatus(id, format string, a ...interface{}) []byte { str := fmt.Sprintf(format, a...) if sf.json { - b, err := json.Marshal(&JSONMessage{ID: id, Status: str}) + b, err := json.Marshal(&jsonmessage.JSONMessage{ID: id, Status: str}) if err != nil { return sf.FormatError(err) } @@ -44,33 +44,25 @@ func (sf *StreamFormatter) FormatStatus(id, format string, a ...interface{}) []b func (sf *StreamFormatter) FormatError(err error) []byte { if sf.json { - jsonError, ok := err.(*JSONError) + jsonError, ok := err.(*jsonmessage.JSONError) if !ok { - jsonError = &JSONError{Message: err.Error()} + jsonError = &jsonmessage.JSONError{Message: err.Error()} } - if b, err := json.Marshal(&JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil { + if b, err := json.Marshal(&jsonmessage.JSONMessage{Error: jsonError, ErrorMessage: err.Error()}); err == nil { return append(b, streamNewlineBytes...) } return []byte("{\"error\":\"format error\"}" + streamNewline) } return []byte("Error: " + err.Error() + streamNewline) } -func (sf *StreamFormatter) FormatProg(id, action string, p interface{}) []byte { - switch progress := p.(type) { - case *JSONProgress: - return sf.FormatProgress(id, action, progress) - case progressreader.PR_JSONProgress: - return sf.FormatProgress(id, action, &JSONProgress{Current: progress.GetCurrent(), Total: progress.GetTotal()}) - } - return nil -} -func (sf *StreamFormatter) FormatProgress(id, action string, progress *JSONProgress) []byte { + +func (sf *StreamFormatter) FormatProgress(id, action string, progress *jsonmessage.JSONProgress) []byte { if progress == nil { - progress = &JSONProgress{} + progress = &jsonmessage.JSONProgress{} } if sf.json { - b, err := json.Marshal(&JSONMessage{ + b, err := json.Marshal(&jsonmessage.JSONMessage{ Status: action, ProgressMessage: progress.String(), Progress: progress, diff --git a/utils/streamformatter_test.go b/pkg/streamformatter/streamformatter_test.go similarity index 88% rename from utils/streamformatter_test.go rename to pkg/streamformatter/streamformatter_test.go index 20610f6c0..edc432e90 100644 --- a/utils/streamformatter_test.go +++ b/pkg/streamformatter/streamformatter_test.go @@ -1,8 +1,9 @@ -package utils +package streamformatter import ( "encoding/json" "errors" + "github.com/docker/docker/pkg/jsonmessage" "reflect" "testing" ) @@ -33,7 +34,7 @@ func TestFormatSimpleError(t *testing.T) { func TestFormatJSONError(t *testing.T) { sf := NewStreamFormatter(true) - err := &JSONError{Code: 50, Message: "Json error"} + err := &jsonmessage.JSONError{Code: 50, Message: "Json error"} res := sf.FormatError(err) if string(res) != `{"errorDetail":{"code":50,"message":"Json error"},"error":"Json error"}`+"\r\n" { t.Fatalf("%q", res) @@ -42,13 +43,13 @@ func TestFormatJSONError(t *testing.T) { func TestFormatProgress(t *testing.T) { sf := NewStreamFormatter(true) - progress := &JSONProgress{ + progress := &jsonmessage.JSONProgress{ Current: 15, Total: 30, Start: 1, } res := sf.FormatProgress("id", "action", progress) - msg := &JSONMessage{} + msg := &jsonmessage.JSONMessage{} if err := json.Unmarshal(res, msg); err != nil { t.Fatal(err) } diff --git a/utils/utils.go b/utils/utils.go index d5ebb68c9..4a765eb09 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -23,6 +23,7 @@ import ( "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/ioutils" + "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/stringutils" ) @@ -254,7 +255,7 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher { } func NewHTTPRequestError(msg string, res *http.Response) error { - return &JSONError{ + return &jsonmessage.JSONError{ Message: msg, Code: res.StatusCode, } From a465e26bb05083c247ba74e1092338c43c76be47 Mon Sep 17 00:00:00 2001 From: Peggy Li Date: Wed, 25 Mar 2015 19:31:29 -0700 Subject: [PATCH 104/999] Fix golint errors for casing in api/client package Signed-off-by: Peggy Li --- api/client/build.go | 8 ++++---- api/client/events.go | 4 ++-- api/client/images.go | 8 ++++---- api/client/ps.go | 4 ++-- api/client/rename.go | 8 ++++---- api/client/run.go | 8 ++++---- api/client/stats.go | 18 +++++++++--------- api/client/utils.go | 12 ++++++------ 8 files changed, 35 insertions(+), 35 deletions(-) diff --git a/api/client/build.go b/api/client/build.go index 91446132a..0847a6c5a 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -47,8 +47,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error { dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')") flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit") flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap") - flCpuShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") - flCpuSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") + flCPUShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") + flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") cmd.Require(flag.Exact, 1) @@ -271,8 +271,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v.Set("pull", "1") } - v.Set("cpusetcpus", *flCpuSetCpus) - v.Set("cpushares", strconv.FormatInt(*flCpuShares, 10)) + v.Set("cpusetcpus", *flCPUSetCpus) + v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10)) v.Set("memory", strconv.FormatInt(memory, 10)) v.Set("memswap", strconv.FormatInt(memorySwap, 10)) diff --git a/api/client/events.go b/api/client/events.go index bc39e3cfd..4e60ec29f 100644 --- a/api/client/events.go +++ b/api/client/events.go @@ -55,11 +55,11 @@ func (cli *DockerCli) CmdEvents(args ...string) error { setTime("until", *until) } if len(eventFilterArgs) > 0 { - filterJson, err := filters.ToParam(eventFilterArgs) + filterJSON, err := filters.ToParam(eventFilterArgs) if err != nil { return err } - v.Set("filters", filterJson) + v.Set("filters", filterJSON) } if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil { return err diff --git a/api/client/images.go b/api/client/images.go index d59a0838a..93959845c 100644 --- a/api/client/images.go +++ b/api/client/images.go @@ -119,11 +119,11 @@ func (cli *DockerCli) CmdImages(args ...string) error { "all": []string{"1"}, } if len(imageFilterArgs) > 0 { - filterJson, err := filters.ToParam(imageFilterArgs) + filterJSON, err := filters.ToParam(imageFilterArgs) if err != nil { return err } - v.Set("filters", filterJson) + v.Set("filters", filterJSON) } body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) @@ -189,11 +189,11 @@ func (cli *DockerCli) CmdImages(args ...string) error { } else { v := url.Values{} if len(imageFilterArgs) > 0 { - filterJson, err := filters.ToParam(imageFilterArgs) + filterJSON, err := filters.ToParam(imageFilterArgs) if err != nil { return err } - v.Set("filters", filterJson) + v.Set("filters", filterJSON) } if cmd.NArg() == 1 { diff --git a/api/client/ps.go b/api/client/ps.go index fa9b2b488..b4160d57a 100644 --- a/api/client/ps.go +++ b/api/client/ps.go @@ -74,12 +74,12 @@ func (cli *DockerCli) CmdPs(args ...string) error { } if len(psFilterArgs) > 0 { - filterJson, err := filters.ToParam(psFilterArgs) + filterJSON, err := filters.ToParam(psFilterArgs) if err != nil { return err } - v.Set("filters", filterJson) + v.Set("filters", filterJSON) } body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false)) diff --git a/api/client/rename.go b/api/client/rename.go index 82d2b74c3..11138cfc5 100644 --- a/api/client/rename.go +++ b/api/client/rename.go @@ -12,12 +12,12 @@ func (cli *DockerCli) CmdRename(args ...string) error { cmd.Usage() return nil } - old_name := cmd.Arg(0) - new_name := cmd.Arg(1) + oldName := cmd.Arg(0) + newName := cmd.Arg(1) - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", old_name, new_name), nil, false)); err != nil { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName), nil, false)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) - return fmt.Errorf("Error: failed to rename container named %s", old_name) + return fmt.Errorf("Error: failed to rename container named %s", oldName) } return nil } diff --git a/api/client/run.go b/api/client/run.go index e00a3d78d..f3cace97b 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -110,14 +110,14 @@ func (cli *DockerCli) CmdRun(args ...string) error { defer signal.StopCatch(sigc) } var ( - waitDisplayId chan struct{} + waitDisplayID chan struct{} errCh chan error ) if !config.AttachStdout && !config.AttachStderr { // Make this asynchronous to allow the client to write to stdin before having to read the ID - waitDisplayId = make(chan struct{}) + waitDisplayID = make(chan struct{}) go func() { - defer close(waitDisplayId) + defer close(waitDisplayID) fmt.Fprintf(cli.out, "%s\n", createResponse.ID) }() } @@ -207,7 +207,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { // Detached mode: wait for the id to be displayed and return. if !config.AttachStdout && !config.AttachStderr { // Detached mode - <-waitDisplayId + <-waitDisplayID return nil } diff --git a/api/client/stats.go b/api/client/stats.go index 8999abdb2..91f024247 100644 --- a/api/client/stats.go +++ b/api/client/stats.go @@ -18,7 +18,7 @@ import ( type containerStats struct { Name string - CpuPercentage float64 + CPUPercentage float64 Memory float64 MemoryLimit float64 MemoryPercentage float64 @@ -36,7 +36,7 @@ func (s *containerStats) Collect(cli *DockerCli) { } defer stream.Close() var ( - previousCpu uint64 + previousCPU uint64 previousSystem uint64 start = true dec = json.NewDecoder(stream) @@ -54,18 +54,18 @@ func (s *containerStats) Collect(cli *DockerCli) { cpuPercent = 0.0 ) if !start { - cpuPercent = calculateCpuPercent(previousCpu, previousSystem, v) + cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v) } start = false s.mu.Lock() - s.CpuPercentage = cpuPercent + s.CPUPercentage = cpuPercent s.Memory = float64(v.MemoryStats.Usage) s.MemoryLimit = float64(v.MemoryStats.Limit) s.MemoryPercentage = memPercent s.NetworkRx = float64(v.Network.RxBytes) s.NetworkTx = float64(v.Network.TxBytes) s.mu.Unlock() - previousCpu = v.CpuStats.CpuUsage.TotalUsage + previousCPU = v.CpuStats.CpuUsage.TotalUsage previousSystem = v.CpuStats.SystemUsage u <- nil } @@ -76,7 +76,7 @@ func (s *containerStats) Collect(cli *DockerCli) { // zero out the values if we have not received an update within // the specified duration. s.mu.Lock() - s.CpuPercentage = 0 + s.CPUPercentage = 0 s.Memory = 0 s.MemoryPercentage = 0 s.mu.Unlock() @@ -99,7 +99,7 @@ func (s *containerStats) Display(w io.Writer) error { } fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n", s.Name, - s.CpuPercentage, + s.CPUPercentage, units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), s.MemoryPercentage, units.BytesSize(s.NetworkRx), units.BytesSize(s.NetworkTx)) @@ -161,11 +161,11 @@ func (cli *DockerCli) CmdStats(args ...string) error { return nil } -func calculateCpuPercent(previousCpu, previousSystem uint64, v *types.Stats) float64 { +func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) float64 { var ( cpuPercent = 0.0 // calculate the change for the cpu usage of the container in between readings - cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCpu) + cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCPU) // calculate the change for the entire system between readings systemDelta = float64(v.CpuStats.SystemUsage - previousSystem) ) diff --git a/api/client/utils.go b/api/client/utils.go index 103bfdec3..6e7784d0f 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -200,8 +200,8 @@ func (cli *DockerCli) resizeTty(id string, isExec bool) { } } -func waitForExit(cli *DockerCli, containerId string) (int, error) { - stream, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil, false) +func waitForExit(cli *DockerCli, containerID string) (int, error) { + stream, _, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, false) if err != nil { return -1, err } @@ -215,8 +215,8 @@ func waitForExit(cli *DockerCli, containerId string) (int, error) { // 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) { - stream, _, err := cli.call("GET", "/containers/"+containerId+"/json", nil, false) +func getExitCode(cli *DockerCli, containerID string) (bool, int, error) { + stream, _, err := cli.call("GET", "/containers/"+containerID+"/json", nil, false) if err != nil { // If we can't connect, then the daemon probably died. if err != ErrConnectionRefused { @@ -236,8 +236,8 @@ func getExitCode(cli *DockerCli, containerId string) (bool, int, error) { // getExecExitCode perform an inspect on the exec command. It returns // the running state and the exit code. -func getExecExitCode(cli *DockerCli, execId string) (bool, int, error) { - stream, _, err := cli.call("GET", "/exec/"+execId+"/json", nil, false) +func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) { + stream, _, err := cli.call("GET", "/exec/"+execID+"/json", nil, false) if err != nil { // If we can't connect, then the daemon probably died. if err != ErrConnectionRefused { From 1bc266dfa714f097cc9babf2f0c771566388d47b Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Wed, 25 Mar 2015 21:01:14 -0600 Subject: [PATCH 105/999] Changes response of postContainersWait to use a struct Signed-off-by: Nick Parker --- api/server/server.go | 11 +++++++---- api/types/types.go | 6 ++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 9c50bfb52..81dccb4ea 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -877,7 +877,6 @@ func postContainersWait(eng *engine.Engine, version version.Version, w http.Resp return fmt.Errorf("Missing parameter") } var ( - env engine.Env stdoutBuffer = bytes.NewBuffer(nil) job = eng.Job("wait", vars["name"]) ) @@ -885,9 +884,13 @@ func postContainersWait(eng *engine.Engine, version version.Version, w http.Resp if err := job.Run(); err != nil { return err } - - env.Set("StatusCode", engine.Tail(stdoutBuffer, 1)) - return writeJSONEnv(w, http.StatusOK, env) + statusCode, err := strconv.Atoi(engine.Tail(stdoutBuffer, 1)) + if err != nil { + return err + } + return writeJSON(w, http.StatusOK, &types.ContainerWaitResponse{ + StatusCode: statusCode, + }) } func postContainersResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api/types/types.go b/api/types/types.go index 21dba7729..af21cd4f6 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -24,3 +24,9 @@ type AuthResponse struct { // Status is the authentication status Status string `json:"Status"` } + +// POST /auth +type ContainerWaitResponse struct { + // StatusCode is the status code of the wait job + StatusCode int `json:"StatusCode"` +} From 4982374739a37e349b82ab168fb2f43a55f3287c Mon Sep 17 00:00:00 2001 From: Liana Lo Date: Wed, 25 Mar 2015 19:30:11 -0700 Subject: [PATCH 106/999] fix typos, grammar, more concise wording Signed-off-by: Liana Lo --- docs/sources/project/set-up-dev-env.md | 25 ++++++++++++------------- docs/sources/project/set-up-git.md | 4 ++-- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/docs/sources/project/set-up-dev-env.md b/docs/sources/project/set-up-dev-env.md index 0629822b9..d5888ef22 100644 --- a/docs/sources/project/set-up-dev-env.md +++ b/docs/sources/project/set-up-dev-env.md @@ -7,7 +7,7 @@ page_keywords: development, inception, container, image Dockerfile, dependencies In this section, you learn to develop like a member of Docker's core team. The `docker` repository includes a `Dockerfile` at its root. This file defines Docker's development environment. The `Dockerfile` lists the environment's -dependencies: system libraries and binaries, go environment, go dependencies, +dependencies: system libraries and binaries, Go environment, Go dependencies, etc. Docker's development environment is itself, ultimately a Docker container. @@ -22,13 +22,12 @@ you continue working with your fork on this branch. ## Clean your host of Docker artifacts -Docker developers run the latest stable release of the Docker software; Or -Boot2docker and Docker if their machine is Mac OS X. They clean their local +Docker developers run the latest stable release of the Docker software (with Boot2Docker if their machine is Mac OS X). They clean their local hosts of unnecessary Docker artifacts such as stopped containers or unused -images. Cleaning unnecessary artifacts isn't strictly necessary but it is +images. Cleaning unnecessary artifacts isn't strictly necessary, but it is good practice, so it is included here. -To remove unnecessary artifacts. +To remove unnecessary artifacts, 1. Verify that you have no unnecessary containers running on your host. @@ -75,7 +74,7 @@ To remove unnecessary artifacts. $ docker rmi -f $(docker images -q -a -f dangling=true) - This command uses `docker images` to lists all images (`-a` flag) by numeric + This command uses `docker images` to list all images (`-a` flag) by numeric IDs (`-q` flag) and filter them to find dangling images (`-f dangling=true`). Then, the `docker rmi` command forcibly (`-f` flag) removes the resulting list. To remove just one image, use the `docker rmi ID` @@ -100,13 +99,13 @@ environment. If you are following along with this guide, you created a `dry-run-test` branch when you set up Git for - contributing + contributing. 4. Ensure you are on your `dry-run-test` branch. $ git checkout dry-run-test - If you get a message that the branch doesn't exist, add the `-b` flag so the + If you get a message that the branch doesn't exist, add the `-b` flag (git checkout -b dry-run-test) so the command both creates the branch and checks it out. 5. Compile your development environment container into an image. @@ -201,7 +200,7 @@ build and run a `docker` binary in your container. ![Multiple terminals](/project/images/three_terms.png) - Mac OSX users, make sure you run `eval "$(boot2docker shellinit)"` in any new + Mac OS X users, make sure you run `eval "$(boot2docker shellinit)"` in any new terminals. 2. In a terminal, create a new container from your `dry-run-test` image. @@ -212,7 +211,7 @@ build and run a `docker` binary in your container. The command creates a container from your `dry-run-test` image. It opens an interactive terminal (`-ti`) running a `/bin/bash shell`. The `--privileged` flag gives the container access to kernel features and device - access. It is this flag that allows you to run a container in a container. + access. This flag allows you to run a container in a container. Finally, the `-rm` flag instructs Docker to remove the container when you exit the `/bin/bash` shell. @@ -282,7 +281,7 @@ with the `make.sh` script. root@5f8630b873fe:/go/src/github.com/docker/docker# docker -dD - The `-dD` flag starts the daemon in debug mode; You'll find this useful + The `-dD` flag starts the daemon in debug mode. You'll find this useful when debugging your code. 9. Bring up one of the terminals on your local host. @@ -365,7 +364,7 @@ container. Your location will be different because it reflects your environment. -3. Create a container using `dry-run-test` but this time mount your repository +3. Create a container using `dry-run-test`, but this time, mount your repository onto the `/go` directory inside the container. $ docker run --privileged --rm -ti -v `pwd`:/go/src/github.com/docker/docker dry-run-test /bin/bash @@ -384,7 +383,7 @@ onto the `/go` directory inside the container. $ cd ~/repos/docker-fork/ -6. Create a fresh binary but this time use the `make` command. +6. Create a fresh binary, but this time, use the `make` command. $ make BINDDIR=. binary diff --git a/docs/sources/project/set-up-git.md b/docs/sources/project/set-up-git.md index 2292d93b3..19c971597 100644 --- a/docs/sources/project/set-up-git.md +++ b/docs/sources/project/set-up-git.md @@ -134,12 +134,12 @@ To configure your username, email, and add a remote: ## Create and push a branch -As you change code in your fork, you make your changes on a repository branch. +As you change code in your fork, make your changes on a repository branch. The branch name should reflect what you are working on. In this section, you create a branch, make a change, and push it up to your fork. This branch is just for testing your config for this guide. The changes are part -of a dry run so the branch name is going to be dry-run-test. To create an push +of a dry run, so the branch name will be dry-run-test. To create and push the branch to your fork on GitHub: 1. Open a terminal and go to the root of your `docker-fork`. From 2a5a402c717fa0ff05b7e57e85bf3388ba1ee1a5 Mon Sep 17 00:00:00 2001 From: Peggy Li Date: Wed, 25 Mar 2015 16:26:28 -0700 Subject: [PATCH 107/999] Update CmdCommit docstring and fix CmdHelp whitespace Signed-off-by: Peggy Li --- api/client/commit.go | 4 ++-- api/client/help.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/api/client/commit.go b/api/client/commit.go index 4500f3b42..4214cffce 100644 --- a/api/client/commit.go +++ b/api/client/commit.go @@ -14,9 +14,9 @@ import ( "github.com/docker/docker/utils" ) -// CmdAttach attaches to a running container. +// CmdCommit creates a new image from a container's changes. // -// Usage: docker attach [OPTIONS] CONTAINER +// Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] func (cli *DockerCli) CmdCommit(args ...string) error { cmd := cli.Subcmd("commit", "CONTAINER [REPOSITORY[:TAG]]", "Create a new image from a container's changes", true) flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit") diff --git a/api/client/help.go b/api/client/help.go index 2f2d50bbf..e95387967 100644 --- a/api/client/help.go +++ b/api/client/help.go @@ -9,7 +9,7 @@ import ( // CmdHelp displays information on a Docker command. // -//If more than one command is specified, information is only shown for the first command. +// If more than one command is specified, information is only shown for the first command. // // Usage: docker help COMMAND or docker COMMAND --help func (cli *DockerCli) CmdHelp(args ...string) error { From c5bf2145f172a264d3d8fc63d6717826b95b5ee2 Mon Sep 17 00:00:00 2001 From: paul Date: Wed, 25 Mar 2015 20:18:42 -0700 Subject: [PATCH 108/999] Fix vet warning Signed-off-by: Paul Mou --- integration-cli/docker_api_containers_test.go | 2 +- integration-cli/docker_cli_daemon_test.go | 16 +++++++-------- integration-cli/docker_cli_links_test.go | 2 +- integration-cli/docker_cli_pause_test.go | 2 +- integration-cli/docker_cli_run_test.go | 20 ++++++++++--------- integration-cli/docker_utils.go | 2 +- 6 files changed, 23 insertions(+), 21 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index ea2f2450a..a85fcbdf6 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -548,7 +548,7 @@ func TestPostContainerBindNormalVolume(t *testing.T) { } if fooDir2 != fooDir { - t.Fatal("expected volume path to be %s, got: %s", fooDir, fooDir2) + t.Fatalf("expected volume path to be %s, got: %s", fooDir, fooDir2) } logDone("container REST API - can use path from normal volume as bind-mount to overwrite another volume") diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index c515a6378..7531e431c 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -524,7 +524,7 @@ func TestDaemonUlimitDefaults(t *testing.T) { outArr := strings.Split(out, "\n") if len(outArr) < 2 { - t.Fatal("got unexpected output: %s", out) + t.Fatalf("got unexpected output: %s", out) } nofile := strings.TrimSpace(outArr[0]) nproc := strings.TrimSpace(outArr[1]) @@ -548,7 +548,7 @@ func TestDaemonUlimitDefaults(t *testing.T) { outArr = strings.Split(out, "\n") if len(outArr) < 2 { - t.Fatal("got unexpected output: %s", out) + t.Fatalf("got unexpected output: %s", out) } nofile = strings.TrimSpace(outArr[0]) nproc = strings.TrimSpace(outArr[1]) @@ -616,9 +616,9 @@ func TestDaemonLoggingDriverDefault(t *testing.T) { t.Fatal(err) } var res struct { - Log string `json:log` - Stream string `json:stream` - Time time.Time `json:time` + Log string `json:"log"` + Stream string `json:"stream"` + Time time.Time `json:"time"` } if err := json.NewDecoder(f).Decode(&res); err != nil { t.Fatal(err) @@ -712,9 +712,9 @@ func TestDaemonLoggingDriverNoneOverride(t *testing.T) { t.Fatal(err) } var res struct { - Log string `json:log` - Stream string `json:stream` - Time time.Time `json:time` + Log string `json:"log"` + Stream string `json:"stream"` + Time time.Time `json:"time"` } if err := json.NewDecoder(f).Decode(&res); err != nil { t.Fatal(err) diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index efee8d04e..1b46da50a 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -75,7 +75,7 @@ func TestLinksInvalidContainerTarget(t *testing.T) { t.Fatal("an invalid container target should produce an error") } if !strings.Contains(out, "Could not get container") { - t.Fatal("error output expected 'Could not get container', but got %q instead; err: %v", out, err) + t.Fatalf("error output expected 'Could not get container', but got %q instead; err: %v", out, err) } logDone("links - linking to non-existent container should not work") diff --git a/integration-cli/docker_cli_pause_test.go b/integration-cli/docker_cli_pause_test.go index f1ccde9cf..2ba8cb0ae 100644 --- a/integration-cli/docker_cli_pause_test.go +++ b/integration-cli/docker_cli_pause_test.go @@ -22,7 +22,7 @@ func TestPause(t *testing.T) { t.Fatalf("error thrown while checking if containers were paused: %v", err) } if len(pausedContainers) != 1 { - t.Fatalf("there should be one paused container and not", len(pausedContainers)) + t.Fatalf("there should be one paused container and not %d", len(pausedContainers)) } dockerCmd(t, "unpause", name) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 26cfcccde..1e4a51162 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -1930,9 +1930,9 @@ func TestRunAttachWithDettach(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "-d", "--attach", "stdout", "busybox", "true") _, stderr, _, err := runCommandWithStdoutStderr(cmd) if err == nil { - t.Fatalf("Container should have exited with error code different than 0", err) + t.Fatal("Container should have exited with error code different than 0") } else if !strings.Contains(stderr, "Conflicting options: -a and -d") { - t.Fatalf("Should have been returned an error with conflicting options -a and -d") + t.Fatal("Should have been returned an error with conflicting options -a and -d") } logDone("run - Attach stdout with -d") @@ -2655,7 +2655,7 @@ func TestRunCreateVolumeEtc(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "-v", "/etc", "busybox", "cat", "/etc/resolv.conf") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal("failed to run container: %v, output: %q", err, out) + t.Fatalf("failed to run container: %v, output: %q", err, out) } if !strings.Contains(out, "nameserver 127.0.0.1") { t.Fatal("/etc volume mount hides /etc/resolv.conf") @@ -2664,7 +2664,7 @@ func TestRunCreateVolumeEtc(t *testing.T) { cmd = exec.Command(dockerBinary, "run", "-h=test123", "-v", "/etc", "busybox", "cat", "/etc/hostname") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal("failed to run container: %v, output: %q", err, out) + t.Fatalf("failed to run container: %v, output: %q", err, out) } if !strings.Contains(out, "test123") { t.Fatal("/etc volume mount hides /etc/hostname") @@ -2673,7 +2673,7 @@ func TestRunCreateVolumeEtc(t *testing.T) { cmd = exec.Command(dockerBinary, "run", "--add-host=test:192.168.0.1", "-v", "/etc", "busybox", "cat", "/etc/hosts") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal("failed to run container: %v, output: %q", err, out) + t.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.Replace(out, "\n", " ", -1) if !strings.Contains(out, "192.168.0.1\ttest") || !strings.Contains(out, "127.0.0.1\tlocalhost") { @@ -2857,14 +2857,16 @@ func TestRunAllowPortRangeThroughExpose(t *testing.T) { t.Fatal(err) } var ports nat.PortMap - err = unmarshalJSON([]byte(portstr), &ports) + if err = unmarshalJSON([]byte(portstr), &ports); err != nil { + t.Fatal(err) + } for port, binding := range ports { portnum, _ := strconv.Atoi(strings.Split(string(port), "/")[0]) if portnum < 3000 || portnum > 3003 { - t.Fatalf("Port is out of range ", portnum, binding, out) + t.Fatalf("Port %d is out of range ", portnum) } if binding == nil || len(binding) != 1 || len(binding[0].HostPort) == 0 { - t.Fatal("Port is not mapped for the port "+port, out) + t.Fatalf("Port is not mapped for the port %d", port) } } if err := deleteContainer(id); err != nil { @@ -3220,7 +3222,7 @@ func TestRunAllowPortRangeThroughPublish(t *testing.T) { for port, binding := range ports { portnum, _ := strconv.Atoi(strings.Split(string(port), "/")[0]) if portnum < 3000 || portnum > 3003 { - t.Fatalf("Port is out of range ", portnum, binding, out) + t.Fatalf("Port %d is out of range ", portnum) } if binding == nil || len(binding) != 1 || len(binding[0].HostPort) == 0 { t.Fatal("Port is not mapped for the port "+port, out) diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 7abdc54dc..fabd21492 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -1045,7 +1045,7 @@ func daemonTime(t *testing.T) time.Time { body, err := sockRequest("GET", "/info", nil) if err != nil { - t.Fatal("daemonTime: failed to get /info: %v", err) + t.Fatalf("daemonTime: failed to get /info: %v", err) } type infoJSON struct { From 273fdd97edec3ad758dbf94e8268fd57512cee02 Mon Sep 17 00:00:00 2001 From: paul Date: Wed, 25 Mar 2015 20:43:23 -0700 Subject: [PATCH 109/999] Fixes pointer error Signed-off-by: Paul Mou --- pkg/ulimit/ulimit_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/ulimit/ulimit_test.go b/pkg/ulimit/ulimit_test.go index a07db6000..1e8c881f5 100644 --- a/pkg/ulimit/ulimit_test.go +++ b/pkg/ulimit/ulimit_test.go @@ -4,8 +4,8 @@ import "testing" func TestParseValid(t *testing.T) { u1 := &Ulimit{"nofile", 1024, 512} - if u2, _ := Parse("nofile=512:1024"); u1 == u2 { - t.Fatalf("expected %s, but got %s", u1.String(), u2.String()) + if u2, _ := Parse("nofile=512:1024"); *u1 != *u2 { + t.Fatalf("expected %q, but got %q", u1, u2) } } From c2fe26243906ad2641876e877d23a419bb4bdb75 Mon Sep 17 00:00:00 2001 From: Jake Champlin Date: Wed, 25 Mar 2015 23:44:09 -0400 Subject: [PATCH 110/999] Add fixes for integration-cli tests w/ --net none Adds network to integration tests that were failing without network. Fixes #10964 Fixes #10968 Signed-off-by: Jake Champlin --- integration-cli/docker_cli_events_test.go | 1 + integration-cli/docker_cli_run_test.go | 1 + integration-cli/docker_cli_search_test.go | 1 + 3 files changed, 3 insertions(+) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index a74ce15fb..e855ce88f 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -149,6 +149,7 @@ func TestEventsImageUntagDelete(t *testing.T) { func TestEventsImagePull(t *testing.T) { since := daemonTime(t).Unix() + testRequires(t, Network) defer deleteImages("hello-world") diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 26cfcccde..36a526953 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -147,6 +147,7 @@ func TestRunLeakyFileDescriptors(t *testing.T) { // it should be possible to lookup Google DNS // this will fail when Internet access is unavailable func TestRunLookupGoogleDns(t *testing.T) { + testRequires(t, Network) defer deleteAllContainers() out, _, _, err := runCommandWithStdoutStderr(exec.Command(dockerBinary, "run", "busybox", "nslookup", "google.com")) diff --git a/integration-cli/docker_cli_search_test.go b/integration-cli/docker_cli_search_test.go index fafb5df75..7d6017e3d 100644 --- a/integration-cli/docker_cli_search_test.go +++ b/integration-cli/docker_cli_search_test.go @@ -8,6 +8,7 @@ import ( // search for repos named "registry" on the central registry func TestSearchOnCentralRegistry(t *testing.T) { + testRequires(t, Network) searchCmd := exec.Command(dockerBinary, "search", "busybox") out, exitCode, err := runCommandWithOutput(searchCmd) if err != nil || exitCode != 0 { From 89d63d2a82e9a4c20696e9e93736a99a10be6c91 Mon Sep 17 00:00:00 2001 From: Nick Parker Date: Wed, 25 Mar 2015 22:22:45 -0600 Subject: [PATCH 111/999] fixes comment for ContainerWaitResponse struct Signed-off-by: Nick Parker --- api/types/types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/types/types.go b/api/types/types.go index af21cd4f6..50a72a550 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -25,7 +25,7 @@ type AuthResponse struct { Status string `json:"Status"` } -// POST /auth +// POST "/containers/"+containerID+"/wait" type ContainerWaitResponse struct { // StatusCode is the status code of the wait job StatusCode int `json:"StatusCode"` From 0f702094ecd38cdfbfb0199a6aeb9e6c90853c70 Mon Sep 17 00:00:00 2001 From: Amy Lindburg Date: Wed, 25 Mar 2015 21:52:32 -0700 Subject: [PATCH 112/999] Fixing typo in cd instruction Signed-off-by: Amy Lindburg --- docs/sources/project/test-and-docs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/project/test-and-docs.md b/docs/sources/project/test-and-docs.md index cef3cae8e..33c13fa1a 100644 --- a/docs/sources/project/test-and-docs.md +++ b/docs/sources/project/test-and-docs.md @@ -249,7 +249,7 @@ can browse the docs. 1. In a terminal, change to the root of your `docker-fork` repository. - $ cd ~/repos/dry-run-test + $ cd ~/repos/docker-fork 2. Make sure you are in your feature branch. From 40ef253ef516a82d4a3d1cf23f7d3bee2a08bc4f Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Thu, 26 Mar 2015 13:12:36 +0800 Subject: [PATCH 113/999] add back job.Errorf c79b9bab54167 (Remove engine.Status and replace it with standard go error) cause a regression that create container won't get any warnings, we still need this to send useful informations to user. Signed-off-by: Qiang Huang --- daemon/create.go | 9 ++++----- engine/job.go | 4 ++++ 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/daemon/create.go b/daemon/create.go index a038635ed..39b6ac58b 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -4,7 +4,6 @@ import ( "fmt" "strings" - log "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/image" @@ -31,11 +30,11 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) error { return fmt.Errorf("Minimum memory limit allowed is 4MB") } if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit { - log.Printf("Your kernel does not support memory limit capabilities. Limitation discarded.\n") + job.Errorf("Your kernel does not support memory limit capabilities. Limitation discarded.\n") hostConfig.Memory = 0 } if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit { - log.Printf("Your kernel does not support swap limit capabilities. Limitation discarded.\n") + job.Errorf("Your kernel does not support swap limit capabilities. Limitation discarded.\n") hostConfig.MemorySwap = -1 } if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory { @@ -57,14 +56,14 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) error { return err } if !container.Config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled { - log.Printf("IPv4 forwarding is disabled.\n") + job.Errorf("IPv4 forwarding is disabled.\n") } container.LogEvent("create") job.Printf("%s\n", container.ID) for _, warning := range buildWarnings { - log.Printf("%s\n", warning) + job.Errorf("%s\n", warning) } return nil diff --git a/engine/job.go b/engine/job.go index d882d9c9d..52c655bae 100644 --- a/engine/job.go +++ b/engine/job.go @@ -204,6 +204,10 @@ func (job *Job) Printf(format string, args ...interface{}) (n int, err error) { return fmt.Fprintf(job.Stdout, format, args...) } +func (job *Job) Errorf(format string, args ...interface{}) (n int, err error) { + return fmt.Fprintf(job.Stderr, format, args...) +} + func (job *Job) SetCloseIO(val bool) { job.closeIO = val } From fabb5114d808d127436121d28e320fd065f30865 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Thu, 26 Mar 2015 13:55:46 +0800 Subject: [PATCH 114/999] update docs for container exec https://github.com/docker/docker/pull/11665 This PR changed container exec API response, we need docs updated. Signed-off-by: Qiang Huang --- docs/sources/reference/api/docker_remote_api.md | 6 +++++- docs/sources/reference/api/docker_remote_api_v1.18.md | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index 3da4cc82d..12f0a71fc 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -75,12 +75,16 @@ Builds can now set resource constraints for all containers created for the build **New!** (`CgroupParent`) can be passed in the host config to setup container cgroups under a specific cgroup. - `POST /build` **New!** Closing the HTTP request will now cause the build to be canceled. +`POST /containers/(id)/exec` + +**New!** +Add `Warnings` field to response. + ## v1.17 ### Full Documentation diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index 321be87e3..6c49fb91f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -1832,6 +1832,7 @@ Sets up an exec instance in a running container `id` { "Id": "f90e34656806" + "Warnings":[] } Json Parameters: From 281abd2c8aff542e3b0309eda15536177bcec713 Mon Sep 17 00:00:00 2001 From: "Daniel, Dao Quang Minh" Date: Thu, 26 Mar 2015 06:02:21 +0000 Subject: [PATCH 115/999] aufs: apply dirperm1 by default if supported Automatically detect support for aufs `dirperm1` option and apply it. `dirperm1` tells aufs to check the permission bits of the directory on the topmost branch and ignore the permission bits on all lower branches. It can be used to fix aufs' permission bug (i.e., upper layer having broader mask than the lower layer). More information about the bug can be found at https://github.com/docker/docker/issues/783 `dirperm1` man page is at: http://aufs.sourceforge.net/aufs3/man.html Signed-off-by: Daniel, Dao Quang Minh --- daemon/graphdriver/aufs/aufs.go | 46 +++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index bc4b5c081..573f41372 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -23,6 +23,7 @@ package aufs import ( "bufio" "fmt" + "io/ioutil" "os" "os/exec" "path" @@ -47,6 +48,9 @@ var ( graphdriver.FsMagicAufs, } backingFs = "" + + enableDirpermLock sync.Once + enableDirperm bool ) func init() { @@ -422,7 +426,11 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro // Mount options are clipped to page size(4096 bytes). If there are more // layers then these are remounted individually using append. - b := make([]byte, syscall.Getpagesize()-len(mountLabel)-54) // room for xino & mountLabel + offset := 54 + if useDirperm() { + offset += len("dirperm1") + } + b := make([]byte, syscall.Getpagesize()-len(mountLabel)-offset) // room for xino & mountLabel bp := copy(b, fmt.Sprintf("br:%s=rw", rw)) firstMount := true @@ -446,7 +454,11 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro } if firstMount { - data := label.FormatMountLabel(fmt.Sprintf("%s,dio,xino=/dev/shm/aufs.xino", string(b[:bp])), mountLabel) + opts := "dio,xino=/dev/shm/aufs.xino" + if useDirperm() { + opts += ",dirperm1" + } + data := label.FormatMountLabel(fmt.Sprintf("%s,%s", string(b[:bp]), opts), mountLabel) if err = mount("none", target, "aufs", 0, data); err != nil { return } @@ -460,3 +472,33 @@ func (a *Driver) aufsMount(ro []string, rw, target, mountLabel string) (err erro return } + +// useDirperm checks dirperm1 mount option can be used with the current +// version of aufs. +func useDirperm() bool { + enableDirpermLock.Do(func() { + base, err := ioutil.TempDir("", "docker-aufs-base") + if err != nil { + log.Errorf("error checking dirperm1: %v", err) + return + } + defer os.RemoveAll(base) + + union, err := ioutil.TempDir("", "docker-aufs-union") + if err != nil { + log.Errorf("error checking dirperm1: %v", err) + return + } + defer os.RemoveAll(union) + + opts := fmt.Sprintf("br:%s,dirperm1,xino=/dev/shm/aufs.xino", base) + if err := mount("none", union, "aufs", 0, opts); err != nil { + return + } + enableDirperm = true + if err := Unmount(union); err != nil { + log.Errorf("error checking dirperm1: failed to unmount %v", err) + } + }) + return enableDirperm +} From d7bbe2fcb5bf44f2fbcfa472e2a06c83d5d3aca1 Mon Sep 17 00:00:00 2001 From: "Daniel, Dao Quang Minh" Date: Thu, 26 Mar 2015 07:46:13 +0000 Subject: [PATCH 116/999] document dirperm1 fix for #783 in known issues Since `dirperm1` requires a more recent aufs patch than many current OS release, we cant remove #783 completely. This documents that docker will apply `dirperm1` automatically for systems that support it Signed-off-by: Daniel, Dao Quang Minh --- docs/sources/reference/builder.md | 10 ++++++++-- docs/sources/release-notes.md | 9 ++++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 9e56abf63..025b253b0 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -280,8 +280,14 @@ The cache for `RUN` instructions can be invalidated by `ADD` instructions. See - [Issue 783](https://github.com/docker/docker/issues/783) is about file permissions problems that can occur when using the AUFS file system. You - might notice it during an attempt to `rm` a file, for example. The issue - describes a workaround. + might notice it during an attempt to `rm` a file, for example. + + For systems that have recent aufs version (i.e., `dirperm1` mount option can + be set), docker will attempt to fix the issue automatically by mounting + the layers with `dirperm1` option. More details on `dirperm1` option can be + found at [`aufs` man page](http://aufs.sourceforge.net/aufs3/man.html) + + If your system doesnt have support for `dirperm1`, the issue describes a workaround. ## CMD diff --git a/docs/sources/release-notes.md b/docs/sources/release-notes.md index 37ae6761a..fe79d881d 100644 --- a/docs/sources/release-notes.md +++ b/docs/sources/release-notes.md @@ -57,7 +57,14 @@ impact on users. This list will be updated as issues are resolved. * **Unexpected File Permissions in Containers** An idiosyncrasy in AUFS prevents permissions from propagating predictably between upper and lower layers. This can cause issues with accessing private -keys, database instances, etc. For complete information and workarounds see +keys, database instances, etc. + +For systems that have recent aufs version (i.e., `dirperm1` mount option can +be set), docker will attempt to fix the issue automatically by mounting +the layers with `dirperm1` option. More details on `dirperm1` option can be +found at [`aufs` man page](http://aufs.sourceforge.net/aufs3/man.html) + +For complete information and workarounds see [Github Issue 783](https://github.com/docker/docker/issues/783). * **Docker Hub incompatible with Safari 8** From b38e11b3c4a84e35bfd76bff8133a5338cd69423 Mon Sep 17 00:00:00 2001 From: Anes Hasicic Date: Thu, 26 Mar 2015 11:36:13 +0100 Subject: [PATCH 117/999] Removed redundant err == nil check Signed-off-by: Anes Hasicic --- pkg/truncindex/truncindex_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/truncindex/truncindex_test.go b/pkg/truncindex/truncindex_test.go index f46a662d7..7253f6a4c 100644 --- a/pkg/truncindex/truncindex_test.go +++ b/pkg/truncindex/truncindex_test.go @@ -60,7 +60,7 @@ func TestTruncIndex(t *testing.T) { assertIndexGet(t, index, id[:1], "", true) // An ambiguous id prefix should return an error - if _, err := index.Get(id[:4]); err == nil || err == nil { + if _, err := index.Get(id[:4]); err == nil { t.Fatal("An ambiguous id prefix should return an error") } From 9b876b9c03f3ab5c2fecc5bc7a1501970f487b2a Mon Sep 17 00:00:00 2001 From: VladimirAus Date: Thu, 26 Mar 2015 21:09:35 +1000 Subject: [PATCH 118/999] #11585: README for pkg/signal. Signed-off-by: Vladimir Roudakov Signed-off-by: VladimirAus --- pkg/signal/README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 pkg/signal/README.md diff --git a/pkg/signal/README.md b/pkg/signal/README.md new file mode 100644 index 000000000..2b237a594 --- /dev/null +++ b/pkg/signal/README.md @@ -0,0 +1 @@ +This package provides helper functions for dealing with signals across various operating systems \ No newline at end of file From 3b8d4bb82ba6abf728cd40c838bfd665f8d10639 Mon Sep 17 00:00:00 2001 From: Michal Minar Date: Thu, 26 Mar 2015 10:27:10 +0100 Subject: [PATCH 119/999] Consider tag updated also in case repo does not exist This patch causes `The image you are pulling has been verified` status message to be produced also when the repository is pulled for the first time. Signed-off-by: Michal Minar --- graph/pull.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graph/pull.go b/graph/pull.go index 0a6b2800c..90206ea8b 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -602,6 +602,8 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri if _, exists := repo[tag]; !exists { tagUpdated = true } + } else { + tagUpdated = true } } From 389d0ae45327926a9d20b2a3e6c15499f3c9c570 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Thu, 26 Mar 2015 07:31:39 -0700 Subject: [PATCH 120/999] Fix TestBuildResourceConstraintsAreUsed Cpuset test Set cpuset to "0" so that it works on single core machines. W/o this (and set to "1") we'll see something like this error when running: System error: write /cgroup/cpuset/docker/66689499bbd08cd8dccc9b7bfd1d6b34e85d73ce8c84d3c69b5e91944322da60/docker/79d7c548b58c85c4cfad6cd01eb7c3b30db254d1014c496137edd93ddc528a6f/cpuset.cpus: invalid argument" Signed-off-by: Doug Davis --- integration-cli/docker_cli_build_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index f81d43181..e4ab58e53 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5524,7 +5524,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { t.Fatal(err) } - cmd := exec.Command(dockerBinary, "build", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=1", "--cpu-shares=100", "-t", name, ".") + cmd := exec.Command(dockerBinary, "build", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpu-shares=100", "-t", name, ".") cmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(cmd) @@ -5555,7 +5555,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { t.Fatal(err, cfg) } mem := int64(c1.Memory) - if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "1" || c1.CpuShares != 100 { + if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpuShares != 100 { t.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpuShares: %d", mem, c1.MemorySwap, c1.CpusetCpus, c1.CpuShares) } @@ -5574,7 +5574,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { t.Fatal(err, cfg) } mem = int64(c2.Memory) - if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "1" || c2.CpuShares == 100 { + if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpuShares == 100 { t.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpuShares: %d", mem, c2.MemorySwap, c2.CpusetCpus, c2.CpuShares) } From a4609a1dfbcfb2fea10aa2be15124e0809528d04 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Wed, 25 Mar 2015 07:48:35 -0700 Subject: [PATCH 121/999] Removing sudo from examples We now have instructions in our Unix installs about setting up docker group to avoid sudo. Also, Mac/Windows shouldn't use sudo. So, I've removed sudo from our examples and added a section at the top reminding them that if they have to use sudo to run docker they can change that. Signed-off-by: Mary Anthony --- docs/sources/reference/run.md | 116 ++++++++++++++++++---------------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 3023da69c..b1d0e92bd 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -24,26 +24,30 @@ other `docker` command. The basic `docker run` command takes this form: - $ sudo docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...] + $ docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...] To learn how to interpret the types of `[OPTIONS]`, see [*Option types*](/reference/commandline/cli/#option-types). -The list of `[OPTIONS]` breaks down into two groups: +The `run` options control the image's runtime behavior in a container. These +settings affect: -1. Settings exclusive to operators, including: - * Detached or Foreground running, - * Container Identification, - * Network settings, and - * Runtime Constraints on CPU and Memory - * Privileges and LXC Configuration -2. Settings shared between operators and developers, where operators can - override defaults developers set in images at build time. + * detached or foreground running + * container identification + * network settings + * runtime constraints on CPU and memory + * privileges and LXC configuration + +An image developer may set defaults for these same settings when they create the +image using the `docker build` command. Operators, however, can override all +defaults set by the developer using the `run` options. And, operators can also +override nearly all the defaults set by the Docker runtime itself. -Together, the `docker run [OPTIONS]` give the operator complete control over runtime -behavior, allowing them to override all defaults set by -the developer during `docker build` and nearly all the defaults set by -the Docker runtime itself. +Finally, depending on your Docker system configuration, you may be required to +preface each `docker` command with `sudo`. To avoid having to use `sudo` with +the `docker` command, your system administrator can create a Unix group called +`docker` and add users to it. For more information about this configuration, +refer to the Docker installation documentation for your operating system. ## Operator exclusive options @@ -99,13 +103,13 @@ streams]( https://github.com/docker/docker/blob/ specify to which of the three standard streams (`STDIN`, `STDOUT`, `STDERR`) you'd like to connect instead, as in: - $ sudo docker run -a stdin -a stdout -i -t ubuntu /bin/bash + $ docker run -a stdin -a stdout -i -t ubuntu /bin/bash For interactive processes (like a shell), you must use `-i -t` together in order to allocate a tty for the container process. `-i -t` is often written `-it` as you'll see in later examples. Specifying `-t` is forbidden when the client standard output is redirected or piped, such as in: -`echo test | sudo docker run -i busybox cat`. +`echo test | docker run -i busybox cat`. ## Container identification @@ -163,7 +167,7 @@ on the system. For example, you could build a container with debugging tools like `strace` or `gdb`, but want to use these tools when debugging processes within the container. - $ sudo docker run --pid=host rhel7 strace -p 1234 + $ docker run --pid=host rhel7 strace -p 1234 This command would allow you to use `strace` inside the container on pid 1234 on the host. @@ -288,9 +292,9 @@ Example running a Redis container with Redis binding to `localhost` then running the `redis-cli` command and connecting to the Redis server over the `localhost` interface. - $ sudo docker run -d --name redis example/redis --bind 127.0.0.1 + $ docker run -d --name redis example/redis --bind 127.0.0.1 $ # use the redis container's network stack to access localhost - $ sudo docker run --rm -it --net container:redis example/redis-cli -h 127.0.0.1 + $ docker run --rm -it --net container:redis example/redis-cli -h 127.0.0.1 ### Managing /etc/hosts @@ -298,7 +302,7 @@ Your container will have lines in `/etc/hosts` which define the hostname of the container itself as well as `localhost` and a few other common things. The `--add-host` flag can be used to add additional lines to `/etc/hosts`. - $ sudo docker run -it --add-host db-static:86.75.30.9 ubuntu cat /etc/hosts + $ docker run -it --add-host db-static:86.75.30.9 ubuntu cat /etc/hosts 172.17.0.22 09d03f76bf2c fe00::0 ip6-localnet ff00::0 ip6-mcastprefix @@ -374,7 +378,7 @@ for a container can be obtained via [`docker inspect`]( /reference/commandline/cli/#inspect). For example, to get the number of restarts for container "my-container"; - $ sudo docker inspect -f "{{ .RestartCount }}" my-container + $ docker inspect -f "{{ .RestartCount }}" my-container # 2 Or, to get the last time the container was (re)started; @@ -388,12 +392,12 @@ results in an error. ###Examples - $ sudo docker run --restart=always redis + $ docker run --restart=always redis This will run the `redis` container with a restart policy of **always** so that if the container exits, Docker will restart it. - $ sudo docker run --restart=on-failure:10 redis + $ docker run --restart=on-failure:10 redis This will run the `redis` container with a restart policy of **on-failure** and a maximum restart count of 10. If the `redis` container exits with a @@ -427,23 +431,23 @@ the `--security-opt` flag. For example, you can specify the MCS/MLS level, a requirement for MLS systems. Specifying the level in the following command allows you to share the same content between containers. - $ sudo docker run --security-opt label:level:s0:c100,c200 -i -t fedora bash + $ docker run --security-opt label:level:s0:c100,c200 -i -t fedora bash An MLS example might be: - $ sudo docker run --security-opt label:level:TopSecret -i -t rhel7 bash + $ docker run --security-opt label:level:TopSecret -i -t rhel7 bash To disable the security labeling for this container versus running with the `--permissive` flag, use the following command: - $ sudo docker run --security-opt label:disable -i -t fedora bash + $ docker run --security-opt label:disable -i -t fedora bash If you want a tighter security policy on the processes within a container, you can specify an alternate type for the container. You could run a container that is only allowed to listen on Apache ports by executing the following command: - $ sudo docker run --security-opt label:type:svirt_apache_t -i -t centos bash + $ docker run --security-opt label:type:svirt_apache_t -i -t centos bash Note: @@ -511,25 +515,25 @@ We have four ways to set memory usage: Examples: - $ sudo docker run -ti ubuntu:14.04 /bin/bash + $ docker run -ti ubuntu:14.04 /bin/bash We set nothing about memory, this means the processes in the container can use as much memory and swap memory as they need. - $ sudo docker run -ti -m 300M --memory-swap -1 ubuntu:14.04 /bin/bash + $ docker run -ti -m 300M --memory-swap -1 ubuntu:14.04 /bin/bash We set memory limit and disabled swap memory limit, this means the processes in the container can use 300M memory and as much swap memory as they need (if the host supports swap memory). - $ sudo docker run -ti -m 300M ubuntu:14.04 /bin/bash + $ docker run -ti -m 300M ubuntu:14.04 /bin/bash We set memory limit only, this means the processes in the container can use 300M memory and 300M swap memory, by default, the total virtual memory size (--memory-swap) will be set as double of memory, in this case, memory + swap would be 2*300M, so processes can use 300M swap memory as well. - $ sudo docker run -ti -m 300M --memory-swap 1G ubuntu:14.04 /bin/bash + $ docker run -ti -m 300M --memory-swap 1G ubuntu:14.04 /bin/bash We set both memory and swap memory, so the processes in the container can use 300M memory and 700M swap memory. @@ -575,11 +579,11 @@ We can set cpus in which to allow execution for containers. Examples: - $ sudo docker run -ti --cpuset-cpus="1,3" ubuntu:14.04 /bin/bash + $ docker run -ti --cpuset-cpus="1,3" ubuntu:14.04 /bin/bash This means processes in container can be executed on cpu 1 and cpu 3. - $ sudo docker run -ti --cpuset-cpus="0-2" ubuntu:14.04 /bin/bash + $ docker run -ti --cpuset-cpus="0-2" ubuntu:14.04 /bin/bash This means processes in container can be executed on cpu 0, cpu 1 and cpu 2. @@ -610,23 +614,23 @@ If you want to limit access to a specific device or devices you can use the `--device` flag. It allows you to specify one or more devices that will be accessible within the container. - $ sudo docker run --device=/dev/snd:/dev/snd ... + $ docker run --device=/dev/snd:/dev/snd ... By default, the container will be able to `read`, `write`, and `mknod` these devices. This can be overridden using a third `:rwm` set of options to each `--device` flag: - $ sudo docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc + $ docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc Command (m for help): q - $ sudo docker run --device=/dev/sda:/dev/xvdc:r --rm -it ubuntu fdisk /dev/xvdc + $ docker run --device=/dev/sda:/dev/xvdc:r --rm -it ubuntu fdisk /dev/xvdc You will not be able to write the partition table. Command (m for help): q - $ sudo docker run --device=/dev/sda:/dev/xvdc:w --rm -it ubuntu fdisk /dev/xvdc + $ docker run --device=/dev/sda:/dev/xvdc:w --rm -it ubuntu fdisk /dev/xvdc crash.... - $ sudo docker run --device=/dev/sda:/dev/xvdc:m --rm -it ubuntu fdisk /dev/xvdc + $ docker run --device=/dev/sda:/dev/xvdc:m --rm -it ubuntu fdisk /dev/xvdc fdisk: unable to open /dev/xvdc: Operation not permitted In addition to `--privileged`, the operator can have fine grain control over the @@ -634,23 +638,23 @@ capabilities using `--cap-add` and `--cap-drop`. By default, Docker has a defaul list of capabilities that are kept. Both flags support the value `all`, so if the operator wants to have all capabilities but `MKNOD` they could use: - $ sudo docker run --cap-add=ALL --cap-drop=MKNOD ... + $ docker run --cap-add=ALL --cap-drop=MKNOD ... For interacting with the network stack, instead of using `--privileged` they should use `--cap-add=NET_ADMIN` to modify the network interfaces. - $ sudo docker run -t -i --rm ubuntu:14.04 ip link add dummy0 type dummy + $ docker run -t -i --rm ubuntu:14.04 ip link add dummy0 type dummy RTNETLINK answers: Operation not permitted - $ sudo docker run -t -i --rm --cap-add=NET_ADMIN ubuntu:14.04 ip link add dummy0 type dummy + $ docker run -t -i --rm --cap-add=NET_ADMIN ubuntu:14.04 ip link add dummy0 type dummy To mount a FUSE based filesystem, you need to combine both `--cap-add` and `--device`: - $ sudo docker run --rm -it --cap-add SYS_ADMIN sshfs sshfs sven@10.10.10.20:/home/sven /mnt + $ docker run --rm -it --cap-add SYS_ADMIN sshfs sshfs sven@10.10.10.20:/home/sven /mnt fuse: failed to open /dev/fuse: Operation not permitted - $ sudo docker run --rm -it --device /dev/fuse sshfs sshfs sven@10.10.10.20:/home/sven /mnt + $ docker run --rm -it --device /dev/fuse sshfs sshfs sven@10.10.10.20:/home/sven /mnt fusermount: mount failed: Operation not permitted - $ sudo docker run --rm -it --cap-add SYS_ADMIN --device /dev/fuse sshfs + $ docker run --rm -it --cap-add SYS_ADMIN --device /dev/fuse sshfs # sshfs sven@10.10.10.20:/home/sven /mnt The authenticity of host '10.10.10.20 (10.10.10.20)' can't be established. ECDSA key fingerprint is 25:34:85:75:25:b0:17:46:05:19:04:93:b5:dd:5f:c6. @@ -727,7 +731,7 @@ Dockerfile instruction and how the operator can override that setting. Recall the optional `COMMAND` in the Docker commandline: - $ sudo docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...] + $ docker run [OPTIONS] IMAGE[:TAG|@DIGEST] [COMMAND] [ARG...] This command is optional because the person who created the `IMAGE` may have already provided a default `COMMAND` using the Dockerfile `CMD` @@ -754,12 +758,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`): - $ sudo 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: - $ sudo docker run -i -t --entrypoint /bin/bash example/redis -c ls -l - $ sudo 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) @@ -846,7 +850,7 @@ Additionally, the operator can **set any environment variable** in the container by using one or more `-e` flags, even overriding those mentioned above, or already defined by the developer with a Dockerfile `ENV`: - $ sudo 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 @@ -864,23 +868,23 @@ information for connecting to the service container. Let's imagine we have a container running Redis: # Start the service container, named redis-name - $ sudo docker run -d --name redis-name dockerfiles/redis + $ docker run -d --name redis-name dockerfiles/redis 4241164edf6f5aca5b0e9e4c9eccd899b0b8080c64c0cd26efe02166c73208f3 # The redis-name container exposed port 6379 - $ sudo docker ps + $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4241164edf6f $ dockerfiles/redis:latest /redis-stable/src/re 5 seconds ago Up 4 seconds 6379/tcp redis-name # Note that there are no public ports exposed since we didn᾿t use -p or -P - $ sudo docker port 4241164edf6f 6379 + $ docker port 4241164edf6f 6379 2014/01/25 00:55:38 Error: No public port '6379' published for 4241164edf6f Yet we can get information about the Redis container's exposed ports with `--link`. Choose an alias that will form a valid environment variable! - $ sudo 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 @@ -897,15 +901,15 @@ valid environment variable! And we can use that information to connect from another container as a client: - $ sudo 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> Docker will also map the private IP address to the alias of a linked container by inserting an entry into `/etc/hosts`. You can use this mechanism to communicate with a linked container by its alias: - $ sudo docker run -d --name servicename busybox sleep 30 - $ sudo docker run -i -t --link servicename:servicealias busybox ping -c 1 servicealias + $ docker run -d --name servicename busybox sleep 30 + $ docker run -i -t --link servicename:servicealias busybox ping -c 1 servicealias If you restart the source container (`servicename` in this case), the recipient container's `/etc/hosts` entry will be automatically updated. From d68d5f2e4bf7f527e06d85ec4ed8cd3917a3fd7f Mon Sep 17 00:00:00 2001 From: "Daniel, Dao Quang Minh" Date: Thu, 26 Mar 2015 17:58:49 +0000 Subject: [PATCH 122/999] print dirperm1 supported status in docker info It's easier for users to check if their systems support dirperm1 just by using docker info Signed-off-by: Daniel, Dao Quang Minh --- daemon/graphdriver/aufs/aufs.go | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index 573f41372..7d36be4dd 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -156,6 +156,7 @@ func (a *Driver) Status() [][2]string { {"Root Dir", a.rootPath()}, {"Backing Filesystem", backingFs}, {"Dirs", fmt.Sprintf("%d", len(ids))}, + {"Dirperm1 Supported", fmt.Sprintf("%v", useDirperm())}, } } From 29d01b7ddedb5d1b5ec2262e2d961b737cbc6f38 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Thu, 26 Mar 2015 11:12:37 -0700 Subject: [PATCH 123/999] Removing sudo from command line examples We now advise people to configure docker group and add to sudo. Mac shouldn't use sudo. Removed sudo from command examples. Left in installation to be removed in installation doc sweep -- removing requires finer grain control. Signed-off-by: Mary Anthony --- docs/man/Dockerfile.5.md | 4 +- docs/man/docker-history.1.md | 2 +- docs/man/docker-load.1.md | 6 +- docs/man/docker-pull.1.md | 8 +- docs/man/docker-run.1.md | 10 +- docs/man/docker-save.1.md | 4 +- docs/man/docker-search.1.md | 4 +- docs/man/docker-stats.1.md | 2 +- docs/man/docker-top.1.md | 2 +- docs/man/docker-wait.1.md | 4 +- .../articles/ambassador_pattern_linking.md | 26 +- docs/sources/articles/baseimages.md | 4 +- docs/sources/articles/basics.md | 60 ++-- .../articles/cfengine_process_management.md | 2 +- docs/sources/articles/chef.md | 4 +- docs/sources/articles/networking.md | 14 +- docs/sources/articles/puppet.md | 4 +- docs/sources/articles/registry_mirror.md | 12 +- docs/sources/articles/using_supervisord.md | 4 +- docs/sources/docker-hub/repos.md | 2 +- docs/sources/examples/apt-cacher-ng.md | 16 +- docs/sources/examples/couchdb_data_volumes.md | 8 +- docs/sources/examples/mongodb.md | 22 +- docs/sources/examples/nodejs_web_app.md | 12 +- docs/sources/examples/postgresql_service.md | 10 +- .../sources/examples/running_redis_service.md | 6 +- docs/sources/examples/running_riak_service.md | 2 +- docs/sources/examples/running_ssh_service.md | 12 +- .../introduction/understanding-docker.md | 2 +- .../reference/api/hub_registry_spec.md | 2 +- docs/sources/reference/builder.md | 10 +- docs/sources/reference/commandline/cli.md | 307 +++++++++--------- docs/sources/terms/registry.md | 2 +- docs/sources/terms/repository.md | 6 +- docs/sources/userguide/dockerhub.md | 4 +- docs/sources/userguide/dockerimages.md | 36 +- docs/sources/userguide/dockerizing.md | 14 +- docs/sources/userguide/dockerlinks.md | 36 +- docs/sources/userguide/dockerrepos.md | 8 +- docs/sources/userguide/dockervolumes.md | 22 +- docs/sources/userguide/usingdocker.md | 38 +-- 41 files changed, 382 insertions(+), 371 deletions(-) diff --git a/docs/man/Dockerfile.5.md b/docs/man/Dockerfile.5.md index dc28d3e03..d29d96197 100644 --- a/docs/man/Dockerfile.5.md +++ b/docs/man/Dockerfile.5.md @@ -31,7 +31,7 @@ A Dockerfile is similar to a Makefile. # USAGE - sudo docker build . + docker build . -- Runs the steps and commits them, building a final image. The path to the source repository defines where to find the context of the @@ -41,7 +41,7 @@ A Dockerfile is similar to a Makefile. daemon. ``` - sudo docker build -t repository/tag . + docker build -t repository/tag . ``` -- specifies a repository and tag at which to save the new image if the build diff --git a/docs/man/docker-history.1.md b/docs/man/docker-history.1.md index 47350f887..24f928c29 100644 --- a/docs/man/docker-history.1.md +++ b/docs/man/docker-history.1.md @@ -26,7 +26,7 @@ Show the history of when and how an image was created. Only show numeric IDs. The default is *false*. # EXAMPLES - $ sudo docker history fedora + $ docker history fedora IMAGE CREATED CREATED BY SIZE 105182bb5e8b 5 days ago /bin/sh -c #(nop) ADD file:71356d2ad59aa3119d 372.7 MB 73bd853d2ea5 13 days ago /bin/sh -c #(nop) MAINTAINER Lokesh Mandvekar 0 B diff --git a/docs/man/docker-load.1.md b/docs/man/docker-load.1.md index 52eaa37a1..c04544368 100644 --- a/docs/man/docker-load.1.md +++ b/docs/man/docker-load.1.md @@ -24,11 +24,11 @@ Restores both images and tags. # EXAMPLES - $ sudo docker images + $ 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 + $ docker load --input fedora.tar + $ 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 diff --git a/docs/man/docker-pull.1.md b/docs/man/docker-pull.1.md index f1963df55..01abc0750 100644 --- a/docs/man/docker-pull.1.md +++ b/docs/man/docker-pull.1.md @@ -29,7 +29,7 @@ It is also possible to specify a non-default registry to pull from. # Note that if the image is previously downloaded then the status would be # 'Status: Image is up to date for fedora' - $ sudo docker pull fedora + $ docker pull fedora Pulling repository fedora ad57ef8d78d7: Download complete 105182bb5e8b: Download complete @@ -38,7 +38,7 @@ It is also possible to specify a non-default registry to pull from. Status: Downloaded newer image for fedora - $ sudo docker images + $ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE fedora rawhide ad57ef8d78d7 5 days ago 359.3 MB fedora 20 105182bb5e8b 5 days ago 372.7 MB @@ -49,7 +49,7 @@ It is also possible to specify a non-default registry to pull from. # Note that if the image is previously downloaded then the status would be # 'Status: Image is up to date for registry.hub.docker.com/fedora:20' - $ sudo docker pull registry.hub.docker.com/fedora:20 + $ docker pull registry.hub.docker.com/fedora:20 Pulling repository fedora 3f2fed40e4b0: Download complete 511136ea3c5a: Download complete @@ -57,7 +57,7 @@ It is also possible to specify a non-default registry to pull from. Status: Downloaded newer image for registry.hub.docker.com/fedora:20 - $ sudo docker images + $ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE fedora 20 3f2fed40e4b0 4 days ago 372.7 MB diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 9ac571738..bbcd93459 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -433,7 +433,7 @@ Host shows a shared memory segment with 7 pids attached, happens to be from http Now run a regular container, and it correctly does NOT see the shared memory segment from the host: ``` - $ sudo docker run -it shm ipcs -m + $ docker run -it shm ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status @@ -442,7 +442,7 @@ Now run a regular container, and it correctly does NOT see the shared memory seg Run a container with the new `--ipc=host` option, and it now sees the shared memory segment from the host httpd: ``` - $ sudo docker run -it --ipc=host shm ipcs -m + $ docker run -it --ipc=host shm ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status @@ -452,7 +452,7 @@ Testing `--ipc=container:CONTAINERID` mode: Start a container with a program to create a shared memory segment: ``` - sudo docker run -it shm bash + $ docker run -it shm bash $ sudo shm/shm_server & $ sudo ipcs -m @@ -462,7 +462,7 @@ Start a container with a program to create a shared memory segment: ``` Create a 2nd container correctly shows no shared memory segment from 1st container: ``` - $ sudo docker run shm ipcs -m + $ docker run shm ipcs -m ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status @@ -471,7 +471,7 @@ Create a 2nd container correctly shows no shared memory segment from 1st contain Create a 3rd container using the new --ipc=container:CONTAINERID option, now it shows the shared memory segment from the first: ``` - $ sudo docker run -it --ipc=container:ed735b2264ac shm ipcs -m + $ docker run -it --ipc=container:ed735b2264ac shm ipcs -m $ sudo ipcs -m ------ Shared Memory Segments -------- diff --git a/docs/man/docker-save.1.md b/docs/man/docker-save.1.md index 91be06c35..5f336ffd3 100644 --- a/docs/man/docker-save.1.md +++ b/docs/man/docker-save.1.md @@ -28,8 +28,8 @@ Stream to a file instead of STDOUT by using **-o**. Save all fedora repository images to a fedora-all.tar and save the latest fedora image to a fedora-latest.tar: - $ sudo docker save fedora > fedora-all.tar - $ sudo docker save --output=fedora-latest.tar fedora:latest + $ docker save fedora > fedora-all.tar + $ docker save --output=fedora-latest.tar fedora:latest $ ls -sh fedora-all.tar 721M fedora-all.tar $ ls -sh fedora-latest.tar diff --git a/docs/man/docker-search.1.md b/docs/man/docker-search.1.md index 0b9df1015..38fa92f17 100644 --- a/docs/man/docker-search.1.md +++ b/docs/man/docker-search.1.md @@ -41,7 +41,7 @@ is automated. Search the registry for the term 'fedora' and only display those images ranked 3 or higher: - $ sudo docker search -s 3 fedora + $ docker search -s 3 fedora NAME DESCRIPTION STARS OFFICIAL AUTOMATED mattdm/fedora A basic Fedora image corresponding roughly... 50 fedora (Semi) Official Fedora base image. 38 @@ -53,7 +53,7 @@ ranked 3 or higher: Search the registry for the term 'fedora' and only display automated images ranked 1 or higher: - $ sudo docker search -s 1 -t fedora + $ docker search -s 1 -t fedora NAME DESCRIPTION STARS OFFICIAL AUTOMATED goldmann/wildfly A WildFly application server running on a ... 3 [OK] tutum/fedora-20 Fedora 20 image with SSH access. For the r... 1 [OK] diff --git a/docs/man/docker-stats.1.md b/docs/man/docker-stats.1.md index 493e402fb..a1adc7ecb 100644 --- a/docs/man/docker-stats.1.md +++ b/docs/man/docker-stats.1.md @@ -21,7 +21,7 @@ Display a live stream of one or more containers' resource usage statistics Run **docker stats** with multiple containers. - $ sudo docker stats redis1 redis2 + $ docker stats redis1 redis2 CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O redis1 0.07% 796 KiB/64 MiB 1.21% 788 B/648 B redis2 0.07% 2.746 MiB/64 MiB 4.29% 1.266 KiB/648 B diff --git a/docs/man/docker-top.1.md b/docs/man/docker-top.1.md index be2bed221..c3bbf883f 100644 --- a/docs/man/docker-top.1.md +++ b/docs/man/docker-top.1.md @@ -22,7 +22,7 @@ Look up the running process of the container. ps-OPTION can be any of the Run **docker top** with the ps option of -x: - $ sudo docker top 8601afda2b -x + $ docker top 8601afda2b -x PID TTY STAT TIME COMMAND 16623 ? Ss 0:00 sleep 99999 diff --git a/docs/man/docker-wait.1.md b/docs/man/docker-wait.1.md index a1e2aa212..5f07bacc0 100644 --- a/docs/man/docker-wait.1.md +++ b/docs/man/docker-wait.1.md @@ -19,9 +19,9 @@ Block until a container stops, then print its exit code. # EXAMPLES - $ sudo docker run -d fedora sleep 99 + $ docker run -d fedora sleep 99 079b83f558a2bc52ecad6b2a5de13622d584e6bb1aea058c11b36511e85e7622 - $ sudo docker wait 079b83f558a2bc + $ docker wait 079b83f558a2bc 0 # HISTORY diff --git a/docs/sources/articles/ambassador_pattern_linking.md b/docs/sources/articles/ambassador_pattern_linking.md index 9b1b0329a..755fa4dc9 100644 --- a/docs/sources/articles/ambassador_pattern_linking.md +++ b/docs/sources/articles/ambassador_pattern_linking.md @@ -34,23 +34,23 @@ controlled entirely from the `docker run` parameters. Start actual Redis server on one Docker host - big-server $ sudo 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 - big-server $ sudo 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` - client-server $ sudo 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. - client-server $ sudo 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 @@ -62,19 +62,19 @@ does automatically (with a tiny amount of `sed`) On the Docker host (192.168.1.52) that Redis will run on: # start actual redis server - $ sudo docker run -d --name redis crosbymichael/redis + $ docker run -d --name redis crosbymichael/redis # get a redis-cli container for connection testing - $ sudo docker pull relateiq/redis-cli + $ docker pull relateiq/redis-cli # test the redis server by talking to it directly - $ sudo 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 - $ sudo 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 `env`: @@ -96,9 +96,9 @@ containers `env`: This environment is used by the ambassador `socat` script to expose Redis to the world (via the `-p 6379:6379` port mapping): - $ sudo docker rm redis_ambassador + $ docker rm redis_ambassador $ sudo ./contrib/mkimage-unittest.sh - $ sudo 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 @@ -107,14 +107,14 @@ Now ping the Redis server via the ambassador: Now go to a different server: $ sudo ./contrib/mkimage-unittest.sh - $ sudo 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 And get the `redis-cli` image so we can talk over the ambassador bridge. - $ sudo docker pull relateiq/redis-cli - $ sudo docker run -i -t --rm --link redis_ambassador:redis relateiq/redis-cli + $ docker pull relateiq/redis-cli + $ docker run -i -t --rm --link redis_ambassador:redis relateiq/redis-cli redis 172.17.0.160:6379> ping PONG diff --git a/docs/sources/articles/baseimages.md b/docs/sources/articles/baseimages.md index 5a5addd1a..701f432ff 100644 --- a/docs/sources/articles/baseimages.md +++ b/docs/sources/articles/baseimages.md @@ -22,9 +22,9 @@ use to build Ubuntu images. It can be as simple as this to create an Ubuntu base image: $ sudo debootstrap raring raring > /dev/null - $ sudo tar -C raring -c . | sudo docker import - raring + $ sudo tar -C raring -c . | docker import - raring a29c15f1bf7a - $ sudo docker run raring cat /etc/lsb-release + $ docker run raring cat /etc/lsb-release DISTRIB_ID=Ubuntu DISTRIB_RELEASE=13.04 DISTRIB_CODENAME=raring diff --git a/docs/sources/articles/basics.md b/docs/sources/articles/basics.md index 4cdcab4aa..94264ece6 100644 --- a/docs/sources/articles/basics.md +++ b/docs/sources/articles/basics.md @@ -4,26 +4,30 @@ page_keywords: Examples, Usage, basic commands, docker, documentation, examples # First steps with Docker -## Check your Docker install - This guide assumes you have a working installation of Docker. To check your Docker install, run the following command: # Check that you have a working install - $ sudo docker info + $ docker info If you get `docker: command not found` or something like `/var/lib/docker/repositories: permission denied` you may have an incomplete Docker installation or insufficient privileges to access -Docker on your machine. +Docker on your machine. Please + +Additionally, depending on your Docker system configuration, you may be required +to preface each `docker` command with `sudo`. To avoid having to use `sudo` with +the `docker` command, your system administrator can create a Unix group called +`docker` and add users to it. + +For more information about installing Docker or `sudo` configuration, refer to +the [installation](/installation) instructions for your operating system. -Please refer to [*Installation*](/installation) -for installation instructions. ## Download a pre-built image # Download an ubuntu image - $ sudo docker pull ubuntu + $ docker pull ubuntu This will find the `ubuntu` image by name on [*Docker Hub*](/userguide/dockerrepos/#searching-for-images) @@ -46,7 +50,7 @@ image cache. # To detach the tty without exiting the shell, # use the escape sequence Ctrl-p + Ctrl-q # note: This will continue to exist in a stopped state once exited (see "docker ps -a") - $ sudo docker run -i -t ubuntu /bin/bash + $ docker run -i -t ubuntu /bin/bash ## Bind Docker to another host/port or a Unix socket @@ -92,7 +96,7 @@ Run Docker in daemon mode: Download an `ubuntu` image: - $ sudo docker -H :5555 pull ubuntu + $ docker -H :5555 pull ubuntu You can use multiple `-H`, for example, if you want to listen on both TCP and a Unix socket @@ -100,60 +104,60 @@ TCP and a Unix socket # Run docker in daemon mode $ sudo /docker -H tcp://127.0.0.1:2375 -H unix:///var/run/docker.sock -d & # Download an ubuntu image, use default Unix socket - $ sudo docker pull ubuntu + $ docker pull ubuntu # OR use the TCP port - $ sudo docker -H tcp://127.0.0.1:2375 pull ubuntu + $ docker -H tcp://127.0.0.1:2375 pull ubuntu ## Starting a long-running worker process # Start a very useful long-running process - $ JOB=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done") + $ JOB=$(docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done") # Collect the output of the job so far - $ sudo docker logs $JOB + $ docker logs $JOB # Kill the job - $ sudo docker kill $JOB + $ docker kill $JOB ## Listing containers - $ sudo docker ps # Lists only running containers - $ sudo docker ps -a # Lists all containers + $ docker ps # Lists only running containers + $ docker ps -a # Lists all containers ## Controlling containers # Start a new container - $ JOB=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done") + $ JOB=$(docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done") # Stop the container - $ sudo docker stop $JOB + $ docker stop $JOB # Start the container - $ sudo docker start $JOB + $ docker start $JOB # Restart the container - $ sudo docker restart $JOB + $ docker restart $JOB # SIGKILL a container - $ sudo docker kill $JOB + $ docker kill $JOB # Remove a container - $ sudo docker stop $JOB # Container must be stopped to remove it - $ sudo docker rm $JOB + $ docker stop $JOB # Container must be stopped to remove it + $ docker rm $JOB ## Bind a service on a TCP port # Bind port 4444 of this container, and tell netcat to listen on it - $ JOB=$(sudo docker run -d -p 4444 ubuntu:12.10 /bin/nc -l 4444) + $ JOB=$(docker run -d -p 4444 ubuntu:12.10 /bin/nc -l 4444) # Which public port is NATed to my container? - $ PORT=$(sudo docker port $JOB 4444 | awk -F: '{ print $2 }') + $ PORT=$(docker port $JOB 4444 | awk -F: '{ print $2 }') # Connect to the public port $ echo hello world | nc 127.0.0.1 $PORT # Verify that the network connection worked - $ echo "Daemon received: $(sudo docker logs $JOB)" + $ echo "Daemon received: $(docker logs $JOB)" ## Committing (saving) a container state @@ -166,10 +170,10 @@ will be stored (as a diff). See which images you already have using the `docker images` command. # Commit your container to a new named image - $ sudo docker commit + $ docker commit # List your containers - $ sudo docker images + $ docker images You now have an image state from which you can create new instances. diff --git a/docs/sources/articles/cfengine_process_management.md b/docs/sources/articles/cfengine_process_management.md index e32b26639..a9441a6d3 100644 --- a/docs/sources/articles/cfengine_process_management.md +++ b/docs/sources/articles/cfengine_process_management.md @@ -94,7 +94,7 @@ your image with the docker build command, e.g., Start the container with `apache2` and `sshd` running and managed, forwarding a port to our SSH instance: - $ sudo docker run -p 127.0.0.1:222:22 -d managed_image "/usr/sbin/sshd" "/etc/init.d/apache2 start" + $ docker run -p 127.0.0.1:222:22 -d managed_image "/usr/sbin/sshd" "/etc/init.d/apache2 start" We now clearly see one of the benefits of the cfe-docker integration: it allows to start several processes as part of a normal `docker run` command. diff --git a/docs/sources/articles/chef.md b/docs/sources/articles/chef.md index cb70215c5..8fe0504ff 100644 --- a/docs/sources/articles/chef.md +++ b/docs/sources/articles/chef.md @@ -43,7 +43,7 @@ The next step is to pull a Docker image. For this, we have a resource: This is equivalent to running: - $ sudo docker pull samalba/docker-registry + $ docker pull samalba/docker-registry There are attributes available to control how long the cookbook will allow for downloading (5 minute default). @@ -68,7 +68,7 @@ managed by Docker. This is equivalent to running the following command, but under upstart: - $ sudo docker run --detach=true --publish='5000:5000' --env='SETTINGS_FLAVOR=local' --volume='/mnt/docker:/docker-storage' samalba/docker-registry + $ 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/articles/networking.md b/docs/sources/articles/networking.md index 7247d298d..754d9989c 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -723,7 +723,7 @@ the Internet. # The network, as seen from a container - $ sudo docker run -i -t --rm base /bin/bash + $ docker run -i -t --rm base /bin/bash $$ ip addr show eth0 24: eth0: mtu 1500 qdisc pfifo_fast state UP group default qlen 1000 @@ -908,14 +908,14 @@ Docker do all of the configuration: # At one shell, start a container and # leave its shell idle and running - $ sudo docker run -i -t --rm --net=none base /bin/bash + $ docker run -i -t --rm --net=none base /bin/bash root@63f36fc01b5f:/# # At another shell, learn the container process ID # and create its namespace entry in /var/run/netns/ # for the "ip netns" command we will be using below - $ sudo docker inspect -f '{{.State.Pid}}' 63f36fc01b5f + $ docker inspect -f '{{.State.Pid}}' 63f36fc01b5f 2778 $ pid=2778 $ sudo mkdir -p /var/run/netns @@ -1016,18 +1016,18 @@ the previous section to go something like this: # Start up two containers in two terminal windows - $ sudo docker run -i -t --rm --net=none base /bin/bash + $ docker run -i -t --rm --net=none base /bin/bash root@1f1f4c1f931a:/# - $ sudo docker run -i -t --rm --net=none base /bin/bash + $ docker run -i -t --rm --net=none base /bin/bash root@12e343489d2f:/# # Learn the container process IDs # and create their namespace entries - $ sudo docker inspect -f '{{.State.Pid}}' 1f1f4c1f931a + $ docker inspect -f '{{.State.Pid}}' 1f1f4c1f931a 2989 - $ sudo docker inspect -f '{{.State.Pid}}' 12e343489d2f + $ docker inspect -f '{{.State.Pid}}' 12e343489d2f 3004 $ sudo mkdir -p /var/run/netns $ sudo ln -s /proc/2989/ns/net /var/run/netns/2989 diff --git a/docs/sources/articles/puppet.md b/docs/sources/articles/puppet.md index d9a7ceb70..705285fba 100644 --- a/docs/sources/articles/puppet.md +++ b/docs/sources/articles/puppet.md @@ -47,7 +47,7 @@ defined type which can be used like so: This is equivalent to running: - $ sudo docker pull ubuntu + $ docker pull ubuntu Note that it will only be downloaded if an image of that name does not already exist. This is downloading a large binary so on first run can @@ -71,7 +71,7 @@ managed by Docker. This is equivalent to running the following command, but under upstart: - $ sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done" + $ docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done" Run also contains a number of optional parameters: diff --git a/docs/sources/articles/registry_mirror.md b/docs/sources/articles/registry_mirror.md index adc470d71..e928af12a 100644 --- a/docs/sources/articles/registry_mirror.md +++ b/docs/sources/articles/registry_mirror.md @@ -29,11 +29,11 @@ There are two steps to set up and use a local registry mirror. You will need to pass the `--registry-mirror` option to your Docker daemon on startup: - sudo docker --registry-mirror=http:// -d + docker --registry-mirror=http:// -d For example, if your mirror is serving on `http://10.0.0.2:5000`, you would run: - sudo docker --registry-mirror=http://10.0.0.2:5000 -d + docker --registry-mirror=http://10.0.0.2:5000 -d **NOTE:** Depending on your local host setup, you may be able to add the @@ -47,7 +47,7 @@ You will need to start a local registry mirror service. The functionality. For example, to run a local registry mirror that serves on port `5000` and mirrors the content at `registry-1.docker.io`: - sudo docker run -p 5000:5000 \ + docker run -p 5000:5000 \ -e STANDALONE=false \ -e MIRROR_SOURCE=https://registry-1.docker.io \ -e MIRROR_SOURCE_INDEX=https://index.docker.io \ @@ -58,7 +58,7 @@ port `5000` and mirrors the content at `registry-1.docker.io`: With your mirror running, pull an image that you haven't pulled before (using `time` to time it): - $ time sudo docker pull node:latest + $ time docker pull node:latest Pulling repository node [...] @@ -68,11 +68,11 @@ With your mirror running, pull an image that you haven't pulled before (using Now, remove the image from your local machine: - $ sudo docker rmi node:latest + $ docker rmi node:latest Finally, re-pull the image: - $ time sudo docker pull node:latest + $ time docker pull node:latest Pulling repository node [...] diff --git a/docs/sources/articles/using_supervisord.md b/docs/sources/articles/using_supervisord.md index 5806707ee..0c5557091 100644 --- a/docs/sources/articles/using_supervisord.md +++ b/docs/sources/articles/using_supervisord.md @@ -91,13 +91,13 @@ launches. We can now build our new image. - $ sudo docker build -t /supervisord . + $ docker build -t /supervisord . ## Running our Supervisor container Once We've got a built image we can launch a container from it. - $ sudo docker run -p 22 -p 80 -t -i /supervisord + $ docker run -p 22 -p 80 -t -i /supervisord 2013-11-25 18:53:22,312 CRIT Supervisor running as root (no user in config file) 2013-11-25 18:53:22,312 WARN Included extra file "/etc/supervisor/conf.d/supervisord.conf" during parsing 2013-11-25 18:53:22,342 INFO supervisord started with pid 1 diff --git a/docs/sources/docker-hub/repos.md b/docs/sources/docker-hub/repos.md index 576583584..35cd4f8cc 100644 --- a/docs/sources/docker-hub/repos.md +++ b/docs/sources/docker-hub/repos.md @@ -11,7 +11,7 @@ page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub You can `search` for all the publicly available repositories and images using Docker. - $ sudo docker search ubuntu + $ docker search ubuntu This will show you a list of the currently available repositories on the Docker Hub which match the provided keyword. diff --git a/docs/sources/examples/apt-cacher-ng.md b/docs/sources/examples/apt-cacher-ng.md index cd92cb59a..9a3631220 100644 --- a/docs/sources/examples/apt-cacher-ng.md +++ b/docs/sources/examples/apt-cacher-ng.md @@ -35,16 +35,16 @@ Use the following Dockerfile: To build the image using: - $ sudo docker build -t eg_apt_cacher_ng . + $ docker build -t eg_apt_cacher_ng . Then run it, mapping the exposed port to one on the host - $ sudo docker run -d -p 3142:3142 --name test_apt_cacher_ng eg_apt_cacher_ng + $ 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: - $ sudo docker logs -f test_apt_cacher_ng + $ docker logs -f test_apt_cacher_ng To get your Debian-based containers to use the proxy, you can do one of three things @@ -68,7 +68,7 @@ a local version of a common base: **Option 2** is good for testing, but will break other HTTP clients which obey `http_proxy`, such as `curl`, `wget` and others: - $ sudo docker run --rm -t -i -e http_proxy=http://dockerhost:3142/ debian bash + $ 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` @@ -78,7 +78,7 @@ Apt-cacher-ng has some tools that allow you to manage the repository, and they can be used by leveraging the `VOLUME` instruction, and the image we built to run the service: - $ sudo docker run --rm -t -i --volumes-from test_apt_cacher_ng eg_apt_cacher_ng bash + $ 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... @@ -102,6 +102,6 @@ instruction, and the image we built to run the service: Finally, clean up after your test by stopping and removing the container, and then removing the image. - $ sudo docker stop test_apt_cacher_ng - $ sudo docker rm test_apt_cacher_ng - $ sudo docker rmi eg_apt_cacher_ng + $ docker stop test_apt_cacher_ng + $ docker rm test_apt_cacher_ng + $ docker rmi eg_apt_cacher_ng diff --git a/docs/sources/examples/couchdb_data_volumes.md b/docs/sources/examples/couchdb_data_volumes.md index 8cd2408e4..483168ae2 100644 --- a/docs/sources/examples/couchdb_data_volumes.md +++ b/docs/sources/examples/couchdb_data_volumes.md @@ -16,7 +16,7 @@ different versions of CouchDB on the same data, etc. Note that we're marking `/var/lib/couchdb` as a data volume. - $ COUCH1=$(sudo docker run -d -p 5984 -v /var/lib/couchdb shykes/couchdb:2013-05-03) + $ COUCH1=$(docker run -d -p 5984 -v /var/lib/couchdb shykes/couchdb:2013-05-03) ## Add data to the first database @@ -24,19 +24,19 @@ We're assuming your Docker host is reachable at `localhost`. If not, replace `localhost` with the public IP of your Docker host. $ HOST=localhost - $ URL="http://$HOST:$(sudo docker port $COUCH1 5984 | grep -o '[1-9][0-9]*$')/_utils/" + $ URL="http://$HOST:$(docker port $COUCH1 5984 | grep -o '[1-9][0-9]*$')/_utils/" $ echo "Navigate to $URL in your browser, and use the couch interface to add data" ## Create second database This time, we're requesting shared access to `$COUCH1`'s volumes. - $ COUCH2=$(sudo docker run -d -p 5984 --volumes-from $COUCH1 shykes/couchdb:2013-05-03) + $ COUCH2=$(docker run -d -p 5984 --volumes-from $COUCH1 shykes/couchdb:2013-05-03) ## Browse data on the second database $ HOST=localhost - $ URL="http://$HOST:$(sudo docker port $COUCH2 5984 | grep -o '[1-9][0-9]*$')/_utils/" + $ URL="http://$HOST:$(docker port $COUCH2 5984 | grep -o '[1-9][0-9]*$')/_utils/" $ echo "Navigate to $URL in your browser. You should see the same data as in the first database"'!' Congratulations, you are now running two Couchdb containers, completely diff --git a/docs/sources/examples/mongodb.md b/docs/sources/examples/mongodb.md index c4fbf5768..376a8d3ff 100644 --- a/docs/sources/examples/mongodb.md +++ b/docs/sources/examples/mongodb.md @@ -100,9 +100,9 @@ With our `Dockerfile`, we can now build the MongoDB image using Docker. Unless experimenting, it is always a good practice to tag Docker images by passing the `--tag` option to `docker build` command. - # Format: sudo docker build --tag/-t / . + # Format: docker build --tag/-t / . # Example: - $ sudo docker build --tag my/repo . + $ docker build --tag my/repo . Once this command is issued, Docker will go through the `Dockerfile` and build the image. The final image will be tagged `my/repo`. @@ -114,13 +114,13 @@ All Docker image repositories can be hosted and shared on you need to be logged-in. # Log-in - $ sudo docker login + $ docker login Username: .. # Push the image - # Format: sudo docker push / - $ sudo docker push my/repo + # Format: docker push / + $ docker push my/repo The push refers to a repository [my/repo] (len: 1) Sending image list Pushing repository my/repo (1 tags) @@ -132,16 +132,16 @@ Using the MongoDB image we created, we can run one or more MongoDB instances as daemon process(es). # Basic way - # Usage: sudo docker run --name -d / - $ sudo docker run --name mongo_instance_001 -d my/repo + # Usage: docker run --name -d / + $ docker run --name mongo_instance_001 -d my/repo # Dockerized MongoDB, lean and mean! - # Usage: sudo docker run --name -d / --noprealloc --smallfiles - $ sudo docker run --name mongo_instance_001 -d my/repo --noprealloc --smallfiles + # Usage: docker run --name -d / --noprealloc --smallfiles + $ docker run --name mongo_instance_001 -d my/repo --noprealloc --smallfiles # Checking out the logs of a MongoDB container - # Usage: sudo docker logs - $ sudo docker logs mongo_instance_001 + # Usage: docker logs + $ docker logs mongo_instance_001 # Playing with MongoDB # Usage: mongo --port diff --git a/docs/sources/examples/nodejs_web_app.md b/docs/sources/examples/nodejs_web_app.md index 56f7687cd..1db61ae62 100644 --- a/docs/sources/examples/nodejs_web_app.md +++ b/docs/sources/examples/nodejs_web_app.md @@ -125,11 +125,11 @@ Go to the directory that has your `Dockerfile` and run the following command to build a Docker image. The `-t` flag lets you tag your image so it's easier to find later using the `docker images` command: - $ sudo docker build -t /centos-node-hello . + $ docker build -t /centos-node-hello . Your image will now be listed by Docker: - $ sudo docker images + $ docker images # Example REPOSITORY TAG ID CREATED @@ -142,15 +142,15 @@ Running your image with `-d` runs the container in detached mode, leaving the container running in the background. The `-p` flag redirects a public port to a private port in the container. Run the image you previously built: - $ sudo docker run -p 49160:8080 -d /centos-node-hello + $ docker run -p 49160:8080 -d /centos-node-hello Print the output of your app: # Get container ID - $ sudo docker ps + $ docker ps # Print app output - $ sudo docker logs + $ docker logs # Example Running on http://localhost:8080 @@ -159,7 +159,7 @@ Print the output of your app: To test your app, get the port of your app that Docker mapped: - $ sudo docker ps + $ docker ps # Example ID IMAGE COMMAND ... PORTS diff --git a/docs/sources/examples/postgresql_service.md b/docs/sources/examples/postgresql_service.md index 87960098f..091179533 100644 --- a/docs/sources/examples/postgresql_service.md +++ b/docs/sources/examples/postgresql_service.md @@ -72,11 +72,11 @@ Start by creating a new `Dockerfile`: Build an image from the Dockerfile assign it a name. - $ sudo docker build -t eg_postgresql . + $ docker build -t eg_postgresql . And run the PostgreSQL server container (in the foreground): - $ sudo docker run --rm -P --name pg_test eg_postgresql + $ docker run --rm -P --name pg_test eg_postgresql There are 2 ways to connect to the PostgreSQL server. We can use [*Link Containers*](/userguide/dockerlinks), or we can access it from our host @@ -93,7 +93,7 @@ Containers can be linked to another container's ports directly using `docker run`. This will set a number of environment variables that can then be used to connect: - $ sudo docker run --rm -t -i --link pg_test:pg eg_postgresql bash + $ 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 @@ host-mapped port to test as well. You need to use `docker ps` to find out what local host port the container is mapped to first: - $ sudo docker ps + $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5e24362f27f6 eg_postgresql:latest /usr/lib/postgresql/ About an hour ago Up About an hour 0.0.0.0:49153->5432/tcp pg_test $ psql -h localhost -p 49153 -d docker -U docker --password @@ -135,7 +135,7 @@ prompt, you can create a table and populate it. You can use the defined volumes to inspect the PostgreSQL log files and to backup your configuration and data: - $ sudo 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/running_redis_service.md b/docs/sources/examples/running_redis_service.md index 99036a042..a00db9896 100644 --- a/docs/sources/examples/running_redis_service.md +++ b/docs/sources/examples/running_redis_service.md @@ -20,7 +20,7 @@ image. Next we build an image from our `Dockerfile`. Replace `` with your own user name. - $ sudo docker build -t /redis . + $ docker build -t /redis . ## Run the service @@ -33,7 +33,7 @@ Importantly, we're not exposing any ports on our container. Instead we're going to use a container link to provide access to our Redis database. - $ sudo docker run --name redis -d /redis + $ docker run --name redis -d /redis ## Create your web application container @@ -43,7 +43,7 @@ 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. - $ sudo docker run --link redis:db -i -t ubuntu:14.04 /bin/bash + $ docker run --link redis:db -i -t ubuntu:14.04 /bin/bash Once inside our freshly created container we need to install Redis to get the `redis-cli` binary to test our connection. diff --git a/docs/sources/examples/running_riak_service.md b/docs/sources/examples/running_riak_service.md index 0b5323404..c3d83bf66 100644 --- a/docs/sources/examples/running_riak_service.md +++ b/docs/sources/examples/running_riak_service.md @@ -101,7 +101,7 @@ Populate it with the following program definitions: Now you should be able to build a Docker image for Riak: - $ sudo docker build -t "/riak" . + $ docker build -t "/riak" . ## Next steps diff --git a/docs/sources/examples/running_ssh_service.md b/docs/sources/examples/running_ssh_service.md index 445cfe525..e2fc3782d 100644 --- a/docs/sources/examples/running_ssh_service.md +++ b/docs/sources/examples/running_ssh_service.md @@ -33,15 +33,15 @@ quick access to a test container. Build the image using: - $ sudo docker build -t eg_sshd . + $ docker build -t eg_sshd . ## Run a `test_sshd` container Then run it. You can then use `docker port` to find out what host port the container's port 22 is mapped to: - $ sudo docker run -d -P --name test_sshd eg_sshd - $ sudo docker port test_sshd 22 + $ docker run -d -P --name test_sshd eg_sshd + $ docker port test_sshd 22 0.0.0.0:49154 And now you can ssh as `root` on the container's IP address (you can find it @@ -72,7 +72,7 @@ short script to do the same before you start `sshd -D` and then replace the Finally, clean up after your test by stopping and removing the container, and then removing the image. - $ sudo docker stop test_sshd - $ sudo docker rm test_sshd - $ sudo docker rmi eg_sshd + $ docker stop test_sshd + $ docker rm test_sshd + $ docker rmi eg_sshd diff --git a/docs/sources/introduction/understanding-docker.md b/docs/sources/introduction/understanding-docker.md index 9c9995972..263690217 100644 --- a/docs/sources/introduction/understanding-docker.md +++ b/docs/sources/introduction/understanding-docker.md @@ -198,7 +198,7 @@ then run. Either by using the `docker` binary or via the API, the Docker client tells the Docker daemon to run a container. - $ sudo docker run -i -t ubuntu /bin/bash + $ docker run -i -t ubuntu /bin/bash Let's break down this command. The Docker client is launched using the `docker` binary with the `run` option telling it to launch a new container. The bare diff --git a/docs/sources/reference/api/hub_registry_spec.md b/docs/sources/reference/api/hub_registry_spec.md index 26d4ffca3..f01007587 100644 --- a/docs/sources/reference/api/hub_registry_spec.md +++ b/docs/sources/reference/api/hub_registry_spec.md @@ -115,7 +115,7 @@ supports: It's possible to run: - $ sudo docker pull https:///repositories/samalba/busybox + $ docker pull https:///repositories/samalba/busybox In this case, Docker bypasses the Docker Hub. However the security is not guaranteed (in case Registry A is corrupted) because there won't be any diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 9e56abf63..46d0e21e8 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -26,7 +26,7 @@ This file will describe the steps to assemble the image. Then call `docker build` with the path of your source repository as the argument (for example, `.`): - $ sudo docker build . + $ docker build . The path to the source repository defines where to find the *context* of the build. The build is run by the Docker daemon, not by the CLI, so the @@ -49,7 +49,7 @@ directory. You can specify a repository and tag at which to save the new image if the build succeeds: - $ sudo docker build -t shykes/myapp . + $ docker build -t shykes/myapp . The Docker daemon will run your steps one-by-one, committing the result to a new image if necessary, before finally outputting the ID of your @@ -65,7 +65,7 @@ accelerating `docker build` significantly (indicated by `Using cache` - see the [`Dockerfile` Best Practices guide](/articles/dockerfile_best-practices/#build-cache) for more information): - $ sudo docker build -t SvenDowideit/ambassador . + $ docker build -t SvenDowideit/ambassador . Uploading context 10.24 kB Uploading context Step 1 : FROM docker-ut @@ -175,7 +175,7 @@ The following example shows the use of the `.dockerignore` file to exclude the `.git` directory from the context. Its effect can be seen in the changed size of the uploaded context. - $ sudo docker build . + $ docker build . Uploading context 18.829 MB Uploading context Step 0 : FROM busybox @@ -185,7 +185,7 @@ the uploaded context. ---> 99cc1ad10469 Successfully built 99cc1ad10469 $ echo ".git" > .dockerignore - $ sudo docker build . + $ docker build . Uploading context 6.76 MB Uploading context Step 0 : FROM busybox diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index a6c1990d0..e6f09a078 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -9,13 +9,20 @@ page_keywords: Docker, Docker documentation, CLI, command line To list available commands, either run `docker` with no parameters or execute `docker help`: - $ sudo docker + $ docker Usage: docker [OPTIONS] COMMAND [arg...] -H, --host=[]: The socket(s) to bind to in daemon mode, specified using one or more tcp://host:port, unix:///path/to/socket, fd://* or fd://socketfd. A self-sufficient runtime for Linux containers. ... +Depending on your Docker system configuration, you may be required +to preface each `docker` command with `sudo`. To avoid having to use `sudo` with +the `docker` command, your system administrator can create a Unix group called +`docker` and add users to it. + +For more information about installing Docker or `sudo` configuration, refer to +the [installation](/installation) instructions for your operating system. ## Environment Variables @@ -44,7 +51,7 @@ variables. ## Help To list the help on any command just execute the command, followed by the `--help` option. - $ sudo docker run --help + $ docker run --help Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] @@ -79,11 +86,11 @@ be set to the non-default value by explicitly setting them to `false`: Options like `-a=[]` indicate they can be specified multiple times: - $ sudo docker run -a stdin -a stdout -a stderr -i -t ubuntu /bin/bash + $ docker run -a stdin -a stdout -a stderr -i -t ubuntu /bin/bash Sometimes this can use a more complex value string, as for `-v`: - $ sudo docker run -v /host:/container example/mysql + $ docker run -v /host:/container example/mysql ### Strings and Integers @@ -184,19 +191,19 @@ time using multiple `-H` options: The Docker client will honor the `DOCKER_HOST` environment variable to set the `-H` flag for the client. - $ sudo docker -H tcp://0.0.0.0:2375 ps + $ docker -H tcp://0.0.0.0:2375 ps # or $ export DOCKER_HOST="tcp://0.0.0.0:2375" - $ sudo docker ps + $ docker ps # both are equal Setting the `DOCKER_TLS_VERIFY` environment variable to any value other than the empty string is equivalent to setting the `--tlsverify` flag. The following are equivalent: - $ sudo docker --tlsverify ps + $ docker --tlsverify ps # or $ export DOCKER_TLS_VERIFY=1 - $ sudo docker ps + $ docker ps The Docker client will honor the `HTTP_PROXY`, `HTTPS_PROXY`, and `NO_PROXY` environment variables (or the lowercase versions thereof). `HTTPS_PROXY` takes @@ -260,7 +267,7 @@ Currently supported options are: Example use: - $ sudo docker -d --storage-opt dm.basesize=20G + $ docker -d --storage-opt dm.basesize=20G * `dm.loopdatasize` @@ -270,7 +277,7 @@ Currently supported options are: Example use: - $ sudo docker -d --storage-opt dm.loopdatasize=200G + $ docker -d --storage-opt dm.loopdatasize=200G * `dm.loopmetadatasize` @@ -281,7 +288,7 @@ Currently supported options are: Example use: - $ sudo docker -d --storage-opt dm.loopmetadatasize=4G + $ docker -d --storage-opt dm.loopmetadatasize=4G * `dm.fs` @@ -290,7 +297,7 @@ Currently supported options are: Example use: - $ sudo docker -d --storage-opt dm.fs=xfs + $ docker -d --storage-opt dm.fs=xfs * `dm.mkfsarg` @@ -298,7 +305,7 @@ Currently supported options are: Example use: - $ sudo docker -d --storage-opt "dm.mkfsarg=-O ^has_journal" + $ docker -d --storage-opt "dm.mkfsarg=-O ^has_journal" * `dm.mountopt` @@ -306,7 +313,7 @@ Currently supported options are: Example use: - $ sudo docker -d --storage-opt dm.mountopt=nodiscard + $ docker -d --storage-opt dm.mountopt=nodiscard * `dm.datadev` @@ -318,7 +325,7 @@ Currently supported options are: Example use: - $ sudo docker -d \ + $ docker -d \ --storage-opt dm.datadev=/dev/sdb1 \ --storage-opt dm.metadatadev=/dev/sdc1 @@ -336,7 +343,7 @@ Currently supported options are: Example use: - $ sudo docker -d \ + $ docker -d \ --storage-opt dm.datadev=/dev/sdb1 \ --storage-opt dm.metadatadev=/dev/sdc1 @@ -347,7 +354,7 @@ Currently supported options are: Example use: - $ sudo docker -d --storage-opt dm.blocksize=512K + $ docker -d --storage-opt dm.blocksize=512K * `dm.blkdiscard` @@ -361,7 +368,7 @@ Currently supported options are: Example use: - $ sudo docker -d --storage-opt dm.blkdiscard=false + $ docker -d --storage-opt dm.blkdiscard=false ### Docker exec-driver option @@ -478,8 +485,8 @@ attaching to a tty-enabled container (i.e.: launched with `-t`). #### Examples - $ sudo docker run -d --name topdemo ubuntu /usr/bin/top -b) - $ sudo docker attach topdemo + $ docker run -d --name topdemo ubuntu /usr/bin/top -b) + $ docker attach topdemo 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 @@ -516,14 +523,14 @@ attaching to a tty-enabled container (i.e.: launched with `-t`). And in this second example, you can see the exit code returned by the `bash` process is returned by the `docker attach` command to its caller too: - $ sudo docker run --name test -d -it debian + $ docker run --name test -d -it debian 275c44472aebd77c926d4527885bb09f2f6db21d878c75f0a1c212c03d3bcfab - $ sudo docker attach test + $ docker attach test $$ exit 13 exit $ echo $? 13 - $ sudo docker ps -a | grep test + $ docker ps -a | grep test 275c44472aeb debian:7 "/bin/bash" 26 seconds ago Exited (13) 17 seconds ago test ## build @@ -636,7 +643,7 @@ See also: #### Examples - $ sudo docker build . + $ docker build . Uploading context 10240 bytes Step 1 : FROM busybox Pulling repository busybox @@ -681,7 +688,7 @@ If you wish to keep the intermediate containers after the build is complete, you must use `--rm=false`. This does not affect the build cache. - $ sudo docker build . + $ docker build . Uploading context 18.829 MB Uploading context Step 0 : FROM busybox @@ -691,7 +698,7 @@ affect the build cache. ---> 99cc1ad10469 Successfully built 99cc1ad10469 $ echo ".git" > .dockerignore - $ sudo docker build . + $ docker build . Uploading context 6.76 MB Uploading context Step 0 : FROM busybox @@ -705,25 +712,25 @@ This example shows the use of the `.dockerignore` file to exclude the `.git` directory from the context. Its effect can be seen in the changed size of the uploaded context. - $ sudo docker build -t vieux/apache:2.0 . + $ docker build -t vieux/apache:2.0 . This will build like the previous example, but it will then tag the resulting image. The repository name will be `vieux/apache` and the tag will be `2.0` - $ sudo docker build - < Dockerfile + $ docker build - < Dockerfile This will read a Dockerfile from `STDIN` without context. Due to the lack of a context, no contents of any local directory will be sent to the Docker daemon. Since there is no context, a Dockerfile `ADD` only works if it refers to a remote URL. - $ sudo docker build - < context.tar.gz + $ docker build - < context.tar.gz This will build an image for a compressed context read from `STDIN`. Supported formats are: bzip2, gzip and xz. - $ sudo docker build github.com/creack/docker-firefox + $ docker build github.com/creack/docker-firefox This will clone the GitHub repository and use the cloned repository as context. The Dockerfile at the root of the @@ -731,21 +738,21 @@ repository is used as Dockerfile. Note that you can specify an arbitrary Git repository by using the `git://` or `git@` schema. - $ sudo docker build -f Dockerfile.debug . + $ docker build -f Dockerfile.debug . This will use a file called `Dockerfile.debug` for the build instructions instead of `Dockerfile`. - $ sudo docker build -f dockerfiles/Dockerfile.debug -t myapp_debug . - $ sudo docker build -f dockerfiles/Dockerfile.prod -t myapp_prod . + $ docker build -f dockerfiles/Dockerfile.debug -t myapp_debug . + $ docker build -f dockerfiles/Dockerfile.prod -t myapp_prod . The above commands will build the current build context (as specified by the `.`) twice, once using a debug version of a `Dockerfile` and once using a production version. $ cd /home/me/myapp/some/dir/really/deep - $ sudo docker build -f /home/me/myapp/dockerfiles/debug /home/me/myapp - $ sudo docker build -f ../../../../dockerfiles/debug /home/me/myapp + $ docker build -f /home/me/myapp/dockerfiles/debug /home/me/myapp + $ docker build -f ../../../../dockerfiles/debug /home/me/myapp These two `docker build` commands do the exact same thing. They both use the contents of the `debug` file instead of looking for a `Dockerfile` @@ -788,27 +795,27 @@ Supported `Dockerfile` instructions: `ADD`|`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`FR #### Commit a container - $ sudo docker ps + $ 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 - $ sudo docker commit c3f279d17e0a SvenDowideit/testimage:version3 + $ docker commit c3f279d17e0a SvenDowideit/testimage:version3 f5283438590d - $ sudo docker images | head + $ docker images | head REPOSITORY TAG ID CREATED VIRTUAL SIZE SvenDowideit/testimage version3 f5283438590d 16 seconds ago 335.7 MB #### Commit a container with new configurations - $ sudo docker ps + $ 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 - $ sudo docker inspect -f "{{ .Config.Env }}" c3f279d17e0a + $ docker inspect -f "{{ .Config.Env }}" c3f279d17e0a [HOME=/ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin] - $ sudo docker commit --change "ENV DEBUG true" c3f279d17e0a SvenDowideit/testimage:version3 + $ docker commit --change "ENV DEBUG true" c3f279d17e0a SvenDowideit/testimage:version3 f5283438590d - $ sudo docker inspect -f "{{ .Config.Env }}" f5283438590d + $ docker inspect -f "{{ .Config.Env }}" f5283438590d [HOME=/ PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin DEBUG=true] ## cp @@ -884,9 +891,9 @@ Please see the [run command](#run) section and the [Docker run reference]( #### Examples - $ sudo docker create -t -i fedora bash + $ docker create -t -i fedora bash 6d8af538ec541dd581ebc2a24153a28329acb5268abe5ef868c1f1a261221752 - $ sudo docker start -a -i 6d8af538ec5 + $ docker start -a -i 6d8af538ec5 bash-4.2# As of v1.4.0 container volumes are initialized during the `docker create` @@ -934,7 +941,7 @@ There are 3 events that are listed in the `diff`: For example: - $ sudo docker diff 7bb0e258aefe + $ docker diff 7bb0e258aefe C /dev A /dev/kmsg @@ -991,13 +998,13 @@ You'll need two shells for this example. **Shell 1: Listening for events:** - $ sudo docker events + $ docker events **Shell 2: Start and Stop containers:** - $ sudo docker start 4386fb97867d - $ sudo docker stop 4386fb97867d - $ sudo docker stop 7805c1d35632 + $ docker start 4386fb97867d + $ docker stop 4386fb97867d + $ docker stop 7805c1d35632 **Shell 1: (Again .. now showing events):** @@ -1009,20 +1016,20 @@ You'll need two shells for this example. **Show events in the past from a specified time:** - $ sudo docker events --since 1378216169 + $ docker events --since 1378216169 2014-03-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die 2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop 2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die 2014-03-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop - $ sudo docker events --since '2013-09-03' + $ docker events --since '2013-09-03' 2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start 2014-09-03T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die 2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop 2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die 2014-09-03T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop - $ sudo docker events --since '2013-09-03T15:49:29' + $ docker events --since '2013-09-03T15:49:29' 2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die 2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop 2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die @@ -1030,29 +1037,29 @@ You'll need two shells for this example. **Filter events:** - $ sudo docker events --filter 'event=stop' + $ docker events --filter 'event=stop' 2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop 2014-09-03T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) stop - $ sudo docker events --filter 'image=ubuntu-1:14.04' + $ docker events --filter 'image=ubuntu-1:14.04' 2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) start 2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die 2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop - $ sudo docker events --filter 'container=7805c1d35632' + $ docker events --filter 'container=7805c1d35632' 2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die 2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop - $ sudo docker events --filter 'container=7805c1d35632' --filter 'container=4386fb97867d' + $ docker events --filter 'container=7805c1d35632' --filter 'container=4386fb97867d' 2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die 2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop 2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die 2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop - $ sudo docker events --filter 'container=7805c1d35632' --filter 'event=stop' + $ docker events --filter 'container=7805c1d35632' --filter 'event=stop' 2014-09-03T15:49:29.999999999Z07:00 7805c1d35632: (from redis:2.8) stop - $ sudo docker events --filter 'container=container_1' --filter 'container=container_2' + $ docker events --filter 'container=container_1' --filter 'container=container_2' 2014-09-03T15:49:29.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) die 2014-05-10T17:42:14.999999999Z07:00 4386fb97867d: (from ubuntu-1:14.04) stop 2014-05-10T17:42:14.999999999Z07:00 7805c1d35632: (from redis:2.8) die @@ -1087,16 +1094,16 @@ If the container is paused, then the `docker exec` command will fail with an err #### Examples - $ sudo docker run --name ubuntu_bash --rm -i -t ubuntu bash + $ docker run --name ubuntu_bash --rm -i -t ubuntu bash This will create a container named `ubuntu_bash` and start a Bash session. - $ sudo docker exec -d ubuntu_bash touch /tmp/execWorks + $ docker exec -d ubuntu_bash touch /tmp/execWorks This will create a new file `/tmp/execWorks` inside the running container `ubuntu_bash`, in the background. - $ sudo docker exec -it ubuntu_bash bash + $ docker exec -it ubuntu_bash bash This will create a new Bash session in the container `ubuntu_bash`. @@ -1112,11 +1119,11 @@ This will create a new Bash session in the container `ubuntu_bash`. For example: - $ sudo docker export red_panda > latest.tar + $ docker export red_panda > latest.tar Or - $ sudo docker export --output="latest.tar" red_panda + $ docker export --output="latest.tar" red_panda > **Note:** > `docker export` does not export the contents of volumes associated with the @@ -1138,7 +1145,7 @@ This will create a new Bash session in the container `ubuntu_bash`. To see how the `docker:latest` image was built: - $ sudo docker history docker + $ docker history docker 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 @@ -1178,7 +1185,7 @@ uses up the `VIRTUAL SIZE` listed only once. #### Listing the most recently created images - $ sudo docker images | head + $ docker images | head REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE 77af4d6b9913 19 hours ago 1.089 GB committ latest b6fa739cedf5 19 hours ago 1.089 GB @@ -1193,7 +1200,7 @@ uses up the `VIRTUAL SIZE` listed only once. #### Listing the full length image IDs - $ sudo docker images --no-trunc | head + $ docker images --no-trunc | head REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE 77af4d6b9913e693e8d0b4b294fa62ade6054e6b2f1ffb617ac955dd63fb0182 19 hours ago 1.089 GB committest latest b6fa739cedf5ea12a620a439402b6004d057da800f91c7524b5086a5e4749c9f 19 hours ago 1.089 GB @@ -1212,7 +1219,7 @@ called a `digest`. As long as the input used to generate the image is unchanged, the digest value is predictable. To list image digest values, use the `--digests` flag: - $ sudo docker images --digests | head + $ docker images --digests | head REPOSITORY TAG DIGEST IMAGE ID CREATED VIRTUAL SIZE localhost:5000/test/busybox sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf 4986bf8c1536 9 weeks ago 2.43 MB @@ -1232,7 +1239,7 @@ Current filters: ##### Untagged images - $ sudo docker images --filter "dangling=true" + $ docker images --filter "dangling=true" REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE 8abc22fbb042 4 weeks ago 0 B @@ -1250,7 +1257,7 @@ By having this flag it allows for batch cleanup. Ready for use by `docker rmi ...`, like: - $ sudo docker rmi $(sudo docker images -f "dangling=true" -q) + $ docker rmi $(docker images -f "dangling=true" -q) 8abc22fbb042 48e5f45168b9 @@ -1287,21 +1294,21 @@ Supported `Dockerfile` instructions: `CMD`, `ENTRYPOINT`, `ENV`, `EXPOSE`, This will create a new untagged image. - $ sudo docker import http://example.com/exampleimage.tgz + $ docker import http://example.com/exampleimage.tgz **Import from a local file:** Import to docker via pipe and `STDIN`. - $ cat exampleimage.tgz | sudo docker import - exampleimagelocal:new + $ cat exampleimage.tgz | docker import - exampleimagelocal:new **Import from a local directory:** - $ sudo tar -c . | sudo docker import - exampleimagedir + $ sudo tar -c . | docker import - exampleimagedir **Import from a local directory with new configurations:** - $ sudo tar -c . | sudo docker import --change "ENV DEBUG true" - exampleimagedir + $ sudo tar -c . | docker import --change "ENV DEBUG true" - exampleimagedir Note the `sudo` in this example – you must preserve the ownership of the files (especially root ownership) during the @@ -1317,7 +1324,7 @@ tar, then the ownerships might not get preserved. For example: - $ sudo docker -D info + $ docker -D info Containers: 14 Images: 52 Storage Driver: aufs @@ -1373,25 +1380,25 @@ describes all the details of the format. For the most part, you can pick out any field from the JSON in a fairly straightforward manner. - $ sudo docker inspect --format='{{.NetworkSettings.IPAddress}}' $INSTANCE_ID + $ docker inspect --format='{{.NetworkSettings.IPAddress}}' $INSTANCE_ID **Get an instance's MAC Address:** For the most part, you can pick out any field from the JSON in a fairly straightforward manner. - $ sudo docker inspect --format='{{.NetworkSettings.MacAddress}}' $INSTANCE_ID + $ docker inspect --format='{{.NetworkSettings.MacAddress}}' $INSTANCE_ID **Get an instance's log path:** - $ sudo docker inspect --format='{{.LogPath}}' $INSTANCE_ID + $ docker inspect --format='{{.LogPath}}' $INSTANCE_ID **List All Port Bindings:** One can loop over arrays and maps in the results to produce simple text output: - $ sudo docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' $INSTANCE_ID + $ docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' $INSTANCE_ID **Find a Specific Port Mapping:** @@ -1403,7 +1410,7 @@ numeric public port, you use `index` to find the specific port map, and then `index` 0 contains the first object inside of that. Then we ask for the `HostPort` field to get the public address. - $ sudo docker inspect --format='{{(index (index .NetworkSettings.Ports "8787/tcp") 0).HostPort}}' $INSTANCE_ID + $ docker inspect --format='{{(index (index .NetworkSettings.Ports "8787/tcp") 0).HostPort}}' $INSTANCE_ID **Get config:** @@ -1412,7 +1419,7 @@ the template language's custom `json` function does. The `.config` section contains complex JSON object, so to grab it as JSON, you use `json` to convert the configuration object into JSON. - $ sudo docker inspect --format='{{json .config}}' $INSTANCE_ID + $ docker inspect --format='{{json .config}}' $INSTANCE_ID ## kill @@ -1436,14 +1443,14 @@ signal specified with option `--signal`. Loads a tarred repository from a file or the standard input stream. Restores both images and tags. - $ sudo docker images + $ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE - $ sudo docker load < busybox.tar - $ sudo docker images + $ docker load < busybox.tar + $ 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 + $ docker load --input fedora.tar + $ 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 @@ -1466,7 +1473,7 @@ If you want to login to a self-hosted registry you can specify this by adding the server name. example: - $ sudo docker login localhost:8080 + $ docker login localhost:8080 ## logout @@ -1477,7 +1484,7 @@ adding the server name. For example: - $ sudo docker logout localhost:8080 + $ docker logout localhost:8080 ## logs @@ -1531,17 +1538,17 @@ for further details. You can find out all the ports mapped by not specifying a `PRIVATE_PORT`, or just a specific mapping: - $ sudo docker ps test + $ docker ps test CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES b650456536c7 busybox:latest top 54 minutes ago Up 54 minutes 0.0.0.0:1234->9876/tcp, 0.0.0.0:4321->7890/tcp test - $ sudo docker port test + $ docker port test 7890/tcp -> 0.0.0.0:4321 9876/tcp -> 0.0.0.0:1234 - $ sudo docker port test 7890/tcp + $ docker port test 7890/tcp 0.0.0.0:4321 - $ sudo docker port test 7890/udp + $ docker port test 7890/udp 2014/06/24 11:53:36 Error: No public port '7890/udp' published for test - $ sudo docker port test 7890 + $ docker port test 7890 0.0.0.0:4321 ## ps @@ -1562,7 +1569,7 @@ just a specific mapping: Running `docker ps --no-trunc` showing 2 linked containers. - $ sudo docker ps + $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4c01db0b339c ubuntu:12.04 bash 17 seconds ago Up 16 seconds 3300-3310/tcp webapp d7886598dbe2 crosbymichael/redis:latest /redis-server --dir 33 minutes ago Up 33 minutes 6379/tcp redis,webapp/db @@ -1585,7 +1592,7 @@ Current filters: ##### Successfully exited containers - $ sudo docker ps -a --filter 'exited=0' + $ docker ps -a --filter 'exited=0' CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES ea09c3c82f6e registry:latest /srv/run.sh 2 weeks ago Exited (0) 2 weeks ago 127.0.0.1:5000->5000/tcp desperate_leakey 106ea823fe4e fedora:latest /bin/sh -c 'bash -l' 2 weeks ago Exited (0) 2 weeks ago determined_albattani @@ -1615,20 +1622,20 @@ a protocol specifier (`https://`, for example). To download a particular image, or set of images (i.e., a repository), use `docker pull`: - $ sudo docker pull debian + $ docker pull debian # will pull the debian:latest image and its intermediate layers - $ sudo docker pull debian:testing + $ docker pull debian:testing # will pull the image named debian:testing and any intermediate # layers it is based on. - $ sudo docker pull debian@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf + $ docker pull debian@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf # will pull the image from the debian repository with the digest # sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf # and any intermediate layers it is based on. # (Typically the empty `scratch` image, a MAINTAINER layer, # and the un-tarred base). - $ sudo docker pull --all-tags centos + $ docker pull --all-tags centos # will pull all the images from the centos repository - $ sudo docker pull registry.hub.docker.com/debian + $ docker pull registry.hub.docker.com/debian # manually specifies the path to the default Docker registry. This could # be replaced with the path to a local registry to pull from another source. @@ -1669,19 +1676,19 @@ The `docker rename` command allows the container to be renamed to a different na #### Examples - $ sudo docker rm /redis + $ docker rm /redis /redis This will remove the container referenced under the link `/redis`. - $ sudo docker rm --link /webapp/redis + $ docker rm --link /webapp/redis /webapp/redis This will remove the underlying link between `/webapp` and the `/redis` containers removing all network communication. - $ sudo docker rm --force redis + $ docker rm --force redis redis The main process inside the container referenced under the link `/redis` will receive @@ -1709,37 +1716,37 @@ You can remove an image using its short or long ID, its tag, or its digest. If an image has one or more tag or digest reference, you must remove all of them before the image is removed. - $ sudo docker images + $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE test1 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) test2 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) - $ sudo docker rmi fd484f19954f + $ docker rmi fd484f19954f Error: Conflict, cannot delete image fd484f19954f because it is tagged in multiple repositories, use -f to force 2013/12/11 05:47:16 Error: failed to remove one or more images - $ sudo docker rmi test1 + $ docker rmi test1 Untagged: test1:latest - $ sudo docker rmi test2 + $ docker rmi test2 Untagged: test2:latest - $ sudo docker images + $ docker images REPOSITORY TAG IMAGE ID CREATED SIZE test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) - $ sudo docker rmi test + $ docker rmi test Untagged: test:latest Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 An image pulled by digest has no tag associated with it: - $ sudo docker images --digests + $ docker images --digests REPOSITORY TAG DIGEST IMAGE ID CREATED VIRTUAL SIZE localhost:5000/test/busybox sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf 4986bf8c1536 9 weeks ago 2.43 MB To remove an image using its digest: - $ sudo docker rmi localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf + $ docker rmi localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf Untagged: localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf Deleted: 4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125 Deleted: ea13149945cb6b1e746bf28032f02e9b5a793523481a0a18645fc77ad53c4ea2 @@ -1814,12 +1821,12 @@ and linking containers. #### Examples - $ sudo docker run --name test -it debian + $ docker run --name test -it debian $$ exit 13 exit $ echo $? 13 - $ sudo docker ps -a | grep test + $ docker ps -a | grep test 275c44472aeb debian:7 "/bin/bash" 26 seconds ago Exited (13) 17 seconds ago test In this example, we are running `bash` interactively in the `debian:latest` image, and giving @@ -1827,14 +1834,14 @@ the container the name `test`. We then quit `bash` by running `exit 13`, which m will have an exit code of `13`. This is then passed on to the caller of `docker run`, and is recorded in the `test` container metadata. - $ sudo docker run --cidfile /tmp/docker_test.cid ubuntu echo "test" + $ docker run --cidfile /tmp/docker_test.cid ubuntu echo "test" This will create a container and print `test` to the console. The `cidfile` flag makes Docker attempt to create a new file and write the container ID to it. If the file exists already, Docker will return an error. Docker will close this file when `docker run` exits. - $ sudo docker run -t -i --rm ubuntu bash + $ docker run -t -i --rm ubuntu bash root@bc338942ef20:/# mount -t tmpfs none /mnt mount: permission denied @@ -1842,7 +1849,7 @@ 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 allow it to run: - $ sudo docker run --privileged ubuntu bash + $ docker run --privileged ubuntu bash root@50e3f57e16e6:/# mount -t tmpfs none /mnt root@50e3f57e16e6:/# df -h Filesystem Size Used Avail Use% Mounted on @@ -1853,12 +1860,12 @@ 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 use-cases, like running Docker within Docker. - $ sudo docker run -w /path/to/dir/ -i -t ubuntu pwd + $ docker run -w /path/to/dir/ -i -t ubuntu pwd The `-w` lets the command being executed inside directory given, here `/path/to/dir/`. If the path does not exists it is created inside the container. - $ sudo docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd + $ docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd The `-v` flag mounts the current working directory into the container. The `-w` lets the command being executed inside the current working directory, by @@ -1866,41 +1873,41 @@ changing into the directory to the value returned by `pwd`. So this combination executes the command using the container, but inside the current working directory. - $ sudo docker run -v /doesnt/exist:/foo -w /foo -i -t ubuntu bash + $ docker run -v /doesnt/exist:/foo -w /foo -i -t ubuntu bash When the host directory of a bind-mounted volume doesn't exist, Docker will automatically create this directory on the host for you. In the example above, Docker will create the `/doesnt/exist` folder before starting your container. - $ sudo docker run --read-only -v /icanwrite busybox touch /icanwrite here + $ docker run --read-only -v /icanwrite busybox touch /icanwrite here Volumes can be used in combination with `--read-only` to control where a container writes files. The `--read-only` flag mounts the container's root filesystem as read only prohibiting writes to locations other than the specified volumes for the container. - $ sudo docker run -t -i -v /var/run/docker.sock:/var/run/docker.sock -v ./static-docker:/usr/bin/docker busybox sh + $ 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.com]( https://get.docker.com)), you give the container the full access to create and manipulate the host's Docker daemon. - $ sudo docker run -p 127.0.0.1:80:8080 ubuntu bash + $ docker run -p 127.0.0.1:80:8080 ubuntu bash This binds port `8080` of the container to port `80` on `127.0.0.1` of the host machine. The [Docker User Guide](/userguide/dockerlinks/) explains in detail how to manipulate ports in Docker. - $ sudo docker run --expose 80 ubuntu bash + $ docker run --expose 80 ubuntu bash This exposes port `80` of the container for use within a link without publishing the port to the host system's interfaces. The [Docker User Guide](/userguide/dockerlinks) explains in detail how to manipulate ports in Docker. - $ sudo docker run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash + $ docker run -e MYVAR1 --env MYVAR2=foo --env-file ./env.list ubuntu bash This sets environmental variables in the container. For illustration all three flags are shown here. Where `-e`, `--env` take an environment variable and @@ -1917,7 +1924,7 @@ override variables as needed. $ 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 + $ 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 @@ -1944,11 +1951,11 @@ An example of a file passed with `--env-file` TEST_APP_DEST_PORT=8888 TEST_PASSTHROUGH=howdy - $ sudo docker run --name console -t -i ubuntu bash + $ docker run --name console -t -i ubuntu bash A label is a a `key=value` pair that applies metadata to a container. To label a container with two labels: - $ sudo docker run -l my-label --label com.example.foo=bar ubuntu bash + $ docker run -l my-label --label com.example.foo=bar ubuntu bash The `my-label` key doesn't specify a value so the label defaults to an empty string(`""`). To add multiple labels, repeat the label flag (`-l` or `--label`). @@ -1961,7 +1968,7 @@ Use the `--label-file` flag to load multiple labels from a file. Delimit each label in the file with an EOL mark. The example below loads labels from a labels file in the current directory: - $ sudo docker run --label-file ./labels ubuntu bash + $ docker run --label-file ./labels ubuntu bash The label-file format is similar to the format for loading environment variables. (Unlike environment variables, labels are not visislbe to processes @@ -1980,7 +1987,7 @@ For additional information on working with labels, see [*Labels - custom metadata in Docker*](/userguide/labels-custom-metadata/) in the Docker User Guide. - $ sudo docker run --link /redis:redis --name console ubuntu bash + $ docker run --link /redis:redis --name console ubuntu bash The `--link` flag will link the container named `/redis` into the newly created container with the alias `redis`. The new container can access the @@ -1988,7 +1995,7 @@ network and environment of the `redis` container via environment variables. The `--name` flag will assign the name `console` to the newly created container. - $ sudo docker run --volumes-from 777f7dc92da7 --volumes-from ba8c0c54f0f2:ro -i -t ubuntu pwd + $ docker run --volumes-from 777f7dc92da7 --volumes-from ba8c0c54f0f2:ro -i -t ubuntu pwd The `--volumes-from` flag mounts all the defined volumes from the referenced containers. Containers can be specified by repetitions of the `--volumes-from` @@ -2000,18 +2007,18 @@ 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. - $ echo "test" | sudo docker run -i -a stdin ubuntu cat - + $ 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`. - $ sudo docker run -a stderr ubuntu echo test + $ 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`. - $ cat somefile | sudo docker run -i -a stdin mybuilder dobuild + $ 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 @@ -2019,7 +2026,7 @@ 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. - $ sudo docker run --device=/dev/sdc:/dev/xvdc --device=/dev/sdd --device=/dev/zero:/dev/nulo -i -t ubuntu ls -l /dev/{xvdc,sdd,nulo} + $ docker run --device=/dev/sdc:/dev/xvdc --device=/dev/sdd --device=/dev/zero:/dev/nulo -i -t ubuntu ls -l /dev/{xvdc,sdd,nulo} brw-rw---- 1 root disk 8, 2 Feb 9 16:05 /dev/xvdc brw-rw---- 1 root disk 8, 3 Feb 9 16:05 /dev/sdd crw-rw-rw- 1 root root 1, 5 Feb 9 16:05 /dev/nulo @@ -2035,19 +2042,19 @@ flag: ``` - $ sudo docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc + $ docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc Command (m for help): q - $ sudo docker run --device=/dev/sda:/dev/xvdc:r --rm -it ubuntu fdisk /dev/xvdc + $ docker run --device=/dev/sda:/dev/xvdc:r --rm -it ubuntu fdisk /dev/xvdc You will not be able to write the partition table. Command (m for help): q - $ sudo docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc + $ docker run --device=/dev/sda:/dev/xvdc --rm -it ubuntu fdisk /dev/xvdc Command (m for help): q - $ sudo docker run --device=/dev/sda:/dev/xvdc:m --rm -it ubuntu fdisk /dev/xvdc + $ docker run --device=/dev/sda:/dev/xvdc:m --rm -it ubuntu fdisk /dev/xvdc fdisk: unable to open /dev/xvdc: Operation not permitted ``` @@ -2057,11 +2064,11 @@ flag: **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=10.0.0.1 --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 + $ docker run -d --name static static-web-files sh + $ docker run -d --expose=8098 --name riak riakserver + $ docker run -d -m 100m -e DEVELOPMENT=1 -e BRANCH=example-code -v $(pwd):/app/bin:ro --name app appserver + $ docker run -d -p 1443:443 --dns=10.0.0.1 --dns-search=dev.org -v /var/log/httpd --volumes-from static --link riak --link app -h www.sven.dev.org --name web webserver + $ docker run -t -i --rm --volumes-from web -w /var/log/httpd busybox tail -f access.log This example shows five containers that might be set up to test a web application change: @@ -2130,7 +2137,7 @@ Docker supports the following restart policies: - $ sudo docker run --restart=always redis + $ docker run --restart=always redis This will run the `redis` container with a restart policy of **always** so that if the container exits, Docker will restart it. @@ -2202,18 +2209,18 @@ each argument provided. It is used to create a backup that can then be used with `docker load` - $ sudo docker save busybox > busybox.tar + $ docker save busybox > busybox.tar $ ls -sh busybox.tar 2.7M busybox.tar - $ sudo docker save --output busybox.tar busybox + $ docker save --output busybox.tar busybox $ ls -sh busybox.tar 2.7M busybox.tar - $ sudo docker save -o fedora-all.tar fedora - $ sudo docker save -o fedora-latest.tar fedora:latest + $ docker save -o fedora-all.tar fedora + $ docker save -o fedora-latest.tar fedora:latest It is even useful to cherry-pick particular tags of an image repository - $ sudo docker save -o ubuntu.tar ubuntu:lucid ubuntu:saucy + $ docker save -o ubuntu.tar ubuntu:lucid ubuntu:saucy ## search @@ -2253,7 +2260,7 @@ more details on finding shared images from the command line. Running `docker stats` on multiple containers - $ sudo docker stats redis1 redis2 + $ docker stats redis1 redis2 CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O redis1 0.07% 796 KiB/64 MiB 1.21% 788 B/648 B redis2 0.07% 2.746 MiB/64 MiB 4.29% 1.266 KiB/648 B @@ -2317,7 +2324,7 @@ for further details. Show the Docker version, API version, Git commit, Go version and OS/architecture of both Docker client and daemon. Example use: - $ sudo docker version + $ docker version Client version: 1.5.0 Client API version: 1.17 Go version (client): go1.4.1 diff --git a/docs/sources/terms/registry.md b/docs/sources/terms/registry.md index 8a7e6237e..68120812c 100644 --- a/docs/sources/terms/registry.md +++ b/docs/sources/terms/registry.md @@ -12,7 +12,7 @@ A Registry is a hosted service containing The default registry can be accessed using a browser at [Docker Hub](https://hub.docker.com) or using the -`sudo docker search` command. +`docker search` command. ## Further Reading diff --git a/docs/sources/terms/repository.md b/docs/sources/terms/repository.md index c4d1d4353..84963b4bf 100644 --- a/docs/sources/terms/repository.md +++ b/docs/sources/terms/repository.md @@ -13,11 +13,11 @@ server. Images can be associated with a repository (or multiple) by giving them an image name using one of three different commands: -1. At build time (e.g., `sudo docker build -t IMAGENAME`), +1. At build time (e.g., `docker build -t IMAGENAME`), 2. When committing a container (e.g., - `sudo docker commit CONTAINERID IMAGENAME`) or + `docker commit CONTAINERID IMAGENAME`) or 3. When tagging an image id with an image name (e.g., - `sudo docker tag IMAGEID IMAGENAME`). + `docker tag IMAGEID IMAGENAME`). A Fully Qualified Image Name (FQIN) can be made up of 3 parts: diff --git a/docs/sources/userguide/dockerhub.md b/docs/sources/userguide/dockerhub.md index 62438b994..bbdd1b6f6 100644 --- a/docs/sources/userguide/dockerhub.md +++ b/docs/sources/userguide/dockerhub.md @@ -42,7 +42,7 @@ going on in the world of Docker. You can also create a Docker Hub account via the command line with the `docker login` command. - $ sudo docker login + $ docker login ### Confirm your email @@ -58,7 +58,7 @@ After you complete the confirmation process, you can login using the web console Or via the command line with the `docker login` command: - $ sudo docker login + $ docker login Your Docker Hub account is now active and ready to use. diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md index 6224479fb..3fe6aa28f 100644 --- a/docs/sources/userguide/dockerimages.md +++ b/docs/sources/userguide/dockerimages.md @@ -27,7 +27,7 @@ including: Let's start with listing the images we have locally on our host. You can do this using the `docker images` command like so: - $ sudo docker images + $ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE training/webapp latest fc77f57ad303 3 weeks ago 280.5 MB ubuntu 13.10 5e019ab7bf6d 4 weeks ago 180 MB @@ -63,11 +63,11 @@ refer to a tagged image like so: So when we run a container we refer to a tagged image like so: - $ sudo docker run -t -i ubuntu:14.04 /bin/bash + $ docker run -t -i ubuntu:14.04 /bin/bash If instead we wanted to run an Ubuntu 12.04 image we'd use: - $ sudo docker run -t -i ubuntu:12.04 /bin/bash + $ docker run -t -i ubuntu:12.04 /bin/bash If you don't specify a variant, for example you just use `ubuntu`, then Docker will default to using the `ubuntu:latest` image. @@ -85,7 +85,7 @@ add some time to the launch of a container. If we want to pre-load an image we can download it using the `docker pull` command. Let's say we'd like to download the `centos` image. - $ sudo docker pull centos + $ docker pull centos Pulling repository centos b7de3133ff98: Pulling dependent layers 5cc9e91966f7: Pulling fs layer @@ -99,7 +99,7 @@ We can see that each layer of the image has been pulled down and now we can run a container from this image and we won't have to wait to download the image. - $ sudo docker run -t -i centos /bin/bash + $ docker run -t -i centos /bin/bash bash-4.1# ## Finding images @@ -117,7 +117,7 @@ which to do our web application development. We can search for a suitable image by using the `docker search` command to find all the images that contain the term `sinatra`. - $ sudo docker search sinatra + $ docker search sinatra NAME DESCRIPTION STARS OFFICIAL AUTOMATED training/sinatra Sinatra training image 0 [OK] marceldegraaf/sinatra Sinatra test app 0 @@ -152,11 +152,11 @@ prefixed with the user name, here `training`, of the user that created them. We've identified a suitable image, `training/sinatra`, and now we can download it using the `docker pull` command. - $ sudo docker pull training/sinatra + $ docker pull training/sinatra The team can now use this image by running their own containers. - $ sudo docker run -t -i training/sinatra /bin/bash + $ docker run -t -i training/sinatra /bin/bash root@a8cb6ce02d85:/# ## Creating our own images @@ -174,7 +174,7 @@ update and create images. To update an image we first need to create a container from the image we'd like to update. - $ sudo docker run -t -i training/sinatra /bin/bash + $ docker run -t -i training/sinatra /bin/bash root@0b2616b0e5a8:/# > **Note:** @@ -192,7 +192,7 @@ Now we have a container with the change we want to make. We can then commit a copy of this container to an image using the `docker commit` command. - $ sudo docker commit -m "Added json gem" -a "Kate Smith" \ + $ docker commit -m "Added json gem" -a "Kate Smith" \ 0b2616b0e5a8 ouruser/sinatra:v2 4f177bd27a9ff0f6dc2a830403925b5360bfe0b93d476f7fc3231110e7f71b1c @@ -215,7 +215,7 @@ the image: `v2`. We can then look at our new `ouruser/sinatra` image using the `docker images` command. - $ sudo docker images + $ docker images REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE training/sinatra latest 5bc342fa0b91 10 hours ago 446.7 MB ouruser/sinatra v2 3c59e02ddd1a 10 hours ago 446.7 MB @@ -223,7 +223,7 @@ command. To use our new image to create a container we can then: - $ sudo docker run -t -i ouruser/sinatra:v2 /bin/bash + $ docker run -t -i ouruser/sinatra:v2 /bin/bash root@78e82f680994:/# ### Building an image from a `Dockerfile` @@ -273,7 +273,7 @@ Sinatra gem. Now let's take our `Dockerfile` and use the `docker build` command to build an image. - $ sudo docker build -t ouruser/sinatra:v2 . + $ docker build -t ouruser/sinatra:v2 . Sending build context to Docker daemon 2.048 kB Sending build context to Docker daemon Step 0 : FROM ubuntu:14.04 @@ -467,7 +467,7 @@ containers will get removed to clean things up. We can then create a container from our new image. - $ sudo docker run -t -i ouruser/sinatra:v2 /bin/bash + $ docker run -t -i ouruser/sinatra:v2 /bin/bash root@8196968dac35:/# > **Note:** @@ -489,14 +489,14 @@ You can also add a tag to an existing image after you commit or build it. We can do this using the `docker tag` command. Let's add a new tag to our `ouruser/sinatra` image. - $ sudo docker tag 5db5f8471261 ouruser/sinatra:devel + $ docker tag 5db5f8471261 ouruser/sinatra:devel The `docker tag` command takes the ID of the image, here `5db5f8471261`, and our user name, the repository name and the new tag. Let's see our new tag using the `docker images` command. - $ sudo docker images ouruser/sinatra + $ docker images ouruser/sinatra REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE ouruser/sinatra latest 5db5f8471261 11 hours ago 446.7 MB ouruser/sinatra devel 5db5f8471261 11 hours ago 446.7 MB @@ -509,7 +509,7 @@ Hub](https://hub.docker.com) using the `docker push` command. This allows you to share it with others, either publicly, or push it into [a private repository](https://registry.hub.docker.com/plans/). - $ sudo docker push ouruser/sinatra + $ docker push ouruser/sinatra The push refers to a repository [ouruser/sinatra] (len: 1) Sending image list Pushing repository ouruser/sinatra (3 tags) @@ -523,7 +523,7 @@ containers]( Let's delete the `training/sinatra` image as we don't need it anymore. - $ sudo docker rmi training/sinatra + $ docker rmi training/sinatra Untagged: training/sinatra:latest Deleted: 5bc342fa0b91cabf65246837015197eecfa24b2213ed6a51a8974ae250fedd8d Deleted: ed0fffdcdae5eb2c3a55549857a8be7fc8bc4241fb19ad714364cbfd7a56b22f diff --git a/docs/sources/userguide/dockerizing.md b/docs/sources/userguide/dockerizing.md index cc7bc8e1c..5896dd78e 100644 --- a/docs/sources/userguide/dockerizing.md +++ b/docs/sources/userguide/dockerizing.md @@ -15,7 +15,7 @@ application inside a container takes a single command: `docker run`. Let's try it now. - $ sudo docker run ubuntu:14.04 /bin/echo 'Hello world' + $ docker run ubuntu:14.04 /bin/echo 'Hello world' Hello world And you just launched your first container! @@ -53,7 +53,7 @@ only run as long as the command you specify is active. Here, as soon as Let's try the `docker run` command again, this time specifying a new command to run in our container. - $ sudo docker run -t -i ubuntu:14.04 /bin/bash + $ docker run -t -i ubuntu:14.04 /bin/bash root@af8bae53bdd3:/# Here we've again specified the `docker run` command and launched an @@ -98,7 +98,7 @@ like most of the applications we're probably going to run with Docker. Again we can do this with the `docker run` command: - $ sudo docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" + $ docker run -d ubuntu:14.04 /bin/sh -c "while true; do echo hello world; sleep 1; done" 1e5535038e285177d5214659a068137486f96ee5c2e85a4ac52dc83f2ebe4147 Wait, what? Where's our "hello world" output? Let's look at what we've run here. @@ -135,7 +135,7 @@ do that with the `docker ps` command. The `docker ps` command queries the Docker daemon for information about all the containers it knows about. - $ sudo docker ps + $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 1e5535038e28 ubuntu:14.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute insane_babbage @@ -155,7 +155,7 @@ Okay, so we now know it's running. But is it doing what we asked it to do? To se we're going to look inside the container using the `docker logs` command. Let's use the container name Docker assigned. - $ sudo docker logs insane_babbage + $ docker logs insane_babbage hello world hello world hello world @@ -171,7 +171,7 @@ Now we've established we can create our own containers let's tidy up after ourselves and stop our daemonized container. To do this we use the `docker stop` command. - $ sudo docker stop insane_babbage + $ docker stop insane_babbage insane_babbage The `docker stop` command tells Docker to politely stop the running @@ -180,7 +180,7 @@ has just stopped. Let's check it worked with the `docker ps` command. - $ sudo docker ps + $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES Excellent. Our container has been stopped. diff --git a/docs/sources/userguide/dockerlinks.md b/docs/sources/userguide/dockerlinks.md index 79ba17900..1c14f47e2 100644 --- a/docs/sources/userguide/dockerlinks.md +++ b/docs/sources/userguide/dockerlinks.md @@ -16,7 +16,7 @@ container linking. In [the Using Docker section](/userguide/usingdocker), you created a container that ran a Python Flask application: - $ sudo docker run -d -P training/webapp python app.py + $ docker run -d -P training/webapp python app.py > **Note:** > Containers have an internal network and an IP address @@ -30,14 +30,14 @@ any network port inside it to a random high port within an *ephemeral port range* on your Docker host. Next, when `docker ps` was run, you saw that port 5000 in the container was bound to port 49155 on the host. - $ sudo docker ps nostalgic_morse + $ docker ps nostalgic_morse CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES bc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse You also saw how you can bind a container's ports to a specific port using the `-p` flag: - $ sudo docker run -d -p 5000:5000 training/webapp python app.py + $ docker run -d -p 5000:5000 training/webapp python app.py And you saw why this isn't such a great idea because it constrains you to only one container on that specific port. @@ -47,7 +47,7 @@ default the `-p` flag will bind the specified port to all interfaces on the host machine. But you can also specify a binding to a specific interface, for example only to the `localhost`. - $ sudo docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py + $ docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py This would bind port 5000 inside the container to port 5000 on the `localhost` or `127.0.0.1` interface on the host machine. @@ -55,18 +55,18 @@ This would bind port 5000 inside the container to port 5000 on the Or, to bind port 5000 of the container to a dynamic port but only on the `localhost`, you could use: - $ sudo docker run -d -p 127.0.0.1::5000 training/webapp python app.py + $ docker run -d -p 127.0.0.1::5000 training/webapp python app.py You can also bind UDP ports by adding a trailing `/udp`. For example: - $ sudo docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py + $ docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py You also learned about the useful `docker port` shortcut which showed us the current port bindings. This is also useful for showing you specific port configurations. For example, if you've bound the container port to the `localhost` on the host machine, then the `docker port` output will reflect that. - $ sudo docker port nostalgic_morse 5000 + $ docker port nostalgic_morse 5000 127.0.0.1:49155 > **Note:** @@ -98,19 +98,19 @@ yourself. This naming provides two useful functions: You can name your container by using the `--name` flag, for example: - $ sudo docker run -d -P --name web training/webapp python app.py + $ docker run -d -P --name web training/webapp python app.py This launches a new container and uses the `--name` flag to name the container `web`. You can see the container's name using the `docker ps` command. - $ sudo docker ps -l + $ docker ps -l CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES aed84ee21bde training/webapp:latest python app.py 12 hours ago Up 2 seconds 0.0.0.0:49154->5000/tcp web You can also use `docker inspect` to return the container's name. - $ sudo docker inspect -f "{{ .Name }}" aed84ee21bde + $ docker inspect -f "{{ .Name }}" aed84ee21bde /web > **Note:** @@ -129,7 +129,7 @@ source container and a recipient container. The recipient can then access select about the source. To create a link, you use the `--link` flag. First, create a new container, this time one containing a database. - $ sudo docker run -d --name db training/postgres + $ docker run -d --name db training/postgres This creates a new container called `db` from the `training/postgres` image, which contains a PostgreSQL database. @@ -137,11 +137,11 @@ image, which contains a PostgreSQL database. Now, you need to delete the `web` container you created previously so you can replace it with a linked one: - $ sudo docker rm -f web + $ docker rm -f web Now, create a new `web` container and link it with your `db` container. - $ sudo docker run -d -P --name web --link db:db training/webapp python app.py + $ docker run -d -P --name web --link db:db training/webapp python app.py This will link the new `web` container with the `db` container you created earlier. The `--link` flag takes the form: @@ -153,7 +153,7 @@ alias for the link name. You'll see how that alias gets used shortly. Next, inspect your linked containers with `docker inspect`: - $ sudo docker inspect -f "{{ .HostConfig.Links }}" web + $ docker inspect -f "{{ .HostConfig.Links }}" web [/db:/web/db] You can see that the `web` container is now linked to the `db` container @@ -239,7 +239,7 @@ Returning back to our database example, you can run the `env` command to list the specified container's environment variables. ``` - $ sudo docker run --rm --name web2 --link db:db training/webapp env + $ docker run --rm --name web2 --link db:db training/webapp env . . . DB_NAME=/web2/db DB_PORT=tcp://172.17.0.5:5432 @@ -276,7 +276,7 @@ In addition to the environment variables, Docker adds a host entry for the source container to the `/etc/hosts` file. Here's an entry for the `web` container: - $ sudo docker run -t -i --rm --link db:webdb training/webapp /bin/bash + $ docker run -t -i --rm --link db:webdb training/webapp /bin/bash root@aed84ee21bde:/opt/webapp# cat /etc/hosts 172.17.0.7 aed84ee21bde . . . @@ -314,9 +314,9 @@ If you restart the source container, the linked containers `/etc/hosts` files will be automatically updated with the source container's new IP address, allowing linked communication to continue. - $ sudo docker restart db + $ docker restart db db - $ sudo docker run -t -i --rm --link db:db training/webapp /bin/bash + $ docker run -t -i --rm --link db:db training/webapp /bin/bash root@aed84ee21bde:/opt/webapp# cat /etc/hosts 172.17.0.7 aed84ee21bde . . . diff --git a/docs/sources/userguide/dockerrepos.md b/docs/sources/userguide/dockerrepos.md index d8dc44e69..a8a1800f5 100644 --- a/docs/sources/userguide/dockerrepos.md +++ b/docs/sources/userguide/dockerrepos.md @@ -27,7 +27,7 @@ Typically, you'll want to start by creating an account on Docker Hub (if you hav already) and logging in. You can create your account directly on [Docker Hub](https://hub.docker.com/account/signup/), or by running: - $ sudo docker login + $ docker login This will prompt you for a user name, which will become the public namespace for your public repositories. @@ -45,7 +45,7 @@ You can search the [Docker Hub](https://hub.docker.com) registry via its search interface or by using the command line interface. Searching can find images by image name, user name, or description: - $ sudo docker search centos + $ docker search centos NAME DESCRIPTION STARS OFFICIAL TRUSTED centos Official CentOS 6 Image as of 12 April 2014 88 tianon/centos CentOS 5 and 6, created using rinse instea... 21 @@ -60,7 +60,7 @@ repository from the image name. Once you've found the image you want, you can download it with `docker pull `: - $ sudo docker pull centos + $ docker pull centos Pulling repository centos 0b443ba03958: Download complete 539c0211cd76: Download complete @@ -86,7 +86,7 @@ or committed your container to a named image as we saw Now you can push this repository to the registry designated by its name or tag. - $ sudo docker push yourname/newimage + $ docker push yourname/newimage The image will then be uploaded and available for use by your team-mates and/or the community. diff --git a/docs/sources/userguide/dockervolumes.md b/docs/sources/userguide/dockervolumes.md index af4a7297f..b8204308e 100644 --- a/docs/sources/userguide/dockervolumes.md +++ b/docs/sources/userguide/dockervolumes.md @@ -44,7 +44,7 @@ You can add a data volume to a container using the `-v` flag with the to mount multiple data volumes. Let's mount a single volume now in our web application container. - $ sudo docker run -d -P --name web -v /webapp training/webapp python app.py + $ docker run -d -P --name web -v /webapp training/webapp python app.py This will create a new volume inside a container at `/webapp`. @@ -86,7 +86,7 @@ directory from your Docker daemon's host into a container. > `docker run -v /c/Users/:/ come from the Boot2Docker virtual machine's filesystem. - $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py + $ docker run -d -P --name web -v /src/webapp:/opt/webapp training/webapp python app.py This will mount the host directory, `/src/webapp`, into the container at `/opt/webapp`. @@ -111,7 +111,7 @@ create it for you. Docker defaults to a read-write volume but we can also mount a directory read-only. - $ sudo docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py + $ docker run -d -P --name web -v /src/webapp:/opt/webapp:ro training/webapp python app.py Here we've mounted the same `/src/webapp` directory but we've added the `ro` option to specify that the mount should be read-only. @@ -121,7 +121,7 @@ option to specify that the mount should be read-only. The `-v` flag can also be used to mount a single file - instead of *just* directories - from the host machine. - $ sudo docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash + $ docker run --rm -it -v ~/.bash_history:/.bash_history ubuntu /bin/bash This will drop you into a bash shell in a new container, you will have your bash history from the host and when you exit the container, the host will have the @@ -145,15 +145,15 @@ Let's create a new named container with a volume to share. While this container doesn't run an application, it reuses the `training/postgres` image so that all containers are using layers in common, saving disk space. - $ sudo docker create -v /dbdata --name dbdata training/postgres /bin/true + $ docker create -v /dbdata --name dbdata training/postgres /bin/true You can then use the `--volumes-from` flag to mount the `/dbdata` volume in another container. - $ sudo docker run -d --volumes-from dbdata --name db1 training/postgres + $ docker run -d --volumes-from dbdata --name db1 training/postgres And another: - $ sudo docker run -d --volumes-from dbdata --name db2 training/postgres + $ docker run -d --volumes-from dbdata --name db2 training/postgres In this case, if the `postgres` image contained a directory called `/dbdata` then mounting the volumes from the `dbdata` container hides the @@ -166,7 +166,7 @@ volumes from multiple containers. You can also extend the chain by mounting the volume that came from the `dbdata` container in yet another container via the `db1` or `db2` containers. - $ sudo docker run -d --name db3 --volumes-from db1 training/postgres + $ docker run -d --name db3 --volumes-from db1 training/postgres If you remove containers that mount volumes, including the initial `dbdata` container, or the subsequent containers `db1` and `db2`, the volumes will not @@ -189,7 +189,7 @@ backups, restores or migrations. We do this by using the `--volumes-from` flag to create a new container that mounts that volume, like so: - $ sudo docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata + $ docker run --volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata Here we've launched a new container and mounted the volume from the `dbdata` container. We've then mounted a local host directory as @@ -201,11 +201,11 @@ we'll be left with a backup of our `dbdata` volume. You could then restore it to the same container, or another that you've made elsewhere. Create a new container. - $ sudo docker run -v /dbdata --name dbdata2 ubuntu /bin/bash + $ docker run -v /dbdata --name dbdata2 ubuntu /bin/bash Then un-tar the backup file in the new container's data volume. - $ sudo docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar + $ docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar You can use the techniques above to automate backup, migration and restore testing using your preferred tools. diff --git a/docs/sources/userguide/usingdocker.md b/docs/sources/userguide/usingdocker.md index fd5f52a37..a58a4a4aa 100644 --- a/docs/sources/userguide/usingdocker.md +++ b/docs/sources/userguide/usingdocker.md @@ -27,12 +27,12 @@ flags and arguments. # Usage: [sudo] docker [command] [flags] [arguments] .. # Example: - $ sudo docker run -i -t ubuntu /bin/bash + $ docker run -i -t ubuntu /bin/bash Let's see this in action by using the `docker version` command to return version information on the currently installed Docker client and daemon. - $ sudo docker version + $ docker version This command will not only provide you the version of Docker client and daemon you are using, but also the version of Go (the programming @@ -54,7 +54,7 @@ language powering Docker). We can see all of the commands available to us with the Docker client by running the `docker` binary without any options. - $ sudo docker + $ docker You will see a list of all currently available commands. @@ -71,12 +71,12 @@ You can also zoom in and review the usage for specific Docker commands. Try typing Docker followed with a `[command]` to see the usage for that command: - $ sudo docker attach + $ docker attach Help output . . . Or you can also pass the `--help` flag to the `docker` binary. - $ sudo docker attach --help + $ docker attach --help This will display the help text and all available flags: @@ -102,7 +102,7 @@ Docker. For our web application we're going to run a Python Flask application. Let's start with a `docker run` command. - $ sudo docker run -d -P training/webapp python app.py + $ docker run -d -P training/webapp python app.py Let's review what our command did. We've specified two flags: `-d` and `-P`. We've already seen the `-d` flag which tells Docker to run the @@ -125,7 +125,7 @@ Lastly, we've specified a command for our container to run: `python app.py`. Thi Now let's see our running container using the `docker ps` command. - $ sudo docker ps -l + $ docker ps -l CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES bc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse @@ -160,7 +160,7 @@ to a high port (from *ephemeral port range* which typically ranges from 32768 to 61000) on the local Docker host. We can also bind Docker containers to specific ports using the `-p` flag, for example: - $ sudo docker run -d -p 5000:5000 training/webapp python app.py + $ docker run -d -p 5000:5000 training/webapp python app.py This would map port 5000 inside our container to port 5000 on our local host. You might be asking about now: why wouldn't we just want to always @@ -196,7 +196,7 @@ Docker has a useful shortcut we can use: `docker port`. To use `docker port` we specify the ID or name of our container and then the port for which we need the corresponding public-facing port. - $ sudo docker port nostalgic_morse 5000 + $ docker port nostalgic_morse 5000 0.0.0.0:49155 In this case we've looked up what port is mapped externally to port 5000 inside @@ -207,7 +207,7 @@ the container. Let's also find out a bit more about what's happening with our application and use another of the commands we've learnt, `docker logs`. - $ sudo docker logs -f nostalgic_morse + $ docker logs -f nostalgic_morse * Running on http://0.0.0.0:5000/ 10.0.2.2 - - [23/May/2014 20:16:31] "GET / HTTP/1.1" 200 - 10.0.2.2 - - [23/May/2014 20:16:31] "GET /favicon.ico HTTP/1.1" 404 - @@ -222,7 +222,7 @@ the application running on port 5000 and the access log entries for it. In addition to the container's logs we can also examine the processes running inside it using the `docker top` command. - $ sudo docker top nostalgic_morse + $ docker top nostalgic_morse PID USER COMMAND 854 root python app.py @@ -235,7 +235,7 @@ Lastly, we can take a low-level dive into our Docker container using the `docker inspect` command. It returns a JSON hash of useful configuration and status information about Docker containers. - $ sudo docker inspect nostalgic_morse + $ docker inspect nostalgic_morse Let's see a sample of that JSON output. @@ -255,7 +255,7 @@ Let's see a sample of that JSON output. We can also narrow down the information we want to return by requesting a specific element, for example to return the container's IP address we would: - $ sudo docker inspect -f '{{ .NetworkSettings.IPAddress }}' nostalgic_morse + $ docker inspect -f '{{ .NetworkSettings.IPAddress }}' nostalgic_morse 172.17.0.5 ## Stopping our Web Application Container @@ -263,13 +263,13 @@ specific element, for example to return the container's IP address we would: Okay we've seen web application working. Now let's stop it using the `docker stop` command and the name of our container: `nostalgic_morse`. - $ sudo docker stop nostalgic_morse + $ docker stop nostalgic_morse nostalgic_morse We can now use the `docker ps` command to check if the container has been stopped. - $ sudo docker ps -l + $ docker ps -l ## Restarting our Web Application Container @@ -278,7 +278,7 @@ developer needs the container back. From here you have two choices: you can create a new container or restart the old one. Let's look at starting our previous container back up. - $ sudo docker start nostalgic_morse + $ docker start nostalgic_morse nostalgic_morse Now quickly run `docker ps -l` again to see the running container is @@ -294,7 +294,7 @@ responds. Your colleague has let you know that they've now finished with the container and won't need it again. So let's remove it using the `docker rm` command. - $ sudo docker rm nostalgic_morse + $ docker rm nostalgic_morse Error: Impossible to remove a running container, please stop it first or use -f 2014/05/24 08:12:56 Error: failed to remove one or more containers @@ -302,9 +302,9 @@ What happened? We can't actually remove a running container. This protects you from accidentally removing a running container you might need. Let's try this again by stopping the container first. - $ sudo docker stop nostalgic_morse + $ docker stop nostalgic_morse nostalgic_morse - $ sudo docker rm nostalgic_morse + $ docker rm nostalgic_morse nostalgic_morse And now our container is stopped and deleted. From 6b2eeaf8965bac07022752c411b1f8a0f35f9571 Mon Sep 17 00:00:00 2001 From: Don Kjer Date: Mon, 12 Jan 2015 19:56:01 +0000 Subject: [PATCH 124/999] Fix for issue 9922: private registry search with auth returns 401 Signed-off-by: Don Kjer --- api/client/attach.go | 2 +- api/client/commit.go | 2 +- api/client/cp.go | 2 +- api/client/create.go | 4 +- api/client/diff.go | 2 +- api/client/exec.go | 4 +- api/client/history.go | 2 +- api/client/images.go | 4 +- api/client/info.go | 2 +- api/client/inspect.go | 4 +- api/client/kill.go | 2 +- api/client/login.go | 2 +- api/client/logs.go | 2 +- api/client/pause.go | 2 +- api/client/port.go | 2 +- api/client/ps.go | 2 +- api/client/pull.go | 35 +------- api/client/push.go | 31 +------ api/client/rename.go | 2 +- api/client/restart.go | 2 +- api/client/rm.go | 2 +- api/client/rmi.go | 2 +- api/client/run.go | 6 +- api/client/search.go | 21 ++++- api/client/start.go | 6 +- api/client/stats.go | 2 +- api/client/stop.go | 2 +- api/client/tag.go | 2 +- api/client/top.go | 2 +- api/client/unpause.go | 2 +- api/client/utils.go | 184 +++++++++++++++++++++++------------------- api/client/version.go | 2 +- registry/auth.go | 51 ++---------- registry/endpoint.go | 18 +++++ registry/session.go | 4 + 35 files changed, 189 insertions(+), 227 deletions(-) diff --git a/api/client/attach.go b/api/client/attach.go index 5b8048589..a2d0cd85b 100644 --- a/api/client/attach.go +++ b/api/client/attach.go @@ -26,7 +26,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { utils.ParseFlags(cmd, args, true) name := cmd.Arg(0) - stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) + stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, nil) if err != nil { return err } diff --git a/api/client/commit.go b/api/client/commit.go index 4214cffce..4f1361015 100644 --- a/api/client/commit.go +++ b/api/client/commit.go @@ -66,7 +66,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error { return err } } - stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, false) + stream, _, err := cli.call("POST", "/commit?"+v.Encode(), config, nil) if err != nil { return err } diff --git a/api/client/cp.go b/api/client/cp.go index 19e050e16..db14e2f53 100644 --- a/api/client/cp.go +++ b/api/client/cp.go @@ -32,7 +32,7 @@ func (cli *DockerCli) CmdCp(args ...string) error { copyData.Set("Resource", info[1]) copyData.Set("HostPath", cmd.Arg(1)) - stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, false) + stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, nil) if stream != nil { defer stream.Close() } diff --git a/api/client/create.go b/api/client/create.go index 10b16da8d..fed4734ec 100644 --- a/api/client/create.go +++ b/api/client/create.go @@ -92,7 +92,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc } //create the container - stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false) + stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil) //if image not found try to pull it if statusCode == 404 { repo, tag := parsers.ParseRepositoryTag(config.Image) @@ -106,7 +106,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc return nil, err } // Retry - if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, false); err != nil { + if stream, _, err = cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil); err != nil { return nil, err } } else if err != nil { diff --git a/api/client/diff.go b/api/client/diff.go index 08f16a712..be58d9cfb 100644 --- a/api/client/diff.go +++ b/api/client/diff.go @@ -20,7 +20,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error { utils.ParseFlags(cmd, args, true) - body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, false)) + body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil)) if err != nil { return err diff --git a/api/client/exec.go b/api/client/exec.go index 51acb3710..c0d8ec0f7 100644 --- a/api/client/exec.go +++ b/api/client/exec.go @@ -24,7 +24,7 @@ func (cli *DockerCli) CmdExec(args ...string) error { return &utils.StatusError{StatusCode: 1} } - stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, false) + stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil) if err != nil { return err } @@ -49,7 +49,7 @@ func (cli *DockerCli) CmdExec(args ...string) error { return err } } else { - if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, false)); err != nil { + if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, nil)); err != nil { return err } // For now don't print this - wait for when we support exec wait() diff --git a/api/client/history.go b/api/client/history.go index b932a4eda..85c48d99d 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -23,7 +23,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { utils.ParseFlags(cmd, args, true) - body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, false)) + body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil)) if err != nil { return err } diff --git a/api/client/images.go b/api/client/images.go index 582b89069..c546619bf 100644 --- a/api/client/images.go +++ b/api/client/images.go @@ -129,7 +129,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { v.Set("filters", filterJSON) } - body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) + body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil)) if err != nil { return err } @@ -207,7 +207,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { v.Set("all", "1") } - body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, false)) + body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil)) if err != nil { return err diff --git a/api/client/info.go b/api/client/info.go index 93f4e1e0e..754474274 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -20,7 +20,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { cmd.Require(flag.Exact, 0) utils.ParseFlags(cmd, args, false) - body, _, err := readBody(cli.call("GET", "/info", nil, false)) + body, _, err := readBody(cli.call("GET", "/info", nil, nil)) if err != nil { return err } diff --git a/api/client/inspect.go b/api/client/inspect.go index 2a840c4bc..34be82e5a 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -37,7 +37,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { status := 0 for _, name := range cmd.Args() { - obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, false)) + obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil)) if err != nil { if strings.Contains(err.Error(), "Too many") { fmt.Fprintf(cli.err, "Error: %v", err) @@ -45,7 +45,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { continue } - obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, false)) + obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil)) if err != nil { if strings.Contains(err.Error(), "No such") { fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name) diff --git a/api/client/kill.go b/api/client/kill.go index 0f4536fee..d7e9a52e6 100644 --- a/api/client/kill.go +++ b/api/client/kill.go @@ -19,7 +19,7 @@ func (cli *DockerCli) CmdKill(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, false)); err != nil { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, nil)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to kill one or more containers") } else { diff --git a/api/client/login.go b/api/client/login.go index 6fe2daba8..27e35d2be 100644 --- a/api/client/login.go +++ b/api/client/login.go @@ -116,7 +116,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig.ServerAddress = serverAddress cli.configFile.Configs[serverAddress] = authconfig - stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], false) + stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], nil) if statusCode == 401 { delete(cli.configFile.Configs, serverAddress) registry.SaveConfig(cli.configFile) diff --git a/api/client/logs.go b/api/client/logs.go index 11809d572..7c4737279 100644 --- a/api/client/logs.go +++ b/api/client/logs.go @@ -25,7 +25,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error { name := cmd.Arg(0) - stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, false) + stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, nil) if err != nil { return err } diff --git a/api/client/pause.go b/api/client/pause.go index a13be5c11..be722e9a4 100644 --- a/api/client/pause.go +++ b/api/client/pause.go @@ -17,7 +17,7 @@ func (cli *DockerCli) CmdPause(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, false)); err != nil { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, nil)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to pause container named %s", name) } else { diff --git a/api/client/port.go b/api/client/port.go index cc3da8afe..574f7616b 100644 --- a/api/client/port.go +++ b/api/client/port.go @@ -19,7 +19,7 @@ func (cli *DockerCli) CmdPort(args ...string) error { cmd.Require(flag.Min, 1) utils.ParseFlags(cmd, args, true) - stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false) + stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil) if err != nil { return err } diff --git a/api/client/ps.go b/api/client/ps.go index dac11f98f..62b82c50d 100644 --- a/api/client/ps.go +++ b/api/client/ps.go @@ -85,7 +85,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { v.Set("filters", filterJSON) } - body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, false)) + body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, nil)) if err != nil { return err } diff --git a/api/client/pull.go b/api/client/pull.go index 3f42ba2ce..f1ba2c061 100644 --- a/api/client/pull.go +++ b/api/client/pull.go @@ -1,11 +1,8 @@ package client import ( - "encoding/base64" - "encoding/json" "fmt" "net/url" - "strings" "github.com/docker/docker/graph" flag "github.com/docker/docker/pkg/mflag" @@ -47,34 +44,6 @@ func (cli *DockerCli) CmdPull(args ...string) error { cli.LoadConfigFile() - // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) - - pull := func(authConfig registry.AuthConfig) error { - buf, err := json.Marshal(authConfig) - if err != nil { - return err - } - registryAuthHeader := []string{ - base64.URLEncoding.EncodeToString(buf), - } - - return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{ - "X-Registry-Auth": registryAuthHeader, - }) - } - - if err := pull(authConfig); err != nil { - if strings.Contains(err.Error(), "Status 401") { - fmt.Fprintln(cli.out, "\nPlease login prior to pull:") - if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil { - return err - } - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) - return pull(authConfig) - } - return err - } - - return nil + _, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull") + return err } diff --git a/api/client/push.go b/api/client/push.go index 025cf682c..7777cc2f9 100644 --- a/api/client/push.go +++ b/api/client/push.go @@ -1,11 +1,8 @@ package client import ( - "encoding/base64" - "encoding/json" "fmt" "net/url" - "strings" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" @@ -50,30 +47,6 @@ 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 { - return err - } - registryAuthHeader := []string{ - base64.URLEncoding.EncodeToString(buf), - } - - return cli.stream("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, map[string][]string{ - "X-Registry-Auth": registryAuthHeader, - }) - } - - if err := push(authConfig); err != nil { - if strings.Contains(err.Error(), "Status 401") { - fmt.Fprintln(cli.out, "\nPlease login prior to push:") - if err := cli.CmdLogin(repoInfo.Index.GetAuthConfigKey()); err != nil { - return err - } - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) - return push(authConfig) - } - return err - } - return nil + _, _, err = cli.clientRequestAttemptLogin("POST", "/images/"+remote+"/push?"+v.Encode(), nil, cli.out, repoInfo.Index, "push") + return err } diff --git a/api/client/rename.go b/api/client/rename.go index 847293d03..278f471f2 100644 --- a/api/client/rename.go +++ b/api/client/rename.go @@ -18,7 +18,7 @@ func (cli *DockerCli) CmdRename(args ...string) error { oldName := cmd.Arg(0) newName := cmd.Arg(1) - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName), nil, false)); err != nil { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName), nil, nil)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) return fmt.Errorf("Error: failed to rename container named %s", oldName) } diff --git a/api/client/restart.go b/api/client/restart.go index 84882f292..609079373 100644 --- a/api/client/restart.go +++ b/api/client/restart.go @@ -24,7 +24,7 @@ func (cli *DockerCli) CmdRestart(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, false)) + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to restart one or more containers") diff --git a/api/client/rm.go b/api/client/rm.go index 0764f76b5..d6ed39b29 100644 --- a/api/client/rm.go +++ b/api/client/rm.go @@ -31,7 +31,7 @@ func (cli *DockerCli) CmdRm(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, false)) + _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more containers") diff --git a/api/client/rmi.go b/api/client/rmi.go index 93e41654b..18659a9af 100644 --- a/api/client/rmi.go +++ b/api/client/rmi.go @@ -32,7 +32,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, false)) + body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") diff --git a/api/client/run.go b/api/client/run.go index e15b7e695..650fe4a18 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -183,14 +183,14 @@ func (cli *DockerCli) CmdRun(args ...string) error { defer func() { if *flAutoRemove { - if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, false)); err != nil { + if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, nil)); err != nil { log.Errorf("Error deleting container: %s", err) } } }() //start the container - if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, false)); err != nil { + if _, _, err = readBody(cli.call("POST", "/containers/"+createResponse.ID+"/start", nil, nil)); err != nil { return err } @@ -220,7 +220,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { if *flAutoRemove { // Autoremove: wait for the container to finish, retrieve // the exit code and remove the container - if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, false)); err != nil { + if _, _, err := readBody(cli.call("POST", "/containers/"+createResponse.ID+"/wait", nil, nil)); err != nil { return err } if _, status, err = getExitCode(cli, createResponse.ID); err != nil { diff --git a/api/client/search.go b/api/client/search.go index 21d704841..3c3de0eb3 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -8,6 +8,8 @@ import ( "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/registry" "github.com/docker/docker/utils" ) @@ -24,16 +26,27 @@ func (cli *DockerCli) CmdSearch(args ...string) error { utils.ParseFlags(cmd, args, true) + name := cmd.Arg(0) v := url.Values{} - v.Set("term", cmd.Arg(0)) - - body, _, err := readBody(cli.call("GET", "/images/search?"+v.Encode(), nil, true)) + v.Set("term", name) + // Resolve the Repository name from fqn to hostname + name + taglessRemote, _ := parsers.ParseRepositoryTag(name) + repoInfo, err := registry.ParseRepositoryInfo(taglessRemote) if err != nil { return err } + + cli.LoadConfigFile() + + body, statusCode, errReq := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search") + rawBody, _, err := readBody(body, statusCode, errReq) + if err != nil { + return err + } + outs := engine.NewTable("star_count", 0) - if _, err := outs.ReadListFrom(body); err != nil { + if _, err := outs.ReadListFrom(rawBody); err != nil { return err } w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) diff --git a/api/client/start.go b/api/client/start.go index ef8f17bd4..42a426555 100644 --- a/api/client/start.go +++ b/api/client/start.go @@ -32,7 +32,7 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { if sig == "" { log.Errorf("Unsupported signal: %v. Discarding.", s) } - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, false)); err != nil { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, nil)); err != nil { log.Debugf("Error sending signal: %s", err) } } @@ -61,7 +61,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { return fmt.Errorf("You cannot start and attach multiple containers at once.") } - stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, false) + stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil) if err != nil { return err } @@ -121,7 +121,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, false)) + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, nil)) if err != nil { if !*attach && !*openStdin { // attach and openStdin is false means it could be starting multiple containers diff --git a/api/client/stats.go b/api/client/stats.go index e28866b9a..eda9ac152 100644 --- a/api/client/stats.go +++ b/api/client/stats.go @@ -29,7 +29,7 @@ type containerStats struct { } func (s *containerStats) Collect(cli *DockerCli) { - stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, false) + stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, nil) if err != nil { s.err = err return diff --git a/api/client/stop.go b/api/client/stop.go index 943f8c58a..e03439c14 100644 --- a/api/client/stop.go +++ b/api/client/stop.go @@ -26,7 +26,7 @@ func (cli *DockerCli) CmdStop(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, false)) + _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to stop one or more containers") diff --git a/api/client/tag.go b/api/client/tag.go index 44a32508f..5b4ebdb4c 100644 --- a/api/client/tag.go +++ b/api/client/tag.go @@ -35,7 +35,7 @@ func (cli *DockerCli) CmdTag(args ...string) error { v.Set("force", "1") } - if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, false)); err != nil { + if _, _, err := readBody(cli.call("POST", "/images/"+cmd.Arg(0)+"/tag?"+v.Encode(), nil, nil)); err != nil { return err } return nil diff --git a/api/client/top.go b/api/client/top.go index b5129acb9..357a5ccc3 100644 --- a/api/client/top.go +++ b/api/client/top.go @@ -25,7 +25,7 @@ func (cli *DockerCli) CmdTop(args ...string) error { val.Set("ps_args", strings.Join(cmd.Args()[1:], " ")) } - stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, false) + stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/top?"+val.Encode(), nil, nil) if err != nil { return err } diff --git a/api/client/unpause.go b/api/client/unpause.go index c5a66f61b..c4ca412b0 100644 --- a/api/client/unpause.go +++ b/api/client/unpause.go @@ -17,7 +17,7 @@ func (cli *DockerCli) CmdUnpause(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, false)); err != nil { + if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, nil)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name) } else { diff --git a/api/client/utils.go b/api/client/utils.go index 62be214f7..7ce0592ed 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -54,68 +54,119 @@ func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) { return params, nil } -func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo bool) (io.ReadCloser, int, error) { - params, err := cli.encodeData(data) - if err != nil { - return nil, -1, err +func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers map[string][]string) (io.ReadCloser, string, int, error) { + expectedPayload := (method == "POST" || method == "PUT") + if expectedPayload && in == nil { + in = bytes.NewReader([]byte{}) } - req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), params) + req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in) if err != nil { - return nil, -1, err - } - if passAuthInfo { - cli.LoadConfigFile() - // Resolve the Auth config relevant for this server - authConfig := cli.configFile.Configs[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 - } - } + return nil, "", -1, err } req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.URL.Host = cli.addr req.URL.Scheme = cli.scheme - if data != nil { - req.Header.Set("Content-Type", "application/json") - } else if method == "POST" { + + if headers != nil { + for k, v := range headers { + req.Header[k] = v + } + } + + if expectedPayload && req.Header.Get("Content-Type") == "" { req.Header.Set("Content-Type", "text/plain") } + resp, err := cli.HTTPClient().Do(req) + statusCode := -1 + if resp != nil { + statusCode = resp.StatusCode + } if err != nil { if strings.Contains(err.Error(), "connection refused") { - return nil, -1, ErrConnectionRefused + return nil, "", statusCode, ErrConnectionRefused } if cli.tlsConfig == nil { - return nil, -1, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err) + return nil, "", statusCode, fmt.Errorf("%v. Are you trying to connect to a TLS-enabled daemon without TLS?", err) } - return nil, -1, fmt.Errorf("An error occurred trying to connect: %v", err) - + return nil, "", statusCode, fmt.Errorf("An error occurred trying to connect: %v", err) } - if resp.StatusCode < 200 || resp.StatusCode >= 400 { + if statusCode < 200 || statusCode >= 400 { body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, "", statusCode, err + } + if len(body) == 0 { + return nil, "", statusCode, fmt.Errorf("Error: request returned %s for API route and version %s, check if the server supports the requested API version", http.StatusText(statusCode), req.URL) + } + return nil, "", statusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body)) + } + + return resp.Body, resp.Header.Get("Content-Type"), statusCode, nil +} + +func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) { + cmdAttempt := func(authConfig registry.AuthConfig) (io.ReadCloser, int, error) { + buf, err := json.Marshal(authConfig) 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) + registryAuthHeader := []string{ + base64.URLEncoding.EncodeToString(buf), } - return nil, resp.StatusCode, fmt.Errorf("Error response from daemon: %s", bytes.TrimSpace(body)) + + // begin the request + body, contentType, statusCode, err := cli.clientRequest(method, path, in, map[string][]string{ + "X-Registry-Auth": registryAuthHeader, + }) + if err == nil && out != nil { + // If we are streaming output, complete the stream since + // errors may not appear until later. + err = cli.streamBody(body, contentType, true, out, nil) + } + if err != nil { + // Since errors in a stream appear after status 200 has been written, + // we may need to change the status code. + if strings.Contains(err.Error(), "Authentication is required") || + strings.Contains(err.Error(), "Status 401") || + strings.Contains(err.Error(), "status code 401") { + statusCode = http.StatusUnauthorized + } + } + return body, statusCode, err } - return resp.Body, resp.StatusCode, nil + // Resolve the Auth config relevant for this server + authConfig := cli.configFile.ResolveAuthConfig(index) + body, statusCode, err := cmdAttempt(authConfig) + if statusCode == http.StatusUnauthorized { + fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName) + if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil { + return nil, -1, err + } + authConfig = cli.configFile.ResolveAuthConfig(index) + return cmdAttempt(authConfig) + } + return body, statusCode, err +} + +func (cli *DockerCli) call(method, path string, data interface{}, headers map[string][]string) (io.ReadCloser, int, error) { + params, err := cli.encodeData(data) + if err != nil { + return nil, -1, err + } + + if data != nil { + if headers == nil { + headers = make(map[string][]string) + } + headers["Content-Type"] = []string{"application/json"} + } + + body, _, statusCode, err := cli.clientRequest(method, path, params, headers) + return body, statusCode, err } func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error { @@ -123,55 +174,26 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h } func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error { - if (method == "POST" || method == "PUT") && in == nil { - in = bytes.NewReader([]byte{}) - } - - req, err := http.NewRequest(method, fmt.Sprintf("/v%s%s", api.APIVERSION, path), in) + body, contentType, _, err := cli.clientRequest(method, path, in, headers) if err != nil { return err } - req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) - req.URL.Host = cli.addr - req.URL.Scheme = cli.scheme - if method == "POST" { - req.Header.Set("Content-Type", "text/plain") - } + return cli.streamBody(body, contentType, setRawTerminal, stdout, stderr) +} - if headers != nil { - for k, v := range headers { - req.Header[k] = v - } - } - resp, err := cli.HTTPClient().Do(req) - 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() +func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, setRawTerminal bool, stdout, stderr io.Writer) error { + defer 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 jsonmessage.DisplayJSONMessagesStream(resp.Body, stdout, cli.outFd, cli.isTerminalOut) + if api.MatchesContentType(contentType, "application/json") { + return jsonmessage.DisplayJSONMessagesStream(body, stdout, cli.outFd, cli.isTerminalOut) } if stdout != nil || stderr != nil { // When TTY is ON, use regular copy + var err error if setRawTerminal { - _, err = io.Copy(stdout, resp.Body) + _, err = io.Copy(stdout, body) } else { - _, err = stdcopy.StdCopy(stdout, stderr, resp.Body) + _, err = stdcopy.StdCopy(stdout, stderr, body) } log.Debugf("[stream] End of stdout") return err @@ -195,13 +217,13 @@ func (cli *DockerCli) resizeTty(id string, isExec bool) { path = "/exec/" + id + "/resize?" } - if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, false)); err != nil { + if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil { log.Debugf("Error resize: %s", err) } } func waitForExit(cli *DockerCli, containerID string) (int, error) { - stream, _, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, false) + stream, _, err := cli.call("POST", "/containers/"+containerID+"/wait", nil, nil) if err != nil { return -1, err } @@ -216,7 +238,7 @@ func waitForExit(cli *DockerCli, containerID string) (int, error) { // 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) { - stream, _, err := cli.call("GET", "/containers/"+containerID+"/json", nil, false) + stream, _, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil) if err != nil { // If we can't connect, then the daemon probably died. if err != ErrConnectionRefused { @@ -237,7 +259,7 @@ func getExitCode(cli *DockerCli, containerID string) (bool, int, error) { // getExecExitCode perform an inspect on the exec command. It returns // the running state and the exit code. func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) { - stream, _, err := cli.call("GET", "/exec/"+execID+"/json", nil, false) + stream, _, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil) if err != nil { // If we can't connect, then the daemon probably died. if err != ErrConnectionRefused { diff --git a/api/client/version.go b/api/client/version.go index d3752d34e..491b3c4ed 100644 --- a/api/client/version.go +++ b/api/client/version.go @@ -33,7 +33,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error { } fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH) - body, _, err := readBody(cli.call("GET", "/version", nil, false)) + body, _, err := readBody(cli.call("GET", "/version", nil, nil)) if err != nil { return err } diff --git a/registry/auth.go b/registry/auth.go index bb91c95c0..4baf114c6 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -1,7 +1,6 @@ package registry import ( - "crypto/tls" "encoding/base64" "encoding/json" "errors" @@ -71,21 +70,7 @@ func (auth *RequestAuthorization) getToken() (string, error) { return auth.tokenCache, nil } - tlsConfig := tls.Config{ - MinVersion: tls.VersionTLS10, - } - if !auth.registryEndpoint.IsSecure { - tlsConfig.InsecureSkipVerify = true - } - - client := &http.Client{ - Transport: &http.Transport{ - DisableKeepAlives: true, - Proxy: http.ProxyFromEnvironment, - TLSClientConfig: &tlsConfig, - }, - CheckRedirect: AddRequiredHeadersToRedirectedRequests, - } + client := auth.registryEndpoint.HTTPClient() factory := HTTPRequestFactory(nil) for _, challenge := range auth.registryEndpoint.AuthChallenges { @@ -252,16 +237,10 @@ func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HT // loginV1 tries to register/login to the v1 registry server. func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) { var ( - status string - reqBody []byte - err error - client = &http.Client{ - Transport: &http.Transport{ - DisableKeepAlives: true, - Proxy: http.ProxyFromEnvironment, - }, - CheckRedirect: AddRequiredHeadersToRedirectedRequests, - } + status string + reqBody []byte + err error + client = registryEndpoint.HTTPClient() reqStatusCode = 0 serverAddress = authConfig.ServerAddress ) @@ -285,7 +264,7 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils. // using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status. b := strings.NewReader(string(jsonBody)) - req1, err := http.Post(serverAddress+"users/", "application/json; charset=utf-8", b) + req1, err := client.Post(serverAddress+"users/", "application/json; charset=utf-8", b) if err != nil { return "", fmt.Errorf("Server Error: %s", err) } @@ -371,26 +350,10 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils. // is to be determined. func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) { log.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint) - - tlsConfig := tls.Config{ - MinVersion: tls.VersionTLS10, - } - if !registryEndpoint.IsSecure { - tlsConfig.InsecureSkipVerify = true - } - - client := &http.Client{ - Transport: &http.Transport{ - DisableKeepAlives: true, - Proxy: http.ProxyFromEnvironment, - TLSClientConfig: &tlsConfig, - }, - CheckRedirect: AddRequiredHeadersToRedirectedRequests, - } - var ( err error allErrors []error + client = registryEndpoint.HTTPClient() ) for _, challenge := range registryEndpoint.AuthChallenges { diff --git a/registry/endpoint.go b/registry/endpoint.go index b1785e4fd..59ae4dd54 100644 --- a/registry/endpoint.go +++ b/registry/endpoint.go @@ -1,6 +1,7 @@ package registry import ( + "crypto/tls" "encoding/json" "fmt" "io/ioutil" @@ -262,3 +263,20 @@ HeaderLoop: return RegistryInfo{}, fmt.Errorf("v2 registry endpoint returned status %d: %q", resp.StatusCode, http.StatusText(resp.StatusCode)) } + +func (e *Endpoint) HTTPClient() *http.Client { + tlsConfig := tls.Config{ + MinVersion: tls.VersionTLS10, + } + if !e.IsSecure { + tlsConfig.InsecureSkipVerify = true + } + return &http.Client{ + Transport: &http.Transport{ + DisableKeepAlives: true, + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tlsConfig, + }, + CheckRedirect: AddRequiredHeadersToRedirectedRequests, + } +} diff --git a/registry/session.go b/registry/session.go index 82338252e..bf04b586d 100644 --- a/registry/session.go +++ b/registry/session.go @@ -511,6 +511,10 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate } defer res.Body.Close() + if res.StatusCode == 401 { + return nil, errLoginRequired + } + var tokens, endpoints []string if !validate { if res.StatusCode != 200 && res.StatusCode != 201 { From 849e55f4e499f729e7e63ca2b20f3371a8e15d86 Mon Sep 17 00:00:00 2001 From: Jamie Hannaford Date: Thu, 26 Mar 2015 20:18:27 +0100 Subject: [PATCH 125/999] Fix spelling Signed-off-by: Jamie Hannaford --- docs/sources/project/test-and-docs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/project/test-and-docs.md b/docs/sources/project/test-and-docs.md index cef3cae8e..7f9171cd3 100644 --- a/docs/sources/project/test-and-docs.md +++ b/docs/sources/project/test-and-docs.md @@ -128,7 +128,7 @@ Run the entire test suite on your current repository: If you are working inside a Docker development container, you use the `hack/make.sh` script to run tests. The `hack/make.sh` script doesn't have a single target that runs all the tests. Instead, you provide a single -commmand line with multiple targets that does the same thing. +command line with multiple targets that does the same thing. Try this now. From 7fdf5257b4d4c94196303cba52b1882001d32754 Mon Sep 17 00:00:00 2001 From: Jamie Hannaford Date: Thu, 26 Mar 2015 20:39:15 +0100 Subject: [PATCH 126/999] Make gofmt use the filename previously referenced Signed-off-by: Jamie Hannaford --- docs/sources/project/work-issue.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/project/work-issue.md b/docs/sources/project/work-issue.md index 561bd231f..2223f8610 100644 --- a/docs/sources/project/work-issue.md +++ b/docs/sources/project/work-issue.md @@ -69,7 +69,7 @@ Follow this workflow as you work: For example, if you edited the `docker.go` file you would format the file like this:

-

$ gofmt -s -w file.go

+

$ gofmt -s -w docker.go

Most file editors have a plugin to format for you. Check your editor's documentation. From 1401b8fe0da7e4cd5ee8a1774c2352c6183be10b Mon Sep 17 00:00:00 2001 From: Pradeep Chhetri Date: Thu, 19 Mar 2015 01:29:26 +0530 Subject: [PATCH 127/999] Added integration tests for docker wait command Signed-off-by: Pradeep Chhetri --- integration-cli/docker_cli_wait_test.go | 121 ++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 integration-cli/docker_cli_wait_test.go diff --git a/integration-cli/docker_cli_wait_test.go b/integration-cli/docker_cli_wait_test.go new file mode 100644 index 000000000..aece88e33 --- /dev/null +++ b/integration-cli/docker_cli_wait_test.go @@ -0,0 +1,121 @@ +package main + +import ( + "os/exec" + "testing" + "time" +) + +// non-blocking wait with 0 exit code +func TestWaitNonBlockedExitZero(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "true") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + containerID := stripTrailingCharacters(out) + + status := "true" + for i := 0; status != "false"; i++ { + runCmd = exec.Command(dockerBinary, "inspect", "--format='{{.State.Running}}'", containerID) + status, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(status, err) + } + status = stripTrailingCharacters(status) + + time.Sleep(time.Second) + if i >= 60 { + t.Fatal("Container should have stopped by now") + } + } + + runCmd = exec.Command(dockerBinary, "wait", containerID) + out, _, err = runCommandWithOutput(runCmd) + + if err != nil || stripTrailingCharacters(out) != "0" { + t.Fatal("failed to set up container", out, err) + } + + logDone("wait - non-blocking wait with 0 exit code") +} + +// blocking wait with 0 exit code +func TestWaitBlockedExitZero(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + containerID := stripTrailingCharacters(out) + + runCmd = exec.Command(dockerBinary, "wait", containerID) + out, _, err = runCommandWithOutput(runCmd) + + if err != nil || stripTrailingCharacters(out) != "0" { + t.Fatal("failed to set up container", out, err) + } + + logDone("wait - blocking wait with 0 exit code") +} + +// non-blocking wait with random exit code +func TestWaitNonBlockedExitRandom(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "exit 99") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + containerID := stripTrailingCharacters(out) + + status := "true" + for i := 0; status != "false"; i++ { + runCmd = exec.Command(dockerBinary, "inspect", "--format='{{.State.Running}}'", containerID) + status, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(status, err) + } + status = stripTrailingCharacters(status) + + time.Sleep(time.Second) + if i >= 60 { + t.Fatal("Container should have stopped by now") + } + } + + runCmd = exec.Command(dockerBinary, "wait", containerID) + out, _, err = runCommandWithOutput(runCmd) + + if err != nil || stripTrailingCharacters(out) != "99" { + t.Fatal("failed to set up container", out, err) + } + + logDone("wait - non-blocking wait with random exit code") +} + +// blocking wait with random exit code +func TestWaitBlockedExitRandom(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10; exit 99") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + containerID := stripTrailingCharacters(out) + + runCmd = exec.Command(dockerBinary, "wait", containerID) + out, _, err = runCommandWithOutput(runCmd) + + if err != nil || stripTrailingCharacters(out) != "99" { + t.Fatal("failed to set up container", out, err) + } + + logDone("wait - blocking wait with random exit code") +} From 2c07fd9fdf78842b4a09ff9a226bd57ba500f16b Mon Sep 17 00:00:00 2001 From: John Willis Date: Thu, 26 Mar 2015 16:09:56 -0400 Subject: [PATCH 128/999] #11465 Add additional doc for locagi registries on pull command - for docker-pull.1.md Signed-off-by: John Willis --- docs/man/docker-pull.1.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/man/docker-pull.1.md b/docs/man/docker-pull.1.md index f1963df55..87a2838e5 100644 --- a/docs/man/docker-pull.1.md +++ b/docs/man/docker-pull.1.md @@ -8,7 +8,7 @@ docker-pull - Pull an image or a repository from the registry **docker pull** [**-a**|**--all-tags**[=*false*]] [**--help**] -NAME[:TAG] +NAME[:TAG] | [REGISTRY_HOST[:REGISTRY_PORT]/]NAME[:TAG] # DESCRIPTION @@ -67,3 +67,4 @@ April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on docker.com source material and internal work. June 2014, updated by Sven Dowideit August 2014, updated by Sven Dowideit +April 2015, updated by John Willis From ae907e7af16136225417242ef5d55d3f6062fb3b Mon Sep 17 00:00:00 2001 From: Peter Choi Date: Wed, 25 Mar 2015 19:40:23 -0600 Subject: [PATCH 129/999] Changed snake case naming to camelCase Signed-off-by: Peter Choi --- api/server/server_unit_test.go | 16 +++++----- daemon/daemon.go | 2 +- daemon/graphdriver/graphtest/graphtest.go | 6 ++-- daemon/info.go | 12 ++++---- daemon/list.go | 14 ++++----- daemon/networkdriver/bridge/driver.go | 6 ++-- daemon/networkdriver/bridge/driver_test.go | 20 ++++++------- engine/hack.go | 4 +-- graph/list.go | 20 ++++++------- graph/pull.go | 34 +++++++++++----------- integration-cli/docker_cli_ps_test.go | 12 ++++---- integration-cli/docker_cli_run_test.go | 14 ++++----- integration-cli/docker_cli_start_test.go | 6 ++-- integration-cli/docker_cli_tag_test.go | 4 +-- integration/utils_test.go | 2 +- pkg/iptables/iptables.go | 4 +-- pkg/mflag/flag.go | 12 ++++---- registry/config.go | 6 ++-- registry/registry_mock_test.go | 8 ++--- 19 files changed, 101 insertions(+), 101 deletions(-) diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index 0501bea36..f83b5cc54 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -276,18 +276,18 @@ func TestGetEvents(t *testing.T) { t.Fatal("handler was not called") } assertContentType(r, "application/json", t) - var stdout_json struct { + var stdoutJSON struct { Since int Until int } - if err := json.Unmarshal(r.Body.Bytes(), &stdout_json); err != nil { + if err := json.Unmarshal(r.Body.Bytes(), &stdoutJSON); err != nil { t.Fatal(err) } - if stdout_json.Since != 1 { - t.Errorf("since != 1: %#v", stdout_json.Since) + if stdoutJSON.Since != 1 { + t.Errorf("since != 1: %#v", stdoutJSON.Since) } - if stdout_json.Until != 0 { - t.Errorf("until != 0: %#v", stdout_json.Until) + if stdoutJSON.Until != 0 { + t.Errorf("until != 0: %#v", stdoutJSON.Until) } } @@ -509,8 +509,8 @@ func toJson(data interface{}, t *testing.T) io.Reader { return &buf } -func assertContentType(recorder *httptest.ResponseRecorder, content_type string, t *testing.T) { - if recorder.HeaderMap.Get("Content-Type") != content_type { +func assertContentType(recorder *httptest.ResponseRecorder, contentType string, t *testing.T) { + if recorder.HeaderMap.Get("Content-Type") != contentType { t.Fatalf("%#v\n", recorder) } } diff --git a/daemon/daemon.go b/daemon/daemon.go index eebf0a45f..c03ffcd9c 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -154,7 +154,7 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { } // FIXME: this hack is necessary for legacy integration tests to access // the daemon object. - eng.Hack_SetGlobalVar("httpapi.daemon", daemon) + eng.HackSetGlobalVar("httpapi.daemon", daemon) return nil } diff --git a/daemon/graphdriver/graphtest/graphtest.go b/daemon/graphdriver/graphtest/graphtest.go index 2bd30f6ae..d9908d400 100644 --- a/daemon/graphdriver/graphtest/graphtest.go +++ b/daemon/graphdriver/graphtest/graphtest.go @@ -24,7 +24,7 @@ type Driver struct { // InitLoopbacks ensures that the loopback devices are properly created within // the system running the device mapper tests. func InitLoopbacks() error { - stat_t, err := getBaseLoopStats() + statT, err := getBaseLoopStats() if err != nil { return err } @@ -34,10 +34,10 @@ func InitLoopbacks() error { // only create new loopback files if they don't exist if _, err := os.Stat(loopPath); err != nil { if mkerr := syscall.Mknod(loopPath, - uint32(stat_t.Mode|syscall.S_IFBLK), int((7<<8)|(i&0xff)|((i&0xfff00)<<12))); mkerr != nil { + uint32(statT.Mode|syscall.S_IFBLK), int((7<<8)|(i&0xff)|((i&0xfff00)<<12))); mkerr != nil { return mkerr } - os.Chown(loopPath, int(stat_t.Uid), int(stat_t.Gid)) + os.Chown(loopPath, int(statT.Uid), int(statT.Gid)) } } return nil diff --git a/daemon/info.go b/daemon/info.go index 91ac5c6a6..0ed3038cb 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -89,14 +89,14 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) error { v.SetInt("NCPU", runtime.NumCPU()) v.SetInt64("MemTotal", meminfo.MemTotal) v.Set("DockerRootDir", daemon.Config().Root) - if http_proxy := os.Getenv("http_proxy"); http_proxy != "" { - v.Set("HttpProxy", http_proxy) + if httpProxy := os.Getenv("http_proxy"); httpProxy != "" { + v.Set("HttpProxy", httpProxy) } - if https_proxy := os.Getenv("https_proxy"); https_proxy != "" { - v.Set("HttpsProxy", https_proxy) + if httpsProxy := os.Getenv("https_proxy"); httpsProxy != "" { + v.Set("HttpsProxy", httpsProxy) } - if no_proxy := os.Getenv("no_proxy"); no_proxy != "" { - v.Set("NoProxy", no_proxy) + if noProxy := os.Getenv("no_proxy"); noProxy != "" { + v.Set("NoProxy", noProxy) } if hostname, err := os.Hostname(); err == nil { diff --git a/daemon/list.go b/daemon/list.go index 3779cc3ec..b1c134375 100644 --- a/daemon/list.go +++ b/daemon/list.go @@ -30,7 +30,7 @@ func (daemon *Daemon) Containers(job *engine.Job) error { n = job.GetenvInt("limit") size = job.GetenvBool("size") psFilters filters.Args - filt_exited []int + filtExited []int ) outs := engine.NewTable("Created", 0) @@ -44,7 +44,7 @@ func (daemon *Daemon) Containers(job *engine.Job) error { if err != nil { return err } - filt_exited = append(filt_exited, code) + filtExited = append(filtExited, code) } } @@ -109,15 +109,15 @@ func (daemon *Daemon) Containers(job *engine.Job) error { return errLast } } - if len(filt_exited) > 0 { - should_skip := true - for _, code := range filt_exited { + if len(filtExited) > 0 { + shouldSkip := true + for _, code := range filtExited { if code == container.ExitCode && !container.Running { - should_skip = false + shouldSkip = false break } } - if should_skip { + if shouldSkip { return nil } } diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index 61237ebe9..b8cb133d1 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -266,7 +266,7 @@ func InitDriver(job *engine.Job) error { ipAllocator.RequestIP(bridgeIPv4Network, bridgeIPv4Network.IP) // https://github.com/docker/docker/issues/2768 - job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", bridgeIPv4Network.IP) + job.Eng.HackSetGlobalVar("httpapi.bridgeIP", bridgeIPv4Network.IP) for name, f := range map[string]engine.Handler{ "allocate_interface": Allocate, @@ -522,8 +522,8 @@ func Allocate(job *engine.Job) error { if globalIPv6Network != nil { // If globalIPv6Network Size is at least a /80 subnet generate IPv6 address from MAC address - netmask_ones, _ := globalIPv6Network.Mask.Size() - if requestedIPv6 == nil && netmask_ones <= 80 { + netmaskOnes, _ := globalIPv6Network.Mask.Size() + if requestedIPv6 == nil && netmaskOnes <= 80 { requestedIPv6 = make(net.IP, len(globalIPv6Network.IP)) copy(requestedIPv6, globalIPv6Network.IP) for i, h := range mac { diff --git a/daemon/networkdriver/bridge/driver_test.go b/daemon/networkdriver/bridge/driver_test.go index 50b2ff503..b646dfd71 100644 --- a/daemon/networkdriver/bridge/driver_test.go +++ b/daemon/networkdriver/bridge/driver_test.go @@ -184,16 +184,16 @@ func TestIPv6InterfaceAllocationAutoNetmaskLe80(t *testing.T) { // ensure global ip with mac ip := net.ParseIP(output.Get("GlobalIPv6")) - expected_ip := net.ParseIP("2001:db8:1234:1234:1234:abcd:abcd:abcd") - if ip.String() != expected_ip.String() { - t.Fatalf("Error ip %s should be %s", ip.String(), expected_ip.String()) + expectedIP := net.ParseIP("2001:db8:1234:1234:1234:abcd:abcd:abcd") + if ip.String() != expectedIP.String() { + t.Fatalf("Error ip %s should be %s", ip.String(), expectedIP.String()) } // ensure link local format ip = net.ParseIP(output.Get("LinkLocalIPv6")) - expected_ip = net.ParseIP("fe80::a9cd:abff:fecd:abcd") - if ip.String() != expected_ip.String() { - t.Fatalf("Error ip %s should be %s", ip.String(), expected_ip.String()) + expectedIP = net.ParseIP("fe80::a9cd:abff:fecd:abcd") + if ip.String() != expectedIP.String() { + t.Fatalf("Error ip %s should be %s", ip.String(), expectedIP.String()) } } @@ -203,18 +203,18 @@ func TestIPv6InterfaceAllocationRequest(t *testing.T) { input := engine.Env{} _, subnet, _ := net.ParseCIDR("2001:db8:1234:1234:1234::/80") - expected_ip := net.ParseIP("2001:db8:1234:1234:1234::1328") + expectedIP := net.ParseIP("2001:db8:1234:1234:1234::1328") // set global ipv6 input.Set("globalIPv6Network", subnet.String()) - input.Set("RequestedIPv6", expected_ip.String()) + input.Set("RequestedIPv6", expectedIP.String()) output := newInterfaceAllocation(t, input) // ensure global ip with mac ip := net.ParseIP(output.Get("GlobalIPv6")) - if ip.String() != expected_ip.String() { - t.Fatalf("Error ip %s should be %s", ip.String(), expected_ip.String()) + if ip.String() != expectedIP.String() { + t.Fatalf("Error ip %s should be %s", ip.String(), expectedIP.String()) } // retry -> fails for duplicated address diff --git a/engine/hack.go b/engine/hack.go index be4fadbe6..10595ce2b 100644 --- a/engine/hack.go +++ b/engine/hack.go @@ -2,7 +2,7 @@ package engine type Hack map[string]interface{} -func (eng *Engine) Hack_GetGlobalVar(key string) interface{} { +func (eng *Engine) HackGetGlobalVar(key string) interface{} { if eng.hack == nil { return nil } @@ -13,7 +13,7 @@ func (eng *Engine) Hack_GetGlobalVar(key string) interface{} { return val } -func (eng *Engine) Hack_SetGlobalVar(key string, val interface{}) { +func (eng *Engine) HackSetGlobalVar(key string, val interface{}) { if eng.hack == nil { eng.hack = make(Hack) } diff --git a/graph/list.go b/graph/list.go index 8e0d12f64..4d269e011 100644 --- a/graph/list.go +++ b/graph/list.go @@ -19,10 +19,10 @@ var acceptedImageFilterTags = map[string]struct{}{ func (s *TagStore) CmdImages(job *engine.Job) error { var ( - allImages map[string]*image.Image - err error - filt_tagged = true - filt_label = false + allImages map[string]*image.Image + err error + filtTagged = true + filtLabel = false ) imageFilters, err := filters.FromParam(job.Getenv("filters")) @@ -38,14 +38,14 @@ func (s *TagStore) CmdImages(job *engine.Job) error { if i, ok := imageFilters["dangling"]; ok { for _, value := range i { if strings.ToLower(value) == "true" { - filt_tagged = false + filtTagged = false } } } - _, filt_label = imageFilters["label"] + _, filtLabel = imageFilters["label"] - if job.GetenvBool("all") && filt_tagged { + if job.GetenvBool("all") && filtTagged { allImages, err = s.graph.Map() } else { allImages, err = s.graph.Heads() @@ -70,7 +70,7 @@ func (s *TagStore) CmdImages(job *engine.Job) error { } if out, exists := lookup[id]; exists { - if filt_tagged { + if filtTagged { if utils.DigestReference(ref) { out.SetList("RepoDigests", append(out.GetList("RepoDigests"), imgRef)) } else { // Tag Ref. @@ -83,7 +83,7 @@ func (s *TagStore) CmdImages(job *engine.Job) error { if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) { continue } - if filt_tagged { + if filtTagged { out := &engine.Env{} out.SetJson("ParentId", image.Parent) out.SetJson("Id", image.ID) @@ -114,7 +114,7 @@ func (s *TagStore) CmdImages(job *engine.Job) error { } // Display images which aren't part of a repository/tag - if job.Getenv("filter") == "" || filt_label { + if job.Getenv("filter") == "" || filtLabel { for _, image := range allImages { if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) { continue diff --git a/graph/pull.go b/graph/pull.go index 0a6b2800c..75e0a6d45 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -152,7 +152,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * errors := make(chan error) - layers_downloaded := false + layersDownloaded := false for _, image := range repoData.ImgList { downloadImage := func(img *registry.ImgData) { if askedTag != "" && img.Tag != askedTag { @@ -189,29 +189,29 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s", img.Tag, repoInfo.CanonicalName), nil)) success := false var lastErr, err error - var is_downloaded bool + var isDownloaded bool for _, ep := range repoInfo.Index.Mirrors { out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, repoInfo.CanonicalName, ep), nil)) - if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { + if isDownloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { // Don't report errors when pulling from mirrors. log.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err) continue } - layers_downloaded = layers_downloaded || is_downloaded + layersDownloaded = layersDownloaded || isDownloaded success = true break } if !success { for _, ep := range repoData.Endpoints { out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, repoInfo.CanonicalName, ep), nil)) - if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { + if isDownloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { // It's not ideal that only the last error is returned, it would be better to concatenate the errors. // As the error is also given to the output stream the user will see the error. lastErr = err out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Error pulling image (%s) from %s, endpoint: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err), nil)) continue } - layers_downloaded = layers_downloaded || is_downloaded + layersDownloaded = layersDownloaded || isDownloaded success = true break } @@ -262,7 +262,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * if len(askedTag) > 0 { requestedTag = utils.ImageReference(repoInfo.CanonicalName, askedTag) } - WriteStatus(requestedTag, out, sf, layers_downloaded) + WriteStatus(requestedTag, out, sf, layersDownloaded) return nil } @@ -275,7 +275,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint // FIXME: Try to stream the images? // FIXME: Launch the getRemoteImage() in goroutines - layers_downloaded := false + layersDownloaded := false for i := len(history) - 1; i >= 0; i-- { id := history[i] @@ -299,16 +299,16 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token) if err != nil && j == retries { out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil)) - return layers_downloaded, err + return layersDownloaded, err } else if err != nil { time.Sleep(time.Duration(j) * 500 * time.Millisecond) continue } img, err = image.NewImgJSON(imgJSON) - layers_downloaded = true + layersDownloaded = true if err != nil && j == retries { out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil)) - return layers_downloaded, fmt.Errorf("Failed to parse json: %s", err) + return layersDownloaded, fmt.Errorf("Failed to parse json: %s", err) } else if err != nil { time.Sleep(time.Duration(j) * 500 * time.Millisecond) continue @@ -333,9 +333,9 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint continue } else if err != nil { out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil)) - return layers_downloaded, err + return layersDownloaded, err } - layers_downloaded = true + layersDownloaded = true defer layer.Close() err = s.graph.Register(img, @@ -353,7 +353,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint continue } else if err != nil { out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error downloading dependent layers", nil)) - return layers_downloaded, err + return layersDownloaded, err } else { break } @@ -361,11 +361,11 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint } out.Write(sf.FormatProgress(stringid.TruncateID(id), "Download complete", nil)) } - return layers_downloaded, nil + return layersDownloaded, nil } -func WriteStatus(requestedTag string, out io.Writer, sf *streamformatter.StreamFormatter, layers_downloaded bool) { - if layers_downloaded { +func WriteStatus(requestedTag string, out io.Writer, sf *streamformatter.StreamFormatter, layersDownloaded bool) { + if layersDownloaded { out.Write(sf.FormatStatus("", "Status: Downloaded newer image for %s", requestedTag)) } else { out.Write(sf.FormatStatus("", "Status: Image is up to date for %s", requestedTag)) diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index c2e108576..1d32a9320 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -251,11 +251,11 @@ func TestPsListContainersSize(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello") runCommandWithOutput(cmd) cmd = exec.Command(dockerBinary, "ps", "-s", "-n=1") - base_out, _, err := runCommandWithOutput(cmd) - base_lines := strings.Split(strings.Trim(base_out, "\n "), "\n") - base_sizeIndex := strings.Index(base_lines[0], "SIZE") - base_foundSize := base_lines[1][base_sizeIndex:] - base_bytes, err := strconv.Atoi(strings.Split(base_foundSize, " ")[0]) + baseOut, _, err := runCommandWithOutput(cmd) + baseLines := strings.Split(strings.Trim(baseOut, "\n "), "\n") + baseSizeIndex := strings.Index(baseLines[0], "SIZE") + baseFoundsize := baseLines[1][baseSizeIndex:] + baseBytes, err := strconv.Atoi(strings.Split(baseFoundsize, " ")[0]) if err != nil { t.Fatal(err) } @@ -292,7 +292,7 @@ func TestPsListContainersSize(t *testing.T) { if foundID != id[:12] { t.Fatalf("Expected id %s, got %s", id[:12], foundID) } - expectedSize := fmt.Sprintf("%d B", (2 + base_bytes)) + expectedSize := fmt.Sprintf("%d B", (2 + baseBytes)) foundSize := lines[1][sizeIndex:] if foundSize != expectedSize { t.Fatalf("Expected size %q, got %q", expectedSize, foundSize) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 0c5f56f8d..b0fe914a3 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2186,15 +2186,15 @@ func eqToBaseDiff(out string, t *testing.T) bool { out1, _, err := runCommandWithOutput(cmd) cID := stripTrailingCharacters(out1) cmd = exec.Command(dockerBinary, "diff", cID) - base_diff, _, err := runCommandWithOutput(cmd) + baseDiff, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, base_diff) + t.Fatal(err, baseDiff) } - base_arr := strings.Split(base_diff, "\n") - sort.Strings(base_arr) - out_arr := strings.Split(out, "\n") - sort.Strings(out_arr) - return sliceEq(base_arr, out_arr) + baseArr := strings.Split(baseDiff, "\n") + sort.Strings(baseArr) + outArr := strings.Split(out, "\n") + sort.Strings(outArr) + return sliceEq(baseArr, outArr) } func sliceEq(a, b []string) bool { diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index 3ec04c916..1e3253e84 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -158,9 +158,9 @@ func TestStartVolumesFromFailsCleanly(t *testing.T) { // Check that we have the volumes we want out, _, _ := dockerCmd(t, "inspect", "--format='{{ len .Volumes }}'", "consumer") - n_volumes := strings.Trim(out, " \r\n'") - if n_volumes != "2" { - t.Fatalf("Missing volumes: expected 2, got %s", n_volumes) + nVolumes := strings.Trim(out, " \r\n'") + if nVolumes != "2" { + t.Fatalf("Missing volumes: expected 2, got %s", nVolumes) } logDone("start - missing containers in --volumes-from did not affect subsequent runs") diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go index b181e2177..89b542367 100644 --- a/integration-cli/docker_cli_tag_test.go +++ b/integration-cli/docker_cli_tag_test.go @@ -61,9 +61,9 @@ func TestTagInvalidUnprefixedRepo(t *testing.T) { // ensure we don't allow the use of invalid tags; these tag operations should fail func TestTagInvalidPrefixedRepo(t *testing.T) { - long_tag := stringutils.GenerateRandomAlphaOnlyString(121) + longTag := stringutils.GenerateRandomAlphaOnlyString(121) - invalidTags := []string{"repo:fo$z$", "repo:Foo@3cc", "repo:Foo$3", "repo:Foo*3", "repo:Fo^3", "repo:Foo!3", "repo:%goodbye", "repo:#hashtagit", "repo:F)xcz(", "repo:-foo", "repo:..", long_tag} + invalidTags := []string{"repo:fo$z$", "repo:Foo@3cc", "repo:Foo$3", "repo:Foo*3", "repo:Fo^3", "repo:Foo!3", "repo:%goodbye", "repo:#hashtagit", "repo:F)xcz(", "repo:-foo", "repo:..", longTag} for _, repotag := range invalidTags { tagCmd := exec.Command(dockerBinary, "tag", "busybox", repotag) diff --git a/integration/utils_test.go b/integration/utils_test.go index 2e90e4f51..1d49ef955 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -150,7 +150,7 @@ func getContainer(eng *engine.Engine, id string, t Fataler) *daemon.Container { } func mkDaemonFromEngine(eng *engine.Engine, t Fataler) *daemon.Daemon { - iDaemon := eng.Hack_GetGlobalVar("httpapi.daemon") + iDaemon := eng.HackGetGlobalVar("httpapi.daemon") if iDaemon == nil { panic("Legacy daemon field not set in engine") } diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index 3e083a43a..53ed45f23 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -260,7 +260,7 @@ func Exists(table Table, chain string, rule ...string) bool { // parse "iptables -S" for the rule (this checks rules in a specific chain // in a specific table) - rule_string := strings.Join(rule, " ") + ruleString := strings.Join(rule, " ") existingRules, _ := exec.Command("iptables", "-t", string(table), "-S", chain).Output() // regex to replace ips in rule @@ -269,7 +269,7 @@ func Exists(table Table, chain string, rule ...string) bool { return strings.Contains( re.ReplaceAllString(string(existingRules), "?"), - re.ReplaceAllString(rule_string, "?"), + re.ReplaceAllString(ruleString, "?"), ) } diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index b35692bfd..81369f88b 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -941,11 +941,11 @@ func (f *FlagSet) parseOne() (bool, string, error) { // it's a flag. does it have an argument? f.args = f.args[1:] - has_value := false + hasValue := false value := "" if i := strings.Index(name, "="); i != -1 { value = trimQuotes(name[i+1:]) - has_value = true + hasValue = true name = name[:i] } @@ -962,7 +962,7 @@ func (f *FlagSet) parseOne() (bool, string, error) { return false, name, ErrRetry } if fv, ok := flag.Value.(boolFlag); ok && fv.IsBoolFlag() { // special case: doesn't need an arg - if has_value { + if hasValue { if err := fv.Set(value); err != nil { return false, "", f.failf("invalid boolean value %q for -%s: %v", value, name, err) } @@ -971,12 +971,12 @@ func (f *FlagSet) parseOne() (bool, string, error) { } } else { // It must have a value, which might be the next argument. - if !has_value && len(f.args) > 0 { + if !hasValue && len(f.args) > 0 { // value is the next arg - has_value = true + hasValue = true value, f.args = f.args[0], f.args[1:] } - if !has_value { + if !hasValue { return false, "", f.failf("flag needs an argument: -%s", name) } if err := flag.Value.Set(value); err != nil { diff --git a/registry/config.go b/registry/config.go index a706f17e6..3515836d1 100644 --- a/registry/config.go +++ b/registry/config.go @@ -60,10 +60,10 @@ func (ipnet *netIPNet) MarshalJSON() ([]byte, error) { } func (ipnet *netIPNet) UnmarshalJSON(b []byte) (err error) { - var ipnet_str string - if err = json.Unmarshal(b, &ipnet_str); err == nil { + var ipnetStr string + if err = json.Unmarshal(b, &ipnetStr); err == nil { var cidr *net.IPNet - if _, cidr, err = net.ParseCIDR(ipnet_str); err == nil { + if _, cidr, err = net.ParseCIDR(ipnetStr); err == nil { *ipnet = netIPNet(*cidr) } } diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go index 57233d7c7..0d987abc7 100644 --- a/registry/registry_mock_test.go +++ b/registry/registry_mock_test.go @@ -171,7 +171,7 @@ func makePublicIndex() *IndexInfo { return index } -func makeServiceConfig(mirrors []string, insecure_registries []string) *ServiceConfig { +func makeServiceConfig(mirrors []string, insecureRegistries []string) *ServiceConfig { options := &Options{ Mirrors: opts.NewListOpts(nil), InsecureRegistries: opts.NewListOpts(nil), @@ -181,9 +181,9 @@ func makeServiceConfig(mirrors []string, insecure_registries []string) *ServiceC options.Mirrors.Set(mirror) } } - if insecure_registries != nil { - for _, insecure_registries := range insecure_registries { - options.InsecureRegistries.Set(insecure_registries) + if insecureRegistries != nil { + for _, insecureRegistries := range insecureRegistries { + options.InsecureRegistries.Set(insecureRegistries) } } From 0532dcf3dc1fa34fab5a9cdee6c4d87af66a6cdf Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Thu, 26 Mar 2015 15:11:28 -0700 Subject: [PATCH 130/999] term/winconsole: Identify tty correctly, fix resize problem This change fixes a bug where stdout/stderr handles are not identified correctly. Previously we used to set the window size to fixed size to fit the default tty size on the host (80x24). Now the attach/exec commands can correctly get the terminal size from windows. We still do not `monitorTtySize()` correctly on windows and update the tty size on the host-side, in order to fix that we'll provide a platform-specific `monitorTtySize` implementation in the future. Signed-off-by: Ahmet Alp Balkan --- pkg/term/winconsole/console_windows.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/term/winconsole/console_windows.go b/pkg/term/winconsole/console_windows.go index 19977b101..85544493f 100644 --- a/pkg/term/winconsole/console_windows.go +++ b/pkg/term/winconsole/console_windows.go @@ -241,8 +241,6 @@ func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) { } handler.screenBufferInfo = screenBufferInfo - // Set the window size - SetWindowSize(stdoutHandle, DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_HEIGHT) buffer = make([]CHAR_INFO, screenBufferInfo.MaximumWindowSize.X*screenBufferInfo.MaximumWindowSize.Y) stdOut = &terminalWriter{ @@ -283,6 +281,12 @@ func GetHandleInfo(in interface{}) (uintptr, bool) { isTerminalIn = IsTerminal(inFd) } } + if tr, ok := in.(*terminalWriter); ok { + if file, ok := tr.wrappedWriter.(*os.File); ok { + inFd = file.Fd() + isTerminalIn = IsTerminal(inFd) + } + } return inFd, isTerminalIn } From 6f4d847046cb4e072de61d042c0266190d73a8c9 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 26 Mar 2015 23:22:04 +0100 Subject: [PATCH 131/999] Replace aliased imports of logrus, fixes #11762 Signed-off-by: Antonio Murdaca --- api/client/attach.go | 4 +- api/client/build.go | 4 +- api/client/exec.go | 12 +- api/client/hijack.go | 12 +- api/client/info.go | 6 +- api/client/run.go | 14 +- api/client/start.go | 12 +- api/client/utils.go | 8 +- api/client/version.go | 6 +- api/common.go | 4 +- api/server/server.go | 36 ++--- builder/dispatchers.go | 4 +- builder/evaluator.go | 6 +- builder/internals.go | 10 +- contrib/docker-device-tool/device_tool.go | 4 +- daemon/attach.go | 30 ++-- daemon/container.go | 40 ++--- daemon/daemon.go | 84 +++++------ daemon/daemon_aufs.go | 4 +- daemon/delete.go | 6 +- daemon/exec.go | 18 +-- daemon/execdriver/lxc/driver.go | 16 +- daemon/execdriver/lxc/lxc_template.go | 10 +- daemon/execdriver/native/driver.go | 6 +- daemon/graphdriver/aufs/aufs.go | 6 +- daemon/graphdriver/aufs/mount.go | 4 +- daemon/graphdriver/devmapper/deviceset.go | 142 +++++++++--------- daemon/graphdriver/devmapper/driver.go | 4 +- daemon/graphdriver/driver.go | 4 +- daemon/graphdriver/fsdiff.go | 6 +- daemon/graphdriver/overlay/overlay.go | 14 +- daemon/info.go | 6 +- daemon/logs.go | 20 +-- daemon/monitor.go | 20 +-- daemon/networkdriver/bridge/driver.go | 40 ++--- daemon/networkdriver/ipallocator/allocator.go | 4 +- .../portallocator/portallocator.go | 6 +- daemon/networkdriver/portmapper/mapper.go | 4 +- daemon/stats_collector.go | 6 +- daemon/volumes.go | 6 +- docker/daemon.go | 24 +-- docker/docker.go | 22 +-- docker/log.go | 8 +- engine/job.go | 6 +- graph/export.go | 12 +- graph/graph.go | 4 +- graph/import.go | 4 +- graph/load.go | 16 +- graph/manifest.go | 4 +- graph/pull.go | 50 +++--- graph/push.go | 30 ++-- graph/service.go | 4 +- integration/commands_test.go | 4 +- integration/runtime_test.go | 34 ++--- pkg/archive/archive.go | 26 ++-- pkg/archive/changes.go | 10 +- pkg/broadcastwriter/broadcastwriter.go | 4 +- pkg/devicemapper/attach_loopback.go | 20 +-- pkg/devicemapper/devmapper.go | 26 ++-- pkg/fileutils/fileutils.go | 8 +- pkg/httputils/resumablerequestreader.go | 4 +- pkg/iptables/iptables.go | 4 +- pkg/jsonlog/jsonlog.go | 4 +- pkg/proxy/tcp_proxy.go | 6 +- pkg/proxy/udp_proxy.go | 8 +- pkg/resolvconf/resolvconf.go | 6 +- pkg/signal/trap.go | 6 +- pkg/stdcopy/stdcopy.go | 20 +-- pkg/sysinfo/sysinfo.go | 8 +- registry/auth.go | 16 +- registry/endpoint.go | 20 +-- registry/registry.go | 10 +- registry/registry_mock_test.go | 6 +- registry/service.go | 8 +- registry/session.go | 38 ++--- registry/session_v2.go | 26 ++-- runconfig/merge.go | 4 +- trust/service.go | 4 +- trust/trusts.go | 16 +- utils/http.go | 4 +- utils/utils.go | 4 +- volumes/repository.go | 8 +- 82 files changed, 597 insertions(+), 597 deletions(-) diff --git a/api/client/attach.go b/api/client/attach.go index a2d0cd85b..e6acec48b 100644 --- a/api/client/attach.go +++ b/api/client/attach.go @@ -5,7 +5,7 @@ import ( "io" "net/url" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/signal" @@ -51,7 +51,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { if tty && cli.isTerminalOut { if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { - log.Debugf("Error monitoring TTY size: %s", err) + logrus.Debugf("Error monitoring TTY size: %s", err) } } diff --git a/api/client/build.go b/api/client/build.go index df5ca9b1f..779e98ecc 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -17,7 +17,7 @@ import ( "strconv" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" @@ -198,7 +198,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // windows: show error message about modified file permissions // FIXME: this is not a valid warning when the daemon is running windows. should be removed once docker engine for windows can build. if runtime.GOOS == "windows" { - log.Warn(`SECURITY WARNING: You are building a Docker image from Windows against a Linux Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`) + logrus.Warn(`SECURITY WARNING: You are building a Docker image from Windows against a Linux Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.`) } var body io.Reader diff --git a/api/client/exec.go b/api/client/exec.go index c0d8ec0f7..27e6878df 100644 --- a/api/client/exec.go +++ b/api/client/exec.go @@ -5,7 +5,7 @@ import ( "fmt" "io" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/runconfig" @@ -67,9 +67,9 @@ func (cli *DockerCli) CmdExec(args ...string) error { // Block the return until the chan gets closed defer func() { - log.Debugf("End of CmdExec(), Waiting for hijack to finish.") + logrus.Debugf("End of CmdExec(), Waiting for hijack to finish.") if _, ok := <-hijacked; ok { - log.Errorf("Hijack did not finish (chan still open)") + logrus.Errorf("Hijack did not finish (chan still open)") } }() @@ -100,19 +100,19 @@ func (cli *DockerCli) CmdExec(args ...string) error { } case err := <-errCh: if err != nil { - log.Debugf("Error hijack: %s", err) + logrus.Debugf("Error hijack: %s", err) return err } } if execConfig.Tty && cli.isTerminalIn { if err := cli.monitorTtySize(execID, true); err != nil { - log.Errorf("Error monitoring TTY size: %s", err) + logrus.Errorf("Error monitoring TTY size: %s", err) } } if err := <-errCh; err != nil { - log.Debugf("Error hijack: %s", err) + logrus.Debugf("Error hijack: %s", err) return err } diff --git a/api/client/hijack.go b/api/client/hijack.go index 4f89c3a76..163538416 100644 --- a/api/client/hijack.go +++ b/api/client/hijack.go @@ -13,7 +13,7 @@ import ( "strings" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/pkg/promise" @@ -211,7 +211,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea } else { _, err = stdcopy.StdCopy(stdout, stderr, br) } - log.Debugf("[hijack] End of stdout") + logrus.Debugf("[hijack] End of stdout") return err }) } @@ -219,14 +219,14 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea sendStdin := promise.Go(func() error { if in != nil { io.Copy(rwc, in) - log.Debugf("[hijack] End of stdin") + logrus.Debugf("[hijack] End of stdin") } if conn, ok := rwc.(interface { CloseWrite() error }); ok { if err := conn.CloseWrite(); err != nil { - log.Debugf("Couldn't send EOF: %s", err) + logrus.Debugf("Couldn't send EOF: %s", err) } } // Discard errors due to pipe interruption @@ -235,14 +235,14 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea if stdout != nil || stderr != nil { if err := <-receiveStdout; err != nil { - log.Debugf("Error receiveStdout: %s", err) + logrus.Debugf("Error receiveStdout: %s", err) return err } } if !cli.isTerminalIn { if err := <-sendStdin; err != nil { - log.Debugf("Error sendStdin: %s", err) + logrus.Debugf("Error sendStdin: %s", err) return err } } diff --git a/api/client/info.go b/api/client/info.go index 754474274..7a350e32a 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -5,7 +5,7 @@ import ( "os" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/units" @@ -32,7 +32,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { } if _, err := out.Write(body); err != nil { - log.Errorf("Error reading remote info: %s", err) + logrus.Errorf("Error reading remote info: %s", err) return err } out.Close() @@ -91,7 +91,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { if remoteInfo.Exists("SystemTime") { t, err := remoteInfo.GetTime("SystemTime") if err != nil { - log.Errorf("Error reading system time: %v", err) + logrus.Errorf("Error reading system time: %v", err) } else { fmt.Fprintf(cli.out, "System Time: %s\n", t.Format(time.UnixDate)) } diff --git a/api/client/run.go b/api/client/run.go index 650fe4a18..b13ffd937 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -6,7 +6,7 @@ import ( "net/url" "os" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/opts" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/resolvconf" @@ -132,9 +132,9 @@ func (cli *DockerCli) CmdRun(args ...string) error { hijacked := make(chan io.Closer) // Block the return until the chan gets closed defer func() { - log.Debugf("End of CmdRun(), Waiting for hijack to finish.") + logrus.Debugf("End of CmdRun(), Waiting for hijack to finish.") if _, ok := <-hijacked; ok { - log.Errorf("Hijack did not finish (chan still open)") + logrus.Errorf("Hijack did not finish (chan still open)") } }() if config.AttachStdin || config.AttachStdout || config.AttachStderr { @@ -176,7 +176,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } case err := <-errCh: if err != nil { - log.Debugf("Error hijack: %s", err) + logrus.Debugf("Error hijack: %s", err) return err } } @@ -184,7 +184,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { defer func() { if *flAutoRemove { if _, _, err = readBody(cli.call("DELETE", "/containers/"+createResponse.ID+"?v=1", nil, nil)); err != nil { - log.Errorf("Error deleting container: %s", err) + logrus.Errorf("Error deleting container: %s", err) } } }() @@ -196,13 +196,13 @@ func (cli *DockerCli) CmdRun(args ...string) error { if (config.AttachStdin || config.AttachStdout || config.AttachStderr) && config.Tty && cli.isTerminalOut { if err := cli.monitorTtySize(createResponse.ID, false); err != nil { - log.Errorf("Error monitoring TTY size: %s", err) + logrus.Errorf("Error monitoring TTY size: %s", err) } } if errCh != nil { if err := <-errCh; err != nil { - log.Debugf("Error hijack: %s", err) + logrus.Debugf("Error hijack: %s", err) return err } } diff --git a/api/client/start.go b/api/client/start.go index 42a426555..554b7bcfa 100644 --- a/api/client/start.go +++ b/api/client/start.go @@ -6,7 +6,7 @@ import ( "net/url" "os" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/promise" @@ -30,10 +30,10 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { } } if sig == "" { - log.Errorf("Unsupported signal: %v. Discarding.", s) + logrus.Errorf("Unsupported signal: %v. Discarding.", s) } if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, nil)); err != nil { - log.Debugf("Error sending signal: %s", err) + logrus.Debugf("Error sending signal: %s", err) } } }() @@ -94,9 +94,9 @@ func (cli *DockerCli) CmdStart(args ...string) error { hijacked := make(chan io.Closer) // Block the return until the chan gets closed defer func() { - log.Debugf("CmdStart() returned, defer waiting for hijack to finish.") + logrus.Debugf("CmdStart() returned, defer waiting for hijack to finish.") if _, ok := <-hijacked; ok { - log.Errorf("Hijack did not finish (chan still open)") + logrus.Errorf("Hijack did not finish (chan still open)") } cli.in.Close() }() @@ -145,7 +145,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { if *openStdin || *attach { if tty && cli.isTerminalOut { if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { - log.Errorf("Error monitoring TTY size: %s", err) + logrus.Errorf("Error monitoring TTY size: %s", err) } } if attchErr := <-cErr; attchErr != nil { diff --git a/api/client/utils.go b/api/client/utils.go index 7ce0592ed..8c53a2d5d 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -15,7 +15,7 @@ import ( "strconv" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/engine" @@ -195,7 +195,7 @@ func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, setRawT } else { _, err = stdcopy.StdCopy(stdout, stderr, body) } - log.Debugf("[stream] End of stdout") + logrus.Debugf("[stream] End of stdout") return err } return nil @@ -218,7 +218,7 @@ func (cli *DockerCli) resizeTty(id string, isExec bool) { } if _, _, err := readBody(cli.call("POST", path+v.Encode(), nil, nil)); err != nil { - log.Debugf("Error resize: %s", err) + logrus.Debugf("Error resize: %s", err) } } @@ -295,7 +295,7 @@ func (cli *DockerCli) getTtySize() (int, int) { } ws, err := term.GetWinsize(cli.outFd) if err != nil { - log.Debugf("Error getting size: %s", err) + logrus.Debugf("Error getting size: %s", err) if ws == nil { return 0, 0 } diff --git a/api/client/version.go b/api/client/version.go index 491b3c4ed..f3fea96a0 100644 --- a/api/client/version.go +++ b/api/client/version.go @@ -4,7 +4,7 @@ import ( "fmt" "runtime" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/engine" @@ -41,11 +41,11 @@ func (cli *DockerCli) CmdVersion(args ...string) error { out := engine.NewOutput() remoteVersion, err := out.AddEnv() if err != nil { - log.Errorf("Error reading remote version: %s", err) + logrus.Errorf("Error reading remote version: %s", err) return err } if _, err := out.Write(body); err != nil { - log.Errorf("Error reading remote version: %s", err) + logrus.Errorf("Error reading remote version: %s", err) return err } out.Close() diff --git a/api/common.go b/api/common.go index a0f44e860..8cffa086e 100644 --- a/api/common.go +++ b/api/common.go @@ -7,7 +7,7 @@ import ( "path/filepath" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/version" @@ -105,7 +105,7 @@ func FormGroup(key string, start, last int) string { func MatchesContentType(contentType, expectedType string) bool { mimetype, _, err := mime.ParseMediaType(contentType) if err != nil { - log.Errorf("Error parsing media type: %s error: %v", contentType, err) + logrus.Errorf("Error parsing media type: %s error: %v", contentType, err) } return err == nil && mimetype == expectedType } diff --git a/api/server/server.go b/api/server/server.go index c52c6bd2a..2dabbbeba 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -24,7 +24,7 @@ import ( "github.com/docker/libcontainer/user" "github.com/gorilla/mux" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/daemon/networkdriver/portallocator" @@ -135,7 +135,7 @@ func httpError(w http.ResponseWriter, err error) { } if err != nil { - log.Errorf("HTTP Error: statusCode=%d %v", statusCode, err) + logrus.Errorf("HTTP Error: statusCode=%d %v", statusCode, err) http.Error(w, err.Error(), statusCode) } } @@ -517,7 +517,7 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit } if err := config.Decode(r.Body); err != nil { - log.Errorf("%s", err) + logrus.Errorf("%s", err) } if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { @@ -987,7 +987,7 @@ func wsContainersAttach(eng *engine.Engine, version version.Version, w http.Resp job.Stdout.Add(ws) job.Stderr.Set(ws) if err := job.Run(); err != nil { - log.Errorf("Error attaching websocket: %s", err) + logrus.Errorf("Error attaching websocket: %s", err) } }) h.ServeHTTP(w, r) @@ -1101,7 +1101,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite select { case <-finished: case <-closeNotifier.CloseNotify(): - log.Infof("Client disconnected, cancelling job: %s", job.Name) + logrus.Infof("Client disconnected, cancelling job: %s", job.Name) job.Cancel() } }() @@ -1146,7 +1146,7 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp job.Stdout.Add(w) w.Header().Set("Content-Type", "application/x-tar") if err := job.Run(); err != nil { - log.Errorf("%v", err) + logrus.Errorf("%v", err) if strings.Contains(strings.ToLower(err.Error()), "no such id") { w.WriteHeader(http.StatusNotFound) } else if strings.Contains(err.Error(), "no such file or directory") { @@ -1262,7 +1262,7 @@ func optionsHandler(eng *engine.Engine, version version.Version, w http.Response return nil } func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string) { - log.Debugf("CORS header is enabled and set to: %s", corsHeaders) + logrus.Debugf("CORS header is enabled and set to: %s", corsHeaders) w.Header().Add("Access-Control-Allow-Origin", corsHeaders) w.Header().Add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth") w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") @@ -1276,16 +1276,16 @@ func ping(eng *engine.Engine, version version.Version, w http.ResponseWriter, r func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, corsHeaders string, dockerVersion version.Version) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // log the request - log.Debugf("Calling %s %s", localMethod, localRoute) + logrus.Debugf("Calling %s %s", localMethod, localRoute) if logging { - log.Infof("%s %s", r.Method, r.RequestURI) + logrus.Infof("%s %s", r.Method, r.RequestURI) } if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") { userAgent := strings.Split(r.Header.Get("User-Agent"), "/") if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) { - log.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion) + logrus.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion) } } version := version.Version(mux.Vars(r)["version"]) @@ -1302,7 +1302,7 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local } if err := handlerFunc(eng, version, w, r, mux.Vars(r)); err != nil { - log.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err) + logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err) httpError(w, err) } } @@ -1406,7 +1406,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, corsHeaders stri for method, routes := range m { for route, fct := range routes { - log.Debugf("Registering %s, %s", method, route) + logrus.Debugf("Registering %s, %s", method, route) // NOTE: scope issue, make sure the variables are local and won't be changed localRoute := route localFct := fct @@ -1454,7 +1454,7 @@ func lookupGidByName(nameOrGid string) (int, error) { } gid, err := strconv.Atoi(nameOrGid) if err == nil { - log.Warnf("Could not find GID %d", gid) + logrus.Warnf("Could not find GID %d", gid) return gid, nil } return -1, fmt.Errorf("Group %s not found", nameOrGid) @@ -1504,7 +1504,7 @@ func changeGroup(addr string, nameOrGid string) error { return err } - log.Debugf("%s group found. gid: %d", nameOrGid, gid) + logrus.Debugf("%s group found. gid: %d", nameOrGid, gid) return os.Chown(addr, 0, gid) } @@ -1517,7 +1517,7 @@ func setSocketGroup(addr, group string) error { if group != "docker" { return err } - log.Debugf("Warning: could not chgrp %s to docker: %v", addr, err) + logrus.Debugf("Warning: could not chgrp %s to docker: %v", addr, err) } return nil @@ -1551,7 +1551,7 @@ func allocateDaemonPort(addr string) error { func setupTcpHttp(addr string, job *engine.Job) (*HttpServer, error) { if !job.GetenvBool("TlsVerify") { - log.Infof("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") + logrus.Infof("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("CorsHeaders"), job.Getenv("Version")) @@ -1601,7 +1601,7 @@ func ServeApi(job *engine.Job) error { return fmt.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) } go func() { - log.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1]) + logrus.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1]) srv, err := NewServer(protoAddrParts[0], protoAddrParts[1], job) if err != nil { chErrors <- err @@ -1609,7 +1609,7 @@ func ServeApi(job *engine.Job) error { } job.Eng.OnShutdown(func() { if err := srv.Close(); err != nil { - log.Error(err) + logrus.Error(err) } }) if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { diff --git a/builder/dispatchers.go b/builder/dispatchers.go index 4d21a75eb..acb4d50de 100644 --- a/builder/dispatchers.go +++ b/builder/dispatchers.go @@ -15,7 +15,7 @@ import ( "sort" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/nat" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/runconfig" @@ -264,7 +264,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string) defer func(cmd []string) { b.Config.Cmd = cmd }(cmd) - log.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd) + logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd) hit, err := b.probeCache() if err != nil { diff --git a/builder/evaluator.go b/builder/evaluator.go index 78c4c12f8..6237f2663 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -26,7 +26,7 @@ import ( "path/filepath" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/builder/command" "github.com/docker/docker/builder/parser" @@ -150,7 +150,7 @@ func (b *Builder) Run(context io.Reader) (string, error) { defer func() { if err := os.RemoveAll(b.contextPath); err != nil { - log.Debugf("[BUILDER] failed to remove temporary context: %s", err) + logrus.Debugf("[BUILDER] failed to remove temporary context: %s", err) } }() @@ -166,7 +166,7 @@ func (b *Builder) Run(context io.Reader) (string, error) { for i, n := range b.dockerfile.Children { select { case <-b.cancelled: - log.Debug("Builder: build cancelled!") + logrus.Debug("Builder: build cancelled!") fmt.Fprintf(b.OutStream, "Build cancelled") return "", fmt.Errorf("Build cancelled") default: diff --git a/builder/internals.go b/builder/internals.go index 1c90bf2d5..f4f6a5575 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -19,7 +19,7 @@ import ( "syscall" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/builder/parser" "github.com/docker/docker/daemon" imagepkg "github.com/docker/docker/image" @@ -522,13 +522,13 @@ func (b *Builder) probeCache() (bool, error) { return false, err } if cache == nil { - log.Debugf("[BUILDER] Cache miss") + logrus.Debugf("[BUILDER] Cache miss") b.cacheBusted = true return false, nil } fmt.Fprintf(b.OutStream, " ---> Using cache\n") - log.Debugf("[BUILDER] Use cached version") + logrus.Debugf("[BUILDER] Use cached version") b.image = cache.ID return true, nil } @@ -587,7 +587,7 @@ func (b *Builder) run(c *daemon.Container) error { go func() { select { case <-b.cancelled: - log.Debugln("Build cancelled, killing container:", c.ID) + logrus.Debugln("Build cancelled, killing container:", c.ID) c.Kill() case <-finished: } @@ -688,7 +688,7 @@ func (b *Builder) addContext(container *daemon.Container, orig, dest string, dec if err := chrootarchive.UntarPath(origPath, tarDest); err == nil { return nil } else if err != io.EOF { - log.Debugf("Couldn't untar %s to %s: %s", origPath, tarDest, err) + logrus.Debugf("Couldn't untar %s to %s: %s", origPath, tarDest, err) } } diff --git a/contrib/docker-device-tool/device_tool.go b/contrib/docker-device-tool/device_tool.go index ffc34a54e..9ad094a34 100644 --- a/contrib/docker-device-tool/device_tool.go +++ b/contrib/docker-device-tool/device_tool.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/graphdriver/devmapper" "github.com/docker/docker/pkg/devicemapper" ) @@ -63,7 +63,7 @@ func main() { if *flDebug { os.Setenv("DEBUG", "1") - log.SetLevel(log.DebugLevel) + logrus.SetLevel(logrus.DebugLevel) } if flag.NArg() < 1 { diff --git a/daemon/attach.go b/daemon/attach.go index 24d67a7c6..a479c040b 100644 --- a/daemon/attach.go +++ b/daemon/attach.go @@ -8,7 +8,7 @@ import ( "sync" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/jsonlog" "github.com/docker/docker/pkg/promise" @@ -39,25 +39,25 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) error { cLog, err := container.ReadLog("json") if err != nil && os.IsNotExist(err) { // Legacy logs - log.Debugf("Old logs format") + logrus.Debugf("Old logs format") if stdout { cLog, err := container.ReadLog("stdout") if err != nil { - log.Errorf("Error reading logs (stdout): %s", err) + logrus.Errorf("Error reading logs (stdout): %s", err) } else if _, err := io.Copy(job.Stdout, cLog); err != nil { - log.Errorf("Error streaming logs (stdout): %s", err) + logrus.Errorf("Error streaming logs (stdout): %s", err) } } if stderr { cLog, err := container.ReadLog("stderr") if err != nil { - log.Errorf("Error reading logs (stderr): %s", err) + logrus.Errorf("Error reading logs (stderr): %s", err) } else if _, err := io.Copy(job.Stderr, cLog); err != nil { - log.Errorf("Error streaming logs (stderr): %s", err) + logrus.Errorf("Error streaming logs (stderr): %s", err) } } } else if err != nil { - log.Errorf("Error reading logs (json): %s", err) + logrus.Errorf("Error reading logs (json): %s", err) } else { dec := json.NewDecoder(cLog) for { @@ -66,7 +66,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) error { if err := dec.Decode(l); err == io.EOF { break } else if err != nil { - log.Errorf("Error streaming logs: %s", err) + logrus.Errorf("Error streaming logs: %s", err) break } if l.Stream == "stdout" && stdout { @@ -90,7 +90,7 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) error { r, w := io.Pipe() go func() { defer w.Close() - defer log.Debugf("Closing buffered stdin pipe") + defer logrus.Debugf("Closing buffered stdin pipe") io.Copy(w, job.Stdin) }() cStdin = r @@ -140,7 +140,7 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t if stdin == nil || !openStdin { return } - log.Debugf("attach: stdin: begin") + logrus.Debugf("attach: stdin: begin") defer func() { if stdinOnce && !tty { cStdin.Close() @@ -154,7 +154,7 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t } } wg.Done() - log.Debugf("attach: stdin: end") + logrus.Debugf("attach: stdin: end") }() var err error @@ -168,7 +168,7 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t err = nil } if err != nil { - log.Errorf("attach: stdin: %s", err) + logrus.Errorf("attach: stdin: %s", err) errors <- err return } @@ -185,16 +185,16 @@ func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, t } streamPipe.Close() wg.Done() - log.Debugf("attach: %s: end", name) + logrus.Debugf("attach: %s: end", name) }() - log.Debugf("attach: %s: begin", name) + logrus.Debugf("attach: %s: begin", name) _, err := io.Copy(stream, streamPipe) if err == io.ErrClosedPipe { err = nil } if err != nil { - log.Errorf("attach: %s: %v", name, err) + logrus.Errorf("attach: %s: %v", name, err) errors <- err } } diff --git a/daemon/container.go b/daemon/container.go index 3f16ab269..ef8667369 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -19,7 +19,7 @@ import ( "github.com/docker/libcontainer/devices" "github.com/docker/libcontainer/label" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/logger" "github.com/docker/docker/daemon/logger/jsonfilelog" @@ -201,7 +201,7 @@ func (container *Container) WriteHostConfig() error { func (container *Container) LogEvent(action string) { d := container.daemon if err := d.eng.Job("log", action, container.ID, d.Repositories().ImageName(container.ImageID)).Run(); err != nil { - log.Errorf("Error logging event %s for %s: %s", action, container.ID, err) + logrus.Errorf("Error logging event %s for %s: %s", action, container.ID, err) } } @@ -659,7 +659,7 @@ func (container *Container) cleanup() { } if err := container.Unmount(); err != nil { - log.Errorf("%v: Failed to umount filesystem: %v", container.ID, err) + logrus.Errorf("%v: Failed to umount filesystem: %v", container.ID, err) } for _, eConfig := range container.execCommands.s { @@ -668,7 +668,7 @@ func (container *Container) cleanup() { } func (container *Container) KillSig(sig int) error { - log.Debugf("Sending %d to %s", sig, container.ID) + logrus.Debugf("Sending %d to %s", sig, container.ID) container.Lock() defer container.Unlock() @@ -699,7 +699,7 @@ func (container *Container) KillSig(sig int) error { func (container *Container) killPossiblyDeadProcess(sig int) error { err := container.KillSig(sig) if err == syscall.ESRCH { - log.Debugf("Cannot kill process (pid=%d) with signal %d: no such process.", container.GetPid(), sig) + logrus.Debugf("Cannot kill process (pid=%d) with signal %d: no such process.", container.GetPid(), sig) return nil } return err @@ -739,12 +739,12 @@ func (container *Container) Kill() error { if _, err := container.WaitStop(10 * time.Second); err != nil { // Ensure that we don't kill ourselves if pid := container.GetPid(); pid != 0 { - log.Infof("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", stringid.TruncateID(container.ID)) + logrus.Infof("Container %s failed to exit within 10 seconds of kill - trying direct SIGKILL", stringid.TruncateID(container.ID)) if err := syscall.Kill(pid, 9); err != nil { if err != syscall.ESRCH { return err } - log.Debugf("Cannot kill process (pid=%d) with signal 9: no such process.", pid) + logrus.Debugf("Cannot kill process (pid=%d) with signal 9: no such process.", pid) } } } @@ -760,7 +760,7 @@ func (container *Container) Stop(seconds int) error { // 1. Send a SIGTERM if err := container.killPossiblyDeadProcess(15); err != nil { - log.Infof("Failed to send SIGTERM to the process, force killing") + logrus.Infof("Failed to send SIGTERM to the process, force killing") if err := container.killPossiblyDeadProcess(9); err != nil { return err } @@ -768,7 +768,7 @@ func (container *Container) Stop(seconds int) error { // 2. Wait for the process to exit on its own if _, err := container.WaitStop(time.Duration(seconds) * time.Second); err != nil { - log.Infof("Container %v failed to exit within %d seconds of SIGTERM - using the force", container.ID, seconds) + logrus.Infof("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 { container.WaitStop(-1 * time.Second) @@ -904,7 +904,7 @@ func (container *Container) GetSize() (int64, int64) { ) if err := container.Mount(); err != nil { - log.Errorf("Failed to compute size of container rootfs %s: %s", container.ID, err) + logrus.Errorf("Failed to compute size of container rootfs %s: %s", container.ID, err) return sizeRw, sizeRootfs } defer container.Unmount() @@ -912,7 +912,7 @@ func (container *Container) GetSize() (int64, int64) { initID := fmt.Sprintf("%s-init", container.ID) sizeRw, err = driver.DiffSize(container.ID, initID) if err != nil { - log.Errorf("Driver %s couldn't return diff size of container %s: %s", driver, container.ID, err) + logrus.Errorf("Driver %s couldn't return diff size of container %s: %s", driver, container.ID, err) // FIXME: GetSize should return an error. Not changing it now in case // there is a side-effect. sizeRw = -1 @@ -1007,7 +1007,7 @@ func (container *Container) DisableLink(name string) { if link, exists := container.activeLinks[name]; exists { link.Disable() } else { - log.Debugf("Could not find active link for %s", name) + logrus.Debugf("Could not find active link for %s", name) } } } @@ -1017,7 +1017,7 @@ func (container *Container) setupContainerDns() error { // check if this is an existing container that needs DNS update: if container.UpdateDns { // read the host's resolv.conf, get the hash and call updateResolvConf - log.Debugf("Check container (%s) for update to resolv.conf - UpdateDns flag was set", container.ID) + logrus.Debugf("Check container (%s) for update to resolv.conf - UpdateDns flag was set", container.ID) latestResolvConf, latestHash := resolvconf.GetLastModified() // clean container resolv.conf re: localhost nameservers and IPv6 NS (if IPv6 disabled) @@ -1133,7 +1133,7 @@ func (container *Container) updateResolvConf(updatedResolvConf []byte, newResolv //if the user has not modified the resolv.conf of the container since we wrote it last //we will replace it with the updated resolv.conf from the host if string(hashBytes) == curHash { - log.Debugf("replacing %q with updated host resolv.conf", container.ResolvConfPath) + logrus.Debugf("replacing %q with updated host resolv.conf", container.ResolvConfPath) // for atomic updates to these files, use temporary files with os.Rename: dir := path.Dir(container.ResolvConfPath) @@ -1172,13 +1172,13 @@ func (container *Container) updateParentsHosts() error { c, err := container.daemon.Get(ref.ParentID) if err != nil { - log.Error(err) + logrus.Error(err) } if c != nil && !container.daemon.config.DisableNetwork && container.hostConfig.NetworkMode.IsPrivate() { - log.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, container.NetworkSettings.IPAddress) + logrus.Debugf("Update /etc/hosts of %s for alias %s with ip %s", c.ID, ref.Name, container.NetworkSettings.IPAddress) if err := etchosts.Update(c.HostsPath, container.NetworkSettings.IPAddress, ref.Name); err != nil { - log.Errorf("Failed to update /etc/hosts in parent container %s for alias %s: %v", c.ID, ref.Name, err) + logrus.Errorf("Failed to update /etc/hosts in parent container %s for alias %s: %v", c.ID, ref.Name, err) } } } @@ -1244,15 +1244,15 @@ func (container *Container) initializeNetworking() error { // Make sure the config is compatible with the current kernel func (container *Container) verifyDaemonSettings() { if container.Config.Memory > 0 && !container.daemon.sysInfo.MemoryLimit { - log.Warnf("Your kernel does not support memory limit capabilities. Limitation discarded.") + logrus.Warnf("Your kernel does not support memory limit capabilities. Limitation discarded.") container.Config.Memory = 0 } if container.Config.Memory > 0 && !container.daemon.sysInfo.SwapLimit { - log.Warnf("Your kernel does not support swap limit capabilities. Limitation discarded.") + logrus.Warnf("Your kernel does not support swap limit capabilities. Limitation discarded.") container.Config.MemorySwap = -1 } if container.daemon.sysInfo.IPv4ForwardingDisabled { - log.Warnf("IPv4 forwarding is disabled. Networking will not work") + logrus.Warnf("IPv4 forwarding is disabled. Networking will not work") } } diff --git a/daemon/daemon.go b/daemon/daemon.go index eebf0a45f..5e2f9c6b7 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -16,7 +16,7 @@ import ( "github.com/docker/libcontainer/label" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/daemon/execdriver" @@ -261,7 +261,7 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err // if so, then we need to restart monitor and init a new lock // If the container is supposed to be running, make sure of it if container.IsRunning() { - log.Debugf("killing old running container %s", container.ID) + logrus.Debugf("killing old running container %s", container.ID) existingPid := container.Pid container.SetStopped(&execdriver.ExitStatus{ExitCode: 0}) @@ -278,23 +278,23 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err var err error cmd.ProcessConfig.Process, err = os.FindProcess(existingPid) if err != nil { - log.Debugf("cannot find existing process for %d", existingPid) + logrus.Debugf("cannot find existing process for %d", existingPid) } daemon.execDriver.Terminate(cmd) } if err := container.Unmount(); err != nil { - log.Debugf("unmount error %s", err) + logrus.Debugf("unmount error %s", err) } if err := container.ToDisk(); err != nil { - log.Debugf("saving stopped state to disk %s", err) + logrus.Debugf("saving stopped state to disk %s", err) } info := daemon.execDriver.Info(container.ID) if !info.IsRunning() { - log.Debugf("Container %s was supposed to be running but is not.", container.ID) + logrus.Debugf("Container %s was supposed to be running but is not.", container.ID) - log.Debug("Marking as stopped") + logrus.Debug("Marking as stopped") container.SetStopped(&execdriver.ExitStatus{ExitCode: -127}) if err := container.ToDisk(); err != nil { @@ -314,7 +314,7 @@ func (daemon *Daemon) ensureName(container *Container) error { container.Name = name if err := container.ToDisk(); err != nil { - log.Debugf("Error saving container name %s", err) + logrus.Debugf("Error saving container name %s", err) } } return nil @@ -337,7 +337,7 @@ func (daemon *Daemon) restore() error { ) if !debug { - log.Info("Loading containers: start.") + logrus.Info("Loading containers: start.") } dir, err := ioutil.ReadDir(daemon.repository) if err != nil { @@ -347,21 +347,21 @@ func (daemon *Daemon) restore() error { for _, v := range dir { id := v.Name() container, err := daemon.load(id) - if !debug && log.GetLevel() == log.InfoLevel { + if !debug && logrus.GetLevel() == logrus.InfoLevel { fmt.Print(".") } if err != nil { - log.Errorf("Failed to load container %v: %v", id, err) + logrus.Errorf("Failed to load container %v: %v", id, err) continue } // Ignore the container if it does not support the current driver being used by the graph if (container.Driver == "" && currentDriver == "aufs") || container.Driver == currentDriver { - log.Debugf("Loaded container %v", container.ID) + logrus.Debugf("Loaded container %v", container.ID) containers[container.ID] = container } else { - log.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID) + logrus.Debugf("Cannot load container %s because it was created with another graph driver.", container.ID) } } @@ -369,7 +369,7 @@ func (daemon *Daemon) restore() error { if entities := daemon.containerGraph.List("/", -1); entities != nil { for _, p := range entities.Paths() { - if !debug && log.GetLevel() == log.InfoLevel { + if !debug && logrus.GetLevel() == logrus.InfoLevel { fmt.Print(".") } @@ -377,7 +377,7 @@ func (daemon *Daemon) restore() error { if container, ok := containers[e.ID()]; ok { if err := daemon.register(container, false); err != nil { - log.Debugf("Failed to register container %s: %s", container.ID, err) + logrus.Debugf("Failed to register container %s: %s", container.ID, err) } registeredContainers = append(registeredContainers, container) @@ -393,11 +393,11 @@ func (daemon *Daemon) restore() error { // Try to set the default name for a container if it exists prior to links container.Name, err = daemon.generateNewName(container.ID) if err != nil { - log.Debugf("Setting default id - %s", err) + logrus.Debugf("Setting default id - %s", err) } if err := daemon.register(container, false); err != nil { - log.Debugf("Failed to register container %s: %s", container.ID, err) + logrus.Debugf("Failed to register container %s: %s", container.ID, err) } registeredContainers = append(registeredContainers, container) @@ -406,25 +406,25 @@ func (daemon *Daemon) restore() error { // check the restart policy on the containers and restart any container with // the restart policy of "always" if daemon.config.AutoRestart { - log.Debug("Restarting containers...") + logrus.Debug("Restarting containers...") for _, container := range registeredContainers { if container.hostConfig.RestartPolicy.Name == "always" || (container.hostConfig.RestartPolicy.Name == "on-failure" && container.ExitCode != 0) { - log.Debugf("Starting container %s", container.ID) + logrus.Debugf("Starting container %s", container.ID) if err := container.Start(); err != nil { - log.Debugf("Failed to start container %s: %s", container.ID, err) + logrus.Debugf("Failed to start container %s: %s", container.ID, err) } } } } if !debug { - if log.GetLevel() == log.InfoLevel { + if logrus.GetLevel() == logrus.InfoLevel { fmt.Println() } - log.Info("Loading containers: done.") + logrus.Info("Loading containers: done.") } return nil @@ -451,7 +451,7 @@ func (daemon *Daemon) setupResolvconfWatcher() error { // without an actual change to the file updatedResolvConf, newResolvConfHash, err := resolvconf.GetIfChanged() if err != nil { - log.Debugf("Error retrieving updated host resolv.conf: %v", err) + logrus.Debugf("Error retrieving updated host resolv.conf: %v", err) } else if updatedResolvConf != nil { // because the new host resolv.conf might have localhost nameservers.. updatedResolvConf, modified := resolvconf.FilterResolvDns(updatedResolvConf, daemon.config.EnableIPv6) @@ -459,22 +459,22 @@ func (daemon *Daemon) setupResolvconfWatcher() error { // changes have occurred during localhost cleanup: generate an updated hash newHash, err := utils.HashData(bytes.NewReader(updatedResolvConf)) if err != nil { - log.Debugf("Error generating hash of new resolv.conf: %v", err) + logrus.Debugf("Error generating hash of new resolv.conf: %v", err) } else { newResolvConfHash = newHash } } - log.Debug("host network resolv.conf changed--walking container list for updates") + logrus.Debug("host network resolv.conf changed--walking container list for updates") contList := daemon.containers.List() for _, container := range contList { if err := container.updateResolvConf(updatedResolvConf, newResolvConfHash); err != nil { - log.Debugf("Error on resolv.conf update check for container ID: %s: %v", container.ID, err) + logrus.Debugf("Error on resolv.conf update check for container ID: %s: %v", container.ID, err) } } } } case err := <-watcher.Errors: - log.Debugf("host resolv.conf notify error: %v", err) + logrus.Debugf("host resolv.conf notify error: %v", err) } } }() @@ -830,7 +830,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) // register portallocator release on shutdown eng.OnShutdown(func() { if err := portallocator.ReleaseAll(); err != nil { - log.Errorf("portallocator.ReleaseAll(): %s", err) + logrus.Errorf("portallocator.ReleaseAll(): %s", err) } }) // Claim the pidfile first, to avoid any and all unexpected race conditions. @@ -892,11 +892,11 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) if err != nil { return nil, fmt.Errorf("error intializing graphdriver: %v", err) } - log.Debugf("Using graph driver %s", driver) + logrus.Debugf("Using graph driver %s", driver) // register cleanup for graph driver eng.OnShutdown(func() { if err := driver.Cleanup(); err != nil { - log.Errorf("Error during graph storage driver.Cleanup(): %v", err) + logrus.Errorf("Error during graph storage driver.Cleanup(): %v", err) } }) @@ -906,9 +906,9 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) if driver.String() == "btrfs" { return nil, fmt.Errorf("SELinux is not supported with the BTRFS graph driver") } - log.Debug("SELinux enabled successfully") + logrus.Debug("SELinux enabled successfully") } else { - log.Warn("Docker could not enable SELinux on the host system") + logrus.Warn("Docker could not enable SELinux on the host system") } } else { selinuxSetDisabled() @@ -925,7 +925,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) return nil, err } - log.Debug("Creating images graph") + logrus.Debug("Creating images graph") g, err := graph.NewGraph(path.Join(config.Root, "graph"), driver) if err != nil { return nil, err @@ -946,7 +946,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) return nil, err } - log.Debug("Creating repository list") + logrus.Debug("Creating repository list") repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, trustKey) if err != nil { return nil, fmt.Errorf("Couldn't create Tag store: %s", err) @@ -988,7 +988,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) // register graph close on shutdown eng.OnShutdown(func() { if err := graph.Close(); err != nil { - log.Errorf("Error during container graph.Close(): %v", err) + logrus.Errorf("Error during container graph.Close(): %v", err) } }) @@ -1042,7 +1042,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) eng.OnShutdown(func() { if err := daemon.shutdown(); err != nil { - log.Errorf("Error during daemon.shutdown(): %v", err) + logrus.Errorf("Error during daemon.shutdown(): %v", err) } }) @@ -1060,20 +1060,20 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) func (daemon *Daemon) shutdown() error { group := sync.WaitGroup{} - log.Debug("starting clean shutdown of all containers...") + logrus.Debug("starting clean shutdown of all containers...") for _, container := range daemon.List() { c := container if c.IsRunning() { - log.Debugf("stopping %s", c.ID) + logrus.Debugf("stopping %s", c.ID) group.Add(1) go func() { defer group.Done() if err := c.KillSig(15); err != nil { - log.Debugf("kill 15 error for %s - %s", c.ID, err) + logrus.Debugf("kill 15 error for %s - %s", c.ID, err) } c.WaitStop(-1 * time.Second) - log.Debugf("container stopped %s", c.ID) + logrus.Debugf("container stopped %s", c.ID) }() } } @@ -1255,11 +1255,11 @@ func checkKernel() error { // the circumstances of pre-3.8 crashes are clearer. // For details see http://github.com/docker/docker/issues/407 if k, err := kernel.GetKernelVersion(); err != nil { - log.Warnf("%s", err) + logrus.Warnf("%s", err) } else { if kernel.CompareKernelVersion(k, &kernel.KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 { if os.Getenv("DOCKER_NOWARN_KERNEL_VERSION") == "" { - log.Warnf("You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) + logrus.Warnf("You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) } } } diff --git a/daemon/daemon_aufs.go b/daemon/daemon_aufs.go index 7d4d3c32e..377e82979 100644 --- a/daemon/daemon_aufs.go +++ b/daemon/daemon_aufs.go @@ -3,7 +3,7 @@ package daemon import ( - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/daemon/graphdriver/aufs" "github.com/docker/docker/graph" @@ -13,7 +13,7 @@ import ( // 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 { - log.Debugf("Migrating existing containers") + logrus.Debugf("Migrating existing containers") if err := ad.Migrate(root, graph.SetupInitLayer); err != nil { return err } diff --git a/daemon/delete.go b/daemon/delete.go index 9f31d6d55..312718196 100644 --- a/daemon/delete.go +++ b/daemon/delete.go @@ -5,7 +5,7 @@ import ( "os" "path" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" ) @@ -77,7 +77,7 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) error { func (daemon *Daemon) DeleteVolumes(volumeIDs map[string]struct{}) { for id := range volumeIDs { if err := daemon.volumes.Delete(id); err != nil { - log.Infof("%s", err) + logrus.Infof("%s", err) continue } } @@ -103,7 +103,7 @@ func (daemon *Daemon) Rm(container *Container) error { daemon.containers.Delete(container.ID) container.derefVolumes() if _, err := daemon.containerGraph.Purge(container.ID); err != nil { - log.Debugf("Unable to remove container from link graph: %s", err) + logrus.Debugf("Unable to remove container from link graph: %s", err) } if err := daemon.driver.Remove(container.ID); err != nil { diff --git a/daemon/exec.go b/daemon/exec.go index c7d494bb0..c5d446176 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -7,7 +7,7 @@ import ( "strings" "sync" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/execdriver/lxc" "github.com/docker/docker/engine" @@ -188,7 +188,7 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) error { return err } - log.Debugf("starting exec command %s in container %s", execConfig.ID, execConfig.Container.ID) + logrus.Debugf("starting exec command %s in container %s", execConfig.ID, execConfig.Container.ID) container := execConfig.Container container.LogEvent("exec_start: " + execConfig.ProcessConfig.Entrypoint + " " + strings.Join(execConfig.ProcessConfig.Arguments, " ")) @@ -197,7 +197,7 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) error { r, w := io.Pipe() go func() { defer w.Close() - defer log.Debugf("Closing buffered stdin pipe") + defer logrus.Debugf("Closing buffered stdin pipe") io.Copy(w, job.Stdin) }() cStdin = r @@ -305,24 +305,24 @@ func (container *Container) monitorExec(execConfig *execConfig, callback execdri pipes := execdriver.NewPipes(execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdout, execConfig.StreamConfig.stderr, execConfig.OpenStdin) exitCode, err = container.daemon.Exec(container, execConfig, pipes, callback) if err != nil { - log.Errorf("Error running command in existing container %s: %s", container.ID, err) + logrus.Errorf("Error running command in existing container %s: %s", container.ID, err) } - log.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode) + logrus.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode) if execConfig.OpenStdin { if err := execConfig.StreamConfig.stdin.Close(); err != nil { - log.Errorf("Error closing stdin while running in %s: %s", container.ID, err) + logrus.Errorf("Error closing stdin while running in %s: %s", container.ID, err) } } if err := execConfig.StreamConfig.stdout.Clean(); err != nil { - log.Errorf("Error closing stdout while running in %s: %s", container.ID, err) + logrus.Errorf("Error closing stdout while running in %s: %s", container.ID, err) } if err := execConfig.StreamConfig.stderr.Clean(); err != nil { - log.Errorf("Error closing stderr while running in %s: %s", container.ID, err) + logrus.Errorf("Error closing stderr while running in %s: %s", container.ID, err) } if execConfig.ProcessConfig.Terminal != nil { if err := execConfig.ProcessConfig.Terminal.Close(); err != nil { - log.Errorf("Error closing terminal while running in container %s: %s", container.ID, err) + logrus.Errorf("Error closing terminal while running in container %s: %s", container.ID, err) } } diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 55c4ac4e1..97b34bb67 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -16,7 +16,7 @@ import ( "syscall" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" sysinfo "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/term" @@ -193,7 +193,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba "unshare", "-m", "--", "/bin/sh", "-c", shellString, } } - log.Debugf("lxc params %s", params) + logrus.Debugf("lxc params %s", params) var ( name = params[0] arg = params[1:] @@ -263,7 +263,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba c.ContainerPid = pid if startCallback != nil { - log.Debugf("Invoking startCallback") + logrus.Debugf("Invoking startCallback") startCallback(&c.ProcessConfig, pid) } @@ -274,9 +274,9 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba if err == nil { _, oomKill = <-oomKillNotification - log.Debugf("oomKill error %s waitErr %s", oomKill, waitErr) + logrus.Debugf("oomKill error %s waitErr %s", oomKill, waitErr) } else { - log.Warnf("Your kernel does not support OOM notifications: %s", err) + logrus.Warnf("Your kernel does not support OOM notifications: %s", err) } // check oom error @@ -351,11 +351,11 @@ func cgroupPaths(containerId string) (map[string]string, error) { if err != nil { return nil, err } - log.Debugf("subsystems: %s", subsystems) + logrus.Debugf("subsystems: %s", subsystems) paths := make(map[string]string) for _, subsystem := range subsystems { cgroupRoot, cgroupDir, err := findCgroupRootAndDir(subsystem) - log.Debugf("cgroup path %s %s", cgroupRoot, cgroupDir) + logrus.Debugf("cgroup path %s %s", cgroupRoot, cgroupDir) if err != nil { //unsupported subystem continue @@ -576,7 +576,7 @@ func (i *info) IsRunning() bool { output, err := i.driver.getInfo(i.ID) if err != nil { - log.Errorf("Error getting info for lxc container %s: %s (%s)", i.ID, err, output) + logrus.Errorf("Error getting info for lxc container %s: %s (%s)", i.ID, err, output) return false } if strings.Contains(string(output), "RUNNING") { diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index e4a8ed6b5..02313d465 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -6,7 +6,7 @@ import ( "strings" "text/template" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" nativeTemplate "github.com/docker/docker/daemon/execdriver/native/template" "github.com/docker/docker/utils" @@ -160,14 +160,14 @@ func escapeFstabSpaces(field string) string { func keepCapabilities(adds []string, drops []string) ([]string, error) { container := nativeTemplate.New() - log.Debugf("adds %s drops %s\n", adds, drops) + logrus.Debugf("adds %s drops %s\n", adds, drops) caps, err := execdriver.TweakCapabilities(container.Capabilities, adds, drops) if err != nil { return nil, err } var newCaps []string for _, cap := range caps { - log.Debugf("cap %s\n", cap) + logrus.Debugf("cap %s\n", cap) realCap := execdriver.GetCapability(cap) numCap := fmt.Sprintf("%d", realCap.Value) newCaps = append(newCaps, numCap) @@ -181,7 +181,7 @@ func dropList(drops []string) ([]string, error) { var newCaps []string for _, capName := range execdriver.GetAllCapabilities() { cap := execdriver.GetCapability(capName) - log.Debugf("drop cap %s\n", cap.Key) + logrus.Debugf("drop cap %s\n", cap.Key) numCap := fmt.Sprintf("%d", cap.Value) newCaps = append(newCaps, numCap) } @@ -192,7 +192,7 @@ func dropList(drops []string) ([]string, error) { func isDirectory(source string) string { f, err := os.Stat(source) - log.Debugf("dir: %s\n", source) + logrus.Debugf("dir: %s\n", source) if err != nil { if os.IsNotExist(err) { return "dir" diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 46097a8cc..030c3b546 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -15,7 +15,7 @@ import ( "syscall" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/reexec" sysinfo "github.com/docker/docker/pkg/system" @@ -159,7 +159,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba oomKillNotification, err := cont.NotifyOOM() if err != nil { oomKillNotification = nil - log.Warnf("Your kernel does not support OOM notifications: %s", err) + logrus.Warnf("Your kernel does not support OOM notifications: %s", err) } waitF := p.Wait if nss := cont.Config().Namespaces; nss.Contains(configs.NEWPID) { @@ -206,7 +206,7 @@ func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*o for _, pid := range processes { process, err := os.FindProcess(pid) if err != nil { - log.Errorf("Failed to kill process: %d", pid) + logrus.Errorf("Failed to kill process: %d", pid) continue } process.Kill() diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index bc4b5c081..5c8662488 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -30,7 +30,7 @@ import ( "sync" "syscall" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" @@ -216,7 +216,7 @@ func (a *Driver) Remove(id string) error { defer a.Unlock() if a.active[id] != 0 { - log.Errorf("Removing active id %s", id) + logrus.Errorf("Removing active id %s", id) } // Make sure the dir is umounted first @@ -405,7 +405,7 @@ func (a *Driver) Cleanup() error { for _, id := range ids { if err := a.unmount(id); err != nil { - log.Errorf("Unmounting %s: %s", stringid.TruncateID(id), err) + logrus.Errorf("Unmounting %s: %s", stringid.TruncateID(id), err) } } diff --git a/daemon/graphdriver/aufs/mount.go b/daemon/graphdriver/aufs/mount.go index a3a5a8659..0a3d9d16a 100644 --- a/daemon/graphdriver/aufs/mount.go +++ b/daemon/graphdriver/aufs/mount.go @@ -4,12 +4,12 @@ import ( "os/exec" "syscall" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) func Unmount(target string) error { if err := exec.Command("auplink", target, "flush").Run(); err != nil { - log.Errorf("Couldn't run auplink before unmount: %s", err) + logrus.Errorf("Couldn't run auplink before unmount: %s", err) } if err := syscall.Unmount(target, 0); err != nil { return err diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 98980cc88..4d35adabc 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -18,7 +18,7 @@ import ( "syscall" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/pkg/devicemapper" "github.com/docker/docker/pkg/parsers" @@ -205,7 +205,7 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) { if !os.IsNotExist(err) { return "", err } - log.Debugf("Creating loopback file %s for device-manage use", filename) + logrus.Debugf("Creating loopback file %s for device-manage use", filename) file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return "", err @@ -320,21 +320,21 @@ func (devices *DeviceSet) deviceFileWalkFunction(path string, finfo os.FileInfo) // Skip some of the meta files which are not device files. if strings.HasSuffix(finfo.Name(), ".migrated") { - log.Debugf("Skipping file %s", path) + logrus.Debugf("Skipping file %s", path) return nil } if strings.HasPrefix(finfo.Name(), ".") { - log.Debugf("Skipping file %s", path) + logrus.Debugf("Skipping file %s", path) return nil } if finfo.Name() == deviceSetMetaFile { - log.Debugf("Skipping file %s", path) + logrus.Debugf("Skipping file %s", path) return nil } - log.Debugf("Loading data for file %s", path) + logrus.Debugf("Loading data for file %s", path) hash := finfo.Name() if hash == "base" { @@ -347,7 +347,7 @@ func (devices *DeviceSet) deviceFileWalkFunction(path string, finfo os.FileInfo) } if dinfo.DeviceId > MaxDeviceId { - log.Errorf("Ignoring Invalid DeviceId=%d", dinfo.DeviceId) + logrus.Errorf("Ignoring Invalid DeviceId=%d", dinfo.DeviceId) return nil } @@ -355,17 +355,17 @@ func (devices *DeviceSet) deviceFileWalkFunction(path string, finfo os.FileInfo) devices.markDeviceIdUsed(dinfo.DeviceId) devices.Unlock() - log.Debugf("Added deviceId=%d to DeviceIdMap", dinfo.DeviceId) + logrus.Debugf("Added deviceId=%d to DeviceIdMap", dinfo.DeviceId) return nil } func (devices *DeviceSet) constructDeviceIdMap() error { - log.Debugf("[deviceset] constructDeviceIdMap()") - defer log.Debugf("[deviceset] constructDeviceIdMap() END") + logrus.Debugf("[deviceset] constructDeviceIdMap()") + defer logrus.Debugf("[deviceset] constructDeviceIdMap() END") var scan = func(path string, info os.FileInfo, err error) error { if err != nil { - log.Debugf("Can't walk the file %s", path) + logrus.Debugf("Can't walk the file %s", path) return nil } @@ -381,7 +381,7 @@ func (devices *DeviceSet) constructDeviceIdMap() error { } func (devices *DeviceSet) unregisterDevice(id int, hash string) error { - log.Debugf("unregisterDevice(%v, %v)", id, hash) + logrus.Debugf("unregisterDevice(%v, %v)", id, hash) info := &DevInfo{ Hash: hash, DeviceId: id, @@ -392,7 +392,7 @@ func (devices *DeviceSet) unregisterDevice(id int, hash string) error { devices.devicesLock.Unlock() if err := devices.removeMetadata(info); err != nil { - log.Debugf("Error removing metadata: %s", err) + logrus.Debugf("Error removing metadata: %s", err) return err } @@ -400,7 +400,7 @@ func (devices *DeviceSet) unregisterDevice(id int, hash string) error { } func (devices *DeviceSet) registerDevice(id int, hash string, size uint64, transactionId uint64) (*DevInfo, error) { - log.Debugf("registerDevice(%v, %v)", id, hash) + logrus.Debugf("registerDevice(%v, %v)", id, hash) info := &DevInfo{ Hash: hash, DeviceId: id, @@ -426,7 +426,7 @@ func (devices *DeviceSet) registerDevice(id int, hash string, size uint64, trans } func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error { - log.Debugf("activateDeviceIfNeeded(%v)", info.Hash) + logrus.Debugf("activateDeviceIfNeeded(%v)", info.Hash) if devinfo, _ := devicemapper.GetInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 { return nil @@ -542,7 +542,7 @@ func (devices *DeviceSet) createRegisterDevice(hash string) (*DevInfo, error) { } if err := devices.openTransaction(hash, deviceId); err != nil { - log.Debugf("Error opening transaction hash = %s deviceId = %d", hash, deviceId) + logrus.Debugf("Error opening transaction hash = %s deviceId = %d", hash, deviceId) devices.markDeviceIdFree(deviceId) return nil, err } @@ -554,7 +554,7 @@ func (devices *DeviceSet) createRegisterDevice(hash string) (*DevInfo, error) { // happen. Now we have a mechianism to find // a free device Id. So something is not right. // Give a warning and continue. - log.Errorf("Device Id %d exists in pool but it is supposed to be unused", deviceId) + logrus.Errorf("Device Id %d exists in pool but it is supposed to be unused", deviceId) deviceId, err = devices.getNextFreeDeviceId() if err != nil { return nil, err @@ -563,14 +563,14 @@ func (devices *DeviceSet) createRegisterDevice(hash string) (*DevInfo, error) { devices.refreshTransaction(deviceId) continue } - log.Debugf("Error creating device: %s", err) + logrus.Debugf("Error creating device: %s", err) devices.markDeviceIdFree(deviceId) return nil, err } break } - log.Debugf("Registering device (id %v) with FS size %v", deviceId, devices.baseFsSize) + logrus.Debugf("Registering device (id %v) with FS size %v", deviceId, devices.baseFsSize) info, err := devices.registerDevice(deviceId, hash, devices.baseFsSize, devices.OpenTransactionId) if err != nil { _ = devicemapper.DeleteDevice(devices.getPoolDevName(), deviceId) @@ -594,7 +594,7 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *DevInf } if err := devices.openTransaction(hash, deviceId); err != nil { - log.Debugf("Error opening transaction hash = %s deviceId = %d", hash, deviceId) + logrus.Debugf("Error opening transaction hash = %s deviceId = %d", hash, deviceId) devices.markDeviceIdFree(deviceId) return err } @@ -606,7 +606,7 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *DevInf // happen. Now we have a mechianism to find // a free device Id. So something is not right. // Give a warning and continue. - log.Errorf("Device Id %d exists in pool but it is supposed to be unused", deviceId) + logrus.Errorf("Device Id %d exists in pool but it is supposed to be unused", deviceId) deviceId, err = devices.getNextFreeDeviceId() if err != nil { return err @@ -615,7 +615,7 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *DevInf devices.refreshTransaction(deviceId) continue } - log.Debugf("Error creating snap device: %s", err) + logrus.Debugf("Error creating snap device: %s", err) devices.markDeviceIdFree(deviceId) return err } @@ -625,7 +625,7 @@ func (devices *DeviceSet) createRegisterSnapDevice(hash string, baseInfo *DevInf if _, err := devices.registerDevice(deviceId, hash, baseInfo.Size, devices.OpenTransactionId); err != nil { devicemapper.DeleteDevice(devices.getPoolDevName(), deviceId) devices.markDeviceIdFree(deviceId) - log.Debugf("Error registering device: %s", err) + logrus.Debugf("Error registering device: %s", err) return err } @@ -660,7 +660,7 @@ func (devices *DeviceSet) setupBaseImage() error { } if oldInfo != nil && !oldInfo.Initialized { - log.Debugf("Removing uninitialized base image") + logrus.Debugf("Removing uninitialized base image") if err := devices.DeleteDevice(""); err != nil { return err } @@ -681,7 +681,7 @@ func (devices *DeviceSet) setupBaseImage() error { } } - log.Debugf("Initializing base device-mapper thin volume") + logrus.Debugf("Initializing base device-mapper thin volume") // Create initial device info, err := devices.createRegisterDevice("") @@ -689,7 +689,7 @@ func (devices *DeviceSet) setupBaseImage() error { return err } - log.Debugf("Creating filesystem on base device-mapper thin volume") + logrus.Debugf("Creating filesystem on base device-mapper thin volume") if err = devices.activateDeviceIfNeeded(info); err != nil { return err @@ -730,7 +730,7 @@ func (devices *DeviceSet) DMLog(level int, file string, line int, dmError int, m } // FIXME(vbatts) push this back into ./pkg/devicemapper/ - log.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) + logrus.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) } func major(device uint64) uint64 { @@ -846,24 +846,24 @@ func (devices *DeviceSet) removeTransactionMetaData() error { } func (devices *DeviceSet) rollbackTransaction() error { - log.Debugf("Rolling back open transaction: TransactionId=%d hash=%s device_id=%d", devices.OpenTransactionId, devices.DeviceIdHash, devices.DeviceId) + logrus.Debugf("Rolling back open transaction: TransactionId=%d hash=%s device_id=%d", devices.OpenTransactionId, devices.DeviceIdHash, devices.DeviceId) // A device id might have already been deleted before transaction // closed. In that case this call will fail. Just leave a message // in case of failure. if err := devicemapper.DeleteDevice(devices.getPoolDevName(), devices.DeviceId); err != nil { - log.Errorf("Unable to delete device: %s", err) + logrus.Errorf("Unable to delete device: %s", err) } dinfo := &DevInfo{Hash: devices.DeviceIdHash} if err := devices.removeMetadata(dinfo); err != nil { - log.Errorf("Unable to remove metadata: %s", err) + logrus.Errorf("Unable to remove metadata: %s", err) } else { devices.markDeviceIdFree(devices.DeviceId) } if err := devices.removeTransactionMetaData(); err != nil { - log.Errorf("Unable to remove transaction meta file %s: %s", devices.transactionMetaFile(), err) + logrus.Errorf("Unable to remove transaction meta file %s: %s", devices.transactionMetaFile(), err) } return nil @@ -883,7 +883,7 @@ func (devices *DeviceSet) processPendingTransaction() error { // If open transaction Id is less than pool transaction Id, something // is wrong. Bail out. if devices.OpenTransactionId < devices.TransactionId { - log.Errorf("Open Transaction id %d is less than pool transaction id %d", devices.OpenTransactionId, devices.TransactionId) + logrus.Errorf("Open Transaction id %d is less than pool transaction id %d", devices.OpenTransactionId, devices.TransactionId) return nil } @@ -940,7 +940,7 @@ func (devices *DeviceSet) refreshTransaction(DeviceId int) error { func (devices *DeviceSet) closeTransaction() error { if err := devices.updatePoolTransactionId(); err != nil { - log.Debugf("Failed to close Transaction") + logrus.Debugf("Failed to close Transaction") return err } return nil @@ -963,9 +963,9 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // https://github.com/docker/docker/issues/4036 if supported := devicemapper.UdevSetSyncSupport(true); !supported { - log.Warnf("Udev sync is not supported. This will lead to unexpected behavior, data loss and errors") + logrus.Warnf("Udev sync is not supported. This will lead to unexpected behavior, data loss and errors") } - log.Debugf("devicemapper: udev sync support: %v", devicemapper.UdevSyncSupported()) + logrus.Debugf("devicemapper: udev sync support: %v", devicemapper.UdevSyncSupported()) if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) { return err @@ -985,13 +985,13 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // - The target of this device is at major and minor // - If is defined, use that file inside the device as a loopback image. Otherwise use the device itself. devices.devicePrefix = fmt.Sprintf("docker-%d:%d-%d", major(sysSt.Dev), minor(sysSt.Dev), sysSt.Ino) - log.Debugf("Generated prefix: %s", devices.devicePrefix) + logrus.Debugf("Generated prefix: %s", devices.devicePrefix) // Check for the existence of the thin-pool device - log.Debugf("Checking for existence of the pool '%s'", devices.getPoolName()) + logrus.Debugf("Checking for existence of the pool '%s'", devices.getPoolName()) info, err := devicemapper.GetInfo(devices.getPoolName()) if info == nil { - log.Debugf("Error device devicemapper.GetInfo: %s", err) + logrus.Debugf("Error device devicemapper.GetInfo: %s", err) return err } @@ -1007,7 +1007,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // If the pool doesn't exist, create it if info.Exists == 0 && devices.thinPoolDevice == "" { - log.Debugf("Pool doesn't exist. Creating it.") + logrus.Debugf("Pool doesn't exist. Creating it.") var ( dataFile *os.File @@ -1029,7 +1029,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { data, err := devices.ensureImage("data", devices.dataLoopbackSize) if err != nil { - log.Debugf("Error device ensureImage (data): %s", err) + logrus.Debugf("Error device ensureImage (data): %s", err) return err } @@ -1062,7 +1062,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { metadata, err := devices.ensureImage("metadata", devices.metaDataLoopbackSize) if err != nil { - log.Debugf("Error device ensureImage (metadata): %s", err) + logrus.Debugf("Error device ensureImage (metadata): %s", err) return err } @@ -1102,7 +1102,7 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // Setup the base image if doInit { if err := devices.setupBaseImage(); err != nil { - log.Debugf("Error device setupBaseImage: %s", err) + logrus.Debugf("Error device setupBaseImage: %s", err) return err } } @@ -1111,8 +1111,8 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { } func (devices *DeviceSet) AddDevice(hash, baseHash string) error { - log.Debugf("[deviceset] AddDevice(hash=%s basehash=%s)", hash, baseHash) - defer log.Debugf("[deviceset] AddDevice(hash=%s basehash=%s) END", hash, baseHash) + logrus.Debugf("[deviceset] AddDevice(hash=%s basehash=%s)", hash, baseHash) + defer logrus.Debugf("[deviceset] AddDevice(hash=%s basehash=%s) END", hash, baseHash) baseInfo, err := devices.lookupDevice(baseHash) if err != nil { @@ -1143,7 +1143,7 @@ func (devices *DeviceSet) deleteDevice(info *DevInfo) error { // manually if err := devices.activateDeviceIfNeeded(info); err == nil { if err := devicemapper.BlockDeviceDiscard(info.DevName()); err != nil { - log.Debugf("Error discarding block on device: %s (ignoring)", err) + logrus.Debugf("Error discarding block on device: %s (ignoring)", err) } } } @@ -1151,18 +1151,18 @@ func (devices *DeviceSet) deleteDevice(info *DevInfo) error { devinfo, _ := devicemapper.GetInfo(info.Name()) if devinfo != nil && devinfo.Exists != 0 { if err := devices.removeDeviceAndWait(info.Name()); err != nil { - log.Debugf("Error removing device: %s", err) + logrus.Debugf("Error removing device: %s", err) return err } } if err := devices.openTransaction(info.Hash, info.DeviceId); err != nil { - log.Debugf("Error opening transaction hash = %s deviceId = %d", "", info.DeviceId) + logrus.Debugf("Error opening transaction hash = %s deviceId = %d", "", info.DeviceId) return err } if err := devicemapper.DeleteDevice(devices.getPoolDevName(), info.DeviceId); err != nil { - log.Debugf("Error deleting device: %s", err) + logrus.Debugf("Error deleting device: %s", err) return err } @@ -1195,8 +1195,8 @@ func (devices *DeviceSet) DeleteDevice(hash string) error { } func (devices *DeviceSet) deactivatePool() error { - log.Debugf("[devmapper] deactivatePool()") - defer log.Debugf("[devmapper] deactivatePool END") + logrus.Debugf("[devmapper] deactivatePool()") + defer logrus.Debugf("[devmapper] deactivatePool END") devname := devices.getPoolDevName() devinfo, err := devicemapper.GetInfo(devname) @@ -1205,7 +1205,7 @@ func (devices *DeviceSet) deactivatePool() error { } if d, err := devicemapper.GetDeps(devname); err == nil { // Access to more Debug output - log.Debugf("[devmapper] devicemapper.GetDeps() %s: %#v", devname, d) + logrus.Debugf("[devmapper] devicemapper.GetDeps() %s: %#v", devname, d) } if devinfo.Exists != 0 { return devicemapper.RemoveDevice(devname) @@ -1215,13 +1215,13 @@ func (devices *DeviceSet) deactivatePool() error { } func (devices *DeviceSet) deactivateDevice(info *DevInfo) error { - log.Debugf("[devmapper] deactivateDevice(%s)", info.Hash) - defer log.Debugf("[devmapper] deactivateDevice END(%s)", info.Hash) + logrus.Debugf("[devmapper] deactivateDevice(%s)", info.Hash) + defer logrus.Debugf("[devmapper] deactivateDevice END(%s)", info.Hash) // Wait for the unmount to be effective, // by watching the value of Info.OpenCount for the device if err := devices.waitClose(info); err != nil { - log.Errorf("Error waiting for device %s to close: %s", info.Hash, err) + logrus.Errorf("Error waiting for device %s to close: %s", info.Hash, err) } devinfo, err := devicemapper.GetInfo(info.Name()) @@ -1271,8 +1271,8 @@ func (devices *DeviceSet) removeDeviceAndWait(devname string) error { // a) the device registered at - is removed, // or b) the 10 second timeout expires. func (devices *DeviceSet) waitRemove(devname string) error { - log.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, devname) - defer log.Debugf("[deviceset %s] waitRemove(%s) END", devices.devicePrefix, devname) + logrus.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, devname) + defer logrus.Debugf("[deviceset %s] waitRemove(%s) END", devices.devicePrefix, devname) i := 0 for ; i < 1000; i++ { devinfo, err := devicemapper.GetInfo(devname) @@ -1282,7 +1282,7 @@ func (devices *DeviceSet) waitRemove(devname string) error { return nil } if i%100 == 0 { - log.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists) + logrus.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists) } if devinfo.Exists == 0 { break @@ -1309,7 +1309,7 @@ func (devices *DeviceSet) waitClose(info *DevInfo) error { return err } if i%100 == 0 { - log.Debugf("Waiting for unmount of %s: opencount=%d", info.Hash, devinfo.OpenCount) + logrus.Debugf("Waiting for unmount of %s: opencount=%d", info.Hash, devinfo.OpenCount) } if devinfo.OpenCount == 0 { break @@ -1325,9 +1325,9 @@ func (devices *DeviceSet) waitClose(info *DevInfo) error { } func (devices *DeviceSet) Shutdown() error { - log.Debugf("[deviceset %s] Shutdown()", devices.devicePrefix) - log.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) - defer log.Debugf("[deviceset %s] Shutdown() END", devices.devicePrefix) + logrus.Debugf("[deviceset %s] Shutdown()", devices.devicePrefix) + logrus.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) + defer logrus.Debugf("[deviceset %s] Shutdown() END", devices.devicePrefix) var devs []*DevInfo @@ -1344,12 +1344,12 @@ func (devices *DeviceSet) Shutdown() error { // container. This means it'll go away from the global scope directly, // and the device will be released when that container dies. if err := syscall.Unmount(info.mountPath, syscall.MNT_DETACH); err != nil { - log.Debugf("Shutdown unmounting %s, error: %s", info.mountPath, err) + logrus.Debugf("Shutdown unmounting %s, error: %s", info.mountPath, err) } devices.Lock() if err := devices.deactivateDevice(info); err != nil { - log.Debugf("Shutdown deactivate %s , error: %s", info.Hash, err) + logrus.Debugf("Shutdown deactivate %s , error: %s", info.Hash, err) } devices.Unlock() } @@ -1361,7 +1361,7 @@ func (devices *DeviceSet) Shutdown() error { info.lock.Lock() devices.Lock() if err := devices.deactivateDevice(info); err != nil { - log.Debugf("Shutdown deactivate base , error: %s", err) + logrus.Debugf("Shutdown deactivate base , error: %s", err) } devices.Unlock() info.lock.Unlock() @@ -1370,7 +1370,7 @@ func (devices *DeviceSet) Shutdown() error { devices.Lock() if devices.thinPoolDevice == "" { if err := devices.deactivatePool(); err != nil { - log.Debugf("Shutdown deactivate pool , error: %s", err) + logrus.Debugf("Shutdown deactivate pool , error: %s", err) } } @@ -1437,8 +1437,8 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error { } func (devices *DeviceSet) UnmountDevice(hash string) error { - log.Debugf("[devmapper] UnmountDevice(hash=%s)", hash) - defer log.Debugf("[devmapper] UnmountDevice(hash=%s) END", hash) + logrus.Debugf("[devmapper] UnmountDevice(hash=%s)", hash) + defer logrus.Debugf("[devmapper] UnmountDevice(hash=%s) END", hash) info, err := devices.lookupDevice(hash) if err != nil { @@ -1460,11 +1460,11 @@ func (devices *DeviceSet) UnmountDevice(hash string) error { return nil } - log.Debugf("[devmapper] Unmount(%s)", info.mountPath) + logrus.Debugf("[devmapper] Unmount(%s)", info.mountPath) if err := syscall.Unmount(info.mountPath, syscall.MNT_DETACH); err != nil { return err } - log.Debugf("[devmapper] Unmount done") + logrus.Debugf("[devmapper] Unmount done") if err := devices.deactivateDevice(info); err != nil { return err @@ -1586,7 +1586,7 @@ func (devices *DeviceSet) getUnderlyingAvailableSpace(loopFile string) (uint64, buf := new(syscall.Statfs_t) err := syscall.Statfs(loopFile, buf) if err != nil { - log.Warnf("Couldn't stat loopfile filesystem %v: %v", loopFile, err) + logrus.Warnf("Couldn't stat loopfile filesystem %v: %v", loopFile, err) return 0, err } return buf.Bfree * uint64(buf.Bsize), nil @@ -1596,7 +1596,7 @@ func (devices *DeviceSet) isRealFile(loopFile string) (bool, error) { if loopFile != "" { fi, err := os.Stat(loopFile) if err != nil { - log.Warnf("Couldn't stat loopfile %v: %v", loopFile, err) + logrus.Warnf("Couldn't stat loopfile %v: %v", loopFile, err) return false, err } return fi.Mode().IsRegular(), nil diff --git a/daemon/graphdriver/devmapper/driver.go b/daemon/graphdriver/devmapper/driver.go index 6dd05ca37..fad0a0c55 100644 --- a/daemon/graphdriver/devmapper/driver.go +++ b/daemon/graphdriver/devmapper/driver.go @@ -8,7 +8,7 @@ import ( "os" "path" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/pkg/devicemapper" "github.com/docker/docker/pkg/mount" @@ -164,7 +164,7 @@ func (d *Driver) Get(id, mountLabel string) (string, error) { func (d *Driver) Put(id string) error { err := d.DeviceSet.UnmountDevice(id) if err != nil { - log.Errorf("Error unmounting device %s: %s", id, err) + logrus.Errorf("Error unmounting device %s: %s", id, err) } return err } diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 9e7f92a0e..01f1182d1 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -7,7 +7,7 @@ import ( "path" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/archive" ) @@ -184,6 +184,6 @@ func checkPriorDriver(name, root string) { } } if len(priorDrivers) > 0 { - log.Warnf("Graphdriver %s selected. Your graphdriver directory %s already contains data managed by other graphdrivers: %s", name, root, strings.Join(priorDrivers, ",")) + logrus.Warnf("Graphdriver %s selected. Your graphdriver directory %s already contains data managed by other graphdrivers: %s", name, root, strings.Join(priorDrivers, ",")) } } diff --git a/daemon/graphdriver/fsdiff.go b/daemon/graphdriver/fsdiff.go index ab1b08f62..e091e619b 100644 --- a/daemon/graphdriver/fsdiff.go +++ b/daemon/graphdriver/fsdiff.go @@ -5,7 +5,7 @@ package graphdriver import ( "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/pkg/ioutils" @@ -120,11 +120,11 @@ func (gdw *naiveDiffDriver) ApplyDiff(id, parent string, diff archive.ArchiveRea defer driver.Put(id) start := time.Now().UTC() - log.Debugf("Start untar layer") + logrus.Debugf("Start untar layer") if size, err = chrootarchive.ApplyLayer(layerFs, diff); err != nil { return } - log.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds()) + logrus.Debugf("Untar time: %vs", time.Now().UTC().Sub(start).Seconds()) return } diff --git a/daemon/graphdriver/overlay/overlay.go b/daemon/graphdriver/overlay/overlay.go index fa5e9a2bf..5b0d3b7f5 100644 --- a/daemon/graphdriver/overlay/overlay.go +++ b/daemon/graphdriver/overlay/overlay.go @@ -12,7 +12,7 @@ import ( "sync" "syscall" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" @@ -113,13 +113,13 @@ func Init(home string, options []string) (graphdriver.Driver, error) { // check if they are running over btrfs or aufs switch fsMagic { case graphdriver.FsMagicBtrfs: - log.Error("'overlay' is not supported over btrfs.") + logrus.Error("'overlay' is not supported over btrfs.") return nil, graphdriver.ErrIncompatibleFS case graphdriver.FsMagicAufs: - log.Error("'overlay' is not supported over aufs.") + logrus.Error("'overlay' is not supported over aufs.") return nil, graphdriver.ErrIncompatibleFS case graphdriver.FsMagicZfs: - log.Error("'overlay' is not supported over zfs.") + logrus.Error("'overlay' is not supported over zfs.") return nil, graphdriver.ErrIncompatibleFS } @@ -153,7 +153,7 @@ func supportsOverlay() error { return nil } } - log.Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.") + logrus.Error("'overlay' not found as a supported filesystem on this host. Please ensure kernel is new enough and has overlay support loaded.") return graphdriver.ErrNotSupported } @@ -317,7 +317,7 @@ func (d *Driver) Put(id string) error { mount := d.active[id] if mount == nil { - log.Debugf("Put on a non-mounted device %s", id) + logrus.Debugf("Put on a non-mounted device %s", id) return nil } @@ -330,7 +330,7 @@ func (d *Driver) Put(id string) error { if mount.mounted { err := syscall.Unmount(mount.path, 0) if err != nil { - log.Debugf("Failed to unmount %s overlay: %v", id, err) + logrus.Debugf("Failed to unmount %s overlay: %v", id, err) } return err } diff --git a/daemon/info.go b/daemon/info.go index 91ac5c6a6..8e343fb62 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -5,7 +5,7 @@ import ( "runtime" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/parsers/kernel" @@ -33,7 +33,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) error { operatingSystem = s } if inContainer, err := operatingsystem.IsContainerized(); err != nil { - log.Errorf("Could not determine if daemon is containerized: %v", err) + logrus.Errorf("Could not determine if daemon is containerized: %v", err) operatingSystem += " (error determining if containerized)" } else if inContainer { operatingSystem += " (containerized)" @@ -41,7 +41,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) error { meminfo, err := system.ReadMemInfo() if err != nil { - log.Errorf("Could not read system memory info: %v", err) + logrus.Errorf("Could not read system memory info: %v", err) } // if we still have the original dockerinit binary from before we copied it locally, let's return the path to that, since that's more intuitive (the copied path is trivial to derive by hand given VERSION) diff --git a/daemon/logs.go b/daemon/logs.go index 14e6aa794..c991fa197 100644 --- a/daemon/logs.go +++ b/daemon/logs.go @@ -9,7 +9,7 @@ import ( "strconv" "sync" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/jsonlog" "github.com/docker/docker/pkg/tailfile" @@ -50,31 +50,31 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error { cLog, err := container.ReadLog("json") if err != nil && os.IsNotExist(err) { // Legacy logs - log.Debugf("Old logs format") + logrus.Debugf("Old logs format") if stdout { cLog, err := container.ReadLog("stdout") if err != nil { - log.Errorf("Error reading logs (stdout): %s", err) + logrus.Errorf("Error reading logs (stdout): %s", err) } else if _, err := io.Copy(job.Stdout, cLog); err != nil { - log.Errorf("Error streaming logs (stdout): %s", err) + logrus.Errorf("Error streaming logs (stdout): %s", err) } } if stderr { cLog, err := container.ReadLog("stderr") if err != nil { - log.Errorf("Error reading logs (stderr): %s", err) + logrus.Errorf("Error reading logs (stderr): %s", err) } else if _, err := io.Copy(job.Stderr, cLog); err != nil { - log.Errorf("Error streaming logs (stderr): %s", err) + logrus.Errorf("Error streaming logs (stderr): %s", err) } } } else if err != nil { - log.Errorf("Error reading logs (json): %s", err) + logrus.Errorf("Error reading logs (json): %s", err) } else { if tail != "all" { var err error lines, err = strconv.Atoi(tail) if err != nil { - log.Errorf("Failed to parse tail %s, error: %v, show all logs", tail, err) + logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", tail, err) lines = -1 } } @@ -97,7 +97,7 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error { if err := dec.Decode(l); err == io.EOF { break } else if err != nil { - log.Errorf("Error streaming logs: %s", err) + logrus.Errorf("Error streaming logs: %s", err) break } logLine := l.Log @@ -143,7 +143,7 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error { for err := range errors { if err != nil { - log.Errorf("%s", err) + logrus.Errorf("%s", err) } } diff --git a/daemon/monitor.go b/daemon/monitor.go index abe7dea2b..293849dd3 100644 --- a/daemon/monitor.go +++ b/daemon/monitor.go @@ -6,7 +6,7 @@ import ( "sync" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" @@ -89,7 +89,7 @@ func (m *containerMonitor) Close() error { // because they share same runconfig and change image. Must be fixed // in builder/builder.go if err := m.container.toDisk(); err != nil { - log.Errorf("Error dumping container %s state to disk: %s", m.container.ID, err) + logrus.Errorf("Error dumping container %s state to disk: %s", m.container.ID, err) return err } @@ -145,7 +145,7 @@ func (m *containerMonitor) Start() error { return err } - log.Errorf("Error running container: %s", err) + logrus.Errorf("Error running container: %s", err) } // here container.Lock is already lost @@ -229,7 +229,7 @@ func (m *containerMonitor) shouldRestart(exitCode int) bool { case "on-failure": // the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count if max := m.restartPolicy.MaximumRetryCount; max != 0 && m.failureCount > max { - log.Debugf("stopping restart of container %s because maximum failure could of %d has been reached", + logrus.Debugf("stopping restart of container %s because maximum failure could of %d has been reached", stringid.TruncateID(m.container.ID), max) return false } @@ -263,7 +263,7 @@ func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid } if err := m.container.ToDisk(); err != nil { - log.Debugf("%s", err) + logrus.Debugf("%s", err) } } @@ -279,21 +279,21 @@ func (m *containerMonitor) resetContainer(lock bool) { if container.Config.OpenStdin { if err := container.stdin.Close(); err != nil { - log.Errorf("%s: Error close stdin: %s", container.ID, err) + logrus.Errorf("%s: Error close stdin: %s", container.ID, err) } } if err := container.stdout.Clean(); err != nil { - log.Errorf("%s: Error close stdout: %s", container.ID, err) + logrus.Errorf("%s: Error close stdout: %s", container.ID, err) } if err := container.stderr.Clean(); err != nil { - log.Errorf("%s: Error close stderr: %s", container.ID, err) + logrus.Errorf("%s: Error close stderr: %s", container.ID, err) } if container.command != nil && container.command.ProcessConfig.Terminal != nil { if err := container.command.ProcessConfig.Terminal.Close(); err != nil { - log.Errorf("%s: Error closing terminal: %s", container.ID, err) + logrus.Errorf("%s: Error closing terminal: %s", container.ID, err) } } @@ -311,7 +311,7 @@ func (m *containerMonitor) resetContainer(lock bool) { }() select { case <-time.After(1 * time.Second): - log.Warnf("Logger didn't exit in time: logs may be truncated") + logrus.Warnf("Logger didn't exit in time: logs may be truncated") case <-exit: } } diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index 61237ebe9..6f65423e2 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -10,7 +10,7 @@ import ( "strings" "sync" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/networkdriver" "github.com/docker/docker/daemon/networkdriver/ipallocator" "github.com/docker/docker/daemon/networkdriver/portmapper" @@ -132,9 +132,9 @@ func InitDriver(job *engine.Job) error { if fixedCIDRv6 != "" { // Setting route to global IPv6 subnet - log.Infof("Adding route to IPv6 network %q via device %q", fixedCIDRv6, bridgeIface) + logrus.Infof("Adding route to IPv6 network %q via device %q", fixedCIDRv6, bridgeIface) if err := netlink.AddRoute(fixedCIDRv6, "", "", bridgeIface); err != nil { - log.Fatalf("Could not add route to IPv6 network %q via device %q", fixedCIDRv6, bridgeIface) + logrus.Fatalf("Could not add route to IPv6 network %q via device %q", fixedCIDRv6, bridgeIface) } } } else { @@ -207,16 +207,16 @@ func InitDriver(job *engine.Job) error { if ipForward { // Enable IPv4 forwarding if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil { - log.Warnf("WARNING: unable to enable IPv4 forwarding: %s\n", err) + logrus.Warnf("WARNING: unable to enable IPv4 forwarding: %s\n", err) } if fixedCIDRv6 != "" { // Enable IPv6 forwarding if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/default/forwarding", []byte{'1', '\n'}, 0644); err != nil { - log.Warnf("WARNING: unable to enable IPv6 default forwarding: %s\n", err) + logrus.Warnf("WARNING: unable to enable IPv6 default forwarding: %s\n", err) } if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/all/forwarding", []byte{'1', '\n'}, 0644); err != nil { - log.Warnf("WARNING: unable to enable IPv6 all forwarding: %s\n", err) + logrus.Warnf("WARNING: unable to enable IPv6 all forwarding: %s\n", err) } } } @@ -244,7 +244,7 @@ func InitDriver(job *engine.Job) error { if err != nil { return err } - log.Debugf("Subnet: %v", subnet) + logrus.Debugf("Subnet: %v", subnet) if err := ipAllocator.RegisterSubnet(bridgeIPv4Network, subnet); err != nil { return err } @@ -255,7 +255,7 @@ func InitDriver(job *engine.Job) error { if err != nil { return err } - log.Debugf("Subnet: %v", subnet) + logrus.Debugf("Subnet: %v", subnet) if err := ipAllocator.RegisterSubnet(subnet, subnet); err != nil { return err } @@ -307,7 +307,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { iptables.Raw(append([]string{"-D", "FORWARD"}, acceptArgs...)...) if !iptables.Exists(iptables.Filter, "FORWARD", dropArgs...) { - log.Debugf("Disable inter-container communication") + logrus.Debugf("Disable inter-container communication") if output, err := iptables.Raw(append([]string{"-I", "FORWARD"}, dropArgs...)...); err != nil { return fmt.Errorf("Unable to prevent intercontainer communication: %s", err) } else if len(output) != 0 { @@ -318,7 +318,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { iptables.Raw(append([]string{"-D", "FORWARD"}, dropArgs...)...) if !iptables.Exists(iptables.Filter, "FORWARD", acceptArgs...) { - log.Debugf("Enable inter-container communication") + logrus.Debugf("Enable inter-container communication") if output, err := iptables.Raw(append([]string{"-I", "FORWARD"}, acceptArgs...)...); err != nil { return fmt.Errorf("Unable to allow intercontainer communication: %s", err) } else if len(output) != 0 { @@ -384,7 +384,7 @@ func configureBridge(bridgeIP string, bridgeIPv6 string, enableIPv6 bool) error ifaceAddr = addr break } else { - log.Debugf("%s %s", addr, err) + logrus.Debugf("%s %s", addr, err) } } } @@ -393,7 +393,7 @@ func configureBridge(bridgeIP string, bridgeIPv6 string, enableIPv6 bool) error if ifaceAddr == "" { return fmt.Errorf("Could not find a free IP address range for interface '%s'. Please configure its address manually and run 'docker -b %s'", bridgeIface, bridgeIface) } - log.Debugf("Creating bridge %s with network %s", bridgeIface, ifaceAddr) + logrus.Debugf("Creating bridge %s with network %s", bridgeIface, ifaceAddr) if err := createBridgeIface(bridgeIface); err != nil { // The bridge may already exist, therefore we can ignore an "exists" error @@ -457,7 +457,7 @@ func createBridgeIface(name string) error { // 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) - log.Debugf("setting bridge mac address = %v", setBridgeMacAddr) + logrus.Debugf("setting bridge mac address = %v", setBridgeMacAddr) return netlink.CreateBridge(name, setBridgeMacAddr) } @@ -533,10 +533,10 @@ func Allocate(job *engine.Job) error { globalIPv6, err = ipAllocator.RequestIP(globalIPv6Network, requestedIPv6) if err != nil { - log.Errorf("Allocator: RequestIP v6: %v", err) + logrus.Errorf("Allocator: RequestIP v6: %v", err) return err } - log.Infof("Allocated IPv6 %s", globalIPv6) + logrus.Infof("Allocated IPv6 %s", globalIPv6) } out := engine.Env{} @@ -588,16 +588,16 @@ func Release(job *engine.Job) error { for _, nat := range containerInterface.PortMappings { if err := portmapper.Unmap(nat); err != nil { - log.Infof("Unable to unmap port %s: %s", nat, err) + logrus.Infof("Unable to unmap port %s: %s", nat, err) } } if err := ipAllocator.ReleaseIP(bridgeIPv4Network, containerInterface.IP); err != nil { - log.Infof("Unable to release IPv4 %s", err) + logrus.Infof("Unable to release IPv4 %s", err) } if globalIPv6Network != nil { if err := ipAllocator.ReleaseIP(globalIPv6Network, containerInterface.IPv6); err != nil { - log.Infof("Unable to release IPv6 %s", err) + logrus.Infof("Unable to release IPv6 %s", err) } } return nil @@ -650,10 +650,10 @@ func AllocatePort(job *engine.Job) error { // There is no point in immediately retrying to map an explicitly // chosen port. if hostPort != 0 { - log.Warnf("Failed to allocate and map port %d: %s", hostPort, err) + logrus.Warnf("Failed to allocate and map port %d: %s", hostPort, err) break } - log.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1) + logrus.Warnf("Failed to allocate and map port: %s, retry: %d", err, i+1) } if err != nil { diff --git a/daemon/networkdriver/ipallocator/allocator.go b/daemon/networkdriver/ipallocator/allocator.go index 628500d0d..62935e175 100644 --- a/daemon/networkdriver/ipallocator/allocator.go +++ b/daemon/networkdriver/ipallocator/allocator.go @@ -6,7 +6,7 @@ import ( "net" "sync" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/networkdriver" ) @@ -157,7 +157,7 @@ func ipToBigInt(ip net.IP) *big.Int { return x.SetBytes(ip6) } - log.Errorf("ipToBigInt: Wrong IP length! %s", ip) + logrus.Errorf("ipToBigInt: Wrong IP length! %s", ip) return nil } diff --git a/daemon/networkdriver/portallocator/portallocator.go b/daemon/networkdriver/portallocator/portallocator.go index 01533419b..e2bb9ee56 100644 --- a/daemon/networkdriver/portallocator/portallocator.go +++ b/daemon/networkdriver/portallocator/portallocator.go @@ -8,7 +8,7 @@ import ( "os" "sync" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) const ( @@ -87,7 +87,7 @@ func init() { file, err := os.Open(portRangeKernelParam) if err != nil { - log.Warnf("port allocator - %s due to error: %v", portRangeFallback, err) + logrus.Warnf("port allocator - %s due to error: %v", portRangeFallback, err) return } var start, end int @@ -96,7 +96,7 @@ func init() { if err == nil { err = fmt.Errorf("unexpected count of parsed numbers (%d)", n) } - log.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err) + logrus.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err) return } beginPortRange = start diff --git a/daemon/networkdriver/portmapper/mapper.go b/daemon/networkdriver/portmapper/mapper.go index 74b329e2f..a01b60416 100644 --- a/daemon/networkdriver/portmapper/mapper.go +++ b/daemon/networkdriver/portmapper/mapper.go @@ -6,7 +6,7 @@ import ( "net" "sync" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/networkdriver/portallocator" "github.com/docker/docker/pkg/iptables" ) @@ -156,7 +156,7 @@ func (pm *PortMapper) Unmap(host net.Addr) error { containerIP, containerPort := getIPAndPort(data.container) hostIP, hostPort := getIPAndPort(data.host) if err := pm.forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { - log.Errorf("Error on iptables delete: %s", err) + logrus.Errorf("Error on iptables delete: %s", err) } switch a := host.(type) { diff --git a/daemon/stats_collector.go b/daemon/stats_collector.go index 779bd1a59..926dd256e 100644 --- a/daemon/stats_collector.go +++ b/daemon/stats_collector.go @@ -9,7 +9,7 @@ import ( "sync" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/pubsub" "github.com/docker/libcontainer/system" @@ -80,13 +80,13 @@ func (s *statsCollector) run() { for container, publisher := range s.publishers { systemUsage, err := s.getSystemCpuUsage() if err != nil { - log.Errorf("collecting system cpu usage for %s: %v", container.ID, err) + logrus.Errorf("collecting system cpu usage for %s: %v", container.ID, err) continue } stats, err := container.Stats() if err != nil { if err != execdriver.ErrNotRunning { - log.Errorf("collecting stats for %s: %v", container.ID, err) + logrus.Errorf("collecting stats for %s: %v", container.ID, err) } continue } diff --git a/daemon/volumes.go b/daemon/volumes.go index 126d74a38..a4645dab2 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -9,7 +9,7 @@ import ( "sort" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/pkg/symlink" @@ -133,7 +133,7 @@ func (container *Container) registerVolumes() { } v, err := container.daemon.volumes.FindOrCreateVolume(path, writable) if err != nil { - log.Debugf("error registering volume %s: %v", path, err) + logrus.Debugf("error registering volume %s: %v", path, err) continue } v.AddContainer(container.ID) @@ -144,7 +144,7 @@ func (container *Container) derefVolumes() { for path := range container.VolumePaths() { vol := container.daemon.volumes.Get(path) if vol == nil { - log.Debugf("Volume %s was not found and could not be dereferenced", path) + logrus.Debugf("Volume %s was not found and could not be dereferenced", path) continue } vol.RemoveContainer(container.ID) diff --git a/docker/daemon.go b/docker/daemon.go index b2a985b22..c4b43d915 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -9,7 +9,7 @@ import ( "path/filepath" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/builder" "github.com/docker/docker/builtins" @@ -46,7 +46,7 @@ func migrateKey() (err error) { if err == nil { err = os.Remove(oldPath) } else { - log.Warnf("Key migration failed, key file not removed at %s", oldPath) + logrus.Warnf("Key migration failed, key file not removed at %s", oldPath) } }() @@ -70,7 +70,7 @@ func migrateKey() (err error) { return fmt.Errorf("error copying key: %s", err) } - log.Infof("Migrated key from %s to %s", oldPath, newPath) + logrus.Infof("Migrated key from %s to %s", oldPath, newPath) } return nil @@ -85,18 +85,18 @@ func mainDaemon() { signal.Trap(eng.Shutdown) if err := migrateKey(); err != nil { - log.Fatal(err) + logrus.Fatal(err) } daemonCfg.TrustKeyPath = *flTrustKey // Load builtins if err := builtins.Register(eng); err != nil { - log.Fatal(err) + logrus.Fatal(err) } // load registry service if err := registry.NewService(registryCfg).Install(eng); err != nil { - log.Fatal(err) + logrus.Fatal(err) } // load the daemon in the background so we can immediately start @@ -110,7 +110,7 @@ func mainDaemon() { return } - log.Infof("docker daemon: %s %s; execdriver: %s; graphdriver: %s", + logrus.Infof("docker daemon: %s %s; execdriver: %s; graphdriver: %s", dockerversion.VERSION, dockerversion.GITCOMMIT, d.ExecutionDriver().Name(), @@ -155,7 +155,7 @@ func mainDaemon() { serveAPIWait := make(chan error) go func() { if err := job.Run(); err != nil { - log.Errorf("ServeAPI error: %v", err) + logrus.Errorf("ServeAPI error: %v", err) serveAPIWait <- err return } @@ -164,7 +164,7 @@ func mainDaemon() { // Wait for the daemon startup goroutine to finish // This makes sure we can actually cleanly shutdown the daemon - log.Debug("waiting for daemon to initialize") + logrus.Debug("waiting for daemon to initialize") errDaemon := <-daemonInitWait if errDaemon != nil { eng.Shutdown() @@ -176,9 +176,9 @@ func mainDaemon() { } // we must "fatal" exit here as the API server may be happy to // continue listening forever if the error had no impact to API - log.Fatal(outStr) + logrus.Fatal(outStr) } else { - log.Info("Daemon has completed initialization") + logrus.Info("Daemon has completed initialization") } // Daemon is fully initialized and handling API traffic @@ -188,7 +188,7 @@ func mainDaemon() { // exited the daemon process above) eng.Shutdown() if errAPI != nil { - log.Fatalf("Shutting down due to ServeAPI error: %v", errAPI) + logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI) } } diff --git a/docker/docker.go b/docker/docker.go index 347424432..c9b2c77b0 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -8,7 +8,7 @@ import ( "os" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/api/client" "github.com/docker/docker/autogen/dockerversion" @@ -44,20 +44,20 @@ func main() { } if *flLogLevel != "" { - lvl, err := log.ParseLevel(*flLogLevel) + lvl, err := logrus.ParseLevel(*flLogLevel) if err != nil { - log.Fatalf("Unable to parse logging level: %s", *flLogLevel) + logrus.Fatalf("Unable to parse logging level: %s", *flLogLevel) } setLogLevel(lvl) } else { - setLogLevel(log.InfoLevel) + setLogLevel(logrus.InfoLevel) } // -D, --debug, -l/--log-level=debug processing // When/if -D is removed this block can be deleted if *flDebug { os.Setenv("DEBUG", "1") - setLogLevel(log.DebugLevel) + setLogLevel(logrus.DebugLevel) } if len(flHosts) == 0 { @@ -68,7 +68,7 @@ func main() { } defaultHost, err := api.ValidateHost(defaultHost) if err != nil { - log.Fatal(err) + logrus.Fatal(err) } flHosts = append(flHosts, defaultHost) } @@ -85,7 +85,7 @@ func main() { } if len(flHosts) > 1 { - log.Fatal("Please specify only one -H") + logrus.Fatal("Please specify only one -H") } protoAddrParts := strings.SplitN(flHosts[0], "://", 2) @@ -106,7 +106,7 @@ func main() { certPool := x509.NewCertPool() file, err := ioutil.ReadFile(*flCa) if err != nil { - log.Fatalf("Couldn't read ca cert %s: %s", *flCa, err) + logrus.Fatalf("Couldn't read ca cert %s: %s", *flCa, err) } certPool.AppendCertsFromPEM(file) tlsConfig.RootCAs = certPool @@ -121,7 +121,7 @@ func main() { *flTls = true cert, err := tls.LoadX509KeyPair(*flCert, *flKey) if err != nil { - log.Fatalf("Couldn't load X509 key pair: %q. Make sure the key is encrypted", err) + logrus.Fatalf("Couldn't load X509 key pair: %q. Make sure the key is encrypted", err) } tlsConfig.Certificates = []tls.Certificate{cert} } @@ -138,11 +138,11 @@ func main() { if err := cli.Cmd(flag.Args()...); err != nil { if sterr, ok := err.(*utils.StatusError); ok { if sterr.Status != "" { - log.Println(sterr.Status) + logrus.Println(sterr.Status) } os.Exit(sterr.StatusCode) } - log.Fatal(err) + logrus.Fatal(err) } } diff --git a/docker/log.go b/docker/log.go index 0dd9a70ee..7b43b56f5 100644 --- a/docker/log.go +++ b/docker/log.go @@ -1,14 +1,14 @@ package main import ( - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "io" ) -func setLogLevel(lvl log.Level) { - log.SetLevel(lvl) +func setLogLevel(lvl logrus.Level) { + logrus.SetLevel(lvl) } func initLogging(stderr io.Writer) { - log.SetOutput(stderr) + logrus.SetOutput(stderr) } diff --git a/engine/job.go b/engine/job.go index 52c655bae..189061bf5 100644 --- a/engine/job.go +++ b/engine/job.go @@ -8,7 +8,7 @@ import ( "sync" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) // A job is the fundamental unit of work in the docker engine. @@ -67,10 +67,10 @@ func (job *Job) Run() error { } // Log beginning and end of the job if job.Eng.Logging { - log.Infof("+job %s", job.CallString()) + logrus.Infof("+job %s", job.CallString()) defer func() { // what if err is nil? - log.Infof("-job %s%s", job.CallString(), job.err) + logrus.Infof("-job %s%s", job.CallString(), job.err) }() } var errorMessage = bytes.NewBuffer(nil) diff --git a/graph/export.go b/graph/export.go index a4c9278bc..f689ba10e 100644 --- a/graph/export.go +++ b/graph/export.go @@ -8,7 +8,7 @@ import ( "os" "path" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/parsers" @@ -33,7 +33,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { rootRepoMap := map[string]Repository{} addKey := func(name string, tag string, id string) { - log.Debugf("add key [%s:%s]", name, tag) + logrus.Debugf("add key [%s:%s]", name, tag) if repo, ok := rootRepoMap[name]; !ok { rootRepoMap[name] = Repository{tag: id} } else { @@ -42,7 +42,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { } for _, name := range job.Args { name = registry.NormalizeLocalName(name) - log.Debugf("Serializing %s", name) + logrus.Debugf("Serializing %s", name) rootRepo := s.Repositories[name] if rootRepo != nil { // this is a base repo name, like 'busybox' @@ -78,7 +78,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { } } } - log.Debugf("End Serializing %s", name) + logrus.Debugf("End Serializing %s", name) } // write repositories, if there is something to write if len(rootRepoMap) > 0 { @@ -87,7 +87,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { return err } } else { - log.Debugf("There were no repositories to write") + logrus.Debugf("There were no repositories to write") } fs, err := archive.Tar(tempdir, archive.Uncompressed) @@ -99,7 +99,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { if _, err := io.Copy(job.Stdout, fs); err != nil { return err } - log.Debugf("End export job: %s", job.Name) + logrus.Debugf("End export job: %s", job.Name) return nil } diff --git a/graph/graph.go b/graph/graph.go index 902018e39..933269a43 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -12,7 +12,7 @@ import ( "syscall" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/image" @@ -68,7 +68,7 @@ func (graph *Graph) restore() error { } } graph.idIndex = truncindex.NewTruncIndex(ids) - log.Debugf("Restored %d elements", len(dir)) + logrus.Debugf("Restored %d elements", len(dir)) return nil } diff --git a/graph/import.go b/graph/import.go index 2b3e8bdd6..8b9918896 100644 --- a/graph/import.go +++ b/graph/import.go @@ -7,7 +7,7 @@ import ( "net/http" "net/url" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/progressreader" @@ -93,7 +93,7 @@ func (s *TagStore) CmdImport(job *engine.Job) error { logID = utils.ImageReference(logID, tag) } if err = job.Eng.Job("log", "import", logID, "").Run(); err != nil { - log.Errorf("Error logging event 'import' for %s: %s", logID, err) + logrus.Errorf("Error logging event 'import' for %s: %s", logID, err) } return nil } diff --git a/graph/load.go b/graph/load.go index 08c7bd877..a3a75bce4 100644 --- a/graph/load.go +++ b/graph/load.go @@ -8,7 +8,7 @@ import ( "os" "path" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" @@ -82,33 +82,33 @@ func (s *TagStore) CmdLoad(job *engine.Job) error { func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error { if err := eng.Job("image_get", address).Run(); err != nil { - log.Debugf("Loading %s", address) + logrus.Debugf("Loading %s", address) imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json")) if err != nil { - log.Debugf("Error reading json", err) + logrus.Debugf("Error reading json", err) return err } layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar")) if err != nil { - log.Debugf("Error reading embedded tar", err) + logrus.Debugf("Error reading embedded tar", err) return err } img, err := image.NewImgJSON(imageJson) if err != nil { - log.Debugf("Error unmarshalling json", err) + logrus.Debugf("Error unmarshalling json", err) return err } if err := utils.ValidateID(img.ID); err != nil { - log.Debugf("Error validating ID: %s", err) + logrus.Debugf("Error validating ID: %s", err) return err } // ensure no two downloads of the same layer happen at the same time if c, err := s.poolAdd("pull", "layer:"+img.ID); err != nil { if c != nil { - log.Debugf("Image (id: %s) load is already running, waiting: %v", img.ID, err) + logrus.Debugf("Image (id: %s) load is already running, waiting: %v", img.ID, err) <-c return nil } @@ -129,7 +129,7 @@ func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string return err } } - log.Debugf("Completed processing %s", address) + logrus.Debugf("Completed processing %s", address) return nil } diff --git a/graph/manifest.go b/graph/manifest.go index 3b1d82557..7e9281537 100644 --- a/graph/manifest.go +++ b/graph/manifest.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" "github.com/docker/docker/engine" "github.com/docker/docker/registry" @@ -89,7 +89,7 @@ func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte, dgst, return nil, false, fmt.Errorf("error running key check: %s", err) } result := engine.Tail(stdoutBuffer, 1) - log.Debugf("Key check result: %q", result) + logrus.Debugf("Key check result: %q", result) if result == "verified" { verified = true } diff --git a/graph/pull.go b/graph/pull.go index 0a6b2800c..2fe5d92e6 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -10,7 +10,7 @@ import ( "strings" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" "github.com/docker/docker/engine" "github.com/docker/docker/image" @@ -59,7 +59,7 @@ func (s *TagStore) CmdPull(job *engine.Job) error { } defer s.poolRemove("pull", utils.ImageReference(repoInfo.LocalName, tag)) - log.Debugf("pulling image from host %q with remote name %q", repoInfo.Index.Name, repoInfo.RemoteName) + logrus.Debugf("pulling image from host %q with remote name %q", repoInfo.Index.Name, repoInfo.RemoteName) endpoint, err := repoInfo.GetEndpoint() if err != nil { return err @@ -79,30 +79,30 @@ func (s *TagStore) CmdPull(job *engine.Job) error { if repoInfo.Official { j := job.Eng.Job("trust_update_base") if err = j.Run(); err != nil { - log.Errorf("error updating trust base graph: %s", err) + logrus.Errorf("error updating trust base graph: %s", err) } } - log.Debugf("pulling v2 repository with local name %q", repoInfo.LocalName) + logrus.Debugf("pulling v2 repository with local name %q", repoInfo.LocalName) if err := s.pullV2Repository(job.Eng, r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err == nil { if err = job.Eng.Job("log", "pull", logName, "").Run(); err != nil { - log.Errorf("Error logging event 'pull' for %s: %s", logName, err) + logrus.Errorf("Error logging event 'pull' for %s: %s", logName, err) } return nil } else if err != registry.ErrDoesNotExist && err != ErrV2RegistryUnavailable { - log.Errorf("Error from V2 registry: %s", err) + logrus.Errorf("Error from V2 registry: %s", err) } - log.Debug("image does not exist on v2 registry, falling back to v1") + logrus.Debug("image does not exist on v2 registry, falling back to v1") } - log.Debugf("pulling v1 repository with local name %q", repoInfo.LocalName) + logrus.Debugf("pulling v1 repository with local name %q", repoInfo.LocalName) if err = s.pullRepository(r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err != nil { return err } if err = job.Eng.Job("log", "pull", logName, "").Run(); err != nil { - log.Errorf("Error logging event 'pull' for %s: %s", logName, err) + logrus.Errorf("Error logging event 'pull' for %s: %s", logName, err) } return nil @@ -120,10 +120,10 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * return err } - log.Debugf("Retrieving the tag list") + logrus.Debugf("Retrieving the tag list") tagsList, err := r.GetRemoteTags(repoData.Endpoints, repoInfo.RemoteName, repoData.Tokens) if err != nil { - log.Errorf("unable to get remote tags: %s", err) + logrus.Errorf("unable to get remote tags: %s", err) return err } @@ -135,7 +135,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * } } - log.Debugf("Registering tags") + logrus.Debugf("Registering tags") // If no tag has been specified, pull them all if askedTag == "" { for tag, id := range tagsList { @@ -163,7 +163,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * } if img.Tag == "" { - log.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID) + logrus.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID) if parallel { errors <- nil } @@ -177,7 +177,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * <-c out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil)) } else { - log.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err) + logrus.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err) } if parallel { errors <- nil @@ -194,7 +194,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, repoInfo.CanonicalName, ep), nil)) if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil { // Don't report errors when pulling from mirrors. - log.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err) + logrus.Debugf("Error pulling image (%s) from %s, mirror: %s, %s", img.Tag, repoInfo.CanonicalName, ep, err) continue } layers_downloaded = layers_downloaded || is_downloaded @@ -281,7 +281,7 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint // ensure no two downloads of the same layer happen at the same time if c, err := s.poolAdd("pull", "layer:"+id); err != nil { - log.Debugf("Image (id: %s) pull is already running, skipping: %v", id, err) + logrus.Debugf("Image (id: %s) pull is already running, skipping: %v", id, err) <-c } defer s.poolRemove("pull", "layer:"+id) @@ -387,7 +387,7 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out endpoint, err := r.V2RegistryEndpoint(repoInfo.Index) if err != nil { if repoInfo.Index.Official { - log.Debugf("Unable to pull from V2 registry, falling back to v1: %s", err) + logrus.Debugf("Unable to pull from V2 registry, falling back to v1: %s", err) return ErrV2RegistryUnavailable } return fmt.Errorf("error getting registry endpoint: %s", err) @@ -398,7 +398,7 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out } var layersDownloaded bool if tag == "" { - log.Debugf("Pulling tag list from V2 registry for %s", repoInfo.CanonicalName) + logrus.Debugf("Pulling tag list from V2 registry for %s", repoInfo.CanonicalName) tags, err := r.GetV2RemoteTags(endpoint, repoInfo.RemoteName, auth) if err != nil { return err @@ -430,7 +430,7 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out } func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, parallel bool, auth *registry.RequestAuthorization) (bool, error) { - log.Debugf("Pulling tag from V2 registry: %q", tag) + logrus.Debugf("Pulling tag from V2 registry: %q", tag) manifestBytes, manifestDigest, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth) if err != nil { @@ -449,7 +449,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri } if verified { - log.Printf("Image manifest for %s has been verified", utils.ImageReference(repoInfo.CanonicalName, tag)) + logrus.Printf("Image manifest for %s has been verified", utils.ImageReference(repoInfo.CanonicalName, tag)) } out.Write(sf.FormatStatus(tag, "Pulling from %s", repoInfo.CanonicalName)) @@ -469,7 +469,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri // Check if exists if s.graph.Exists(img.ID) { - log.Debugf("Image already exists: %s", img.ID) + logrus.Debugf("Image already exists: %s", img.ID) continue } @@ -482,7 +482,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Pulling fs layer", nil)) downloadFunc := func(di *downloadInfo) error { - log.Debugf("pulling blob %q to V1 img %s", sumStr, img.ID) + logrus.Debugf("pulling blob %q to V1 img %s", sumStr, img.ID) if c, err := s.poolAdd("pull", "img:"+img.ID); err != nil { if c != nil { @@ -490,7 +490,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri <-c out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil)) } else { - log.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err) + logrus.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err) } } else { defer s.poolRemove("pull", "img:"+img.ID) @@ -525,13 +525,13 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Verifying Checksum", nil)) if !verifier.Verified() { - log.Infof("Image verification failed: checksum mismatch for %q", di.digest.String()) + logrus.Infof("Image verification failed: checksum mismatch for %q", di.digest.String()) verified = false } out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), "Download complete", nil)) - log.Debugf("Downloaded %s to tempfile %s", img.ID, tmpFile.Name()) + logrus.Debugf("Downloaded %s to tempfile %s", img.ID, tmpFile.Name()) di.tmpFile = tmpFile di.length = l di.downloaded = true diff --git a/graph/push.go b/graph/push.go index 4bd80f120..927c13c9d 100644 --- a/graph/push.go +++ b/graph/push.go @@ -12,7 +12,7 @@ import ( "strings" "sync" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" "github.com/docker/docker/engine" "github.com/docker/docker/image" @@ -75,14 +75,14 @@ func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string if len(imageList) == 0 { return nil, nil, fmt.Errorf("No images found for the requested repository / tag") } - log.Debugf("Image list: %v", imageList) - log.Debugf("Tags by image: %v", tagsByImage) + logrus.Debugf("Image list: %v", imageList) + logrus.Debugf("Tags by image: %v", tagsByImage) return imageList, tagsByImage, nil } func (s *TagStore) getImageTags(localRepo map[string]string, askedTag string) ([]string, error) { - log.Debugf("Checking %s against %#v", askedTag, localRepo) + logrus.Debugf("Checking %s against %#v", askedTag, localRepo) if len(askedTag) > 0 { if _, ok := localRepo[askedTag]; !ok || utils.DigestReference(askedTag) { return nil, fmt.Errorf("Tag does not exist: %s", askedTag) @@ -136,7 +136,7 @@ func lookupImageOnEndpoint(wg *sync.WaitGroup, r *registry.Session, out io.Write defer wg.Done() for image := range images { if err := r.LookupRemoteImage(image.id, image.endpoint, image.tokens); err != nil { - log.Errorf("Error in LookupRemoteImage: %s", err) + logrus.Errorf("Error in LookupRemoteImage: %s", err) imagesToPush <- image.id continue } @@ -205,7 +205,7 @@ func (s *TagStore) pushImageToEndpoint(endpoint string, out io.Writer, remoteNam func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, localRepo map[string]string, tag string, sf *streamformatter.StreamFormatter) error { - log.Debugf("Local repo: %s", localRepo) + logrus.Debugf("Local repo: %s", localRepo) out = utils.NewWriteFlusher(out) imgList, tags, err := s.getImageList(localRepo, tag) if err != nil { @@ -214,9 +214,9 @@ func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, out.Write(sf.FormatStatus("", "Sending image list")) imageIndex := s.createImageIndex(imgList, tags) - log.Debugf("Preparing to push %s with the following images and tags", localRepo) + logrus.Debugf("Preparing to push %s with the following images and tags", localRepo) for _, data := range imageIndex { - log.Debugf("Pushing ID: %s with Tag: %s", data.ID, data.Tag) + logrus.Debugf("Pushing ID: %s with Tag: %s", data.ID, data.Tag) } // Register all the images in a repository with the registry // If an image is not in this list it will not be associated with the repository @@ -267,7 +267,7 @@ func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep strin defer os.RemoveAll(layerData.Name()) // Send the layer - log.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size) + logrus.Debugf("rendered layer for %s of [%d] size", imgData.ID, layerData.Size) checksum, checksumPayload, err := r.PushImageLayerRegistry(imgData.ID, progressreader.New(progressreader.Config{ @@ -297,7 +297,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o endpoint, err := r.V2RegistryEndpoint(repoInfo.Index) if err != nil { if repoInfo.Index.Official { - log.Debugf("Unable to push to V2 registry, falling back to v1: %s", err) + logrus.Debugf("Unable to push to V2 registry, falling back to v1: %s", err) return ErrV2RegistryUnavailable } return fmt.Errorf("error getting registry endpoint: %s", err) @@ -317,7 +317,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o } for _, tag := range tags { - log.Debugf("Pushing repository: %s:%s", repoInfo.CanonicalName, tag) + logrus.Debugf("Pushing repository: %s:%s", repoInfo.CanonicalName, tag) layerId, exists := localRepo[tag] if !exists { @@ -358,7 +358,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o // Schema version 1 requires layer ordering from top to root for i, layer := range layers { - log.Debugf("Pushing layer: %s", layer.ID) + logrus.Debugf("Pushing layer: %s", layer.ID) if layer.Config != nil && metadata.Image != layer.ID { err = runconfig.Merge(&metadata, layer.Config) @@ -411,7 +411,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o return fmt.Errorf("invalid manifest: %s", err) } - log.Debugf("Pushing %s:%s to v2 repository", repoInfo.LocalName, tag) + logrus.Debugf("Pushing %s:%s to v2 repository", repoInfo.LocalName, tag) mBytes, err := json.MarshalIndent(m, "", " ") if err != nil { return err @@ -429,7 +429,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o if err != nil { return err } - log.Infof("Signed manifest for %s:%s using daemon's key: %s", repoInfo.LocalName, tag, s.trustKey.KeyID()) + logrus.Infof("Signed manifest for %s:%s using daemon's key: %s", repoInfo.LocalName, tag, s.trustKey.KeyID()) // push the manifest digest, err := r.PutV2ImageManifest(endpoint, repoInfo.RemoteName, tag, signedBody, mBytes, auth) @@ -473,7 +473,7 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint * dgst := digest.NewDigest("sha256", h) // Send the layer - log.Debugf("rendered layer for %s of [%d] size", img.ID, size) + logrus.Debugf("rendered layer for %s of [%d] size", img.ID, size) if err := r.PutV2ImageBlob(endpoint, imageName, dgst.Algorithm(), dgst.Hex(), progressreader.New(progressreader.Config{ diff --git a/graph/service.go b/graph/service.go index 03ae8f4c0..c6d6a0872 100644 --- a/graph/service.go +++ b/graph/service.go @@ -4,7 +4,7 @@ import ( "fmt" "io" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/image" ) @@ -174,7 +174,7 @@ func (s *TagStore) CmdTarLayer(job *engine.Job) error { if err != nil { return err } - log.Debugf("rendered layer for %s of [%d] size", image.ID, written) + logrus.Debugf("rendered layer for %s of [%d] size", image.ID, written) return nil } return fmt.Errorf("No such image: %s", name) diff --git a/integration/commands_test.go b/integration/commands_test.go index f748efd6d..97a927b8b 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -9,7 +9,7 @@ import ( "testing" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" "github.com/docker/docker/daemon" "github.com/docker/docker/pkg/stringid" @@ -337,7 +337,7 @@ func TestAttachDisconnect(t *testing.T) { go func() { // Start a process in daemon mode if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil { - log.Debugf("Error CmdRun: %s", err) + logrus.Debugf("Error CmdRun: %s", err) } }() diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 4c1632771..07d9de7c6 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -16,7 +16,7 @@ import ( "testing" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/engine" @@ -94,23 +94,23 @@ func init() { } if uid := syscall.Geteuid(); uid != 0 { - log.Fatalf("docker tests need to be run as root") + logrus.Fatalf("docker tests need to be run as root") } // Copy dockerinit into our current testing directory, if provided (so we can test a separate dockerinit binary) if dockerinit := os.Getenv("TEST_DOCKERINIT_PATH"); dockerinit != "" { src, err := os.Open(dockerinit) if err != nil { - log.Fatalf("Unable to open TEST_DOCKERINIT_PATH: %s", err) + logrus.Fatalf("Unable to open TEST_DOCKERINIT_PATH: %s", err) } defer src.Close() dst, err := os.OpenFile(filepath.Join(filepath.Dir(utils.SelfPath()), "dockerinit"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0555) if err != nil { - log.Fatalf("Unable to create dockerinit in test directory: %s", err) + logrus.Fatalf("Unable to create dockerinit in test directory: %s", err) } defer dst.Close() if _, err := io.Copy(dst, src); err != nil { - log.Fatalf("Unable to copy dockerinit to TEST_DOCKERINIT_PATH: %s", err) + logrus.Fatalf("Unable to copy dockerinit to TEST_DOCKERINIT_PATH: %s", err) } dst.Close() src.Close() @@ -137,14 +137,14 @@ func setupBaseImage() { job = eng.Job("pull", unitTestImageName) job.Stdout.Add(ioutils.NopWriteCloser(os.Stdout)) if err := job.Run(); err != nil { - log.Fatalf("Unable to pull the test image: %s", err) + logrus.Fatalf("Unable to pull the test image: %s", err) } } } func spawnGlobalDaemon() { if globalDaemon != nil { - log.Debugf("Global daemon already exists. Skipping.") + logrus.Debugf("Global daemon already exists. Skipping.") return } t := std_log.New(os.Stderr, "", 0) @@ -154,7 +154,7 @@ func spawnGlobalDaemon() { // Spawn a Daemon go func() { - log.Debugf("Spawning global daemon for integration tests") + logrus.Debugf("Spawning global daemon for integration tests") listenURL := &url.URL{ Scheme: testDaemonProto, Host: testDaemonAddr, @@ -162,7 +162,7 @@ func spawnGlobalDaemon() { job := eng.Job("serveapi", listenURL.String()) job.SetenvBool("Logging", true) if err := job.Run(); err != nil { - log.Fatalf("Unable to spawn the test daemon: %s", err) + logrus.Fatalf("Unable to spawn the test daemon: %s", err) } }() @@ -171,7 +171,7 @@ func spawnGlobalDaemon() { time.Sleep(time.Second) if err := eng.Job("acceptconnections").Run(); err != nil { - log.Fatalf("Unable to accept connections for test api: %s", err) + logrus.Fatalf("Unable to accept connections for test api: %s", err) } } @@ -204,7 +204,7 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { // Spawn a Daemon go func() { - log.Debugf("Spawning https daemon for integration tests") + logrus.Debugf("Spawning https daemon for integration tests") listenURL := &url.URL{ Scheme: testDaemonHttpsProto, Host: addr, @@ -217,7 +217,7 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { job.Setenv("TlsCert", cert) job.Setenv("TlsKey", key) if err := job.Run(); err != nil { - log.Fatalf("Unable to spawn the test daemon: %s", err) + logrus.Fatalf("Unable to spawn the test daemon: %s", err) } }() @@ -225,7 +225,7 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { time.Sleep(time.Second) if err := eng.Job("acceptconnections").Run(); err != nil { - log.Fatalf("Unable to accept connections for test api: %s", err) + logrus.Fatalf("Unable to accept connections for test api: %s", err) } return eng } @@ -235,14 +235,14 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { func GetTestImage(daemon *daemon.Daemon) *image.Image { imgs, err := daemon.Graph().Map() if err != nil { - log.Fatalf("Unable to get the test image: %s", err) + logrus.Fatalf("Unable to get the test image: %s", err) } for _, image := range imgs { if image.ID == unitTestImageID { return image } } - log.Fatalf("Test image %v not found in %s: %s", unitTestImageID, daemon.Graph().Root, imgs) + logrus.Fatalf("Test image %v not found in %s: %s", unitTestImageID, daemon.Graph().Root, imgs) return nil } @@ -707,9 +707,9 @@ func TestRandomContainerName(t *testing.T) { } if c, err := daemon.Get(container.Name); err != nil { - log.Fatalf("Could not lookup container %s by its name", container.Name) + logrus.Fatalf("Could not lookup container %s by its name", container.Name) } else if c.ID != containerID { - log.Fatalf("Looking up container name %s returned id %s instead of %s", container.Name, c.ID, containerID) + logrus.Fatalf("Looking up container name %s returned id %s instead of %s", container.Name, c.ID, containerID) } } diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index bfa6e1846..36425f70a 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -18,7 +18,7 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/pools" "github.com/docker/docker/pkg/promise" @@ -78,7 +78,7 @@ func DetectCompression(source []byte) Compression { Xz: {0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}, } { if len(source) < len(m) { - log.Debugf("Len too short") + logrus.Debugf("Len too short") continue } if bytes.Compare(m, source[:len(m)]) == 0 { @@ -331,7 +331,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L } case tar.TypeXGlobalHeader: - log.Debugf("PAX Global Extended Headers found and ignored") + logrus.Debugf("PAX Global Extended Headers found and ignored") return nil default: @@ -426,7 +426,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) for _, include := range options.IncludeFiles { filepath.Walk(filepath.Join(srcPath, include), func(filePath string, f os.FileInfo, err error) error { if err != nil { - log.Debugf("Tar: Can't stat file %s to tar: %s", srcPath, err) + logrus.Debugf("Tar: Can't stat file %s to tar: %s", srcPath, err) return nil } @@ -447,7 +447,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) if include != relFilePath { skip, err = fileutils.Matches(relFilePath, options.ExcludePatterns) if err != nil { - log.Debugf("Error matching %s", relFilePath, err) + logrus.Debugf("Error matching %s", relFilePath, err) return err } } @@ -474,7 +474,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) } if err := ta.addTarFile(filePath, relFilePath); err != nil { - log.Debugf("Can't add file %s to tar: %s", filePath, err) + logrus.Debugf("Can't add file %s to tar: %s", filePath, err) } return nil }) @@ -482,13 +482,13 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) // Make sure to check the error on Close. if err := ta.TarWriter.Close(); err != nil { - log.Debugf("Can't close tar writer: %s", err) + logrus.Debugf("Can't close tar writer: %s", err) } if err := compressWriter.Close(); err != nil { - log.Debugf("Can't close compress writer: %s", err) + logrus.Debugf("Can't close compress writer: %s", err) } if err := pipeWriter.Close(); err != nil { - log.Debugf("Can't close pipe writer: %s", err) + logrus.Debugf("Can't close pipe writer: %s", err) } }() @@ -606,7 +606,7 @@ func Untar(archive io.Reader, dest string, options *TarOptions) error { } func (archiver *Archiver) TarUntar(src, dst string) error { - log.Debugf("TarUntar(%s %s)", src, dst) + logrus.Debugf("TarUntar(%s %s)", src, dst) archive, err := TarWithOptions(src, &TarOptions{Compression: Uncompressed}) if err != nil { return err @@ -648,11 +648,11 @@ func (archiver *Archiver) CopyWithTar(src, dst string) error { return archiver.CopyFileWithTar(src, dst) } // Create dst, copy src's content into it - log.Debugf("Creating dest directory: %s", dst) + logrus.Debugf("Creating dest directory: %s", dst) if err := os.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) { return err } - log.Debugf("Calling TarUntar(%s, %s)", src, dst) + logrus.Debugf("Calling TarUntar(%s, %s)", src, dst) return archiver.TarUntar(src, dst) } @@ -665,7 +665,7 @@ func CopyWithTar(src, dst string) error { } func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) { - log.Debugf("CopyFileWithTar(%s, %s)", src, dst) + logrus.Debugf("CopyFileWithTar(%s, %s)", src, dst) srcSt, err := os.Stat(src) if err != nil { return err diff --git a/pkg/archive/changes.go b/pkg/archive/changes.go index 96aff36a3..06fad8eb4 100644 --- a/pkg/archive/changes.go +++ b/pkg/archive/changes.go @@ -13,7 +13,7 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/pools" "github.com/docker/docker/pkg/system" ) @@ -401,22 +401,22 @@ func ExportChanges(dir string, changes []Change) (Archive, error) { ChangeTime: timestamp, } if err := ta.TarWriter.WriteHeader(hdr); err != nil { - log.Debugf("Can't write whiteout header: %s", err) + logrus.Debugf("Can't write whiteout header: %s", err) } } else { path := filepath.Join(dir, change.Path) if err := ta.addTarFile(path, change.Path[1:]); err != nil { - log.Debugf("Can't add file %s to tar: %s", path, err) + logrus.Debugf("Can't add file %s to tar: %s", path, err) } } } // Make sure to check the error on Close. if err := ta.TarWriter.Close(); err != nil { - log.Debugf("Can't close layer: %s", err) + logrus.Debugf("Can't close layer: %s", err) } if err := writer.Close(); err != nil { - log.Debugf("failed close Changes writer: %s", err) + logrus.Debugf("failed close Changes writer: %s", err) } }() return reader, nil diff --git a/pkg/broadcastwriter/broadcastwriter.go b/pkg/broadcastwriter/broadcastwriter.go index 232cf3dfc..1d3c3c5f1 100644 --- a/pkg/broadcastwriter/broadcastwriter.go +++ b/pkg/broadcastwriter/broadcastwriter.go @@ -6,7 +6,7 @@ import ( "sync" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/jsonlog" ) @@ -61,7 +61,7 @@ func (w *BroadcastWriter) Write(p []byte) (n int, err error) { jsonLog := jsonlog.JSONLog{Log: line, Stream: stream, Created: created} err = jsonLog.MarshalJSONBuf(w.jsLogBuf) if err != nil { - log.Errorf("Error making JSON log line: %s", err) + logrus.Errorf("Error making JSON log line: %s", err) continue } w.jsLogBuf.WriteByte('\n') diff --git a/pkg/devicemapper/attach_loopback.go b/pkg/devicemapper/attach_loopback.go index d39cbc6cf..424a97468 100644 --- a/pkg/devicemapper/attach_loopback.go +++ b/pkg/devicemapper/attach_loopback.go @@ -7,7 +7,7 @@ import ( "os" "syscall" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) func stringToLoopName(src string) [LoNameSize]uint8 { @@ -39,20 +39,20 @@ func openNextAvailableLoopback(index int, sparseFile *os.File) (loopFile *os.Fil fi, err := os.Stat(target) if err != nil { if os.IsNotExist(err) { - log.Errorf("There are no more loopback devices available.") + logrus.Errorf("There are no more loopback devices available.") } return nil, ErrAttachLoopbackDevice } if fi.Mode()&os.ModeDevice != os.ModeDevice { - log.Errorf("Loopback device %s is not a block device.", target) + logrus.Errorf("Loopback device %s is not a block device.", target) continue } // OpenFile adds O_CLOEXEC loopFile, err = os.OpenFile(target, os.O_RDWR, 0644) if err != nil { - log.Errorf("Error opening loopback device: %s", err) + logrus.Errorf("Error opening loopback device: %s", err) return nil, ErrAttachLoopbackDevice } @@ -62,7 +62,7 @@ func openNextAvailableLoopback(index int, sparseFile *os.File) (loopFile *os.Fil // If the error is EBUSY, then try the next loopback if err != syscall.EBUSY { - log.Errorf("Cannot set up loopback device %s: %s", target, err) + logrus.Errorf("Cannot set up loopback device %s: %s", target, err) return nil, ErrAttachLoopbackDevice } @@ -75,7 +75,7 @@ func openNextAvailableLoopback(index int, sparseFile *os.File) (loopFile *os.Fil // This can't happen, but let's be sure if loopFile == nil { - log.Errorf("Unreachable code reached! Error attaching %s to a loopback device.", sparseFile.Name()) + logrus.Errorf("Unreachable code reached! Error attaching %s to a loopback device.", sparseFile.Name()) return nil, ErrAttachLoopbackDevice } @@ -91,13 +91,13 @@ func AttachLoopDevice(sparseName string) (loop *os.File, err error) { // loopback from index 0. startIndex, err := getNextFreeLoopbackIndex() if err != nil { - log.Debugf("Error retrieving the next available loopback: %s", err) + logrus.Debugf("Error retrieving the next available loopback: %s", err) } // OpenFile adds O_CLOEXEC sparseFile, err := os.OpenFile(sparseName, os.O_RDWR, 0644) if err != nil { - log.Errorf("Error opening sparse file %s: %s", sparseName, err) + logrus.Errorf("Error opening sparse file %s: %s", sparseName, err) return nil, ErrAttachLoopbackDevice } defer sparseFile.Close() @@ -115,11 +115,11 @@ func AttachLoopDevice(sparseName string) (loop *os.File, err error) { } if err := ioctlLoopSetStatus64(loopFile.Fd(), loopInfo); err != nil { - log.Errorf("Cannot set up loopback device info: %s", err) + logrus.Errorf("Cannot set up loopback device info: %s", err) // If the call failed, then free the loopback device if err := ioctlLoopClrFd(loopFile.Fd()); err != nil { - log.Errorf("Error while cleaning up the loopback device") + logrus.Errorf("Error while cleaning up the loopback device") } loopFile.Close() return nil, ErrAttachLoopbackDevice diff --git a/pkg/devicemapper/devmapper.go b/pkg/devicemapper/devmapper.go index 1d45d0588..ec9ff1c8f 100644 --- a/pkg/devicemapper/devmapper.go +++ b/pkg/devicemapper/devmapper.go @@ -9,7 +9,7 @@ import ( "runtime" "syscall" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) type DevmapperLogger interface { @@ -237,7 +237,7 @@ func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64, func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) { loopInfo, err := ioctlLoopGetStatus64(file.Fd()) if err != nil { - log.Errorf("Error get loopback backing file: %s", err) + logrus.Errorf("Error get loopback backing file: %s", err) return 0, 0, ErrGetLoopbackBackingFile } return loopInfo.loDevice, loopInfo.loInode, nil @@ -245,7 +245,7 @@ func getLoopbackBackingFile(file *os.File) (uint64, uint64, error) { func LoopbackSetCapacity(file *os.File) error { if err := ioctlLoopSetCapacity(file.Fd(), 0); err != nil { - log.Errorf("Error loopbackSetCapacity: %s", err) + logrus.Errorf("Error loopbackSetCapacity: %s", err) return ErrLoopbackSetCapacity } return nil @@ -285,7 +285,7 @@ func FindLoopDeviceFor(file *os.File) *os.File { func UdevWait(cookie uint) error { if res := DmUdevWait(cookie); res != 1 { - log.Debugf("Failed to wait on udev cookie %d", cookie) + logrus.Debugf("Failed to wait on udev cookie %d", cookie) return ErrUdevWait } return nil @@ -305,7 +305,7 @@ func LogInit(logger DevmapperLogger) { func SetDevDir(dir string) error { if res := DmSetDevDir(dir); res != 1 { - log.Debugf("Error dm_set_dev_dir") + logrus.Debugf("Error dm_set_dev_dir") return ErrSetDevDir } return nil @@ -348,8 +348,8 @@ func CookieSupported() bool { // Useful helper for cleanup func RemoveDevice(name string) error { - log.Debugf("[devmapper] RemoveDevice START(%s)", name) - defer log.Debugf("[devmapper] RemoveDevice END(%s)", name) + logrus.Debugf("[devmapper] RemoveDevice START(%s)", name) + defer logrus.Debugf("[devmapper] RemoveDevice END(%s)", name) task, err := TaskCreateNamed(DeviceRemove, name) if task == nil { return err @@ -375,7 +375,7 @@ func RemoveDevice(name string) error { func GetBlockDeviceSize(file *os.File) (uint64, error) { size, err := ioctlBlkGetSize64(file.Fd()) if err != nil { - log.Errorf("Error getblockdevicesize: %s", err) + logrus.Errorf("Error getblockdevicesize: %s", err) return 0, ErrGetBlockSize } return uint64(size), nil @@ -494,21 +494,21 @@ func GetDriverVersion() (string, error) { func GetStatus(name string) (uint64, uint64, string, string, error) { task, err := TaskCreateNamed(DeviceStatus, name) if task == nil { - log.Debugf("GetStatus: Error TaskCreateNamed: %s", err) + logrus.Debugf("GetStatus: Error TaskCreateNamed: %s", err) return 0, 0, "", "", err } if err := task.Run(); err != nil { - log.Debugf("GetStatus: Error Run: %s", err) + logrus.Debugf("GetStatus: Error Run: %s", err) return 0, 0, "", "", err } devinfo, err := task.GetInfo() if err != nil { - log.Debugf("GetStatus: Error GetInfo: %s", err) + logrus.Debugf("GetStatus: Error GetInfo: %s", err) return 0, 0, "", "", err } if devinfo.Exists == 0 { - log.Debugf("GetStatus: Non existing device %s", name) + logrus.Debugf("GetStatus: Non existing device %s", name) return 0, 0, "", "", fmt.Errorf("Non existing device %s", name) } @@ -567,7 +567,7 @@ func ResumeDevice(name string) error { } func CreateDevice(poolName string, deviceId int) error { - log.Debugf("[devmapper] CreateDevice(poolName=%v, deviceId=%v)", poolName, deviceId) + logrus.Debugf("[devmapper] CreateDevice(poolName=%v, deviceId=%v)", poolName, deviceId) task, err := TaskCreateNamed(DeviceTargetMsg, poolName) if task == nil { return err diff --git a/pkg/fileutils/fileutils.go b/pkg/fileutils/fileutils.go index 4e4a91b91..64442e40f 100644 --- a/pkg/fileutils/fileutils.go +++ b/pkg/fileutils/fileutils.go @@ -1,7 +1,7 @@ package fileutils import ( - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "path/filepath" ) @@ -10,15 +10,15 @@ func Matches(relFilePath string, patterns []string) (bool, error) { for _, exclude := range patterns { matched, err := filepath.Match(exclude, relFilePath) if err != nil { - log.Errorf("Error matching: %s (pattern: %s)", relFilePath, exclude) + logrus.Errorf("Error matching: %s (pattern: %s)", relFilePath, exclude) return false, err } if matched { if filepath.Clean(relFilePath) == "." { - log.Errorf("Can't exclude whole path, excluding pattern: %s", exclude) + logrus.Errorf("Can't exclude whole path, excluding pattern: %s", exclude) continue } - log.Debugf("Skipping excluded path: %s", relFilePath) + logrus.Debugf("Skipping excluded path: %s", relFilePath) return true, nil } } diff --git a/pkg/httputils/resumablerequestreader.go b/pkg/httputils/resumablerequestreader.go index 10edd43a9..f690d0e0e 100644 --- a/pkg/httputils/resumablerequestreader.go +++ b/pkg/httputils/resumablerequestreader.go @@ -6,7 +6,7 @@ import ( "net/http" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) type resumableRequestReader struct { @@ -72,7 +72,7 @@ func (r *resumableRequestReader) Read(p []byte) (n int, err error) { r.cleanUpResponse() } if err != nil && err != io.EOF { - log.Infof("encountered error during pull and clearing it before resume: %s", err) + logrus.Infof("encountered error during pull and clearing it before resume: %s", err) err = nil } return n, err diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index 3e083a43a..ec2055787 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -9,7 +9,7 @@ import ( "strconv" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) type Action string @@ -283,7 +283,7 @@ func Raw(args ...string) ([]byte, error) { args = append([]string{"--wait"}, args...) } - log.Debugf("%s, %v", iptablesPath, args) + logrus.Debugf("%s, %v", iptablesPath, args) output, err := exec.Command(iptablesPath, args...).CombinedOutput() if err != nil { diff --git a/pkg/jsonlog/jsonlog.go b/pkg/jsonlog/jsonlog.go index e2c2a2cab..261c64cdc 100644 --- a/pkg/jsonlog/jsonlog.go +++ b/pkg/jsonlog/jsonlog.go @@ -6,7 +6,7 @@ import ( "io" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) type JSONLog struct { @@ -39,7 +39,7 @@ func WriteLog(src io.Reader, dst io.Writer, format string) error { if err := dec.Decode(l); err == io.EOF { return nil } else if err != nil { - log.Printf("Error streaming logs: %s", err) + logrus.Printf("Error streaming logs: %s", err) return err } line, err := l.Format(format) diff --git a/pkg/proxy/tcp_proxy.go b/pkg/proxy/tcp_proxy.go index eacf1427a..9942e6d90 100644 --- a/pkg/proxy/tcp_proxy.go +++ b/pkg/proxy/tcp_proxy.go @@ -5,7 +5,7 @@ import ( "net" "syscall" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) type TCPProxy struct { @@ -31,7 +31,7 @@ func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) { func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) { backend, err := net.DialTCP("tcp", nil, proxy.backendAddr) if err != nil { - log.Printf("Can't forward traffic to backend tcp/%v: %s\n", proxy.backendAddr, err) + logrus.Printf("Can't forward traffic to backend tcp/%v: %s\n", proxy.backendAddr, err) client.Close() return } @@ -78,7 +78,7 @@ func (proxy *TCPProxy) Run() { for { client, err := proxy.listener.Accept() if err != nil { - log.Printf("Stopping proxy on tcp/%v for tcp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err) + logrus.Printf("Stopping proxy on tcp/%v for tcp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err) return } go proxy.clientLoop(client.(*net.TCPConn), quit) diff --git a/pkg/proxy/udp_proxy.go b/pkg/proxy/udp_proxy.go index a3fcf116e..2a073dfe8 100644 --- a/pkg/proxy/udp_proxy.go +++ b/pkg/proxy/udp_proxy.go @@ -8,7 +8,7 @@ import ( "syscall" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) const ( @@ -105,7 +105,7 @@ func (proxy *UDPProxy) Run() { // ECONNREFUSED like Read do (see comment in // UDPProxy.replyLoop) if !isClosedError(err) { - log.Printf("Stopping proxy on udp/%v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err) + logrus.Printf("Stopping proxy on udp/%v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err) } break } @@ -116,7 +116,7 @@ func (proxy *UDPProxy) Run() { if !hit { proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr) if err != nil { - log.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err) + logrus.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err) proxy.connTrackLock.Unlock() continue } @@ -127,7 +127,7 @@ func (proxy *UDPProxy) Run() { for i := 0; i != read; { written, err := proxyConn.Write(readBuf[i:read]) if err != nil { - log.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err) + logrus.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err) break } i += written diff --git a/pkg/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go index d6f0f7a95..d7d53e16d 100644 --- a/pkg/resolvconf/resolvconf.go +++ b/pkg/resolvconf/resolvconf.go @@ -8,7 +8,7 @@ import ( "strings" "sync" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/utils" ) @@ -99,10 +99,10 @@ func FilterResolvDns(resolvConf []byte, ipv6Enabled bool) ([]byte, bool) { // if the resulting resolvConf has no more nameservers defined, add appropriate // default DNS servers for IPv4 and (optionally) IPv6 if len(GetNameservers(cleanedResolvConf)) == 0 { - log.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers : %v", defaultIPv4Dns) + logrus.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers : %v", defaultIPv4Dns) dns := defaultIPv4Dns if ipv6Enabled { - log.Infof("IPv6 enabled; Adding default IPv6 external servers : %v", defaultIPv6Dns) + logrus.Infof("IPv6 enabled; Adding default IPv6 external servers : %v", defaultIPv6Dns) dns = append(dns, defaultIPv6Dns...) } cleanedResolvConf = append(cleanedResolvConf, []byte("\n"+strings.Join(dns, "\n"))...) diff --git a/pkg/signal/trap.go b/pkg/signal/trap.go index 78a709b30..7469dbcc2 100644 --- a/pkg/signal/trap.go +++ b/pkg/signal/trap.go @@ -6,7 +6,7 @@ import ( "sync/atomic" "syscall" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) // Trap sets up a simplified signal "trap", appropriate for common @@ -29,7 +29,7 @@ func Trap(cleanup func()) { interruptCount := uint32(0) for sig := range c { go func(sig os.Signal) { - log.Infof("Received signal '%v', starting shutdown of docker...", sig) + logrus.Infof("Received signal '%v', starting shutdown of docker...", sig) switch sig { case os.Interrupt, syscall.SIGTERM: // If the user really wants to interrupt, let him do so. @@ -43,7 +43,7 @@ func Trap(cleanup func()) { return } } else { - log.Infof("Force shutdown of docker, interrupting cleanup") + logrus.Infof("Force shutdown of docker, interrupting cleanup") } case syscall.SIGQUIT: } diff --git a/pkg/stdcopy/stdcopy.go b/pkg/stdcopy/stdcopy.go index a61779ce5..ccf1d9dba 100644 --- a/pkg/stdcopy/stdcopy.go +++ b/pkg/stdcopy/stdcopy.go @@ -5,7 +5,7 @@ import ( "errors" "io" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) const ( @@ -95,13 +95,13 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) nr += nr2 if er == io.EOF { if nr < StdWriterPrefixLen { - log.Debugf("Corrupted prefix: %v", buf[:nr]) + logrus.Debugf("Corrupted prefix: %v", buf[:nr]) return written, nil } break } if er != nil { - log.Debugf("Error reading header: %s", er) + logrus.Debugf("Error reading header: %s", er) return 0, er } } @@ -117,18 +117,18 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) // Write on stderr out = dsterr default: - log.Debugf("Error selecting output fd: (%d)", buf[StdWriterFdIndex]) + logrus.Debugf("Error selecting output fd: (%d)", buf[StdWriterFdIndex]) return 0, ErrInvalidStdHeader } // Retrieve the size of the frame frameSize = int(binary.BigEndian.Uint32(buf[StdWriterSizeIndex : StdWriterSizeIndex+4])) - log.Debugf("framesize: %d", frameSize) + logrus.Debugf("framesize: %d", frameSize) // Check if the buffer is big enough to read the frame. // Extend it if necessary. if frameSize+StdWriterPrefixLen > bufLen { - log.Debugf("Extending buffer cap by %d (was %d)", frameSize+StdWriterPrefixLen-bufLen+1, len(buf)) + logrus.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) } @@ -140,13 +140,13 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) nr += nr2 if er == io.EOF { if nr < frameSize+StdWriterPrefixLen { - log.Debugf("Corrupted frame: %v", buf[StdWriterPrefixLen:nr]) + logrus.Debugf("Corrupted frame: %v", buf[StdWriterPrefixLen:nr]) return written, nil } break } if er != nil { - log.Debugf("Error reading frame: %s", er) + logrus.Debugf("Error reading frame: %s", er) return 0, er } } @@ -154,12 +154,12 @@ func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) // Write the retrieved frame (without header) nw, ew = out.Write(buf[StdWriterPrefixLen : frameSize+StdWriterPrefixLen]) if ew != nil { - log.Debugf("Error writing frame: %s", ew) + logrus.Debugf("Error writing frame: %s", ew) return 0, ew } // If the frame has not been fully written: error if nw != frameSize { - log.Debugf("Error Short Write: (%d on %d)", nw, frameSize) + logrus.Debugf("Error Short Write: (%d on %d)", nw, frameSize) return 0, io.ErrShortWrite } written += int64(nw) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 1d540d2e7..506124e74 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -5,7 +5,7 @@ import ( "os" "path" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/libcontainer/cgroups" ) @@ -20,20 +20,20 @@ func New(quiet bool) *SysInfo { sysInfo := &SysInfo{} if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil { if !quiet { - log.Warnf("%s", err) + logrus.Warnf("%s", err) } } else { _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes")) _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) sysInfo.MemoryLimit = err1 == nil && err2 == nil if !sysInfo.MemoryLimit && !quiet { - log.Warnf("Your kernel does not support cgroup memory limit.") + logrus.Warnf("Your kernel does not support cgroup memory limit.") } _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) sysInfo.SwapLimit = err == nil if !sysInfo.SwapLimit && !quiet { - log.Warnf("Your kernel does not support cgroup swap limit.") + logrus.Warnf("Your kernel does not support cgroup swap limit.") } } diff --git a/registry/auth.go b/registry/auth.go index 4baf114c6..eaecc0f26 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -13,7 +13,7 @@ import ( "sync" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/utils" ) @@ -66,7 +66,7 @@ func (auth *RequestAuthorization) getToken() (string, error) { defer auth.tokenLock.Unlock() now := time.Now() if now.Before(auth.tokenExpiration) { - log.Debugf("Using cached token for %s", auth.authConfig.Username) + logrus.Debugf("Using cached token for %s", auth.authConfig.Username) return auth.tokenCache, nil } @@ -78,7 +78,7 @@ func (auth *RequestAuthorization) getToken() (string, error) { case "basic": // no token necessary case "bearer": - log.Debugf("Getting bearer token with %s for %s", challenge.Parameters, auth.authConfig.Username) + logrus.Debugf("Getting bearer token with %s for %s", challenge.Parameters, auth.authConfig.Username) params := map[string]string{} for k, v := range challenge.Parameters { params[k] = v @@ -93,7 +93,7 @@ func (auth *RequestAuthorization) getToken() (string, error) { return token, nil default: - log.Infof("Unsupported auth scheme: %q", challenge.Scheme) + logrus.Infof("Unsupported auth scheme: %q", challenge.Scheme) } } @@ -245,7 +245,7 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils. serverAddress = authConfig.ServerAddress ) - log.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint) + logrus.Debugf("attempting v1 login to registry endpoint %s", registryEndpoint) if serverAddress == "" { return "", fmt.Errorf("Server Error: Server Address not set.") @@ -349,7 +349,7 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils. // served by the v2 registry service provider. Whether this will be supported in the future // is to be determined. func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) { - log.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint) + logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint) var ( err error allErrors []error @@ -357,7 +357,7 @@ func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils. ) for _, challenge := range registryEndpoint.AuthChallenges { - log.Debugf("trying %q auth challenge with params %s", challenge.Scheme, challenge.Parameters) + logrus.Debugf("trying %q auth challenge with params %s", challenge.Scheme, challenge.Parameters) switch strings.ToLower(challenge.Scheme) { case "basic": @@ -373,7 +373,7 @@ func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils. return "Login Succeeded", nil } - log.Debugf("error trying auth challenge %q: %s", challenge.Scheme, err) + logrus.Debugf("error trying auth challenge %q: %s", challenge.Scheme, err) allErrors = append(allErrors, err) } diff --git a/registry/endpoint.go b/registry/endpoint.go index 59ae4dd54..b883d36d0 100644 --- a/registry/endpoint.go +++ b/registry/endpoint.go @@ -10,7 +10,7 @@ import ( "net/url" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/registry/v2" "github.com/docker/docker/utils" ) @@ -57,7 +57,7 @@ func NewEndpoint(index *IndexInfo) (*Endpoint, error) { } func validateEndpoint(endpoint *Endpoint) error { - log.Debugf("pinging registry endpoint %s", endpoint) + logrus.Debugf("pinging registry endpoint %s", endpoint) // Try HTTPS ping to registry endpoint.URL.Scheme = "https" @@ -69,7 +69,7 @@ func validateEndpoint(endpoint *Endpoint) error { } // If registry is insecure and HTTPS failed, fallback to HTTP. - log.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err) + logrus.Debugf("Error from registry %q marked as insecure: %v. Insecurely falling back to HTTP", endpoint, err) endpoint.URL.Scheme = "http" var err2 error @@ -163,7 +163,7 @@ func (e *Endpoint) Ping() (RegistryInfo, error) { } func (e *Endpoint) pingV1(factory *utils.HTTPRequestFactory) (RegistryInfo, error) { - log.Debugf("attempting v1 ping for registry endpoint %s", e) + logrus.Debugf("attempting v1 ping for registry endpoint %s", e) if e.String() == IndexServerAddress() { // Skip the check, we know this one is valid @@ -194,17 +194,17 @@ func (e *Endpoint) pingV1(factory *utils.HTTPRequestFactory) (RegistryInfo, erro Standalone: true, } if err := json.Unmarshal(jsonString, &info); err != nil { - log.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err) + logrus.Debugf("Error unmarshalling the _ping RegistryInfo: %s", err) // don't stop here. Just assume sane defaults } if hdr := resp.Header.Get("X-Docker-Registry-Version"); hdr != "" { - log.Debugf("Registry version header: '%s'", hdr) + logrus.Debugf("Registry version header: '%s'", hdr) info.Version = hdr } - log.Debugf("RegistryInfo.Version: %q", info.Version) + logrus.Debugf("RegistryInfo.Version: %q", info.Version) standalone := resp.Header.Get("X-Docker-Registry-Standalone") - log.Debugf("Registry standalone header: '%s'", standalone) + logrus.Debugf("Registry standalone header: '%s'", standalone) // Accepted values are "true" (case-insensitive) and "1". if strings.EqualFold(standalone, "true") || standalone == "1" { info.Standalone = true @@ -212,12 +212,12 @@ func (e *Endpoint) pingV1(factory *utils.HTTPRequestFactory) (RegistryInfo, erro // there is a header set, and it is not "true" or "1", so assume fails info.Standalone = false } - log.Debugf("RegistryInfo.Standalone: %t", info.Standalone) + logrus.Debugf("RegistryInfo.Standalone: %t", info.Standalone) return info, nil } func (e *Endpoint) pingV2(factory *utils.HTTPRequestFactory) (RegistryInfo, error) { - log.Debugf("attempting v2 ping for registry endpoint %s", e) + logrus.Debugf("attempting v2 ping for registry endpoint %s", e) req, err := factory.NewRequest("GET", e.Path(""), nil) if err != nil { diff --git a/registry/registry.go b/registry/registry.go index a8bb83318..163e2de37 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -13,7 +13,7 @@ import ( "strings" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/timeoutconn" ) @@ -100,7 +100,7 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur } hostDir := path.Join("/etc/docker/certs.d", req.URL.Host) - log.Debugf("hostDir: %s", hostDir) + logrus.Debugf("hostDir: %s", hostDir) fs, err := ioutil.ReadDir(hostDir) if err != nil && !os.IsNotExist(err) { return nil, nil, err @@ -111,7 +111,7 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur if pool == nil { pool = x509.NewCertPool() } - log.Debugf("crt: %s", hostDir+"/"+f.Name()) + logrus.Debugf("crt: %s", hostDir+"/"+f.Name()) data, err := ioutil.ReadFile(path.Join(hostDir, f.Name())) if err != nil { return nil, nil, err @@ -121,7 +121,7 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur if strings.HasSuffix(f.Name(), ".cert") { certName := f.Name() keyName := certName[:len(certName)-5] + ".key" - log.Debugf("cert: %s", hostDir+"/"+f.Name()) + logrus.Debugf("cert: %s", hostDir+"/"+f.Name()) if !hasFile(fs, keyName) { return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName) } @@ -134,7 +134,7 @@ func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secur if strings.HasSuffix(f.Name(), ".key") { keyName := f.Name() certName := keyName[:len(keyName)-4] + ".cert" - log.Debugf("key: %s", hostDir+"/"+f.Name()) + logrus.Debugf("key: %s", hostDir+"/"+f.Name()) if !hasFile(fs, certName) { return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName) } diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go index 57233d7c7..82818b41c 100644 --- a/registry/registry_mock_test.go +++ b/registry/registry_mock_test.go @@ -18,7 +18,7 @@ import ( "github.com/docker/docker/opts" "github.com/gorilla/mux" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) var ( @@ -134,7 +134,7 @@ func init() { func handlerAccessLog(handler http.Handler) http.Handler { logHandler := func(w http.ResponseWriter, r *http.Request) { - log.Debugf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL) + logrus.Debugf("%s \"%s %s\"", r.RemoteAddr, r.Method, r.URL) handler.ServeHTTP(w, r) } return http.HandlerFunc(logHandler) @@ -467,7 +467,7 @@ func TestPing(t *testing.T) { * WARNING: Don't push on the repos uncommented, it'll block the tests * func TestWait(t *testing.T) { - log.Println("Test HTTP server ready and waiting:", testHttpServer.URL) + logrus.Println("Test HTTP server ready and waiting:", testHttpServer.URL) c := make(chan int) <-c } diff --git a/registry/service.go b/registry/service.go index 5daacb2b1..f464faabc 100644 --- a/registry/service.go +++ b/registry/service.go @@ -3,7 +3,7 @@ package registry import ( "fmt" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" ) @@ -62,18 +62,18 @@ func (s *Service) Auth(job *engine.Job) error { } if endpoint, err = NewEndpoint(index); err != nil { - log.Errorf("unable to get new registry endpoint: %s", err) + logrus.Errorf("unable to get new registry endpoint: %s", err) return err } authConfig.ServerAddress = endpoint.String() if status, err = Login(authConfig, endpoint, HTTPRequestFactory(nil)); err != nil { - log.Errorf("unable to login against registry endpoint %s: %s", endpoint, err) + logrus.Errorf("unable to login against registry endpoint %s: %s", endpoint, err) return err } - log.Infof("successful registry login for endpoint %s: %s", endpoint, status) + logrus.Infof("successful registry login for endpoint %s: %s", endpoint, status) job.Printf("%s\n", status) return nil diff --git a/registry/session.go b/registry/session.go index bf04b586d..1d70eff9a 100644 --- a/registry/session.go +++ b/registry/session.go @@ -17,7 +17,7 @@ import ( "strings" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/utils" @@ -54,7 +54,7 @@ func NewSession(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, endpo return nil, err } if info.Standalone { - log.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", r.indexEndpoint.String()) + logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", r.indexEndpoint.String()) dec := utils.NewHTTPAuthDecorator(authConfig.Username, authConfig.Password) factory.AddDecorator(dec) } @@ -93,7 +93,7 @@ func (r *Session) GetRemoteHistory(imgID, registry string, token []string) ([]st return nil, fmt.Errorf("Error while reading the http response: %s", err) } - log.Debugf("Ancestry: %s", jsonString) + logrus.Debugf("Ancestry: %s", jsonString) history := new([]string) if err := json.Unmarshal(jsonString, history); err != nil { return nil, err @@ -169,7 +169,7 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, token []string, im statusCode = 0 res, client, err = r.doRequest(req) if err != nil { - log.Debugf("Error contacting registry: %s", err) + logrus.Debugf("Error contacting registry: %s", err) if res != nil { if res.Body != nil { res.Body.Close() @@ -193,10 +193,10 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, token []string, im } if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 { - log.Debugf("server supports resume") + logrus.Debugf("server supports resume") return httputils.ResumableRequestReaderWithInitialResponse(client, req, 5, imgSize, res), nil } - log.Debugf("server doesn't support resume") + logrus.Debugf("server doesn't support resume") return res.Body, nil } @@ -219,7 +219,7 @@ func (r *Session) GetRemoteTags(registries []string, repository string, token [] return nil, err } - log.Debugf("Got status code %d from %s", res.StatusCode, endpoint) + logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint) defer res.Body.Close() if res.StatusCode != 200 && res.StatusCode != 404 { @@ -259,7 +259,7 @@ func buildEndpointsList(headers []string, indexEp string) ([]string, error) { func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) { repositoryTarget := fmt.Sprintf("%srepositories/%s/images", r.indexEndpoint.VersionString(1), remote) - log.Debugf("[registry] Calling GET %s", repositoryTarget) + logrus.Debugf("[registry] Calling GET %s", repositoryTarget) req, err := r.reqFactory.NewRequest("GET", repositoryTarget, nil) if err != nil { @@ -285,7 +285,7 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) { } else if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - log.Debugf("Error reading response body: %s", err) + logrus.Debugf("Error reading response body: %s", err) } return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res) } @@ -326,7 +326,7 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) { func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string, token []string) error { - log.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum") + logrus.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum") req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil) if err != nil { @@ -363,7 +363,7 @@ func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string, t // Push a local image to the registry func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string, token []string) error { - log.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json") + logrus.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json") req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw)) if err != nil { @@ -398,7 +398,7 @@ func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regist func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string, jsonRaw []byte) (checksum string, checksumPayload string, err error) { - log.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer") + logrus.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer") tarsumLayer, err := tarsum.NewTarSum(layer, false, tarsum.Version0) if err != nil { @@ -486,8 +486,8 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate suffix = "images" } u := fmt.Sprintf("%srepositories/%s/%s", r.indexEndpoint.VersionString(1), remote, suffix) - log.Debugf("[registry] PUT %s", u) - log.Debugf("Image list pushed to index:\n%s", imgListJSON) + logrus.Debugf("[registry] PUT %s", u) + logrus.Debugf("Image list pushed to index:\n%s", imgListJSON) headers := map[string][]string{ "Content-type": {"application/json"}, "X-Docker-Token": {"true"}, @@ -507,7 +507,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate } res.Body.Close() u = res.Header.Get("Location") - log.Debugf("Redirected to %s", u) + logrus.Debugf("Redirected to %s", u) } defer res.Body.Close() @@ -520,13 +520,13 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate if res.StatusCode != 200 && res.StatusCode != 201 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - log.Debugf("Error reading response body: %s", err) + logrus.Debugf("Error reading response body: %s", err) } return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res) } if res.Header.Get("X-Docker-Token") != "" { tokens = res.Header["X-Docker-Token"] - log.Debugf("Auth token: %v", tokens) + logrus.Debugf("Auth token: %v", tokens) } else { return nil, fmt.Errorf("Index response didn't contain an access token") } @@ -544,7 +544,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate if res.StatusCode != 204 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - log.Debugf("Error reading response body: %s", err) + logrus.Debugf("Error reading response body: %s", err) } return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote, errBody), res) } @@ -578,7 +578,7 @@ func shouldRedirect(response *http.Response) bool { } func (r *Session) SearchRepositories(term string) (*SearchResults, error) { - log.Debugf("Index server: %s", r.indexEndpoint) + logrus.Debugf("Index server: %s", r.indexEndpoint) u := r.indexEndpoint.VersionString(1) + "search?q=" + url.QueryEscape(term) req, err := r.reqFactory.NewRequest("GET", u, nil) if err != nil { diff --git a/registry/session_v2.go b/registry/session_v2.go index 22f39317b..a01c8b9ab 100644 --- a/registry/session_v2.go +++ b/registry/session_v2.go @@ -9,7 +9,7 @@ import ( "net/http" "strconv" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" "github.com/docker/docker/registry/v2" "github.com/docker/docker/utils" @@ -57,7 +57,7 @@ func (r *Session) GetV2Authorization(ep *Endpoint, imageName string, readOnly bo scopes = append(scopes, "push") } - log.Debugf("Getting authorization for %s %s", imageName, scopes) + logrus.Debugf("Getting authorization for %s %s", imageName, scopes) return NewRequestAuthorization(r.GetAuthConfig(true), ep, "repository", imageName, scopes), nil } @@ -75,7 +75,7 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au } method := "GET" - log.Debugf("[registry] Calling %q %s", method, routeURL) + logrus.Debugf("[registry] Calling %q %s", method, routeURL) req, err := r.reqFactory.NewRequest(method, routeURL, nil) if err != nil { @@ -116,7 +116,7 @@ func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, } method := "HEAD" - log.Debugf("[registry] Calling %q %s", method, routeURL) + logrus.Debugf("[registry] Calling %q %s", method, routeURL) req, err := r.reqFactory.NewRequest(method, routeURL, nil) if err != nil { @@ -151,7 +151,7 @@ func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, b } method := "GET" - log.Debugf("[registry] Calling %q %s", method, routeURL) + logrus.Debugf("[registry] Calling %q %s", method, routeURL) req, err := r.reqFactory.NewRequest(method, routeURL, nil) if err != nil { return err @@ -182,7 +182,7 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum str } method := "GET" - log.Debugf("[registry] Calling %q %s", method, routeURL) + logrus.Debugf("[registry] Calling %q %s", method, routeURL) req, err := r.reqFactory.NewRequest(method, routeURL, nil) if err != nil { return nil, 0, err @@ -219,7 +219,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string } method := "PUT" - log.Debugf("[registry] Calling %q %s", method, location) + logrus.Debugf("[registry] Calling %q %s", method, location) req, err := r.reqFactory.NewRequest(method, location, ioutil.NopCloser(blobRdr)) if err != nil { return err @@ -244,7 +244,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string if err != nil { return err } - log.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) + logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s:%s", res.StatusCode, imageName, sumType, sumStr), res) } @@ -258,7 +258,7 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque return "", err } - log.Debugf("[registry] Calling %q %s", "POST", routeURL) + logrus.Debugf("[registry] Calling %q %s", "POST", routeURL) req, err := r.reqFactory.NewRequest("POST", routeURL, nil) if err != nil { return "", err @@ -285,7 +285,7 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque return "", err } - log.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) + logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: unexpected %d response status trying to initiate upload of %s", res.StatusCode, imageName), res) } @@ -304,7 +304,7 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, si } method := "PUT" - log.Debugf("[registry] Calling %q %s", method, routeURL) + logrus.Debugf("[registry] Calling %q %s", method, routeURL) req, err := r.reqFactory.NewRequest(method, routeURL, bytes.NewReader(signedManifest)) if err != nil { return "", err @@ -327,7 +327,7 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, si if err != nil { return "", err } - log.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) + logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res) } @@ -364,7 +364,7 @@ func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestA } method := "GET" - log.Debugf("[registry] Calling %q %s", method, routeURL) + logrus.Debugf("[registry] Calling %q %s", method, routeURL) req, err := r.reqFactory.NewRequest(method, routeURL, nil) if err != nil { diff --git a/runconfig/merge.go b/runconfig/merge.go index 9bbdc6ad2..68d3d6ee1 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -3,7 +3,7 @@ package runconfig import ( "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/nat" ) @@ -50,7 +50,7 @@ func Merge(userConf, imageConf *Config) error { } if len(imageConf.PortSpecs) > 0 { // FIXME: I think we can safely remove this. Leaving it for now for the sake of reverse-compat paranoia. - log.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", ")) + logrus.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", ")) if userConf.ExposedPorts == nil { userConf.ExposedPorts = make(nat.PortSet) } diff --git a/trust/service.go b/trust/service.go index 923537c9c..12b964566 100644 --- a/trust/service.go +++ b/trust/service.go @@ -4,7 +4,7 @@ import ( "fmt" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/libtrust" ) @@ -56,7 +56,7 @@ func (t *TrustStore) CmdCheckKey(job *engine.Job) error { return fmt.Errorf("Error verifying key to namespace: %s", namespace) } if !verified { - log.Debugf("Verification failed for %s using key %s", namespace, pk.KeyID()) + logrus.Debugf("Verification failed for %s using key %s", namespace, pk.KeyID()) job.Stdout.Write([]byte("not verified")) } else if t.expiration.Before(time.Now()) { job.Stdout.Write([]byte("expired")) diff --git a/trust/trusts.go b/trust/trusts.go index f5e317e9e..c4a2f4158 100644 --- a/trust/trusts.go +++ b/trust/trusts.go @@ -12,7 +12,7 @@ import ( "sync" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/libtrust/trustgraph" ) @@ -93,7 +93,7 @@ func (t *TrustStore) reload() error { } if len(statements) == 0 { if t.autofetch { - log.Debugf("No grants, fetching") + logrus.Debugf("No grants, fetching") t.fetcher = time.AfterFunc(t.fetchTime, t.fetch) } return nil @@ -106,7 +106,7 @@ func (t *TrustStore) reload() error { t.expiration = expiration t.graph = trustgraph.NewMemoryGraph(grants) - log.Debugf("Reloaded graph with %d grants expiring at %s", len(grants), expiration) + logrus.Debugf("Reloaded graph with %d grants expiring at %s", len(grants), expiration) if t.autofetch { nextFetch := expiration.Sub(time.Now()) @@ -161,28 +161,28 @@ func (t *TrustStore) fetch() { for bg, ep := range t.baseEndpoints { statement, err := t.fetchBaseGraph(ep) if err != nil { - log.Infof("Trust graph fetch failed: %s", err) + logrus.Infof("Trust graph fetch failed: %s", err) continue } b, err := statement.Bytes() if err != nil { - log.Infof("Bad trust graph statement: %s", err) + logrus.Infof("Bad trust graph statement: %s", err) continue } // TODO check if value differs err = ioutil.WriteFile(path.Join(t.path, bg+".json"), b, 0600) if err != nil { - log.Infof("Error writing trust graph statement: %s", err) + logrus.Infof("Error writing trust graph statement: %s", err) } fetchCount++ } - log.Debugf("Fetched %d base graphs at %s", fetchCount, time.Now()) + logrus.Debugf("Fetched %d base graphs at %s", fetchCount, time.Now()) if fetchCount > 0 { go func() { err := t.reload() if err != nil { - log.Infof("Reload of trust graph failed: %s", err) + logrus.Infof("Reload of trust graph failed: %s", err) } }() t.fetchTime = defaultFetchtime diff --git a/utils/http.go b/utils/http.go index 24eaea56b..01251d9ac 100644 --- a/utils/http.go +++ b/utils/http.go @@ -5,7 +5,7 @@ import ( "net/http" "strings" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" ) // VersionInfo is used to model entities which has a version. @@ -163,6 +163,6 @@ func (h *HTTPRequestFactory) NewRequest(method, urlStr string, body io.Reader, d return nil, err } } - log.Debugf("%v -- HEADERS: %v", req.URL, req.Header) + logrus.Debugf("%v -- HEADERS: %v", req.URL, req.Header) return req, err } diff --git a/utils/utils.go b/utils/utils.go index 4a765eb09..d0e76bf23 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -18,7 +18,7 @@ import ( "strings" "sync" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/fileutils" @@ -157,7 +157,7 @@ func DockerInitPath(localCopy string) string { func GetTotalUsedFds() int { if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil { - log.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err) + logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err) } else { return len(fds) } diff --git a/volumes/repository.go b/volumes/repository.go index ee555e401..08c584981 100644 --- a/volumes/repository.go +++ b/volumes/repository.go @@ -7,7 +7,7 @@ import ( "path/filepath" "sync" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/pkg/stringid" ) @@ -95,16 +95,16 @@ func (r *Repository) restore() error { } if err := vol.FromDisk(); err != nil { if !os.IsNotExist(err) { - log.Debugf("Error restoring volume: %v", err) + logrus.Debugf("Error restoring volume: %v", err) continue } if err := vol.initialize(); err != nil { - log.Debugf("%s", err) + logrus.Debugf("%s", err) continue } } if err := r.add(vol); err != nil { - log.Debugf("Error restoring volume: %v", err) + logrus.Debugf("Error restoring volume: %v", err) } } return nil From ebbceea8a79766b7624dd7970a79054ecf582b6d Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Thu, 26 Mar 2015 15:52:16 -0700 Subject: [PATCH 132/999] windows: monitorTtySize correctly by polling This change makes `monitorTtySize` work correctly on windows by polling into win32 API to get terminal size (because there's no SIGWINCH on windows) and send it to the engine over Remove API properly. Average getttysize syscall takes around 30-40 ms on an average windows machine as far as I can tell, therefore in a `for` loop, checking every 250ms if size has changed or not. I'm not sure if there's a better way to do it on windows, if so, somebody please send a link 'cause I could not find. Signed-off-by: Ahmet Alp Balkan --- api/client/utils.go | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/api/client/utils.go b/api/client/utils.go index 7ce0592ed..1e4c0487c 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -12,8 +12,10 @@ import ( "net/url" "os" gosignal "os/signal" + "runtime" "strconv" "strings" + "time" log "github.com/Sirupsen/logrus" "github.com/docker/docker/api" @@ -279,13 +281,29 @@ func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) { func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { cli.resizeTty(id, isExec) - sigchan := make(chan os.Signal, 1) - gosignal.Notify(sigchan, signal.SIGWINCH) - go func() { - for _ = range sigchan { - cli.resizeTty(id, isExec) - } - }() + if runtime.GOOS == "windows" { + go func() { + prevW, prevH := cli.getTtySize() + for { + time.Sleep(time.Millisecond * 250) + w, h := cli.getTtySize() + + if prevW != w || prevH != h { + cli.resizeTty(id, isExec) + } + prevW = w + prevH = h + } + }() + } else { + sigchan := make(chan os.Signal, 1) + gosignal.Notify(sigchan, signal.SIGWINCH) + go func() { + for _ = range sigchan { + cli.resizeTty(id, isExec) + } + }() + } return nil } From f581f742095e4b01b73e391546ee11323f8b5fd7 Mon Sep 17 00:00:00 2001 From: chli Date: Thu, 26 Mar 2015 19:18:23 -0400 Subject: [PATCH 133/999] Issue #11836 Signed-off-by: chli --- api/server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/server/server.go b/api/server/server.go index c52c6bd2a..5d6d92748 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1297,7 +1297,7 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local } 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) + http.Error(w, fmt.Errorf("client and server don't have same version (client API version: %s, server API version: %s)", version, api.APIVERSION).Error(), http.StatusNotFound) return } From ddcb3ad061db518aa076422ed6d81a2247fb3a8e Mon Sep 17 00:00:00 2001 From: Daniel S Date: Thu, 26 Mar 2015 19:25:57 -0400 Subject: [PATCH 134/999] Update set-up-dev-env.md Signed-off-by: graycoder --- docs/sources/project/set-up-dev-env.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/sources/project/set-up-dev-env.md b/docs/sources/project/set-up-dev-env.md index 0629822b9..e40a8e6c1 100644 --- a/docs/sources/project/set-up-dev-env.md +++ b/docs/sources/project/set-up-dev-env.md @@ -252,10 +252,9 @@ with the `make.sh` script. When the command completes successfully, you should see the following output: - ---> Making bundle: ubuntu (in bundles/1.5.0-dev/ubuntu) - Created package {:path=>"lxc-docker-1.5.0-dev_1.5.0~dev~git20150223.181106.0.1ab0d23_amd64.deb"} - Created package {:path=>"lxc-docker_1.5.0~dev~git20150223.181106.0.1ab0d23_amd64.deb"} - + ---> Making bundle: binary (in bundles/1.5.0-dev/binary) + Created binary: /go/src/github.com/docker/docker/bundles/1.5.0-dev/binary/docker-1.5.0-dev + 5. List all the contents of the `binary` directory. root@5f8630b873fe:/go/src/github.com/docker/docker# ls bundles/1.5.0-dev/binary/ From 9a9d23dbc40c9b650bd1f9f98a421b7ff2312f36 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Thu, 26 Mar 2015 17:25:50 -0700 Subject: [PATCH 135/999] Clean up integration-cli tests My AR couldn't take it any more: - one logDone per test - PASSED lines don't wrap Signed-off-by: Doug Davis --- integration-cli/docker_cli_build_test.go | 6 ++---- integration-cli/docker_cli_export_import_test.go | 6 ++---- integration-cli/docker_cli_run_test.go | 6 ++---- integration-cli/docker_cli_save_load_unix_test.go | 4 +--- integration-cli/docker_cli_tag_test.go | 10 ++-------- integration-cli/utils.go | 2 +- 6 files changed, 10 insertions(+), 24 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index e4ab58e53..cec62419b 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -1911,8 +1911,6 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) { } logDone("build - ADD from context with inaccessible files must not pass") - logDone("build - ADD from context with accessible links must work") - logDone("build - ADD from context with ignored inaccessible files must work") } func TestBuildForceRm(t *testing.T) { @@ -2152,7 +2150,6 @@ func TestBuildRm(t *testing.T) { } logDone("build - ensure --rm doesn't leave containers behind and that --rm=true is the default") - logDone("build - ensure --rm=false overrides the default") } func TestBuildWithVolumes(t *testing.T) { @@ -3265,15 +3262,16 @@ CMD ["cat", "/foo"]`, if out, _, err := runCommandWithOutput(buildCmd); err != nil { t.Fatalf("build failed to complete: %v %v", out, err) } - logDone(fmt.Sprintf("build - build an image with a context tar, compression: %v", compression)) } func TestBuildContextTarGzip(t *testing.T) { testContextTar(t, archive.Gzip) + logDone(fmt.Sprintf("build - build an image with a context tar, compression: %v", archive.Gzip)) } func TestBuildContextTarNoCompression(t *testing.T) { testContextTar(t, archive.Uncompressed) + logDone(fmt.Sprintf("build - build an image with a context tar, compression: %v", archive.Uncompressed)) } func TestBuildNoContext(t *testing.T) { diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go index 5b2a016f1..e1aa1d667 100644 --- a/integration-cli/docker_cli_export_import_test.go +++ b/integration-cli/docker_cli_export_import_test.go @@ -45,8 +45,7 @@ func TestExportContainerAndImportImage(t *testing.T) { deleteContainer(cleanedContainerID) deleteImages("repo/testexp:v1") - logDone("export - export a container") - logDone("import - import an image") + logDone("export - export/import a container/image") } // Used to test output flag in the export command @@ -94,6 +93,5 @@ func TestExportContainerWithOutputAndImportImage(t *testing.T) { os.Remove("/tmp/testexp.tar") - logDone("export - export a container with output flag") - logDone("import - import an image with output flag") + logDone("export - export/import a container/image with output flag") } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 0c5f56f8d..c971c89ef 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -295,8 +295,7 @@ func TestRunWorkingDirectory(t *testing.T) { t.Errorf("--workdir failed to set working directory") } - logDone("run - run with working directory set by -w") - logDone("run - run with working directory set by --workdir") + logDone("run - run with working directory set by -w/--workdir") } // pinging Google's DNS resolver should fail when we disable the networking @@ -321,8 +320,7 @@ func TestRunWithoutNetworking(t *testing.T) { t.Errorf("-n=false should've disabled the network; the container shouldn't have been able to ping 8.8.8.8") } - logDone("run - disable networking with --net=none") - logDone("run - disable networking with -n=false") + logDone("run - disable networking with --net=none/-n=false") } //test --link use container name to link target diff --git a/integration-cli/docker_cli_save_load_unix_test.go b/integration-cli/docker_cli_save_load_unix_test.go index c6704877a..29e756c04 100644 --- a/integration-cli/docker_cli_save_load_unix_test.go +++ b/integration-cli/docker_cli_save_load_unix_test.go @@ -70,8 +70,6 @@ func TestSaveAndLoadRepoStdout(t *testing.T) { os.Remove("/tmp/foobar-save-load-test.tar") - logDone("save - save/load a repo using stdout") - pty, tty, err := pty.Open() if err != nil { t.Fatalf("Could not open pty: %v", err) @@ -98,5 +96,5 @@ func TestSaveAndLoadRepoStdout(t *testing.T) { t.Fatal("help output is not being yielded", out) } - logDone("save - do not save to a tty") + logDone("save - save/load a repo using stdout") } diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go index b181e2177..4af6e2f88 100644 --- a/integration-cli/docker_cli_tag_test.go +++ b/integration-cli/docker_cli_tag_test.go @@ -1,7 +1,6 @@ package main import ( - "fmt" "os/exec" "strings" "testing" @@ -91,9 +90,8 @@ func TestTagValidPrefixedRepo(t *testing.T) { continue } deleteImages(repo) - logMessage := fmt.Sprintf("tag - busybox %v", repo) - logDone(logMessage) } + logDone("tag - tag valid prefixed repo") } // tag an image with an existed tag name without -f option should fail @@ -162,9 +160,6 @@ func TestTagOfficialNames(t *testing.T) { } else if strings.Contains(out, name) { t.Errorf("images should not have listed '%s'", name) deleteImages(name + ":latest") - } else { - logMessage := fmt.Sprintf("tag official name - busybox %v", name) - logDone(logMessage) } } @@ -176,7 +171,6 @@ func TestTagOfficialNames(t *testing.T) { continue } deleteImages("fooo/bar:latest") - logMessage := fmt.Sprintf("tag official name - %v fooo/bar", name) - logDone(logMessage) } + logDone("tag - tag official names") } diff --git a/integration-cli/utils.go b/integration-cli/utils.go index d6b65acc0..7beb28974 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -166,7 +166,7 @@ func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in } func logDone(message string) { - fmt.Printf("[PASSED]: %s\n", message) + fmt.Printf("[PASSED]: %.69s\n", message) } func stripTrailingCharacters(target string) string { From 54c9ae187f5a3544881650c09e923d67ed229771 Mon Sep 17 00:00:00 2001 From: Katrina Owen Date: Thu, 26 Mar 2015 19:21:16 -0700 Subject: [PATCH 136/999] Clarify git instructions in project guide. Fixes #11796 The git model uses `upstream master` to refer to the branch on the remote repository, and `upstream/master` to refer to the local cache of the upstream branch. I did not explain the difference in the docs (that seemed a bit excessive), but I did clarify the instructions so that it refers to the correct concept in each place. Signed-off-by: Katrina Owen --- docs/sources/project/find-an-issue.md | 15 +++++++-------- docs/sources/project/work-issue.md | 19 ++++++++----------- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/docs/sources/project/find-an-issue.md b/docs/sources/project/find-an-issue.md index 2b3396e6e..0cfe7d7b5 100644 --- a/docs/sources/project/find-an-issue.md +++ b/docs/sources/project/find-an-issue.md @@ -164,17 +164,16 @@ To sync your repository: $ git remote add upstream https://github.com/docker/docker.git -5. Fetch all the changes from the `upstream/master` branch. +5. Fetch all the changes from the `upstream master` branch. - $ git fetch upstream + $ git fetch upstream master remote: Counting objects: 141, done. remote: Compressing objects: 100% (29/29), done. remote: Total 141 (delta 52), reused 46 (delta 46), pack-reused 66 Receiving objects: 100% (141/141), 112.43 KiB | 0 bytes/s, done. Resolving deltas: 100% (79/79), done. - From github.com:docker/docker - 9ffdf1e..01d09e4 docs -> upstream/docs - 05ba127..ac2521b master -> upstream/master + From github.com:docker/docker + * branch master -> FETCH_HEAD This command says get all the changes from the `master` branch belonging to the `upstream` remote. @@ -197,9 +196,9 @@ To sync your repository: nothing to commit, working directory clean Your local repository now has any changes from the `upstream` remote. You - need to push the changes to your own remote fork which is `origin/master`. + need to push the changes to your own remote fork which is `origin master`. -9. Push the rebased master to `origin/master`. +9. Push the rebased master to `origin master`. $ git push origin Username for 'https://github.com': moxiegirl @@ -219,7 +218,7 @@ To sync your repository: $ git checkout -b 11038-fix-rhel-link Switched to a new branch '11038-fix-rhel-link' - Your branch should be up-to-date with the upstream/master. Why? Because you + Your branch should be up-to-date with the `upstream/master`. Why? Because you branched off a freshly synced master. Let's check this anyway in the next step. diff --git a/docs/sources/project/work-issue.md b/docs/sources/project/work-issue.md index 2223f8610..5e70bc32c 100644 --- a/docs/sources/project/work-issue.md +++ b/docs/sources/project/work-issue.md @@ -119,11 +119,7 @@ Follow this workflow as you work: To https://github.com/moxiegirl/docker.git * [new branch] 11038-fix-rhel-link -> 11038-fix-rhel-link Branch 11038-fix-rhel-link set up to track remote branch 11038-fix-rhel-link from origin. - - The first time you push a change, you must specify the branch. Later, you can just do this: - - git push origin - + ## Review your branch on GitHub After you push a new branch, you should verify it on GitHub: @@ -155,19 +151,20 @@ You should pull and rebase frequently as you work. $ git branch 11038-fix-rhel-link -3. Fetch all the changes from the `upstream/master` branch. +3. Fetch all the changes from the `upstream master` branch. - $ git fetch upstream/master + $ git fetch upstream master This command says get all the changes from the `master` branch belonging to the `upstream` remote. -4. Rebase your local master with Docker's `upstream/master` branch. +4. Rebase your master with the local copy of Docker's `master` branch. $ git rebase -i upstream/master - This command starts an interactive rebase to merge code from Docker's - `upstream/master` branch into your local branch. If you aren't familiar or + This command starts an interactive rebase to rewrite all the commits from + Docker's `upstream/master` onto your local branch, and then re-apply each of + your commits on top of the upstream changes. If you aren't familiar or comfortable with rebase, you can learn more about rebasing on the web. @@ -190,7 +187,7 @@ You should pull and rebase frequently as you work. After closing the file, `git` opens your editor again to edit the commit message. -7. Edit and save your commit message. +7. Edit the commit message to reflect the entire change. Make sure you include your signature. From dd6f988b23b56ce0ca17ab892143db53f1fa2f84 Mon Sep 17 00:00:00 2001 From: Christy Perez Date: Thu, 26 Mar 2015 21:38:46 -0500 Subject: [PATCH 137/999] Fix typo in doc at /set-up-dev-env Save a whale! Signed-off-by: Christy Perez --- docs/sources/project/set-up-dev-env.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/project/set-up-dev-env.md b/docs/sources/project/set-up-dev-env.md index 0ab08cea0..3ab92ae56 100644 --- a/docs/sources/project/set-up-dev-env.md +++ b/docs/sources/project/set-up-dev-env.md @@ -272,8 +272,8 @@ with the `make.sh` script. root@5f8630b873fe:/go/src/github.com/docker/docker# docker --version Docker version 1.5.0-dev, build 6e728fb - Inside the container you are running a development version. This is version - on the current branch it reflects the value of the `VERSION` file at the + Inside the container you are running a development version. This is the version + on the current branch. It reflects the value of the `VERSION` file at the root of your `docker-fork` repository. 8. Start a `docker` daemon running inside your container. From f5ad895ba624b6baddfa0c54a2da439b41b0535f Mon Sep 17 00:00:00 2001 From: Srini Brahmaroutu Date: Thu, 26 Mar 2015 19:43:00 +0000 Subject: [PATCH 138/999] Use common code to test all events, when using filter that expect all lifecycle events. Addresses: #10654 Signed-off-by: Srini Brahmaroutu --- integration-cli/docker_cli_events_test.go | 61 ++++++++--------------- 1 file changed, 21 insertions(+), 40 deletions(-) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index e855ce88f..3cd5edd4c 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -317,26 +317,7 @@ func TestEventsFilterContainerID(t *testing.T) { t.Fatalf("Failed to get events, error: %s(%s)", err, out) } events := strings.Split(out, "\n") - events = events[:len(events)-1] - if len(events) == 0 || len(events) > 3 { - t.Fatalf("Expected 3 events, got %d: %v", len(events), events) - } - createEvent := strings.Fields(events[0]) - if createEvent[len(createEvent)-1] != "create" { - t.Fatalf("first event should be create, not %#v", createEvent) - } - if len(events) > 1 { - startEvent := strings.Fields(events[1]) - if startEvent[len(startEvent)-1] != "start" { - t.Fatalf("second event should be start, not %#v", startEvent) - } - } - if len(events) == 3 { - dieEvent := strings.Fields(events[len(events)-1]) - if dieEvent[len(dieEvent)-1] != "die" { - t.Fatalf("event should be die, not %#v", dieEvent) - } - } + checkEvents(t, events[:len(events)-1]) } logDone("events - filters using container id") @@ -363,27 +344,27 @@ func TestEventsFilterContainerName(t *testing.T) { t.Fatalf("Failed to get events, error : %s(%s)", err, out) } events := strings.Split(out, "\n") - events = events[:len(events)-1] - if len(events) == 0 || len(events) > 3 { - t.Fatalf("Expected 3 events, got %d: %v", len(events), events) - } - createEvent := strings.Fields(events[0]) - if createEvent[len(createEvent)-1] != "create" { - t.Fatalf("first event should be create, not %#v", createEvent) - } - if len(events) > 1 { - startEvent := strings.Fields(events[1]) - if startEvent[len(startEvent)-1] != "start" { - t.Fatalf("second event should be start, not %#v", startEvent) - } - } - if len(events) == 3 { - dieEvent := strings.Fields(events[len(events)-1]) - if dieEvent[len(dieEvent)-1] != "die" { - t.Fatalf("event should be die, not %#v", dieEvent) - } - } + checkEvents(t, events[:len(events)-1]) } logDone("events - filters using container name") } + +func checkEvents(t *testing.T, events []string) { + if len(events) != 3 { + t.Fatalf("Expected 3 events, got %d: %v", len(events), events) + } + createEvent := strings.Fields(events[0]) + if createEvent[len(createEvent)-1] != "create" { + t.Fatalf("first event should be create, not %#v", createEvent) + } + startEvent := strings.Fields(events[1]) + if startEvent[len(startEvent)-1] != "start" { + t.Fatalf("second event should be start, not %#v", startEvent) + } + dieEvent := strings.Fields(events[len(events)-1]) + if dieEvent[len(dieEvent)-1] != "die" { + t.Fatalf("event should be die, not %#v", dieEvent) + } + +} From 01bbc3fbb9c3ab3dec0f271710739465b0f80b7a Mon Sep 17 00:00:00 2001 From: Eric Rafaloff Date: Thu, 26 Mar 2015 23:05:07 -0400 Subject: [PATCH 139/999] Add some basic doc for SysInfo Signed-off-by: Eric Rafaloff --- pkg/sysinfo/README.md | 1 + pkg/sysinfo/sysinfo.go | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 pkg/sysinfo/README.md diff --git a/pkg/sysinfo/README.md b/pkg/sysinfo/README.md new file mode 100644 index 000000000..c1530cef0 --- /dev/null +++ b/pkg/sysinfo/README.md @@ -0,0 +1 @@ +SysInfo stores information about which features a kernel supports. diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 506124e74..7c769e308 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -9,6 +9,7 @@ import ( "github.com/docker/libcontainer/cgroups" ) +// SysInfo stores information about which features a kernel supports. type SysInfo struct { MemoryLimit bool SwapLimit bool @@ -16,6 +17,7 @@ type SysInfo struct { AppArmor bool } +// Returns a new SysInfo, using the filesystem to detect which features the kernel supports. func New(quiet bool) *SysInfo { sysInfo := &SysInfo{} if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil { @@ -37,7 +39,7 @@ func New(quiet bool) *SysInfo { } } - // Check if AppArmor seems to be enabled on this system. + // Check if AppArmor is supported if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) { sysInfo.AppArmor = false } else { From 6abe160eaec49c693b69d14304fe39d78ea4abb4 Mon Sep 17 00:00:00 2001 From: Eric Rafaloff Date: Thu, 26 Mar 2015 23:22:05 -0400 Subject: [PATCH 140/999] Add missing . in comment Signed-off-by: Eric Rafaloff --- pkg/sysinfo/sysinfo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 7c769e308..67dc589bb 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -39,7 +39,7 @@ func New(quiet bool) *SysInfo { } } - // Check if AppArmor is supported + // Check if AppArmor is supported. if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) { sysInfo.AppArmor = false } else { From 89a29d7e99e57bcbcf87a7657070bfdf59d7b6ad Mon Sep 17 00:00:00 2001 From: cheney90 Date: Fri, 27 Mar 2015 16:41:06 +0800 Subject: [PATCH 141/999] Add capabilities list information table. Signed-off-by: Chen Qiu <21321229@zju.edu.cn> --- docs/sources/reference/run.md | 45 ++++++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index b1d0e92bd..7be09fa19 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -635,7 +635,50 @@ This can be overridden using a third `:rwm` set of options to each `--device` fl In addition to `--privileged`, the operator can have fine grain control over the capabilities using `--cap-add` and `--cap-drop`. By default, Docker has a default -list of capabilities that are kept. Both flags support the value `all`, so if the +list of capabilities that are kept. Here is a table to list the reference information on capabilities. + +| Capability Key | Capability Value | Capability Description | +| :----------------- | :---------------| :-------------------- | +| SETPCAP | capability.CAP_SETPCAP | Modify process capabilities. | +| SYS_MODULE | capability.CAP_SYS_MODULE | Load and unload kernel modules. | +| SYS_RAWIO | capability.CAP_SYS_RAWIO | Perform I/O port operations (iopl(2) and ioperm(2)). | +| SYS_PACCT | capability.CAP_SYS_PACCT | Use acct(2), switch process accounting on or off. | +| SYS_ADMIN | capability.CAP_SYS_ADMIN | Perform a range of system administration operations. | +| SYS_NICE | capability.CAP_SYS_NICE | Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes. | +| SYS_RESOURCE | capability.CAP_SYS_RESOURCE | Override Resource Limits. | +| SYS_TIME | capability.CAP_SYS_TIME | Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock. | +| SYS_TTY_CONFIG | capability.CAP_SYS_TTY_CONFIG | Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals. | +| MKNOD | capability.CAP_MKNOD | Create special files using mknod(2). | +| AUDIT_WRITE | capability.CAP_AUDIT_WRITE | Write records to kernel auditing log. | +| AUDIT_CONTROL | capability.CAP_AUDIT_CONTROL | Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules. | +| MAC_OVERRIDE | capability.CAP_MAC_OVERRIDE | Allow MAC configuration or state changes. Implemented for the Smack LSM. | +| MAC_ADMIN | capability.CAP_MAC_ADMIN | Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM). | +| NET_ADMIN | capability.CAP_NET_ADMIN | Perform various network-related operations. | +| SYSLOG | capability.CAP_SYSLOG | Perform privileged syslog(2) operations. | +| CHOWN | capability.CAP_CHOWN | Make arbitrary changes to file UIDs and GIDs (see chown(2)). | +| NET_RAW | capability.CAP_NET_RAW | Use RAW and PACKET sockets. | +| DAC_OVERRIDE | capability.CAP_DAC_OVERRIDE | Bypass file read, write, and execute permission checks. | +| FOWNER | capability.CAP_FOWNER | Bypass permission checks on operations that normally require the file system UID of the process to match the UID of the file. | +| DAC_READ_SEARCH | capability.CAP_DAC_READ_SEARCH | Bypass file read permission checks and directory read and execute permission checks. | +| FSETID | capability.CAP_FSETID | Don't clear set-user-ID and set-group-ID permission bits when a file is modified. | +| KILL | apability.CAP_KILL | Bypass permission checks for sending signals. | +| SETGID | capability.CAP_SETGID | Make arbitrary manipulations of process GIDs and supplementary GID list. | +| SETUID | capability.CAP_SETUID | Make arbitrary manipulations of process UIDs. | +| LINUX_IMMUTABLE | capability.CAP_LINUX_IMMUTABLE | Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags. | +| NET_BIND_SERVICE | capability.CAP_NET_BIND_SERVICE | Bind a socket to Internet domain privileged ports (port numbers less than 1024). | +| NET_BROADCAST | capability.CAP_NET_BROADCAST} | Make socket broadcasts, and listen to multicasts. | +| IPC_LOCK | capability.CAP_IPC_LOCK | Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)). | +| IPC_OWNER | capability.CAP_IPC_OWNER | Bypass permission checks for operations on System V IPC objects. | +| SYS_CHROOT | capability.CAP_SYS_CHROOT | Use chroot(2), change root directory. | +| SYS_PTRACE | capability.CAP_SYS_PTRACE | Trace arbitrary processes using ptrace(2). | +| SYS_BOOT | capability.CAP_SYS_BOOT | Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution. | +| LEASE | capability.CAP_LEASE | Establish leases on arbitrary files (see fcntl(2)). | +| SETFCAP | capability.CAP_SETFCAP | Set file capabilities.| +| WAKE_ALARM | capability.CAP_WAKE_ALARM | Trigger something that will wake up the system. | +| BLOCK_SUSPEND | capability.CAP_BLOCK_SUSPEND | Employ features that can block system suspend. | + + +Both flags support the value `all`, so if the operator wants to have all capabilities but `MKNOD` they could use: $ docker run --cap-add=ALL --cap-drop=MKNOD ... From 3716df57c097803dec220ff95fffdae8394145fc Mon Sep 17 00:00:00 2001 From: Eric Rafaloff Date: Fri, 27 Mar 2015 13:55:22 -0400 Subject: [PATCH 142/999] Update inline doc for New Signed-off-by: Eric Rafaloff --- pkg/sysinfo/sysinfo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 67dc589bb..16839bcb4 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -17,7 +17,7 @@ type SysInfo struct { AppArmor bool } -// Returns a new SysInfo, using the filesystem to detect which features the kernel supports. +// New returns a new SysInfo, using the filesystem to detect which features the kernel supports. func New(quiet bool) *SysInfo { sysInfo := &SysInfo{} if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil { From 60085e22ff9981bf26711295d12ea64ee29d85ea Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 26 Mar 2015 10:48:07 +0100 Subject: [PATCH 143/999] Remove err field from Job struct, fixes #11804 Signed-off-by: Antonio Murdaca --- engine/job.go | 65 ++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 34 deletions(-) diff --git a/engine/job.go b/engine/job.go index 189061bf5..12acdc933 100644 --- a/engine/job.go +++ b/engine/job.go @@ -17,12 +17,7 @@ import ( // download an archive from the internet, serve the http api, etc. // // The job API is designed after unix processes: a job has a name, arguments, -// environment variables, standard streams for input, output and error, and -// an exit status which can indicate success (0) or error (anything else). -// -// For status, 0 indicates success, and any other integers indicates an error. -// This allows for richer error reporting. -// +// environment variables, standard streams for input, output and error. type Job struct { Eng *Engine Name string @@ -32,7 +27,6 @@ type Job struct { Stderr *Output Stdin *Input handler Handler - err error end time.Time closeIO bool @@ -45,7 +39,22 @@ type Job struct { // Run executes the job and blocks until the job completes. // If the job fails it returns an error -func (job *Job) Run() error { +func (job *Job) Run() (err error) { + defer func() { + // Wait for all background tasks to complete + if job.closeIO { + if err := job.Stdout.Close(); err != nil { + logrus.Error(err) + } + if err := job.Stderr.Close(); err != nil { + logrus.Error(err) + } + if err := job.Stdin.Close(); err != nil { + logrus.Error(err) + } + } + }() + if job.Eng.IsShutdown() && !job.GetenvBool("overrideShutdown") { return fmt.Errorf("engine is shutdown") } @@ -69,32 +78,25 @@ func (job *Job) Run() error { if job.Eng.Logging { logrus.Infof("+job %s", job.CallString()) defer func() { - // what if err is nil? - logrus.Infof("-job %s%s", job.CallString(), job.err) + okerr := "OK" + if err != nil { + okerr = fmt.Sprintf("ERR: %s", err) + } + logrus.Infof("-job %s %s", job.CallString(), okerr) }() } - var errorMessage = bytes.NewBuffer(nil) - job.Stderr.Add(errorMessage) + if job.handler == nil { - job.err = fmt.Errorf("%s: command not found", job.Name) - } else { - job.err = job.handler(job) - job.end = time.Now() - } - if job.closeIO { - // Wait for all background tasks to complete - if err := job.Stdout.Close(); err != nil { - return err - } - if err := job.Stderr.Close(); err != nil { - return err - } - if err := job.Stdin.Close(); err != nil { - return err - } + return fmt.Errorf("%s: command not found", job.Name) } - return job.err + var errorMessage = bytes.NewBuffer(nil) + job.Stderr.Add(errorMessage) + + err = job.handler(job) + job.end = time.Now() + + return } func (job *Job) CallString() string { @@ -195,11 +197,6 @@ func (job *Job) Environ() map[string]string { return job.env.Map() } -func (job *Job) Logf(format string, args ...interface{}) (n int, err error) { - prefixedFormat := fmt.Sprintf("[%s] %s\n", job, strings.TrimRight(format, "\n")) - return fmt.Fprintf(job.Stderr, prefixedFormat, args...) -} - func (job *Job) Printf(format string, args ...interface{}) (n int, err error) { return fmt.Fprintf(job.Stdout, format, args...) } From 17303b18b8813a184dadaacf63b10a419e476d07 Mon Sep 17 00:00:00 2001 From: Elijah Zupancic Date: Tue, 24 Mar 2015 19:02:10 -0700 Subject: [PATCH 144/999] Removed ulimit steps from Dockerfile because they aren't applied according to @cpuguy83. Signed-off-by: Elijah Zupancic --- docs/sources/examples/running_riak_service.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/sources/examples/running_riak_service.md b/docs/sources/examples/running_riak_service.md index 0b5323404..4945feff7 100644 --- a/docs/sources/examples/running_riak_service.md +++ b/docs/sources/examples/running_riak_service.md @@ -60,7 +60,6 @@ After that, we install Riak and alter a few defaults: # Install Riak and prepare it to run RUN apt-get update && apt-get install -y riak RUN sed -i.bak 's/127.0.0.1/0.0.0.0/' /etc/riak/app.config - RUN echo "ulimit -n 4096" >> /etc/default/riak Then, we expose the Riak Protocol Buffers and HTTP interfaces, along with SSH: From 31c50411574b19cb81b5f88c0f8ca4bb4a2ead83 Mon Sep 17 00:00:00 2001 From: Elijah Zupancic Date: Tue, 24 Mar 2015 19:27:58 -0700 Subject: [PATCH 145/999] Removed references to creating an OpenSSH server. This method of accessing the Docker container is no longer needed now that the exec command is available. Signed-off-by: Elijah Zupancic --- docs/sources/examples/running_riak_service.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/sources/examples/running_riak_service.md b/docs/sources/examples/running_riak_service.md index 4945feff7..7c00ab0cd 100644 --- a/docs/sources/examples/running_riak_service.md +++ b/docs/sources/examples/running_riak_service.md @@ -31,17 +31,13 @@ After that, we install and setup a few dependencies: repository key - `lsb-release` helps us derive the Ubuntu release codename - - `openssh-server` allows us to login to - containers remotely and join Riak nodes to form a cluster - - `supervisor` is used manage the OpenSSH and Riak - processes + - `supervisor` is used manage the Riak processes # Install and setup project dependencies - RUN apt-get update && apt-get install -y curl lsb-release supervisor openssh-server + RUN apt-get update && apt-get install -y curl lsb-release supervisor - RUN mkdir -p /var/run/sshd RUN mkdir -p /var/log/supervisor RUN locale-gen en_US en_US.UTF-8 @@ -64,8 +60,8 @@ After that, we install Riak and alter a few defaults: Then, we expose the Riak Protocol Buffers and HTTP interfaces, along with SSH: - # Expose Riak Protocol Buffers and HTTP interfaces, along with SSH - EXPOSE 8087 8098 22 + # Expose Riak Protocol Buffers and HTTP interfaces + EXPOSE 8087 8098 Finally, run `supervisord` so that Riak and OpenSSH are started: From de45aacc322445924710fc12ad6e2d9832c27c6e Mon Sep 17 00:00:00 2001 From: Elijah Zupancic Date: Fri, 27 Mar 2015 11:18:40 -0700 Subject: [PATCH 146/999] Added updated working Riak Dockerfile and documentation. Signed-off-by: Elijah Zupancic --- .../examples/running_riak_service.Dockerfile | 31 +++++++++ docs/sources/examples/running_riak_service.md | 64 +++++++++---------- docs/sources/examples/supervisord.conf | 12 ++++ 3 files changed, 73 insertions(+), 34 deletions(-) create mode 100644 docs/sources/examples/running_riak_service.Dockerfile create mode 100644 docs/sources/examples/supervisord.conf diff --git a/docs/sources/examples/running_riak_service.Dockerfile b/docs/sources/examples/running_riak_service.Dockerfile new file mode 100644 index 000000000..1051c1a42 --- /dev/null +++ b/docs/sources/examples/running_riak_service.Dockerfile @@ -0,0 +1,31 @@ +# Riak +# +# VERSION 0.1.1 + +# Use the Ubuntu base image provided by dotCloud +FROM ubuntu:trusty +MAINTAINER Hector Castro hector@basho.com + +# Install Riak repository before we do apt-get update, so that update happens +# in a single step +RUN apt-get install -q -y curl && \ + curl -sSL https://packagecloud.io/install/repositories/basho/riak/script.deb | sudo bash + +# Install and setup project dependencies +RUN apt-get update && \ + apt-get install -y supervisor riak=2.0.5-1 + +RUN mkdir -p /var/log/supervisor + +RUN locale-gen en_US en_US.UTF-8 + +COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf + +# Configure Riak to accept connections from any host +RUN sed -i "s|listener.http.internal = 127.0.0.1:8098|listener.http.internal = 0.0.0.0:8098|" /etc/riak/riak.conf +RUN sed -i "s|listener.protobuf.internal = 127.0.0.1:8087|listener.protobuf.internal = 0.0.0.0:8087|" /etc/riak/riak.conf + +# Expose Riak Protocol Buffers and HTTP interfaces +EXPOSE 8087 8098 + +CMD ["/usr/bin/supervisord"] diff --git a/docs/sources/examples/running_riak_service.md b/docs/sources/examples/running_riak_service.md index 7c00ab0cd..d5fcc547f 100644 --- a/docs/sources/examples/running_riak_service.md +++ b/docs/sources/examples/running_riak_service.md @@ -15,47 +15,46 @@ Create an empty file called `Dockerfile`: Next, define the parent image you want to use to build your image on top of. We'll use [Ubuntu](https://registry.hub.docker.com/_/ubuntu/) (tag: -`latest`), which is available on [Docker Hub](https://hub.docker.com): +`trusty`), which is available on [Docker Hub](https://hub.docker.com): # Riak # - # VERSION 0.1.0 - + # VERSION 0.1.1 + # Use the Ubuntu base image provided by dotCloud - FROM ubuntu:latest + FROM ubuntu:trusty MAINTAINER Hector Castro hector@basho.com -After that, we install and setup a few dependencies: +After that, we install the curl which is used to download the repository setup +script and we download the setup script and run it. + + # Install Riak repository before we do apt-get update, so that update happens + # in a single step + RUN apt-get install -q -y curl && \ + curl -sSL https://packagecloud.io/install/repositories/basho/riak/script.deb | sudo bash + +Then we install and setup a few dependencies: - - `curl` is used to download Basho's APT - repository key - - `lsb-release` helps us derive the Ubuntu release - codename - `supervisor` is used manage the Riak processes + - `riak=2.0.5-1` is the Riak package coded to version 2.0.5 # Install and setup project dependencies - RUN apt-get update && apt-get install -y curl lsb-release supervisor + RUN apt-get update && \ + apt-get install -y supervisor riak=2.0.5-1 RUN mkdir -p /var/log/supervisor - + RUN locale-gen en_US en_US.UTF-8 - + COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf - RUN echo 'root:basho' | chpasswd +After that, we modify Riak's configuration: -Next, we add Basho's APT repository: - - RUN curl -sSL http://apt.basho.com/gpg/basho.apt.key | apt-key add -- - RUN echo "deb http://apt.basho.com $(lsb_release -cs) main" > /etc/apt/sources.list.d/basho.list - -After that, we install Riak and alter a few defaults: - - # Install Riak and prepare it to run - RUN apt-get update && apt-get install -y riak - RUN sed -i.bak 's/127.0.0.1/0.0.0.0/' /etc/riak/app.config + # Configure Riak to accept connections from any host + RUN sed -i "s|listener.http.internal = 127.0.0.1:8098|listener.http.internal = 0.0.0.0:8098|" /etc/riak/riak.conf + RUN sed -i "s|listener.protobuf.internal = 127.0.0.1:8087|listener.protobuf.internal = 0.0.0.0:8087|" /etc/riak/riak.conf Then, we expose the Riak Protocol Buffers and HTTP interfaces, along with SSH: @@ -63,8 +62,7 @@ with SSH: # Expose Riak Protocol Buffers and HTTP interfaces EXPOSE 8087 8098 -Finally, run `supervisord` so that Riak and OpenSSH -are started: +Finally, run `supervisord` so that Riak is started: CMD ["/usr/bin/supervisord"] @@ -79,16 +77,14 @@ Populate it with the following program definitions: [supervisord] nodaemon=true - - [program:sshd] - command=/usr/sbin/sshd -D - stdout_logfile=/var/log/supervisor/%(program_name)s.log - stderr_logfile=/var/log/supervisor/%(program_name)s.log - autorestart=true - + [program:riak] - command=bash -c ". /etc/default/riak && /usr/sbin/riak console" - pidfile=/var/log/riak/riak.pid + command=bash -c "/usr/sbin/riak console" + numprocs=1 + autostart=true + autorestart=true + user=riak + environment=HOME="/var/lib/riak" stdout_logfile=/var/log/supervisor/%(program_name)s.log stderr_logfile=/var/log/supervisor/%(program_name)s.log diff --git a/docs/sources/examples/supervisord.conf b/docs/sources/examples/supervisord.conf new file mode 100644 index 000000000..385fbe7a4 --- /dev/null +++ b/docs/sources/examples/supervisord.conf @@ -0,0 +1,12 @@ +[supervisord] +nodaemon=true + +[program:riak] +command=bash -c "/usr/sbin/riak console" +numprocs=1 +autostart=true +autorestart=true +user=riak +environment=HOME="/var/lib/riak" +stdout_logfile=/var/log/supervisor/%(program_name)s.log +stderr_logfile=/var/log/supervisor/%(program_name)s.log From 6b764bba8ad1c1006dadf24ec55edb9de200c706 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A9lix=20Baylac-Jacqu=C3=A9?= Date: Fri, 27 Mar 2015 20:16:25 +0100 Subject: [PATCH 147/999] Fix vet warning in devicemapper. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue #11828 Signed-off-by: Félix Baylac-Jacqué --- pkg/devicemapper/devmapper.go | 5 +++-- pkg/devicemapper/devmapper_wrapper.go | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/pkg/devicemapper/devmapper.go b/pkg/devicemapper/devmapper.go index 1d45d0588..9a34a41d6 100644 --- a/pkg/devicemapper/devmapper.go +++ b/pkg/devicemapper/devmapper.go @@ -8,6 +8,7 @@ import ( "os" "runtime" "syscall" + "unsafe" log "github.com/Sirupsen/logrus" ) @@ -226,7 +227,7 @@ func (t *Task) GetDriverVersion() (string, error) { return res, nil } -func (t *Task) GetNextTarget(next uintptr) (nextPtr uintptr, start uint64, +func (t *Task) GetNextTarget(next unsafe.Pointer) (nextPtr unsafe.Pointer, start uint64, length uint64, targetType string, params string) { return DmGetNextTarget(t.unmanaged, next, &start, &length, @@ -512,7 +513,7 @@ func GetStatus(name string) (uint64, uint64, string, string, error) { return 0, 0, "", "", fmt.Errorf("Non existing device %s", name) } - _, start, length, targetType, params := task.GetNextTarget(0) + _, start, length, targetType, params := task.GetNextTarget(unsafe.Pointer(nil)) return start, length, targetType, params, nil } diff --git a/pkg/devicemapper/devmapper_wrapper.go b/pkg/devicemapper/devmapper_wrapper.go index ae4f30fb3..e436cca32 100644 --- a/pkg/devicemapper/devmapper_wrapper.go +++ b/pkg/devicemapper/devmapper_wrapper.go @@ -219,7 +219,7 @@ func dmTaskGetDriverVersionFct(task *CDmTask) string { return C.GoString((*C.char)(buffer)) } -func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, target, params *string) uintptr { +func dmGetNextTargetFct(task *CDmTask, next unsafe.Pointer, start, length *uint64, target, params *string) unsafe.Pointer { var ( Cstart, Clength C.uint64_t CtargetType, Cparams *C.char @@ -231,8 +231,8 @@ func dmGetNextTargetFct(task *CDmTask, next uintptr, start, length *uint64, targ *params = C.GoString(Cparams) }() - nextp := C.dm_get_next_target((*C.struct_dm_task)(task), unsafe.Pointer(next), &Cstart, &Clength, &CtargetType, &Cparams) - return uintptr(nextp) + nextp := C.dm_get_next_target((*C.struct_dm_task)(task), next, &Cstart, &Clength, &CtargetType, &Cparams) + return nextp } func dmUdevSetSyncSupportFct(syncWithUdev int) { From 6d21b2ba80e274e49d67e8f3d88bf9b3df567ff5 Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Fri, 27 Mar 2015 11:27:03 -0700 Subject: [PATCH 148/999] Replace fmt.Fprint* with io.WriteString This fixes vet warnings in api/client/stats.go Fixes #11825 Signed-off-by: Ankush Agarwal --- api/client/stats.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/client/stats.go b/api/client/stats.go index eda9ac152..6df22ff3d 100644 --- a/api/client/stats.go +++ b/api/client/stats.go @@ -123,9 +123,9 @@ func (cli *DockerCli) CmdStats(args ...string) error { w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) ) printHeader := func() { - fmt.Fprint(cli.out, "\033[2J") - fmt.Fprint(cli.out, "\033[H") - fmt.Fprintln(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O") + io.WriteString(cli.out, "\033[2J") + io.WriteString(cli.out, "\033[H") + io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O\n") } for _, n := range names { s := &containerStats{Name: n} From 986ae5d52afe91da6f9e836f8e4ba2148b2c5193 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Fri, 27 Mar 2015 13:59:35 -0700 Subject: [PATCH 149/999] docs: remove unused windows images These images was just sitting around and referenced from nowhere, nor they seemed any useful. Signed-off-by: Ahmet Alp Balkan --- docs/sources/installation/images/win/_01.gif | Bin 14848 -> 0 bytes docs/sources/installation/images/win/_02.gif | Bin 29330 -> 0 bytes docs/sources/installation/images/win/_06.gif | Bin 3529 -> 0 bytes .../sources/installation/images/win/cygwin.gif | Bin 63170 -> 0 bytes .../installation/images/win/hp_bios_vm.JPG | Bin 37702 -> 0 bytes docs/sources/installation/images/win/putty.gif | Bin 101057 -> 0 bytes .../installation/images/win/putty_2.gif | Bin 40117 -> 0 bytes .../installation/images/win/run_02_.gif | Bin 209837 -> 0 bytes .../sources/installation/images/win/run_03.gif | Bin 55009 -> 0 bytes .../sources/installation/images/win/run_04.gif | Bin 8899 -> 0 bytes .../installation/images/win/ssh-config.gif | Bin 36173 -> 0 bytes .../installation/images/win/ts_go_bios.JPG | Bin 46857 -> 0 bytes .../installation/images/win/ts_no_docker.JPG | Bin 14168 -> 0 bytes 13 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/sources/installation/images/win/_01.gif delete mode 100644 docs/sources/installation/images/win/_02.gif delete mode 100644 docs/sources/installation/images/win/_06.gif delete mode 100644 docs/sources/installation/images/win/cygwin.gif delete mode 100644 docs/sources/installation/images/win/hp_bios_vm.JPG delete mode 100644 docs/sources/installation/images/win/putty.gif delete mode 100644 docs/sources/installation/images/win/putty_2.gif delete mode 100644 docs/sources/installation/images/win/run_02_.gif delete mode 100644 docs/sources/installation/images/win/run_03.gif delete mode 100644 docs/sources/installation/images/win/run_04.gif delete mode 100644 docs/sources/installation/images/win/ssh-config.gif delete mode 100644 docs/sources/installation/images/win/ts_go_bios.JPG delete mode 100644 docs/sources/installation/images/win/ts_no_docker.JPG diff --git a/docs/sources/installation/images/win/_01.gif b/docs/sources/installation/images/win/_01.gif deleted file mode 100644 index fbfc0a30284dba83a60357cf682120a6778828cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14848 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°vlk*SGp6qsD_X_QHo{wDr@wqa)u}A{HQ4t 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 diff --git a/docs/sources/installation/images/win/_02.gif b/docs/sources/installation/images/win/_02.gif deleted file mode 100644 index 16d8a688ff0bdae02f9568e3eb0358882183f466..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 29330 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 diff --git a/docs/sources/installation/images/win/_06.gif b/docs/sources/installation/images/win/_06.gif deleted file mode 100644 index d935c02ae96b00c9d089b2f1ee9e5895b9c29359..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3529 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 diff --git a/docs/sources/installation/images/win/cygwin.gif b/docs/sources/installation/images/win/cygwin.gif deleted file mode 100644 index d00445486e2b451f6a8e6573c27aebf36f39ea3e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 63170 zcmWh!X*|^57yiyZgE3>@HDr&BB1>e9y)0wjHOihn`%*IogOKc78vBw&A(b-L2pL6JRm>>K=L4YxH$y*U?4CL8-xeU&cn{Z0|JY% zLqtIABL61xA^AACd0@PPJUl#tf9A(6zmM}HLQ?ky-hn zMEu!ge0k&p1WrT>ZuGEo_7Qo%|fU1AKgZq9XjGqN4*tLT`lICti|BACestn;d*K`+|R!jbFWeM59Y=yLouy<=Dqw zan)vSRe{%gl4H|pVHLD%J<+L|&M6Ol=#N9PT4U>){nGlv(_cgt4n-7=#n!wEqpTz) zB^BJD7v!gu+o zS5*Hfr=|67)7{#-+K#*RjcpB&+8gTcwl}mtdf4``tE;QC^HF zd((CKkNY3D4z$({F5r8XS4T8hx_%r2j?d;OoYLsgi-M(wE0L4BQYr?Y}tGTJ_>4PCs4L^g;D-uAEJy>+l0FA{~G6+~^j`!@N}8@cne> zRas*&!HZZ;0M(Bd5k-F43g>zEgLq-hq}CnWAnbJJrKW?an%Vp0zObeAu!gY~->F8o zPhV^2ADnV}{-^k~jMY8vPnVh+9yJ&=V__!s8jQEPKBLSJQ}qAd>cs6c|BMu3?&w7A z;AS6g&V@dEk*$A2JTRv1!&JS^UH7pk5g&t_FWyMeDLLc$rXqB$Josnm=J;u2<5!$NzkYn(YGZs(`EkU5^W8_6qniIbiP?EmQ>FXg;Tx@w z@0uN=f{*?@`S>M|zS(^7Pt@AJ>Z7u&6<%j>VFxBrIO=9i;akcE)wn#G_sb?DVnneo z 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 diff --git a/docs/sources/installation/images/win/hp_bios_vm.JPG b/docs/sources/installation/images/win/hp_bios_vm.JPG deleted file mode 100644 index 468d95ef5a061873f0312f340b08b9b6bc0f0136..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 37702 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 MUYut*gMW?v57K diff --git a/docs/sources/installation/images/win/putty.gif b/docs/sources/installation/images/win/putty.gif deleted file mode 100644 index e7d418d2dee7232d79be90b1f7014b4789bb3335..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 101057 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

qs)VDm;#wGp`lsSG_f9#!R^ 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 KC_n%N1OPiq?jOSd diff --git a/docs/sources/installation/images/win/putty_2.gif b/docs/sources/installation/images/win/putty_2.gif deleted file mode 100644 index 053ad231fc95ce724fb2303cc3e83ff908c00921..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40117 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

0%1sX=H;T5HaYGSM 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 F06RcI^R55@ diff --git a/docs/sources/installation/images/win/run_02_.gif b/docs/sources/installation/images/win/run_02_.gif deleted file mode 100644 index 4243bf618620cd22e4ea8b6325d081739944e6d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 209837 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 z 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

c|QZ>cy{v`D zJ!<;WwxZh5&%rd9D&}_-93jVw3ikLGyGG7gG|ljmTd7nezIo(<=sobG-Frd_xcL?d zq#tG~+%YYxE5>^m7mjgk(Nnp`IlAS^NH$^Ui_4CAMC#-QP!^pj%S2u<^UIQ^CaHoz zt{)9Y_w{=)oO?HhZWew|*xfVb?_HU^$FfMe@g?c~h}v+(%N8-jA%wTN!vFsRqiL~Z zh^8E+%xre%&0#Sw&>XVI(_Wxmd=fSbp%Ue>sr6^xQwq5;X|{j4 z$cVq$6_dHi*=tkwJsB{)iU>mMqutY1T1RYezwCQO4cG<8MNROx~;K2Z9BmBioT2;zkT?~Fnj)$(f`7yY*eM?ulk;j zD(pa$PBA=0o!lIQM4&LlE1c)~cjB~*)`Sru1;9J~H1WcXd0eZtenBgdlguLeq*Y{% z>Y+eq<0?M=Fgi*uP6`SNtEHn@)ePr^blNjag8-8YIZoc1;oItqE=?H;IT_8_F>s>p zQ_UQ}3(7q3n)z$5Pb$2~td_&HXRqZ7bYH6j)sBEzXA)k`5c(+p2H@8lsv*M75Za$| zkYr~p`t*jSM5%^xH?AKi6s+@Pjj`1Nx}DhmFD(z_oIIYEJTnWnkfS3?V5c$kPcqb& zRol#K3G;K{u{l~`4Y6lOQXNMREN4nNAUa49WQfqU-%MIg3{)hX%wbYPbiHCkT0klD zsT0Sp1B}8TuN#{5oF?h!5N)Zr(CIeUS|;`LH993`(arwKEAo2ceks8+IwfNUie~gQ);RM*!aWq?6jB zU4u4tpe*y9HxW&snxQRZAAp>gXWP`XCuu-gpeOvz%w z73m>}zcU-Duyo^)&Ne@PHjuC|Yv}(QRuT*&AIfu}=5V^k%!5CmU}7qT!SIES=z2Ii zyCKgS7XQD1k%2b;jf8~8*jAxHP$Plm;cP6;D7BXWwe;__x12m95S=;QrlWl5-R|Pz zETmmA*-pwH)!o@mCO5NLvcXyIYu;*+j@VS*NOYiByh$B&~Chh zjGRIhGPb`WTG09EOB5pDHPc_4gC0}F}3!29Z9RS0G-rU#b>$8$g1auoX-q|gs zvmhCF`sBUnFD}=Q2j z6|a&^b?>i)X4tn&PU_6m@+E;QB zgP#vb>MqaHP|EE@Thk*5w*(1L+Ct#(lnV;twFSVFlyy>TLpRBl#*vgxzsB&L(-W3^ z^nI2mpKafpIqj4QC1|B@3kZ(&fb}($^1t#`H)$QHZC_rP;Iw{c#5S>c}RSs zO3?W0NCrxkq7Urek0Am%lW)P$pgCqL93s!2U3?Pv%A6ouM`s0yZ}Us`*k;mKg*ak3 zE;yE6;>MDi!JkfV3~FuAcrzOtY*jO5S5iUh^9|K^|)N_P%P)HhDOqizo&i)K<#N&PWn_c*e!d#k8F@s+a4~aiLGE! zZ%HJ*OwbZ^auwI*`tWhsk(-wW75$ckg-QLZ;q=MWI_pnI4n+nTy(sSjcbh6cY{-E~ z*#09W{_2bkQH`;_k17)b)_gQ_Z)VZ0CuYl%k6KY~tfPObe=x(v{|IWZ)TVY`8~h65T2Xkz`fLnxv0*WRt9fzr)DPSqm86)70#gY=L}LaXb`lela- zN#0DIru4;Id^xu*3pHI^Yk0qQY7WqUZ77?{zdw;dRPaFL_R;^{o9Qc1XQ)$BUZcet zp}5dZaVSA&osU{wjcB+;6J8QK6X1375f!EIR<)K7VhPn_ueLNy6Rt`V zgx2zW4lIfuvTV)cL*-)FvdZJ0eB?h?LOwH>^%V1StOXxcUp925cGvV?Y*PugWa;J_%B|j2zgdjv_ znwlCwxr@E}LLXUD^9hboG;V&8LMt^=?E5L{VhJqtmq{qerL1L-oT~e=1|+$y^pb)s z)+h|9IzD(^b=;jJlQEsJdGtc$`&MSj{#GlM-j;$ZLfGE%1E{$emJDO-siCTiIm10}s;2Ck43dp$@XRm)=1@73F_f#ibu*UdaQ+UQy9B!ks{6|5m9 zIrx;Z^h@@>VL)|yNw?KT%xZGH9DgoQ4#>0q-|>>1{N4XlO7cHc79M6#TMeW_Vvz$?SgWDbqG7`W6qtu-YR&B#|uMZs)%=6ui2RfYHzXS_edM*Q)3I8 zF^3o9@C{@F09ScD+*j4pRs9UD#lr@sS6L|726My3jla$eB#iyHU^o}(mE*bP)FlQKw-{h|QB0PEg4Hn=56M<0qW%=NG)(dRuhf`a~Vo3`qM z%CtD~8xGPk!m6M5W^a@bWSupgl1(0-#myfkp?}e}_h5>(*(J`)tRFxFOp8OFbNs<@ zgrEQT*X8OdFv$qKmx7~uqf5NIsf4rMgmGkeBss7=QY^!8epbyo+LV6T56Dyp1Lb}8Gv@%$p-AZek=>=o{ zr_w!aZ`TTASVY=tNTGjAxut{ckJB_qtCRsKgThCimB6z7^ru-H7n3Xh{rk)u{@y|e z2zh9%f;p}>amu3m+op;*(`6+i9Q$biH4tn*!kAjcyG-1a73n!Eso*v;mM>}w143$6 zgMeZfO2t4c!`A#UN`U9XR!oK~Fo7z}7WZoshRA3m1=fuM0BFplqD-9QyrpGvn@V<@ zeV~VV8KuI~-PZuPRnx;zc7ke#6c)^b(586Jdb|i^*EYRy%bbL3atw728U{uPIy_&H!F;t<)1!ZCmnZ9;!QaR{Mg6`z?^0K&rVZkK zXxnA#T2hOYCCE1%qZ*!H{rY_6lI!$r^3I(gfJSJMDUwrV!GL1c%OV=MT)hk&S^J## zT-|WhGVVUx=yEIz`|L(6wKO$*ETb*B>^Y3h9vg^n;CP>?<`?z=SQP>WSkBgpFF%(e zh6@;b0zEiT(5=USD5{8d0Tg|BJEw)3(yv83b=x+r48w>{k=%O`zeVy~5}=|=f8DRy zqAP^Q3E{Jt%qbRLSctjKUqTr@i;n$bp$pgk$a-Y{m&NJ?Esw4Tq+jjG>%~L9^+)qJ zE)F90x@W6g1mJupOO^x6gPJ&2jxf3Fa^vm6y)!dJ{QX_VF>$XT6D1ztJhyh2!n7zjA=CU$6=u+Y5fGqb3`#v>JLqBD zp{@jEhwL71!kFdM5II?r*?kJM2I-^Gg^pT%zd}WplcmBunn&Pl7j&y>Y$RbNjFrp= z^kv#BfaZGw{CZ>>`M}Hoa~-~`^z>!{FZde{66dS{mO{3s97G!NH*(!5LGB9A49!T% ze>bt9IjyO%PxYM^$BI)e-8l9rQ&ywpV~Q0Y614RS-%`m8?+8pNFEu0!@RIP_ZSrZ6 z_PX4rgg%fF1;Fvyv_#=k^Jrw2a3EnYvHHx&Um+y@ees->wWLFOiBbTlZ?cgiF;>fkIfYs=dvF6#%j4i zZ_keC)RbO#X5T>~f-0r*ckTdo=M=iVClHz}N%cT2*h|C<0}iY7(!5Cn(I z%$23C$NdCZ5v!+k~DwNi5BF(tp+U74u0Y_D@3*SkZKFZtwm zL7>QI)q&|S1gMl!DELmghrGI3-`8wm7N;jF8N=G>;3?GLi?5IlN^!`+<951o-7ka6 z|1m39jYJ4qZ%WA3@#?cu@D{m^oF%2$%Q(!EP$-SMoQaN*?4(IwGozR_$@eD8l%t>4 zWLfN}_vW9m_)XrYY`(SD)+QExn8?nl3y)Kyl>I~YoumNivFkkAbD;4l|c4TtbN1%MOAiK2|Unl|JM_6&PovY6w*I@+{tCb^--LVVkDUJO#{Q|0Wv(scS1rBKWmKsc z;X)_kPT&S@(reLgKz9aWIFDPHxwf3ihE=03#((bZwrCi^a4_!=+N|AmI_f*NOCh37 zL0Y$soWKYZ?Q7Pm7XFUxn1jMx&Ew@gz)QCZ!_q*vVf&R%Q+(-#9_@|xGtN<@i=Jqa zdoFPGK!Q`g6~*WM2Gj+U#Cl$k)Kd$md7W5dO`W9>I{snRs-lW(nxpj4!#9b1Rus}U&y z@4khG_`;5W*x*SlQ531z-!gRJPEAjP)_THOj@t|={9}$& z4#4d%+2%;lHmALa*QQ)T#{MaS*j5Y~joLp?pZv4;?m^A4ZbJzZLunf}KEh>FqG&)_ zm|m&thP{jqWpBKMRt(rVX_apB<8I0;k1sw5LrkQb z&4w9A`A$_6O0%!7TRfc6vlE>E_;?$Kuz7RQz9vO2rTa)5@iz{Ff#@D2G1GpmipenP znT7cGd~#D8qlJcf)u{tWm`AV${9bD%{)rhkv!FyyS=bdzfq=^y#<=Lp^2S)c}fgV&y$_dXb(5eCKx>_Y&o?MRHm+}$@`ys@`y0eV6~JKx%x(> zNc_GVB8n=5WE@#Dm2HjYn6tvqPStQ2fRpKbhStPW;3`MP;t2$R_2@x;0!NtJxHJ7x zewpUiPmkSgSUBlVqGRWgK<#r1B?1L(e^5D#f*IVu1-9G*CuCF@!!_+hG=*-(p@!yI zL;3~PN@(KikuA=51X4WHy?V;0k8qfnau4=~ehR+Gv-kL8V87Hav3Q4Is~R z5+58i3pP4H-z80F3L6gGi`UoT844eEJJ=3I^3>aZ5|N=bj#;5Kjn_~cv9ye@A=ugA zS5%%DibTEBDI%7o>(6s&YtKWa9d!{%Z6-7dVQ(UOme2&7=v^hK*2ba9M!8DEc0oC2 zTxB^$D2(_#oR5T1dj~qVJ6QWvc~!~oxHlN-7Iiw2iX63R@)vU~J>DvxO>Y6=q zAMP_l93lI?O?_;P9;T(}-KXC&$H@$E>W9p2k<#QknDsAdnOHIqa?YQbQ3+d%u~|bW zy+)DP$w+}Rh~H#MO}ZdC=aK-s!-;>xtks7!N}i2#X&I0Dj!Gw%rjQ-@?$_zzlk-v9 z$R|fmqIn=w$Nhd;OhV|@+ccZ^?3jIfn#^S6$~ z9F&R@=s<51MY1TCPc8a*>{{fj#xU1Ju_>>(2L_HtiEN_v_jX^vtfMzs1NcTVC=1M& z%|2LEzv;8GuP-pQ#(STGG4m5M^02MS;%FHD6_2Nb!o=1I|8n@q0aCZ680EIeD1(d@ z1?x=}ZNCR<$qg-;%MndURQoD={>|ec(sH(ey*M<#f7^+CgSdN7?L&r+Ezein#MOSV zbM`!VA>5g^Xmm_RBUQ&BmGvA;mt8DRHS#Qmeit_Co^O;iB6$0CN;KUI;2Y=P222w` zPogzkF>fmPfKn-`0HyNq1#&StYQ%^WdMFYHfB*>S+@5+QDN0;->0Oz5nIj!mJDIGC3p*uYZYN=LWaJ7Z|6lb~gj{`KlYdUIKg-%G-NYbwy zLSSMaC~j@drK1$JOe@(h{kF)zP)!2~xJhG9cA93UiH6rl_5+z6U#9d;t<5x;CYVGN z>8vZ|q+~r3CV2WjxWx5;b+@y==dwFISWj#2PS$A>J&2!@v~9&l@XRr1yeoWW14C&u z?#3#G$1tO@fM;uP!$n`10a9M;kGOy+&)p`?xW4z4i1QUd9Rp&LVDRjQVb?;A_Pq@k zF*5?>e{$o1qBw@eWe$N1`*)myU6nB zsiER=j(`vbI~ZNcrUlShr)OK=Q83Su!`$B8;cC~}oiF}*GcPoOLdJeT6bR44r2DrF zKkI;4V?aw+67Dw1GPnJLJS<*>r3Q0YblB_8Ni%n-Xgks1bh0Q^cIl74#0Qi zbPa}SEX+lMe)0PcZ$;hR^1Azi?svYRT3yBvIMSU12oXq$UN#QhAAFsGJNt0Y9#754 zAfh#xx>>+y!US}ttmTD~VYHAmfp27zASxjCQNcaO1Il}C)|{xt@;>elK)_gn)nt2N zc>=`s_V`at8gQf2;@DxVdCSGA9%_>v_T#Q<%mY2Dk=D>Gi6IXgy< z_cIIo7A!cE^of%%l~@>_aXW%3{h5k!*UTFz?%syFN9)CAQ&aQ4RV~|>#hlt%oqGJ-OjMl4 zPgWF~J-K(5+mD{C!4eq4!8iUe_Nv_%J^!4LV$9 zPH!Nt-{}&goP;BwtvfVGqj%#V3}o{mB`*$fpXF=r2G=-=yoDXu9aY18b$@&0+srf<-+4slp$ z?;+8;P;|(~xwPfc2}7AyJc0uk1$UW;Ej(?|f4W(s+_mD+vTs0$6>3AyMuX7+fD7#2 z0;Pc>*>_=$D(*+<^4$q;BZ`gtvuV?gC@bB0Q~b}59x-)X1oH-SQALXTO79Z1V@a7U))oFORJZ-43WssffJan#<5?r?PEc8$t+FR1a{C}|_ zDCe)2d5iy$tS|rLP&psUZ`dGjJKsXR&;4!a%fJ?ZD=_I>$IW^piUjO4^( z=>qsK3{HwS4t|4EEjT5;2TZ2cyig5oQgN-`+^N=>-Ij>3Dm^#eXAjpX6|oWfdAu^P z`xx}BTi6_fjMF|4WfwwUSNvk#0SCC%W5N@Ei%y*ovN9___-WU)<%>B@)%alpd7VA` zIQ|_1|2W41Tx$FgBHi-W1H)zSzfqv@uC!rA(`%4L&E<}7tnya04iApm94iJ{^#42J z#6M#7gvt8#`Wp|BlKTLLGB#H!=e+`UrNN@3iCe#KF-YlsYO|+kKskyqXP=c`?yHyI zzzz%t&GK8@sRsqOAzwv%!^fQOt-n6KKcp{*`sqs}X7Exk;m36&!JS|0p6NpU49klE zB;g`cluqmSr3D7*W4KjoTw`??^Vi&m??IP$Y!;ur@z*uyVan;DkuItL5|7!r1jTL^ zb;i>|*8zv3U5D+S_-Nd|;E1JnfgvvsS@FO$jvezqF@W5O?r-)7F+*R;Ontf%9Y&>k z(L*@tWbjXlC@fv*5FG#D3r@v8#z-y$QkL~-HKTP8Z1B=@Y4Oo!;ONP~w5f)ymN1h? z0%tV{z{I`xnJSJ2iZ1YG3pgsVNEq(k zHzqKi3gEDJrAoFMh~e_iuN&}{KDgQzIUg41rzTw{FUF`Pd8iPQ)R~19P^Vr2@Ie z0D>b-gY?2ZD!~S~dKG)nIxT=C6UB*Xoh<~s{kTyem4+lE`-7@T#*h|8k*;!9wh%D< z>BB=SoHeZoHr<2szA3D#4SnMCVltiKsiikYS!?%jeLitezNa?PduuQKcHxQzo6Q*v}!~| zZB|d>;rS@(#p6^62yo{W(o}PliejmTGD-TFAQDI`QvRtoiN zgttm_`(oci2swS~5|m8NqiI46GFQ6^_HE{KC*S1tEu@U@2oJN`0ygz*)OxZM52}K5y4M0_|Mm-A>5!nlcFJqOQw3l}55ACg@d_^d% z8!}ZTpi|3(gP2FGT~YBeJ*ow~ddCh{+)}T*38^u95&^Bm?-BuRb0Y+XddX)*y36QJ z>Ob%VgG?*!v-uT!z8BNOzFp?jWdfEWX1k)$nR4-ucIAa@$}***#29C? zII|esQA3B&KdP-mtRC}n8bf>*;F4GRUlirHHRr-zLVtz@OLcYpP(E0wpUVEAI^H?K zQ0N=CWP)H8L)i_%k{B<&!Y@SEQROTLJ?Tz4kt*xmrpNzO`t~b3;Bz%&Y7`m?gW}n3 zk)=eZr#=Pc6}xGf$yC_iYV70(3^sDW9a@E=KQ_~?wt!bis9*$?t7x18XlqJlym0Y(a&aG{a`%yPXxeWW;fiu+eVNb?K@ z`?tXRU=OO(iv^IZI|A;%<$}^;m_$-R0VChfR&NCH1V!S(*b6QI_M5yAD@IUDF=m@S ze$+pbvzDNeVkdrflIrc}?erWX+5@fo;=tU-*)m|jb#r7d$lf(85^dE%X>DL<=$IF# zDAGc(F@6+Oo+eLzS6(ZP3fAx|OeVZ)cymln5#&|T$IUB zH;a9Q2m{z^yPSPv;Z+vgo=zSC2|0*R#B6QH^i(u7;TiLAdX5M;fFd}f;+LZ+($ z)o38F~t8(v7$R_J#mGpY?e+B=5Gzdg=HyLKaspiryvz2RzF*it*f^VJScLf7sI zd-}r|E(nxLpCt_%AA|$E4mLn)=q8@Y>t;o|T;nAFtR*BVzw-4pK+_0(keO9hDgGv^n+17)x4HuKb1kw zb!Q}%Mk#EV9Kg)R;p&ZW?JY^0f0Jft&Ynxd^8WXu1Rn1fx~^0JwTvo(zePKq?c(H| z|FQSMWI2VIpGN3^E)Jv8dfP&L0|t$_2^+qlp0CiJM(NVl+`O4arn;ONFZ({C zuV}Fwi(BmN{P5xQ(!;}c?(2B&l@eMq2I`M37mznJ|6;B#oL*Eeme_LsPMa?%J+)|8 zE6&)yuj^}=UTCN(b@MXUda$CAxUf9u0^OO2fe@an{BHSn2m*Xz{v;C;%7z*bGes)2KLNt$x&f}9uBLVOk448aE zFQe(x%C!TKHL8RN>lnFCvgn4ZXu}Fh0exn#FB#JW$e+j>v1MAW+KPgv%09q??Xb3? z(**eY6!_K_F7oGEuKWWo zQYt1A9XiyXU3d?GymBu5joRFceV?68=w$^mbwIA?!?OGJ?$fcc=P_1z3DvVb0ZbKq z|KVFEIEd)+&^{4URIdvel#zY+h)*a_P49V;wCAaj;rWOsNa{|-UJQ(Yq!|??j{{(O z)}ng=2C(nIXufNp0JK$rKBMmp1lTR*C+7*?2u9v>tl&8U@`jk|x4AGsOX0_+QS^b5 zmXX#xt_?JmckK1Ej}mo_6j(Xo*|q%0cz!y*MK{p`eOE-Kg22K-lk5?E}K0Ky9mJ!p;MqWKk*qkPCmk4qUyS zkH+ig2h?(bE?~ypf-cr(68}HxU1LY$%U+tsh!HhWH<=(>1xc#$(|xq2X!D=SCnp&V zR~fdJyY;4~eSc^dn$P3~5emADT>INnk59QSUAXrJsh29HmOQw_D?m{-%;*!i<&OO` znPip)@yqeLAd$qa(Ok{599S^f&ys+ksV(c3kavx>Vt2EwsMc1j#^5Wi1Z0tPlifAj z>&sw~lG3~?qR#-vbJW5>F+jARvF(zgL^8$OCdv?*p{VdU!|X7!^S_G zFbqbr3?wp%dp@e=DrTgW7t{Ggom(bq*MX8|-_b~*P-~QEHXwReL0!dLFw5gC(h?$U zhs$>fb$aqebmQ{v2wkNRU6IV>UQ7K)@?0T9?6+j7$D%*avl6gaZYU{iQ3p2K^pbS+ zZ3xQWrJ4h-AMuYgJ_tWZ(b48LHBcY?pYP#| zHg6t>blvt4o`tg_1+_W@nW$$*e9?M$0+efRa*dnwG)~IiGzwBk6DyM$<-k7X=rc7Q zP$0yx*G#J1UCS&c$aSGo54M2kR(p?NT1}lhV?GhAJ+zw;c64OE@*bbbw znq-Q9Gbal{={(+5(n*XGjsz68RaK*%eonlQv$mI4c2mmf?^Qti2Xbp=vA*>{*9y|G zzvQohvvzHZj*T4h?R=C2F+7f8RVd9A&|#h2c`6MSqQc7O-PGX)UCGHOUF{4#jQyc_ z2x^gd6L8dXB-AQvoC)aTRv6%%7e9Okx_d0dg)>dKZ#-kJdsG)MM0jUWd|9%bcqK3( zfN`lQF%_gK1KLRmcib6 z{Z*Y#ckSb?(){mBD3odkgql-PJl43q0H2!YpPfHsxUeo>DA_#zP=RR`728;~LeP*8 z|4F+f(Rque=-;f9e(X{GswibdxSg+-Es|vWc%9Z-$$OHjc6is0wJn3fbmUsfe#^Y-wewBHBnBBO|Er9}{1#wB{||Ppp%|6? z0zy;Y(!cW5Kxs=%vhob|OV4#`nOMF&P|)}q_@y_NW&M?4fUb>#Hy3y8U7z{fc(;<) zIL+_1JWw!a6HFaBa);3kpz-MDdsqJagIu0LxkkJrpT9?zu;S4Vyv(3nQEfa~PzY6$ zp6zokx6SgP+a%||?^(1zw5=>g%RUN< z!GF!U8qX@kVKq*r{}7d49_bfL7rk7EuI!f~hHpz!3R_fJj^w|X88`Y$%vLCGG>Z4k zg7EbW5|t@%_rrNYGb-M3^^HoFC&!R3BFe^uiliS6Pvj z##9|7^cc6rXC}ctXQE~K)_Fb^s9qPZMf)EX&!$nbw4Z;G51`bLoq5(&93|bPbl?PBB^9e)}~MN87-K^)(B(Q;GiMNoJP>l7P-JTneSX&j}_B zCOijh(Ruw5;?t~MJrj!k`HM+%T^2GNu?CpL2F5?)8CLj5C*nielTRaB?~G5*(p1^5 zs^E2nS(yHpc9%yyI(!w=QTA%O{8mQy*lE}=aLdo9!NOcLkNL0k+}mBy?WOurqmU@) z^%PT6MR$ZIszl$@deKcXw^iNL6`(MIW+(^h!rv63kfM43*I2dqPg*FUuSRMw7-Cq* z8~XI>#AFRfH)2t7mTxC{mfwMc-_6_6>49d z1)(|L4wz0xKc^qxUIXx^v8 zwOWby?^vFYY0a22sHx8IQ>}=(EOMY)M>vno$OIi*(l2&W@X0ovTj8Ns9#84N%Q#V%KEQ_+ z`gW1 z`(##0ajf-F=cRYJ?$EW0{Q|AjI6-%bJ^x}P-XW_wYP(vc-ZxP4r z%kax7A5w`ycv1}erH!MeO68HRqHkprTk?a8WLB+e%}Os;Z*+wFWICl`M8!)^*=g}j z%dUccmT{jI#pa2ov2dc$%z+NWkN>0SyaJN!`!EcO2nq_~UO5qGsJKU{xEJnK<`!2u zb7kR3!7ZY>bFa)?nUxjhHn&zC(qIU;QrnBbxkGKsHrX@Fo;O5Wn@60Di};*Es)C2X)Mx8twfXH zdi()pj(kjYA76UlY7N;s2z*ywmDRv9vuexMoy46@*qITUl3wD~3`bYCtZ-^t?`Kr&FY{@HfvUN|sPWCf)xeyhDz#c@=ObG^9ouP^pYhhWs)=cM}p8x<)J`3=g zaCbkQOlQ;I7AOHVjUsS!0Lm>ZRwnk)*JewGH;)X~zTSWj=Q}uOyoKdkjz?<3^6cG( zB>>75BYHKm(DEmufcm|*$@(c4tiLf-DWE2J#gisBCoN3CsW*=}zs-E-AS=&exGPvT zUn|=#n9RTa4lxdc9wKPDk|Tw{`Dwj*mh#Bx8=2GCqzhPFh$&STX#a82-fqTC%_k{y zuRquPpZ3Oh>AV|bWcF{|LISHQgG%Old-Qf#mhV?*%a-P-cpwYO_|$NM-&p4YH+0?! zjm!-T{m^;Sh0*bPRq2rxH?7mp3foqMERtRzD;D2uL))IEKgRmPA=!T zN)@WevD*-;FD9itJX~PD8;Jd!;ySj|Y);L*ISJg3icSsY{CK?qEpBM>(Oyc0@w1%m z78hQp3;?+2>(Lg>YT@x1!W7tQw(H8RU=xfdemU8_iOKtvl{ z4l(tGbWwIb;~LXIxsEcS6S>Q8+gJz_fRt^N$;f7jfT}chDha}x@vM;5A{$*VY|9!0 zQlnIVz|K+Ub9570hExft+;0LlRStc~@-0!$^vpU450@uh#r$m5t407J=P+Ow4cmY5 z2i!vY)n+YPa{%kvp-D!rN4j%a@-jaPl>MxQZyv&Z!naL3^9tk4!hYdH)UDk6WqT`&9)`D+&M5stnZtm*Zkp4mv$d zcZ?2~%KsKIf}X|-0&KqZkKDnUTzpzI^ct=LBYR1%>>K zWGpMQO7lJ^-H{L*P&k)pss=%_H3_2gyKx!DA(9mPkYzb_e!}p4t+JE8FEkq8aXw!! z_wsf1r@y9h(9-^y7Vl)F8wvyUHK1dWlg17~u#aY(OW9sG&MKVxAuW1`3ikH;pS3uREo7lDGLCX|3%YV7*}Fz` zKxtGP@~|uj!H_xiw+P#WVr+k(CXg(=8}p|Yc;0`iM;-WR-o2*J%nPDe+S!nBH&;FM z6?;uu)d4K9`v$B7vDtrN`^KooInd@CBO<*J<&{LpoSp7d{q%9iu#7O6+4JB`u@GsR zRpV_dT>E`pp36+L#$UvEgj%Fkijp5|WFuD#Fy*Co(tT-@!PYX@Y4xrZ#48ANYGvXQ z&yAG~)m$h_bSBSLLo4w4r0iK!VTsYUrkHg!_sj!PiUSZNd{35L%kkE&52~KZD}!8G zd1FUW2F1Ugec|jUd4Do4>3U#rZ)||CXt@*;sJpPkcHbzx#y$)32_VjnwfH{R>#t(w zXxwXrW!E<&4U>~Er}%~Gy;=bm zeHT$5{+f1#dl*uxU$w4PZ^bUVJS#4}c=}8|drjlo)P3!bW-&cfi_?CXs|j1L__|MQ zZ*dxs3jjdoi`=JRg5MTS8_JsC)og;bq92xu9$bsscH(K?fWE^*X?`lZ&1$wNOXqh& zTR}mabBD9U*ZFDrVW{ADmJ2^ z6Op`iv4NV=1}*&O9AtJIs?ysSiK!B9&mBL+;-ZEK?hX5YR;Mq&owKYb{GwcFDS73g{)~F$u^bw#7 zn-Oj;Qc5^hV3##hv)ebPlt-Isfw-~lJ>n?(GE#eYh@UKp_mW2bQ;4 z;Qzq-IU9;PTRbx_U>09bkF{x74d>E4f));72PinP?QFU)aSm(wCEd4g*LnCDY22SsaJJ*AHC7GM4*QQ7<%DW=(d z#E+v0$qzzn)oaIi$EzGlIw}=49zB7f9muLtlImY2rM3eYK{)6JpV5$thPH3%0l&De zQh&)MyB6qT98N7B_r@1hj}H|e0$_$PSB%7E_f=m@&_M|23V9*d^lCwYa;94I zk}?7F+-vh$cZ@}n*t!1J2EJqj-d$j2G_bS3hixJ>+IGbPicp#kHM>K(_+J@e!|EI{ zj&@n1xG2exCh?X5)T^FT*1oPwadM@Sg2_{`b()gNu=(1kg=!rf72|BO<8-=RQlgzfI3a{kf-@5vC2ShA})MW&IC!ARc7-^0v_YEyi6V4 z{8VP0Q@zba&o)RNG~vKs&msCeAFIiw7C{XaZI6xkOWnn0&@LCH%KrtLdb|_8F~ReU zDIY$o*~jIDtaD7^8_G@Jw7dEo0VeuItRU7cqt4LsLLIV1bMBSjymHx{OV%1L8RnKj zx*7`AA54|o&@6lJSP3$MhZI+z21(E*%F7E+oq+;$qHt1+e7p%}p+VOOW-eETkn0|P z10aAoUAmNo_pdJ^*a@((87j*sdFY6R)$zV5YGm1W29fvy&QOxWovDS}huuiD* zja8ibyIvbFZ{@2XYk%C{lWlX?E7qp~AzQXzta6LUl z&Ae@ED4_R_b5&DVp|#j#38Tr02m7)PQNS&IjpQd#DED|2CI z0e8kp245WaDYd!yK+JdAh4Gtom3PdkQQmM%^}up?4mMMt%JeQw;z}ul{}QBBHNp>D z+`>Gp3cpJ60Iv54WH7X75Y?{OpVHz3Dj$17?w1DJ`TJR65bo^BtZJIN_ap^maenQh zi_j2x8e(NX{dC(bE-kn+FsD5MvHvVuh%=1u8cAL%%t-Ev8TG7zwsuKSjrG)7J-XK? zwNTi@OU2DRu6M^W^ycwVkr~B8nClI0@_AxnGa3;~;O@yHE4@AkxBJ7e+E;p+l&fZu zz$Ja!#pg)FCpKtb60iHcXo0y2(^Sz1cbYYD#~HXkl`Da7^)aVzS2^3rwq%=zQ^?=QH|e6l>tZ*|xB{$B=n!U3zTWN>%T zf{y16hxPE8MBU!|g^Z4<5p9L*w1cA0~>7xB_A{yIIw`uJq^2Jj{u7Hg;@rmXtF! zJjxDlP23e-D~oueA%^}#E%@Vl^#<;$_a&=VXCTuHD(VZ!1(wxiEi{bAMjZBjt3cX} z+h|TfcNkFIZkI&5_UVdPF^9(r@_;Ax0l{+3%*#73-4id#@>hJ|fp2YGy0Qqk$1!a@e^aG}j{R(Yt$Ow67e%b z%oIW~a`J{v)X|l}pMK}Xza|3;p)R3?E?ZHiZZ+bY5r#t;Z<^+t@t}7nh^b+KW#!wV z93k%YuCRWuoA@NO<>&WVs+)RING*S9 zn@onI`N3J!d#ZGrLP+QDx2m2~>O;gT7)dA&oyDFS{+{MskQOWRE~i}}#X=(!Ps!so zPhE2r!y3WqkpOQ;xz-C+Wm(@o*yQ*+D1-l{2_BOsvr7_n@11hEk^TFViT!X@W&B?} z{1_mzv+*Wvybah17Q!RmGBg%@G4NQy{7KVsPzQ2r#C99@_5_8E)n>fsQ@;~>dtDSF zaxo32YuX^^(jXyFq-FRGtHE5**ZEohsBT=6RzWs~4;`F*030h-a%`yu=ZLN$#`5wP z^4JBmfT)ovA$U4;s1UlWAoR_Yb8Y_w$D01~wW@htjL4$AdBc=wFH&YRZ~KYBh@XTm zOW_t)J;Ek(6K=nm&WPC4h8RO4fWEV{~!# zcf-ObLc!l0EBN=w;l6EQiKnYc!B;Pd2eE!WT?%Nvo4k2_$dl8F!HV}Zl)S+0tK-^e zazkh`UkOpD-{PH-_yCHS=D_!YP70vu^<~P~3Cm)mbWU7|0)Q;k#0sts+3F(~%K_LF zjFU-)jYdJ!L}R9jcDGb&HrUj2hp^#01*&l7nb>Iq07>&F+Sf^JqYZ_RQyT|h(20PG z4F0YWSe;xe41LnqvTV}M1}GWaoRBY`u-tO3S2_{Exzpy$G|rLR8fVK~fRvboJ}K66 z-SUW${X2D24Bw*c|5oWjEg(RZQOXHSSvH8EZ0sVO$4_vo;Kx4=K-?Z`XDQS8faw={ z(QBq$y5spx$S;U!Z+!N@=gQ<}NWnL>_u5g^&Sc5y;MZ%%F?$P5_E$Uaz0tYy^1aTh zmRslVU4Cuw2{nGMvL7S*-;eFP6Io_=RLe^aGu^R9C480gU__#!9H~YXs_F#ViFi~N znvQ8;HXaIJ7+{G_qoATF74`~t;8u)m6VW&vlMXA4JZ&A2tkm7pyakbEiuYm48g^MF zifyVAIlA*MTNe&caho}S3gXsCZH$f1MDd*9;*RFz?$pi`fC+7{j;o=b2hgCKYpEFA zm4Ufi7~t&K*}AM|(c56RXo<3hLL`YF5>i zDr(%R(4R#m&!WAb1tGb9fBiw>_`Cz^Ne3gEWXu(5Q?=ks5mhvl-F;cgQ*)WX)Xqd? zP$of83XDtwso9Z@zE{El5g-2$pIi*#sZ|A#^(V=}B&YdCl0FWDn#!yS83)<`CvkPz zAhqIP7zWR4t;Pdz5_q>`B@0Y5vQgpuQV*|~^UEfpB>dae6-Jmp9MDAtQ<%9Mq?hyO zX>TgzWsa?+EM+4xiO1{K$Q+n+j!N?)Pb*$#3Y(5dQZlBos_c(shbnD&W$t*kHh5Tjw=7uF6HD*YxQq@yoQr`n5GlfeTxIYw2^OMeEZhe^vgKh z3PkD-Fln_3iI9IPrbPy*hOp#HfVe(KDLhV)_B0IQ>dHVd5CWe??dTdPLV>_JD@^6* z_J`7yvuzj_>)gTOFuPgbuvC_2jSjNq$gf-LA(Hj7Z)*LgM(E{<>Q;b)PT7R9oVskA z;!q(|&3Hcu`Ot+N3~2?FQHwma!9|@!P79yaLiL?sj?6NORRGQMbD1x7J+(_IT-SU@ zvsb5>aDXr2EO~YI$XIafz_}td)9`G`UD9&ih1aU4XIdAyNfdu1-+{}STm*>w8K;yc z&zni~&)i)hkdl`Ne*gCt!Bam_!q=l%U+Js9?I2Ysys$pwhg_x?CrGnAk?8r9# zboD4*QD>Z_URQu0F=)z(5?WlV>q3x{m3BIU@#N-X-l{hHL!KqDN@QP-!qnA49=pqM z7_zTR)yvAB7A5o#%=VXv+KiF&%aIQ4-g;2@*Q=H?Y_Pt`252F9^vQ z=}Dc+2uMRy4yLaRhIst#4@NL9+sj{?Vq#3C)#$m-ZKdL}WZ;ub-qn!+0PF;;T)TMv zbwV3JI)>BMKo?+NcS~E{1mXa87o|$E)k7q*u_4V)1wY5^IyxK)s=o~=g>$WzzQhvV zQeM39NRfvIgk_|>2~-!-d@3$r1Q$ST#O`^eUFLKU!~(8s5>ikGO)C>YaOt69NjuRXdjZ?;|ToTWnQ zwPnMD41Y`1FnP5RPi_){^9$KT&5Bc=E&14|&(()!dZokKt;D9(mFs>=4C{nU8JoT= zQ+TF@cZndK8bMsVrI|44fnl21g;u>|?00xlrvZ7uf^pxBO*iFPZ4CUrnL6nRo9;>1V#k zu6iLMndcT+tPX&i!bs?vgM6X%DHh_TAcCEWt3&AvAbokvHs6hS<3DeuV2`Jhsb&Vz zck)xFo%HVUn6D4U)EsuSoXQyh{9i;k4V^qYDK=3l#rit$_liUxC8yw~g3OZgM6=Ox zvs27(kvBmjH$NLs0GI|jQ(Md0=yQo+;}ZcG;nnkc#AIuRrH%i!ieICK6@)B7#2Scf zrO?OM41^^u>V`<6V09VgNA*IX3`k%3-vhhkbAFAw16ME~K6ly1tt-oUt$O88#_f3& z3?`a){hcSoPp8*-A6vn{6v0f*ta!Py;e}q^PxEPO9~y|Lk;UJ8`2jj>_VPuZBW4jm z{8mV3UssA)+Foo+)y2M$nY#GD%T4&S;1YeW-R;mMG|OeUy#Dp_=1t`&Z^eP&3zlSGuG-*qJ>1G2zW8(F zp9QKbE{rmNQr?#71n;=_DFwZpYqC%)qM%mSFz=Gz{Ot@MNRgY4%gKD2D5&_jnw{Gq zp@S__Sa}1%*mHzFEorcS@Zn1O6rd z`}>c1wLc0h!qJET}q%_L|$uKX)MG_*dd{V#j*^n0@qeq z5vGE(PkE`FV2fL@968V$wgb@1qtfOw(atvBZ>hWkK1~VP z8dvvQ=|oQc-v)rfyGy@m{StXwg8YFF)Jpsro3dJ4ogOYBB(4V(90`ePrRAw*KSERs z0#U;hp3VtlrB!6&Q+QZiHd|rMU+erUta$xt69Xl_`aSjw2a;Z@>JL}tBCz-Z%>+PJ=u>h zKW4P~E^w4VsawVzTv8R6a8Oz8Q8$=OP*Tmc zYSkZ}FJ1V`0glS-M@;#%NVLfamJr}gm7+n!Q}&S$(oLlIQ^C?v6$T=-@v~?bHHa+F z8Q596SRd~O_AY1=4g_S`AewC$jpGZT9}c5A*4h6gY~$*yPvE%Q=~dA^6v5b*wrsB0 z9P1w$)>o(bj(~aXpPB9hd}VJ*n%4)NmcQebYhzTF3|@Qo-8xZ)%$b-sm!W>vl@;)?@I={nE{9q< zCQsJkfIXMCNJb%mQrHtnz|}h3x+)~G-lOcR01&%*lW3yGW+Fnl?}+@~%P8ZB5?yq9 zA`dfAnGbEh{U=W%j6+^s0T^0m!5qH-`8PZhahg?{tFA%O;DhR~n>>}F9O?$o`i%Pb zSCD6~<$KyXoez{__2IiPFXEwEfFCJws{?y~ElI(radw$+ZKxuXRq48&55H@z(K^+Q zC`Cys9U|TPQrBBL1L>U^|I@;=kFq#KY~=VM0Bd}cb2)|Ojqn_)OJS2So2yJ z<{NkShj%twshQ5GM0x6Y1XfBW+ajL2I<4xFk6oy7bqvMc<28}N$=mMK%%l@Y-GI|i zM*1ltM|Fc1aZ}Ylne(fob8T8T-=Hhet0gnty(jF(=-Z zq3W$}E}8x%r?%&d?SbJ6%ss!Gtn;~le4lo|Z=K%B3qX!@Odh2P{*dUF1tP>UAIZjp?$+ZwlEt#7M@pyEKSyCHNCbeG6Y1VVTdTMt+c{}V6tH<0+j4nx(BAA>_tN4&*=1!miV4Lo^ zpuMKw_lP#kLKbFwWTYbwlh)e*7;9&Xf`Hr@)v22lK=X47A<=)*|8JWUkT76XC75^1< zqnhoV;`l_DF11ZAOYi%k#&44DH`$i6lFm6*VPdVv%`T|j`>s*mYNYr_V1~gNYkhnF zetTk{vNrSmy(lA_`G=^xeJI(1C&@xuLb}bwn6nmvi&rT2-)o3?p+f-4h0VA zJxXmZ#43LS42h<&kiheO5GTa7{8iXirBF5gUEtT3omfll1(1czTB4%OHYIlv2a^~z z%sMI^CmD)Y@v~06>k@@YoErflg6su=vLIeQE5+uR$8BYLE(CmH537~%9 z-SAruYpn8=G<7cOVJ9(%6U=v6X2~tzI4)$Z5dQy-5`H&1BWR8`EhNRA& z&qB}56 zwU(Jg0YxVA^i8CM>ozz8p*g1eBVr>+*XON0K+~lIC@FJhc!F-*#(a}$EGL<|{s!Rf zwElh|T=Q60NxNF}(a_t6≷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

02r1hN6D%RriJSsB5Cy>u1sM|u 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 d2mHVTl#vrb(Kdl(7ZCtMR9#4#` diff --git a/docs/sources/installation/images/win/ts_go_bios.JPG b/docs/sources/installation/images/win/ts_go_bios.JPG deleted file mode 100644 index c4159fc715fbea4cf5626c91fb45604e08c10b0f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46857 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 diff --git a/docs/sources/installation/images/win/ts_no_docker.JPG b/docs/sources/installation/images/win/ts_no_docker.JPG deleted file mode 100644 index 9ccba01c1ed86177f6937218d12b0c61d47946b0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14168 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 From d1af2bc253f61a86c7d383ba3072c39e2a5b129d Mon Sep 17 00:00:00 2001 From: Maxim Kulkin Date: Fri, 27 Mar 2015 01:38:07 -0700 Subject: [PATCH 150/999] Add tool to conver disk image into Docker image dockerize-image tool takes a virtual disk image file and creates a Docker image based on it. You can specify a base Docker image to make this tool create an image that will contain only filesystem diff instead of full filesystem. See tools usage for details. Signed-off-by: Maxim Kulkin --- contrib/dockerize-disk.sh | 118 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100755 contrib/dockerize-disk.sh diff --git a/contrib/dockerize-disk.sh b/contrib/dockerize-disk.sh new file mode 100755 index 000000000..6e72d9fa4 --- /dev/null +++ b/contrib/dockerize-disk.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +set -e + +if ! command -v qemu-nbd &> /dev/null; then + echo >&2 'error: "qemu-nbd" not found!' + exit 1 +fi + +usage() { + echo "Convert disk image to docker image" + echo "" + echo "usage: $0 image-name disk-image-file [ base-image ]" + echo " ie: $0 cirros:0.3.3 cirros-0.3.3-x86_64-disk.img" + echo " $0 ubuntu:cloud ubuntu-14.04-server-cloudimg-amd64-disk1.img ubuntu:14.04" +} + +if [ "$#" -lt 2 ]; then + usage + exit 1 +fi + +CURDIR=$(pwd) + +image_name="${1%:*}" +image_tag="${1#*:}" +if [ "$image_tag" == "$1" ]; then + image_tag="latest" +fi + +disk_image_file="$2" +docker_base_image="$3" + +block_device=/dev/nbd0 + +builddir=$(mktemp -d) + +cleanup() { + umount "$builddir/disk_image" || true + umount "$builddir/workdir" || true + qemu-nbd -d $block_device &> /dev/null || true + rm -rf $builddir +} +trap cleanup EXIT + +# Mount disk image +modprobe nbd max_part=63 +qemu-nbd -rc ${block_device} -P 1 "$disk_image_file" +mkdir "$builddir/disk_image" +mount -o ro ${block_device} "$builddir/disk_image" + +mkdir "$builddir/workdir" +mkdir "$builddir/diff" + +base_image_mounts="" + +# Unpack base image +if [ -n "$docker_base_image" ]; then + mkdir -p "$builddir/base" + docker pull "$docker_base_image" + docker save "$docker_base_image" | tar -xC "$builddir/base" + + image_id=$(docker inspect -f "{{.Id}}" "$docker_base_image") + while [ -n "$image_id" ]; do + mkdir -p "$builddir/base/$image_id/layer" + tar -xf "$builddir/base/$image_id/layer.tar" -C "$builddir/base/$image_id/layer" + + base_image_mounts="${base_image_mounts}:$builddir/base/$image_id/layer=ro+wh" + image_id=$(docker inspect -f "{{.Parent}}" "$image_id") + done +fi + +# Mount work directory +mount -t aufs -o "br=$builddir/diff=rw${base_image_mounts},dio,xino=/dev/shm/aufs.xino" none "$builddir/workdir" + +# Update files +cd $builddir +diff -rq disk_image workdir \ + | sed -re "s|Only in workdir(.*?): |DEL \1/|g;s|Only in disk_image(.*?): |ADD \1/|g;s|Files disk_image/(.+) and workdir/(.+) differ|UPDATE /\1|g" \ + | while read action entry; do + case "$action" in + ADD|UPDATE) + cp -a "disk_image$entry" "workdir$entry" + ;; + DEL) + rm -rf "workdir$entry" + ;; + *) + echo "Error: unknown diff line: $action $entry" >&2 + ;; + esac + done + +# Pack new image +new_image_id="$(for i in $(seq 1 32); do printf "%02x" $(($RANDOM % 256)); done)" +mkdir -p $builddir/result/$new_image_id +cd diff +tar -cf $builddir/result/$new_image_id/layer.tar * +echo "1.0" > $builddir/result/$new_image_id/VERSION +cat > $builddir/result/$new_image_id/json <<-EOS +{ "docker_version": "1.4.1" +, "id": "$new_image_id" +, "created": "$(date -u +%Y-%m-%dT%H:%M:%S.%NZ)" +EOS + +if [ -n "$docker_base_image" ]; then + image_id=$(docker inspect -f "{{.Id}}" "$docker_base_image") + echo ", \"parent\": \"$image_id\"" >> $builddir/result/$new_image_id/json +fi + +echo "}" >> $builddir/result/$new_image_id/json + +echo "{\"$image_name\":{\"$image_tag\":\"$new_image_id\"}}" > $builddir/result/repositories + +cd $builddir/result + +# mkdir -p $CURDIR/$image_name +# cp -r * $CURDIR/$image_name +tar -c * | docker load From de09c553946943e9f4be5b3d0c4edf03b740ccf4 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Fri, 27 Mar 2015 14:54:37 -0700 Subject: [PATCH 151/999] Update boot2docker on Windows documentation Boot2Docker experience is updated now that we have a Docker client on Windows. Instead of running `boot2docker ssh`, users can also use boot2docker on Windows Command Prompt (`cmd.exe`) and PowerShell. Updated documentation and screenshots, added a few details, reorganized sections by importance, fixed a few errors. Remaining: the video link in the Demonstration section needs to be updated once I shoot a new video. Signed-off-by: Ahmet Alp Balkan --- .../images/windows-boot2docker-cmd.png | Bin 0 -> 37436 bytes .../images/windows-boot2docker-powershell.png | Bin 0 -> 37945 bytes .../images/windows-boot2docker-start.png | Bin 21764 -> 253995 bytes .../installation/images/windows-installer.png | Bin 22913 -> 191913 bytes docs/sources/installation/windows.md | 142 ++++++++++++------ 5 files changed, 94 insertions(+), 48 deletions(-) create mode 100644 docs/sources/installation/images/windows-boot2docker-cmd.png create mode 100644 docs/sources/installation/images/windows-boot2docker-powershell.png diff --git a/docs/sources/installation/images/windows-boot2docker-cmd.png b/docs/sources/installation/images/windows-boot2docker-cmd.png new file mode 100644 index 0000000000000000000000000000000000000000..09e3206ef97ecef230a24e13e9b25e106f7b576e GIT binary patch literal 37436 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 literal 0 HcmV?d00001 diff --git a/docs/sources/installation/images/windows-boot2docker-powershell.png b/docs/sources/installation/images/windows-boot2docker-powershell.png new file mode 100644 index 0000000000000000000000000000000000000000..b1ef89672619b3dd7f4c1ee7d269f221bec3395d GIT binary patch literal 37945 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®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

%P{4>xD(SVWI?8XVKdzOBkj(gR$$Nf`C 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%0ATSnl0MRgz8Yk95xPJ5>!by5ocP3Xph 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 z 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%0AB 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 Ad;kCd diff --git a/docs/sources/installation/images/windows-installer.png b/docs/sources/installation/images/windows-installer.png index ea1d3d2326feabaa1b9c11f0192f3c3de41d131c..7580ed6226d073c83596f9866654d675a269af9c 100644 GIT binary patch literal 191913 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 z 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 Xp`6&kJHUR=?z?&2?ppcPCsF?gwmnej literal 22913 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 Your processor needs to support hardware virtualization. The Docker Engine uses Linux-specific kernel features, so to run it on Windows -we need to use a lightweight virtual machine (vm). You use the Windows Docker client to -control the virtualized Docker Engine to build, run, and manage Docker containers. +we need to use a lightweight virtual machine (VM). You use the **Windows Docker +Client** to control the virtualized Docker Engine to build, run, and manage +Docker containers. To make this process easier, we've designed a helper application called -[Boot2Docker](https://github.com/boot2docker/boot2docker) that installs the -virtual machine and runs the Docker daemon. +[Boot2Docker](https://github.com/boot2docker/boot2docker) creates a Linux virtual +machine on Windows to run Docker on a Linux operating system. + +Although you will be using Windows Docker client, the docker engine hosting the +containers will still be running on Linux. Until the Docker engine for Windows +is developed, you can launch only Linux containers from your Windows machine. ## Demonstration @@ -21,18 +26,77 @@ virtual machine and runs the Docker daemon. ## Installation -1. Download the latest release of the [Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest) -2. Run the installer, which will install VirtualBox, MSYS-git, the boot2docker Linux ISO, -and the Boot2Docker management tool. +1. Download the latest release of the + [Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest). +2. Run the installer, which will install Docker Client or Windows, VirtualBox, + Git for Windows (MSYS-git), the boot2docker Linux ISO, and the Boot2Docker + management tool. ![](/installation/images/windows-installer.png) -3. Run the `Boot2Docker Start` shell script from your Desktop or Program Files > Boot2Docker for Windows. +3. Run the **Boot2Docker Start** shortcut from your Desktop or “Program Files → + Boot2Docker for Windows”. The Start script will ask you to enter an ssh key passphrase - the simplest (but least secure) is to just hit [Enter]. - ![](/installation/images/windows-boot2docker-start.png) +4. The **Boot2Docker Start** will start a unix shell already configured to manage + Docker running inside the virtual machine. Run `docker version` to see + if it is working correctly: - The `Boot2Docker Start` script will connect you to a shell session in the virtual - machine. If needed, it will initialize a new VM and start it. +![](/installation/images/windows-boot2docker-start.png) + +## Running Docker + +{{ include "no-remote-sudo.md" }} + +**Boot2Docker Start** will automatically start a shell with environment variables +correctly set so you can start using Docker right away: + +Let's try the `hello-world` example image. Run + + $ docker run hello-world + +This should download the very small `hello-world` image and print a +`Hello from Docker.` message. + +## Using docker from Windows Command Line Prompt (cmd.exe) + +Launch a Windows Command Line Prompt (cmd.exe). + +Boot2Docker command requires `ssh.exe` to be in the PATH, therefore we need to +include `bin` folder of the Git installation (which has ssh.exe) to the `%PATH%` +environment variable by running: + + set PATH=%PATH%;"c:\Program Files (x86)\Git\bin" + +and then we can run the `boot2docker start` command to start the Boot2Docker VM. +(Run `boot2docker init` command if you get an error saying machine does not +exist.) Then copy the instructions for cmd.exe to set the environment variables +to your console window and you are ready to run docker commands such as +`docker ps`: + +![](/installation/images/windows-boot2docker-cmd.png) + +## Using docker from PowerShell + +Launch a PowerShell window, then you need to add `ssh.exe` to your PATH: + + $Env:Path = "${Env:Path};c:\Program Files (x86)\Git\bin" + +and after running `boot2docker start` command it will print PowerShell commands +to set the environment variables to connect Docker running inside VM. Run these +commands and you are ready to run docker commands such as `docker ps`: + +![](/installation/images/windows-boot2docker-powershell.png) + +> NOTE: You can alternatively run `boot2docker shellinit | Invoke-Expression` +> command to set the environment variables instead of copying and pasting on +> PowerShell. + +# Further Details + +The Boot2Docker management tool provides several commands: + + $ boot2docker + Usage: boot2docker.exe [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|shellinit|delete|download|upgrade|version} [] ## Upgrading @@ -47,46 +111,13 @@ and the Boot2Docker management tool. boot2docker download boot2docker start -## Running Docker - -{{ include "no-remote-sudo.md" }} - -Boot2Docker will log you in automatically so you can start using Docker right away. - -Let's try the `hello-world` example image. Run - - $ docker run hello-world - -This should download the very small `hello-world` image and print a `Hello from Docker.` message. - -## Login with PUTTY instead of using the CMD - -Boot2Docker generates and uses the public/private key pair in your `%HOMEPATH%\.ssh` -directory so to log in you need to use the private key from this same directory. - -The private key needs to be converted into the format PuTTY uses. - -You can do this with -[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html): - -- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from - `%HOMEPATH%\.ssh\id_boot2docker` -- then click: "Save Private Key". -- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`. - -# Further Details - -The Boot2Docker management tool provides several commands: - - $ ./boot2docker - Usage: ./boot2docker [] {help|init|up|ssh|save|down|poweroff|reset|restart|config|status|info|ip|delete|download|version} [] - - ## Container port redirection -If you are curious, the username for the boot2docker default user is `docker` and the password is `tcuser`. +If you are curious, the username for the boot2docker default user is `docker` +and the password is `tcuser`. -The latest version of `boot2docker` sets up a host only network adaptor which provides access to the container's ports. +The latest version of `boot2docker` sets up a host only network adaptor which +provides access to the container's ports. If you run a container with an exposed port: @@ -101,3 +132,18 @@ Typically, it is 192.168.59.103, but it could get changed by Virtualbox's DHCP implementation. For further information or to report issues, please see the [Boot2Docker site](http://boot2docker.io) + +## Login with PUTTY instead of using the CMD + +Boot2Docker generates and uses the public/private key pair in your `%USERPROFILE%\.ssh` +directory so to log in you need to use the private key from this same directory. + +The private key needs to be converted into the format PuTTY uses. + +You can do this with +[puttygen](http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html): + +- Open `puttygen.exe` and load ("File"->"Load" menu) the private key from + `%USERPROFILE%\.ssh\id_boot2docker` +- then click: "Save Private Key". +- Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`. From 013fb87543a47524efba3c371a1d22afe151287c Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Sat, 28 Mar 2015 09:07:20 +0800 Subject: [PATCH 152/999] Fix error from daemon no such image even when the image exist Signed-off-by: Lei Jitang --- api/client/create.go | 3 ++- builder/dispatchers.go | 2 +- daemon/create.go | 2 +- daemon/image_delete.go | 2 +- graph/graph.go | 4 ++-- integration-cli/docker_cli_run_test.go | 11 +++++++++++ 6 files changed, 18 insertions(+), 6 deletions(-) diff --git a/api/client/create.go b/api/client/create.go index fed4734ec..c3daf46fd 100644 --- a/api/client/create.go +++ b/api/client/create.go @@ -7,6 +7,7 @@ import ( "io" "net/url" "os" + "strings" "github.com/docker/docker/api/types" "github.com/docker/docker/graph" @@ -94,7 +95,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc //create the container stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), mergedConfig, nil) //if image not found try to pull it - if statusCode == 404 { + if statusCode == 404 && strings.Contains(err.Error(), config.Image) { repo, tag := parsers.ParseRepositoryTag(config.Image) if tag == "" { tag = graph.DEFAULTTAG diff --git a/builder/dispatchers.go b/builder/dispatchers.go index acb4d50de..e47991194 100644 --- a/builder/dispatchers.go +++ b/builder/dispatchers.go @@ -166,7 +166,7 @@ func from(b *Builder, args []string, attributes map[string]bool, original string } } if err != nil { - if b.Daemon.Graph().IsNotExist(err) { + if b.Daemon.Graph().IsNotExist(err, name) { image, err = b.pullImage(name) } diff --git a/daemon/create.go b/daemon/create.go index 39b6ac58b..c820201e4 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -46,7 +46,7 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) error { container, buildWarnings, err := daemon.Create(config, hostConfig, name) if err != nil { - if daemon.Graph().IsNotExist(err) { + if daemon.Graph().IsNotExist(err, config.Image) { _, tag := parsers.ParseRepositoryTag(config.Image) if tag == "" { tag = graph.DEFAULTTAG diff --git a/daemon/image_delete.go b/daemon/image_delete.go index 1c865c58d..075672a4c 100644 --- a/daemon/image_delete.go +++ b/daemon/image_delete.go @@ -138,7 +138,7 @@ func (daemon *Daemon) canDeleteImage(imgID string, force bool) error { for _, container := range daemon.List() { parent, err := daemon.Repositories().LookupImage(container.ImageID) if err != nil { - if daemon.Graph().IsNotExist(err) { + if daemon.Graph().IsNotExist(err, container.ImageID) { return nil } return err diff --git a/graph/graph.go b/graph/graph.go index 933269a43..994ce3028 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -74,8 +74,8 @@ func (graph *Graph) restore() error { // FIXME: Implement error subclass instead of looking at the error text // Note: This is the way golang implements os.IsNotExists on Plan9 -func (graph *Graph) IsNotExist(err error) bool { - return err != nil && (strings.Contains(strings.ToLower(err.Error()), "does not exist") || strings.Contains(strings.ToLower(err.Error()), "no such")) +func (graph *Graph) IsNotExist(err error, id string) bool { + return err != nil && (strings.Contains(strings.ToLower(err.Error()), "does not exist") || strings.Contains(strings.ToLower(err.Error()), "no such")) && strings.Contains(err.Error(), id) } // Exists returns true if an image is registered at the given id. diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index d8e93c0fc..17c22e9b3 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2990,6 +2990,17 @@ func TestRunModeIpcContainer(t *testing.T) { logDone("run - ipc container mode") } +func TestRunModeIpcContainerNotExists(t *testing.T) { + defer deleteAllContainers() + cmd := exec.Command(dockerBinary, "run", "-d", "--ipc", "container:abcd1234", "busybox", "top") + out, _, err := runCommandWithOutput(cmd) + if !strings.Contains(out, "abcd1234") || err == nil { + t.Fatalf("run IPC from a non exists container should with correct error out") + } + + logDone("run - ipc from a non exists container failed with correct error out") +} + func TestContainerNetworkMode(t *testing.T) { defer deleteAllContainers() testRequires(t, SameHostDaemon) From 194cad243ccf622c8ff048975e26cc6c51ccbf30 Mon Sep 17 00:00:00 2001 From: Harry Zhang Date: Fri, 27 Mar 2015 12:06:49 +0000 Subject: [PATCH 153/999] Remove dupllicated prefix to make table shorter & fix bugs Signed-off-by: Harry Zhang --- docs/sources/reference/run.md | 77 ++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 38 deletions(-) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 7be09fa19..84b5938e9 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -637,46 +637,47 @@ In addition to `--privileged`, the operator can have fine grain control over the capabilities using `--cap-add` and `--cap-drop`. By default, Docker has a default list of capabilities that are kept. Here is a table to list the reference information on capabilities. -| Capability Key | Capability Value | Capability Description | +| Capability Key | Capability Description | | :----------------- | :---------------| :-------------------- | -| SETPCAP | capability.CAP_SETPCAP | Modify process capabilities. | -| SYS_MODULE | capability.CAP_SYS_MODULE | Load and unload kernel modules. | -| SYS_RAWIO | capability.CAP_SYS_RAWIO | Perform I/O port operations (iopl(2) and ioperm(2)). | -| SYS_PACCT | capability.CAP_SYS_PACCT | Use acct(2), switch process accounting on or off. | -| SYS_ADMIN | capability.CAP_SYS_ADMIN | Perform a range of system administration operations. | -| SYS_NICE | capability.CAP_SYS_NICE | Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes. | -| SYS_RESOURCE | capability.CAP_SYS_RESOURCE | Override Resource Limits. | -| SYS_TIME | capability.CAP_SYS_TIME | Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock. | -| SYS_TTY_CONFIG | capability.CAP_SYS_TTY_CONFIG | Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals. | -| MKNOD | capability.CAP_MKNOD | Create special files using mknod(2). | -| AUDIT_WRITE | capability.CAP_AUDIT_WRITE | Write records to kernel auditing log. | -| AUDIT_CONTROL | capability.CAP_AUDIT_CONTROL | Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules. | -| MAC_OVERRIDE | capability.CAP_MAC_OVERRIDE | Allow MAC configuration or state changes. Implemented for the Smack LSM. | -| MAC_ADMIN | capability.CAP_MAC_ADMIN | Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM). | -| NET_ADMIN | capability.CAP_NET_ADMIN | Perform various network-related operations. | -| SYSLOG | capability.CAP_SYSLOG | Perform privileged syslog(2) operations. | -| CHOWN | capability.CAP_CHOWN | Make arbitrary changes to file UIDs and GIDs (see chown(2)). | -| NET_RAW | capability.CAP_NET_RAW | Use RAW and PACKET sockets. | -| DAC_OVERRIDE | capability.CAP_DAC_OVERRIDE | Bypass file read, write, and execute permission checks. | -| FOWNER | capability.CAP_FOWNER | Bypass permission checks on operations that normally require the file system UID of the process to match the UID of the file. | -| DAC_READ_SEARCH | capability.CAP_DAC_READ_SEARCH | Bypass file read permission checks and directory read and execute permission checks. | -| FSETID | capability.CAP_FSETID | Don't clear set-user-ID and set-group-ID permission bits when a file is modified. | -| KILL | apability.CAP_KILL | Bypass permission checks for sending signals. | -| SETGID | capability.CAP_SETGID | Make arbitrary manipulations of process GIDs and supplementary GID list. | -| SETUID | capability.CAP_SETUID | Make arbitrary manipulations of process UIDs. | -| LINUX_IMMUTABLE | capability.CAP_LINUX_IMMUTABLE | Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags. | -| NET_BIND_SERVICE | capability.CAP_NET_BIND_SERVICE | Bind a socket to Internet domain privileged ports (port numbers less than 1024). | -| NET_BROADCAST | capability.CAP_NET_BROADCAST} | Make socket broadcasts, and listen to multicasts. | -| IPC_LOCK | capability.CAP_IPC_LOCK | Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)). | -| IPC_OWNER | capability.CAP_IPC_OWNER | Bypass permission checks for operations on System V IPC objects. | -| SYS_CHROOT | capability.CAP_SYS_CHROOT | Use chroot(2), change root directory. | -| SYS_PTRACE | capability.CAP_SYS_PTRACE | Trace arbitrary processes using ptrace(2). | -| SYS_BOOT | capability.CAP_SYS_BOOT | Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution. | -| LEASE | capability.CAP_LEASE | Establish leases on arbitrary files (see fcntl(2)). | -| SETFCAP | capability.CAP_SETFCAP | Set file capabilities.| -| WAKE_ALARM | capability.CAP_WAKE_ALARM | Trigger something that will wake up the system. | -| BLOCK_SUSPEND | capability.CAP_BLOCK_SUSPEND | Employ features that can block system suspend. | +| SETPCAP | Modify process capabilities. | +| SYS_MODULE| Load and unload kernel modules. | +| SYS_RAWIO | Perform I/O port operations (iopl(2) and ioperm(2)). | +| SYS_PACCT | Use acct(2), switch process accounting on or off. | +| SYS_ADMIN | Perform a range of system administration operations. | +| SYS_NICE | Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes. | +| SYS_RESOURCE | Override Resource Limits. | +| SYS_TIME | Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock. | +| SYS_TTY_CONFIG | Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals. | +| MKNOD | Create special files using mknod(2). | +| AUDIT_WRITE | Write records to kernel auditing log. | +| AUDIT_CONTROL | Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules. | +| MAC_OVERRIDE | Allow MAC configuration or state changes. Implemented for the Smack LSM. | +| MAC_ADMIN | Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM). | +| NET_ADMIN | Perform various network-related operations. | +| SYSLOG | Perform privileged syslog(2) operations. | +| CHOWN | Make arbitrary changes to file UIDs and GIDs (see chown(2)). | +| NET_RAW | Use RAW and PACKET sockets. | +| DAC_OVERRIDE | Bypass file read, write, and execute permission checks. | +| FOWNER | Bypass permission checks on operations that normally require the file system UID of the process to match the UID of the file. | +| DAC_READ_SEARCH | Bypass file read permission checks and directory read and execute permission checks. | +| FSETID | Don't clear set-user-ID and set-group-ID permission bits when a file is modified. | +| KILL | Bypass permission checks for sending signals. | +| SETGID | Make arbitrary manipulations of process GIDs and supplementary GID list. | +| SETUID | Make arbitrary manipulations of process UIDs. | +| LINUX_IMMUTABLE | Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags. | +| NET_BIND_SERVICE | Bind a socket to Internet domain privileged ports (port numbers less than 1024). | +| NET_BROADCAST | Make socket broadcasts, and listen to multicasts. | +| IPC_LOCK | Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)). | +| IPC_OWNER | Bypass permission checks for operations on System V IPC objects. | +| SYS_CHROOT | Use chroot(2), change root directory. | +| SYS_PTRACE | Trace arbitrary processes using ptrace(2). | +| SYS_BOOT | Use reboot(2) and kexec_load(2), reboot and load a new kernel for later execution. | +| LEASE | Establish leases on arbitrary files (see fcntl(2)). | +| SETFCAP | Set file capabilities.| +| WAKE_ALARM | Trigger something that will wake up the system. | +| BLOCK_SUSPEND | Employ features that can block system suspend. | +For futher understanding, please check [capabilities(7) - Linux man page](http://linux.die.net/man/7/capabilities) Both flags support the value `all`, so if the operator wants to have all capabilities but `MKNOD` they could use: From f7d75cc08a6ebdc50ea9b7473342fdf6e22dc848 Mon Sep 17 00:00:00 2001 From: Michael West Date: Fri, 27 Mar 2015 23:23:50 -0400 Subject: [PATCH 154/999] Add man pages generation instructions. Signed-off-by: Michael West --- docs/README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/README.md b/docs/README.md index 5feb496a7..24f91e115 100755 --- a/docs/README.md +++ b/docs/README.md @@ -280,3 +280,24 @@ aws cloudfront create-invalidation --profile docs.docker.com --distribution-id aws cloudfront create-invalidation --profile docs.docker.com --distribution-id $DISTRIBUTION_ID --invalidation-batch '{"Paths":{"Quantity":1, "Items":["/v1.1/reference/api/docker_io_oauth_api/"]},"CallerReference":"6Mar2015sventest1"}' ``` +### Generate the man pages for Mac OSX + +When using Docker on Mac OSX the man pages will be missing by default. You can manually generate them by following these steps: + +1. Checkout the docker source. You must clone into your `/Users` directory because Boot2Docker can only share this path + with docker containers. + + $ git clone https://github.com/docker/docker.git + +2. Build the docker image. + + $ cd docker/docs/man + $ docker build -t docker/md2man . + +3. Build the man pages. + + $ docker run -v /Users//docker/docs/man:/docs:rw -w /docs -i docker/md2man /docs/md2man-all.sh + +4. Copy the generated man pages to `/usr/share/man` + + $ cp -R man* /usr/share/man/ From b4905859d55cf756d3855c1deddb68a9509bdab4 Mon Sep 17 00:00:00 2001 From: Michael West Date: Fri, 27 Mar 2015 23:25:12 -0400 Subject: [PATCH 155/999] Add a missing definite article Signed-off-by: Michael West --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index 24f91e115..15fee1d36 100755 --- a/docs/README.md +++ b/docs/README.md @@ -285,7 +285,7 @@ aws cloudfront create-invalidation --profile docs.docker.com --distribution-id When using Docker on Mac OSX the man pages will be missing by default. You can manually generate them by following these steps: 1. Checkout the docker source. You must clone into your `/Users` directory because Boot2Docker can only share this path - with docker containers. + with the docker containers. $ git clone https://github.com/docker/docker.git From cee62a95a2086dace52f2492de781aa333abca3b Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Fri, 27 Mar 2015 23:17:50 +0800 Subject: [PATCH 156/999] Add nice error message Generally, when using Remote API to push images there needs a http Header X-Registry-Auth. For compatibility if there was no authConfig header, everything will be okay if a proper JSON-http-body was applied. But when both X-Registry-Auth Header and the Body are missing, due to the function of decode JSON, it will return an EOF error which was not very clear to user. So I think we can make the respone error be more nice. Signed-off-by: Hu Keping --- api/server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/server/server.go b/api/server/server.go index 2dabbbeba..ec90f7fb9 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -662,7 +662,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response } else { // the old format is supported for compatibility if there was no authConfig header if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { - return err + return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err) } } From 0dc996a7d779100b44e5c6f492efe231140d76cf Mon Sep 17 00:00:00 2001 From: Ian Babrou Date: Sat, 28 Mar 2015 14:31:02 +0300 Subject: [PATCH 157/999] fixed code formatting on docs.docker.com Signed-off-by: Ian Babrou --- docs/sources/project/set-up-dev-env.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/project/set-up-dev-env.md b/docs/sources/project/set-up-dev-env.md index 3ab92ae56..80d4f335b 100644 --- a/docs/sources/project/set-up-dev-env.md +++ b/docs/sources/project/set-up-dev-env.md @@ -75,8 +75,8 @@ To remove unnecessary artifacts, $ docker rmi -f $(docker images -q -a -f dangling=true) This command uses `docker images` to list all images (`-a` flag) by numeric - IDs (`-q` flag) and filter them to find dangling images (`-f - dangling=true`). Then, the `docker rmi` command forcibly (`-f` flag) removes + IDs (`-q` flag) and filter them to find dangling images (`-f dangling=true`). + Then, the `docker rmi` command forcibly (`-f` flag) removes the resulting list. To remove just one image, use the `docker rmi ID` command. From 8b795a05a8fa8a6f747ee5cc0c087bca9d42199d Mon Sep 17 00:00:00 2001 From: Jamie Hannaford Date: Sat, 28 Mar 2015 17:39:24 +0100 Subject: [PATCH 158/999] Use ContainerCommitResponse struct for Commit cmd Signed-off-by: Jamie Hannaford --- api/client/commit.go | 12 +++++++----- api/server/server.go | 6 +++--- api/types/types.go | 5 +++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/api/client/commit.go b/api/client/commit.go index 4f1361015..643b19931 100644 --- a/api/client/commit.go +++ b/api/client/commit.go @@ -5,7 +5,7 @@ import ( "fmt" "net/url" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" @@ -57,9 +57,10 @@ func (cli *DockerCli) CmdCommit(args ...string) error { } var ( - config *runconfig.Config - env engine.Env + config *runconfig.Config + response types.ContainerCommitResponse ) + if *flConfig != "" { config = &runconfig.Config{} if err := json.Unmarshal([]byte(*flConfig), config); err != nil { @@ -70,10 +71,11 @@ func (cli *DockerCli) CmdCommit(args ...string) error { if err != nil { return err } - if err := env.Decode(stream); err != nil { + + if err := json.NewDecoder(stream).Decode(&response); err != nil { return err } - fmt.Fprintf(cli.out, "%s\n", env.Get("Id")) + fmt.Fprintln(cli.out, response.ID) return nil } diff --git a/api/server/server.go b/api/server/server.go index 3ceb1017a..28f068b36 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -507,7 +507,6 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit } var ( config engine.Env - env engine.Env job = eng.Job("commit", r.Form.Get("container")) stdoutBuffer = bytes.NewBuffer(nil) ) @@ -537,8 +536,9 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit if err := job.Run(); err != nil { return err } - env.Set("Id", engine.Tail(stdoutBuffer, 1)) - return writeJSONEnv(w, http.StatusCreated, env) + return writeJSON(w, http.StatusCreated, &types.ContainerCommitResponse{ + ID: engine.Tail(stdoutBuffer, 1), + }) } // Creates an image from Pull or from Import diff --git a/api/types/types.go b/api/types/types.go index 50a72a550..85b300290 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -30,3 +30,8 @@ type ContainerWaitResponse struct { // StatusCode is the status code of the wait job StatusCode int `json:"StatusCode"` } + +// POST "/commit?container="+containerID +type ContainerCommitResponse struct { + ID string `json:"Id"` +} From a09cc935c3531e0132640f0fe258041f1b445fdc Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Sat, 28 Mar 2015 11:32:33 -0700 Subject: [PATCH 159/999] Do not complete --cgroup-parent as _filedir This is a follow-up on PR 11708, as suggested by tianon. Signed-off-by: Harald Albers --- contrib/completion/bash/docker | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 2397cb59f..69117890f 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -802,7 +802,7 @@ _docker_run() { __docker_capabilities return ;; - --cidfile|--cgroup-parent|--env-file|--label-file) + --cidfile|--env-file|--label-file) _filedir return ;; From 5670c6c6954e08575a6c821137a7ea6d9084af93 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 29 Mar 2015 03:22:46 +0200 Subject: [PATCH 160/999] Refactor utils/flags.go, fixes #11892 Signed-off-by: Antonio Murdaca --- api/client/attach.go | 2 +- api/client/build.go | 3 +-- api/client/commit.go | 3 +-- api/client/cp.go | 3 +-- api/client/create.go | 2 +- api/client/diff.go | 3 +-- api/client/events.go | 3 +-- api/client/export.go | 3 +-- api/client/history.go | 2 +- api/client/images.go | 2 +- api/client/import.go | 3 +-- api/client/info.go | 3 +-- api/client/inspect.go | 2 +- api/client/kill.go | 3 +-- api/client/load.go | 3 +-- api/client/login.go | 3 +-- api/client/logout.go | 3 +-- api/client/logs.go | 3 +-- api/client/pause.go | 3 +-- api/client/port.go | 3 +-- api/client/ps.go | 2 +- api/client/pull.go | 2 +- api/client/push.go | 3 +-- api/client/restart.go | 3 +-- api/client/rm.go | 3 +-- api/client/rmi.go | 3 +-- api/client/run.go | 2 +- api/client/save.go | 3 +-- api/client/search.go | 2 +- api/client/start.go | 2 +- api/client/stats.go | 3 +-- api/client/stop.go | 3 +-- api/client/tag.go | 3 +-- api/client/top.go | 3 +-- api/client/unpause.go | 3 +-- api/client/version.go | 3 +-- api/client/wait.go | 3 +-- pkg/mflag/flag.go | 36 ++++++++++++++++++++++++++++++++++ runconfig/exec.go | 3 +-- runconfig/parse.go | 2 +- utils/flags.go | 45 ------------------------------------------- 41 files changed, 75 insertions(+), 112 deletions(-) delete mode 100644 utils/flags.go diff --git a/api/client/attach.go b/api/client/attach.go index e6acec48b..48cb8b447 100644 --- a/api/client/attach.go +++ b/api/client/attach.go @@ -23,7 +23,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { ) cmd.Require(flag.Exact, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) name := cmd.Arg(0) stream, _, err := cli.call("GET", "/containers/"+name+"/json", nil, nil) diff --git a/api/client/build.go b/api/client/build.go index 779e98ecc..53601763d 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -58,8 +58,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") cmd.Require(flag.Exact, 1) - - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var ( context archive.Archive diff --git a/api/client/commit.go b/api/client/commit.go index 4f1361015..3286c1461 100644 --- a/api/client/commit.go +++ b/api/client/commit.go @@ -11,7 +11,6 @@ import ( "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) // CmdCommit creates a new image from a container's changes. @@ -28,7 +27,7 @@ func (cli *DockerCli) CmdCommit(args ...string) error { 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") cmd.Require(flag.Max, 2) cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var ( name = cmd.Arg(0) diff --git a/api/client/cp.go b/api/client/cp.go index db14e2f53..9cc1b3be6 100644 --- a/api/client/cp.go +++ b/api/client/cp.go @@ -8,7 +8,6 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdCp copies files/folders from a path on the container to a directory on the host running the command. @@ -20,7 +19,7 @@ func (cli *DockerCli) CmdCp(args ...string) error { cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTDIR|-", "Copy files/folders from a PATH on the container to a HOSTDIR on the host\nrunning the command. Use '-' to write the data\nas a tar file to STDOUT.", true) cmd.Require(flag.Exact, 2) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var copyData engine.Env info := strings.Split(cmd.Arg(0), ":") diff --git a/api/client/create.go b/api/client/create.go index c3daf46fd..bb84d5e46 100644 --- a/api/client/create.go +++ b/api/client/create.go @@ -142,7 +142,7 @@ func (cli *DockerCli) CmdCreate(args ...string) error { config, hostConfig, cmd, err := runconfig.Parse(cmd, args) if err != nil { - utils.ReportError(cmd, err.Error(), true) + cmd.ReportError(err.Error(), true) } if config.Image == "" { cmd.Usage() diff --git a/api/client/diff.go b/api/client/diff.go index be58d9cfb..0ba7b53f3 100644 --- a/api/client/diff.go +++ b/api/client/diff.go @@ -6,7 +6,6 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdDiff shows changes on a container's filesystem. @@ -18,7 +17,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error { cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem", true) cmd.Require(flag.Exact, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil)) diff --git a/api/client/events.go b/api/client/events.go index 6cba102f0..2154e0ccd 100644 --- a/api/client/events.go +++ b/api/client/events.go @@ -9,7 +9,6 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers/filters" "github.com/docker/docker/pkg/timeutils" - "github.com/docker/docker/utils" ) // CmdEvents prints a live stream of real time events from the server. @@ -23,7 +22,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error { cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") cmd.Require(flag.Exact, 0) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var ( v = url.Values{} diff --git a/api/client/export.go b/api/client/export.go index dab83490e..8f1642f60 100644 --- a/api/client/export.go +++ b/api/client/export.go @@ -7,7 +7,6 @@ import ( "os" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdExport exports a filesystem as a tar archive. @@ -20,7 +19,7 @@ func (cli *DockerCli) CmdExport(args ...string) error { outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT") cmd.Require(flag.Exact, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var ( output io.Writer = cli.out diff --git a/api/client/history.go b/api/client/history.go index 85c48d99d..e8ad19c8a 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -21,7 +21,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") cmd.Require(flag.Exact, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil)) if err != nil { diff --git a/api/client/images.go b/api/client/images.go index c546619bf..4cfa3abaf 100644 --- a/api/client/images.go +++ b/api/client/images.go @@ -102,7 +102,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") cmd.Require(flag.Max, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) // Consolidate all filter flags, and sanity check them early. // They'll get process in the daemon/server. diff --git a/api/client/import.go b/api/client/import.go index 4264e1c28..a6cc4cdc7 100644 --- a/api/client/import.go +++ b/api/client/import.go @@ -9,7 +9,6 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/registry" - "github.com/docker/docker/utils" ) // CmdImport creates an empty filesystem image, imports the contents of the tarball into the image, and optionally tags the image. @@ -23,7 +22,7 @@ func (cli *DockerCli) CmdImport(args ...string) error { cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image") cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var ( v = url.Values{} diff --git a/api/client/info.go b/api/client/info.go index 7a350e32a..704351b3d 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -9,7 +9,6 @@ import ( "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/units" - "github.com/docker/docker/utils" ) // CmdInfo displays system-wide information. @@ -18,7 +17,7 @@ import ( func (cli *DockerCli) CmdInfo(args ...string) error { cmd := cli.Subcmd("info", "", "Display system-wide information", true) cmd.Require(flag.Exact, 0) - utils.ParseFlags(cmd, args, false) + cmd.ParseFlags(args, false) body, _, err := readBody(cli.call("GET", "/info", nil, nil)) if err != nil { diff --git a/api/client/inspect.go b/api/client/inspect.go index 34be82e5a..02428ee23 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -20,7 +20,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template") cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var tmpl *template.Template if *tmplStr != "" { diff --git a/api/client/kill.go b/api/client/kill.go index d7e9a52e6..7ad1e5613 100644 --- a/api/client/kill.go +++ b/api/client/kill.go @@ -4,7 +4,6 @@ import ( "fmt" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdKill kills one or more running container using SIGKILL or a specified signal. @@ -15,7 +14,7 @@ func (cli *DockerCli) CmdKill(args ...string) error { signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container") cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var encounteredError error for _, name := range cmd.Args() { diff --git a/api/client/load.go b/api/client/load.go index e8eb8e251..7338c770d 100644 --- a/api/client/load.go +++ b/api/client/load.go @@ -5,7 +5,6 @@ import ( "os" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdLoad loads an image from a tar archive. @@ -18,7 +17,7 @@ func (cli *DockerCli) CmdLoad(args ...string) error { infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN") cmd.Require(flag.Exact, 0) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var ( input io.Reader = cli.in diff --git a/api/client/login.go b/api/client/login.go index 27e35d2be..b24ef7df7 100644 --- a/api/client/login.go +++ b/api/client/login.go @@ -14,7 +14,6 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" "github.com/docker/docker/registry" - "github.com/docker/docker/utils" ) // CmdLogin logs in or registers a user to a Docker registry service. @@ -32,7 +31,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { cmd.StringVar(&password, []string{"p", "-password"}, "", "Password") cmd.StringVar(&email, []string{"e", "-email"}, "", "Email") - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) serverAddress := registry.IndexServerAddress() if len(cmd.Args()) > 0 { diff --git a/api/client/logout.go b/api/client/logout.go index 5d9a77f2c..9282f22f0 100644 --- a/api/client/logout.go +++ b/api/client/logout.go @@ -5,7 +5,6 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/registry" - "github.com/docker/docker/utils" ) // CmdLogout logs a user out from a Docker registry. @@ -17,7 +16,7 @@ func (cli *DockerCli) CmdLogout(args ...string) error { cmd := cli.Subcmd("logout", "[SERVER]", "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServerAddress()+"\" is the default.", true) cmd.Require(flag.Max, 1) - utils.ParseFlags(cmd, args, false) + cmd.ParseFlags(args, false) serverAddress := registry.IndexServerAddress() if len(cmd.Args()) > 0 { serverAddress = cmd.Arg(0) diff --git a/api/client/logs.go b/api/client/logs.go index 7c4737279..9039ecf09 100644 --- a/api/client/logs.go +++ b/api/client/logs.go @@ -6,7 +6,6 @@ import ( "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdLogs fetches the logs of a given container. @@ -21,7 +20,7 @@ func (cli *DockerCli) CmdLogs(args ...string) error { ) cmd.Require(flag.Exact, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) name := cmd.Arg(0) diff --git a/api/client/pause.go b/api/client/pause.go index be722e9a4..6c807410b 100644 --- a/api/client/pause.go +++ b/api/client/pause.go @@ -4,7 +4,6 @@ import ( "fmt" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdPause pauses all processes within one or more containers. @@ -13,7 +12,7 @@ import ( func (cli *DockerCli) CmdPause(args ...string) error { cmd := cli.Subcmd("pause", "CONTAINER [CONTAINER...]", "Pause all processes within a container", true) cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, false) + cmd.ParseFlags(args, false) var encounteredError error for _, name := range cmd.Args() { diff --git a/api/client/port.go b/api/client/port.go index 574f7616b..a683db3f6 100644 --- a/api/client/port.go +++ b/api/client/port.go @@ -7,7 +7,6 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/nat" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdPort lists port mappings for a container. @@ -17,7 +16,7 @@ import ( func (cli *DockerCli) CmdPort(args ...string) error { cmd := cli.Subcmd("port", "CONTAINER [PRIVATE_PORT[/PROTO]]", "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true) cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) stream, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/json", nil, nil) if err != nil { diff --git a/api/client/ps.go b/api/client/ps.go index 62b82c50d..35d4279a6 100644 --- a/api/client/ps.go +++ b/api/client/ps.go @@ -43,7 +43,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) if *last == -1 && *nLatest { *last = 1 } diff --git a/api/client/pull.go b/api/client/pull.go index f1ba2c061..a554e1f45 100644 --- a/api/client/pull.go +++ b/api/client/pull.go @@ -19,7 +19,7 @@ func (cli *DockerCli) CmdPull(args ...string) error { allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository") cmd.Require(flag.Exact, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var ( v = url.Values{} diff --git a/api/client/push.go b/api/client/push.go index 7777cc2f9..a31a04ed4 100644 --- a/api/client/push.go +++ b/api/client/push.go @@ -7,7 +7,6 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/registry" - "github.com/docker/docker/utils" ) // CmdPush pushes an image or repository to the registry. @@ -17,7 +16,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { cmd := cli.Subcmd("push", "NAME[:TAG]", "Push an image or a repository to the registry", true) cmd.Require(flag.Exact, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) name := cmd.Arg(0) diff --git a/api/client/restart.go b/api/client/restart.go index 609079373..41b10676b 100644 --- a/api/client/restart.go +++ b/api/client/restart.go @@ -6,7 +6,6 @@ import ( "strconv" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdRestart restarts one or more running containers. @@ -17,7 +16,7 @@ func (cli *DockerCli) CmdRestart(args ...string) error { nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container") cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) v := url.Values{} v.Set("t", strconv.Itoa(*nSeconds)) diff --git a/api/client/rm.go b/api/client/rm.go index d6ed39b29..89b118254 100644 --- a/api/client/rm.go +++ b/api/client/rm.go @@ -5,7 +5,6 @@ import ( "net/url" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) func (cli *DockerCli) CmdRm(args ...string) error { @@ -15,7 +14,7 @@ func (cli *DockerCli) CmdRm(args ...string) error { force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)") cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) val := url.Values{} if *v { diff --git a/api/client/rmi.go b/api/client/rmi.go index 18659a9af..580c0b747 100644 --- a/api/client/rmi.go +++ b/api/client/rmi.go @@ -6,7 +6,6 @@ import ( "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdRmi removes all images with the specified name(s). @@ -20,7 +19,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error { ) cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) v := url.Values{} if *force { diff --git a/api/client/run.go b/api/client/run.go index b13ffd937..474c88f98 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -58,7 +58,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { config, hostConfig, cmd, err := runconfig.Parse(cmd, args) // just in case the Parse does not exit if err != nil { - utils.ReportError(cmd, err.Error(), true) + cmd.ReportError(err.Error(), true) } if len(hostConfig.Dns) > 0 { diff --git a/api/client/save.go b/api/client/save.go index e0cdd1c29..5d9d27615 100644 --- a/api/client/save.go +++ b/api/client/save.go @@ -7,7 +7,6 @@ import ( "os" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdSave saves one or more images to a tar archive. @@ -20,7 +19,7 @@ func (cli *DockerCli) CmdSave(args ...string) error { outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT") cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var ( output io.Writer = cli.out diff --git a/api/client/search.go b/api/client/search.go index 3c3de0eb3..beb9000d0 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -24,7 +24,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least x stars") cmd.Require(flag.Exact, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) name := cmd.Arg(0) v := url.Values{} diff --git a/api/client/start.go b/api/client/start.go index 554b7bcfa..66aa5150d 100644 --- a/api/client/start.go +++ b/api/client/start.go @@ -54,7 +54,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { ) cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) if *attach || *openStdin { if cmd.NArg() > 1 { diff --git a/api/client/stats.go b/api/client/stats.go index 6df22ff3d..bf9d3a814 100644 --- a/api/client/stats.go +++ b/api/client/stats.go @@ -13,7 +13,6 @@ import ( "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/units" - "github.com/docker/docker/utils" ) type containerStats struct { @@ -114,7 +113,7 @@ func (s *containerStats) Display(w io.Writer) error { func (cli *DockerCli) CmdStats(args ...string) error { cmd := cli.Subcmd("stats", "CONTAINER [CONTAINER...]", "Display a live stream of one or more containers' resource usage statistics", true) cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) names := cmd.Args() sort.Strings(names) diff --git a/api/client/stop.go b/api/client/stop.go index e03439c14..08a1f5ba1 100644 --- a/api/client/stop.go +++ b/api/client/stop.go @@ -6,7 +6,6 @@ import ( "strconv" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdStop stops one or more running containers. @@ -19,7 +18,7 @@ func (cli *DockerCli) CmdStop(args ...string) error { nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it") cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) v := url.Values{} v.Set("t", strconv.Itoa(*nSeconds)) diff --git a/api/client/tag.go b/api/client/tag.go index 5b4ebdb4c..56541f86d 100644 --- a/api/client/tag.go +++ b/api/client/tag.go @@ -6,7 +6,6 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/registry" - "github.com/docker/docker/utils" ) // CmdTag tags an image into a repository. @@ -17,7 +16,7 @@ func (cli *DockerCli) CmdTag(args ...string) error { force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force") cmd.Require(flag.Exact, 2) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var ( repository, tag = parsers.ParseRepositoryTag(cmd.Arg(1)) diff --git a/api/client/top.go b/api/client/top.go index 357a5ccc3..9de04cac6 100644 --- a/api/client/top.go +++ b/api/client/top.go @@ -8,7 +8,6 @@ import ( "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdTop displays the running processes of a container. @@ -18,7 +17,7 @@ func (cli *DockerCli) CmdTop(args ...string) error { cmd := cli.Subcmd("top", "CONTAINER [ps OPTIONS]", "Display the running processes of a container", true) cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) val := url.Values{} if cmd.NArg() > 1 { diff --git a/api/client/unpause.go b/api/client/unpause.go index c4ca412b0..bcecb4633 100644 --- a/api/client/unpause.go +++ b/api/client/unpause.go @@ -4,7 +4,6 @@ import ( "fmt" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdUnpause unpauses all processes within a container, for one or more containers. @@ -13,7 +12,7 @@ import ( func (cli *DockerCli) CmdUnpause(args ...string) error { cmd := cli.Subcmd("unpause", "CONTAINER [CONTAINER...]", "Unpause all processes within a container", true) cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, false) + cmd.ParseFlags(args, false) var encounteredError error for _, name := range cmd.Args() { diff --git a/api/client/version.go b/api/client/version.go index f3fea96a0..25a7e367e 100644 --- a/api/client/version.go +++ b/api/client/version.go @@ -9,7 +9,6 @@ import ( "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdVersion shows Docker version information. @@ -21,7 +20,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error { cmd := cli.Subcmd("version", "", "Show the Docker version information.", true) cmd.Require(flag.Exact, 0) - utils.ParseFlags(cmd, args, false) + cmd.ParseFlags(args, false) if dockerversion.VERSION != "" { fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) diff --git a/api/client/wait.go b/api/client/wait.go index 92c3b05dc..8f34b2452 100644 --- a/api/client/wait.go +++ b/api/client/wait.go @@ -4,7 +4,6 @@ import ( "fmt" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdWait blocks until a container stops, then prints its exit code. @@ -16,7 +15,7 @@ func (cli *DockerCli) CmdWait(args ...string) error { cmd := cli.Subcmd("wait", "CONTAINER [CONTAINER...]", "Block until a container stops, then print its exit code.", true) cmd.Require(flag.Min, 1) - utils.ParseFlags(cmd, args, true) + cmd.ParseFlags(args, true) var encounteredError error for _, name := range cmd.Args() { diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index 81369f88b..f2da1cd1b 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -1054,6 +1054,42 @@ func (f *FlagSet) Parse(arguments []string) error { return nil } +// ParseFlags is a utility function that adds a help flag if withHelp is true, +// calls cmd.Parse(args) and prints a relevant error message if there are +// incorrect number of arguments. It returns error only if error handling is +// set to ContinueOnError and parsing fails. If error handling is set to +// ExitOnError, it's safe to ignore the return value. +func (cmd *FlagSet) ParseFlags(args []string, withHelp bool) error { + var help *bool + if withHelp { + help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") + } + if err := cmd.Parse(args); err != nil { + return err + } + if help != nil && *help { + cmd.Usage() + // just in case Usage does not exit + os.Exit(0) + } + if str := cmd.CheckArgs(); str != "" { + cmd.ReportError(str, withHelp) + } + return nil +} + +func (cmd *FlagSet) ReportError(str string, withHelp bool) { + if withHelp { + if os.Args[0] == cmd.Name() { + str += ". See '" + os.Args[0] + " --help'" + } else { + str += ". See '" + os.Args[0] + " " + cmd.Name() + " --help'" + } + } + fmt.Fprintf(cmd.Out(), "docker: %s.\n", str) + os.Exit(1) +} + // Parsed reports whether f.Parse has been called. func (f *FlagSet) Parsed() bool { return f.parsed diff --git a/runconfig/exec.go b/runconfig/exec.go index 9390781a4..1bcdad159 100644 --- a/runconfig/exec.go +++ b/runconfig/exec.go @@ -5,7 +5,6 @@ import ( "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) type ExecConfig struct { @@ -50,7 +49,7 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { container string ) cmd.Require(flag.Min, 2) - if err := utils.ParseFlags(cmd, args, true); err != nil { + if err := cmd.ParseFlags(args, true); err != nil { return nil, err } container = cmd.Arg(0) diff --git a/runconfig/parse.go b/runconfig/parse.go index ccd8056cf..9f6459b23 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -96,7 +96,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe cmd.Require(flag.Min, 1) - if err := utils.ParseFlags(cmd, args, true); err != nil { + if err := cmd.ParseFlags(args, true); err != nil { return nil, nil, cmd, err } diff --git a/utils/flags.go b/utils/flags.go deleted file mode 100644 index 33c72279b..000000000 --- a/utils/flags.go +++ /dev/null @@ -1,45 +0,0 @@ -package utils - -import ( - "fmt" - "os" - - flag "github.com/docker/docker/pkg/mflag" -) - -// ParseFlags is a utility function that adds a help flag if withHelp is true, -// calls cmd.Parse(args) and prints a relevant error message if there are -// incorrect number of arguments. It returns error only if error handling is -// set to ContinueOnError and parsing fails. If error handling is set to -// ExitOnError, it's safe to ignore the return value. -// TODO: move this to a better package than utils -func ParseFlags(cmd *flag.FlagSet, args []string, withHelp bool) error { - var help *bool - if withHelp { - help = cmd.Bool([]string{"#help", "-help"}, false, "Print usage") - } - if err := cmd.Parse(args); err != nil { - return err - } - if help != nil && *help { - cmd.Usage() - // just in case Usage does not exit - os.Exit(0) - } - if str := cmd.CheckArgs(); str != "" { - ReportError(cmd, str, withHelp) - } - return nil -} - -func ReportError(cmd *flag.FlagSet, str string, withHelp bool) { - if withHelp { - if os.Args[0] == cmd.Name() { - str += ". See '" + os.Args[0] + " --help'" - } else { - str += ". See '" + os.Args[0] + " " + cmd.Name() + " --help'" - } - } - fmt.Fprintf(cmd.Out(), "docker: %s.\n", str) - os.Exit(1) -} From 7583b491250beac9caeeac13b7b68c4e0f03eb60 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Sun, 29 Mar 2015 13:35:36 +0800 Subject: [PATCH 161/999] Fix create volume in a directory which is a symbolic link Signed-off-by: Lei Jitang --- daemon/volumes.go | 9 ++++--- integration-cli/docker_cli_run_test.go | 36 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/daemon/volumes.go b/daemon/volumes.go index a4645dab2..7312264af 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -189,10 +189,13 @@ func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) if _, exists := container.Volumes[path]; exists { continue } - - if stat, err := os.Stat(filepath.Join(container.basefs, path)); err == nil { + realpath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, path), container.basefs) + if err != nil { + return nil, fmt.Errorf("failed to evaluate the absolute path of symlink") + } + if stat, err := os.Stat(realpath); err == nil { if !stat.IsDir() { - return nil, fmt.Errorf("file exists at %s, can't create volume there") + return nil, fmt.Errorf("file exists at %s, can't create volume there", realpath) } } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index b0fe914a3..e28901c7e 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -477,6 +477,42 @@ func TestRunWithVolumesFromExited(t *testing.T) { logDone("run - regression test for #4979 - volumes-from on exited container") } +// Test create volume in a dirctory which is a symbolic link +func TestRunCreateVolumesInSymlinkDir(t *testing.T) { + defer deleteAllContainers() + // This test has to create a file on host + hostFile := "/tmp/abcd" + cmd := exec.Command("touch", hostFile) + if out, _, err := runCommandWithOutput(cmd); err != nil { + t.Fatalf("failed to create file %s on host: %v, output: %q", hostFile, err, out) + } + defer func() { + cmd := exec.Command("rm", "-f", hostFile) + if out, _, err := runCommandWithOutput(cmd); err != nil { + t.Fatalf("failed to remove file %s on host: %v, output: %q", hostFile, err, out) + } + }() + // create symlink directory /home/test link to /tmp + cmd = exec.Command(dockerBinary, "run", "--name=test", "busybox", "ln", "-s", "/tmp", "/home/test") + if out, _, err := runCommandWithOutput(cmd); err != nil { + t.Fatalf("failed to run container: %v, output: %q", err, out) + } + cmd = exec.Command(dockerBinary, "commit", "test", "busybox:test") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to commit container: %v, output: %q", err, out) + } + cleanedImageID := stripTrailingCharacters(out) + defer deleteImages(cleanedImageID) + // directory /home/test is link to /tmp, /home/test/abcd==/tmp/abcd + cmd = exec.Command(dockerBinary, "run", "-v", "/home/test/abcd", "busybox", "touch", "/home/test/abcd/Hello") + if out, _, err = runCommandWithOutput(cmd); err != nil { + t.Fatalf("failed to create volume in symlink directory: %v, output %q", err, out) + } + + logDone("run - create volume in symlink directory") +} + // Regression test for #4830 func TestRunWithRelativePath(t *testing.T) { defer deleteAllContainers() From db877d8a425a3c063eedb1908ab1b50df1a33a57 Mon Sep 17 00:00:00 2001 From: unclejack Date: Sun, 29 Mar 2015 15:20:53 +0300 Subject: [PATCH 162/999] pkg/broadcastwriter: avoid alloc w/ WriteString Signed-off-by: Cristian Staretu --- pkg/broadcastwriter/broadcastwriter.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/broadcastwriter/broadcastwriter.go b/pkg/broadcastwriter/broadcastwriter.go index 1d3c3c5f1..2ee68944d 100644 --- a/pkg/broadcastwriter/broadcastwriter.go +++ b/pkg/broadcastwriter/broadcastwriter.go @@ -51,7 +51,7 @@ func (w *BroadcastWriter) Write(p []byte) (n int, err error) { for { line, err := w.buf.ReadString('\n') if err != nil { - w.buf.Write([]byte(line)) + w.buf.WriteString(line) break } for stream, writers := range w.streams { From e8edcf47b4b495070a3a3db7b2faeef91adf83f9 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Sun, 29 Mar 2015 05:42:48 -0700 Subject: [PATCH 163/999] Enable bash completion in build environment Installs and configures bash completion for Docker. Note that bash completion still has to be initialized by a custom .bashrc file. Signed-off-by: Harald Albers --- Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Dockerfile b/Dockerfile index b06407613..6c6e0b5bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,7 @@ RUN apt-get update && apt-get install -y \ apparmor \ aufs-tools \ automake \ + bash-completion \ btrfs-tools \ build-essential \ curl \ @@ -142,6 +143,9 @@ ENV DOCKER_BUILDTAGS apparmor selinux btrfs_noversion # Let us use a .bashrc file RUN ln -sfv $PWD/.bashrc ~/.bashrc +# Register Docker's bash completion. +RUN ln -sv $PWD/contrib/completion/bash/docker /etc/bash_completion.d/docker + # Get useful and necessary Hub images so we can "docker load" locally instead of pulling COPY contrib/download-frozen-image.sh /go/src/github.com/docker/docker/contrib/ RUN ./contrib/download-frozen-image.sh /docker-frozen-images \ From cf438a542e76279715dc7d414a11845725044e90 Mon Sep 17 00:00:00 2001 From: Harald Albers Date: Sun, 29 Mar 2015 09:24:08 -0700 Subject: [PATCH 164/999] Add missing filters to bash completion for docker images and docker ps Signed-off-by: Harald Albers --- contrib/completion/bash/docker | 40 +++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 2397cb59f..ddd8d9a2a 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -58,6 +58,18 @@ __docker_containers_unpauseable() { __docker_containers_all '.State.Paused' } +__docker_container_names() { + local containers=( $(__docker_q ps -aq --no-trunc) ) + local names=( $(__docker_q inspect --format '{{.Name}}' "${containers[@]}") ) + names=( "${names[@]#/}" ) # trim off the leading "/" from the container names + COMPREPLY=( $(compgen -W "${names[*]}" -- "$cur") ) +} + +__docker_container_ids() { + local containers=( $(__docker_q ps -aq) ) + COMPREPLY=( $(compgen -W "${containers[*]}" -- "$cur") ) +} + __docker_image_repos() { local repos="$(__docker_q images | awk 'NR>1 && $1 != "" { print $1 }')" COMPREPLY=( $(compgen -W "$repos" -- "$cur") ) @@ -437,7 +449,10 @@ _docker_history() { _docker_images() { case "$prev" in --filter|-f) - COMPREPLY=( $( compgen -W "dangling=true" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "dangling=true label=" -- "$cur" ) ) + if [ "$COMPREPLY" = "label=" ]; then + compopt -o nospace + fi return ;; esac @@ -447,17 +462,20 @@ _docker_images() { COMPREPLY=( $( compgen -W "true false" -- "${cur#=}" ) ) return ;; + *label=*) + return + ;; esac case "$cur" in -*) COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-trunc --quiet -q" -- "$cur" ) ) ;; + =) + return + ;; *) - local counter=$(__docker_pos_first_nonflag) - if [ $cword -eq $counter ]; then - __docker_image_repos - fi + __docker_image_repos ;; esac } @@ -616,7 +634,7 @@ _docker_ps() { __docker_containers_all ;; --filter|-f) - COMPREPLY=( $( compgen -S = -W "exited status" -- "$cur" ) ) + COMPREPLY=( $( compgen -S = -W "exited id label name status" -- "$cur" ) ) compopt -o nospace return ;; @@ -626,6 +644,16 @@ _docker_ps() { esac case "${words[$cword-2]}$prev=" in + *id=*) + cur="${cur#=}" + __docker_container_ids + return + ;; + *name=*) + cur="${cur#=}" + __docker_container_names + return + ;; *status=*) COMPREPLY=( $( compgen -W "exited paused restarting running" -- "${cur#=}" ) ) return From f1bbc1f34f98529f352130b5985ae5763e5b9c2e Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 29 Mar 2015 20:51:17 +0200 Subject: [PATCH 165/999] Refactor utils/tmpdir.go, fixes #11905 Signed-off-by: Antonio Murdaca --- daemon/daemon.go | 14 ++++++++++++-- utils/tmpdir.go | 16 ---------------- 2 files changed, 12 insertions(+), 18 deletions(-) delete mode 100644 utils/tmpdir.go diff --git a/daemon/daemon.go b/daemon/daemon.go index 7029478a4..2c8f04b88 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -857,8 +857,8 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) return nil, err } - // set up the TempDir to use a canonical path - tmp, err := utils.TempDir(config.Root) + // set up the tmpDir to use a canonical path + tmp, err := tempDir(config.Root) if err != nil { return nil, fmt.Errorf("Unable to get the TempDir under %s: %s", config.Root, err) } @@ -1246,6 +1246,16 @@ func (daemon *Daemon) ImageGetCached(imgID string, config *runconfig.Config) (*i return match, nil } +// tempDir returns the default directory to use for temporary files. +func tempDir(rootDir string) (string, error) { + var tmpDir string + if tmpDir = os.Getenv("DOCKER_TMPDIR"); tmpDir == "" { + tmpDir = filepath.Join(rootDir, "tmp") + } + err := os.MkdirAll(tmpDir, 0700) + return tmpDir, err +} + func checkKernel() error { // Check for unsupported kernel versions // FIXME: it would be cleaner to not test for specific versions, but rather diff --git a/utils/tmpdir.go b/utils/tmpdir.go deleted file mode 100644 index e200f340d..000000000 --- a/utils/tmpdir.go +++ /dev/null @@ -1,16 +0,0 @@ -package utils - -import ( - "os" - "path/filepath" -) - -// TempDir returns the default directory to use for temporary files. -func TempDir(rootDir string) (string, error) { - var tmpDir string - if tmpDir = os.Getenv("DOCKER_TMPDIR"); tmpDir == "" { - tmpDir = filepath.Join(rootDir, "tmp") - } - err := os.MkdirAll(tmpDir, 0700) - return tmpDir, err -} From 567f723721b7bac57a91d82d27472e568a101c4f Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Sun, 29 Mar 2015 03:48:59 -0700 Subject: [PATCH 166/999] Add Glossary to documentation Closes #11057 Signed-off-by: Ankush Agarwal --- docs/mkdocs.yml | 1 + docs/sources/project/glossary.md | 7 - docs/sources/reference/glossary.md | 201 +++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 7 deletions(-) delete mode 100644 docs/sources/project/glossary.md create mode 100644 docs/sources/reference/glossary.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 87bad208e..74b17a32f 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -122,6 +122,7 @@ pages: - ['reference/commandline/cli.md', 'Reference', 'Docker command line'] - ['reference/builder.md', 'Reference', 'Dockerfile'] - ['faq.md', 'Reference', 'FAQ'] +- ['reference/glossary.md', 'Reference', 'Glossary'] - ['reference/run.md', 'Reference', 'Run Reference'] - ['compose/cli.md', 'Reference', 'Compose command line'] - ['compose/yml.md', 'Reference', 'Compose yml'] diff --git a/docs/sources/project/glossary.md b/docs/sources/project/glossary.md deleted file mode 100644 index 5324cda15..000000000 --- a/docs/sources/project/glossary.md +++ /dev/null @@ -1,7 +0,0 @@ -page_title: Glossary -page_description: tbd -page_keywords: tbd - -## Glossary - -TBD \ No newline at end of file diff --git a/docs/sources/reference/glossary.md b/docs/sources/reference/glossary.md new file mode 100644 index 000000000..d33d01569 --- /dev/null +++ b/docs/sources/reference/glossary.md @@ -0,0 +1,201 @@ +page_title: Docker Glossary +page_description: Glossary of terms used around Docker +page_keywords: glossary, docker, terms, definitions + +# Glossary + +A list of terms used around the Docker project. + +## aufs + +aufs (advanced multi layered unification filesystem) is a Linux [filesystem](#filesystem) that +Docker supports as a storage backend. It implements the +[union mount](http://en.wikipedia.org/wiki/Union_mount) for Linux file systems. + +## boot2docker + +[boot2docker](http://boot2docker.io/) is a lightweight Linux distribution made +specifically to run Docker containers. It is a common choice for a [VM](#virtual-machine) +to run Docker on Windows and Mac OS X. + +boot2docker can also refer to the boot2docker management tool on Windows and +Mac OS X which manages the boot2docker VM. + +## btrfs + +btrfs (B-tree file system) is a Linux [filesystem](#filesystem) that Docker +supports as a storage backend. It is a [copy-on-write](http://en.wikipedia.org/wiki/Copy-on-write) +filesystem. + +## build + +build is the process of building Docker images using a [Dockerfile](#dockerfile). +The build uses a Dockerfile and a "context". The context is the set of files in the +directory in which the image is built. + +## cgroups + +cgroups is a Linux kernel feature that limits, accounts for, and isolates +the resource usage (CPU, memory, disk I/O, network, etc.) of a collection +of processes. Docker relies on cgroups to control and isolate resource limits. + +*Also known as : control groups* + +## Compose + +[Compose](https://github.com/docker/compose) is a tool for defining and +running complex applications with Docker. With compose, you define a +multi-container application in a single file, then spin your +application up in a single command which does everything that needs to +be done to get it running. + +*Also known as : docker-compose, fig* + +## container + +A container is a runtime instance of a [docker image](#image). + +A Docker container consists of + +- A Docker image +- Execution environment +- A standard set of instructions + +The concept is borrowed from Shipping Containers, which define a standard to ship +goods globally. Docker defines a standard to ship software. + +## data volume + +A data volume is a specially-designated directory within one or more containers +that bypasses the Union File System. Data volumes are designed to persist data, +independent of the container's life cycle. Docker therefore never automatically +delete volumes when you remove a container, nor will it "garbage collect" +volumes that are no longer referenced by a container. + + +## Docker + +The term Docker can refer to + +- The Docker project as a whole, which is a platform for developers and sysadmins to +develop, ship, and run applications +- The docker daemon process running on the host which manages images and containers + + +## Docker Hub + +The [Docker Hub](https://hub.docker.com/) is a centralized resource for working with +Docker and its components. It provides the following services: + +- Docker image hosting +- User authentication +- Automated image builds and work-flow tools such as build triggers and web hooks +- Integration with GitHub and BitBucket + + +## Dockerfile + +A Dockerfile is a text document that contains all the commands you would +normally execute manually in order to build a Docker image. Docker can +build images automatically by reading the instructions from a Dockerfile. + +## filesystem + +A file system is the method an operating system uses to name files +and assign them locations for efficient storage and retrieval. + +Examples : + +- Linux : ext4, aufs, btrfs, zfs +- Windows : NTFS +- OS X : HFS+ + +## image + +Docker images are the basis of [containers](#container). An Image is an +ordered collection of root filesystem changes and the corresponding +execution parameters for use within a container runtime. An image typically +contains a union of layered filesystems stacked on top of each other. An image +does not have state and it never changes. + +## libcontainer + +libcontainer provides a native Go implementation for creating containers with +namespaces, cgroups, capabilities, and filesystem access controls. It allows +you to manage the lifecycle of the container performing additional operations +after the container is created. + +## link + +links provide an interface to connect Docker containers running on the same host +to each other without exposing the hosts' network ports. When you set up a link, +you create a conduit between a source container and a recipient container. +The recipient can then access select data about the source. To create a link, +you can use the `--link` flag. + +## Machine + +[Machine](https://github.com/docker/machine) is a Docker tool which +makes it really easy to create Docker hosts on your computer, on +cloud providers and inside your own data center. It creates servers, +installs Docker on them, then configures the Docker client to talk to them. + +*Also known as : docker-machine* + +## overlay + +OverlayFS is a [filesystem](#filesystem) service for Linux which implements a +[union mount](http://en.wikipedia.org/wiki/Union_mount) for other file systems. +It is supported by the Docker daemon as a storage driver. + +## registry + +A Registry is a hosted service containing [repositories](#repository) of [images](#image) +which responds to the Registry API. + +The default registry can be accessed using a browser at [Docker Hub](#docker-hub) +or using the `docker search` command. + +## repository + +A repository is a set of Docker images. A repository can be shared by pushing it +to a [registry](#registry) server. The different images in the repository can be +labeled using [tags](#tag). + +Here is an example of the shared [nginx repository](https://registry.hub.docker.com/_/nginx/) +and its [tags](https://registry.hub.docker.com/_/nginx/tags/manage/) + +## Swarm + +[Swarm](https://github.com/docker/swarm) is a native clustering tool for Docker. +Swarm pools together several Docker hosts and exposes them as a single virtual +Docker host. It serves the standard Docker API, so any tool that already works +with Docker can now transparently scale up to multiple hosts. + +*Also known as : docker-swarm* + +## tag + +A tag is a label applied to a Docker image in a [repository](#repository). +tags are how various images in a repository are distinguished from each other. + +*Note : This label is not related to the key=value labels set for docker daemon* + +## Union file system + +Union file systems, or UnionFS, are file systems that operate by creating layers, making them +very lightweight and fast. Docker uses union file systems to provide the building +blocks for containers. + + +## Virtual Machine + +A Virtual Machine is a program that emulates a complete computer and imitates dedicated hardware. +It shares physical hardware resources with other users but isolates the operating system. The +end user has the same experience on a Virtual Machine as they would have on dedicated hardware. + +Compared to to containers, a Virtual Machine is heavier to run, provides more isolation, +gets its own set of resources and does minimal sharing. + +*Also known as : VM* + From 8bc330d8632ef0129b433b877a5e2fc88bb2eb39 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Sun, 29 Mar 2015 12:58:57 +0200 Subject: [PATCH 167/999] Docker cp handles resolv.conf, hostname & hosts, fixes #9998 Add a integration test TestCpSpecialFiles Signed-off-by: Vincent Demeester --- daemon/container.go | 11 +++++ integration-cli/docker_cli_cp_test.go | 64 +++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/daemon/container.go b/daemon/container.go index ef8667369..70abc6b96 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -944,6 +944,17 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { } } + // Check if this is a special one (resolv.conf, hostname, ..) + if resource == "etc/resolv.conf" { + basePath = container.ResolvConfPath + } + if resource == "etc/hostname" { + basePath = container.HostnamePath + } + if resource == "etc/hosts" { + basePath = container.HostsPath + } + stat, err := os.Stat(basePath) if err != nil { container.Unmount() diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index db5f36388..c20289261 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -384,6 +384,70 @@ func TestCpUnprivilegedUser(t *testing.T) { logDone("cp - unprivileged user") } +func TestCpSpecialFiles(t *testing.T) { + testRequires(t, SameHostDaemon) + + outDir, err := ioutil.TempDir("", "cp-test-special-files") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(outDir) + + out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo") + if err != nil || exitCode != 0 { + t.Fatal("failed to create a container", out, err) + } + + cleanedContainerID := stripTrailingCharacters(out) + defer deleteContainer(cleanedContainerID) + + out, _, err = dockerCmd(t, "wait", cleanedContainerID) + if err != nil || stripTrailingCharacters(out) != "0" { + t.Fatal("failed to set up container", out, err) + } + + // Copy actual /etc/resolv.conf + _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/etc/resolv.conf", outDir) + if err != nil { + t.Fatalf("couldn't copy from container: %s:%s %v", cleanedContainerID, "/etc/resolv.conf", err) + } + + expected, err := ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/resolv.conf") + actual, err := ioutil.ReadFile(outDir + "/resolv.conf") + + if !bytes.Equal(actual, expected) { + t.Fatalf("Expected copied file to be duplicate of the container resolvconf") + } + + // Copy actual /etc/hosts + _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/etc/hosts", outDir) + if err != nil { + t.Fatalf("couldn't copy from container: %s:%s %v", cleanedContainerID, "/etc/hosts", err) + } + + expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hosts") + actual, err = ioutil.ReadFile(outDir + "/hosts") + + if !bytes.Equal(actual, expected) { + t.Fatalf("Expected copied file to be duplicate of the container hosts") + } + + // Copy actual /etc/resolv.conf + _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/etc/hostname", outDir) + if err != nil { + t.Fatalf("couldn't copy from container: %s:%s %v", cleanedContainerID, "/etc/hostname", err) + } + + expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hostname") + actual, err = ioutil.ReadFile(outDir + "/hostname") + + if !bytes.Equal(actual, expected) { + t.Fatalf("Expected copied file to be duplicate of the container resolvconf") + } + + logDone("cp - special files (resolv.conf, hosts, hostname)") +} + func TestCpVolumePath(t *testing.T) { testRequires(t, SameHostDaemon) From 08331294bcabf90ce3d22dca729f206912d2a752 Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Mon, 30 Mar 2015 08:31:28 +0800 Subject: [PATCH 168/999] Fix a typo in daemon/networkdriver/ipallocator/allocator.go Signed-off-by: Yuan Sun --- daemon/networkdriver/ipallocator/allocator.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/networkdriver/ipallocator/allocator.go b/daemon/networkdriver/ipallocator/allocator.go index 62935e175..554dbdd5b 100644 --- a/daemon/networkdriver/ipallocator/allocator.go +++ b/daemon/networkdriver/ipallocator/allocator.go @@ -128,7 +128,7 @@ func (allocated *allocatedMap) checkIP(ip net.IP) (net.IP, error) { } // return an available ip if one is currently available. If not, -// return the next available ip for the nextwork +// return the next available ip for the network func (allocated *allocatedMap) getNextIP() (net.IP, error) { pos := big.NewInt(0).Set(allocated.last) allRange := big.NewInt(0).Sub(allocated.end, allocated.begin) From f5310f403da573e28477e142e5f70efe0045b323 Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Mon, 30 Mar 2015 09:07:58 +0800 Subject: [PATCH 169/999] Verify MaximumRetryCount=0 if the restart policy is always. Signed-off-by: Yuan Sun --- integration-cli/docker_cli_restart_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/integration-cli/docker_cli_restart_test.go b/integration-cli/docker_cli_restart_test.go index 7b97c0725..99b559244 100644 --- a/integration-cli/docker_cli_restart_test.go +++ b/integration-cli/docker_cli_restart_test.go @@ -191,6 +191,16 @@ func TestRestartPolicyAlways(t *testing.T) { t.Fatalf("Container restart policy name is %s, expected %s", name, "always") } + MaximumRetryCount, err := inspectField(id, "HostConfig.RestartPolicy.MaximumRetryCount") + if err != nil { + t.Fatal(err) + } + + // MaximumRetryCount=0 if the restart policy is always + if MaximumRetryCount != "0" { + t.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "0") + } + logDone("restart - recording restart policy name for --restart=always") } From 0995ab5946b068a14cba05be8b2693c4181097e3 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 29 Mar 2015 15:51:08 +0200 Subject: [PATCH 170/999] Refactor utils/http.go, fixes #11899 Signed-off-by: Antonio Murdaca --- pkg/requestdecorator/README.md | 2 + pkg/requestdecorator/requestdecorator.go | 172 ++++++++++++++ pkg/requestdecorator/requestdecorator_test.go | 222 ++++++++++++++++++ registry/auth.go | 12 +- registry/endpoint.go | 6 +- registry/httpfactory.go | 44 ++-- registry/registry_test.go | 6 +- registry/session.go | 7 +- registry/token.go | 4 +- utils/http.go | 168 ------------- 10 files changed, 428 insertions(+), 215 deletions(-) create mode 100644 pkg/requestdecorator/README.md create mode 100644 pkg/requestdecorator/requestdecorator.go create mode 100644 pkg/requestdecorator/requestdecorator_test.go delete mode 100644 utils/http.go diff --git a/pkg/requestdecorator/README.md b/pkg/requestdecorator/README.md new file mode 100644 index 000000000..76f8ca798 --- /dev/null +++ b/pkg/requestdecorator/README.md @@ -0,0 +1,2 @@ +This package provides helper functions for decorating a request with user agent +versions, auth, meta headers. diff --git a/pkg/requestdecorator/requestdecorator.go b/pkg/requestdecorator/requestdecorator.go new file mode 100644 index 000000000..c236e3fe3 --- /dev/null +++ b/pkg/requestdecorator/requestdecorator.go @@ -0,0 +1,172 @@ +// Package requestdecorator provides helper functions to decorate a request with +// user agent versions, auth, meta headers. +package requestdecorator + +import ( + "errors" + "io" + "net/http" + "strings" + + "github.com/Sirupsen/logrus" +) + +var ( + ErrNilRequest = errors.New("request cannot be nil") +) + +// UAVersionInfo is used to model UserAgent versions. +type UAVersionInfo struct { + Name string + Version string +} + +func NewUAVersionInfo(name, version string) UAVersionInfo { + return UAVersionInfo{ + Name: name, + Version: version, + } +} + +func (vi *UAVersionInfo) isValid() bool { + const stopChars = " \t\r\n/" + name := vi.Name + vers := vi.Version + if len(name) == 0 || strings.ContainsAny(name, stopChars) { + return false + } + if len(vers) == 0 || strings.ContainsAny(vers, stopChars) { + return false + } + return true +} + +// Convert versions to a string and append the string to the string base. +// +// Each UAVersionInfo will be converted to a string in the format of +// "product/version", where the "product" is get from the name field, while +// version is get from the version field. Several pieces of verson information +// will be concatinated and separated by space. +func appendVersions(base string, versions ...UAVersionInfo) string { + if len(versions) == 0 { + return base + } + + verstrs := make([]string, 0, 1+len(versions)) + if len(base) > 0 { + verstrs = append(verstrs, base) + } + + for _, v := range versions { + if !v.isValid() { + continue + } + verstrs = append(verstrs, v.Name+"/"+v.Version) + } + return strings.Join(verstrs, " ") +} + +// Decorator is used to change an instance of +// http.Request. It could be used to add more header fields, +// change body, etc. +type Decorator interface { + // ChangeRequest() changes the request accordingly. + // The changed request will be returned or err will be non-nil + // if an error occur. + ChangeRequest(req *http.Request) (newReq *http.Request, err error) +} + +// UserAgentDecorator appends the product/version to the user agent field +// of a request. +type UserAgentDecorator struct { + Versions []UAVersionInfo +} + +func (h *UserAgentDecorator) ChangeRequest(req *http.Request) (*http.Request, error) { + if req == nil { + return req, ErrNilRequest + } + + userAgent := appendVersions(req.UserAgent(), h.Versions...) + if len(userAgent) > 0 { + req.Header.Set("User-Agent", userAgent) + } + return req, nil +} + +type MetaHeadersDecorator struct { + Headers map[string][]string +} + +func (h *MetaHeadersDecorator) ChangeRequest(req *http.Request) (*http.Request, error) { + if h.Headers == nil { + return req, ErrNilRequest + } + for k, v := range h.Headers { + req.Header[k] = v + } + return req, nil +} + +type AuthDecorator struct { + login string + password string +} + +func NewAuthDecorator(login, password string) Decorator { + return &AuthDecorator{ + login: login, + password: password, + } +} + +func (self *AuthDecorator) ChangeRequest(req *http.Request) (*http.Request, error) { + if req == nil { + return req, ErrNilRequest + } + req.SetBasicAuth(self.login, self.password) + return req, nil +} + +// RequestFactory creates an HTTP request +// and applies a list of decorators on the request. +type RequestFactory struct { + decorators []Decorator +} + +func NewRequestFactory(d ...Decorator) *RequestFactory { + return &RequestFactory{ + decorators: d, + } +} + +func (f *RequestFactory) AddDecorator(d ...Decorator) { + f.decorators = append(f.decorators, d...) +} + +func (f *RequestFactory) GetDecorators() []Decorator { + return f.decorators +} + +// NewRequest() creates a new *http.Request, +// applies all decorators in the Factory on the request, +// then applies decorators provided by d on the request. +func (h *RequestFactory) NewRequest(method, urlStr string, body io.Reader, d ...Decorator) (*http.Request, error) { + req, err := http.NewRequest(method, urlStr, body) + if err != nil { + return nil, err + } + + // By default, a nil factory should work. + if h == nil { + return req, nil + } + for _, dec := range h.decorators { + req, _ = dec.ChangeRequest(req) + } + for _, dec := range d { + req, _ = dec.ChangeRequest(req) + } + logrus.Debugf("%v -- HEADERS: %v", req.URL, req.Header) + return req, err +} diff --git a/pkg/requestdecorator/requestdecorator_test.go b/pkg/requestdecorator/requestdecorator_test.go new file mode 100644 index 000000000..5f1c2565a --- /dev/null +++ b/pkg/requestdecorator/requestdecorator_test.go @@ -0,0 +1,222 @@ +package requestdecorator + +import ( + "net/http" + "strings" + "testing" +) + +func TestUAVersionInfo(t *testing.T) { + uavi := NewUAVersionInfo("foo", "bar") + if !uavi.isValid() { + t.Fatalf("UAVersionInfo should be valid") + } + uavi = NewUAVersionInfo("", "bar") + if uavi.isValid() { + t.Fatalf("Expected UAVersionInfo to be invalid") + } + uavi = NewUAVersionInfo("foo", "") + if uavi.isValid() { + t.Fatalf("Expected UAVersionInfo to be invalid") + } +} + +func TestUserAgentDecorator(t *testing.T) { + httpVersion := make([]UAVersionInfo, 2) + httpVersion = append(httpVersion, NewUAVersionInfo("testname", "testversion")) + httpVersion = append(httpVersion, NewUAVersionInfo("name", "version")) + uad := &UserAgentDecorator{ + Versions: httpVersion, + } + + req, err := http.NewRequest("GET", "/something", strings.NewReader("test")) + if err != nil { + t.Fatal(err) + } + reqDecorated, err := uad.ChangeRequest(req) + if err != nil { + t.Fatal(err) + } + + if reqDecorated.Header.Get("User-Agent") != "testname/testversion name/version" { + t.Fatalf("Request should have User-Agent 'testname/testversion name/version'") + } +} + +func TestUserAgentDecoratorErr(t *testing.T) { + httpVersion := make([]UAVersionInfo, 0) + uad := &UserAgentDecorator{ + Versions: httpVersion, + } + + var req *http.Request + _, err := uad.ChangeRequest(req) + if err == nil { + t.Fatalf("Expected to get ErrNilRequest instead no error was returned") + } +} + +func TestMetaHeadersDecorator(t *testing.T) { + var headers = map[string][]string{ + "key1": {"value1"}, + "key2": {"value2"}, + } + mhd := &MetaHeadersDecorator{ + Headers: headers, + } + + req, err := http.NewRequest("GET", "/something", strings.NewReader("test")) + if err != nil { + t.Fatal(err) + } + reqDecorated, err := mhd.ChangeRequest(req) + if err != nil { + t.Fatal(err) + } + + v, ok := reqDecorated.Header["key1"] + if !ok { + t.Fatalf("Expected to have header key1") + } + if v[0] != "value1" { + t.Fatalf("Expected value for key1 isn't value1") + } + + v, ok = reqDecorated.Header["key2"] + if !ok { + t.Fatalf("Expected to have header key2") + } + if v[0] != "value2" { + t.Fatalf("Expected value for key2 isn't value2") + } +} + +func TestMetaHeadersDecoratorErr(t *testing.T) { + mhd := &MetaHeadersDecorator{} + + var req *http.Request + _, err := mhd.ChangeRequest(req) + if err == nil { + t.Fatalf("Expected to get ErrNilRequest instead no error was returned") + } +} + +func TestAuthDecorator(t *testing.T) { + ad := NewAuthDecorator("test", "password") + + req, err := http.NewRequest("GET", "/something", strings.NewReader("test")) + if err != nil { + t.Fatal(err) + } + reqDecorated, err := ad.ChangeRequest(req) + if err != nil { + t.Fatal(err) + } + + username, password, ok := reqDecorated.BasicAuth() + if !ok { + t.Fatalf("Cannot retrieve basic auth info from request") + } + if username != "test" { + t.Fatalf("Expected username to be test, got %s", username) + } + if password != "password" { + t.Fatalf("Expected password to be password, got %s", password) + } +} + +func TestAuthDecoratorErr(t *testing.T) { + ad := &AuthDecorator{} + + var req *http.Request + _, err := ad.ChangeRequest(req) + if err == nil { + t.Fatalf("Expected to get ErrNilRequest instead no error was returned") + } +} + +func TestRequestFactory(t *testing.T) { + ad := NewAuthDecorator("test", "password") + httpVersion := make([]UAVersionInfo, 2) + httpVersion = append(httpVersion, NewUAVersionInfo("testname", "testversion")) + httpVersion = append(httpVersion, NewUAVersionInfo("name", "version")) + uad := &UserAgentDecorator{ + Versions: httpVersion, + } + + requestFactory := NewRequestFactory(ad, uad) + + if dlen := requestFactory.GetDecorators(); len(dlen) != 2 { + t.Fatalf("Expected to have two decorators, got %d", dlen) + } + + req, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test")) + if err != nil { + t.Fatal(err) + } + + username, password, ok := req.BasicAuth() + if !ok { + t.Fatalf("Cannot retrieve basic auth info from request") + } + if username != "test" { + t.Fatalf("Expected username to be test, got %s", username) + } + if password != "password" { + t.Fatalf("Expected password to be password, got %s", password) + } + if req.Header.Get("User-Agent") != "testname/testversion name/version" { + t.Fatalf("Request should have User-Agent 'testname/testversion name/version'") + } +} + +func TestRequestFactoryNewRequestWithDecorators(t *testing.T) { + ad := NewAuthDecorator("test", "password") + + requestFactory := NewRequestFactory(ad) + + if dlen := requestFactory.GetDecorators(); len(dlen) != 1 { + t.Fatalf("Expected to have one decorators, got %d", dlen) + } + + ad2 := NewAuthDecorator("test2", "password2") + + req, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test"), ad2) + if err != nil { + t.Fatal(err) + } + + username, password, ok := req.BasicAuth() + if !ok { + t.Fatalf("Cannot retrieve basic auth info from request") + } + if username != "test2" { + t.Fatalf("Expected username to be test, got %s", username) + } + if password != "password2" { + t.Fatalf("Expected password to be password, got %s", password) + } +} + +func TestRequestFactoryAddDecorator(t *testing.T) { + requestFactory := NewRequestFactory() + + if dlen := requestFactory.GetDecorators(); len(dlen) != 0 { + t.Fatalf("Expected to have zero decorators, got %d", dlen) + } + + ad := NewAuthDecorator("test", "password") + requestFactory.AddDecorator(ad) + + if dlen := requestFactory.GetDecorators(); len(dlen) != 1 { + t.Fatalf("Expected to have one decorators, got %d", dlen) + } +} + +func TestRequestFactoryNil(t *testing.T) { + var requestFactory RequestFactory + _, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test")) + if err != nil { + t.Fatalf("Expected not to get and error, got %s", err) + } +} diff --git a/registry/auth.go b/registry/auth.go index eaecc0f26..2c37f7f64 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -14,7 +14,7 @@ import ( "time" "github.com/Sirupsen/logrus" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/requestdecorator" ) const ( @@ -225,7 +225,7 @@ func SaveConfig(configFile *ConfigFile) error { } // Login tries to register/login to the registry server. -func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) { +func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { // Separates the v2 registry login logic from the v1 logic. if registryEndpoint.Version == APIVersion2 { return loginV2(authConfig, registryEndpoint, factory) @@ -235,7 +235,7 @@ func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HT } // loginV1 tries to register/login to the v1 registry server. -func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) { +func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { var ( status string reqBody []byte @@ -348,7 +348,7 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils. // now, users should create their account through other means like directly from a web page // served by the v2 registry service provider. Whether this will be supported in the future // is to be determined. -func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) { +func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint) var ( err error @@ -381,7 +381,7 @@ func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils. return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors) } -func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *utils.HTTPRequestFactory) error { +func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil) if err != nil { return err @@ -402,7 +402,7 @@ func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, regis return nil } -func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *utils.HTTPRequestFactory) error { +func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint, client, factory) if err != nil { return err diff --git a/registry/endpoint.go b/registry/endpoint.go index b883d36d0..69a718e12 100644 --- a/registry/endpoint.go +++ b/registry/endpoint.go @@ -11,8 +11,8 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/requestdecorator" "github.com/docker/docker/registry/v2" - "github.com/docker/docker/utils" ) // for mocking in unit tests @@ -162,7 +162,7 @@ func (e *Endpoint) Ping() (RegistryInfo, error) { return RegistryInfo{}, fmt.Errorf("unable to ping registry endpoint %s\nv2 ping attempt failed with error: %s\n v1 ping attempt failed with error: %s", e, errV2, errV1) } -func (e *Endpoint) pingV1(factory *utils.HTTPRequestFactory) (RegistryInfo, error) { +func (e *Endpoint) pingV1(factory *requestdecorator.RequestFactory) (RegistryInfo, error) { logrus.Debugf("attempting v1 ping for registry endpoint %s", e) if e.String() == IndexServerAddress() { @@ -216,7 +216,7 @@ func (e *Endpoint) pingV1(factory *utils.HTTPRequestFactory) (RegistryInfo, erro return info, nil } -func (e *Endpoint) pingV2(factory *utils.HTTPRequestFactory) (RegistryInfo, error) { +func (e *Endpoint) pingV2(factory *requestdecorator.RequestFactory) (RegistryInfo, error) { logrus.Debugf("attempting v2 ping for registry endpoint %s", e) req, err := factory.NewRequest("GET", e.Path(""), nil) diff --git a/registry/httpfactory.go b/registry/httpfactory.go index a4fea3822..f1b89e582 100644 --- a/registry/httpfactory.go +++ b/registry/httpfactory.go @@ -5,42 +5,26 @@ import ( "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/pkg/parsers/kernel" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/requestdecorator" ) -func HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory { +func HTTPRequestFactory(metaHeaders map[string][]string) *requestdecorator.RequestFactory { // FIXME: this replicates the 'info' job. - httpVersion := make([]utils.VersionInfo, 0, 4) - httpVersion = append(httpVersion, &simpleVersionInfo{"docker", dockerversion.VERSION}) - httpVersion = append(httpVersion, &simpleVersionInfo{"go", runtime.Version()}) - httpVersion = append(httpVersion, &simpleVersionInfo{"git-commit", dockerversion.GITCOMMIT}) + httpVersion := make([]requestdecorator.UAVersionInfo, 0, 4) + httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("docker", dockerversion.VERSION)) + httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("go", runtime.Version())) + httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("git-commit", dockerversion.GITCOMMIT)) if kernelVersion, err := kernel.GetKernelVersion(); err == nil { - httpVersion = append(httpVersion, &simpleVersionInfo{"kernel", kernelVersion.String()}) + httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("kernel", kernelVersion.String())) } - httpVersion = append(httpVersion, &simpleVersionInfo{"os", runtime.GOOS}) - httpVersion = append(httpVersion, &simpleVersionInfo{"arch", runtime.GOARCH}) - ud := utils.NewHTTPUserAgentDecorator(httpVersion...) - md := &utils.HTTPMetaHeadersDecorator{ + httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("os", runtime.GOOS)) + httpVersion = append(httpVersion, requestdecorator.NewUAVersionInfo("arch", runtime.GOARCH)) + uad := &requestdecorator.UserAgentDecorator{ + Versions: httpVersion, + } + mhd := &requestdecorator.MetaHeadersDecorator{ Headers: metaHeaders, } - factory := utils.NewHTTPRequestFactory(ud, md) + factory := requestdecorator.NewRequestFactory(uad, mhd) return factory } - -// simpleVersionInfo is a simple implementation of -// the interface VersionInfo, which is used -// to provide version information for some product, -// component, etc. It stores the product name and the version -// in string and returns them on calls to Name() and Version(). -type simpleVersionInfo struct { - name string - version string -} - -func (v *simpleVersionInfo) Name() string { - return v.name -} - -func (v *simpleVersionInfo) Version() string { - return v.version -} diff --git a/registry/registry_test.go b/registry/registry_test.go index d96630d90..a066de9f8 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/requestdecorator" ) var ( @@ -25,7 +25,7 @@ func spawnTestRegistrySession(t *testing.T) *Session { if err != nil { t.Fatal(err) } - r, err := NewSession(authConfig, utils.NewHTTPRequestFactory(), endpoint, true) + r, err := NewSession(authConfig, requestdecorator.NewRequestFactory(), endpoint, true) if err != nil { t.Fatal(err) } @@ -40,7 +40,7 @@ func TestPublicSession(t *testing.T) { if err != nil { t.Fatal(err) } - r, err := NewSession(authConfig, utils.NewHTTPRequestFactory(), endpoint, true) + r, err := NewSession(authConfig, requestdecorator.NewRequestFactory(), endpoint, true) if err != nil { t.Fatal(err) } diff --git a/registry/session.go b/registry/session.go index 1d70eff9a..4682a5074 100644 --- a/registry/session.go +++ b/registry/session.go @@ -19,19 +19,20 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/httputils" + "github.com/docker/docker/pkg/requestdecorator" "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/utils" ) type Session struct { authConfig *AuthConfig - reqFactory *utils.HTTPRequestFactory + reqFactory *requestdecorator.RequestFactory indexEndpoint *Endpoint jar *cookiejar.Jar timeout TimeoutType } -func NewSession(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) { +func NewSession(authConfig *AuthConfig, factory *requestdecorator.RequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) { r = &Session{ authConfig: authConfig, indexEndpoint: endpoint, @@ -55,7 +56,7 @@ func NewSession(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, endpo } if info.Standalone { logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", r.indexEndpoint.String()) - dec := utils.NewHTTPAuthDecorator(authConfig.Username, authConfig.Password) + dec := requestdecorator.NewAuthDecorator(authConfig.Username, authConfig.Password) factory.AddDecorator(dec) } } diff --git a/registry/token.go b/registry/token.go index c79a8ca6c..b03bd891b 100644 --- a/registry/token.go +++ b/registry/token.go @@ -8,14 +8,14 @@ import ( "net/url" "strings" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/requestdecorator" ) type tokenResponse struct { Token string `json:"token"` } -func getToken(username, password string, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *utils.HTTPRequestFactory) (token string, err error) { +func getToken(username, password string, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) (token string, err error) { realm, ok := params["realm"] if !ok { return "", errors.New("no realm specified for token auth challenge") diff --git a/utils/http.go b/utils/http.go deleted file mode 100644 index 01251d9ac..000000000 --- a/utils/http.go +++ /dev/null @@ -1,168 +0,0 @@ -package utils - -import ( - "io" - "net/http" - "strings" - - "github.com/Sirupsen/logrus" -) - -// VersionInfo is used to model entities which has a version. -// It is basically a tupple with name and version. -type VersionInfo interface { - Name() string - Version() string -} - -func validVersion(version VersionInfo) bool { - const stopChars = " \t\r\n/" - name := version.Name() - vers := version.Version() - if len(name) == 0 || strings.ContainsAny(name, stopChars) { - return false - } - if len(vers) == 0 || strings.ContainsAny(vers, stopChars) { - return false - } - return true -} - -// Convert versions to a string and append the string to the string base. -// -// Each VersionInfo will be converted to a string in the format of -// "product/version", where the "product" is get from the Name() method, while -// version is get from the Version() method. Several pieces of verson information -// will be concatinated and separated by space. -func appendVersions(base string, versions ...VersionInfo) string { - if len(versions) == 0 { - return base - } - - verstrs := make([]string, 0, 1+len(versions)) - if len(base) > 0 { - verstrs = append(verstrs, base) - } - - for _, v := range versions { - if !validVersion(v) { - continue - } - verstrs = append(verstrs, v.Name()+"/"+v.Version()) - } - return strings.Join(verstrs, " ") -} - -// HTTPRequestDecorator is used to change an instance of -// http.Request. It could be used to add more header fields, -// change body, etc. -type HTTPRequestDecorator interface { - // ChangeRequest() changes the request accordingly. - // The changed request will be returned or err will be non-nil - // if an error occur. - ChangeRequest(req *http.Request) (newReq *http.Request, err error) -} - -// HTTPUserAgentDecorator appends the product/version to the user agent field -// of a request. -type HTTPUserAgentDecorator struct { - versions []VersionInfo -} - -func NewHTTPUserAgentDecorator(versions ...VersionInfo) HTTPRequestDecorator { - return &HTTPUserAgentDecorator{ - versions: versions, - } -} - -func (h *HTTPUserAgentDecorator) ChangeRequest(req *http.Request) (newReq *http.Request, err error) { - if req == nil { - return req, nil - } - - userAgent := appendVersions(req.UserAgent(), h.versions...) - if len(userAgent) > 0 { - req.Header.Set("User-Agent", userAgent) - } - return req, nil -} - -type HTTPMetaHeadersDecorator struct { - Headers map[string][]string -} - -func (h *HTTPMetaHeadersDecorator) ChangeRequest(req *http.Request) (newReq *http.Request, err error) { - if h.Headers == nil { - return req, nil - } - for k, v := range h.Headers { - req.Header[k] = v - } - return req, nil -} - -type HTTPAuthDecorator struct { - login string - password string -} - -func NewHTTPAuthDecorator(login, password string) HTTPRequestDecorator { - return &HTTPAuthDecorator{ - login: login, - password: password, - } -} - -func (self *HTTPAuthDecorator) ChangeRequest(req *http.Request) (*http.Request, error) { - req.SetBasicAuth(self.login, self.password) - return req, nil -} - -// HTTPRequestFactory creates an HTTP request -// and applies a list of decorators on the request. -type HTTPRequestFactory struct { - decorators []HTTPRequestDecorator -} - -func NewHTTPRequestFactory(d ...HTTPRequestDecorator) *HTTPRequestFactory { - return &HTTPRequestFactory{ - decorators: d, - } -} - -func (self *HTTPRequestFactory) AddDecorator(d ...HTTPRequestDecorator) { - self.decorators = append(self.decorators, d...) -} - -func (self *HTTPRequestFactory) GetDecorators() []HTTPRequestDecorator { - return self.decorators -} - -// NewRequest() creates a new *http.Request, -// applies all decorators in the HTTPRequestFactory on the request, -// then applies decorators provided by d on the request. -func (h *HTTPRequestFactory) NewRequest(method, urlStr string, body io.Reader, d ...HTTPRequestDecorator) (*http.Request, error) { - req, err := http.NewRequest(method, urlStr, body) - if err != nil { - return nil, err - } - - // By default, a nil factory should work. - if h == nil { - return req, nil - } - for _, dec := range h.decorators { - req, err = dec.ChangeRequest(req) - if err != nil { - return nil, err - } - } - for _, dec := range d { - req, err = dec.ChangeRequest(req) - if err != nil { - return nil, err - } - } - logrus.Debugf("%v -- HEADERS: %v", req.URL, req.Header) - return req, err -} From ddd27616e0abe44c7589b15de3fdbf5bb043ea6a Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Sat, 28 Mar 2015 18:32:00 -0700 Subject: [PATCH 171/999] Explain --net=host option usage Fixes #11630 Signed-off-by: Ankush Agarwal --- docs/sources/reference/run.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index b1d0e92bd..3fed51a18 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -279,6 +279,13 @@ container. The container's hostname will match the hostname on the host system. Publishing ports and linking to other containers will not work when sharing the host's network stack. +Compared to the default `bridge` mode, the `host` mode gives *significantly* +better networking performance since it uses the host's native networking stack +wheras the bridge has to go through one level of virtualizaion through the +docker daemon. It is recommended to run containers in this mode when their +networking performance is critical, for example, a production Load Balancer +or a High Performance Web Server. + > **Note**: `--net="host"` gives the container full access to local system > services such as D-bus and is therefore considered insecure. From d1306e63e478cf8fb7ee093c828763e27a5b0cc9 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 30 Mar 2015 11:46:44 -0700 Subject: [PATCH 172/999] Fix AUFS logrus build errors on import Signed-off-by: Michael Crosby --- daemon/graphdriver/aufs/aufs.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/graphdriver/aufs/aufs.go b/daemon/graphdriver/aufs/aufs.go index fd854d0f5..b319d2471 100644 --- a/daemon/graphdriver/aufs/aufs.go +++ b/daemon/graphdriver/aufs/aufs.go @@ -480,14 +480,14 @@ func useDirperm() bool { enableDirpermLock.Do(func() { base, err := ioutil.TempDir("", "docker-aufs-base") if err != nil { - log.Errorf("error checking dirperm1: %v", err) + logrus.Errorf("error checking dirperm1: %v", err) return } defer os.RemoveAll(base) union, err := ioutil.TempDir("", "docker-aufs-union") if err != nil { - log.Errorf("error checking dirperm1: %v", err) + logrus.Errorf("error checking dirperm1: %v", err) return } defer os.RemoveAll(union) @@ -498,7 +498,7 @@ func useDirperm() bool { } enableDirperm = true if err := Unmount(union); err != nil { - log.Errorf("error checking dirperm1: failed to unmount %v", err) + logrus.Errorf("error checking dirperm1: failed to unmount %v", err) } }) return enableDirperm From 851c64725d0b1b37e51fa0d0df744bbe82ad4c7b Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Mon, 30 Mar 2015 11:19:12 -0700 Subject: [PATCH 173/999] Compress layers on push to a v2 registry When buffering to file add support for compressing the tar contents. Since digest should be computed while writing buffer, include digest creation during buffer. Signed-off-by: Derek McGowan (github: dmcgowan) --- graph/graph.go | 24 ++++++++++++++++++------ graph/push.go | 9 +-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/graph/graph.go b/graph/graph.go index 994ce3028..087a6f093 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -1,6 +1,8 @@ package graph import ( + "compress/gzip" + "crypto/sha256" "fmt" "io" "io/ioutil" @@ -13,6 +15,7 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/distribution/digest" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/daemon/graphdriver" "github.com/docker/docker/image" @@ -242,18 +245,27 @@ func (graph *Graph) newTempFile() (*os.File, error) { return ioutil.TempFile(tmp, "") } -func bufferToFile(f *os.File, src io.Reader) (int64, error) { - n, err := io.Copy(f, src) +func bufferToFile(f *os.File, src io.Reader) (int64, digest.Digest, error) { + var ( + h = sha256.New() + w = gzip.NewWriter(io.MultiWriter(f, h)) + ) + _, err := io.Copy(w, src) + w.Close() if err != nil { - return n, err + return 0, "", err } if err = f.Sync(); err != nil { - return n, err + return 0, "", err + } + n, err := f.Seek(0, os.SEEK_CUR) + if err != nil { + return 0, "", err } if _, err := f.Seek(0, 0); err != nil { - return n, err + return 0, "", err } - return n, nil + return n, digest.NewDigest("sha256", h), nil } // setupInitLayer populates a directory with mountpoints suitable diff --git a/graph/push.go b/graph/push.go index 927c13c9d..767f118c5 100644 --- a/graph/push.go +++ b/graph/push.go @@ -1,7 +1,6 @@ package graph import ( - "crypto/sha256" "encoding/json" "errors" "fmt" @@ -13,7 +12,6 @@ import ( "sync" "github.com/Sirupsen/logrus" - "github.com/docker/distribution/digest" "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" @@ -465,12 +463,7 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint * os.Remove(tf.Name()) }() - h := sha256.New() - size, err := bufferToFile(tf, io.TeeReader(arch, h)) - if err != nil { - return "", err - } - dgst := digest.NewDigest("sha256", h) + size, dgst, err := bufferToFile(tf, arch) // Send the layer logrus.Debugf("rendered layer for %s of [%d] size", img.ID, size) From 711e580320a5da5cb33e8f03a17d355b4fb127c1 Mon Sep 17 00:00:00 2001 From: Tiffany Low Date: Fri, 27 Mar 2015 18:38:00 -0700 Subject: [PATCH 174/999] Upgrade logrus to v0.7.2 - Daemon logs now report to millisecond resolution Signed-off-by: Tiffany Low --- docker/daemon.go | 4 +++ hack/vendor.sh | 2 +- .../github.com/Sirupsen/logrus/CHANGELOG.md | 3 +++ .../src/github.com/Sirupsen/logrus/README.md | 12 +++++---- .../Sirupsen/logrus/text_formatter.go | 15 ++++++++--- .../Sirupsen/logrus/text_formatter_test.go | 26 ++++++++++++++++++- 6 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md diff --git a/docker/daemon.go b/docker/daemon.go index c4b43d915..6ea96e889 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -20,6 +20,7 @@ import ( "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/pkg/timeutils" "github.com/docker/docker/registry" "github.com/docker/docker/utils" ) @@ -81,6 +82,9 @@ func mainDaemon() { flag.Usage() return } + + logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: timeutils.RFC3339NanoFixed}) + eng := engine.New() signal.Trap(eng.Shutdown) diff --git a/hack/vendor.sh b/hack/vendor.sh index 984cee301..f5df649cd 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -53,7 +53,7 @@ clone hg code.google.com/p/gosqlite 74691fb6f837 clone git github.com/docker/libtrust 230dfd18c232 -clone git github.com/Sirupsen/logrus v0.7.1 +clone git github.com/Sirupsen/logrus v0.7.2 clone git github.com/go-fsnotify/fsnotify v1.0.4 diff --git a/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md b/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md new file mode 100644 index 000000000..566a6fbd9 --- /dev/null +++ b/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md @@ -0,0 +1,3 @@ +# 0.7.2 + +formatter/text: Add configuration option for time format (#158) diff --git a/vendor/src/github.com/Sirupsen/logrus/README.md b/vendor/src/github.com/Sirupsen/logrus/README.md index 512f26e5e..bf09541e8 100644 --- a/vendor/src/github.com/Sirupsen/logrus/README.md +++ b/vendor/src/github.com/Sirupsen/logrus/README.md @@ -37,11 +37,13 @@ attached, the output is compatible with the [logfmt](http://godoc.org/github.com/kr/logfmt) format: ```text -time="2014-04-20 15:36:23.830442383 -0400 EDT" level="info" msg="A group of walrus emerges from the ocean" animal="walrus" size=10 -time="2014-04-20 15:36:23.830584199 -0400 EDT" level="warning" msg="The group's number increased tremendously!" omg=true number=122 -time="2014-04-20 15:36:23.830596521 -0400 EDT" level="info" msg="A giant walrus appears!" animal="walrus" size=10 -time="2014-04-20 15:36:23.830611837 -0400 EDT" level="info" msg="Tremendously sized cow enters the ocean." animal="walrus" size=9 -time="2014-04-20 15:36:23.830626464 -0400 EDT" level="fatal" msg="The ice breaks!" omg=true number=100 +time="2015-03-26T01:27:38-04:00" level=debug msg="Started observing beach" animal=walrus number=8 +time="2015-03-26T01:27:38-04:00" level=info msg="A group of walrus emerges from the ocean" animal=walrus size=10 +time="2015-03-26T01:27:38-04:00" level=warning msg="The group's number increased tremendously!" number=122 omg=true +time="2015-03-26T01:27:38-04:00" level=debug msg="Temperature changes" temperature=-4 +time="2015-03-26T01:27:38-04:00" level=panic msg="It's over 9000!" animal=orca size=9009 +time="2015-03-26T01:27:38-04:00" level=fatal msg="The ice breaks!" err=&{0x2082280c0 map[animal:orca size:9009] 2015-03-26 01:27:38.441574009 -0400 EDT panic It's over 9000!} number=100 omg=true +exit status 1 ``` #### Example diff --git a/vendor/src/github.com/Sirupsen/logrus/text_formatter.go b/vendor/src/github.com/Sirupsen/logrus/text_formatter.go index 0a06a1105..d3687ba25 100644 --- a/vendor/src/github.com/Sirupsen/logrus/text_formatter.go +++ b/vendor/src/github.com/Sirupsen/logrus/text_formatter.go @@ -18,8 +18,9 @@ const ( ) var ( - baseTimestamp time.Time - isTerminal bool + baseTimestamp time.Time + isTerminal bool + defaultTimestampFormat = time.RFC3339 ) func init() { @@ -46,6 +47,9 @@ type TextFormatter struct { // the time passed since beginning of execution. FullTimestamp bool + // Timestamp format to use for display, if a full timestamp is printed + TimestampFormat string + // The fields are sorted by default for a consistent output. For applications // that log extremely frequently and don't use the JSON formatter this may not // be desired. @@ -68,11 +72,14 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { isColored := (f.ForceColors || isTerminal) && !f.DisableColors + if f.TimestampFormat == "" { + f.TimestampFormat = defaultTimestampFormat + } if isColored { f.printColored(b, entry, keys) } else { if !f.DisableTimestamp { - f.appendKeyValue(b, "time", entry.Time.Format(time.RFC3339)) + f.appendKeyValue(b, "time", entry.Time.Format(f.TimestampFormat)) } f.appendKeyValue(b, "level", entry.Level.String()) f.appendKeyValue(b, "msg", entry.Message) @@ -103,7 +110,7 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin if !f.FullTimestamp { fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d] %-44s ", levelColor, levelText, miniTS(), entry.Message) } else { - fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(time.RFC3339), entry.Message) + fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %-44s ", levelColor, levelText, entry.Time.Format(f.TimestampFormat), entry.Message) } for _, k := range keys { v := entry.Data[k] diff --git a/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go b/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go index 28a949907..e25a44f67 100644 --- a/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go +++ b/vendor/src/github.com/Sirupsen/logrus/text_formatter_test.go @@ -3,8 +3,8 @@ package logrus import ( "bytes" "errors" - "testing" + "time" ) func TestQuoting(t *testing.T) { @@ -33,5 +33,29 @@ func TestQuoting(t *testing.T) { checkQuoting(true, errors.New("invalid argument")) } +func TestTimestampFormat(t *testing.T) { + checkTimeStr := func(format string) { + customFormatter := &TextFormatter{DisableColors: true, TimestampFormat: format} + customStr, _ := customFormatter.Format(WithField("test", "test")) + timeStart := bytes.Index(customStr, ([]byte)("time=")) + timeEnd := bytes.Index(customStr, ([]byte)("level=")) + timeStr := customStr[timeStart+5 : timeEnd-1] + if timeStr[0] == '"' && timeStr[len(timeStr)-1] == '"' { + timeStr = timeStr[1 : len(timeStr)-1] + } + if format == "" { + format = time.RFC3339 + } + _, e := time.Parse(format, (string)(timeStr)) + if e != nil { + t.Errorf("time string \"%s\" did not match provided time format \"%s\": %s", timeStr, format, e) + } + } + + checkTimeStr("2006-01-02T15:04:05.000000000Z07:00") + checkTimeStr("Mon Jan _2 15:04:05 2006") + checkTimeStr("") +} + // TODO add tests for sorting etc., this requires a parser for the text // formatter output. From 489ab77f4aea9bc3e7ada751cab5d827040b1e8b Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 30 Mar 2015 13:27:38 -0700 Subject: [PATCH 175/999] Use proper wait function for --pid=host Signed-off-by: Alexander Morozov --- daemon/execdriver/native/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 030c3b546..5938ffc3d 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -162,7 +162,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba logrus.Warnf("Your kernel does not support OOM notifications: %s", err) } waitF := p.Wait - if nss := cont.Config().Namespaces; nss.Contains(configs.NEWPID) { + if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) { // we need such hack for tracking processes with inerited fds, // because cmd.Wait() waiting for all streams to be copied waitF = waitInPIDHost(p, cont) From 5a1e5cf8c9bff51f6314754c039bf7511e2fb613 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 30 Mar 2015 13:28:34 -0700 Subject: [PATCH 176/999] Get child processes before main process die Signed-off-by: Alexander Morozov --- daemon/execdriver/native/driver.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 5938ffc3d..98c7ef3e3 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -189,6 +189,8 @@ func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*o return nil, err } + processes, err := c.Processes() + process, err := os.FindProcess(pid) s, err := process.Wait() if err != nil { From cc46ae8eaef2ce441e5f35412c4ee6ccbb65c2de Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 30 Mar 2015 23:07:43 +0200 Subject: [PATCH 177/999] Remove duplicate assignment Signed-off-by: Antonio Murdaca --- daemon/execdriver/driver.go | 1 - 1 file changed, 1 deletion(-) diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index e937de3be..637f7d779 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -173,7 +173,6 @@ func InitContainer(c *Command) *configs.Config { container.Hostname = getEnv("HOSTNAME", c.ProcessConfig.Env) container.Cgroups.Name = c.ID container.Cgroups.AllowedDevices = c.AllowedDevices - container.Readonlyfs = c.ReadonlyRootfs container.Devices = c.AutoCreatedDevices container.Rootfs = c.Rootfs container.Readonlyfs = c.ReadonlyRootfs From 1d23bae785ce8c94227def97b673307e1743db88 Mon Sep 17 00:00:00 2001 From: Abhishek Chanda Date: Mon, 30 Mar 2015 14:54:17 -0700 Subject: [PATCH 178/999] Remove unused daemon.LogToDisk function Fixes #11937 Signed-off-by: Abhishek Chanda --- daemon/daemon.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 2c8f04b88..ba9c73ce6 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -320,15 +320,6 @@ func (daemon *Daemon) ensureName(container *Container) error { return nil } -func (daemon *Daemon) LogToDisk(src *broadcastwriter.BroadcastWriter, dst, stream string) error { - log, err := os.OpenFile(dst, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0600) - if err != nil { - return err - } - src.AddWriter(log, stream) - return nil -} - func (daemon *Daemon) restore() error { var ( debug = (os.Getenv("DEBUG") != "" || os.Getenv("TEST") != "") From 4a2ef6c8053d5b0eb768297af8609505198c7187 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 30 Mar 2015 15:27:46 -0700 Subject: [PATCH 179/999] fix basicAuth function not in go1.3.3 Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) --- pkg/requestdecorator/requestdecorator_test.go | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/pkg/requestdecorator/requestdecorator_test.go b/pkg/requestdecorator/requestdecorator_test.go index 5f1c2565a..12e2194f0 100644 --- a/pkg/requestdecorator/requestdecorator_test.go +++ b/pkg/requestdecorator/requestdecorator_test.go @@ -1,11 +1,45 @@ package requestdecorator import ( + "encoding/base64" "net/http" "strings" "testing" ) +// The following 2 functions are here for 1.3.3 support +// After we drop 1.3.3 support we can use the functions supported +// in go v1.4.0 + +// BasicAuth returns the username and password provided in the request's +// Authorization header, if the request uses HTTP Basic Authentication. +// See RFC 2617, Section 2. +func basicAuth(r *http.Request) (username, password string, ok bool) { + auth := r.Header.Get("Authorization") + if auth == "" { + return + } + return parseBasicAuth(auth) +} + +// parseBasicAuth parses an HTTP Basic Authentication string. +// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true). +func parseBasicAuth(auth string) (username, password string, ok bool) { + const prefix = "Basic " + if !strings.HasPrefix(auth, prefix) { + return + } + c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) + if err != nil { + return + } + cs := string(c) + s := strings.IndexByte(cs, ':') + if s < 0 { + return + } + return cs[:s], cs[s+1:], true +} + func TestUAVersionInfo(t *testing.T) { uavi := NewUAVersionInfo("foo", "bar") if !uavi.isValid() { @@ -113,7 +147,7 @@ func TestAuthDecorator(t *testing.T) { t.Fatal(err) } - username, password, ok := reqDecorated.BasicAuth() + username, password, ok := basicAuth(reqDecorated) if !ok { t.Fatalf("Cannot retrieve basic auth info from request") } @@ -155,7 +189,7 @@ func TestRequestFactory(t *testing.T) { t.Fatal(err) } - username, password, ok := req.BasicAuth() + username, password, ok := basicAuth(req) if !ok { t.Fatalf("Cannot retrieve basic auth info from request") } @@ -186,7 +220,7 @@ func TestRequestFactoryNewRequestWithDecorators(t *testing.T) { t.Fatal(err) } - username, password, ok := req.BasicAuth() + username, password, ok := basicAuth(req) if !ok { t.Fatalf("Cannot retrieve basic auth info from request") } From f468bbb7e8c89204bd5d8f346ecec4606b9f3b31 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 30 Mar 2015 13:29:33 -0700 Subject: [PATCH 180/999] Do not mask *exec.ExitError Fix #11764 Signed-off-by: Alexander Morozov --- daemon/execdriver/native/driver.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 98c7ef3e3..816207ed3 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -169,11 +169,11 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba } ps, err := waitF() if err != nil { - if err, ok := err.(*exec.ExitError); !ok { + execErr, ok := err.(*exec.ExitError) + if !ok { return execdriver.ExitStatus{ExitCode: -1}, err - } else { - ps = err.ProcessState } + ps = execErr.ProcessState } cont.Destroy() @@ -194,13 +194,12 @@ func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*o process, err := os.FindProcess(pid) s, err := process.Wait() if err != nil { - if err, ok := err.(*exec.ExitError); !ok { + execErr, ok := err.(*exec.ExitError) + if !ok { return s, err - } else { - s = err.ProcessState } + s = execErr.ProcessState } - processes, err := c.Processes() if err != nil { return s, err } From 17ecbcf8ff051575928a6e9fb13be0b034b3090d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 30 Mar 2015 16:29:00 -0700 Subject: [PATCH 181/999] Update libcontainer to c8512754166539461fd860451ff Signed-off-by: Michael Crosby --- hack/vendor.sh | 2 +- .../libcontainer/cgroups/fs/apply_raw.go | 8 +- .../cgroups/systemd/apply_systemd.go | 31 ++++++- .../docker/libcontainer/container_linux.go | 13 +-- .../docker/libcontainer/init_linux.go | 22 +++-- .../libcontainer/integration/exec_test.go | 85 +++++++++++++++++++ .../docker/libcontainer/nsinit/README.md | 57 +++++++++++++ .../github.com/docker/libcontainer/process.go | 4 + .../docker/libcontainer/process_linux.go | 17 +++- 9 files changed, 215 insertions(+), 24 deletions(-) create mode 100644 vendor/src/github.com/docker/libcontainer/nsinit/README.md diff --git a/hack/vendor.sh b/hack/vendor.sh index f5df649cd..0246f03cc 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -75,7 +75,7 @@ rm -rf src/github.com/docker/distribution mkdir -p src/github.com/docker/distribution mv tmp-digest src/github.com/docker/distribution/digest -clone git github.com/docker/libcontainer a6044b701c166fe538fc760f9e2dcea3d737cd2a +clone git github.com/docker/libcontainer c8512754166539461fd860451ff1a0af7491c197 # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) rm -rf src/github.com/docker/libcontainer/vendor eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')" diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go index c771245da..0a2d76bcd 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go @@ -220,16 +220,16 @@ func getCgroupData(c *configs.Cgroup, pid int) (*data, error) { }, nil } -func (raw *data) parent(subsystem string) (string, error) { +func (raw *data) parent(subsystem, mountpoint string) (string, error) { initPath, err := cgroups.GetInitCgroupDir(subsystem) if err != nil { return "", err } - return filepath.Join(raw.root, subsystem, initPath), nil + return filepath.Join(mountpoint, initPath), nil } func (raw *data) path(subsystem string) (string, error) { - _, err := cgroups.FindCgroupMountpoint(subsystem) + mnt, err := cgroups.FindCgroupMountpoint(subsystem) // If we didn't mount the subsystem, there is no point we make the path. if err != nil { return "", err @@ -240,7 +240,7 @@ func (raw *data) path(subsystem string) (string, error) { return filepath.Join(raw.root, subsystem, raw.cgroup), nil } - parent, err := raw.parent(subsystem) + parent, err := raw.parent(subsystem, mnt) if err != nil { return "", err } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go index f35364069..85ee5db06 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go @@ -43,6 +43,10 @@ var subsystems = map[string]subsystem{ "freezer": &fs.FreezerGroup{}, } +const ( + testScopeWait = 4 +) + var ( connLock sync.Mutex theConn *systemd.Conn @@ -86,16 +90,41 @@ func UseSystemd() bool { } } + // Ensure the scope name we use doesn't exist. Use the Pid to + // avoid collisions between multiple libcontainer users on a + // single host. + scope := fmt.Sprintf("libcontainer-%d-systemd-test-default-dependencies.scope", os.Getpid()) + testScopeExists := true + for i := 0; i <= testScopeWait; i++ { + if _, err := theConn.StopUnit(scope, "replace"); err != nil { + if dbusError, ok := err.(dbus.Error); ok { + if strings.Contains(dbusError.Name, "org.freedesktop.systemd1.NoSuchUnit") { + testScopeExists = false + break + } + } + } + time.Sleep(time.Millisecond) + } + + // Bail out if we can't kill this scope without testing for DefaultDependencies + if testScopeExists { + return hasStartTransientUnit + } + // Assume StartTransientUnit on a scope allows DefaultDependencies hasTransientDefaultDependencies = true ddf := newProp("DefaultDependencies", false) - if _, err := theConn.StartTransientUnit("docker-systemd-test-default-dependencies.scope", "replace", ddf); err != nil { + if _, err := theConn.StartTransientUnit(scope, "replace", ddf); err != nil { if dbusError, ok := err.(dbus.Error); ok { if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") { hasTransientDefaultDependencies = false } } } + + // Not critical because of the stop unit logic above. + theConn.StopUnit(scope, "replace") } return hasStartTransientUnit } diff --git a/vendor/src/github.com/docker/libcontainer/container_linux.go b/vendor/src/github.com/docker/libcontainer/container_linux.go index c44c8dacc..54d40617e 100644 --- a/vendor/src/github.com/docker/libcontainer/container_linux.go +++ b/vendor/src/github.com/docker/libcontainer/container_linux.go @@ -193,12 +193,13 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, func (c *linuxContainer) newInitConfig(process *Process) *initConfig { return &initConfig{ - Config: c.config, - Args: process.Args, - Env: process.Env, - User: process.User, - Cwd: process.Cwd, - Console: process.consolePath, + Config: c.config, + Args: process.Args, + Env: process.Env, + User: process.User, + Cwd: process.Cwd, + Console: process.consolePath, + Capabilities: process.Capabilities, } } diff --git a/vendor/src/github.com/docker/libcontainer/init_linux.go b/vendor/src/github.com/docker/libcontainer/init_linux.go index aa95423e5..0468b2e93 100644 --- a/vendor/src/github.com/docker/libcontainer/init_linux.go +++ b/vendor/src/github.com/docker/libcontainer/init_linux.go @@ -40,13 +40,14 @@ type network struct { // initConfig is used for transferring parameters from Exec() to Init() type initConfig struct { - Args []string `json:"args"` - Env []string `json:"env"` - Cwd string `json:"cwd"` - User string `json:"user"` - Config *configs.Config `json:"config"` - Console string `json:"console"` - Networks []*network `json:"network"` + Args []string `json:"args"` + Env []string `json:"env"` + Cwd string `json:"cwd"` + Capabilities []string `json:"capabilities"` + User string `json:"user"` + Config *configs.Config `json:"config"` + Console string `json:"console"` + Networks []*network `json:"network"` } type initer interface { @@ -99,7 +100,12 @@ func finalizeNamespace(config *initConfig) error { if err := utils.CloseExecFrom(3); err != nil { return err } - w, err := newCapWhitelist(config.Config.Capabilities) + + capabilities := config.Config.Capabilities + if config.Capabilities != nil { + capabilities = config.Capabilities + } + w, err := newCapWhitelist(capabilities) if err != nil { return err } diff --git a/vendor/src/github.com/docker/libcontainer/integration/exec_test.go b/vendor/src/github.com/docker/libcontainer/integration/exec_test.go index b68cb739c..4afff77de 100644 --- a/vendor/src/github.com/docker/libcontainer/integration/exec_test.go +++ b/vendor/src/github.com/docker/libcontainer/integration/exec_test.go @@ -4,6 +4,7 @@ import ( "bytes" "io/ioutil" "os" + "strconv" "strings" "testing" @@ -395,6 +396,90 @@ func TestProcessEnv(t *testing.T) { } } +func TestProcessCaps(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + + factory, err := libcontainer.New(root, libcontainer.Cgroupfs) + if err != nil { + t.Fatal(err) + } + + container, err := factory.Create("test", config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + processCaps := append(config.Capabilities, "NET_ADMIN") + + var stdout bytes.Buffer + pconfig := libcontainer.Process{ + Args: []string{"sh", "-c", "cat /proc/self/status"}, + Env: standardEnvironment, + Capabilities: processCaps, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&pconfig) + if err != nil { + t.Fatal(err) + } + + // Wait for process + waitProcess(&pconfig, t) + + outputStatus := string(stdout.Bytes()) + if err != nil { + t.Fatal(err) + } + + lines := strings.Split(outputStatus, "\n") + + effectiveCapsLine := "" + for _, l := range lines { + line := strings.TrimSpace(l) + if strings.Contains(line, "CapEff:") { + effectiveCapsLine = line + break + } + } + + if effectiveCapsLine == "" { + t.Fatal("Couldn't find effective caps: ", outputStatus) + } + + parts := strings.Split(effectiveCapsLine, ":") + effectiveCapsStr := strings.TrimSpace(parts[1]) + + effectiveCaps, err := strconv.ParseUint(effectiveCapsStr, 16, 64) + if err != nil { + t.Fatal("Could not parse effective caps", err) + } + + var netAdminMask uint64 + var netAdminBit uint + netAdminBit = 12 // from capability.h + netAdminMask = 1 << netAdminBit + if effectiveCaps&netAdminMask != netAdminMask { + t.Fatal("CAP_NET_ADMIN is not set as expected") + } +} + func TestFreeze(t *testing.T) { if testing.Short() { return diff --git a/vendor/src/github.com/docker/libcontainer/nsinit/README.md b/vendor/src/github.com/docker/libcontainer/nsinit/README.md new file mode 100644 index 000000000..f321e2271 --- /dev/null +++ b/vendor/src/github.com/docker/libcontainer/nsinit/README.md @@ -0,0 +1,57 @@ +## nsinit + +`nsinit` is a cli application which demonstrates the use of libcontainer. +It is able to spawn new containers or join existing containers. + +### How to build? + +First to add the `libcontainer/vendor` into your GOPATH. It's because something related with this [issue](https://github.com/docker/libcontainer/issues/210). + +``` +export GOPATH=$GOPATH:/your/path/to/libcontainer/vendor +``` + +Then get into the nsinit folder and get the imported file. Use `make` command to make the nsinit binary. + +``` +cd libcontainer/nsinit +go get +make +``` + +We have finished compiling the nsinit package, but a root filesystem must be provided for use along with a container configuration file. + +Choose a proper place to run your container. For example we use `/busybox`. + +``` +mkdir /busybox +curl -sSL 'https://github.com/jpetazzo/docker-busybox/raw/buildroot-2014.11/rootfs.tar' | tar -xC /busybox +``` + +Then you may need to write a configure file named `container.json` in the `/busybox` folder. +Environment, networking, and different capabilities for the container are specified in this file. +The configuration is used for each process executed inside the container +See the `sample_configs` folder for examples of what the container configuration should look like. + +``` +cp libcontainer/sample_configs/minimal.json /busybox/container.json +cd /busybox +``` + +Now the nsinit is ready to work. +To execute `/bin/bash` in the current directory as a container just run the following **as root**: +```bash +nsinit exec --tty /bin/bash +``` + +If you wish to spawn another process inside the container while your +current bash session is running, run the same command again to +get another bash shell (or change the command). If the original +process (PID 1) dies, all other processes spawned inside the container +will be killed and the namespace will be removed. + +You can identify if a process is running in a container by +looking to see if `state.json` is in the root of the directory. + +You may also specify an alternate root place where +the `container.json` file is read and where the `state.json` file will be saved. diff --git a/vendor/src/github.com/docker/libcontainer/process.go b/vendor/src/github.com/docker/libcontainer/process.go index 12f90daf7..82fcff8c4 100644 --- a/vendor/src/github.com/docker/libcontainer/process.go +++ b/vendor/src/github.com/docker/libcontainer/process.go @@ -41,6 +41,10 @@ type Process struct { // consolePath is the path to the console allocated to the container. consolePath string + // Capabilities specify the capabilities to keep when executing the process inside the container + // All capbilities not specified will be dropped from the processes capability mask + Capabilities []string + ops processOperations } diff --git a/vendor/src/github.com/docker/libcontainer/process_linux.go b/vendor/src/github.com/docker/libcontainer/process_linux.go index 5aab5a7f5..1c74b6549 100644 --- a/vendor/src/github.com/docker/libcontainer/process_linux.go +++ b/vendor/src/github.com/docker/libcontainer/process_linux.go @@ -4,6 +4,7 @@ package libcontainer import ( "encoding/json" + "errors" "io" "os" "os/exec" @@ -44,8 +45,12 @@ func (p *setnsProcess) startTime() (string, error) { return system.GetProcessStartTime(p.pid()) } -func (p *setnsProcess) signal(s os.Signal) error { - return p.cmd.Process.Signal(s) +func (p *setnsProcess) signal(sig os.Signal) error { + s, ok := sig.(syscall.Signal) + if !ok { + return errors.New("os: unsupported signal type") + } + return syscall.Kill(p.cmd.Process.Pid, s) } func (p *setnsProcess) start() (err error) { @@ -235,6 +240,10 @@ func (p *initProcess) createNetworkInterfaces() error { return nil } -func (p *initProcess) signal(s os.Signal) error { - return p.cmd.Process.Signal(s) +func (p *initProcess) signal(sig os.Signal) error { + s, ok := sig.(syscall.Signal) + if !ok { + return errors.New("os: unsupported signal type") + } + return syscall.Kill(p.cmd.Process.Pid, s) } From 43a50b06187e324e43b26003cace0f351a5fb78c Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 30 Mar 2015 17:14:46 -0700 Subject: [PATCH 182/999] Refactor port allocator to not have ANY global state Signed-off-by: Michael Crosby --- .../portallocator/portallocator.go | 139 ++++++++---------- .../portallocator/portallocator_test.go | 25 ++-- 2 files changed, 73 insertions(+), 91 deletions(-) diff --git a/daemon/networkdriver/portallocator/portallocator.go b/daemon/networkdriver/portallocator/portallocator.go index e2bb9ee56..c1f414b67 100644 --- a/daemon/networkdriver/portallocator/portallocator.go +++ b/daemon/networkdriver/portallocator/portallocator.go @@ -16,59 +16,14 @@ const ( DefaultPortRangeEnd = 65535 ) -var ( - beginPortRange = DefaultPortRangeStart - endPortRange = DefaultPortRangeEnd -) - -type portMap struct { - p map[int]struct{} - last int -} - -func newPortMap() *portMap { - return &portMap{ - p: map[int]struct{}{}, - last: endPortRange, - } -} - -type protoMap map[string]*portMap - -func newProtoMap() protoMap { - return protoMap{ - "tcp": newPortMap(), - "udp": newPortMap(), - } -} - type ipMapping map[string]protoMap var ( ErrAllPortsAllocated = errors.New("all ports are allocated") ErrUnknownProtocol = errors.New("unknown protocol") + defaultIP = net.ParseIP("0.0.0.0") ) -var ( - defaultIP = net.ParseIP("0.0.0.0") - - DefaultPortAllocator = New() - RequestPort = DefaultPortAllocator.RequestPort - ReleasePort = DefaultPortAllocator.ReleasePort - ReleaseAll = DefaultPortAllocator.ReleaseAll -) - -type PortAllocator struct { - mutex sync.Mutex - ipMap ipMapping -} - -func New() *PortAllocator { - return &PortAllocator{ - ipMap: ipMapping{}, - } -} - type ErrPortAlreadyAllocated struct { ip string port int @@ -81,32 +36,6 @@ func NewErrPortAlreadyAllocated(ip string, port int) ErrPortAlreadyAllocated { } } -func init() { - const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range" - portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", beginPortRange, endPortRange) - - file, err := os.Open(portRangeKernelParam) - if err != nil { - logrus.Warnf("port allocator - %s due to error: %v", portRangeFallback, err) - return - } - var start, end int - n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end) - if n != 2 || err != nil { - if err == nil { - err = fmt.Errorf("unexpected count of parsed numbers (%d)", n) - } - logrus.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err) - return - } - beginPortRange = start - endPortRange = end -} - -func PortRange() (int, int) { - return beginPortRange, endPortRange -} - func (e ErrPortAlreadyAllocated) IP() string { return e.ip } @@ -123,6 +52,51 @@ func (e ErrPortAlreadyAllocated) Error() string { return fmt.Sprintf("Bind for %s:%d failed: port is already allocated", e.ip, e.port) } +type ( + PortAllocator struct { + mutex sync.Mutex + ipMap ipMapping + Begin int + End int + } + portMap struct { + p map[int]struct{} + begin, end int + last int + } + protoMap map[string]*portMap +) + +func New() *PortAllocator { + start, end, err := getDynamicPortRange() + if err != nil { + logrus.Warn(err) + start, end = DefaultPortRangeStart, DefaultPortRangeEnd + } + return &PortAllocator{ + ipMap: ipMapping{}, + Begin: start, + End: end, + } +} + +func getDynamicPortRange() (start int, end int, err error) { + const portRangeKernelParam = "/proc/sys/net/ipv4/ip_local_port_range" + portRangeFallback := fmt.Sprintf("using fallback port range %d-%d", DefaultPortRangeStart, DefaultPortRangeEnd) + file, err := os.Open(portRangeKernelParam) + if err != nil { + return 0, 0, fmt.Errorf("port allocator - %s due to error: %v", portRangeFallback, err) + } + n, err := fmt.Fscanf(bufio.NewReader(file), "%d\t%d", &start, &end) + if n != 2 || err != nil { + if err == nil { + err = fmt.Errorf("unexpected count of parsed numbers (%d)", n) + } + return 0, 0, fmt.Errorf("port allocator - failed to parse system ephemeral port range from %s - %s: %v", portRangeKernelParam, portRangeFallback, err) + } + return start, end, nil +} + // RequestPort requests new port from global ports pool for specified ip and proto. // If port is 0 it returns first free port. Otherwise it cheks port availability // in pool and return that port or error if port is already busy. @@ -140,7 +114,11 @@ func (p *PortAllocator) RequestPort(ip net.IP, proto string, port int) (int, err ipstr := ip.String() protomap, ok := p.ipMap[ipstr] if !ok { - protomap = newProtoMap() + protomap = protoMap{ + "tcp": p.newPortMap(), + "udp": p.newPortMap(), + } + p.ipMap[ipstr] = protomap } mapping := protomap[proto] @@ -175,6 +153,15 @@ func (p *PortAllocator) ReleasePort(ip net.IP, proto string, port int) error { return nil } +func (p *PortAllocator) newPortMap() *portMap { + return &portMap{ + p: map[int]struct{}{}, + begin: p.Begin, + end: p.End, + last: p.End, + } +} + // ReleaseAll releases all ports for all ips. func (p *PortAllocator) ReleaseAll() error { p.mutex.Lock() @@ -185,10 +172,10 @@ func (p *PortAllocator) ReleaseAll() error { func (pm *portMap) findPort() (int, error) { port := pm.last - for i := 0; i <= endPortRange-beginPortRange; i++ { + for i := 0; i <= pm.end-pm.begin; i++ { port++ - if port > endPortRange { - port = beginPortRange + if port > pm.end { + port = pm.begin } if _, ok := pm.p[port]; !ok { diff --git a/daemon/networkdriver/portallocator/portallocator_test.go b/daemon/networkdriver/portallocator/portallocator_test.go index f6f122bbd..17201235e 100644 --- a/daemon/networkdriver/portallocator/portallocator_test.go +++ b/daemon/networkdriver/portallocator/portallocator_test.go @@ -5,11 +5,6 @@ import ( "testing" ) -func init() { - beginPortRange = DefaultPortRangeStart - endPortRange = DefaultPortRangeEnd -} - func TestRequestNewPort(t *testing.T) { p := New() @@ -18,7 +13,7 @@ func TestRequestNewPort(t *testing.T) { t.Fatal(err) } - if expected := beginPortRange; port != expected { + if expected := p.Begin; port != expected { t.Fatalf("Expected port %d got %d", expected, port) } } @@ -101,13 +96,13 @@ func TestUnknowProtocol(t *testing.T) { func TestAllocateAllPorts(t *testing.T) { p := New() - for i := 0; i <= endPortRange-beginPortRange; i++ { + for i := 0; i <= p.End-p.Begin; i++ { port, err := p.RequestPort(defaultIP, "tcp", 0) if err != nil { t.Fatal(err) } - if expected := beginPortRange + i; port != expected { + if expected := p.Begin + i; port != expected { t.Fatalf("Expected port %d got %d", expected, port) } } @@ -122,7 +117,7 @@ func TestAllocateAllPorts(t *testing.T) { } // release a port in the middle and ensure we get another tcp port - port := beginPortRange + 5 + port := p.Begin + 5 if err := p.ReleasePort(defaultIP, "tcp", port); err != nil { t.Fatal(err) } @@ -152,13 +147,13 @@ func BenchmarkAllocatePorts(b *testing.B) { p := New() for i := 0; i < b.N; i++ { - for i := 0; i <= endPortRange-beginPortRange; i++ { + for i := 0; i <= p.End-p.Begin; i++ { port, err := p.RequestPort(defaultIP, "tcp", 0) if err != nil { b.Fatal(err) } - if expected := beginPortRange + i; port != expected { + if expected := p.Begin + i; port != expected { b.Fatalf("Expected port %d got %d", expected, port) } } @@ -230,15 +225,15 @@ func TestPortAllocation(t *testing.T) { func TestNoDuplicateBPR(t *testing.T) { p := New() - if port, err := p.RequestPort(defaultIP, "tcp", beginPortRange); err != nil { + if port, err := p.RequestPort(defaultIP, "tcp", p.Begin); err != nil { t.Fatal(err) - } else if port != beginPortRange { - t.Fatalf("Expected port %d got %d", beginPortRange, port) + } else if port != p.Begin { + t.Fatalf("Expected port %d got %d", p.Begin, port) } if port, err := p.RequestPort(defaultIP, "tcp", 0); err != nil { t.Fatal(err) - } else if port == beginPortRange { + } else if port == p.Begin { t.Fatalf("Acquire(0) allocated the same port twice: %d", port) } } From 62522c98539e1591017cf0d4f28e6a58f3b1ec6b Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 30 Mar 2015 17:31:21 -0700 Subject: [PATCH 183/999] Refactor portmapper to remove ALL global state Signed-off-by: Michael Crosby --- daemon/networkdriver/portmapper/mapper.go | 9 +-------- daemon/networkdriver/portmapper/mapper_test.go | 3 +-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/daemon/networkdriver/portmapper/mapper.go b/daemon/networkdriver/portmapper/mapper.go index a01b60416..7092352f5 100644 --- a/daemon/networkdriver/portmapper/mapper.go +++ b/daemon/networkdriver/portmapper/mapper.go @@ -18,14 +18,7 @@ type mapping struct { container net.Addr } -var ( - NewProxy = NewProxyCommand - - DefaultPortMapper = NewWithPortAllocator(portallocator.DefaultPortAllocator) - SetIptablesChain = DefaultPortMapper.SetIptablesChain - Map = DefaultPortMapper.Map - Unmap = DefaultPortMapper.Unmap -) +var NewProxy = NewProxyCommand var ( ErrUnknownBackendAddressType = errors.New("unknown container address type not supported") diff --git a/daemon/networkdriver/portmapper/mapper_test.go b/daemon/networkdriver/portmapper/mapper_test.go index 4082a6002..d5d10d8cb 100644 --- a/daemon/networkdriver/portmapper/mapper_test.go +++ b/daemon/networkdriver/portmapper/mapper_test.go @@ -4,7 +4,6 @@ import ( "net" "testing" - "github.com/docker/docker/daemon/networkdriver/portallocator" "github.com/docker/docker/pkg/iptables" ) @@ -126,7 +125,7 @@ func TestMapAllPortsSingleInterface(t *testing.T) { }() for i := 0; i < 10; i++ { - start, end := portallocator.PortRange() + start, end := pm.allocator.Begin, pm.allocator.End for i := start; i < end; i++ { if host, err = pm.Map(srcAddr1, dstIp1, 0); err != nil { t.Fatal(err) From 8f6a14452dfd88aedc8ac9577a98c38a555baadc Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Mon, 30 Mar 2015 17:39:43 -0700 Subject: [PATCH 184/999] Avoid ServeApi race condition If job "acceptconnections" is called before "serveapi" the API Accept() method will hang forever waiting for activation. This is due to the fact that when "acceptconnections" ran the activation channel was nil. Signed-off-by: Darren Shepherd --- api/server/server.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index a7d0a58b2..c1b89fcaa 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -39,7 +39,7 @@ import ( ) var ( - activationLock chan struct{} + activationLock chan struct{} = make(chan struct{}) ) type HttpServer struct { @@ -1593,7 +1593,6 @@ func ServeApi(job *engine.Job) error { protoAddrs = job.Args chErrors = make(chan error, len(protoAddrs)) ) - activationLock = make(chan struct{}) for _, protoAddr := range protoAddrs { protoAddrParts := strings.SplitN(protoAddr, "://", 2) From 302e3834a0bfa860f9d06b42a2955b0cbd135c38 Mon Sep 17 00:00:00 2001 From: Lewis Marshall Date: Tue, 31 Mar 2015 01:33:27 +0100 Subject: [PATCH 185/999] Prevent Upstart post-start stanza from hanging Once the job has failed and is respawned, the status becomes `docker respawn/post-start` after subsequent failures (as opposed to `docker stop/post-start`), so the post-start script needs to take this into account. I could not find specific documentation on the job transitioning to the `respawn/post-start` state, but this was observed on Ubuntu 14.04.2. Signed-off-by: Lewis Marshall --- 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 f9930bd39..4ad6058ed 100644 --- a/contrib/init/upstart/docker.conf +++ b/contrib/init/upstart/docker.conf @@ -49,7 +49,7 @@ post-start script fi if ! printf "%s" "$DOCKER_OPTS" | grep -qE -e '-H|--host'; then while ! [ -e /var/run/docker.sock ]; do - initctl status $UPSTART_JOB | grep -q "stop/" && exit 1 + initctl status $UPSTART_JOB | grep -qE "(stop|respawn)/" && exit 1 echo "Waiting for /var/run/docker.sock" sleep 0.1 done From d8c628cf082a50c0a2a5e381a21da8279a5462b4 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 30 Mar 2015 18:06:16 -0700 Subject: [PATCH 186/999] Ensure that bridge driver does not use global mappers This has a few hacks in it but it ensures that the bridge driver does not use global state in the mappers, atleast as much as possible at this point without further refactoring. Some of the exported fields are hacks to handle the daemon port mapping but this results in a much cleaner approach and completely remove the global state from the mapper and allocator. Signed-off-by: Michael Crosby --- api/server/server.go | 4 ++-- daemon/daemon.go | 7 ------- daemon/networkdriver/bridge/driver.go | 12 +++++++++--- daemon/networkdriver/portmapper/mapper.go | 16 ++++++++-------- daemon/networkdriver/portmapper/mapper_test.go | 2 +- 5 files changed, 20 insertions(+), 21 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index a7d0a58b2..96abf5c3b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -27,7 +27,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/api/types" - "github.com/docker/docker/daemon/networkdriver/portallocator" + "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/listenbuffer" "github.com/docker/docker/pkg/parsers" @@ -1542,7 +1542,7 @@ func allocateDaemonPort(addr string) error { } for _, hostIP := range hostIPs { - if _, err := portallocator.RequestPort(hostIP, "tcp", intPort); err != nil { + if _, err := bridge.RequestPort(hostIP, "tcp", intPort); err != nil { return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err) } } diff --git a/daemon/daemon.go b/daemon/daemon.go index ba9c73ce6..36cf438bb 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -25,7 +25,6 @@ import ( "github.com/docker/docker/daemon/graphdriver" _ "github.com/docker/docker/daemon/graphdriver/vfs" _ "github.com/docker/docker/daemon/networkdriver/bridge" - "github.com/docker/docker/daemon/networkdriver/portallocator" "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/image" @@ -818,12 +817,6 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) } config.DisableNetwork = config.BridgeIface == disableNetworkBridge - // register portallocator release on shutdown - eng.OnShutdown(func() { - if err := portallocator.ReleaseAll(); err != nil { - logrus.Errorf("portallocator.ReleaseAll(): %s", err) - } - }) // Claim the pidfile first, to avoid any and all unexpected race conditions. // Some of the init doesn't need a pidfile lock - but let's not try to be smart. if config.Pidfile != "" { diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index c5f5704f9..21d8c4455 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -77,6 +77,7 @@ var ( bridgeIPv4Network *net.IPNet bridgeIPv6Addr net.IP globalIPv6Network *net.IPNet + portMapper *portmapper.PortMapper defaultBindingIP = net.ParseIP("0.0.0.0") currentInterfaces = ifaces{c: make(map[string]*networkInterface)} @@ -99,6 +100,7 @@ func InitDriver(job *engine.Job) error { fixedCIDR = job.Getenv("FixedCIDR") fixedCIDRv6 = job.Getenv("FixedCIDRv6") ) + portMapper = portmapper.New() if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" { defaultBindingIP = net.ParseIP(defaultIP) @@ -235,7 +237,7 @@ func InitDriver(job *engine.Job) error { if err != nil { return err } - portmapper.SetIptablesChain(chain) + portMapper.SetIptablesChain(chain) } bridgeIPv4Network = networkv4 @@ -350,6 +352,10 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { return nil } +func RequestPort(ip net.IP, proto string, port int) (int, error) { + return portMapper.Allocator.RequestPort(ip, proto, port) +} + // configureBridge attempts to create and configure a network bridge interface named `bridgeIface` on the host // If bridgeIP is empty, it will try to find a non-conflicting IP from the Docker-specified private ranges // If the bridge `bridgeIface` already exists, it will only perform the IP address association with the existing @@ -587,7 +593,7 @@ func Release(job *engine.Job) error { } for _, nat := range containerInterface.PortMappings { - if err := portmapper.Unmap(nat); err != nil { + if err := portMapper.Unmap(nat); err != nil { logrus.Infof("Unable to unmap port %s: %s", nat, err) } } @@ -644,7 +650,7 @@ func AllocatePort(job *engine.Job) error { var host net.Addr for i := 0; i < MaxAllocatedPortAttempts; i++ { - if host, err = portmapper.Map(container, ip, hostPort); err == nil { + if host, err = portMapper.Map(container, ip, hostPort); err == nil { break } // There is no point in immediately retrying to map an explicitly diff --git a/daemon/networkdriver/portmapper/mapper.go b/daemon/networkdriver/portmapper/mapper.go index 7092352f5..8f79bae3f 100644 --- a/daemon/networkdriver/portmapper/mapper.go +++ b/daemon/networkdriver/portmapper/mapper.go @@ -33,7 +33,7 @@ type PortMapper struct { currentMappings map[string]*mapping lock sync.Mutex - allocator *portallocator.PortAllocator + Allocator *portallocator.PortAllocator } func New() *PortMapper { @@ -43,7 +43,7 @@ func New() *PortMapper { func NewWithPortAllocator(allocator *portallocator.PortAllocator) *PortMapper { return &PortMapper{ currentMappings: make(map[string]*mapping), - allocator: allocator, + Allocator: allocator, } } @@ -65,7 +65,7 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host switch container.(type) { case *net.TCPAddr: proto = "tcp" - if allocatedHostPort, err = pm.allocator.RequestPort(hostIP, proto, hostPort); err != nil { + if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil { return nil, err } @@ -78,7 +78,7 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port) case *net.UDPAddr: proto = "udp" - if allocatedHostPort, err = pm.allocator.RequestPort(hostIP, proto, hostPort); err != nil { + if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil { return nil, err } @@ -96,7 +96,7 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host // release the allocated port on any further error during return. defer func() { if err != nil { - pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort) + pm.Allocator.ReleasePort(hostIP, proto, allocatedHostPort) } }() @@ -114,7 +114,7 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host // need to undo the iptables rules before we return proxy.Stop() pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort) - if err := pm.allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil { + if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil { return err } @@ -154,9 +154,9 @@ func (pm *PortMapper) Unmap(host net.Addr) error { switch a := host.(type) { case *net.TCPAddr: - return pm.allocator.ReleasePort(a.IP, "tcp", a.Port) + return pm.Allocator.ReleasePort(a.IP, "tcp", a.Port) case *net.UDPAddr: - return pm.allocator.ReleasePort(a.IP, "udp", a.Port) + return pm.Allocator.ReleasePort(a.IP, "udp", a.Port) } return nil } diff --git a/daemon/networkdriver/portmapper/mapper_test.go b/daemon/networkdriver/portmapper/mapper_test.go index d5d10d8cb..729fe5607 100644 --- a/daemon/networkdriver/portmapper/mapper_test.go +++ b/daemon/networkdriver/portmapper/mapper_test.go @@ -125,7 +125,7 @@ func TestMapAllPortsSingleInterface(t *testing.T) { }() for i := 0; i < 10; i++ { - start, end := pm.allocator.Begin, pm.allocator.End + start, end := pm.Allocator.Begin, pm.Allocator.End for i := start; i < end; i++ { if host, err = pm.Map(srcAddr1, dstIp1, 0); err != nil { t.Fatal(err) From 8d3d34d5e310e6d3c80b4c3301b804a1edd25fb6 Mon Sep 17 00:00:00 2001 From: dalanlan Date: Mon, 30 Mar 2015 09:00:05 +0800 Subject: [PATCH 187/999] fix issue #11676 #11754, disable RLIMIT_AS,edit DOCKER_OPTS Signed-off-by: Simei He --- docs/sources/articles/networking.md | 28 +++++++++++++++-------- docs/sources/reference/commandline/cli.md | 2 ++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index 754d9989c..95881e280 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -121,8 +121,23 @@ Finally, several networking options can only be provided when calling * `-P` or `--publish-all=true|false` — see [Binding container ports](#binding-ports) -The following sections tackle all of the above topics in an order that -moves roughly from simplest to most complex. +To supply networking options to the Docker server at startup, use the +`DOCKER_OPTS` in the Docker upstart configuration file. For Ubuntu, edit the +variable in `/etc/default/docker` and `/etc/sysconfig/docker` for Centos. + +The following example illustrates how to configure Docker on Ubuntu to recognize a +newly build bridge. Edit the `/etc/default/docker` file: + + $ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker + +Then, restart the Docker server. + + $ sudo service docker start + +For additional information on bridges, see [building your own +bridge](#building-your-own-bridge) later on this page. + +The following sections tackle all of the above topics in an order that we can move roughly from simplest to most complex. ## Configuring DNS @@ -296,8 +311,7 @@ system level, by two factors. policy to `DROP` if `--icc=false`. It is a strategic question whether to leave `--icc=true` or change it to -`--icc=false` (on Ubuntu, by editing the `DOCKER_OPTS` variable in -`/etc/default/docker` and restarting the Docker server) so that +`--icc=false` so that `iptables` will protect other containers — and the main host — from having arbitrary ports probed or accessed by a container that gets compromised. @@ -426,8 +440,7 @@ you can use either `-p IP:host_port:container_port` or `-p IP::port` to specify the external interface for one particular binding. Or if you always want Docker port forwards to bind to one specific IP -address, you can edit your system-wide Docker server settings (on -Ubuntu, by editing `DOCKER_OPTS` in `/etc/default/docker`) and add the +address, you can edit your system-wide Docker server settings and add the option `--ip=IP_ADDRESS`. Remember to restart your Docker server after editing this setting. @@ -692,9 +705,6 @@ options are configurable at server startup: * `--mtu=BYTES` — override the maximum packet length on `docker0`. -On Ubuntu you would add these to the `DOCKER_OPTS` setting in -`/etc/default/docker` on your Docker host and restarting the Docker -service. Once you have one or more containers up and running, you can confirm that Docker has properly connected them to the `docker0` bridge by diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index e3344991b..ba30e387b 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -2195,6 +2195,8 @@ available in the default container, you can set these using the `--ulimit` flag. > If you do not provide a `hard limit`, the `soft limit` will be used for both values. If no `ulimits` are set, they will be inherited from the default `ulimits` set on the daemon. +> `as` option is disabled for now. In other words, the following script is not supported: +> `$docker run -it --ulimit as=1024 fedora /bin/bash` ## save From bf15f675b9042db661de720900f367eff19737c0 Mon Sep 17 00:00:00 2001 From: Harry Zhang Date: Tue, 31 Mar 2015 02:41:49 +0000 Subject: [PATCH 188/999] Revison the some columns to make table clearer Signed-off-by: Harry Zhang --- docs/sources/reference/run.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 84b5938e9..2598bc3cd 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -635,7 +635,7 @@ This can be overridden using a third `:rwm` set of options to each `--device` fl In addition to `--privileged`, the operator can have fine grain control over the capabilities using `--cap-add` and `--cap-drop`. By default, Docker has a default -list of capabilities that are kept. Here is a table to list the reference information on capabilities. +list of capabilities that are kept. The following table lists the Linux capability options which can be added or dropped. | Capability Key | Capability Description | | :----------------- | :---------------| :-------------------- | @@ -645,7 +645,7 @@ list of capabilities that are kept. Here is a table to list the reference inform | SYS_PACCT | Use acct(2), switch process accounting on or off. | | SYS_ADMIN | Perform a range of system administration operations. | | SYS_NICE | Raise process nice value (nice(2), setpriority(2)) and change the nice value for arbitrary processes. | -| SYS_RESOURCE | Override Resource Limits. | +| SYS_RESOURCE | Override resource Limits. | | SYS_TIME | Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock. | | SYS_TTY_CONFIG | Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals. | | MKNOD | Create special files using mknod(2). | @@ -665,7 +665,7 @@ list of capabilities that are kept. Here is a table to list the reference inform | SETGID | Make arbitrary manipulations of process GIDs and supplementary GID list. | | SETUID | Make arbitrary manipulations of process UIDs. | | LINUX_IMMUTABLE | Set the FS_APPEND_FL and FS_IMMUTABLE_FL i-node flags. | -| NET_BIND_SERVICE | Bind a socket to Internet domain privileged ports (port numbers less than 1024). | +| NET_BIND_SERVICE | Bind a socket to internet domain privileged ports (port numbers less than 1024). | | NET_BROADCAST | Make socket broadcasts, and listen to multicasts. | | IPC_LOCK | Lock memory (mlock(2), mlockall(2), mmap(2), shmctl(2)). | | IPC_OWNER | Bypass permission checks for operations on System V IPC objects. | @@ -677,7 +677,7 @@ list of capabilities that are kept. Here is a table to list the reference inform | WAKE_ALARM | Trigger something that will wake up the system. | | BLOCK_SUSPEND | Employ features that can block system suspend. | -For futher understanding, please check [capabilities(7) - Linux man page](http://linux.die.net/man/7/capabilities) +Further reference information is available on the [capabilities(7) - Linux man page](http://linux.die.net/man/7/capabilities) Both flags support the value `all`, so if the operator wants to have all capabilities but `MKNOD` they could use: From d322cd5dcb0ad480c974f4cc58d9d01c3d2801a7 Mon Sep 17 00:00:00 2001 From: Pavel Tikhomirov Date: Wed, 21 Jan 2015 09:09:53 -0500 Subject: [PATCH 189/999] docker-tests: mount hierarchies and make symlinks for subsystems Docker does not know about our named cpuacct,cpu,cpuset cgroup hierarchy with multiple subsystems in it. So to use them with docker in integration-cli test TestRunWithCpuset inside docker container we need to add symlinks to them in hack/dind script. Example: old version of parser will do: cat /proc/1/cgroup 11:cpu,cpuacct,name=my_cpu_cpuacct:/ ... and create and mount this hierarchy to directory /cgroup/cpu,cpuacct,name=my_cpu_cpuacct/ so docker cannot find it because it has strange name in new parser directory will be same as on host /cgroup/my_cpu_cpuacct and have symlinks for docker to find it /cgroup/cpu -> /cgroup/my_cpu_cpuacct /cgroup/cpuacct -> /cgroup/my_cpu_cpuacct in other case if where is no name cat /proc/1/cgroup 11:cpu,cpuacct:/ ... mount will be same for both parsers /cgroup/cpu,cpuacct and new one will also create symlinks /cgroup/cpu -> /cgroup/cpu,cpuacct /cgroup/cpuacct -> /cgroup/cpu,cpuacct Signed-off-by: Pavel Tikhomirov --- hack/dind | 52 ++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/hack/dind b/hack/dind index f8fae6379..1242cbffe 100755 --- a/hack/dind +++ b/hack/dind @@ -33,28 +33,35 @@ if [ -d /sys/kernel/security ] && ! mountpoint -q /sys/kernel/security; then fi # Mount the cgroup hierarchies exactly as they are in the parent system. -for SUBSYS in $(cut -d: -f2 /proc/1/cgroup); do - mkdir -p "$CGROUP/$SUBSYS" - if ! mountpoint -q $CGROUP/$SUBSYS; then - mount -n -t cgroup -o "$SUBSYS" cgroup "$CGROUP/$SUBSYS" - fi +for HIER in $(cut -d: -f2 /proc/1/cgroup); do - # The two following sections address a bug which manifests itself + # The following sections address a bug which manifests itself # by a cryptic "lxc-start: no ns_cgroup option specified" when - # trying to start containers withina container. + # trying to start containers within a container. # The bug seems to appear when the cgroup hierarchies are not # mounted on the exact same directories in the host, and in the # container. + SUBSYSTEMS="${HIER%name=*}" + + # If cgroup hierarchy is named(mounted with "-o name=foo") we + # need to mount it in $CGROUP/foo to create exect same + # directoryes as on host. Else we need to mount it as is e.g. + # "subsys1,subsys2" if it has two subsystems + # Named, control-less cgroups are mounted with "-o name=foo" # (and appear as such under /proc//cgroup) but are usually # mounted on a directory named "foo" (without the "name=" prefix). # Systemd and OpenRC (and possibly others) both create such a - # cgroup. To avoid the aforementioned bug, we symlink "foo" to - # "name=foo". This shouldn't have any adverse effect. - name="${SUBSYS#name=}" - if [ "$name" != "$SUBSYS" ]; then - ln -s "$SUBSYS" "$CGROUP/$name" + # cgroup. So just mount them on directory $CGROUP/foo. + + OHIER=$HIER + HIER="${HIER#*name=}" + + mkdir -p "$CGROUP/$HIER" + + if ! mountpoint -q $CGROUP/$HIER; then + mount -n -t cgroup -o "$OHIER" cgroup "$CGROUP/$HIER" fi # Likewise, on at least one system, it has been reported that @@ -62,8 +69,25 @@ for SUBSYS in $(cut -d: -f2 /proc/1/cgroup); do # (respectively "cpu" and "cpuacct") with "-o cpuacct,cpu" # but on a directory called "cpu,cpuacct" (note the inversion # in the order of the groups). This tries to work around it. - if [ "$SUBSYS" = 'cpuacct,cpu' ]; then - ln -s "$SUBSYS" "$CGROUP/cpu,cpuacct" + + if [ "$HIER" = 'cpuacct,cpu' ]; then + ln -s "$HIER" "$CGROUP/cpu,cpuacct" + fi + + # If hierarchy has multiple subsystems, in /proc//cgroup + # we will see ":subsys1,subsys2,subsys3,name=foo:" substring, + # we need to mount it to "$CGROUP/foo" and if there were no + # name to "$CGROUP/subsys1,subsys2,subsys3", so we must create + # symlinks for docker daemon to find these subsystems: + # ln -s $CGROUP/foo $CGROUP/subsys1 + # ln -s $CGROUP/subsys1,subsys2,subsys3 $CGROUP/subsys1 + + if [ "$SUBSYSTEMS" != "${SUBSYSTEMS//,/ }" ]; then + SUBSYSTEMS="${SUBSYSTEMS//,/ }" + for SUBSYS in $SUBSYSTEMS + do + ln -s "$CGROUP/$HIER" "$CGROUP/$SUBSYS" + done fi done From 40945fc186067e5b7edd1f6cd7645ff2ae7cea6c Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Thu, 12 Mar 2015 15:26:17 -0400 Subject: [PATCH 190/999] container: Do not remove contianer if any of the resource failed cleanup Do not remove container if any of the resource could not be cleaned up. We don't want to leak resources. Two new states have been created. RemovalInProgress and Dead. Once container is Dead, it can not be started/restarted. Dead container signifies the container where we tried to remove it but removal failed. User now needs to figure out what went wrong, corrent the situation and try cleanup again. RemovalInProgress signifies that container is already being removed. Only one removal can be in progress. Also, do not allow start of a container if it is already dead or removal is in progress. Also extend existing force option (-f) to docker rm to not return an error and remove container from user view even if resource cleanup failed. This will allow a user to get back to old behavior where resources might leak but atleast user will be able to make progress. Signed-off-by: Vivek Goyal --- daemon/container.go | 4 ++++ daemon/delete.go | 58 +++++++++++++++++++++++++++++++++++++-------- daemon/state.go | 57 ++++++++++++++++++++++++++++++++++++-------- 3 files changed, 99 insertions(+), 20 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 70abc6b96..47ff70645 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -360,6 +360,10 @@ func (container *Container) Start() (err error) { return nil } + if container.removalInProgress || container.Dead { + return fmt.Errorf("Container is marked for removal and cannot be started.") + } + // if we encounter an error during start we need to ensure that any other // setup has been cleaned up properly defer func() { diff --git a/daemon/delete.go b/daemon/delete.go index 312718196..eb93973aa 100644 --- a/daemon/delete.go +++ b/daemon/delete.go @@ -63,8 +63,15 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) error { return fmt.Errorf("Conflict, You cannot remove a running container. Stop the container before attempting removal or use -f") } } - if err := daemon.Rm(container); err != nil { - return fmt.Errorf("Cannot destroy container %s: %s", name, err) + + if forceRemove { + if err := daemon.ForceRm(container); err != nil { + logrus.Errorf("Cannot destroy container %s: %v", name, err) + } + } else { + if err := daemon.Rm(container); err != nil { + return fmt.Errorf("Cannot destroy container %s: %v", name, err) + } } container.LogEvent("destroy") if removeVolume { @@ -83,8 +90,16 @@ func (daemon *Daemon) DeleteVolumes(volumeIDs map[string]struct{}) { } } +func (daemon *Daemon) Rm(container *Container) (err error) { + return daemon.commonRm(container, false) +} + +func (daemon *Daemon) ForceRm(container *Container) (err error) { + return daemon.commonRm(container, true) +} + // Destroy unregisters a container from the daemon and cleanly removes its contents from the filesystem. -func (daemon *Daemon) Rm(container *Container) error { +func (daemon *Daemon) commonRm(container *Container, forceRemove bool) (err error) { if container == nil { return fmt.Errorf("The given container is ") } @@ -94,19 +109,40 @@ func (daemon *Daemon) Rm(container *Container) error { return fmt.Errorf("Container %v not found - maybe it was already destroyed?", container.ID) } - if err := container.Stop(3); err != nil { + // Container state RemovalInProgress should be used to avoid races. + if err = container.SetRemovalInProgress(); err != nil { + return fmt.Errorf("Failed to set container state to RemovalInProgress: %s", err) + } + + defer container.ResetRemovalInProgress() + + if err = container.Stop(3); err != nil { return err } - // Deregister the container before removing its directory, to avoid race conditions - daemon.idIndex.Delete(container.ID) - daemon.containers.Delete(container.ID) + // Mark container dead. We don't want anybody to be restarting it. + container.SetDead() + + // Save container state to disk. So that if error happens before + // container meta file got removed from disk, then a restart of + // docker should not make a dead container alive. + container.ToDisk() + + // If force removal is required, delete container from various + // indexes even if removal failed. + defer func() { + if err != nil && forceRemove { + daemon.idIndex.Delete(container.ID) + daemon.containers.Delete(container.ID) + } + }() + container.derefVolumes() if _, err := daemon.containerGraph.Purge(container.ID); err != nil { logrus.Debugf("Unable to remove container from link graph: %s", err) } - if err := daemon.driver.Remove(container.ID); err != nil { + if err = daemon.driver.Remove(container.ID); err != nil { return fmt.Errorf("Driver %s failed to remove root filesystem %s: %s", daemon.driver, container.ID, err) } @@ -115,15 +151,17 @@ func (daemon *Daemon) Rm(container *Container) error { return fmt.Errorf("Driver %s failed to remove init filesystem %s: %s", daemon.driver, initID, err) } - if err := os.RemoveAll(container.root); err != nil { + if err = os.RemoveAll(container.root); err != nil { return fmt.Errorf("Unable to remove filesystem for %v: %v", container.ID, err) } - if err := daemon.execDriver.Clean(container.ID); err != nil { + if err = daemon.execDriver.Clean(container.ID); err != nil { return fmt.Errorf("Unable to remove execdriver data for %s: %s", container.ID, err) } selinuxFreeLxcContexts(container.ProcessLabel) + daemon.idIndex.Delete(container.ID) + daemon.containers.Delete(container.ID) return nil } diff --git a/daemon/state.go b/daemon/state.go index 3aba57090..6387e6fc5 100644 --- a/daemon/state.go +++ b/daemon/state.go @@ -11,16 +11,18 @@ import ( type State struct { sync.Mutex - Running bool - Paused bool - Restarting bool - OOMKilled bool - Pid int - ExitCode int - Error string // contains last known error when starting the container - StartedAt time.Time - FinishedAt time.Time - waitChan chan struct{} + Running bool + Paused bool + Restarting bool + OOMKilled bool + removalInProgress bool // Not need for this to be persistent on disk. + Dead bool + Pid int + ExitCode int + Error string // contains last known error when starting the container + StartedAt time.Time + FinishedAt time.Time + waitChan chan struct{} } func NewState() *State { @@ -42,6 +44,14 @@ func (s *State) String() string { return fmt.Sprintf("Up %s", units.HumanDuration(time.Now().UTC().Sub(s.StartedAt))) } + if s.removalInProgress { + return "Removal In Progress" + } + + if s.Dead { + return "Dead" + } + if s.FinishedAt.IsZero() { return "" } @@ -60,6 +70,11 @@ func (s *State) StateString() string { } return "running" } + + if s.Dead { + return "dead" + } + return "exited" } @@ -217,3 +232,25 @@ func (s *State) IsPaused() bool { s.Unlock() return res } + +func (s *State) SetRemovalInProgress() error { + s.Lock() + defer s.Unlock() + if s.removalInProgress { + return fmt.Errorf("Status is already RemovalInProgress") + } + s.removalInProgress = true + return nil +} + +func (s *State) ResetRemovalInProgress() { + s.Lock() + s.removalInProgress = false + s.Unlock() +} + +func (s *State) SetDead() { + s.Lock() + s.Dead = true + s.Unlock() +} From 9e9adf807505e4355a98206f0937e504fcf77a84 Mon Sep 17 00:00:00 2001 From: Jun-Ru Chang Date: Sat, 28 Mar 2015 12:33:24 +0800 Subject: [PATCH 191/999] mkimage-arch: set C.UTF-8 default locale It may not work fine when doing expect script if setting other locales. Signed-off-by: Jun-Ru Chang --- contrib/mkimage-arch.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/mkimage-arch.sh b/contrib/mkimage-arch.sh index bbecf7290..06406fecd 100755 --- a/contrib/mkimage-arch.sh +++ b/contrib/mkimage-arch.sh @@ -14,6 +14,8 @@ hash expect &>/dev/null || { exit 1 } +export LANG="C.UTF-8" + ROOTFS=$(mktemp -d ${TMPDIR:-/var/tmp}/rootfs-archlinux-XXXXXXXXXX) chmod 755 $ROOTFS From 584180fce7ad11516a256b8abd4621138337e918 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 31 Mar 2015 09:34:03 -0700 Subject: [PATCH 192/999] Initialize portMapper in RequestPort too Api requesting port for daemon before init_networkdriver called. Problem is that now initialization of api depends on initialization of daemon and their intializations runs in parallel. Proper fix will be just do it sequentially. For now I don't want refactor it, because it can bring additional problems in 1.6.0. Signed-off-by: Alexander Morozov --- daemon/networkdriver/bridge/driver.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index 21d8c4455..42015ce2f 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -78,12 +78,19 @@ var ( bridgeIPv6Addr net.IP globalIPv6Network *net.IPNet portMapper *portmapper.PortMapper + once sync.Once defaultBindingIP = net.ParseIP("0.0.0.0") currentInterfaces = ifaces{c: make(map[string]*networkInterface)} ipAllocator = ipallocator.New() ) +func initPortMapper() { + once.Do(func() { + portMapper = portmapper.New() + }) +} + func InitDriver(job *engine.Job) error { var ( networkv4 *net.IPNet @@ -100,7 +107,7 @@ func InitDriver(job *engine.Job) error { fixedCIDR = job.Getenv("FixedCIDR") fixedCIDRv6 = job.Getenv("FixedCIDRv6") ) - portMapper = portmapper.New() + initPortMapper() if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" { defaultBindingIP = net.ParseIP(defaultIP) @@ -353,6 +360,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { } func RequestPort(ip net.IP, proto string, port int) (int, error) { + initPortMapper() return portMapper.Allocator.RequestPort(ip, proto, port) } From 7609d5279743b47450cc1273ee75504bb6abf8b6 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 31 Mar 2015 11:38:17 -0700 Subject: [PATCH 193/999] Move Profiler into specific http.Handler Signed-off-by: Michael Crosby --- api/server/profiler.go | 52 ++++++++++++++++++++++++++++++++++++++++++ api/server/server.go | 32 +------------------------- 2 files changed, 53 insertions(+), 31 deletions(-) create mode 100644 api/server/profiler.go diff --git a/api/server/profiler.go b/api/server/profiler.go new file mode 100644 index 000000000..f27dc6cca --- /dev/null +++ b/api/server/profiler.go @@ -0,0 +1,52 @@ +package server + +import ( + "expvar" + "fmt" + "net/http" + "net/http/pprof" + + "github.com/gorilla/mux" +) + +func NewProfiler() http.Handler { + var ( + p = &Profiler{} + r = mux.NewRouter() + ) + r.HandleFunc("/vars", p.expVars) + r.HandleFunc("/pprof/", pprof.Index) + r.HandleFunc("/pprof/cmdline", pprof.Cmdline) + r.HandleFunc("/pprof/profile", pprof.Profile) + r.HandleFunc("/pprof/symbol", pprof.Symbol) + r.HandleFunc("/pprof/block", pprof.Handler("block").ServeHTTP) + r.HandleFunc("/pprof/heap", pprof.Handler("heap").ServeHTTP) + r.HandleFunc("/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP) + r.HandleFunc("/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP) + p.r = r + return p +} + +// Profiler enables pprof and expvar support via a HTTP API. +type Profiler struct { + r *mux.Router +} + +func (p *Profiler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + p.r.ServeHTTP(w, r) +} + +// Replicated from expvar.go as not public. +func (p *Profiler) expVars(w http.ResponseWriter, r *http.Request) { + first := true + w.Header().Set("Content-Type", "application/json; charset=utf-8") + fmt.Fprintf(w, "{\n") + expvar.Do(func(kv expvar.KeyValue) { + if !first { + fmt.Fprintf(w, ",\n") + } + first = false + fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) + }) + fmt.Fprintf(w, "\n}\n") +} diff --git a/api/server/server.go b/api/server/server.go index 96abf5c3b..3d248bb58 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -6,13 +6,11 @@ import ( "encoding/base64" "encoding/json" - "expvar" "fmt" "io" "io/ioutil" "net" "net/http" - "net/http/pprof" "os" "strconv" "strings" @@ -1308,38 +1306,11 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local } } -// Replicated from expvar.go as not public. -func expvarHandler(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json; charset=utf-8") - fmt.Fprintf(w, "{\n") - first := true - expvar.Do(func(kv expvar.KeyValue) { - if !first { - fmt.Fprintf(w, ",\n") - } - first = false - fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value) - }) - fmt.Fprintf(w, "\n}\n") -} - -func AttachProfiler(router *mux.Router) { - router.HandleFunc("/debug/vars", expvarHandler) - router.HandleFunc("/debug/pprof/", pprof.Index) - router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) - router.HandleFunc("/debug/pprof/profile", pprof.Profile) - router.HandleFunc("/debug/pprof/symbol", pprof.Symbol) - router.HandleFunc("/debug/pprof/block", pprof.Handler("block").ServeHTTP) - router.HandleFunc("/debug/pprof/heap", pprof.Handler("heap").ServeHTTP) - router.HandleFunc("/debug/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP) - router.HandleFunc("/debug/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP) -} - // we keep enableCors just for legacy usage, need to be removed in the future func createRouter(eng *engine.Engine, logging, enableCors bool, corsHeaders string, dockerVersion string) *mux.Router { r := mux.NewRouter() if os.Getenv("DEBUG") != "" { - AttachProfiler(r) + r.Handle("/debug", NewProfiler()) } m := map[string]map[string]HttpApiFunc{ "GET": { @@ -1494,7 +1465,6 @@ func newListener(proto, addr string, bufferRequests bool) (net.Listener, error) if bufferRequests { return listenbuffer.NewListenBuffer(proto, addr, activationLock) } - return net.Listen(proto, addr) } From 8caf8f0a79df8dcdac122157a917504b414e4ecf Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Tue, 31 Mar 2015 03:03:18 -0700 Subject: [PATCH 194/999] Use different host and container port for clarity Fixes #11953 Signed-off-by: Ankush Agarwal --- docs/sources/userguide/dockerlinks.md | 11 ++++++----- docs/sources/userguide/usingdocker.md | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/sources/userguide/dockerlinks.md b/docs/sources/userguide/dockerlinks.md index 1c14f47e2..66dd3d7a4 100644 --- a/docs/sources/userguide/dockerlinks.md +++ b/docs/sources/userguide/dockerlinks.md @@ -35,9 +35,10 @@ range* on your Docker host. Next, when `docker ps` was run, you saw that port bc533791f3f5 training/webapp:latest python app.py 5 seconds ago Up 2 seconds 0.0.0.0:49155->5000/tcp nostalgic_morse You also saw how you can bind a container's ports to a specific port using -the `-p` flag: +the `-p` flag. Here port 80 of the host is mapped to port 5000 of the +container: - $ docker run -d -p 5000:5000 training/webapp python app.py + $ docker run -d -p 80:5000 training/webapp python app.py And you saw why this isn't such a great idea because it constrains you to only one container on that specific port. @@ -47,9 +48,9 @@ default the `-p` flag will bind the specified port to all interfaces on the host machine. But you can also specify a binding to a specific interface, for example only to the `localhost`. - $ docker run -d -p 127.0.0.1:5000:5000 training/webapp python app.py + $ docker run -d -p 127.0.0.1:80:5000 training/webapp python app.py -This would bind port 5000 inside the container to port 5000 on the +This would bind port 5000 inside the container to port 80 on the `localhost` or `127.0.0.1` interface on the host machine. Or, to bind port 5000 of the container to a dynamic port but only on the @@ -59,7 +60,7 @@ Or, to bind port 5000 of the container to a dynamic port but only on the You can also bind UDP ports by adding a trailing `/udp`. For example: - $ docker run -d -p 127.0.0.1:5000:5000/udp training/webapp python app.py + $ docker run -d -p 127.0.0.1:80:5000/udp training/webapp python app.py You also learned about the useful `docker port` shortcut which showed us the current port bindings. This is also useful for showing you specific port diff --git a/docs/sources/userguide/usingdocker.md b/docs/sources/userguide/usingdocker.md index a58a4a4aa..26a9814b8 100644 --- a/docs/sources/userguide/usingdocker.md +++ b/docs/sources/userguide/usingdocker.md @@ -160,9 +160,9 @@ to a high port (from *ephemeral port range* which typically ranges from 32768 to 61000) on the local Docker host. We can also bind Docker containers to specific ports using the `-p` flag, for example: - $ docker run -d -p 5000:5000 training/webapp python app.py + $ docker run -d -p 80:5000 training/webapp python app.py -This would map port 5000 inside our container to port 5000 on our local +This would map port 5000 inside our container to port 80 on our local host. You might be asking about now: why wouldn't we just want to always use 1:1 port mappings in Docker containers rather than mapping to high ports? Well 1:1 mappings have the constraint of only being able to map From e94a48ffc8fa287c4b1b441c5308999693c58b75 Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Tue, 31 Mar 2015 01:03:31 -0700 Subject: [PATCH 195/999] Add some documentation to pkg/system Partially addresses #11581 Signed-off-by: Ankush Agarwal --- pkg/system/lstat.go | 4 ++++ pkg/system/lstat_test.go | 1 + pkg/system/meminfo_linux.go | 8 ++++++-- pkg/system/meminfo_linux_test.go | 1 + pkg/system/mknod.go | 2 ++ pkg/system/stat.go | 2 ++ pkg/system/stat_linux.go | 5 +++++ pkg/system/stat_test.go | 1 + pkg/system/stat_unsupported.go | 1 + pkg/system/utimes_test.go | 1 + 10 files changed, 24 insertions(+), 2 deletions(-) diff --git a/pkg/system/lstat.go b/pkg/system/lstat.go index 6c1ed2e38..a966cd488 100644 --- a/pkg/system/lstat.go +++ b/pkg/system/lstat.go @@ -6,6 +6,10 @@ import ( "syscall" ) +// Lstat takes a path to a file and returns +// a system.Stat_t type pertaining to that file. +// +// Throws an error if the file does not exist func Lstat(path string) (*Stat_t, error) { s := &syscall.Stat_t{} err := syscall.Lstat(path, s) diff --git a/pkg/system/lstat_test.go b/pkg/system/lstat_test.go index 9bab4d7b0..6bac492eb 100644 --- a/pkg/system/lstat_test.go +++ b/pkg/system/lstat_test.go @@ -5,6 +5,7 @@ import ( "testing" ) +// TestLstat tests Lstat for existing and non existing files func TestLstat(t *testing.T) { file, invalid, _, dir := prepareFiles(t) defer os.RemoveAll(dir) diff --git a/pkg/system/meminfo_linux.go b/pkg/system/meminfo_linux.go index b7de3ff77..e2ca14009 100644 --- a/pkg/system/meminfo_linux.go +++ b/pkg/system/meminfo_linux.go @@ -15,8 +15,8 @@ var ( ErrMalformed = errors.New("malformed file") ) -// Retrieve memory statistics of the host system and parse them into a MemInfo -// type. +// ReadMemInfo retrieves memory statistics of the host system and returns a +// MemInfo type. func ReadMemInfo() (*MemInfo, error) { file, err := os.Open("/proc/meminfo") if err != nil { @@ -26,6 +26,10 @@ func ReadMemInfo() (*MemInfo, error) { return parseMemInfo(file) } +// parseMemInfo parses the /proc/meminfo file into +// a MemInfo object given a io.Reader to the file. +// +// Throws error if there are problems reading from the file func parseMemInfo(reader io.Reader) (*MemInfo, error) { meminfo := &MemInfo{} scanner := bufio.NewScanner(reader) diff --git a/pkg/system/meminfo_linux_test.go b/pkg/system/meminfo_linux_test.go index 377405ea6..10ddf796c 100644 --- a/pkg/system/meminfo_linux_test.go +++ b/pkg/system/meminfo_linux_test.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/pkg/units" ) +// TestMemInfo tests parseMemInfo with a static meminfo string func TestMemInfo(t *testing.T) { const input = ` MemTotal: 1 kB diff --git a/pkg/system/mknod.go b/pkg/system/mknod.go index 06f9c6afb..26617eb08 100644 --- a/pkg/system/mknod.go +++ b/pkg/system/mknod.go @@ -6,6 +6,8 @@ import ( "syscall" ) +// Mknod creates a filesystem node (file, device special file or named pipe) named path +// with attributes specified by mode and dev func Mknod(path string, mode uint32, dev int) error { return syscall.Mknod(path, mode, dev) } diff --git a/pkg/system/stat.go b/pkg/system/stat.go index 186e85287..ba22b4dd9 100644 --- a/pkg/system/stat.go +++ b/pkg/system/stat.go @@ -4,6 +4,8 @@ import ( "syscall" ) +// Stat_t type contains status of a file. It contains metadata +// like permission, owner, group, size, etc about a file type Stat_t struct { mode uint32 uid uint32 diff --git a/pkg/system/stat_linux.go b/pkg/system/stat_linux.go index 072728d0a..928ba89e6 100644 --- a/pkg/system/stat_linux.go +++ b/pkg/system/stat_linux.go @@ -4,6 +4,7 @@ import ( "syscall" ) +// fromStatT converts a syscall.Stat_t type to a system.Stat_t type func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { return &Stat_t{size: s.Size, mode: s.Mode, @@ -13,6 +14,10 @@ func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { mtim: s.Mtim}, nil } +// Stat takes a path to a file and returns +// a system.Stat_t type pertaining to that file. +// +// Throws an error if the file does not exist func Stat(path string) (*Stat_t, error) { s := &syscall.Stat_t{} err := syscall.Stat(path, s) diff --git a/pkg/system/stat_test.go b/pkg/system/stat_test.go index abcc8ea7a..453412920 100644 --- a/pkg/system/stat_test.go +++ b/pkg/system/stat_test.go @@ -6,6 +6,7 @@ import ( "testing" ) +// TestFromStatT tests fromStatT for a tempfile func TestFromStatT(t *testing.T) { file, _, _, dir := prepareFiles(t) defer os.RemoveAll(dir) diff --git a/pkg/system/stat_unsupported.go b/pkg/system/stat_unsupported.go index 66323eee2..7e0d0348f 100644 --- a/pkg/system/stat_unsupported.go +++ b/pkg/system/stat_unsupported.go @@ -6,6 +6,7 @@ import ( "syscall" ) +// fromStatT creates a system.Stat_t type from a syscall.Stat_t type func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { return &Stat_t{size: s.Size, mode: uint32(s.Mode), diff --git a/pkg/system/utimes_test.go b/pkg/system/utimes_test.go index 1dea47cc1..350cce1ea 100644 --- a/pkg/system/utimes_test.go +++ b/pkg/system/utimes_test.go @@ -8,6 +8,7 @@ import ( "testing" ) +// prepareFiles creates files for testing in the temp directory func prepareFiles(t *testing.T) (string, string, string, string) { dir, err := ioutil.TempDir("", "docker-system-test") if err != nil { From 63708dca8a633d68f9342eebd4f7a616e8c48234 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 30 Mar 2015 14:35:37 -0400 Subject: [PATCH 196/999] Use getResourcePath instead Also cleans up tests to not shell out for file creation. Signed-off-by: Brian Goff --- daemon/volumes.go | 6 +-- integration-cli/docker_cli_run_test.go | 51 ++++++++++++-------------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/daemon/volumes.go b/daemon/volumes.go index 7312264af..f40fdd3e4 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -189,13 +189,13 @@ func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) if _, exists := container.Volumes[path]; exists { continue } - realpath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, path), container.basefs) + realPath, err := container.getResourcePath(path) if err != nil { return nil, fmt.Errorf("failed to evaluate the absolute path of symlink") } - if stat, err := os.Stat(realpath); err == nil { + if stat, err := os.Stat(realPath); err == nil { if !stat.IsDir() { - return nil, fmt.Errorf("file exists at %s, can't create volume there", realpath) + return nil, fmt.Errorf("file exists at %s, can't create volume there", realPath) } } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index e28901c7e..db7c8b909 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -477,37 +477,34 @@ func TestRunWithVolumesFromExited(t *testing.T) { logDone("run - regression test for #4979 - volumes-from on exited container") } -// Test create volume in a dirctory which is a symbolic link +// Volume path is a symlink which also exists on the host, and the host side is a file not a dir +// But the volume call is just a normal volume, not a bind mount func TestRunCreateVolumesInSymlinkDir(t *testing.T) { + testRequires(t, SameHostDaemon) + testRequires(t, NativeExecDriver) defer deleteAllContainers() - // This test has to create a file on host - hostFile := "/tmp/abcd" - cmd := exec.Command("touch", hostFile) - if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("failed to create file %s on host: %v, output: %q", hostFile, err, out) - } - defer func() { - cmd := exec.Command("rm", "-f", hostFile) - if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("failed to remove file %s on host: %v, output: %q", hostFile, err, out) - } - }() - // create symlink directory /home/test link to /tmp - cmd = exec.Command(dockerBinary, "run", "--name=test", "busybox", "ln", "-s", "/tmp", "/home/test") - if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) - } - cmd = exec.Command(dockerBinary, "commit", "test", "busybox:test") - out, _, err := runCommandWithOutput(cmd) + name := "test-volume-symlink" + + dir, err := ioutil.TempDir("", name) if err != nil { - t.Fatalf("failed to commit container: %v, output: %q", err, out) + t.Fatal(err) } - cleanedImageID := stripTrailingCharacters(out) - defer deleteImages(cleanedImageID) - // directory /home/test is link to /tmp, /home/test/abcd==/tmp/abcd - cmd = exec.Command(dockerBinary, "run", "-v", "/home/test/abcd", "busybox", "touch", "/home/test/abcd/Hello") - if out, _, err = runCommandWithOutput(cmd); err != nil { - t.Fatalf("failed to create volume in symlink directory: %v, output %q", err, out) + defer os.RemoveAll(dir) + + f, err := os.OpenFile(filepath.Join(dir, "test"), os.O_CREATE, 0700) + if err != nil { + t.Fatal(err) + } + f.Close() + + dockerFile := fmt.Sprintf("FROM busybox\nRUN mkdir -p %s\nRUN ln -s %s /test", dir, dir) + if _, err := buildImage(name, dockerFile, false); err != nil { + t.Fatal(err) + } + defer deleteImages(name) + + if out, _, err := dockerCmd(t, "run", "-v", "/test/test", name); err != nil { + t.Fatal(err, out) } logDone("run - create volume in symlink directory") From aa3083f577224ad74384f648b17c1474ab47b44f Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Tue, 31 Mar 2015 13:17:25 -0700 Subject: [PATCH 197/999] Fix progress reader output on close Currently the progress reader won't close properly by not setting the close size. fixes #11849 Signed-off-by: Derek McGowan (github: dmcgowan) --- pkg/progressreader/progressreader.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/progressreader/progressreader.go b/pkg/progressreader/progressreader.go index e548b0755..652831bff 100644 --- a/pkg/progressreader/progressreader.go +++ b/pkg/progressreader/progressreader.go @@ -1,9 +1,10 @@ package progressreader import ( + "io" + "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/streamformatter" - "io" ) // Reader with progress bar @@ -43,6 +44,7 @@ func (config *Config) Read(p []byte) (n int, err error) { return read, err } func (config *Config) Close() error { + config.Current = config.Size config.Out.Write(config.Formatter.FormatProgress(config.ID, config.Action, &jsonmessage.JSONProgress{Current: config.Current, Total: config.Size})) return config.In.Close() } From 62806cc85e7faee56acc454b67b8f36786472759 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 31 Mar 2015 13:37:49 -0700 Subject: [PATCH 198/999] Refactor API socket handling Signed-off-by: Michael Crosby --- api/server/server.go | 118 ------------------------------- api/server/server_linux.go | 130 ++++++++++++++++------------------- api/server/server_windows.go | 29 ++++++-- api/server/tcp_socket.go | 74 ++++++++++++++++++++ api/server/unix_socket.go | 78 +++++++++++++++++++++ docker/daemon.go | 1 - 6 files changed, 233 insertions(+), 197 deletions(-) create mode 100644 api/server/tcp_socket.go create mode 100644 api/server/unix_socket.go diff --git a/api/server/server.go b/api/server/server.go index 3d248bb58..bb406aac5 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -15,11 +15,7 @@ import ( "strconv" "strings" - "crypto/tls" - "crypto/x509" - "code.google.com/p/go.net/websocket" - "github.com/docker/libcontainer/user" "github.com/gorilla/mux" "github.com/Sirupsen/logrus" @@ -27,7 +23,6 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" - "github.com/docker/docker/pkg/listenbuffer" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/streamformatter" @@ -1409,90 +1404,6 @@ func ServeRequest(eng *engine.Engine, apiversion version.Version, w http.Respons router.ServeHTTP(w, req) } -func lookupGidByName(nameOrGid string) (int, error) { - groupFile, err := user.GetGroupPath() - if err != nil { - return -1, err - } - groups, err := user.ParseGroupFileFilter(groupFile, func(g user.Group) bool { - return g.Name == nameOrGid || strconv.Itoa(g.Gid) == nameOrGid - }) - if err != nil { - return -1, err - } - if groups != nil && len(groups) > 0 { - return groups[0].Gid, nil - } - gid, err := strconv.Atoi(nameOrGid) - if err == nil { - logrus.Warnf("Could not find GID %d", gid) - return gid, nil - } - return -1, fmt.Errorf("Group %s not found", nameOrGid) -} - -func setupTls(cert, key, ca string, l net.Listener) (net.Listener, error) { - tlsCert, err := tls.LoadX509KeyPair(cert, key) - if err != nil { - if os.IsNotExist(err) { - return nil, fmt.Errorf("Could not load X509 key pair (%s, %s): %v", cert, key, err) - } - return nil, fmt.Errorf("Error reading X509 key pair (%s, %s): %q. Make sure the key is encrypted.", - cert, key, err) - } - tlsConfig := &tls.Config{ - NextProtos: []string{"http/1.1"}, - Certificates: []tls.Certificate{tlsCert}, - // Avoid fallback on insecure SSL protocols - MinVersion: tls.VersionTLS10, - } - - if ca != "" { - certPool := x509.NewCertPool() - file, err := ioutil.ReadFile(ca) - if err != nil { - return nil, fmt.Errorf("Could not read CA certificate: %v", err) - } - certPool.AppendCertsFromPEM(file) - tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert - tlsConfig.ClientCAs = certPool - } - - return tls.NewListener(l, tlsConfig), nil -} - -func newListener(proto, addr string, bufferRequests bool) (net.Listener, error) { - if bufferRequests { - return listenbuffer.NewListenBuffer(proto, addr, activationLock) - } - return net.Listen(proto, addr) -} - -func changeGroup(addr string, nameOrGid string) error { - gid, err := lookupGidByName(nameOrGid) - if err != nil { - return err - } - - logrus.Debugf("%s group found. gid: %d", nameOrGid, gid) - return os.Chown(addr, 0, gid) -} - -func setSocketGroup(addr, group string) error { - if group == "" { - return nil - } - - if err := changeGroup(addr, group); err != nil { - if group != "docker" { - return err - } - logrus.Debugf("Warning: could not chgrp %s to docker: %v", addr, err) - } - - return nil -} - func allocateDaemonPort(addr string) error { host, port, err := net.SplitHostPort(addr) if err != nil { @@ -1519,35 +1430,6 @@ func allocateDaemonPort(addr string) error { return nil } -func setupTcpHttp(addr string, job *engine.Job) (*HttpServer, error) { - if !job.GetenvBool("TlsVerify") { - logrus.Infof("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") - } - - r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("CorsHeaders"), job.Getenv("Version")) - - l, err := newListener("tcp", addr, job.GetenvBool("BufferRequests")) - if err != nil { - return nil, err - } - - if err := allocateDaemonPort(addr); err != nil { - return nil, err - } - - if job.GetenvBool("Tls") || job.GetenvBool("TlsVerify") { - var tlsCa string - if job.GetenvBool("TlsVerify") { - tlsCa = job.Getenv("TlsCa") - } - l, err = setupTls(job.Getenv("TlsCert"), job.Getenv("TlsKey"), tlsCa, l) - if err != nil { - return nil, err - } - } - return &HttpServer{&http.Server{Addr: addr, Handler: r}, l}, nil -} - type Server interface { Serve() error Close() error diff --git a/api/server/server_linux.go b/api/server/server_linux.go index 972f5ff74..dd00e7d2e 100644 --- a/api/server/server_linux.go +++ b/api/server/server_linux.go @@ -4,100 +4,86 @@ package server import ( "fmt" + "net" "net/http" - "os" - "syscall" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/systemd" ) // NewServer sets up the required Server and does protocol specific checking. func NewServer(proto, addr string, job *engine.Job) (Server, error) { - // Basic error and sanity checking + var ( + err error + l net.Listener + r = createRouter( + job.Eng, + job.GetenvBool("Logging"), + job.GetenvBool("EnableCors"), + job.Getenv("CorsHeaders"), + job.Getenv("Version"), + ) + ) switch proto { case "fd": - return nil, serveFd(addr, job) - case "tcp": - return setupTcpHttp(addr, job) - case "unix": - return setupUnixHttp(addr, job) - default: - return nil, fmt.Errorf("Invalid protocol format.") - } -} - -func setupUnixHttp(addr string, job *engine.Job) (*HttpServer, error) { - r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("CorsHeaders"), job.Getenv("Version")) - - if err := syscall.Unlink(addr); err != nil && !os.IsNotExist(err) { - return nil, err - } - mask := syscall.Umask(0777) - defer syscall.Umask(mask) - - l, err := newListener("unix", addr, job.GetenvBool("BufferRequests")) - if err != nil { - return nil, err - } - - if err := setSocketGroup(addr, job.Getenv("SocketGroup")); err != nil { - return nil, err - } - - if err := os.Chmod(addr, 0660); err != nil { - return nil, err - } - - return &HttpServer{&http.Server{Addr: addr, Handler: r}, l}, nil -} - -// serveFd creates an http.Server and sets it up to serve given a socket activated -// argument. -func serveFd(addr string, job *engine.Job) error { - r := createRouter(job.Eng, job.GetenvBool("Logging"), job.GetenvBool("EnableCors"), job.Getenv("CorsHeaders"), job.Getenv("Version")) - - ls, e := systemd.ListenFD(addr) - if e != nil { - return e - } - - chErrors := make(chan error, len(ls)) - - // We don't want to start serving on these sockets until the - // daemon is initialized and installed. Otherwise required handlers - // won't be ready. - <-activationLock - - // Since ListenFD will return one or more sockets we have - // to create a go func to spawn off multiple serves - for i := range ls { - listener := ls[i] - go func() { - httpSrv := http.Server{Handler: r} - chErrors <- httpSrv.Serve(listener) - }() - } - - for i := 0; i < len(ls); i++ { - err := <-chErrors + ls, err := systemd.ListenFD(addr) if err != nil { - return err + return nil, err } + chErrors := make(chan error, len(ls)) + // We don't want to start serving on these sockets until the + // daemon is initialized and installed. Otherwise required handlers + // won't be ready. + <-activationLock + // Since ListenFD will return one or more sockets we have + // to create a go func to spawn off multiple serves + for i := range ls { + listener := ls[i] + go func() { + httpSrv := http.Server{Handler: r} + chErrors <- httpSrv.Serve(listener) + }() + } + for i := 0; i < len(ls); i++ { + if err := <-chErrors; err != nil { + return nil, err + } + } + return nil, nil + case "tcp": + if !job.GetenvBool("TlsVerify") { + logrus.Infof("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") + } + if l, err = NewTcpSocket(addr, tlsConfigFromJob(job)); err != nil { + return nil, err + } + if err := allocateDaemonPort(addr); err != nil { + return nil, err + } + case "unix": + if l, err = NewUnixSocket(addr, job.Getenv("SocketGroup")); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("Invalid protocol format: %q", proto) } - - return nil + return &HttpServer{ + &http.Server{ + Addr: addr, + Handler: r, + }, + l, + }, nil } // Called through eng.Job("acceptconnections") func AcceptConnections(job *engine.Job) error { // Tell the init daemon we are accepting requests go systemd.SdNotify("READY=1") - // close the lock so the listeners start accepting connections if activationLock != nil { close(activationLock) } - return nil } diff --git a/api/server/server_windows.go b/api/server/server_windows.go index c5d2c2ca5..e7feb55a2 100644 --- a/api/server/server_windows.go +++ b/api/server/server_windows.go @@ -1,19 +1,38 @@ // +build windows - package server import ( - "fmt" + "errors" + "net" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" ) // NewServer sets up the required Server and does protocol specific checking. func NewServer(proto, addr string, job *engine.Job) (Server, error) { - // Basic error and sanity checking + var ( + err error + l net.Listener + r = createRouter( + job.Eng, + job.GetenvBool("Logging"), + job.GetenvBool("EnableCors"), + job.Getenv("CorsHeaders"), + job.Getenv("Version"), + ) + ) switch proto { case "tcp": - return setupTcpHttp(addr, job) + if !job.GetenvBool("TlsVerify") { + logrus.Infof("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") + } + if l, err = NewTcpSocket(addr, tlsConfigFromJob(job)); err != nil { + return nil, err + } + if err := allocateDaemonPort(addr); err != nil { + return nil, err + } default: return nil, errors.New("Invalid protocol format. Windows only supports tcp.") } @@ -21,11 +40,9 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { // Called through eng.Job("acceptconnections") func AcceptConnections(job *engine.Job) engine.Status { - // close the lock so the listeners start accepting connections if activationLock != nil { close(activationLock) } - return engine.StatusOK } diff --git a/api/server/tcp_socket.go b/api/server/tcp_socket.go new file mode 100644 index 000000000..415542c14 --- /dev/null +++ b/api/server/tcp_socket.go @@ -0,0 +1,74 @@ +package server + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "io/ioutil" + "net" + "os" + + "github.com/docker/docker/engine" + "github.com/docker/docker/pkg/listenbuffer" +) + +type tlsConfig struct { + CA string + Certificate string + Key string + Verify bool +} + +func tlsConfigFromJob(job *engine.Job) *tlsConfig { + verify := job.GetenvBool("TlsVerify") + if !job.GetenvBool("Tls") && !verify { + return nil + } + return &tlsConfig{ + Verify: verify, + Certificate: job.Getenv("TlsCert"), + Key: job.Getenv("TlsKey"), + CA: job.Getenv("TlsCa"), + } +} + +func NewTcpSocket(addr string, config *tlsConfig) (net.Listener, error) { + l, err := listenbuffer.NewListenBuffer("tcp", addr, activationLock) + if err != nil { + return nil, err + } + if config != nil { + if l, err = setupTls(l, config); err != nil { + return nil, err + } + } + return l, nil +} + +func setupTls(l net.Listener, config *tlsConfig) (net.Listener, error) { + tlsCert, err := tls.LoadX509KeyPair(config.Certificate, config.Key) + if err != nil { + if os.IsNotExist(err) { + return nil, fmt.Errorf("Could not load X509 key pair (%s, %s): %v", config.Certificate, config.Key, err) + } + return nil, fmt.Errorf("Error reading X509 key pair (%s, %s): %q. Make sure the key is encrypted.", + config.Certificate, config.Key, err) + } + tlsConfig := &tls.Config{ + NextProtos: []string{"http/1.1"}, + Certificates: []tls.Certificate{tlsCert}, + // Avoid fallback on insecure SSL protocols + MinVersion: tls.VersionTLS10, + } + if config.CA != "" { + certPool := x509.NewCertPool() + file, err := ioutil.ReadFile(config.CA) + if err != nil { + return nil, fmt.Errorf("Could not read CA certificate: %v", err) + } + certPool.AppendCertsFromPEM(file) + tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert + tlsConfig.ClientCAs = certPool + } + return tls.NewListener(l, tlsConfig), nil +} diff --git a/api/server/unix_socket.go b/api/server/unix_socket.go new file mode 100644 index 000000000..e472efd0a --- /dev/null +++ b/api/server/unix_socket.go @@ -0,0 +1,78 @@ +package server + +import ( + "fmt" + "net" + "os" + "strconv" + "syscall" + + "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/listenbuffer" + "github.com/docker/libcontainer/user" +) + +func NewUnixSocket(path, group string) (net.Listener, error) { + if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) { + return nil, err + } + mask := syscall.Umask(0777) + defer syscall.Umask(mask) + l, err := listenbuffer.NewListenBuffer("unix", path, activationLock) + if err != nil { + return nil, err + } + if err := setSocketGroup(path, group); err != nil { + l.Close() + return nil, err + } + if err := os.Chmod(path, 0660); err != nil { + l.Close() + return nil, err + } + return l, nil +} + +func setSocketGroup(path, group string) error { + if group == "" { + return nil + } + if err := changeGroup(path, group); err != nil { + if group != "docker" { + return err + } + logrus.Debugf("Warning: could not change group %s to docker: %v", path, err) + } + return nil +} + +func changeGroup(path string, nameOrGid string) error { + gid, err := lookupGidByName(nameOrGid) + if err != nil { + return err + } + logrus.Debugf("%s group found. gid: %d", nameOrGid, gid) + return os.Chown(path, 0, gid) +} + +func lookupGidByName(nameOrGid string) (int, error) { + groupFile, err := user.GetGroupPath() + if err != nil { + return -1, err + } + groups, err := user.ParseGroupFileFilter(groupFile, func(g user.Group) bool { + return g.Name == nameOrGid || strconv.Itoa(g.Gid) == nameOrGid + }) + if err != nil { + return -1, err + } + if groups != nil && len(groups) > 0 { + return groups[0].Gid, nil + } + gid, err := strconv.Atoi(nameOrGid) + if err == nil { + logrus.Warnf("Could not find GID %d", gid) + return gid, nil + } + return -1, fmt.Errorf("Group %s not found", nameOrGid) +} diff --git a/docker/daemon.go b/docker/daemon.go index 6ea96e889..534bc3a47 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -151,7 +151,6 @@ func mainDaemon() { job.Setenv("TlsCa", *flCa) job.Setenv("TlsCert", *flCert) job.Setenv("TlsKey", *flKey) - job.SetenvBool("BufferRequests", true) // The serve API job never exits unless an error occurs // We need to start it as a goroutine and wait on it so From 2c72ff1dbfa83aa8f797bdfebaacb8a919677326 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 31 Mar 2015 15:10:23 -0400 Subject: [PATCH 199/999] graphdriver: promote overlay above vfs It's about time to let folks not hit 'vfs', when 'overlay' is supported on their kernel. Especially now that v3.18.y is a long-term kernel. Signed-off-by: Vincent Batts --- daemon/graphdriver/driver.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 01f1182d1..26095b05c 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -39,9 +39,8 @@ var ( "aufs", "btrfs", "devicemapper", - "vfs", - // experimental, has to be enabled manually for now "overlay", + "vfs", } ErrNotSupported = errors.New("driver not supported") From f011d722ce2ec7ef00654130f7f4ff8d295f025f Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Tue, 17 Mar 2015 11:03:56 -0700 Subject: [PATCH 200/999] Vendor distribution v2 api Signed-off-by: Derek McGowan (github: dmcgowan) --- hack/vendor.sh | 7 +- .../registry/api/v2/descriptors.go | 1459 +++++++++++++++++ .../distribution/registry/api/v2/doc.go | 9 + .../distribution/registry/api/v2/errors.go | 194 +++ .../registry/api/v2/errors_test.go | 165 ++ .../distribution/registry/api/v2/names.go | 100 ++ .../registry/api/v2/names_test.go | 100 ++ .../distribution/registry/api/v2/routes.go | 47 + .../registry/api/v2/routes_test.go | 315 ++++ .../distribution/registry/api/v2/urls.go | 217 +++ .../distribution/registry/api/v2/urls_test.go | 225 +++ vendor/src/github.com/gorilla/mux/mux.go | 6 +- vendor/src/github.com/gorilla/mux/mux_test.go | 45 + vendor/src/github.com/gorilla/mux/old_test.go | 48 +- vendor/src/github.com/gorilla/mux/regexp.go | 62 +- vendor/src/github.com/gorilla/mux/route.go | 63 +- 16 files changed, 2970 insertions(+), 92 deletions(-) create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/doc.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/errors.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/errors_test.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/names.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/names_test.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/routes.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/routes_test.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/urls.go create mode 100644 vendor/src/github.com/docker/distribution/registry/api/v2/urls_test.go diff --git a/hack/vendor.sh b/hack/vendor.sh index 0246f03cc..abaa4d720 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -43,7 +43,7 @@ clone git github.com/kr/pty 05017fcccf clone git github.com/gorilla/context 14f550f51a -clone git github.com/gorilla/mux 136d54f81f +clone git github.com/gorilla/mux e444e69cbd clone git github.com/tchap/go-patricia v1.0.1 @@ -68,12 +68,15 @@ if [ "$1" = '--go' ]; then mv tmp-tar src/code.google.com/p/go/src/pkg/archive/tar fi -# get digest package from distribution +# get distribution packages clone git github.com/docker/distribution d957768537c5af40e4f4cd96871f7b2bde9e2923 mv src/github.com/docker/distribution/digest tmp-digest +mv src/github.com/docker/distribution/registry/api tmp-api rm -rf src/github.com/docker/distribution mkdir -p src/github.com/docker/distribution mv tmp-digest src/github.com/docker/distribution/digest +mkdir -p src/github.com/docker/distribution/registry +mv tmp-api src/github.com/docker/distribution/registry/api clone git github.com/docker/libcontainer c8512754166539461fd860451ff1a0af7491c197 # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go b/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go new file mode 100644 index 000000000..5f091bbc9 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go @@ -0,0 +1,1459 @@ +package v2 + +import ( + "net/http" + "regexp" + + "github.com/docker/distribution/digest" +) + +var ( + nameParameterDescriptor = ParameterDescriptor{ + Name: "name", + Type: "string", + Format: RepositoryNameRegexp.String(), + Required: true, + Description: `Name of the target repository.`, + } + + tagParameterDescriptor = ParameterDescriptor{ + Name: "tag", + Type: "string", + Format: TagNameRegexp.String(), + Required: true, + Description: `Tag of the target manifiest.`, + } + + uuidParameterDescriptor = ParameterDescriptor{ + Name: "uuid", + Type: "opaque", + Required: true, + Description: `A uuid identifying the upload. This field can accept almost anything.`, + } + + digestPathParameter = ParameterDescriptor{ + Name: "digest", + Type: "path", + Required: true, + Format: digest.DigestRegexp.String(), + Description: `Digest of desired blob.`, + } + + hostHeader = ParameterDescriptor{ + Name: "Host", + Type: "string", + Description: "Standard HTTP Host Header. Should be set to the registry host.", + Format: "", + Examples: []string{"registry-1.docker.io"}, + } + + authHeader = ParameterDescriptor{ + Name: "Authorization", + Type: "string", + Description: "An RFC7235 compliant authorization header.", + Format: " ", + Examples: []string{"Bearer dGhpcyBpcyBhIGZha2UgYmVhcmVyIHRva2VuIQ=="}, + } + + authChallengeHeader = ParameterDescriptor{ + Name: "WWW-Authenticate", + Type: "string", + Description: "An RFC7235 compliant authentication challenge header.", + Format: ` realm="", ..."`, + Examples: []string{ + `Bearer realm="https://auth.docker.com/", service="registry.docker.com", scopes="repository:library/ubuntu:pull"`, + }, + } + + contentLengthZeroHeader = ParameterDescriptor{ + Name: "Content-Length", + Description: "The `Content-Length` header must be zero and the body must be empty.", + Type: "integer", + Format: "0", + } + + dockerUploadUUIDHeader = ParameterDescriptor{ + Name: "Docker-Upload-UUID", + Description: "Identifies the docker upload uuid for the current request.", + Type: "uuid", + Format: "", + } + + digestHeader = ParameterDescriptor{ + Name: "Docker-Content-Digest", + Description: "Digest of the targeted content for the request.", + Type: "digest", + Format: "", + } + + unauthorizedResponse = ResponseDescriptor{ + Description: "The client does not have access to the repository.", + StatusCode: http.StatusUnauthorized, + Headers: []ParameterDescriptor{ + authChallengeHeader, + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON error response body.", + Format: "", + }, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: unauthorizedErrorsBody, + }, + } + + unauthorizedResponsePush = ResponseDescriptor{ + Description: "The client does not have access to push to the repository.", + StatusCode: http.StatusUnauthorized, + Headers: []ParameterDescriptor{ + authChallengeHeader, + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON error response body.", + Format: "", + }, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: unauthorizedErrorsBody, + }, + } +) + +const ( + manifestBody = `{ + "name": , + "tag": , + "fsLayers": [ + { + "blobSum": + }, + ... + ] + ], + "history": , + "signature": +}` + + errorsBody = `{ + "errors:" [ + { + "code": , + "message": "", + "detail": ... + }, + ... + ] +}` + + unauthorizedErrorsBody = `{ + "errors:" [ + { + "code": "UNAUTHORIZED", + "message": "access to the requested resource is not authorized", + "detail": ... + }, + ... + ] +}` +) + +// APIDescriptor exports descriptions of the layout of the v2 registry API. +var APIDescriptor = struct { + // RouteDescriptors provides a list of the routes available in the API. + RouteDescriptors []RouteDescriptor + + // ErrorDescriptors provides a list of the error codes and their + // associated documentation and metadata. + ErrorDescriptors []ErrorDescriptor +}{ + RouteDescriptors: routeDescriptors, + ErrorDescriptors: errorDescriptors, +} + +// RouteDescriptor describes a route specified by name. +type RouteDescriptor struct { + // Name is the name of the route, as specified in RouteNameXXX exports. + // These names a should be considered a unique reference for a route. If + // the route is registered with gorilla, this is the name that will be + // used. + Name string + + // Path is a gorilla/mux-compatible regexp that can be used to match the + // route. For any incoming method and path, only one route descriptor + // should match. + Path string + + // Entity should be a short, human-readalbe description of the object + // targeted by the endpoint. + Entity string + + // Description should provide an accurate overview of the functionality + // provided by the route. + Description string + + // Methods should describe the various HTTP methods that may be used on + // this route, including request and response formats. + Methods []MethodDescriptor +} + +// MethodDescriptor provides a description of the requests that may be +// conducted with the target method. +type MethodDescriptor struct { + + // Method is an HTTP method, such as GET, PUT or POST. + Method string + + // Description should provide an overview of the functionality provided by + // the covered method, suitable for use in documentation. Use of markdown + // here is encouraged. + Description string + + // Requests is a slice of request descriptors enumerating how this + // endpoint may be used. + Requests []RequestDescriptor +} + +// RequestDescriptor covers a particular set of headers and parameters that +// can be carried out with the parent method. Its most helpful to have one +// RequestDescriptor per API use case. +type RequestDescriptor struct { + // Name provides a short identifier for the request, usable as a title or + // to provide quick context for the particalar request. + Name string + + // Description should cover the requests purpose, covering any details for + // this particular use case. + Description string + + // Headers describes headers that must be used with the HTTP request. + Headers []ParameterDescriptor + + // PathParameters enumerate the parameterized path components for the + // given request, as defined in the route's regular expression. + PathParameters []ParameterDescriptor + + // QueryParameters provides a list of query parameters for the given + // request. + QueryParameters []ParameterDescriptor + + // Body describes the format of the request body. + Body BodyDescriptor + + // Successes enumerates the possible responses that are considered to be + // the result of a successful request. + Successes []ResponseDescriptor + + // Failures covers the possible failures from this particular request. + Failures []ResponseDescriptor +} + +// ResponseDescriptor describes the components of an API response. +type ResponseDescriptor struct { + // Name provides a short identifier for the response, usable as a title or + // to provide quick context for the particalar response. + Name string + + // Description should provide a brief overview of the role of the + // response. + Description string + + // StatusCode specifies the status recieved by this particular response. + StatusCode int + + // Headers covers any headers that may be returned from the response. + Headers []ParameterDescriptor + + // ErrorCodes enumerates the error codes that may be returned along with + // the response. + ErrorCodes []ErrorCode + + // Body describes the body of the response, if any. + Body BodyDescriptor +} + +// BodyDescriptor describes a request body and its expected content type. For +// the most part, it should be example json or some placeholder for body +// data in documentation. +type BodyDescriptor struct { + ContentType string + Format string +} + +// ParameterDescriptor describes the format of a request parameter, which may +// be a header, path parameter or query parameter. +type ParameterDescriptor struct { + // Name is the name of the parameter, either of the path component or + // query parameter. + Name string + + // Type specifies the type of the parameter, such as string, integer, etc. + Type string + + // Description provides a human-readable description of the parameter. + Description string + + // Required means the field is required when set. + Required bool + + // Format is a specifying the string format accepted by this parameter. + Format string + + // Regexp is a compiled regular expression that can be used to validate + // the contents of the parameter. + Regexp *regexp.Regexp + + // Examples provides multiple examples for the values that might be valid + // for this parameter. + Examples []string +} + +// ErrorDescriptor provides relevant information about a given error code. +type ErrorDescriptor struct { + // Code is the error code that this descriptor describes. + Code ErrorCode + + // Value provides a unique, string key, often captilized with + // underscores, to identify the error code. This value is used as the + // keyed value when serializing api errors. + Value string + + // Message is a short, human readable decription of the error condition + // included in API responses. + Message string + + // Description provides a complete account of the errors purpose, suitable + // for use in documentation. + Description string + + // HTTPStatusCodes provides a list of status under which this error + // condition may arise. If it is empty, the error condition may be seen + // for any status code. + HTTPStatusCodes []int +} + +var routeDescriptors = []RouteDescriptor{ + { + Name: RouteNameBase, + Path: "/v2/", + Entity: "Base", + Description: `Base V2 API route. Typically, this can be used for lightweight version checks and to validate registry authorization.`, + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Check that the endpoint implements Docker Registry API V2.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + Successes: []ResponseDescriptor{ + { + Description: "The API implements V2 protocol and is accessible.", + StatusCode: http.StatusOK, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "The client is not authorized to access the registry.", + StatusCode: http.StatusUnauthorized, + Headers: []ParameterDescriptor{ + authChallengeHeader, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + }, + { + Description: "The registry does not implement the V2 API.", + StatusCode: http.StatusNotFound, + }, + }, + }, + }, + }, + }, + }, + { + Name: RouteNameTags, + Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/tags/list", + Entity: "Tags", + Description: "Retrieve information about tags.", + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Fetch the tags under the repository identified by `name`.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + StatusCode: http.StatusOK, + Description: "A list of tags for the named repository.", + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON response body.", + Format: "", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: `{ + "name": , + "tags": [ + , + ... + ] +}`, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + StatusCode: http.StatusNotFound, + Description: "The repository is not known to the registry.", + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeNameUnknown, + }, + }, + { + StatusCode: http.StatusUnauthorized, + Description: "The client does not have access to the repository.", + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + }, + }, + }, + }, + }, + }, + }, + { + Name: RouteNameManifest, + Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{reference:" + TagNameRegexp.String() + "|" + digest.DigestRegexp.String() + "}", + Entity: "Manifest", + Description: "Create, update and retrieve manifests.", + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Fetch the manifest identified by `name` and `reference` where `reference` can be a tag or digest.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + tagParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + Description: "The manifest idenfied by `name` and `reference`. The contents can be used to identify and resolve resources required to run the specified image.", + StatusCode: http.StatusOK, + Headers: []ParameterDescriptor{ + digestHeader, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: manifestBody, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "The name or reference was invalid.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeTagInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + StatusCode: http.StatusUnauthorized, + Description: "The client does not have access to the repository.", + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + }, + { + Description: "The named manifest is not known to the registry.", + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeNameUnknown, + ErrorCodeManifestUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + }, + }, + }, + }, + { + Method: "PUT", + Description: "Put the manifest identified by `name` and `reference` where `reference` can be a tag or digest.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + tagParameterDescriptor, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: manifestBody, + }, + Successes: []ResponseDescriptor{ + { + Description: "The manifest has been accepted by the registry and is stored under the specified `name` and `tag`.", + StatusCode: http.StatusAccepted, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Description: "The canonical location url of the uploaded manifest.", + Format: "", + }, + contentLengthZeroHeader, + digestHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Manifest", + Description: "The received manifest was invalid in some way, as described by the error codes. The client should resolve the issue and retry the request.", + StatusCode: http.StatusBadRequest, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeTagInvalid, + ErrorCodeManifestInvalid, + ErrorCodeManifestUnverified, + ErrorCodeBlobUnknown, + }, + }, + { + StatusCode: http.StatusUnauthorized, + Description: "The client does not have permission to push to the repository.", + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + }, + { + Name: "Missing Layer(s)", + Description: "One or more layers may be missing during a manifest upload. If so, the missing layers will be enumerated in the error response.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeBlobUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: `{ + "errors:" [{ + "code": "BLOB_UNKNOWN", + "message": "blob unknown to registry", + "detail": { + "digest": + } + }, + ... + ] +}`, + }, + }, + { + StatusCode: http.StatusUnauthorized, + Headers: []ParameterDescriptor{ + authChallengeHeader, + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON error response body.", + Format: "", + }, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + }, + }, + }, + }, + { + Method: "DELETE", + Description: "Delete the manifest identified by `name` and `reference` where `reference` can be a tag or digest.", + Requests: []RequestDescriptor{ + { + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + tagParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + StatusCode: http.StatusAccepted, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Name or Tag", + Description: "The specified `name` or `tag` were invalid and the delete was unable to proceed.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeTagInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + StatusCode: http.StatusUnauthorized, + Headers: []ParameterDescriptor{ + authChallengeHeader, + { + Name: "Content-Length", + Type: "integer", + Description: "Length of the JSON error response body.", + Format: "", + }, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeUnauthorized, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Name: "Unknown Manifest", + Description: "The specified `name` or `tag` are unknown to the registry and the delete was unable to proceed. Clients can assume the manifest was already deleted if this response is returned.", + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeNameUnknown, + ErrorCodeManifestUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + }, + }, + }, + }, + }, + }, + + { + Name: RouteNameBlob, + Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/{digest:" + digest.DigestRegexp.String() + "}", + Entity: "Blob", + Description: "Fetch the blob identified by `name` and `digest`. Used to fetch layers by tarsum digest.", + Methods: []MethodDescriptor{ + + { + Method: "GET", + Description: "Retrieve the blob from the registry identified by `digest`. A `HEAD` request can also be issued to this endpoint to obtain resource information without receiving all data.", + Requests: []RequestDescriptor{ + { + Name: "Fetch Blob", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + digestPathParameter, + }, + Successes: []ResponseDescriptor{ + { + Description: "The blob identified by `digest` is available. The blob content will be present in the body of the request.", + StatusCode: http.StatusOK, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "The length of the requested blob content.", + Format: "", + }, + digestHeader, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + }, + { + Description: "The blob identified by `digest` is available at the provided location.", + StatusCode: http.StatusTemporaryRedirect, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Description: "The location where the layer should be accessible.", + Format: "", + }, + digestHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was a problem with the request that needs to be addressed by the client, such as an invalid `name` or `tag`.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeDigestInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponse, + { + Description: "The blob, identified by `name` and `digest`, is unknown to the registry.", + StatusCode: http.StatusNotFound, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + ErrorCodes: []ErrorCode{ + ErrorCodeNameUnknown, + ErrorCodeBlobUnknown, + }, + }, + }, + }, + { + Name: "Fetch Blob Part", + Description: "This endpoint may also support RFC7233 compliant range requests. Support can be detected by issuing a HEAD request. If the header `Accept-Range: bytes` is returned, range requests can be used to fetch partial content.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + { + Name: "Range", + Type: "string", + Description: "HTTP Range header specifying blob chunk.", + Format: "bytes=-", + }, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + digestPathParameter, + }, + Successes: []ResponseDescriptor{ + { + Description: "The blob identified by `digest` is available. The specified chunk of blob content will be present in the body of the request.", + StatusCode: http.StatusPartialContent, + Headers: []ParameterDescriptor{ + { + Name: "Content-Length", + Type: "integer", + Description: "The length of the requested blob chunk.", + Format: "", + }, + { + Name: "Content-Range", + Type: "byte range", + Description: "Content range of blob chunk.", + Format: "bytes -/", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was a problem with the request that needs to be addressed by the client, such as an invalid `name` or `tag`.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeDigestInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponse, + { + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeNameUnknown, + ErrorCodeBlobUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The range specification cannot be satisfied for the requested content. This can happen when the range is not formatted correctly or if the range is outside of the valid size of the content.", + StatusCode: http.StatusRequestedRangeNotSatisfiable, + }, + }, + }, + }, + }, + // TODO(stevvooe): We may want to add a PUT request here to + // kickoff an upload of a blob, integrated with the blob upload + // API. + }, + }, + + { + Name: RouteNameBlobUpload, + Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/", + Entity: "Intiate Blob Upload", + Description: "Initiate a blob upload. This endpoint can be used to create resumable uploads or monolithic uploads.", + Methods: []MethodDescriptor{ + { + Method: "POST", + Description: "Initiate a resumable blob upload. If successful, an upload location will be provided to complete the upload. Optionally, if the `digest` parameter is present, the request body will be used to complete the upload in a single request.", + Requests: []RequestDescriptor{ + { + Name: "Initiate Monolithic Blob Upload", + Description: "Upload a blob identified by the `digest` parameter in single request. This upload will not be resumable unless a recoverable error is returned.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + { + Name: "Content-Length", + Type: "integer", + Format: "", + }, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + }, + QueryParameters: []ParameterDescriptor{ + { + Name: "digest", + Type: "query", + Format: "", + Regexp: digest.DigestRegexp, + Description: `Digest of uploaded blob. If present, the upload will be completed, in a single request, with contents of the request body as the resulting blob.`, + }, + }, + Body: BodyDescriptor{ + ContentType: "application/octect-stream", + Format: "", + }, + Successes: []ResponseDescriptor{ + { + Description: "The blob has been created in the registry and is available at the provided location.", + StatusCode: http.StatusCreated, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "", + }, + contentLengthZeroHeader, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Name or Digest", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + }, + }, + unauthorizedResponsePush, + }, + }, + { + Name: "Initiate Resumable Blob Upload", + Description: "Initiate a resumable blob upload with an empty request body.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + contentLengthZeroHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + Description: "The upload has been created. The `Location` header must be used to complete the upload. The response should be identical to a `GET` request on the contents of the returned `Location` header.", + StatusCode: http.StatusAccepted, + Headers: []ParameterDescriptor{ + contentLengthZeroHeader, + { + Name: "Location", + Type: "url", + Format: "/v2//blobs/uploads/", + Description: "The location of the created upload. Clients should use the contents verbatim to complete the upload, adding parameters where required.", + }, + { + Name: "Range", + Format: "0-0", + Description: "Range header indicating the progress of the upload. When starting an upload, it will return an empty range, since no content has been received.", + }, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Name: "Invalid Name or Digest", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + }, + }, + unauthorizedResponsePush, + }, + }, + }, + }, + }, + }, + + { + Name: RouteNameBlobUploadChunk, + Path: "/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid}", + Entity: "Blob Upload", + Description: "Interact with blob uploads. Clients should never assemble URLs for this endpoint and should only take it through the `Location` header on related API requests. The `Location` header and its parameters should be preserved by clients, using the latest value returned via upload related API calls.", + Methods: []MethodDescriptor{ + { + Method: "GET", + Description: "Retrieve status of upload identified by `uuid`. The primary purpose of this endpoint is to resolve the current status of a resumable upload.", + Requests: []RequestDescriptor{ + { + Description: "Retrieve the progress of the current upload, as reported by the `Range` header.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + Successes: []ResponseDescriptor{ + { + Name: "Upload Progress", + Description: "The upload is known and in progress. The last received offset is available in the `Range` header.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + { + Name: "Range", + Type: "header", + Format: "0-", + Description: "Range indicating the current progress of the upload.", + }, + contentLengthZeroHeader, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was an error processing the upload and it must be restarted.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponse, + { + Description: "The upload is unknown to the registry. The upload must be restarted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + }, + }, + }, + }, + { + Method: "PATCH", + Description: "Upload a chunk of data for the specified upload.", + Requests: []RequestDescriptor{ + { + Description: "Upload a chunk of data to specified upload without completing the upload.", + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + { + Name: "Content-Range", + Type: "header", + Format: "-", + Required: true, + Description: "Range of bytes identifying the desired block of content represented by the body. Start must the end offset retrieved via status check plus one. Note that this is a non-standard use of the `Content-Range` header.", + }, + { + Name: "Content-Length", + Type: "integer", + Format: "", + Description: "Length of the chunk being uploaded, corresponding the length of the request body.", + }, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + Successes: []ResponseDescriptor{ + { + Name: "Chunk Accepted", + Description: "The chunk of data has been accepted and the current progress is available in the range header. The updated upload location is available in the `Location` header.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "/v2//blobs/uploads/", + Description: "The location of the upload. Clients should assume this changes after each request. Clients should use the contents verbatim to complete the upload, adding parameters where required.", + }, + { + Name: "Range", + Type: "header", + Format: "0-", + Description: "Range indicating the current progress of the upload.", + }, + contentLengthZeroHeader, + dockerUploadUUIDHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was an error processing the upload and it must be restarted.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponsePush, + { + Description: "The upload is unknown to the registry. The upload must be restarted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The `Content-Range` specification cannot be accepted, either because it does not overlap with the current progress or it is invalid.", + StatusCode: http.StatusRequestedRangeNotSatisfiable, + }, + }, + }, + }, + }, + { + Method: "PUT", + Description: "Complete the upload specified by `uuid`, optionally appending the body as the final chunk.", + Requests: []RequestDescriptor{ + { + // TODO(stevvooe): Break this down into three separate requests: + // 1. Complete an upload where all data has already been sent. + // 2. Complete an upload where the entire body is in the PUT. + // 3. Complete an upload where the final, partial chunk is the body. + + Description: "Complete the upload, providing the _final_ chunk of data, if necessary. This method may take a body with all the data. If the `Content-Range` header is specified, it may include the final chunk. A request without a body will just complete the upload with previously uploaded content.", + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + { + Name: "Content-Range", + Type: "header", + Format: "-", + Description: "Range of bytes identifying the block of content represented by the body. Start must the end offset retrieved via status check plus one. Note that this is a non-standard use of the `Content-Range` header. May be omitted if no data is provided.", + }, + { + Name: "Content-Length", + Type: "integer", + Format: "", + Description: "Length of the chunk being uploaded, corresponding to the length of the request body. May be zero if no data is provided.", + }, + }, + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + QueryParameters: []ParameterDescriptor{ + { + Name: "digest", + Type: "string", + Format: "", + Regexp: digest.DigestRegexp, + Required: true, + Description: `Digest of uploaded blob.`, + }, + }, + Body: BodyDescriptor{ + ContentType: "application/octet-stream", + Format: "", + }, + Successes: []ResponseDescriptor{ + { + Name: "Upload Complete", + Description: "The upload has been completed and accepted by the registry. The canonical location will be available in the `Location` header.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "", + }, + { + Name: "Content-Range", + Type: "header", + Format: "-", + Description: "Range of bytes identifying the desired block of content represented by the body. Start must match the end of offset retrieved via status check. Note that this is a non-standard use of the `Content-Range` header.", + }, + { + Name: "Content-Length", + Type: "integer", + Format: "", + Description: "Length of the chunk being uploaded, corresponding the length of the request body.", + }, + digestHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "There was an error processing the upload and it must be restarted.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeDigestInvalid, + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponsePush, + { + Description: "The upload is unknown to the registry. The upload must be restarted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + { + Description: "The `Content-Range` specification cannot be accepted, either because it does not overlap with the current progress or it is invalid. The contents of the `Range` header may be used to resolve the condition.", + StatusCode: http.StatusRequestedRangeNotSatisfiable, + Headers: []ParameterDescriptor{ + { + Name: "Location", + Type: "url", + Format: "/v2//blobs/uploads/", + Description: "The location of the upload. Clients should assume this changes after each request. Clients should use the contents verbatim to complete the upload, adding parameters where required.", + }, + { + Name: "Range", + Type: "header", + Format: "0-", + Description: "Range indicating the current progress of the upload.", + }, + }, + }, + }, + }, + }, + }, + { + Method: "DELETE", + Description: "Cancel outstanding upload processes, releasing associated resources. If this is not called, the unfinished uploads will eventually timeout.", + Requests: []RequestDescriptor{ + { + Description: "Cancel the upload specified by `uuid`.", + PathParameters: []ParameterDescriptor{ + nameParameterDescriptor, + uuidParameterDescriptor, + }, + Headers: []ParameterDescriptor{ + hostHeader, + authHeader, + contentLengthZeroHeader, + }, + Successes: []ResponseDescriptor{ + { + Name: "Upload Deleted", + Description: "The upload has been successfully deleted.", + StatusCode: http.StatusNoContent, + Headers: []ParameterDescriptor{ + contentLengthZeroHeader, + }, + }, + }, + Failures: []ResponseDescriptor{ + { + Description: "An error was encountered processing the delete. The client may ignore this error.", + StatusCode: http.StatusBadRequest, + ErrorCodes: []ErrorCode{ + ErrorCodeNameInvalid, + ErrorCodeBlobUploadInvalid, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + unauthorizedResponse, + { + Description: "The upload is unknown to the registry. The client may ignore this error and assume the upload has been deleted.", + StatusCode: http.StatusNotFound, + ErrorCodes: []ErrorCode{ + ErrorCodeBlobUploadUnknown, + }, + Body: BodyDescriptor{ + ContentType: "application/json; charset=utf-8", + Format: errorsBody, + }, + }, + }, + }, + }, + }, + }, + }, +} + +// ErrorDescriptors provides a list of HTTP API Error codes that may be +// encountered when interacting with the registry API. +var errorDescriptors = []ErrorDescriptor{ + { + Code: ErrorCodeUnknown, + Value: "UNKNOWN", + Message: "unknown error", + Description: `Generic error returned when the error does not have an + API classification.`, + }, + { + Code: ErrorCodeUnsupported, + Value: "UNSUPPORTED", + Message: "The operation is unsupported.", + Description: `The operation was unsupported due to a missing + implementation or invalid set of parameters.`, + }, + { + Code: ErrorCodeUnauthorized, + Value: "UNAUTHORIZED", + Message: "access to the requested resource is not authorized", + Description: `The access controller denied access for the operation on + a resource. Often this will be accompanied by a 401 Unauthorized + response status.`, + }, + { + Code: ErrorCodeDigestInvalid, + Value: "DIGEST_INVALID", + Message: "provided digest did not match uploaded content", + Description: `When a blob is uploaded, the registry will check that + the content matches the digest provided by the client. The error may + include a detail structure with the key "digest", including the + invalid digest string. This error may also be returned when a manifest + includes an invalid layer digest.`, + HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + }, + { + Code: ErrorCodeSizeInvalid, + Value: "SIZE_INVALID", + Message: "provided length did not match content length", + Description: `When a layer is uploaded, the provided size will be + checked against the uploaded content. If they do not match, this error + will be returned.`, + HTTPStatusCodes: []int{http.StatusBadRequest}, + }, + { + Code: ErrorCodeNameInvalid, + Value: "NAME_INVALID", + Message: "invalid repository name", + Description: `Invalid repository name encountered either during + manifest validation or any API operation.`, + HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + }, + { + Code: ErrorCodeTagInvalid, + Value: "TAG_INVALID", + Message: "manifest tag did not match URI", + Description: `During a manifest upload, if the tag in the manifest + does not match the uri tag, this error will be returned.`, + HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + }, + { + Code: ErrorCodeNameUnknown, + Value: "NAME_UNKNOWN", + Message: "repository name not known to registry", + Description: `This is returned if the name used during an operation is + unknown to the registry.`, + HTTPStatusCodes: []int{http.StatusNotFound}, + }, + { + Code: ErrorCodeManifestUnknown, + Value: "MANIFEST_UNKNOWN", + Message: "manifest unknown", + Description: `This error is returned when the manifest, identified by + name and tag is unknown to the repository.`, + HTTPStatusCodes: []int{http.StatusNotFound}, + }, + { + Code: ErrorCodeManifestInvalid, + Value: "MANIFEST_INVALID", + Message: "manifest invalid", + Description: `During upload, manifests undergo several checks ensuring + validity. If those checks fail, this error may be returned, unless a + more specific error is included. The detail will contain information + the failed validation.`, + HTTPStatusCodes: []int{http.StatusBadRequest}, + }, + { + Code: ErrorCodeManifestUnverified, + Value: "MANIFEST_UNVERIFIED", + Message: "manifest failed signature verification", + Description: `During manifest upload, if the manifest fails signature + verification, this error will be returned.`, + HTTPStatusCodes: []int{http.StatusBadRequest}, + }, + { + Code: ErrorCodeBlobUnknown, + Value: "BLOB_UNKNOWN", + Message: "blob unknown to registry", + Description: `This error may be returned when a blob is unknown to the + registry in a specified repository. This can be returned with a + standard get or if a manifest references an unknown layer during + upload.`, + HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, + }, + + { + Code: ErrorCodeBlobUploadUnknown, + Value: "BLOB_UPLOAD_UNKNOWN", + Message: "blob upload unknown to registry", + Description: `If a blob upload has been cancelled or was never + started, this error code may be returned.`, + HTTPStatusCodes: []int{http.StatusNotFound}, + }, + { + Code: ErrorCodeBlobUploadInvalid, + Value: "BLOB_UPLOAD_INVALID", + Message: "blob upload invalid", + Description: `The blob upload encountered an error and can no + longer proceed.`, + HTTPStatusCodes: []int{http.StatusNotFound}, + }, +} + +var errorCodeToDescriptors map[ErrorCode]ErrorDescriptor +var idToDescriptors map[string]ErrorDescriptor +var routeDescriptorsMap map[string]RouteDescriptor + +func init() { + errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(errorDescriptors)) + idToDescriptors = make(map[string]ErrorDescriptor, len(errorDescriptors)) + routeDescriptorsMap = make(map[string]RouteDescriptor, len(routeDescriptors)) + + for _, descriptor := range errorDescriptors { + errorCodeToDescriptors[descriptor.Code] = descriptor + idToDescriptors[descriptor.Value] = descriptor + } + for _, descriptor := range routeDescriptors { + routeDescriptorsMap[descriptor.Name] = descriptor + } +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/doc.go b/vendor/src/github.com/docker/distribution/registry/api/v2/doc.go new file mode 100644 index 000000000..cde011959 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/doc.go @@ -0,0 +1,9 @@ +// Package v2 describes routes, urls and the error codes used in the Docker +// Registry JSON HTTP API V2. In addition to declarations, descriptors are +// provided for routes and error codes that can be used for implementation and +// automatically generating documentation. +// +// Definitions here are considered to be locked down for the V2 registry api. +// Any changes must be considered carefully and should not proceed without a +// change proposal in docker core. +package v2 diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/errors.go b/vendor/src/github.com/docker/distribution/registry/api/v2/errors.go new file mode 100644 index 000000000..cbae020ef --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/errors.go @@ -0,0 +1,194 @@ +package v2 + +import ( + "fmt" + "strings" +) + +// ErrorCode represents the error type. The errors are serialized via strings +// and the integer format may change and should *never* be exported. +type ErrorCode int + +const ( + // ErrorCodeUnknown is a catch-all for errors not defined below. + ErrorCodeUnknown ErrorCode = iota + + // ErrorCodeUnsupported is returned when an operation is not supported. + ErrorCodeUnsupported + + // ErrorCodeUnauthorized is returned if a request is not authorized. + ErrorCodeUnauthorized + + // ErrorCodeDigestInvalid is returned when uploading a blob if the + // provided digest does not match the blob contents. + ErrorCodeDigestInvalid + + // ErrorCodeSizeInvalid is returned when uploading a blob if the provided + // size does not match the content length. + ErrorCodeSizeInvalid + + // ErrorCodeNameInvalid is returned when the name in the manifest does not + // match the provided name. + ErrorCodeNameInvalid + + // ErrorCodeTagInvalid is returned when the tag in the manifest does not + // match the provided tag. + ErrorCodeTagInvalid + + // ErrorCodeNameUnknown when the repository name is not known. + ErrorCodeNameUnknown + + // ErrorCodeManifestUnknown returned when image manifest is unknown. + ErrorCodeManifestUnknown + + // ErrorCodeManifestInvalid returned when an image manifest is invalid, + // typically during a PUT operation. This error encompasses all errors + // encountered during manifest validation that aren't signature errors. + ErrorCodeManifestInvalid + + // ErrorCodeManifestUnverified is returned when the manifest fails + // signature verfication. + ErrorCodeManifestUnverified + + // ErrorCodeBlobUnknown is returned when a blob is unknown to the + // registry. This can happen when the manifest references a nonexistent + // layer or the result is not found by a blob fetch. + ErrorCodeBlobUnknown + + // ErrorCodeBlobUploadUnknown is returned when an upload is unknown. + ErrorCodeBlobUploadUnknown + + // ErrorCodeBlobUploadInvalid is returned when an upload is invalid. + ErrorCodeBlobUploadInvalid +) + +// ParseErrorCode attempts to parse the error code string, returning +// ErrorCodeUnknown if the error is not known. +func ParseErrorCode(s string) ErrorCode { + desc, ok := idToDescriptors[s] + + if !ok { + return ErrorCodeUnknown + } + + return desc.Code +} + +// Descriptor returns the descriptor for the error code. +func (ec ErrorCode) Descriptor() ErrorDescriptor { + d, ok := errorCodeToDescriptors[ec] + + if !ok { + return ErrorCodeUnknown.Descriptor() + } + + return d +} + +// String returns the canonical identifier for this error code. +func (ec ErrorCode) String() string { + return ec.Descriptor().Value +} + +// Message returned the human-readable error message for this error code. +func (ec ErrorCode) Message() string { + return ec.Descriptor().Message +} + +// MarshalText encodes the receiver into UTF-8-encoded text and returns the +// result. +func (ec ErrorCode) MarshalText() (text []byte, err error) { + return []byte(ec.String()), nil +} + +// UnmarshalText decodes the form generated by MarshalText. +func (ec *ErrorCode) UnmarshalText(text []byte) error { + desc, ok := idToDescriptors[string(text)] + + if !ok { + desc = ErrorCodeUnknown.Descriptor() + } + + *ec = desc.Code + + return nil +} + +// Error provides a wrapper around ErrorCode with extra Details provided. +type Error struct { + Code ErrorCode `json:"code"` + Message string `json:"message,omitempty"` + Detail interface{} `json:"detail,omitempty"` +} + +// Error returns a human readable representation of the error. +func (e Error) Error() string { + return fmt.Sprintf("%s: %s", + strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)), + e.Message) +} + +// Errors provides the envelope for multiple errors and a few sugar methods +// for use within the application. +type Errors struct { + Errors []Error `json:"errors,omitempty"` +} + +// Push pushes an error on to the error stack, with the optional detail +// argument. It is a programming error (ie panic) to push more than one +// detail at a time. +func (errs *Errors) Push(code ErrorCode, details ...interface{}) { + if len(details) > 1 { + panic("please specify zero or one detail items for this error") + } + + var detail interface{} + if len(details) > 0 { + detail = details[0] + } + + if err, ok := detail.(error); ok { + detail = err.Error() + } + + errs.PushErr(Error{ + Code: code, + Message: code.Message(), + Detail: detail, + }) +} + +// PushErr pushes an error interface onto the error stack. +func (errs *Errors) PushErr(err error) { + switch err.(type) { + case Error: + errs.Errors = append(errs.Errors, err.(Error)) + default: + errs.Errors = append(errs.Errors, Error{Message: err.Error()}) + } +} + +func (errs *Errors) Error() string { + switch errs.Len() { + case 0: + return "" + case 1: + return errs.Errors[0].Error() + default: + msg := "errors:\n" + for _, err := range errs.Errors { + msg += err.Error() + "\n" + } + return msg + } +} + +// Clear clears the errors. +func (errs *Errors) Clear() { + errs.Errors = errs.Errors[:0] +} + +// Len returns the current number of errors. +func (errs *Errors) Len() int { + return len(errs.Errors) +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/errors_test.go b/vendor/src/github.com/docker/distribution/registry/api/v2/errors_test.go new file mode 100644 index 000000000..9cc831c44 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/errors_test.go @@ -0,0 +1,165 @@ +package v2 + +import ( + "encoding/json" + "reflect" + "testing" + + "github.com/docker/distribution/digest" +) + +// TestErrorCodes ensures that error code format, mappings and +// marshaling/unmarshaling. round trips are stable. +func TestErrorCodes(t *testing.T) { + for _, desc := range errorDescriptors { + if desc.Code.String() != desc.Value { + t.Fatalf("error code string incorrect: %q != %q", desc.Code.String(), desc.Value) + } + + if desc.Code.Message() != desc.Message { + t.Fatalf("incorrect message for error code %v: %q != %q", desc.Code, desc.Code.Message(), desc.Message) + } + + // Serialize the error code using the json library to ensure that we + // get a string and it works round trip. + p, err := json.Marshal(desc.Code) + + if err != nil { + t.Fatalf("error marshaling error code %v: %v", desc.Code, err) + } + + if len(p) <= 0 { + t.Fatalf("expected content in marshaled before for error code %v", desc.Code) + } + + // First, unmarshal to interface and ensure we have a string. + var ecUnspecified interface{} + if err := json.Unmarshal(p, &ecUnspecified); err != nil { + t.Fatalf("error unmarshaling error code %v: %v", desc.Code, err) + } + + if _, ok := ecUnspecified.(string); !ok { + t.Fatalf("expected a string for error code %v on unmarshal got a %T", desc.Code, ecUnspecified) + } + + // Now, unmarshal with the error code type and ensure they are equal + var ecUnmarshaled ErrorCode + if err := json.Unmarshal(p, &ecUnmarshaled); err != nil { + t.Fatalf("error unmarshaling error code %v: %v", desc.Code, err) + } + + if ecUnmarshaled != desc.Code { + t.Fatalf("unexpected error code during error code marshal/unmarshal: %v != %v", ecUnmarshaled, desc.Code) + } + } +} + +// TestErrorsManagement does a quick check of the Errors type to ensure that +// members are properly pushed and marshaled. +func TestErrorsManagement(t *testing.T) { + var errs Errors + + errs.Push(ErrorCodeDigestInvalid) + errs.Push(ErrorCodeBlobUnknown, + map[string]digest.Digest{"digest": "sometestblobsumdoesntmatter"}) + + p, err := json.Marshal(errs) + + if err != nil { + t.Fatalf("error marashaling errors: %v", err) + } + + expectedJSON := "{\"errors\":[{\"code\":\"DIGEST_INVALID\",\"message\":\"provided digest did not match uploaded content\"},{\"code\":\"BLOB_UNKNOWN\",\"message\":\"blob unknown to registry\",\"detail\":{\"digest\":\"sometestblobsumdoesntmatter\"}}]}" + + if string(p) != expectedJSON { + t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON) + } + + errs.Clear() + errs.Push(ErrorCodeUnknown) + expectedJSON = "{\"errors\":[{\"code\":\"UNKNOWN\",\"message\":\"unknown error\"}]}" + p, err = json.Marshal(errs) + + if err != nil { + t.Fatalf("error marashaling errors: %v", err) + } + + if string(p) != expectedJSON { + t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON) + } +} + +// TestMarshalUnmarshal ensures that api errors can round trip through json +// without losing information. +func TestMarshalUnmarshal(t *testing.T) { + + var errors Errors + + for _, testcase := range []struct { + description string + err Error + }{ + { + description: "unknown error", + err: Error{ + + Code: ErrorCodeUnknown, + Message: ErrorCodeUnknown.Descriptor().Message, + }, + }, + { + description: "unknown manifest", + err: Error{ + Code: ErrorCodeManifestUnknown, + Message: ErrorCodeManifestUnknown.Descriptor().Message, + }, + }, + { + description: "unknown manifest", + err: Error{ + Code: ErrorCodeBlobUnknown, + Message: ErrorCodeBlobUnknown.Descriptor().Message, + Detail: map[string]interface{}{"digest": "asdfqwerqwerqwerqwer"}, + }, + }, + } { + fatalf := func(format string, args ...interface{}) { + t.Fatalf(testcase.description+": "+format, args...) + } + + unexpectedErr := func(err error) { + fatalf("unexpected error: %v", err) + } + + p, err := json.Marshal(testcase.err) + if err != nil { + unexpectedErr(err) + } + + var unmarshaled Error + if err := json.Unmarshal(p, &unmarshaled); err != nil { + unexpectedErr(err) + } + + if !reflect.DeepEqual(unmarshaled, testcase.err) { + fatalf("errors not equal after round trip: %#v != %#v", unmarshaled, testcase.err) + } + + // Roll everything up into an error response envelope. + errors.PushErr(testcase.err) + } + + p, err := json.Marshal(errors) + if err != nil { + t.Fatalf("unexpected error marshaling error envelope: %v", err) + } + + var unmarshaled Errors + if err := json.Unmarshal(p, &unmarshaled); err != nil { + t.Fatalf("unexpected error unmarshaling error envelope: %v", err) + } + + if !reflect.DeepEqual(unmarshaled, errors) { + t.Fatalf("errors not equal after round trip: %#v != %#v", unmarshaled, errors) + } +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/names.go b/vendor/src/github.com/docker/distribution/registry/api/v2/names.go new file mode 100644 index 000000000..e4a98861c --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/names.go @@ -0,0 +1,100 @@ +package v2 + +import ( + "fmt" + "regexp" + "strings" +) + +// TODO(stevvooe): Move these definitions back to an exported package. While +// they are used with v2 definitions, their relevance expands beyond. +// "distribution/names" is a candidate package. + +const ( + // RepositoryNameComponentMinLength is the minimum number of characters in a + // single repository name slash-delimited component + RepositoryNameComponentMinLength = 2 + + // RepositoryNameMinComponents is the minimum number of slash-delimited + // components that a repository name must have + RepositoryNameMinComponents = 1 + + // RepositoryNameTotalLengthMax is the maximum total number of characters in + // a repository name + RepositoryNameTotalLengthMax = 255 +) + +// RepositoryNameComponentRegexp restricts registry path component names to +// start with at least one letter or number, with following parts able to +// be separated by one period, dash or underscore. +var RepositoryNameComponentRegexp = regexp.MustCompile(`[a-z0-9]+(?:[._-][a-z0-9]+)*`) + +// RepositoryNameComponentAnchoredRegexp is the version of +// RepositoryNameComponentRegexp which must completely match the content +var RepositoryNameComponentAnchoredRegexp = regexp.MustCompile(`^` + RepositoryNameComponentRegexp.String() + `$`) + +// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow +// multiple path components, separated by a forward slash. +var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/)*` + RepositoryNameComponentRegexp.String()) + +// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go. +var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`) + +// TODO(stevvooe): Contribute these exports back to core, so they are shared. + +var ( + // ErrRepositoryNameComponentShort is returned when a repository name + // contains a component which is shorter than + // RepositoryNameComponentMinLength + ErrRepositoryNameComponentShort = fmt.Errorf("respository name component must be %v or more characters", RepositoryNameComponentMinLength) + + // ErrRepositoryNameMissingComponents is returned when a repository name + // contains fewer than RepositoryNameMinComponents components + ErrRepositoryNameMissingComponents = fmt.Errorf("repository name must have at least %v components", RepositoryNameMinComponents) + + // ErrRepositoryNameLong is returned when a repository name is longer than + // RepositoryNameTotalLengthMax + ErrRepositoryNameLong = fmt.Errorf("repository name must not be more than %v characters", RepositoryNameTotalLengthMax) + + // ErrRepositoryNameComponentInvalid is returned when a repository name does + // not match RepositoryNameComponentRegexp + ErrRepositoryNameComponentInvalid = fmt.Errorf("repository name component must match %q", RepositoryNameComponentRegexp.String()) +) + +// ValidateRespositoryName ensures the repository name is valid for use in the +// registry. This function accepts a superset of what might be accepted by +// docker core or docker hub. If the name does not pass validation, an error, +// describing the conditions, is returned. +// +// Effectively, the name should comply with the following grammar: +// +// alpha-numeric := /[a-z0-9]+/ +// separator := /[._-]/ +// component := alpha-numeric [separator alpha-numeric]* +// namespace := component ['/' component]* +// +// The result of the production, known as the "namespace", should be limited +// to 255 characters. +func ValidateRespositoryName(name string) error { + if len(name) > RepositoryNameTotalLengthMax { + return ErrRepositoryNameLong + } + + components := strings.Split(name, "/") + + if len(components) < RepositoryNameMinComponents { + return ErrRepositoryNameMissingComponents + } + + for _, component := range components { + if len(component) < RepositoryNameComponentMinLength { + return ErrRepositoryNameComponentShort + } + + if !RepositoryNameComponentAnchoredRegexp.MatchString(component) { + return ErrRepositoryNameComponentInvalid + } + } + + return nil +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/names_test.go b/vendor/src/github.com/docker/distribution/registry/api/v2/names_test.go new file mode 100644 index 000000000..de6a168f0 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/names_test.go @@ -0,0 +1,100 @@ +package v2 + +import ( + "strings" + "testing" +) + +func TestRepositoryNameRegexp(t *testing.T) { + for _, testcase := range []struct { + input string + err error + }{ + { + input: "short", + }, + { + input: "simple/name", + }, + { + input: "library/ubuntu", + }, + { + input: "docker/stevvooe/app", + }, + { + input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb", + }, + { + input: "aa/aa/bb/bb/bb", + }, + { + input: "a/a/a/b/b", + err: ErrRepositoryNameComponentShort, + }, + { + input: "a/a/a/a/", + err: ErrRepositoryNameComponentShort, + }, + { + input: "foo.com/bar/baz", + }, + { + input: "blog.foo.com/bar/baz", + }, + { + input: "asdf", + }, + { + input: "asdf$$^/aa", + err: ErrRepositoryNameComponentInvalid, + }, + { + input: "aa-a/aa", + }, + { + input: "aa/aa", + }, + { + input: "a-a/a-a", + }, + { + input: "a", + err: ErrRepositoryNameComponentShort, + }, + { + input: "a-/a/a/a", + err: ErrRepositoryNameComponentInvalid, + }, + { + input: strings.Repeat("a", 255), + }, + { + input: strings.Repeat("a", 256), + err: ErrRepositoryNameLong, + }, + } { + + failf := func(format string, v ...interface{}) { + t.Logf(testcase.input+": "+format, v...) + t.Fail() + } + + if err := ValidateRespositoryName(testcase.input); err != testcase.err { + if testcase.err != nil { + if err != nil { + failf("unexpected error for invalid repository: got %v, expected %v", err, testcase.err) + } else { + failf("expected invalid repository: %v", testcase.err) + } + } else { + if err != nil { + // Wrong error returned. + failf("unexpected error validating repository name: %v, expected %v", err, testcase.err) + } else { + failf("unexpected error validating repository name: %v", err) + } + } + } + } +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/routes.go b/vendor/src/github.com/docker/distribution/registry/api/v2/routes.go new file mode 100644 index 000000000..69f9d9012 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/routes.go @@ -0,0 +1,47 @@ +package v2 + +import "github.com/gorilla/mux" + +// The following are definitions of the name under which all V2 routes are +// registered. These symbols can be used to look up a route based on the name. +const ( + RouteNameBase = "base" + RouteNameManifest = "manifest" + RouteNameTags = "tags" + RouteNameBlob = "blob" + RouteNameBlobUpload = "blob-upload" + RouteNameBlobUploadChunk = "blob-upload-chunk" +) + +var allEndpoints = []string{ + RouteNameManifest, + RouteNameTags, + RouteNameBlob, + RouteNameBlobUpload, + RouteNameBlobUploadChunk, +} + +// Router builds a gorilla router with named routes for the various API +// methods. This can be used directly by both server implementations and +// clients. +func Router() *mux.Router { + return RouterWithPrefix("") +} + +// RouterWithPrefix builds a gorilla router with a configured prefix +// on all routes. +func RouterWithPrefix(prefix string) *mux.Router { + rootRouter := mux.NewRouter() + router := rootRouter + if prefix != "" { + router = router.PathPrefix(prefix).Subrouter() + } + + router.StrictSlash(true) + + for _, descriptor := range routeDescriptors { + router.Path(descriptor.Path).Name(descriptor.Name) + } + + return rootRouter +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/routes_test.go b/vendor/src/github.com/docker/distribution/registry/api/v2/routes_test.go new file mode 100644 index 000000000..afab71fce --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/routes_test.go @@ -0,0 +1,315 @@ +package v2 + +import ( + "encoding/json" + "fmt" + "math/rand" + "net/http" + "net/http/httptest" + "reflect" + "strings" + "testing" + "time" + + "github.com/gorilla/mux" +) + +type routeTestCase struct { + RequestURI string + ExpectedURI string + Vars map[string]string + RouteName string + StatusCode int +} + +// TestRouter registers a test handler with all the routes and ensures that +// each route returns the expected path variables. Not method verification is +// present. This not meant to be exhaustive but as check to ensure that the +// expected variables are extracted. +// +// This may go away as the application structure comes together. +func TestRouter(t *testing.T) { + testCases := []routeTestCase{ + { + RouteName: RouteNameBase, + RequestURI: "/v2/", + Vars: map[string]string{}, + }, + { + RouteName: RouteNameManifest, + RequestURI: "/v2/foo/manifests/bar", + Vars: map[string]string{ + "name": "foo", + "reference": "bar", + }, + }, + { + RouteName: RouteNameManifest, + RequestURI: "/v2/foo/bar/manifests/tag", + Vars: map[string]string{ + "name": "foo/bar", + "reference": "tag", + }, + }, + { + RouteName: RouteNameManifest, + RequestURI: "/v2/foo/bar/manifests/sha256:abcdef01234567890", + Vars: map[string]string{ + "name": "foo/bar", + "reference": "sha256:abcdef01234567890", + }, + }, + { + RouteName: RouteNameTags, + RequestURI: "/v2/foo/bar/tags/list", + Vars: map[string]string{ + "name": "foo/bar", + }, + }, + { + RouteName: RouteNameBlob, + RequestURI: "/v2/foo/bar/blobs/tarsum.dev+foo:abcdef0919234", + Vars: map[string]string{ + "name": "foo/bar", + "digest": "tarsum.dev+foo:abcdef0919234", + }, + }, + { + RouteName: RouteNameBlob, + RequestURI: "/v2/foo/bar/blobs/sha256:abcdef0919234", + Vars: map[string]string{ + "name": "foo/bar", + "digest": "sha256:abcdef0919234", + }, + }, + { + RouteName: RouteNameBlobUpload, + RequestURI: "/v2/foo/bar/blobs/uploads/", + Vars: map[string]string{ + "name": "foo/bar", + }, + }, + { + RouteName: RouteNameBlobUploadChunk, + RequestURI: "/v2/foo/bar/blobs/uploads/uuid", + Vars: map[string]string{ + "name": "foo/bar", + "uuid": "uuid", + }, + }, + { + RouteName: RouteNameBlobUploadChunk, + RequestURI: "/v2/foo/bar/blobs/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", + Vars: map[string]string{ + "name": "foo/bar", + "uuid": "D95306FA-FAD3-4E36-8D41-CF1C93EF8286", + }, + }, + { + RouteName: RouteNameBlobUploadChunk, + RequestURI: "/v2/foo/bar/blobs/uploads/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==", + Vars: map[string]string{ + "name": "foo/bar", + "uuid": "RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==", + }, + }, + { + // Check ambiguity: ensure we can distinguish between tags for + // "foo/bar/image/image" and image for "foo/bar/image" with tag + // "tags" + RouteName: RouteNameManifest, + RequestURI: "/v2/foo/bar/manifests/manifests/tags", + Vars: map[string]string{ + "name": "foo/bar/manifests", + "reference": "tags", + }, + }, + { + // This case presents an ambiguity between foo/bar with tag="tags" + // and list tags for "foo/bar/manifest" + RouteName: RouteNameTags, + RequestURI: "/v2/foo/bar/manifests/tags/list", + Vars: map[string]string{ + "name": "foo/bar/manifests", + }, + }, + } + + checkTestRouter(t, testCases, "", true) + checkTestRouter(t, testCases, "/prefix/", true) +} + +func TestRouterWithPathTraversals(t *testing.T) { + testCases := []routeTestCase{ + { + RouteName: RouteNameBlobUploadChunk, + RequestURI: "/v2/foo/../../blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", + ExpectedURI: "/blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", + StatusCode: http.StatusNotFound, + }, + { + // Testing for path traversal attack handling + RouteName: RouteNameTags, + RequestURI: "/v2/foo/../bar/baz/tags/list", + ExpectedURI: "/v2/bar/baz/tags/list", + Vars: map[string]string{ + "name": "bar/baz", + }, + }, + } + checkTestRouter(t, testCases, "", false) +} + +func TestRouterWithBadCharacters(t *testing.T) { + if testing.Short() { + testCases := []routeTestCase{ + { + RouteName: RouteNameBlobUploadChunk, + RequestURI: "/v2/foo/blob/uploads/不95306FA-FAD3-4E36-8D41-CF1C93EF8286", + StatusCode: http.StatusNotFound, + }, + { + // Testing for path traversal attack handling + RouteName: RouteNameTags, + RequestURI: "/v2/foo/不bar/tags/list", + StatusCode: http.StatusNotFound, + }, + } + checkTestRouter(t, testCases, "", true) + } else { + // in the long version we're going to fuzz the router + // with random UTF8 characters not in the 128 bit ASCII range. + // These are not valid characters for the router and we expect + // 404s on every test. + rand.Seed(time.Now().UTC().UnixNano()) + testCases := make([]routeTestCase, 1000) + for idx := range testCases { + testCases[idx] = routeTestCase{ + RouteName: RouteNameTags, + RequestURI: fmt.Sprintf("/v2/%v/%v/tags/list", randomString(10), randomString(10)), + StatusCode: http.StatusNotFound, + } + } + checkTestRouter(t, testCases, "", true) + } +} + +func checkTestRouter(t *testing.T, testCases []routeTestCase, prefix string, deeplyEqual bool) { + router := RouterWithPrefix(prefix) + + testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + testCase := routeTestCase{ + RequestURI: r.RequestURI, + Vars: mux.Vars(r), + RouteName: mux.CurrentRoute(r).GetName(), + } + + enc := json.NewEncoder(w) + + if err := enc.Encode(testCase); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + }) + + // Startup test server + server := httptest.NewServer(router) + + for _, testcase := range testCases { + testcase.RequestURI = strings.TrimSuffix(prefix, "/") + testcase.RequestURI + // Register the endpoint + route := router.GetRoute(testcase.RouteName) + if route == nil { + t.Fatalf("route for name %q not found", testcase.RouteName) + } + + route.Handler(testHandler) + + u := server.URL + testcase.RequestURI + + resp, err := http.Get(u) + + if err != nil { + t.Fatalf("error issuing get request: %v", err) + } + + if testcase.StatusCode == 0 { + // Override default, zero-value + testcase.StatusCode = http.StatusOK + } + if testcase.ExpectedURI == "" { + // Override default, zero-value + testcase.ExpectedURI = testcase.RequestURI + } + + if resp.StatusCode != testcase.StatusCode { + t.Fatalf("unexpected status for %s: %v %v", u, resp.Status, resp.StatusCode) + } + + if testcase.StatusCode != http.StatusOK { + // We don't care about json response. + continue + } + + dec := json.NewDecoder(resp.Body) + + var actualRouteInfo routeTestCase + if err := dec.Decode(&actualRouteInfo); err != nil { + t.Fatalf("error reading json response: %v", err) + } + // Needs to be set out of band + actualRouteInfo.StatusCode = resp.StatusCode + + if actualRouteInfo.RequestURI != testcase.ExpectedURI { + t.Fatalf("URI %v incorrectly parsed, expected %v", actualRouteInfo.RequestURI, testcase.ExpectedURI) + } + + if actualRouteInfo.RouteName != testcase.RouteName { + t.Fatalf("incorrect route %q matched, expected %q", actualRouteInfo.RouteName, testcase.RouteName) + } + + // when testing deep equality, the actualRouteInfo has an empty ExpectedURI, we don't want + // that to make the comparison fail. We're otherwise done with the testcase so empty the + // testcase.ExpectedURI + testcase.ExpectedURI = "" + if deeplyEqual && !reflect.DeepEqual(actualRouteInfo, testcase) { + t.Fatalf("actual does not equal expected: %#v != %#v", actualRouteInfo, testcase) + } + } + +} + +// -------------- START LICENSED CODE -------------- +// The following code is derivative of https://github.com/google/gofuzz +// gofuzz is licensed under the Apache License, Version 2.0, January 2004, +// a copy of which can be found in the LICENSE file at the root of this +// repository. + +// These functions allow us to generate strings containing only multibyte +// characters that are invalid in our URLs. They are used above for fuzzing +// to ensure we always get 404s on these invalid strings +type charRange struct { + first, last rune +} + +// choose returns a random unicode character from the given range, using the +// given randomness source. +func (r *charRange) choose() rune { + count := int64(r.last - r.first) + return r.first + rune(rand.Int63n(count)) +} + +var unicodeRanges = []charRange{ + {'\u00a0', '\u02af'}, // Multi-byte encoded characters + {'\u4e00', '\u9fff'}, // Common CJK (even longer encodings) +} + +func randomString(length int) string { + runes := make([]rune, length) + for i := range runes { + runes[i] = unicodeRanges[rand.Intn(len(unicodeRanges))].choose() + } + return string(runes) +} + +// -------------- END LICENSED CODE -------------- diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/urls.go b/vendor/src/github.com/docker/distribution/registry/api/v2/urls.go new file mode 100644 index 000000000..4b42dd162 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/urls.go @@ -0,0 +1,217 @@ +package v2 + +import ( + "net/http" + "net/url" + "strings" + + "github.com/docker/distribution/digest" + "github.com/gorilla/mux" +) + +// URLBuilder creates registry API urls from a single base endpoint. It can be +// used to create urls for use in a registry client or server. +// +// All urls will be created from the given base, including the api version. +// For example, if a root of "/foo/" is provided, urls generated will be fall +// under "/foo/v2/...". Most application will only provide a schema, host and +// port, such as "https://localhost:5000/". +type URLBuilder struct { + root *url.URL // url root (ie http://localhost/) + router *mux.Router +} + +// NewURLBuilder creates a URLBuilder with provided root url object. +func NewURLBuilder(root *url.URL) *URLBuilder { + return &URLBuilder{ + root: root, + router: Router(), + } +} + +// NewURLBuilderFromString workes identically to NewURLBuilder except it takes +// a string argument for the root, returning an error if it is not a valid +// url. +func NewURLBuilderFromString(root string) (*URLBuilder, error) { + u, err := url.Parse(root) + if err != nil { + return nil, err + } + + return NewURLBuilder(u), nil +} + +// NewURLBuilderFromRequest uses information from an *http.Request to +// construct the root url. +func NewURLBuilderFromRequest(r *http.Request) *URLBuilder { + var scheme string + + forwardedProto := r.Header.Get("X-Forwarded-Proto") + + switch { + case len(forwardedProto) > 0: + scheme = forwardedProto + case r.TLS != nil: + scheme = "https" + case len(r.URL.Scheme) > 0: + scheme = r.URL.Scheme + default: + scheme = "http" + } + + host := r.Host + forwardedHost := r.Header.Get("X-Forwarded-Host") + if len(forwardedHost) > 0 { + host = forwardedHost + } + + basePath := routeDescriptorsMap[RouteNameBase].Path + + requestPath := r.URL.Path + index := strings.Index(requestPath, basePath) + + u := &url.URL{ + Scheme: scheme, + Host: host, + } + + if index > 0 { + // N.B. index+1 is important because we want to include the trailing / + u.Path = requestPath[0 : index+1] + } + + return NewURLBuilder(u) +} + +// BuildBaseURL constructs a base url for the API, typically just "/v2/". +func (ub *URLBuilder) BuildBaseURL() (string, error) { + route := ub.cloneRoute(RouteNameBase) + + baseURL, err := route.URL() + if err != nil { + return "", err + } + + return baseURL.String(), nil +} + +// BuildTagsURL constructs a url to list the tags in the named repository. +func (ub *URLBuilder) BuildTagsURL(name string) (string, error) { + route := ub.cloneRoute(RouteNameTags) + + tagsURL, err := route.URL("name", name) + if err != nil { + return "", err + } + + return tagsURL.String(), nil +} + +// BuildManifestURL constructs a url for the manifest identified by name and +// reference. The argument reference may be either a tag or digest. +func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) { + route := ub.cloneRoute(RouteNameManifest) + + manifestURL, err := route.URL("name", name, "reference", reference) + if err != nil { + return "", err + } + + return manifestURL.String(), nil +} + +// BuildBlobURL constructs the url for the blob identified by name and dgst. +func (ub *URLBuilder) BuildBlobURL(name string, dgst digest.Digest) (string, error) { + route := ub.cloneRoute(RouteNameBlob) + + layerURL, err := route.URL("name", name, "digest", dgst.String()) + if err != nil { + return "", err + } + + return layerURL.String(), nil +} + +// BuildBlobUploadURL constructs a url to begin a blob upload in the +// repository identified by name. +func (ub *URLBuilder) BuildBlobUploadURL(name string, values ...url.Values) (string, error) { + route := ub.cloneRoute(RouteNameBlobUpload) + + uploadURL, err := route.URL("name", name) + if err != nil { + return "", err + } + + return appendValuesURL(uploadURL, values...).String(), nil +} + +// BuildBlobUploadChunkURL constructs a url for the upload identified by uuid, +// including any url values. This should generally not be used by clients, as +// this url is provided by server implementations during the blob upload +// process. +func (ub *URLBuilder) BuildBlobUploadChunkURL(name, uuid string, values ...url.Values) (string, error) { + route := ub.cloneRoute(RouteNameBlobUploadChunk) + + uploadURL, err := route.URL("name", name, "uuid", uuid) + if err != nil { + return "", err + } + + return appendValuesURL(uploadURL, values...).String(), nil +} + +// clondedRoute returns a clone of the named route from the router. Routes +// must be cloned to avoid modifying them during url generation. +func (ub *URLBuilder) cloneRoute(name string) clonedRoute { + route := new(mux.Route) + root := new(url.URL) + + *route = *ub.router.GetRoute(name) // clone the route + *root = *ub.root + + return clonedRoute{Route: route, root: root} +} + +type clonedRoute struct { + *mux.Route + root *url.URL +} + +func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) { + routeURL, err := cr.Route.URL(pairs...) + if err != nil { + return nil, err + } + + if routeURL.Scheme == "" && routeURL.User == nil && routeURL.Host == "" { + routeURL.Path = routeURL.Path[1:] + } + + return cr.root.ResolveReference(routeURL), nil +} + +// appendValuesURL appends the parameters to the url. +func appendValuesURL(u *url.URL, values ...url.Values) *url.URL { + merged := u.Query() + + for _, v := range values { + for k, vv := range v { + merged[k] = append(merged[k], vv...) + } + } + + u.RawQuery = merged.Encode() + return u +} + +// appendValues appends the parameters to the url. Panics if the string is not +// a url. +func appendValues(u string, values ...url.Values) string { + up, err := url.Parse(u) + + if err != nil { + panic(err) // should never happen + } + + return appendValuesURL(up, values...).String() +} diff --git a/vendor/src/github.com/docker/distribution/registry/api/v2/urls_test.go b/vendor/src/github.com/docker/distribution/registry/api/v2/urls_test.go new file mode 100644 index 000000000..237d0f615 --- /dev/null +++ b/vendor/src/github.com/docker/distribution/registry/api/v2/urls_test.go @@ -0,0 +1,225 @@ +package v2 + +import ( + "net/http" + "net/url" + "testing" +) + +type urlBuilderTestCase struct { + description string + expectedPath string + build func() (string, error) +} + +func makeURLBuilderTestCases(urlBuilder *URLBuilder) []urlBuilderTestCase { + return []urlBuilderTestCase{ + { + description: "test base url", + expectedPath: "/v2/", + build: urlBuilder.BuildBaseURL, + }, + { + description: "test tags url", + expectedPath: "/v2/foo/bar/tags/list", + build: func() (string, error) { + return urlBuilder.BuildTagsURL("foo/bar") + }, + }, + { + description: "test manifest url", + expectedPath: "/v2/foo/bar/manifests/tag", + build: func() (string, error) { + return urlBuilder.BuildManifestURL("foo/bar", "tag") + }, + }, + { + description: "build blob url", + expectedPath: "/v2/foo/bar/blobs/tarsum.v1+sha256:abcdef0123456789", + build: func() (string, error) { + return urlBuilder.BuildBlobURL("foo/bar", "tarsum.v1+sha256:abcdef0123456789") + }, + }, + { + description: "build blob upload url", + expectedPath: "/v2/foo/bar/blobs/uploads/", + build: func() (string, error) { + return urlBuilder.BuildBlobUploadURL("foo/bar") + }, + }, + { + description: "build blob upload url with digest and size", + expectedPath: "/v2/foo/bar/blobs/uploads/?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000", + build: func() (string, error) { + return urlBuilder.BuildBlobUploadURL("foo/bar", url.Values{ + "size": []string{"10000"}, + "digest": []string{"tarsum.v1+sha256:abcdef0123456789"}, + }) + }, + }, + { + description: "build blob upload chunk url", + expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part", + build: func() (string, error) { + return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part") + }, + }, + { + description: "build blob upload chunk url with digest and size", + expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000", + build: func() (string, error) { + return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part", url.Values{ + "size": []string{"10000"}, + "digest": []string{"tarsum.v1+sha256:abcdef0123456789"}, + }) + }, + }, + } +} + +// TestURLBuilder tests the various url building functions, ensuring they are +// returning the expected values. +func TestURLBuilder(t *testing.T) { + roots := []string{ + "http://example.com", + "https://example.com", + "http://localhost:5000", + "https://localhost:5443", + } + + for _, root := range roots { + urlBuilder, err := NewURLBuilderFromString(root) + if err != nil { + t.Fatalf("unexpected error creating urlbuilder: %v", err) + } + + for _, testCase := range makeURLBuilderTestCases(urlBuilder) { + url, err := testCase.build() + if err != nil { + t.Fatalf("%s: error building url: %v", testCase.description, err) + } + + expectedURL := root + testCase.expectedPath + + if url != expectedURL { + t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) + } + } + } +} + +func TestURLBuilderWithPrefix(t *testing.T) { + roots := []string{ + "http://example.com/prefix/", + "https://example.com/prefix/", + "http://localhost:5000/prefix/", + "https://localhost:5443/prefix/", + } + + for _, root := range roots { + urlBuilder, err := NewURLBuilderFromString(root) + if err != nil { + t.Fatalf("unexpected error creating urlbuilder: %v", err) + } + + for _, testCase := range makeURLBuilderTestCases(urlBuilder) { + url, err := testCase.build() + if err != nil { + t.Fatalf("%s: error building url: %v", testCase.description, err) + } + + expectedURL := root[0:len(root)-1] + testCase.expectedPath + + if url != expectedURL { + t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) + } + } + } +} + +type builderFromRequestTestCase struct { + request *http.Request + base string +} + +func TestBuilderFromRequest(t *testing.T) { + u, err := url.Parse("http://example.com") + if err != nil { + t.Fatal(err) + } + + forwardedProtoHeader := make(http.Header, 1) + forwardedProtoHeader.Set("X-Forwarded-Proto", "https") + + testRequests := []struct { + request *http.Request + base string + }{ + { + request: &http.Request{URL: u, Host: u.Host}, + base: "http://example.com", + }, + { + request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader}, + base: "https://example.com", + }, + } + + for _, tr := range testRequests { + builder := NewURLBuilderFromRequest(tr.request) + + for _, testCase := range makeURLBuilderTestCases(builder) { + url, err := testCase.build() + if err != nil { + t.Fatalf("%s: error building url: %v", testCase.description, err) + } + + expectedURL := tr.base + testCase.expectedPath + + if url != expectedURL { + t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) + } + } + } +} + +func TestBuilderFromRequestWithPrefix(t *testing.T) { + u, err := url.Parse("http://example.com/prefix/v2/") + if err != nil { + t.Fatal(err) + } + + forwardedProtoHeader := make(http.Header, 1) + forwardedProtoHeader.Set("X-Forwarded-Proto", "https") + + testRequests := []struct { + request *http.Request + base string + }{ + { + request: &http.Request{URL: u, Host: u.Host}, + base: "http://example.com/prefix/", + }, + { + request: &http.Request{URL: u, Host: u.Host, Header: forwardedProtoHeader}, + base: "https://example.com/prefix/", + }, + } + + for _, tr := range testRequests { + builder := NewURLBuilderFromRequest(tr.request) + + for _, testCase := range makeURLBuilderTestCases(builder) { + url, err := testCase.build() + if err != nil { + t.Fatalf("%s: error building url: %v", testCase.description, err) + } + + expectedURL := tr.base[0:len(tr.base)-1] + testCase.expectedPath + + if url != expectedURL { + t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) + } + } + } +} diff --git a/vendor/src/github.com/gorilla/mux/mux.go b/vendor/src/github.com/gorilla/mux/mux.go index 8b23c39d3..5b5f8e7db 100644 --- a/vendor/src/github.com/gorilla/mux/mux.go +++ b/vendor/src/github.com/gorilla/mux/mux.go @@ -87,10 +87,10 @@ func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { setCurrentRoute(req, match.Route) } if handler == nil { - if r.NotFoundHandler == nil { - r.NotFoundHandler = http.NotFoundHandler() - } handler = r.NotFoundHandler + if handler == nil { + handler = http.NotFoundHandler() + } } if !r.KeepContext { defer context.Clear(req) diff --git a/vendor/src/github.com/gorilla/mux/mux_test.go b/vendor/src/github.com/gorilla/mux/mux_test.go index 0e2e48067..e455bce8f 100644 --- a/vendor/src/github.com/gorilla/mux/mux_test.go +++ b/vendor/src/github.com/gorilla/mux/mux_test.go @@ -462,6 +462,15 @@ func TestQueries(t *testing.T) { path: "", shouldMatch: true, }, + { + title: "Queries route, match with a query string out of order", + route: new(Route).Host("www.example.com").Path("/api").Queries("foo", "bar", "baz", "ding"), + request: newRequest("GET", "http://www.example.com/api?baz=ding&foo=bar"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: true, + }, { title: "Queries route, bad query", route: new(Route).Queries("foo", "bar", "baz", "ding"), @@ -471,6 +480,42 @@ func TestQueries(t *testing.T) { path: "", shouldMatch: false, }, + { + title: "Queries route with pattern, match", + route: new(Route).Queries("foo", "{v1}"), + request: newRequest("GET", "http://localhost?foo=bar"), + vars: map[string]string{"v1": "bar"}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route with multiple patterns, match", + route: new(Route).Queries("foo", "{v1}", "baz", "{v2}"), + request: newRequest("GET", "http://localhost?foo=bar&baz=ding"), + vars: map[string]string{"v1": "bar", "v2": "ding"}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern, match", + route: new(Route).Queries("foo", "{v1:[0-9]+}"), + request: newRequest("GET", "http://localhost?foo=10"), + vars: map[string]string{"v1": "10"}, + host: "", + path: "", + shouldMatch: true, + }, + { + title: "Queries route with regexp pattern, regexp does not match", + route: new(Route).Queries("foo", "{v1:[0-9]+}"), + request: newRequest("GET", "http://localhost?foo=a"), + vars: map[string]string{}, + host: "", + path: "", + shouldMatch: false, + }, } for _, test := range tests { diff --git a/vendor/src/github.com/gorilla/mux/old_test.go b/vendor/src/github.com/gorilla/mux/old_test.go index 42530590e..1f7c190c0 100644 --- a/vendor/src/github.com/gorilla/mux/old_test.go +++ b/vendor/src/github.com/gorilla/mux/old_test.go @@ -329,35 +329,6 @@ var pathMatcherTests = []pathMatcherTest{ }, } -type queryMatcherTest struct { - matcher queryMatcher - url string - result bool -} - -var queryMatcherTests = []queryMatcherTest{ - { - matcher: queryMatcher(map[string]string{"foo": "bar", "baz": "ding"}), - url: "http://localhost:8080/?foo=bar&baz=ding", - result: true, - }, - { - matcher: queryMatcher(map[string]string{"foo": "", "baz": ""}), - url: "http://localhost:8080/?foo=anything&baz=anything", - result: true, - }, - { - matcher: queryMatcher(map[string]string{"foo": "ding", "baz": "bar"}), - url: "http://localhost:8080/?foo=bar&baz=ding", - result: false, - }, - { - matcher: queryMatcher(map[string]string{"bar": "foo", "ding": "baz"}), - url: "http://localhost:8080/?foo=bar&baz=ding", - result: false, - }, -} - type schemeMatcherTest struct { matcher schemeMatcher url string @@ -519,23 +490,8 @@ func TestPathMatcher(t *testing.T) { } } -func TestQueryMatcher(t *testing.T) { - for _, v := range queryMatcherTests { - request, _ := http.NewRequest("GET", v.url, nil) - var routeMatch RouteMatch - result := v.matcher.Match(request, &routeMatch) - if result != v.result { - if v.result { - t.Errorf("%#v: should match %v.", v.matcher, v.url) - } else { - t.Errorf("%#v: should not match %v.", v.matcher, v.url) - } - } - } -} - func TestSchemeMatcher(t *testing.T) { - for _, v := range queryMatcherTests { + for _, v := range schemeMatcherTests { request, _ := http.NewRequest("GET", v.url, nil) var routeMatch RouteMatch result := v.matcher.Match(request, &routeMatch) @@ -735,7 +691,7 @@ func TestNewRegexp(t *testing.T) { } for pattern, paths := range tests { - p, _ = newRouteRegexp(pattern, false, false, false) + p, _ = newRouteRegexp(pattern, false, false, false, false) for path, result := range paths { matches = p.regexp.FindStringSubmatch(path) if result == nil { diff --git a/vendor/src/github.com/gorilla/mux/regexp.go b/vendor/src/github.com/gorilla/mux/regexp.go index 925f268ab..a6305483d 100644 --- a/vendor/src/github.com/gorilla/mux/regexp.go +++ b/vendor/src/github.com/gorilla/mux/regexp.go @@ -14,7 +14,7 @@ import ( ) // newRouteRegexp parses a route template and returns a routeRegexp, -// used to match a host or path. +// used to match a host, a path or a query string. // // It will extract named variables, assemble a regexp to be matched, create // a "reverse" template to build URLs and compile regexps to validate variable @@ -23,7 +23,7 @@ import ( // Previously we accepted only Python-like identifiers for variable // names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that // name and pattern can't be empty, and names can't contain a colon. -func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*routeRegexp, error) { +func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash bool) (*routeRegexp, error) { // Check if it is well-formed. idxs, errBraces := braceIndices(tpl) if errBraces != nil { @@ -33,11 +33,15 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout template := tpl // Now let's parse it. defaultPattern := "[^/]+" - if matchHost { + if matchQuery { + defaultPattern = "[^?&]+" + matchPrefix = true + } else if matchHost { defaultPattern = "[^.]+" - matchPrefix, strictSlash = false, false + matchPrefix = false } - if matchPrefix { + // Only match strict slash if not matching + if matchPrefix || matchHost || matchQuery { strictSlash = false } // Set a flag for strictSlash. @@ -48,7 +52,10 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout } varsN := make([]string, len(idxs)/2) varsR := make([]*regexp.Regexp, len(idxs)/2) - pattern := bytes.NewBufferString("^") + pattern := bytes.NewBufferString("") + if !matchQuery { + pattern.WriteByte('^') + } reverse := bytes.NewBufferString("") var end int var err error @@ -100,6 +107,7 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout return &routeRegexp{ template: template, matchHost: matchHost, + matchQuery: matchQuery, strictSlash: strictSlash, regexp: reg, reverse: reverse.String(), @@ -113,8 +121,10 @@ func newRouteRegexp(tpl string, matchHost, matchPrefix, strictSlash bool) (*rout type routeRegexp struct { // The unmodified template. template string - // True for host match, false for path match. + // True for host match, false for path or query string match. matchHost bool + // True for query string match, false for path and host match. + matchQuery bool // The strictSlash value defined on the route, but disabled if PathPrefix was used. strictSlash bool // Expanded regexp. @@ -130,7 +140,11 @@ type routeRegexp struct { // Match matches the regexp against the URL host or path. func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool { if !r.matchHost { - return r.regexp.MatchString(req.URL.Path) + if r.matchQuery { + return r.regexp.MatchString(req.URL.RawQuery) + } else { + return r.regexp.MatchString(req.URL.Path) + } } return r.regexp.MatchString(getHost(req)) } @@ -196,8 +210,9 @@ func braceIndices(s string) ([]int, error) { // routeRegexpGroup groups the route matchers that carry variables. type routeRegexpGroup struct { - host *routeRegexp - path *routeRegexp + host *routeRegexp + path *routeRegexp + queries []*routeRegexp } // setMatch extracts the variables from the URL once a route matches. @@ -234,17 +249,28 @@ func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) } } } + // Store query string variables. + rawQuery := req.URL.RawQuery + for _, q := range v.queries { + queryVars := q.regexp.FindStringSubmatch(rawQuery) + if queryVars != nil { + for k, v := range q.varsN { + m.Vars[v] = queryVars[k+1] + } + } + } } // getHost tries its best to return the request host. func getHost(r *http.Request) string { - if !r.URL.IsAbs() { - host := r.Host - // Slice off any port information. - if i := strings.Index(host, ":"); i != -1 { - host = host[:i] - } - return host + if r.URL.IsAbs() { + return r.URL.Host } - return r.URL.Host + host := r.Host + // Slice off any port information. + if i := strings.Index(host, ":"); i != -1 { + host = host[:i] + } + return host + } diff --git a/vendor/src/github.com/gorilla/mux/route.go b/vendor/src/github.com/gorilla/mux/route.go index 5cb2526d6..c310e66bc 100644 --- a/vendor/src/github.com/gorilla/mux/route.go +++ b/vendor/src/github.com/gorilla/mux/route.go @@ -135,12 +135,12 @@ func (r *Route) addMatcher(m matcher) *Route { } // addRegexpMatcher adds a host or path matcher and builder to a route. -func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error { +func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error { if r.err != nil { return r.err } r.regexp = r.getRegexpGroup() - if !matchHost { + if !matchHost && !matchQuery { if len(tpl) == 0 || tpl[0] != '/' { return fmt.Errorf("mux: path must start with a slash, got %q", tpl) } @@ -148,10 +148,15 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl } } - rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, r.strictSlash) + rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash) if err != nil { return err } + for _, q := range r.regexp.queries { + if err = uniqueVars(rr.varsN, q.varsN); err != nil { + return err + } + } if matchHost { if r.regexp.path != nil { if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil { @@ -165,7 +170,11 @@ func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix bool) error return err } } - r.regexp.path = rr + if matchQuery { + r.regexp.queries = append(r.regexp.queries, rr) + } else { + r.regexp.path = rr + } } r.addMatcher(rr) return nil @@ -219,7 +228,7 @@ func (r *Route) Headers(pairs ...string) *Route { // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). func (r *Route) Host(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, true, false) + r.err = r.addRegexpMatcher(tpl, true, false, false) return r } @@ -278,7 +287,7 @@ func (r *Route) Methods(methods ...string) *Route { // Variable names must be unique in a given route. They can be retrieved // calling mux.Vars(request). func (r *Route) Path(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, false, false) + r.err = r.addRegexpMatcher(tpl, false, false, false) return r } @@ -294,35 +303,42 @@ func (r *Route) Path(tpl string) *Route { // Also note that the setting of Router.StrictSlash() has no effect on routes // with a PathPrefix matcher. func (r *Route) PathPrefix(tpl string) *Route { - r.err = r.addRegexpMatcher(tpl, false, true) + r.err = r.addRegexpMatcher(tpl, false, true, false) return r } // Query ---------------------------------------------------------------------- -// queryMatcher matches the request against URL queries. -type queryMatcher map[string]string - -func (m queryMatcher) Match(r *http.Request, match *RouteMatch) bool { - return matchMap(m, r.URL.Query(), false) -} - // Queries adds a matcher for URL query values. -// It accepts a sequence of key/value pairs. For example: +// It accepts a sequence of key/value pairs. Values may define variables. +// For example: // // r := mux.NewRouter() -// r.Queries("foo", "bar", "baz", "ding") +// r.Queries("foo", "bar", "id", "{id:[0-9]+}") // // The above route will only match if the URL contains the defined queries -// values, e.g.: ?foo=bar&baz=ding. +// values, e.g.: ?foo=bar&id=42. // // It the value is an empty string, it will match any value if the key is set. +// +// Variables can define an optional regexp pattern to me matched: +// +// - {name} matches anything until the next slash. +// +// - {name:pattern} matches the given regexp pattern. func (r *Route) Queries(pairs ...string) *Route { - if r.err == nil { - var queries map[string]string - queries, r.err = mapFromPairs(pairs...) - return r.addMatcher(queryMatcher(queries)) + length := len(pairs) + if length%2 != 0 { + r.err = fmt.Errorf( + "mux: number of parameters must be multiple of 2, got %v", pairs) + return nil } + for i := 0; i < length; i += 2 { + if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], false, true, true); r.err != nil { + return r + } + } + return r } @@ -498,8 +514,9 @@ func (r *Route) getRegexpGroup() *routeRegexpGroup { } else { // Copy. r.regexp = &routeRegexpGroup{ - host: regexp.host, - path: regexp.path, + host: regexp.host, + path: regexp.path, + queries: regexp.queries, } } } From f26f405b00a2d9d1c6ebc11bffb810c54ac510ce Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 1 Apr 2015 00:28:50 +0300 Subject: [PATCH 201/999] pkg/broadcastwriter: add test w/ "" stream only Signed-off-by: Cristian Staretu --- pkg/broadcastwriter/broadcastwriter_test.go | 30 +++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/pkg/broadcastwriter/broadcastwriter_test.go b/pkg/broadcastwriter/broadcastwriter_test.go index 62ca12659..71227821b 100644 --- a/pkg/broadcastwriter/broadcastwriter_test.go +++ b/pkg/broadcastwriter/broadcastwriter_test.go @@ -142,3 +142,33 @@ func BenchmarkBroadcastWriter(b *testing.B) { b.StartTimer() } } + +func BenchmarkBroadcastWriterWithoutStdoutStderr(b *testing.B) { + writer := New() + setUpWriter := func() { + for i := 0; i < 100; i++ { + writer.AddWriter(devNullCloser(0), "") + } + } + testLine := "Line that thinks that it is log line from docker" + var buf bytes.Buffer + for i := 0; i < 100; i++ { + buf.Write([]byte(testLine + "\n")) + } + // line without eol + buf.Write([]byte(testLine)) + testText := buf.Bytes() + b.SetBytes(int64(5 * len(testText))) + b.ResetTimer() + for i := 0; i < b.N; i++ { + setUpWriter() + + for j := 0; j < 5; j++ { + if _, err := writer.Write(testText); err != nil { + b.Fatal(err) + } + } + + writer.Clean() + } +} From b535ed3595036931e8e6f4e9da7cd2beb67cb19e Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 1 Apr 2015 00:57:39 +0300 Subject: [PATCH 202/999] pkg/jsonlog: add JSONLogBytes for low allocations Signed-off-by: Cristian Staretu --- pkg/jsonlog/jsonlogbytes.go | 115 ++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 pkg/jsonlog/jsonlogbytes.go diff --git a/pkg/jsonlog/jsonlogbytes.go b/pkg/jsonlog/jsonlogbytes.go new file mode 100644 index 000000000..0d8fd9c82 --- /dev/null +++ b/pkg/jsonlog/jsonlogbytes.go @@ -0,0 +1,115 @@ +package jsonlog + +import ( + "bytes" + "unicode/utf8" +) + +// JSONLogBytes is based on JSONLog. +// It allows marshalling JSONLog from Log as []byte +// and an already marshalled Created timestamp. +type JSONLogBytes struct { + Log []byte `json:"log,omitempty"` + Stream string `json:"stream,omitempty"` + Created string `json:"time"` +} + +// MarshalJSONBuf is based on the same method from JSONLog +// It has been modified to take into account the necessary changes. +func (mj *JSONLogBytes) MarshalJSONBuf(buf *bytes.Buffer) error { + var first = true + + buf.WriteString(`{`) + if len(mj.Log) != 0 { + if first == true { + first = false + } else { + buf.WriteString(`,`) + } + buf.WriteString(`"log":`) + ffjson_WriteJsonBytesAsString(buf, mj.Log) + } + if len(mj.Stream) != 0 { + if first == true { + first = false + } else { + buf.WriteString(`,`) + } + buf.WriteString(`"stream":`) + ffjson_WriteJsonString(buf, mj.Stream) + } + if first == true { + first = false + } else { + buf.WriteString(`,`) + } + buf.WriteString(`"time":`) + buf.WriteString(mj.Created) + buf.WriteString(`}`) + return nil +} + +// This is based on ffjson_WriteJsonString. It has been changed +// to accept a string passed as a slice of bytes. +func ffjson_WriteJsonBytesAsString(buf *bytes.Buffer, s []byte) { + const hex = "0123456789abcdef" + + buf.WriteByte('"') + start := 0 + for i := 0; i < len(s); { + if b := s[i]; b < utf8.RuneSelf { + if 0x20 <= b && b != '\\' && b != '"' && b != '<' && b != '>' && b != '&' { + i++ + continue + } + if start < i { + buf.Write(s[start:i]) + } + switch b { + case '\\', '"': + buf.WriteByte('\\') + buf.WriteByte(b) + case '\n': + buf.WriteByte('\\') + buf.WriteByte('n') + case '\r': + buf.WriteByte('\\') + buf.WriteByte('r') + default: + + buf.WriteString(`\u00`) + buf.WriteByte(hex[b>>4]) + buf.WriteByte(hex[b&0xF]) + } + i++ + start = i + continue + } + c, size := utf8.DecodeRune(s[i:]) + if c == utf8.RuneError && size == 1 { + if start < i { + buf.Write(s[start:i]) + } + buf.WriteString(`\ufffd`) + i += size + start = i + continue + } + + if c == '\u2028' || c == '\u2029' { + if start < i { + buf.Write(s[start:i]) + } + buf.WriteString(`\u202`) + buf.WriteByte(hex[c&0xF]) + i += size + start = i + continue + } + i += size + } + if start < len(s) { + buf.Write(s[start:]) + } + buf.WriteByte('"') +} From 5550a46946a2e6e1be8844d78a78122b333fae26 Mon Sep 17 00:00:00 2001 From: unclejack Date: Mon, 30 Mar 2015 22:09:56 +0300 Subject: [PATCH 203/999] pkg/broadcastwriter: use []byte to lower alloc Signed-off-by: Cristian Staretu --- pkg/broadcastwriter/broadcastwriter.go | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/pkg/broadcastwriter/broadcastwriter.go b/pkg/broadcastwriter/broadcastwriter.go index 2ee68944d..ce63f7613 100644 --- a/pkg/broadcastwriter/broadcastwriter.go +++ b/pkg/broadcastwriter/broadcastwriter.go @@ -8,6 +8,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/jsonlog" + "github.com/docker/docker/pkg/timeutils" ) // BroadcastWriter accumulate multiple io.WriteCloser by stream. @@ -33,6 +34,7 @@ func (w *BroadcastWriter) AddWriter(writer io.WriteCloser, stream string) { // Write writes bytes to all writers. Failed writers will be evicted during // this call. func (w *BroadcastWriter) Write(p []byte) (n int, err error) { + var timestamp string created := time.Now().UTC() w.Lock() if writers, ok := w.streams[""]; ok { @@ -49,16 +51,26 @@ func (w *BroadcastWriter) Write(p []byte) (n int, err error) { } w.buf.Write(p) for { - line, err := w.buf.ReadString('\n') - if err != nil { - w.buf.WriteString(line) + if n := w.buf.Len(); n == 0 { break } + i := bytes.IndexByte(w.buf.Bytes(), '\n') + if i < 0 { + break + } + lineBytes := w.buf.Next(i + 1) + if timestamp == "" { + timestamp, err = timeutils.FastMarshalJSON(created) + if err != nil { + continue + } + } + for stream, writers := range w.streams { if stream == "" { continue } - jsonLog := jsonlog.JSONLog{Log: line, Stream: stream, Created: created} + jsonLog := jsonlog.JSONLogBytes{Log: lineBytes, Stream: stream, Created: timestamp} err = jsonLog.MarshalJSONBuf(w.jsLogBuf) if err != nil { logrus.Errorf("Error making JSON log line: %s", err) From d15d1674c318366e881fa4ea2977192750d3471b Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 30 Mar 2015 14:30:01 -0700 Subject: [PATCH 204/999] Skip heavy operations if there is no jsonlog writers Signed-off-by: Alexander Morozov --- pkg/broadcastwriter/broadcastwriter.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pkg/broadcastwriter/broadcastwriter.go b/pkg/broadcastwriter/broadcastwriter.go index ce63f7613..248cd8f46 100644 --- a/pkg/broadcastwriter/broadcastwriter.go +++ b/pkg/broadcastwriter/broadcastwriter.go @@ -34,8 +34,6 @@ func (w *BroadcastWriter) AddWriter(writer io.WriteCloser, stream string) { // Write writes bytes to all writers. Failed writers will be evicted during // this call. func (w *BroadcastWriter) Write(p []byte) (n int, err error) { - var timestamp string - created := time.Now().UTC() w.Lock() if writers, ok := w.streams[""]; ok { for sw := range writers { @@ -44,11 +42,19 @@ func (w *BroadcastWriter) Write(p []byte) (n int, err error) { delete(writers, sw) } } + // exit if there is no more writers + if len(w.streams) == 1 { + w.buf.Reset() + w.Unlock() + return len(p), nil + } } if w.jsLogBuf == nil { w.jsLogBuf = new(bytes.Buffer) w.jsLogBuf.Grow(1024) } + var timestamp string + created := time.Now().UTC() w.buf.Write(p) for { if n := w.buf.Len(); n == 0 { From 62009ef77efcbe30afea0cd124f3fbff0d5030cd Mon Sep 17 00:00:00 2001 From: Derek McGowan Date: Tue, 31 Mar 2015 15:02:27 -0700 Subject: [PATCH 205/999] Use vendored v2 registry api Update registry package to use the v2 registry api from distribution. Update interfaces to directly take in digests. Signed-off-by: Derek McGowan (github: dmcgowan) --- graph/pull.go | 2 +- graph/push.go | 12 +-- registry/endpoint.go | 2 +- registry/session_v2.go | 24 ++--- registry/v2/descriptors.go | 144 ---------------------------- registry/v2/doc.go | 13 --- registry/v2/errors.go | 185 ----------------------------------- registry/v2/errors_test.go | 163 ------------------------------- registry/v2/regexp.go | 22 ----- registry/v2/routes.go | 66 ------------- registry/v2/routes_test.go | 192 ------------------------------------- registry/v2/urls.go | 179 ---------------------------------- registry/v2/urls_test.go | 113 ---------------------- 13 files changed, 20 insertions(+), 1097 deletions(-) delete mode 100644 registry/v2/descriptors.go delete mode 100644 registry/v2/doc.go delete mode 100644 registry/v2/errors.go delete mode 100644 registry/v2/errors_test.go delete mode 100644 registry/v2/regexp.go delete mode 100644 registry/v2/routes.go delete mode 100644 registry/v2/routes_test.go delete mode 100644 registry/v2/urls.go delete mode 100644 registry/v2/urls_test.go diff --git a/graph/pull.go b/graph/pull.go index 023b7cbc0..1524860cf 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -499,7 +499,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri return err } - r, l, err := r.GetV2ImageBlobReader(endpoint, repoInfo.RemoteName, di.digest.Algorithm(), di.digest.Hex(), auth) + r, l, err := r.GetV2ImageBlobReader(endpoint, repoInfo.RemoteName, di.digest, auth) if err != nil { return err } diff --git a/graph/push.go b/graph/push.go index 767f118c5..1f747c55b 100644 --- a/graph/push.go +++ b/graph/push.go @@ -8,10 +8,10 @@ import ( "io/ioutil" "os" "path" - "strings" "sync" "github.com/Sirupsen/logrus" + "github.com/docker/distribution/digest" "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" @@ -376,13 +376,13 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o var exists bool if len(checksum) > 0 { - sumParts := strings.SplitN(checksum, ":", 2) - if len(sumParts) < 2 { - return fmt.Errorf("Invalid checksum: %s", checksum) + dgst, err := digest.ParseDigest(checksum) + if err != nil { + return fmt.Errorf("Invalid checksum %s: %s", checksum, err) } // Call mount blob - exists, err = r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, sumParts[0], sumParts[1], auth) + exists, err = r.HeadV2ImageBlob(endpoint, repoInfo.RemoteName, dgst, auth) if err != nil { out.Write(sf.FormatProgress(stringid.TruncateID(layer.ID), "Image push failed", nil)) return err @@ -468,7 +468,7 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint * // Send the layer logrus.Debugf("rendered layer for %s of [%d] size", img.ID, size) - if err := r.PutV2ImageBlob(endpoint, imageName, dgst.Algorithm(), dgst.Hex(), + if err := r.PutV2ImageBlob(endpoint, imageName, dgst, progressreader.New(progressreader.Config{ In: tf, Out: out, diff --git a/registry/endpoint.go b/registry/endpoint.go index 69a718e12..84b11a987 100644 --- a/registry/endpoint.go +++ b/registry/endpoint.go @@ -11,8 +11,8 @@ import ( "strings" "github.com/Sirupsen/logrus" + "github.com/docker/distribution/registry/api/v2" "github.com/docker/docker/pkg/requestdecorator" - "github.com/docker/docker/registry/v2" ) // for mocking in unit tests diff --git a/registry/session_v2.go b/registry/session_v2.go index a01c8b9ab..fb1d18e8e 100644 --- a/registry/session_v2.go +++ b/registry/session_v2.go @@ -11,7 +11,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" - "github.com/docker/docker/registry/v2" + "github.com/docker/distribution/registry/api/v2" "github.com/docker/docker/utils" ) @@ -109,8 +109,8 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au // - Succeeded to head image blob (already exists) // - Failed with no error (continue to Push the Blob) // - Failed with error -func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, auth *RequestAuthorization) (bool, error) { - routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum) +func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (bool, error) { + routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst) if err != nil { return false, err } @@ -141,11 +141,11 @@ func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, return false, nil } - return false, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s:%s", res.StatusCode, imageName, sumType, sum), res) + return false, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s", res.StatusCode, imageName, dgst), res) } -func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, blobWrtr io.Writer, auth *RequestAuthorization) error { - routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum) +func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobWrtr io.Writer, auth *RequestAuthorization) error { + routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst) if err != nil { return err } @@ -175,8 +175,8 @@ func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, b return err } -func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum string, auth *RequestAuthorization) (io.ReadCloser, int64, error) { - routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, sumType+":"+sum) +func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName string, dgst digest.Digest, auth *RequestAuthorization) (io.ReadCloser, int64, error) { + routeURL, err := getV2Builder(ep).BuildBlobURL(imageName, dgst) if err != nil { return nil, 0, err } @@ -198,7 +198,7 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum str if res.StatusCode == 401 { return nil, 0, errLoginRequired } - return nil, 0, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s:%s", res.StatusCode, imageName, sumType, sum), res) + return nil, 0, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s", res.StatusCode, imageName, dgst), res) } lenStr := res.Header.Get("Content-Length") l, err := strconv.ParseInt(lenStr, 10, 64) @@ -212,7 +212,7 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName, sumType, sum str // Push the image to the server for storage. // 'layer' is an uncompressed reader of the blob to be pushed. // The server will generate it's own checksum calculation. -func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string, blobRdr io.Reader, auth *RequestAuthorization) error { +func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobRdr io.Reader, auth *RequestAuthorization) error { location, err := r.initiateBlobUpload(ep, imageName, auth) if err != nil { return err @@ -225,7 +225,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string return err } queryParams := req.URL.Query() - queryParams.Add("digest", sumType+":"+sumStr) + queryParams.Add("digest", dgst.String()) req.URL.RawQuery = queryParams.Encode() if err := auth.Authorize(req); err != nil { return err @@ -245,7 +245,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName, sumType, sumStr string return err } logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) - return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s:%s", res.StatusCode, imageName, sumType, sumStr), res) + return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s", res.StatusCode, imageName, dgst), res) } return nil diff --git a/registry/v2/descriptors.go b/registry/v2/descriptors.go deleted file mode 100644 index 68d182411..000000000 --- a/registry/v2/descriptors.go +++ /dev/null @@ -1,144 +0,0 @@ -package v2 - -import "net/http" - -// TODO(stevvooe): Add route descriptors for each named route, along with -// accepted methods, parameters, returned status codes and error codes. - -// ErrorDescriptor provides relevant information about a given error code. -type ErrorDescriptor struct { - // Code is the error code that this descriptor describes. - Code ErrorCode - - // Value provides a unique, string key, often captilized with - // underscores, to identify the error code. This value is used as the - // keyed value when serializing api errors. - Value string - - // Message is a short, human readable decription of the error condition - // included in API responses. - Message string - - // Description provides a complete account of the errors purpose, suitable - // for use in documentation. - Description string - - // HTTPStatusCodes provides a list of status under which this error - // condition may arise. If it is empty, the error condition may be seen - // for any status code. - HTTPStatusCodes []int -} - -// ErrorDescriptors provides a list of HTTP API Error codes that may be -// encountered when interacting with the registry API. -var ErrorDescriptors = []ErrorDescriptor{ - { - Code: ErrorCodeUnknown, - Value: "UNKNOWN", - Message: "unknown error", - Description: `Generic error returned when the error does not have an - API classification.`, - }, - { - Code: ErrorCodeDigestInvalid, - Value: "DIGEST_INVALID", - Message: "provided digest did not match uploaded content", - Description: `When a blob is uploaded, the registry will check that - the content matches the digest provided by the client. The error may - include a detail structure with the key "digest", including the - invalid digest string. This error may also be returned when a manifest - includes an invalid layer digest.`, - HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, - }, - { - Code: ErrorCodeSizeInvalid, - Value: "SIZE_INVALID", - Message: "provided length did not match content length", - Description: `When a layer is uploaded, the provided size will be - checked against the uploaded content. If they do not match, this error - will be returned.`, - HTTPStatusCodes: []int{http.StatusBadRequest}, - }, - { - Code: ErrorCodeNameInvalid, - Value: "NAME_INVALID", - Message: "manifest name did not match URI", - Description: `During a manifest upload, if the name in the manifest - does not match the uri name, this error will be returned.`, - HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, - }, - { - Code: ErrorCodeTagInvalid, - Value: "TAG_INVALID", - Message: "manifest tag did not match URI", - Description: `During a manifest upload, if the tag in the manifest - does not match the uri tag, this error will be returned.`, - HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, - }, - { - Code: ErrorCodeNameUnknown, - Value: "NAME_UNKNOWN", - Message: "repository name not known to registry", - Description: `This is returned if the name used during an operation is - unknown to the registry.`, - HTTPStatusCodes: []int{http.StatusNotFound}, - }, - { - Code: ErrorCodeManifestUnknown, - Value: "MANIFEST_UNKNOWN", - Message: "manifest unknown", - Description: `This error is returned when the manifest, identified by - name and tag is unknown to the repository.`, - HTTPStatusCodes: []int{http.StatusNotFound}, - }, - { - Code: ErrorCodeManifestInvalid, - Value: "MANIFEST_INVALID", - Message: "manifest invalid", - Description: `During upload, manifests undergo several checks ensuring - validity. If those checks fail, this error may be returned, unless a - more specific error is included. The detail will contain information - the failed validation.`, - HTTPStatusCodes: []int{http.StatusBadRequest}, - }, - { - Code: ErrorCodeManifestUnverified, - Value: "MANIFEST_UNVERIFIED", - Message: "manifest failed signature verification", - Description: `During manifest upload, if the manifest fails signature - verification, this error will be returned.`, - HTTPStatusCodes: []int{http.StatusBadRequest}, - }, - { - Code: ErrorCodeBlobUnknown, - Value: "BLOB_UNKNOWN", - Message: "blob unknown to registry", - Description: `This error may be returned when a blob is unknown to the - registry in a specified repository. This can be returned with a - standard get or if a manifest references an unknown layer during - upload.`, - HTTPStatusCodes: []int{http.StatusBadRequest, http.StatusNotFound}, - }, - - { - Code: ErrorCodeBlobUploadUnknown, - Value: "BLOB_UPLOAD_UNKNOWN", - Message: "blob upload unknown to registry", - Description: `If a blob upload has been cancelled or was never - started, this error code may be returned.`, - HTTPStatusCodes: []int{http.StatusNotFound}, - }, -} - -var errorCodeToDescriptors map[ErrorCode]ErrorDescriptor -var idToDescriptors map[string]ErrorDescriptor - -func init() { - errorCodeToDescriptors = make(map[ErrorCode]ErrorDescriptor, len(ErrorDescriptors)) - idToDescriptors = make(map[string]ErrorDescriptor, len(ErrorDescriptors)) - - for _, descriptor := range ErrorDescriptors { - errorCodeToDescriptors[descriptor.Code] = descriptor - idToDescriptors[descriptor.Value] = descriptor - } -} diff --git a/registry/v2/doc.go b/registry/v2/doc.go deleted file mode 100644 index 30fe2271a..000000000 --- a/registry/v2/doc.go +++ /dev/null @@ -1,13 +0,0 @@ -// Package v2 describes routes, urls and the error codes used in the Docker -// Registry JSON HTTP API V2. In addition to declarations, descriptors are -// provided for routes and error codes that can be used for implementation and -// automatically generating documentation. -// -// Definitions here are considered to be locked down for the V2 registry api. -// Any changes must be considered carefully and should not proceed without a -// change proposal. -// -// Currently, while the HTTP API definitions are considered stable, the Go API -// exports are considered unstable. Go API consumers should take care when -// relying on these definitions until this message is deleted. -package v2 diff --git a/registry/v2/errors.go b/registry/v2/errors.go deleted file mode 100644 index 8c85d3a97..000000000 --- a/registry/v2/errors.go +++ /dev/null @@ -1,185 +0,0 @@ -package v2 - -import ( - "fmt" - "strings" -) - -// ErrorCode represents the error type. The errors are serialized via strings -// and the integer format may change and should *never* be exported. -type ErrorCode int - -const ( - // ErrorCodeUnknown is a catch-all for errors not defined below. - ErrorCodeUnknown ErrorCode = iota - - // ErrorCodeDigestInvalid is returned when uploading a blob if the - // provided digest does not match the blob contents. - ErrorCodeDigestInvalid - - // ErrorCodeSizeInvalid is returned when uploading a blob if the provided - // size does not match the content length. - ErrorCodeSizeInvalid - - // ErrorCodeNameInvalid is returned when the name in the manifest does not - // match the provided name. - ErrorCodeNameInvalid - - // ErrorCodeTagInvalid is returned when the tag in the manifest does not - // match the provided tag. - ErrorCodeTagInvalid - - // ErrorCodeNameUnknown when the repository name is not known. - ErrorCodeNameUnknown - - // ErrorCodeManifestUnknown returned when image manifest is unknown. - ErrorCodeManifestUnknown - - // ErrorCodeManifestInvalid returned when an image manifest is invalid, - // typically during a PUT operation. This error encompasses all errors - // encountered during manifest validation that aren't signature errors. - ErrorCodeManifestInvalid - - // ErrorCodeManifestUnverified is returned when the manifest fails - // signature verfication. - ErrorCodeManifestUnverified - - // ErrorCodeBlobUnknown is returned when a blob is unknown to the - // registry. This can happen when the manifest references a nonexistent - // layer or the result is not found by a blob fetch. - ErrorCodeBlobUnknown - - // ErrorCodeBlobUploadUnknown is returned when an upload is unknown. - ErrorCodeBlobUploadUnknown -) - -// ParseErrorCode attempts to parse the error code string, returning -// ErrorCodeUnknown if the error is not known. -func ParseErrorCode(s string) ErrorCode { - desc, ok := idToDescriptors[s] - - if !ok { - return ErrorCodeUnknown - } - - return desc.Code -} - -// Descriptor returns the descriptor for the error code. -func (ec ErrorCode) Descriptor() ErrorDescriptor { - d, ok := errorCodeToDescriptors[ec] - - if !ok { - return ErrorCodeUnknown.Descriptor() - } - - return d -} - -// String returns the canonical identifier for this error code. -func (ec ErrorCode) String() string { - return ec.Descriptor().Value -} - -// Message returned the human-readable error message for this error code. -func (ec ErrorCode) Message() string { - return ec.Descriptor().Message -} - -// MarshalText encodes the receiver into UTF-8-encoded text and returns the -// result. -func (ec ErrorCode) MarshalText() (text []byte, err error) { - return []byte(ec.String()), nil -} - -// UnmarshalText decodes the form generated by MarshalText. -func (ec *ErrorCode) UnmarshalText(text []byte) error { - desc, ok := idToDescriptors[string(text)] - - if !ok { - desc = ErrorCodeUnknown.Descriptor() - } - - *ec = desc.Code - - return nil -} - -// Error provides a wrapper around ErrorCode with extra Details provided. -type Error struct { - Code ErrorCode `json:"code"` - Message string `json:"message,omitempty"` - Detail interface{} `json:"detail,omitempty"` -} - -// Error returns a human readable representation of the error. -func (e Error) Error() string { - return fmt.Sprintf("%s: %s", - strings.ToLower(strings.Replace(e.Code.String(), "_", " ", -1)), - e.Message) -} - -// Errors provides the envelope for multiple errors and a few sugar methods -// for use within the application. -type Errors struct { - Errors []Error `json:"errors,omitempty"` -} - -// Push pushes an error on to the error stack, with the optional detail -// argument. It is a programming error (ie panic) to push more than one -// detail at a time. -func (errs *Errors) Push(code ErrorCode, details ...interface{}) { - if len(details) > 1 { - panic("please specify zero or one detail items for this error") - } - - var detail interface{} - if len(details) > 0 { - detail = details[0] - } - - if err, ok := detail.(error); ok { - detail = err.Error() - } - - errs.PushErr(Error{ - Code: code, - Message: code.Message(), - Detail: detail, - }) -} - -// PushErr pushes an error interface onto the error stack. -func (errs *Errors) PushErr(err error) { - switch err.(type) { - case Error: - errs.Errors = append(errs.Errors, err.(Error)) - default: - errs.Errors = append(errs.Errors, Error{Message: err.Error()}) - } -} - -func (errs *Errors) Error() string { - switch errs.Len() { - case 0: - return "" - case 1: - return errs.Errors[0].Error() - default: - msg := "errors:\n" - for _, err := range errs.Errors { - msg += err.Error() + "\n" - } - return msg - } -} - -// Clear clears the errors. -func (errs *Errors) Clear() { - errs.Errors = errs.Errors[:0] -} - -// Len returns the current number of errors. -func (errs *Errors) Len() int { - return len(errs.Errors) -} diff --git a/registry/v2/errors_test.go b/registry/v2/errors_test.go deleted file mode 100644 index 4a80cdfe2..000000000 --- a/registry/v2/errors_test.go +++ /dev/null @@ -1,163 +0,0 @@ -package v2 - -import ( - "encoding/json" - "reflect" - "testing" -) - -// TestErrorCodes ensures that error code format, mappings and -// marshaling/unmarshaling. round trips are stable. -func TestErrorCodes(t *testing.T) { - for _, desc := range ErrorDescriptors { - if desc.Code.String() != desc.Value { - t.Fatalf("error code string incorrect: %q != %q", desc.Code.String(), desc.Value) - } - - if desc.Code.Message() != desc.Message { - t.Fatalf("incorrect message for error code %v: %q != %q", desc.Code, desc.Code.Message(), desc.Message) - } - - // Serialize the error code using the json library to ensure that we - // get a string and it works round trip. - p, err := json.Marshal(desc.Code) - - if err != nil { - t.Fatalf("error marshaling error code %v: %v", desc.Code, err) - } - - if len(p) <= 0 { - t.Fatalf("expected content in marshaled before for error code %v", desc.Code) - } - - // First, unmarshal to interface and ensure we have a string. - var ecUnspecified interface{} - if err := json.Unmarshal(p, &ecUnspecified); err != nil { - t.Fatalf("error unmarshaling error code %v: %v", desc.Code, err) - } - - if _, ok := ecUnspecified.(string); !ok { - t.Fatalf("expected a string for error code %v on unmarshal got a %T", desc.Code, ecUnspecified) - } - - // Now, unmarshal with the error code type and ensure they are equal - var ecUnmarshaled ErrorCode - if err := json.Unmarshal(p, &ecUnmarshaled); err != nil { - t.Fatalf("error unmarshaling error code %v: %v", desc.Code, err) - } - - if ecUnmarshaled != desc.Code { - t.Fatalf("unexpected error code during error code marshal/unmarshal: %v != %v", ecUnmarshaled, desc.Code) - } - } -} - -// TestErrorsManagement does a quick check of the Errors type to ensure that -// members are properly pushed and marshaled. -func TestErrorsManagement(t *testing.T) { - var errs Errors - - errs.Push(ErrorCodeDigestInvalid) - errs.Push(ErrorCodeBlobUnknown, - map[string]string{"digest": "sometestblobsumdoesntmatter"}) - - p, err := json.Marshal(errs) - - if err != nil { - t.Fatalf("error marashaling errors: %v", err) - } - - expectedJSON := "{\"errors\":[{\"code\":\"DIGEST_INVALID\",\"message\":\"provided digest did not match uploaded content\"},{\"code\":\"BLOB_UNKNOWN\",\"message\":\"blob unknown to registry\",\"detail\":{\"digest\":\"sometestblobsumdoesntmatter\"}}]}" - - if string(p) != expectedJSON { - t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON) - } - - errs.Clear() - errs.Push(ErrorCodeUnknown) - expectedJSON = "{\"errors\":[{\"code\":\"UNKNOWN\",\"message\":\"unknown error\"}]}" - p, err = json.Marshal(errs) - - if err != nil { - t.Fatalf("error marashaling errors: %v", err) - } - - if string(p) != expectedJSON { - t.Fatalf("unexpected json: %q != %q", string(p), expectedJSON) - } -} - -// TestMarshalUnmarshal ensures that api errors can round trip through json -// without losing information. -func TestMarshalUnmarshal(t *testing.T) { - - var errors Errors - - for _, testcase := range []struct { - description string - err Error - }{ - { - description: "unknown error", - err: Error{ - - Code: ErrorCodeUnknown, - Message: ErrorCodeUnknown.Descriptor().Message, - }, - }, - { - description: "unknown manifest", - err: Error{ - Code: ErrorCodeManifestUnknown, - Message: ErrorCodeManifestUnknown.Descriptor().Message, - }, - }, - { - description: "unknown manifest", - err: Error{ - Code: ErrorCodeBlobUnknown, - Message: ErrorCodeBlobUnknown.Descriptor().Message, - Detail: map[string]interface{}{"digest": "asdfqwerqwerqwerqwer"}, - }, - }, - } { - fatalf := func(format string, args ...interface{}) { - t.Fatalf(testcase.description+": "+format, args...) - } - - unexpectedErr := func(err error) { - fatalf("unexpected error: %v", err) - } - - p, err := json.Marshal(testcase.err) - if err != nil { - unexpectedErr(err) - } - - var unmarshaled Error - if err := json.Unmarshal(p, &unmarshaled); err != nil { - unexpectedErr(err) - } - - if !reflect.DeepEqual(unmarshaled, testcase.err) { - fatalf("errors not equal after round trip: %#v != %#v", unmarshaled, testcase.err) - } - - // Roll everything up into an error response envelope. - errors.PushErr(testcase.err) - } - - p, err := json.Marshal(errors) - if err != nil { - t.Fatalf("unexpected error marshaling error envelope: %v", err) - } - - var unmarshaled Errors - if err := json.Unmarshal(p, &unmarshaled); err != nil { - t.Fatalf("unexpected error unmarshaling error envelope: %v", err) - } - - if !reflect.DeepEqual(unmarshaled, errors) { - t.Fatalf("errors not equal after round trip: %#v != %#v", unmarshaled, errors) - } -} diff --git a/registry/v2/regexp.go b/registry/v2/regexp.go deleted file mode 100644 index 07484dcd6..000000000 --- a/registry/v2/regexp.go +++ /dev/null @@ -1,22 +0,0 @@ -package v2 - -import "regexp" - -// This file defines regular expressions for use in route definition. These -// are also defined in the registry code base. Until they are in a common, -// shared location, and exported, they must be repeated here. - -// RepositoryNameComponentRegexp restricts registtry path components names to -// start with at least two letters or numbers, with following parts able to -// separated by one period, dash or underscore. -var RepositoryNameComponentRegexp = regexp.MustCompile(`[a-z0-9]+(?:[._-][a-z0-9]+)*`) - -// RepositoryNameRegexp builds on RepositoryNameComponentRegexp to allow 1 to -// 5 path components, separated by a forward slash. -var RepositoryNameRegexp = regexp.MustCompile(`(?:` + RepositoryNameComponentRegexp.String() + `/){0,4}` + RepositoryNameComponentRegexp.String()) - -// TagNameRegexp matches valid tag names. From docker/docker:graph/tags.go. -var TagNameRegexp = regexp.MustCompile(`[\w][\w.-]{0,127}`) - -// DigestRegexp matches valid digest types. -var DigestRegexp = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+`) diff --git a/registry/v2/routes.go b/registry/v2/routes.go deleted file mode 100644 index de0a38fb8..000000000 --- a/registry/v2/routes.go +++ /dev/null @@ -1,66 +0,0 @@ -package v2 - -import "github.com/gorilla/mux" - -// The following are definitions of the name under which all V2 routes are -// registered. These symbols can be used to look up a route based on the name. -const ( - RouteNameBase = "base" - RouteNameManifest = "manifest" - RouteNameTags = "tags" - RouteNameBlob = "blob" - RouteNameBlobUpload = "blob-upload" - RouteNameBlobUploadChunk = "blob-upload-chunk" -) - -var allEndpoints = []string{ - RouteNameManifest, - RouteNameTags, - RouteNameBlob, - RouteNameBlobUpload, - RouteNameBlobUploadChunk, -} - -// Router builds a gorilla router with named routes for the various API -// methods. This can be used directly by both server implementations and -// clients. -func Router() *mux.Router { - router := mux.NewRouter(). - StrictSlash(true) - - // GET /v2/ Check Check that the registry implements API version 2(.1) - router. - Path("/v2/"). - Name(RouteNameBase) - - // GET /v2//manifest/ Image Manifest Fetch the image manifest identified by name and reference where reference can be a tag or digest. - // PUT /v2//manifest/ Image Manifest Upload the image manifest identified by name and reference where reference can be a tag or digest. - // DELETE /v2//manifest/ Image Manifest Delete the image identified by name and reference where reference can be a tag or digest. - router. - Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/manifests/{reference:" + TagNameRegexp.String() + "|" + DigestRegexp.String() + "}"). - Name(RouteNameManifest) - - // GET /v2//tags/list Tags Fetch the tags under the repository identified by name. - router. - Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/tags/list"). - Name(RouteNameTags) - - // GET /v2//blob/ Layer Fetch the blob identified by digest. - router. - Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/{digest:[a-zA-Z0-9-_+.]+:[a-zA-Z0-9-_+.=]+}"). - Name(RouteNameBlob) - - // POST /v2//blob/upload/ Layer Upload Initiate an upload of the layer identified by tarsum. - router. - Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/"). - Name(RouteNameBlobUpload) - - // GET /v2//blob/upload/ Layer Upload Get the status of the upload identified by tarsum and uuid. - // PUT /v2//blob/upload/ Layer Upload Upload all or a chunk of the upload identified by tarsum and uuid. - // DELETE /v2//blob/upload/ Layer Upload Cancel the upload identified by layer and uuid - router. - Path("/v2/{name:" + RepositoryNameRegexp.String() + "}/blobs/uploads/{uuid}"). - Name(RouteNameBlobUploadChunk) - - return router -} diff --git a/registry/v2/routes_test.go b/registry/v2/routes_test.go deleted file mode 100644 index 0191feed0..000000000 --- a/registry/v2/routes_test.go +++ /dev/null @@ -1,192 +0,0 @@ -package v2 - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "reflect" - "testing" - - "github.com/gorilla/mux" -) - -type routeTestCase struct { - RequestURI string - Vars map[string]string - RouteName string - StatusCode int -} - -// TestRouter registers a test handler with all the routes and ensures that -// each route returns the expected path variables. Not method verification is -// present. This not meant to be exhaustive but as check to ensure that the -// expected variables are extracted. -// -// This may go away as the application structure comes together. -func TestRouter(t *testing.T) { - - router := Router() - - testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - testCase := routeTestCase{ - RequestURI: r.RequestURI, - Vars: mux.Vars(r), - RouteName: mux.CurrentRoute(r).GetName(), - } - - enc := json.NewEncoder(w) - - if err := enc.Encode(testCase); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - }) - - // Startup test server - server := httptest.NewServer(router) - - for _, testcase := range []routeTestCase{ - { - RouteName: RouteNameBase, - RequestURI: "/v2/", - Vars: map[string]string{}, - }, - { - RouteName: RouteNameManifest, - RequestURI: "/v2/foo/manifests/bar", - Vars: map[string]string{ - "name": "foo", - "reference": "bar", - }, - }, - { - RouteName: RouteNameManifest, - RequestURI: "/v2/foo/bar/manifests/tag", - Vars: map[string]string{ - "name": "foo/bar", - "reference": "tag", - }, - }, - { - RouteName: RouteNameTags, - RequestURI: "/v2/foo/bar/tags/list", - Vars: map[string]string{ - "name": "foo/bar", - }, - }, - { - RouteName: RouteNameBlob, - RequestURI: "/v2/foo/bar/blobs/tarsum.dev+foo:abcdef0919234", - Vars: map[string]string{ - "name": "foo/bar", - "digest": "tarsum.dev+foo:abcdef0919234", - }, - }, - { - RouteName: RouteNameBlob, - RequestURI: "/v2/foo/bar/blobs/sha256:abcdef0919234", - Vars: map[string]string{ - "name": "foo/bar", - "digest": "sha256:abcdef0919234", - }, - }, - { - RouteName: RouteNameBlobUpload, - RequestURI: "/v2/foo/bar/blobs/uploads/", - Vars: map[string]string{ - "name": "foo/bar", - }, - }, - { - RouteName: RouteNameBlobUploadChunk, - RequestURI: "/v2/foo/bar/blobs/uploads/uuid", - Vars: map[string]string{ - "name": "foo/bar", - "uuid": "uuid", - }, - }, - { - RouteName: RouteNameBlobUploadChunk, - RequestURI: "/v2/foo/bar/blobs/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", - Vars: map[string]string{ - "name": "foo/bar", - "uuid": "D95306FA-FAD3-4E36-8D41-CF1C93EF8286", - }, - }, - { - RouteName: RouteNameBlobUploadChunk, - RequestURI: "/v2/foo/bar/blobs/uploads/RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==", - Vars: map[string]string{ - "name": "foo/bar", - "uuid": "RDk1MzA2RkEtRkFEMy00RTM2LThENDEtQ0YxQzkzRUY4Mjg2IA==", - }, - }, - { - // Check ambiguity: ensure we can distinguish between tags for - // "foo/bar/image/image" and image for "foo/bar/image" with tag - // "tags" - RouteName: RouteNameManifest, - RequestURI: "/v2/foo/bar/manifests/manifests/tags", - Vars: map[string]string{ - "name": "foo/bar/manifests", - "reference": "tags", - }, - }, - { - // This case presents an ambiguity between foo/bar with tag="tags" - // and list tags for "foo/bar/manifest" - RouteName: RouteNameTags, - RequestURI: "/v2/foo/bar/manifests/tags/list", - Vars: map[string]string{ - "name": "foo/bar/manifests", - }, - }, - { - RouteName: RouteNameBlobUploadChunk, - RequestURI: "/v2/foo/../../blob/uploads/D95306FA-FAD3-4E36-8D41-CF1C93EF8286", - StatusCode: http.StatusNotFound, - }, - } { - // Register the endpoint - router.GetRoute(testcase.RouteName).Handler(testHandler) - u := server.URL + testcase.RequestURI - - resp, err := http.Get(u) - - if err != nil { - t.Fatalf("error issuing get request: %v", err) - } - - if testcase.StatusCode == 0 { - // Override default, zero-value - testcase.StatusCode = http.StatusOK - } - - if resp.StatusCode != testcase.StatusCode { - t.Fatalf("unexpected status for %s: %v %v", u, resp.Status, resp.StatusCode) - } - - if testcase.StatusCode != http.StatusOK { - // We don't care about json response. - continue - } - - dec := json.NewDecoder(resp.Body) - - var actualRouteInfo routeTestCase - if err := dec.Decode(&actualRouteInfo); err != nil { - t.Fatalf("error reading json response: %v", err) - } - // Needs to be set out of band - actualRouteInfo.StatusCode = resp.StatusCode - - if actualRouteInfo.RouteName != testcase.RouteName { - t.Fatalf("incorrect route %q matched, expected %q", actualRouteInfo.RouteName, testcase.RouteName) - } - - if !reflect.DeepEqual(actualRouteInfo, testcase) { - t.Fatalf("actual does not equal expected: %#v != %#v", actualRouteInfo, testcase) - } - } - -} diff --git a/registry/v2/urls.go b/registry/v2/urls.go deleted file mode 100644 index 38fa98af0..000000000 --- a/registry/v2/urls.go +++ /dev/null @@ -1,179 +0,0 @@ -package v2 - -import ( - "net/http" - "net/url" - - "github.com/gorilla/mux" -) - -// URLBuilder creates registry API urls from a single base endpoint. It can be -// used to create urls for use in a registry client or server. -// -// All urls will be created from the given base, including the api version. -// For example, if a root of "/foo/" is provided, urls generated will be fall -// under "/foo/v2/...". Most application will only provide a schema, host and -// port, such as "https://localhost:5000/". -type URLBuilder struct { - root *url.URL // url root (ie http://localhost/) - router *mux.Router -} - -// NewURLBuilder creates a URLBuilder with provided root url object. -func NewURLBuilder(root *url.URL) *URLBuilder { - return &URLBuilder{ - root: root, - router: Router(), - } -} - -// NewURLBuilderFromString workes identically to NewURLBuilder except it takes -// a string argument for the root, returning an error if it is not a valid -// url. -func NewURLBuilderFromString(root string) (*URLBuilder, error) { - u, err := url.Parse(root) - if err != nil { - return nil, err - } - - return NewURLBuilder(u), nil -} - -// NewURLBuilderFromRequest uses information from an *http.Request to -// construct the root url. -func NewURLBuilderFromRequest(r *http.Request) *URLBuilder { - u := &url.URL{ - Scheme: r.URL.Scheme, - Host: r.Host, - } - - return NewURLBuilder(u) -} - -// BuildBaseURL constructs a base url for the API, typically just "/v2/". -func (ub *URLBuilder) BuildBaseURL() (string, error) { - route := ub.cloneRoute(RouteNameBase) - - baseURL, err := route.URL() - if err != nil { - return "", err - } - - return baseURL.String(), nil -} - -// BuildTagsURL constructs a url to list the tags in the named repository. -func (ub *URLBuilder) BuildTagsURL(name string) (string, error) { - route := ub.cloneRoute(RouteNameTags) - - tagsURL, err := route.URL("name", name) - if err != nil { - return "", err - } - - return tagsURL.String(), nil -} - -// BuildManifestURL constructs a url for the manifest identified by name and reference. -func (ub *URLBuilder) BuildManifestURL(name, reference string) (string, error) { - route := ub.cloneRoute(RouteNameManifest) - - manifestURL, err := route.URL("name", name, "reference", reference) - if err != nil { - return "", err - } - - return manifestURL.String(), nil -} - -// BuildBlobURL constructs the url for the blob identified by name and dgst. -func (ub *URLBuilder) BuildBlobURL(name string, dgst string) (string, error) { - route := ub.cloneRoute(RouteNameBlob) - - layerURL, err := route.URL("name", name, "digest", dgst) - if err != nil { - return "", err - } - - return layerURL.String(), nil -} - -// BuildBlobUploadURL constructs a url to begin a blob upload in the -// repository identified by name. -func (ub *URLBuilder) BuildBlobUploadURL(name string, values ...url.Values) (string, error) { - route := ub.cloneRoute(RouteNameBlobUpload) - - uploadURL, err := route.URL("name", name) - if err != nil { - return "", err - } - - return appendValuesURL(uploadURL, values...).String(), nil -} - -// BuildBlobUploadChunkURL constructs a url for the upload identified by uuid, -// including any url values. This should generally not be used by clients, as -// this url is provided by server implementations during the blob upload -// process. -func (ub *URLBuilder) BuildBlobUploadChunkURL(name, uuid string, values ...url.Values) (string, error) { - route := ub.cloneRoute(RouteNameBlobUploadChunk) - - uploadURL, err := route.URL("name", name, "uuid", uuid) - if err != nil { - return "", err - } - - return appendValuesURL(uploadURL, values...).String(), nil -} - -// clondedRoute returns a clone of the named route from the router. Routes -// must be cloned to avoid modifying them during url generation. -func (ub *URLBuilder) cloneRoute(name string) clonedRoute { - route := new(mux.Route) - root := new(url.URL) - - *route = *ub.router.GetRoute(name) // clone the route - *root = *ub.root - - return clonedRoute{Route: route, root: root} -} - -type clonedRoute struct { - *mux.Route - root *url.URL -} - -func (cr clonedRoute) URL(pairs ...string) (*url.URL, error) { - routeURL, err := cr.Route.URL(pairs...) - if err != nil { - return nil, err - } - - return cr.root.ResolveReference(routeURL), nil -} - -// appendValuesURL appends the parameters to the url. -func appendValuesURL(u *url.URL, values ...url.Values) *url.URL { - merged := u.Query() - - for _, v := range values { - for k, vv := range v { - merged[k] = append(merged[k], vv...) - } - } - - u.RawQuery = merged.Encode() - return u -} - -// appendValues appends the parameters to the url. Panics if the string is not -// a url. -func appendValues(u string, values ...url.Values) string { - up, err := url.Parse(u) - - if err != nil { - panic(err) // should never happen - } - - return appendValuesURL(up, values...).String() -} diff --git a/registry/v2/urls_test.go b/registry/v2/urls_test.go deleted file mode 100644 index f30c96c0a..000000000 --- a/registry/v2/urls_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package v2 - -import ( - "net/url" - "testing" -) - -type urlBuilderTestCase struct { - description string - expectedPath string - build func() (string, error) -} - -// TestURLBuilder tests the various url building functions, ensuring they are -// returning the expected values. -func TestURLBuilder(t *testing.T) { - var ( - urlBuilder *URLBuilder - err error - ) - - testCases := []urlBuilderTestCase{ - { - description: "test base url", - expectedPath: "/v2/", - build: func() (string, error) { - return urlBuilder.BuildBaseURL() - }, - }, - { - description: "test tags url", - expectedPath: "/v2/foo/bar/tags/list", - build: func() (string, error) { - return urlBuilder.BuildTagsURL("foo/bar") - }, - }, - { - description: "test manifest url", - expectedPath: "/v2/foo/bar/manifests/tag", - build: func() (string, error) { - return urlBuilder.BuildManifestURL("foo/bar", "tag") - }, - }, - { - description: "build blob url", - expectedPath: "/v2/foo/bar/blobs/tarsum.v1+sha256:abcdef0123456789", - build: func() (string, error) { - return urlBuilder.BuildBlobURL("foo/bar", "tarsum.v1+sha256:abcdef0123456789") - }, - }, - { - description: "build blob upload url", - expectedPath: "/v2/foo/bar/blobs/uploads/", - build: func() (string, error) { - return urlBuilder.BuildBlobUploadURL("foo/bar") - }, - }, - { - description: "build blob upload url with digest and size", - expectedPath: "/v2/foo/bar/blobs/uploads/?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000", - build: func() (string, error) { - return urlBuilder.BuildBlobUploadURL("foo/bar", url.Values{ - "size": []string{"10000"}, - "digest": []string{"tarsum.v1+sha256:abcdef0123456789"}, - }) - }, - }, - { - description: "build blob upload chunk url", - expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part", - build: func() (string, error) { - return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part") - }, - }, - { - description: "build blob upload chunk url with digest and size", - expectedPath: "/v2/foo/bar/blobs/uploads/uuid-part?digest=tarsum.v1%2Bsha256%3Aabcdef0123456789&size=10000", - build: func() (string, error) { - return urlBuilder.BuildBlobUploadChunkURL("foo/bar", "uuid-part", url.Values{ - "size": []string{"10000"}, - "digest": []string{"tarsum.v1+sha256:abcdef0123456789"}, - }) - }, - }, - } - - roots := []string{ - "http://example.com", - "https://example.com", - "http://localhost:5000", - "https://localhost:5443", - } - - for _, root := range roots { - urlBuilder, err = NewURLBuilderFromString(root) - if err != nil { - t.Fatalf("unexpected error creating urlbuilder: %v", err) - } - - for _, testCase := range testCases { - url, err := testCase.build() - if err != nil { - t.Fatalf("%s: error building url: %v", testCase.description, err) - } - - expectedURL := root + testCase.expectedPath - - if url != expectedURL { - t.Fatalf("%s: %q != %q", testCase.description, url, expectedURL) - } - } - } -} From c71747571458bf992c729c483c9509e8182d630b Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 31 Mar 2015 15:12:15 -0700 Subject: [PATCH 206/999] Fix panic in integration tests Closing activationLock only if it's not closed already. This is needed only because integration tests using docker code directly and doesn't care about global state. Signed-off-by: Alexander Morozov --- api/server/server_linux.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/api/server/server_linux.go b/api/server/server_linux.go index 972f5ff74..0de20a5f1 100644 --- a/api/server/server_linux.go +++ b/api/server/server_linux.go @@ -95,7 +95,9 @@ func AcceptConnections(job *engine.Job) error { go systemd.SdNotify("READY=1") // close the lock so the listeners start accepting connections - if activationLock != nil { + select { + case <-activationLock: + default: close(activationLock) } From 153f98bad51e2f46cf3853d2da0173bcfb4b687d Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 1 Apr 2015 00:23:04 +0300 Subject: [PATCH 207/999] pkg/broadcastwriter: reset after 4 KB w/o stream Signed-off-by: Cristian Staretu --- pkg/broadcastwriter/broadcastwriter.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/broadcastwriter/broadcastwriter.go b/pkg/broadcastwriter/broadcastwriter.go index 248cd8f46..bd9b67555 100644 --- a/pkg/broadcastwriter/broadcastwriter.go +++ b/pkg/broadcastwriter/broadcastwriter.go @@ -42,9 +42,12 @@ func (w *BroadcastWriter) Write(p []byte) (n int, err error) { delete(writers, sw) } } - // exit if there is no more writers if len(w.streams) == 1 { - w.buf.Reset() + if w.buf.Len() >= 4096 { + w.buf.Reset() + } else { + w.buf.Write(p) + } w.Unlock() return len(p), nil } From 6a47c70d60f5ac92b152e1c8ef7fe5b714fe7833 Mon Sep 17 00:00:00 2001 From: Wolfgang Powisch Date: Wed, 1 Apr 2015 00:21:21 +0200 Subject: [PATCH 208/999] Update networking.md Signed-off-by: Wolfgang Powisch --- docs/sources/articles/networking.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index 754d9989c..b3d2bdace 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -652,7 +652,7 @@ for Docker. When adding a third host you would add a route for the subnet Remember the subnet for Docker containers should at least have a size of `/80`. This way an IPv6 address can end with the container's MAC address and you prevent NDP neighbor cache invalidation issues in the Docker layer. So if you -have a `/64` for your whole environment use `/68` subnets for the hosts and +have a `/64` for your whole environment use `/78` subnets for the hosts and `/80` for the containers. This way you can use 4096 hosts with 16 `/80` subnets each. From f855d5dde70f1f3e4390e53576a3ca9b5a9db8ba Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 1 Apr 2015 00:45:40 +0200 Subject: [PATCH 209/999] Fix heading level of logging drivers Syslog was a heading-2, but should be heading-3; changed the headings to heading-4 to match the "network settings" section. Also changed "Log driver" to "logging driver" for JSON. Signed-off-by: Sebastiaan van Stijn --- docs/sources/reference/run.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index ea98fee38..000adcb7a 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -742,17 +742,17 @@ familiar with using LXC directly. You can specify a different logging driver for the container than for the daemon. -### Logging driver: none +#### Logging driver: none Disables any logging for the container. `docker logs` won't be available with this driver. -### Log driver: json-file +#### Logging driver: json-file Default logging driver for Docker. Writes JSON messages to file. `docker logs` command is available only for this logging driver -## Logging driver: syslog +#### Logging driver: syslog Syslog logging driver for Docker. Writes log messages to syslog. `docker logs` command is not available for this logging driver From 03d3d79b2b3f8b720fff2d649aff0ef791cff417 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 31 Mar 2015 16:21:37 -0700 Subject: [PATCH 210/999] Remove jobs from registry.Service This makes `registry.Service` a first class type and does not use jobs to interact with this type. Signed-off-by: Michael Crosby --- api/client/search.go | 1 + api/server/server.go | 55 +++++------ builder/internals.go | 3 +- daemon/daemon.go | 11 ++- daemon/info.go | 11 +-- docker/daemon.go | 8 +- graph/pull.go | 2 +- graph/push.go | 2 +- graph/tags.go | 20 ++-- graph/tags_unit_test.go | 2 +- integration/utils_test.go | 6 +- registry/auth.go | 1 - registry/service.go | 201 ++++---------------------------------- 13 files changed, 71 insertions(+), 252 deletions(-) diff --git a/api/client/search.go b/api/client/search.go index beb9000d0..6f035bdf6 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -49,6 +49,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { if _, err := outs.ReadListFrom(rawBody); err != nil { return err } + outs.ReverseSort() w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") for _, out := range outs.Data { diff --git a/api/server/server.go b/api/server/server.go index bb406aac5..21524e1b4 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -8,7 +8,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net" "net/http" "os" @@ -21,6 +20,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/api/types" + "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/parsers" @@ -169,29 +169,25 @@ func getBoolParam(value string) (bool, error) { return ret, nil } +func getDaemon(eng *engine.Engine) *daemon.Daemon { + return eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon) +} + func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - var ( - authConfig, err = ioutil.ReadAll(r.Body) - job = eng.Job("auth") - stdoutBuffer = bytes.NewBuffer(nil) - ) + var config *registry.AuthConfig + err := json.NewDecoder(r.Body).Decode(&config) + r.Body.Close() if err != nil { return err } - job.Setenv("authConfig", string(authConfig)) - job.Stdout.Add(stdoutBuffer) - if err = job.Run(); err != nil { + d := getDaemon(eng) + status, err := d.RegistryService.Auth(config) + if err != nil { return err } - if status := engine.Tail(stdoutBuffer, 1); status != "" { - var env engine.Env - env.Set("Status", status) - return writeJSON(w, http.StatusOK, &types.AuthResponse{ - Status: status, - }) - } - w.WriteHeader(http.StatusNoContent) - return nil + return writeJSON(w, http.StatusOK, &types.AuthResponse{ + Status: status, + }) } func getVersion(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -601,31 +597,30 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons return err } var ( + config *registry.AuthConfig authEncoded = r.Header.Get("X-Registry-Auth") - authConfig = ®istry.AuthConfig{} - metaHeaders = map[string][]string{} + headers = map[string][]string{} ) if authEncoded != "" { authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) - if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { + if err := json.NewDecoder(authJson).Decode(&config); 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 = ®istry.AuthConfig{} + config = ®istry.AuthConfig{} } } for k, v := range r.Header { if strings.HasPrefix(k, "X-Meta-") { - metaHeaders[k] = v + headers[k] = v } } - - var job = eng.Job("search", r.Form.Get("term")) - job.SetenvJson("metaHeaders", metaHeaders) - job.SetenvJson("authConfig", authConfig) - streamJSON(job, w, false) - - return job.Run() + d := getDaemon(eng) + query, err := d.RegistryService.Search(r.Form.Get("term"), config, headers) + if err != nil { + return err + } + return json.NewEncoder(w).Encode(query.Results) } func postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/builder/internals.go b/builder/internals.go index f4f6a5575..0ee6f76a6 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -34,7 +34,6 @@ import ( "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/pkg/urlutil" - "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -439,7 +438,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { pullRegistryAuth := b.AuthConfig if len(b.AuthConfigFile.Configs) > 0 { // The request came with a full auth config file, we prefer to use that - repoInfo, err := registry.ResolveRepositoryInfo(job, remote) + repoInfo, err := b.Daemon.RegistryService.ResolveRepository(remote) if err != nil { return nil, err } diff --git a/daemon/daemon.go b/daemon/daemon.go index 36cf438bb..a072fc34a 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -40,6 +40,7 @@ import ( "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/sysinfo" "github.com/docker/docker/pkg/truncindex" + "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/trust" "github.com/docker/docker/utils" @@ -107,6 +108,7 @@ type Daemon struct { trustStore *trust.TrustStore statsCollector *statsCollector defaultLogConfig runconfig.LogConfig + RegistryService *registry.Service } // Install installs daemon capabilities to eng. @@ -793,15 +795,15 @@ func (daemon *Daemon) RegisterLinks(container *Container, hostConfig *runconfig. } // FIXME: harmonize with NewGraph() -func NewDaemon(config *Config, eng *engine.Engine) (*Daemon, error) { - daemon, err := NewDaemonFromDirectory(config, eng) +func NewDaemon(config *Config, eng *engine.Engine, registryService *registry.Service) (*Daemon, error) { + daemon, err := NewDaemonFromDirectory(config, eng, registryService) if err != nil { return nil, err } return daemon, nil } -func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) { +func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService *registry.Service) (*Daemon, error) { if config.Mtu == 0 { config.Mtu = getDefaultNetworkMtu() } @@ -931,7 +933,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) } logrus.Debug("Creating repository list") - repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, trustKey) + repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, trustKey, registryService) if err != nil { return nil, fmt.Errorf("Couldn't create Tag store: %s", err) } @@ -1022,6 +1024,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error) trustStore: t, statsCollector: newStatsCollector(1 * time.Second), defaultLogConfig: config.LogConfig, + RegistryService: registryService, } eng.OnShutdown(func() { diff --git a/daemon/info.go b/daemon/info.go index 824647f8d..7885e7a9d 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -56,15 +56,6 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) error { if err := cjob.Run(); err != nil { return err } - registryJob := job.Eng.Job("registry_config") - registryEnv, _ := registryJob.Stdout.AddEnv() - if err := registryJob.Run(); err != nil { - return err - } - registryConfig := registry.ServiceConfig{} - if err := registryEnv.GetJson("config", ®istryConfig); err != nil { - return err - } v := &engine.Env{} v.SetJson("ID", daemon.ID) v.SetInt("Containers", len(daemon.List())) @@ -83,7 +74,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) error { v.Set("KernelVersion", kernelVersion) v.Set("OperatingSystem", operatingSystem) v.Set("IndexServerAddress", registry.IndexServerAddress()) - v.SetJson("RegistryConfig", registryConfig) + v.SetJson("RegistryConfig", daemon.RegistryService.Config) v.Set("InitSha1", dockerversion.INITSHA1) v.Set("InitPath", initPath) v.SetInt("NCPU", runtime.NumCPU()) diff --git a/docker/daemon.go b/docker/daemon.go index 534bc3a47..861bcdbc1 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -98,17 +98,13 @@ func mainDaemon() { logrus.Fatal(err) } - // load registry service - if err := registry.NewService(registryCfg).Install(eng); err != nil { - logrus.Fatal(err) - } - + registryService := registry.NewService(registryCfg) // load the daemon in the background so we can immediately start // the http api so that connections don't fail while the daemon // is booting daemonInitWait := make(chan error) go func() { - d, err := daemon.NewDaemon(daemonCfg, eng) + d, err := daemon.NewDaemon(daemonCfg, eng, registryService) if err != nil { daemonInitWait <- err return diff --git a/graph/pull.go b/graph/pull.go index 023b7cbc0..08b688cb2 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -35,7 +35,7 @@ func (s *TagStore) CmdPull(job *engine.Job) error { ) // Resolve the Repository name from fqn to RepositoryInfo - repoInfo, err := registry.ResolveRepositoryInfo(job, localName) + repoInfo, err := s.registryService.ResolveRepository(localName) if err != nil { return err } diff --git a/graph/push.go b/graph/push.go index 767f118c5..a542dbf81 100644 --- a/graph/push.go +++ b/graph/push.go @@ -498,7 +498,7 @@ func (s *TagStore) CmdPush(job *engine.Job) error { ) // Resolve the Repository name from fqn to RepositoryInfo - repoInfo, err := registry.ResolveRepositoryInfo(job, localName) + repoInfo, err := s.registryService.ResolveRepository(localName) if err != nil { return err } diff --git a/graph/tags.go b/graph/tags.go index 87c045b82..4ed63d959 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -36,8 +36,9 @@ type TagStore struct { sync.Mutex // FIXME: move push/pull-related fields // to a helper type - pullingPool map[string]chan struct{} - pushingPool map[string]chan struct{} + pullingPool map[string]chan struct{} + pushingPool map[string]chan struct{} + registryService *registry.Service } type Repository map[string]string @@ -60,19 +61,20 @@ func (r Repository) Contains(u Repository) bool { return true } -func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey) (*TagStore, error) { +func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey, registryService *registry.Service) (*TagStore, error) { abspath, err := filepath.Abs(path) if err != nil { return nil, err } store := &TagStore{ - path: abspath, - graph: graph, - trustKey: key, - Repositories: make(map[string]Repository), - pullingPool: make(map[string]chan struct{}), - pushingPool: make(map[string]chan struct{}), + path: abspath, + graph: graph, + trustKey: key, + Repositories: make(map[string]Repository), + pullingPool: make(map[string]chan struct{}), + pushingPool: make(map[string]chan struct{}), + registryService: registryService, } // Load the json file if it exists, otherwise create it. if err := store.reload(); os.IsNotExist(err) { diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go index c1a686bbc..001a10527 100644 --- a/graph/tags_unit_test.go +++ b/graph/tags_unit_test.go @@ -59,7 +59,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore { if err != nil { t.Fatal(err) } - store, err := NewTagStore(path.Join(root, "tags"), graph, nil) + store, err := NewTagStore(path.Join(root, "tags"), graph, nil, nil) if err != nil { t.Fatal(err) } diff --git a/integration/utils_test.go b/integration/utils_test.go index 1d49ef955..706ac6484 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -177,10 +177,6 @@ func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine { if err := builtins.Register(eng); err != nil { t.Fatal(err) } - // load registry service - if err := registry.NewService(nil).Install(eng); err != nil { - t.Fatal(err) - } // (This is manually copied and modified from main() until we have a more generic plugin system) cfg := &daemon.Config{ @@ -193,7 +189,7 @@ func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine { TrustKeyPath: filepath.Join(root, "key.json"), LogConfig: runconfig.LogConfig{Type: "json-file"}, } - d, err := daemon.NewDaemon(cfg, eng) + d, err := daemon.NewDaemon(cfg, eng, registry.NewService(nil)) if err != nil { t.Fatal(err) } diff --git a/registry/auth.go b/registry/auth.go index 2c37f7f64..51b781dd9 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -230,7 +230,6 @@ func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestd if registryEndpoint.Version == APIVersion2 { return loginV2(authConfig, registryEndpoint, factory) } - return loginV1(authConfig, registryEndpoint, factory) } diff --git a/registry/service.go b/registry/service.go index f464faabc..cf29732f4 100644 --- a/registry/service.go +++ b/registry/service.go @@ -1,20 +1,5 @@ package registry -import ( - "fmt" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" -) - -// Service exposes registry capabilities in the standard Engine -// interface. Once installed, it extends the engine with the -// following calls: -// -// 'auth': Authenticate against the public registry -// 'search': Search for images on the public registry -// 'pull': Download images from any registry (TODO) -// 'push': Upload images to any registry (TODO) type Service struct { Config *ServiceConfig } @@ -27,201 +12,53 @@ func NewService(options *Options) *Service { } } -// Install installs registry capabilities to eng. -func (s *Service) Install(eng *engine.Engine) error { - eng.Register("auth", s.Auth) - eng.Register("search", s.Search) - eng.Register("resolve_repository", s.ResolveRepository) - eng.Register("resolve_index", s.ResolveIndex) - eng.Register("registry_config", s.GetRegistryConfig) - return nil -} - // Auth contacts the public registry with the provided credentials, // and returns OK if authentication was sucessful. // It can be used to verify the validity of a client's credentials. -func (s *Service) Auth(job *engine.Job) error { - var ( - authConfig = new(AuthConfig) - endpoint *Endpoint - index *IndexInfo - status string - err error - ) - - job.GetenvJson("authConfig", authConfig) - +func (s *Service) Auth(authConfig *AuthConfig) (string, error) { addr := authConfig.ServerAddress if addr == "" { // Use the official registry address if not specified. addr = IndexServerAddress() } - - if index, err = ResolveIndexInfo(job, addr); err != nil { - return err + index, err := s.ResolveIndex(addr) + if err != nil { + return "", err } - - if endpoint, err = NewEndpoint(index); err != nil { - logrus.Errorf("unable to get new registry endpoint: %s", err) - return err + endpoint, err := NewEndpoint(index) + if err != nil { + return "", err } - authConfig.ServerAddress = endpoint.String() - - if status, err = Login(authConfig, endpoint, HTTPRequestFactory(nil)); err != nil { - logrus.Errorf("unable to login against registry endpoint %s: %s", endpoint, err) - return err - } - - logrus.Infof("successful registry login for endpoint %s: %s", endpoint, status) - job.Printf("%s\n", status) - - return nil + return Login(authConfig, endpoint, HTTPRequestFactory(nil)) } // Search queries the public registry for images matching the specified // search terms, and returns the results. -// -// Argument syntax: search TERM -// -// Option environment: -// 'authConfig': json-encoded credentials to authenticate against the registry. -// The search extends to images only accessible via the credentials. -// -// 'metaHeaders': extra HTTP headers to include in the request to the registry. -// The headers should be passed as a json-encoded dictionary. -// -// Output: -// Results are sent as a collection of structured messages (using engine.Table). -// Each result is sent as a separate message. -// Results are ordered by number of stars on the public registry. -func (s *Service) Search(job *engine.Job) error { - if n := len(job.Args); n != 1 { - return fmt.Errorf("Usage: %s TERM", job.Name) - } - var ( - term = job.Args[0] - metaHeaders = map[string][]string{} - authConfig = &AuthConfig{} - ) - job.GetenvJson("authConfig", authConfig) - job.GetenvJson("metaHeaders", metaHeaders) - - repoInfo, err := ResolveRepositoryInfo(job, term) +func (s *Service) Search(term string, authConfig *AuthConfig, headers map[string][]string) (*SearchResults, error) { + repoInfo, err := s.ResolveRepository(term) if err != nil { - return err + return nil, err } // *TODO: Search multiple indexes. endpoint, err := repoInfo.GetEndpoint() if err != nil { - return err + return nil, err } - r, err := NewSession(authConfig, HTTPRequestFactory(metaHeaders), endpoint, true) + r, err := NewSession(authConfig, HTTPRequestFactory(headers), endpoint, true) if err != nil { - return err + return nil, err } - results, err := r.SearchRepositories(repoInfo.GetSearchTerm()) - if err != nil { - return err - } - outs := engine.NewTable("star_count", 0) - for _, result := range results.Results { - out := &engine.Env{} - out.Import(result) - outs.Add(out) - } - outs.ReverseSort() - if _, err := outs.WriteListTo(job.Stdout); err != nil { - return err - } - return nil + return r.SearchRepositories(repoInfo.GetSearchTerm()) } // ResolveRepository splits a repository name into its components // and configuration of the associated registry. -func (s *Service) ResolveRepository(job *engine.Job) error { - var ( - reposName = job.Args[0] - ) - - repoInfo, err := s.Config.NewRepositoryInfo(reposName) - if err != nil { - return err - } - - out := engine.Env{} - err = out.SetJson("repository", repoInfo) - if err != nil { - return err - } - out.WriteTo(job.Stdout) - - return nil -} - -// Convenience wrapper for calling resolve_repository Job from a running job. -func ResolveRepositoryInfo(jobContext *engine.Job, reposName string) (*RepositoryInfo, error) { - job := jobContext.Eng.Job("resolve_repository", reposName) - env, err := job.Stdout.AddEnv() - if err != nil { - return nil, err - } - if err := job.Run(); err != nil { - return nil, err - } - info := RepositoryInfo{} - if err := env.GetJson("repository", &info); err != nil { - return nil, err - } - return &info, nil +func (s *Service) ResolveRepository(name string) (*RepositoryInfo, error) { + return s.Config.NewRepositoryInfo(name) } // ResolveIndex takes indexName and returns index info -func (s *Service) ResolveIndex(job *engine.Job) error { - var ( - indexName = job.Args[0] - ) - - index, err := s.Config.NewIndexInfo(indexName) - if err != nil { - return err - } - - out := engine.Env{} - err = out.SetJson("index", index) - if err != nil { - return err - } - out.WriteTo(job.Stdout) - - return nil -} - -// Convenience wrapper for calling resolve_index Job from a running job. -func ResolveIndexInfo(jobContext *engine.Job, indexName string) (*IndexInfo, error) { - job := jobContext.Eng.Job("resolve_index", indexName) - env, err := job.Stdout.AddEnv() - if err != nil { - return nil, err - } - if err := job.Run(); err != nil { - return nil, err - } - info := IndexInfo{} - if err := env.GetJson("index", &info); err != nil { - return nil, err - } - return &info, nil -} - -// GetRegistryConfig returns current registry configuration. -func (s *Service) GetRegistryConfig(job *engine.Job) error { - out := engine.Env{} - err := out.SetJson("config", s.Config) - if err != nil { - return err - } - out.WriteTo(job.Stdout) - - return nil +func (s *Service) ResolveIndex(name string) (*IndexInfo, error) { + return s.Config.NewIndexInfo(name) } From 8d1455d88b66011713da67098dc3464b897af9cd Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Tue, 31 Mar 2015 16:57:43 -0700 Subject: [PATCH 211/999] Increase timeout on TestRunOOMExitCode test I can never get it to work for me when its just 3 seconds. With this change it generates the OOM message around 17 seconds, but I increased the timeout to 30 for people with slower machines Signed-off-by: Doug Davis --- integration-cli/docker_cli_run_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 0b6e2eddb..043716cae 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3281,7 +3281,7 @@ func TestRunOOMExitCode(t *testing.T) { go func() { defer close(done) - runCmd := exec.Command(dockerBinary, "run", "-m", "4MB", "busybox", "sh", "-c", "x=a; while true; do x=$x$x; done") + runCmd := exec.Command(dockerBinary, "run", "-m", "4MB", "busybox", "sh", "-c", "x=a; while true; do x=$x$x$x$x; done") out, exitCode, _ := runCommandWithOutput(runCmd) if expected := 137; exitCode != expected { t.Fatalf("wrong exit code for OOM container: expected %d, got %d (output: %q)", expected, exitCode, out) @@ -3290,7 +3290,7 @@ func TestRunOOMExitCode(t *testing.T) { select { case <-done: - case <-time.After(3 * time.Second): + case <-time.After(30 * time.Second): t.Fatal("Timeout waiting for container to die on OOM") } From 7105f93a72db06f9c57d6eae413883fef88102d7 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Wed, 1 Apr 2015 10:35:05 +0800 Subject: [PATCH 212/999] Add some missing option to bash completion Signed-off-by: Lei Jitang --- contrib/completion/bash/docker | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 19544c4da..ad48f2886 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -220,6 +220,10 @@ _docker_docker() { _filedir -d return ;; + --log-driver) + COMPREPLY=( $( compgen -W "json-file syslog none" -- "$cur" ) ) + return + ;; --log-level|-l) COMPREPLY=( $( compgen -W "debug info warn error fatal" -- "$cur" ) ) return @@ -275,7 +279,7 @@ _docker_build() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--file -f --force-rm --help --no-cache --pull --quiet -q --rm --tag -t" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--cpu-shares -c --cpuset-cpus --file -f --force-rm --help --memory -m --memory-swap --no-cache --pull --quiet -q --rm --tag -t" -- "$cur" ) ) ;; *) local counter="$(__docker_pos_first_nonflag '--tag|-t')" @@ -469,7 +473,7 @@ _docker_images() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--all -a --filter -f --help --no-trunc --quiet -q" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--all -a --digests --filter -f --help --no-trunc --quiet -q" -- "$cur" ) ) ;; =) return @@ -1154,6 +1158,7 @@ _docker() { --insecure-registry --ip --label + --log-driver --log-level -l --mtu --pidfile -p From e15c3e36cc5559c3ec17179d8740b2841709daf5 Mon Sep 17 00:00:00 2001 From: Mabin Date: Tue, 31 Mar 2015 11:26:27 +0800 Subject: [PATCH 213/999] Fix random bug in cli events test Signed-off-by: Mabin --- integration-cli/docker_cli_events_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 3cd5edd4c..117858efb 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -311,6 +311,10 @@ func TestEventsFilterContainerID(t *testing.T) { container2 := stripTrailingCharacters(out) for _, s := range []string{container1, container2, container1[:12], container2[:12]} { + if err := waitInspect(s, "{{.State.Running}}", "false", 5); err != nil { + t.Fatalf("Failed to get container %s state, error: %s", s, err) + } + eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("container=%s", s)) out, _, err := runCommandWithOutput(eventsCmd) if err != nil { @@ -338,6 +342,10 @@ func TestEventsFilterContainerName(t *testing.T) { } for _, s := range []string{"container_1", "container_2"} { + if err := waitInspect(s, "{{.State.Running}}", "false", 5); err != nil { + t.Fatalf("Failed to get container %s state, error: %s", s, err) + } + eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("container=%s", s)) out, _, err := runCommandWithOutput(eventsCmd) if err != nil { From f5a401d3fe2e8c74e6b6a38e9073b843b16fae68 Mon Sep 17 00:00:00 2001 From: Deng Guangxing Date: Wed, 1 Apr 2015 09:58:07 +0800 Subject: [PATCH 214/999] docker info show logging driver info Signed-off-by: Deng Guangxing --- api/client/info.go | 3 +++ daemon/info.go | 1 + docs/man/docker-info.1.md | 1 + docs/sources/reference/commandline/cli.md | 1 + docs/sources/userguide/labels-custom-metadata.md | 1 + integration-cli/docker_cli_info_test.go | 2 +- 6 files changed, 8 insertions(+), 1 deletion(-) diff --git a/api/client/info.go b/api/client/info.go index 704351b3d..0f509d83f 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -57,6 +57,9 @@ func (cli *DockerCli) CmdInfo(args ...string) error { if remoteInfo.Exists("ExecutionDriver") { fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver")) } + if remoteInfo.Exists("LoggingDriver") { + fmt.Fprintf(cli.out, "Logging Driver: %s\n", remoteInfo.Get("LoggingDriver")) + } if remoteInfo.Exists("KernelVersion") { fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion")) } diff --git a/daemon/info.go b/daemon/info.go index 824647f8d..17d56eaf3 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -79,6 +79,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) error { v.SetInt("NGoroutines", runtime.NumGoroutine()) v.Set("SystemTime", time.Now().Format(time.RFC3339Nano)) v.Set("ExecutionDriver", daemon.ExecutionDriver().Name()) + v.Set("LoggingDriver", daemon.defaultLogConfig.Type) v.SetInt("NEventsListener", env.GetInt("count")) v.Set("KernelVersion", kernelVersion) v.Set("OperatingSystem", operatingSystem) diff --git a/docs/man/docker-info.1.md b/docs/man/docker-info.1.md index 346df866a..a3bbd7982 100644 --- a/docs/man/docker-info.1.md +++ b/docs/man/docker-info.1.md @@ -37,6 +37,7 @@ Here is a sample output: Root Dir: /var/lib/docker/aufs Dirs: 80 Execution Driver: native-0.2 + Logging Driver: json-file Kernel Version: 3.13.0-24-generic Operating System: Ubuntu 14.04 LTS CPUs: 1 diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index e3344991b..13ea68ec1 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1332,6 +1332,7 @@ For example: Backing Filesystem: extfs Dirs: 545 Execution Driver: native-0.2 + Logging Driver: json-file Kernel Version: 3.13.0-24-generic Operating System: Ubuntu 14.04 LTS CPUs: 1 diff --git a/docs/sources/userguide/labels-custom-metadata.md b/docs/sources/userguide/labels-custom-metadata.md index eb21cc98d..792c2f505 100644 --- a/docs/sources/userguide/labels-custom-metadata.md +++ b/docs/sources/userguide/labels-custom-metadata.md @@ -171,6 +171,7 @@ These labels appear as part of the `docker info` output for the daemon: Backing Filesystem: extfs Dirs: 697 Execution Driver: native-0.2 + Logging Driver: json-file Kernel Version: 3.13.0-32-generic Operating System: Ubuntu 14.04.1 LTS CPUs: 1 diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go index 2e8239a4b..68c24f292 100644 --- a/integration-cli/docker_cli_info_test.go +++ b/integration-cli/docker_cli_info_test.go @@ -14,7 +14,7 @@ func TestInfoEnsureSucceeds(t *testing.T) { t.Fatalf("failed to execute docker info: %s, %v", out, err) } - stringsToCheck := []string{"Containers:", "Execution Driver:", "Kernel Version:"} + stringsToCheck := []string{"Containers:", "Execution Driver:", "Logging Driver:", "Kernel Version:"} for _, linePrefix := range stringsToCheck { if !strings.Contains(out, linePrefix) { From a44451d40eb3e1b3d19a0525f70736406c66d2d1 Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Wed, 1 Apr 2015 17:08:29 +0800 Subject: [PATCH 215/999] Fix typoes in docker-run.1.md Signed-off-by: Yuan Sun --- docs/man/docker-run.1.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index bbcd93459..53d762cf6 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -101,7 +101,7 @@ the number of containers running on the system. For example, consider three containers, one has a cpu-share of 1024 and two others have a cpu-share setting of 512. When processes in all three containers attempt to use 100% of CPU, the first container would receive -50% of the total CPU time. If you add a fouth container with a cpu-share +50% of the total CPU time. If you add a fourth container with a cpu-share of 1024, the first container only gets 33% of the CPU. The remaining containers receive 16.5%, 16.5% and 33% of the CPU. @@ -239,7 +239,7 @@ system's page size (the value would be very large, that's millions of trillions) Total memory limit (memory + swap) Set `-1` to disable swap (format: , where unit = b, k, m or g). -This value should always larger than **-m**, so you should alway use this with **-m**. +This value should always larger than **-m**, so you should always use this with **-m**. **--mac-address**="" Container MAC address (e.g. 92:d0:c6:0a:29:33) From d351aef8afc5a215668248652aad52856949eaa0 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 1 Apr 2015 07:36:06 -0700 Subject: [PATCH 216/999] Remove dead code looking for non-existent err msg Closes #11985 Signed-off-by: Doug Davis --- api/client/inspect.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/api/client/inspect.go b/api/client/inspect.go index 02428ee23..0f47480b1 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -39,12 +39,6 @@ func (cli *DockerCli) CmdInspect(args ...string) error { for _, name := range cmd.Args() { obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil)) if err != nil { - if strings.Contains(err.Error(), "Too many") { - fmt.Fprintf(cli.err, "Error: %v", err) - status = 1 - continue - } - obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil)) if err != nil { if strings.Contains(err.Error(), "No such") { From 39908fc6d9a13f0d44196475ba938b6cc352fdbe Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 28 Jan 2015 18:28:48 -0800 Subject: [PATCH 217/999] Add support for more advanced ${xxx:...} syntax Just ${xxx:+...} and ${xxx:-...} for now Signed-off-by: Doug Davis --- builder/shell_parser.go | 35 +++++++++++++++++++++++- builder/words | 15 ++++++++++ docs/sources/reference/builder.md | 20 +++++++++++--- integration-cli/docker_cli_build_test.go | 32 ++++++++++++++++++++++ 4 files changed, 97 insertions(+), 5 deletions(-) diff --git a/builder/shell_parser.go b/builder/shell_parser.go index d086645eb..1ea44f64e 100644 --- a/builder/shell_parser.go +++ b/builder/shell_parser.go @@ -157,7 +157,40 @@ func (sw *shellWord) processDollar() (string, error) { sw.next() return sw.getEnv(name), nil } - return "", fmt.Errorf("Unsupported ${} substitution: %s", sw.word) + if ch == ':' { + // Special ${xx:...} format processing + // Yes it allows for recursive $'s in the ... spot + + sw.next() // skip over : + modifier := sw.next() + + word, err := sw.processStopOn('}') + if err != nil { + return "", err + } + + // Grab the current value of the variable in question so we + // can use to to determine what to do based on the modifier + newValue := sw.getEnv(name) + + switch modifier { + case '+': + if newValue != "" { + newValue = word + } + return newValue, nil + + case '-': + if newValue == "" { + newValue = word + } + return newValue, nil + + default: + return "", fmt.Errorf("Unsupported modifier (%c) in substitution: %s", modifier, sw.word) + } + } + return "", fmt.Errorf("Missing ':' in substitution: %s", sw.word) } // $xxx case name := sw.processName() diff --git a/builder/words b/builder/words index 5cac826a6..1114a7e46 100644 --- a/builder/words +++ b/builder/words @@ -30,6 +30,17 @@ he${hi} | he he${hi}xx | hexx he${PWD} | he/home he${.} | error +he${XXX:-000}xx | he000xx +he${PWD:-000}xx | he/homexx +he${XXX:-$PWD}xx | he/homexx +he${XXX:-${PWD:-yyy}}xx | he/homexx +he${XXX:-${YYY:-yyy}}xx | heyyyxx +he${XXX:YYY} | error +he${XXX:+${PWD}}xx | hexx +he${PWD:+${XXX}}xx | hexx +he${PWD:+${SHELL}}xx | hebashxx +he${XXX:+000}xx | hexx +he${PWD:+000}xx | he000xx 'he${XX}' | he${XX} "he${PWD}" | he/home "he'$PWD'" | he'/home' @@ -41,3 +52,7 @@ he\$PWD | he$PWD "he\$PWD" | he$PWD 'he\$PWD' | he\$PWD he${PWD | error +he${PWD:=000}xx | error +he${PWD:+${PWD}:}xx | he/home:xx +he${XXX:-\$PWD:}xx | he$PWD:xx +he${XXX:-\${PWD}z}xx | he${PWDz}xx diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index adf42ae76..d837541aa 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -113,18 +113,30 @@ images. > replacement at the time. After 1.3 this behavior will be preserved and > canonical. -Environment variables (declared with [the `ENV` statement](#env)) can also be used in -certain instructions as variables to be interpreted by the `Dockerfile`. Escapes -are also handled for including variable-like syntax into a statement literally. +Environment variables (declared with [the `ENV` statement](#env)) can also be +used in certain instructions as variables to be interpreted by the +`Dockerfile`. Escapes are also handled for including variable-like syntax +into a statement literally. Environment variables are notated in the `Dockerfile` either with `$variable_name` or `${variable_name}`. They are treated equivalently and the brace syntax is typically used to address issues with variable names with no whitespace, like `${foo}_bar`. +The `${variable_name}` syntax also supports a few of the standard `bash` +modifiers as specified below: + +* `${variable:-word}` indicates that if `variable` is set then the result + will be that value. If `variable` is not set then `word` will be the result. +* `${variable:+word}` indiates that if `variable` is set then `word` will be + the result, otherwise the result is the empty string. + +In all cases, `word` can be any string, including additional environment +variables. + Escaping is possible by adding a `\` before the variable: `\$foo` or `\${foo}`, for example, will translate to `$foo` and `${foo}` literals respectively. - + Example (parsed representation is displayed after the `#`): FROM busybox diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index cec62419b..e907afb5f 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -214,13 +214,19 @@ func TestBuildEnvironmentReplacementAddCopy(t *testing.T) { ENV baz foo ENV quux bar ENV dot . + ENV fee fff + ENV gee ggg ADD ${baz} ${dot} COPY ${quux} ${dot} + ADD ${zzz:-${fee}} ${dot} + COPY ${zzz:-${gee}} ${dot} `, map[string]string{ "foo": "test1", "bar": "test2", + "fff": "test3", + "ggg": "test4", }) if err != nil { @@ -286,6 +292,11 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) { if parts[1] != "zzz" { t.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) } + } else if strings.HasPrefix(parts[0], "env") { + envCount++ + if parts[1] != "foo" { + t.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) + } } } @@ -4069,6 +4080,27 @@ RUN [ "$abc" = "'foo'" ] ENV abc \"foo\" RUN [ "$abc" = '"foo"' ] +ENV abc=ABC +RUN [ "$abc" = "ABC" ] +ENV def=${abc:-DEF} +RUN [ "$def" = "ABC" ] +ENV def=${ccc:-DEF} +RUN [ "$def" = "DEF" ] +ENV def=${ccc:-${def}xx} +RUN [ "$def" = "DEFxx" ] +ENV def=${def:+ALT} +RUN [ "$def" = "ALT" ] +ENV def=${def:+${abc}:} +RUN [ "$def" = "ABC:" ] +ENV def=${ccc:-\$abc:} +RUN [ "$def" = '$abc:' ] +ENV def=${ccc:-\${abc}:} +RUN [ "$def" = '${abc:}' ] +ENV mypath=${mypath:+$mypath:}/home +RUN [ "$mypath" = '/home' ] +ENV mypath=${mypath:+$mypath:}/away +RUN [ "$mypath" = '/home:/away' ] + ENV e1=bar ENV e2=$e1 ENV e3=$e11 From 8f7ac20bacfa6964ea830bfc6dfd4fc8c66bc9c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Martins?= Date: Sun, 22 Mar 2015 19:40:16 +0000 Subject: [PATCH 218/999] Fixes docker events since beginning of unix time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes issue #11555 Applied a workaround to check if since and until flags are valid or not. Signed-off-by: André Martins --- events/events.go | 4 +-- integration-cli/docker_cli_events_test.go | 35 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/events/events.go b/events/events.go index 6940bafbb..93ea9a039 100644 --- a/events/events.go +++ b/events/events.go @@ -59,7 +59,7 @@ func (e *Events) Get(job *engine.Job) error { } // If no until, disable timeout - if until == 0 { + if job.Getenv("until") == "" { timeout.Stop() } @@ -70,7 +70,7 @@ func (e *Events) Get(job *engine.Job) error { job.Stdout.Write(nil) // Resend every event in the [since, until] time interval. - if since != 0 { + if job.Getenv("since") != "" { if err := e.writeCurrent(job, since, until, eventFilters); err != nil { return err } diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 3cd5edd4c..bba8acfc7 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -112,6 +112,41 @@ func TestEventsContainerEvents(t *testing.T) { logDone("events - container create, start, die, destroy is logged") } +func TestEventsContainerEventsSinceUnixEpoch(t *testing.T) { + dockerCmd(t, "run", "--rm", "busybox", "true") + timeBeginning := time.Unix(0, 0).Format(time.RFC3339Nano) + timeBeginning = strings.Replace(timeBeginning, "Z", ".000000000Z", -1) + eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since='%s'", timeBeginning), + fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + out, exitCode, err := runCommandWithOutput(eventsCmd) + if exitCode != 0 || err != nil { + t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) + } + events := strings.Split(out, "\n") + events = events[:len(events)-1] + if len(events) < 4 { + t.Fatalf("Missing expected event") + } + createEvent := strings.Fields(events[len(events)-4]) + startEvent := strings.Fields(events[len(events)-3]) + dieEvent := strings.Fields(events[len(events)-2]) + destroyEvent := strings.Fields(events[len(events)-1]) + if createEvent[len(createEvent)-1] != "create" { + t.Fatalf("event should be create, not %#v", createEvent) + } + if startEvent[len(startEvent)-1] != "start" { + t.Fatalf("event should be start, not %#v", startEvent) + } + if dieEvent[len(dieEvent)-1] != "die" { + t.Fatalf("event should be die, not %#v", dieEvent) + } + if destroyEvent[len(destroyEvent)-1] != "destroy" { + t.Fatalf("event should be destroy, not %#v", destroyEvent) + } + + logDone("events - container create, start, die, destroy since Unix Epoch time") +} + func TestEventsImageUntagDelete(t *testing.T) { name := "testimageevents" defer deleteImages(name) From 6e46204ab80b6df52c168fc06873280fc051d768 Mon Sep 17 00:00:00 2001 From: Jay Date: Mon, 30 Mar 2015 23:19:13 +0700 Subject: [PATCH 219/999] Added information about accessing host directory on Windows. Signed-off-by: Tibor Vass --- docs/sources/userguide/dockerimages.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md index 3fe6aa28f..f97231506 100644 --- a/docs/sources/userguide/dockerimages.md +++ b/docs/sources/userguide/dockerimages.md @@ -242,6 +242,9 @@ Let's create a directory and a `Dockerfile` first. $ cd sinatra $ touch Dockerfile +If you are using Boot2Docker on Windows, you may access your host +directory by `cd` to `/c/Users/your_user_name`. + Each instruction creates a new layer of the image. Let's look at a simple example now for building our own Sinatra image for our development team. From b58c4b8e689121ab4869472405a3db89094ac4de Mon Sep 17 00:00:00 2001 From: Simon Leinen Date: Wed, 1 Apr 2015 21:45:37 +0200 Subject: [PATCH 220/999] Make explanation consistent with command To reduce confusion of the reader. Fixes a likely cut & paste error. Signed-off-by: Simon Leinen --- docs/sources/installation/ubuntulinux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index a7931c1ff..eea2e58f7 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -283,7 +283,7 @@ To specify a DNS server for use by Docker: **Or, as an alternative to the previous procedure,** disable `dnsmasq` in NetworkManager (this might slow your network). -1. Open the `/etc/default/docker` file for editing. +1. Open the `/etc/NetworkManager/NetworkManager.conf` file for editing. $ sudo nano /etc/NetworkManager/NetworkManager.conf From 81f9b72c648142745c63ab0ba2b495646dccd88d Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Wed, 1 Apr 2015 13:35:38 -0700 Subject: [PATCH 221/999] Bump API version and docs to v1.19 Signed-off-by: Alexander Morozov --- api/common.go | 2 +- docs/mkdocs.yml | 3 +- .../reference/api/docker_remote_api.md | 15 +- .../reference/api/docker_remote_api_v1.19.md | 2065 +++++++++++++++++ 4 files changed, 2080 insertions(+), 5 deletions(-) create mode 100644 docs/sources/reference/api/docker_remote_api_v1.19.md diff --git a/api/common.go b/api/common.go index 8cffa086e..8251fdcf8 100644 --- a/api/common.go +++ b/api/common.go @@ -16,7 +16,7 @@ import ( // Common constants for daemon and client. const ( - APIVERSION version.Version = "1.18" // Current REST API version + APIVERSION version.Version = "1.19" // Current REST API version DEFAULTHTTPHOST = "127.0.0.1" // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080 DEFAULTUNIXSOCKET = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket DefaultDockerfileName string = "Dockerfile" // Default filename with Docker commands, read by docker build diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 74b17a32f..b5b30d72d 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -139,9 +139,10 @@ pages: - ['reference/api/hub_registry_spec.md', 'Reference', 'Docker Hub and Registry Spec'] #- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0'] - ['reference/api/docker_remote_api.md', 'Reference', 'Docker Remote API'] +- ['reference/api/docker_remote_api_v1.19.md', 'Reference', 'Docker Remote API v1.19'] - ['reference/api/docker_remote_api_v1.18.md', 'Reference', 'Docker Remote API v1.18'] - ['reference/api/docker_remote_api_v1.17.md', 'Reference', 'Docker Remote API v1.17'] -- ['reference/api/docker_remote_api_v1.16.md', 'Reference', 'Docker Remote API v1.16'] +- ['reference/api/docker_remote_api_v1.16.md', '**HIDDEN**'] - ['reference/api/docker_remote_api_v1.15.md', '**HIDDEN**'] - ['reference/api/docker_remote_api_v1.14.md', '**HIDDEN**'] - ['reference/api/docker_remote_api_v1.13.md', '**HIDDEN**'] diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index 12f0a71fc..7772a651a 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -30,13 +30,22 @@ page_keywords: API, Docker, rcli, REST, documentation Client applications need to take this into account to ensure they will not break when talking to newer Docker daemons. -The current version of the API is v1.18 +The current version of the API is v1.19 Calling `/info` is the same as calling -`/v1.18/info`. +`/v1.19/info`. You can still call an old version of the API using -`/v1.17/info`. +`/v1.18/info`. + +## v1.19 + +### Full Documentation + +[*Docker Remote API v1.19*](/reference/api/docker_remote_api_v1.19/) + +### What's new + ## v1.18 diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md new file mode 100644 index 000000000..a98206cbc --- /dev/null +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -0,0 +1,2065 @@ +page_title: Remote API v1.19 +page_description: API Documentation for Docker +page_keywords: API, Docker, rcli, REST, documentation + +# Docker Remote API v1.19 + +## 1. Brief introduction + + - The Remote API has replaced `rcli`. + - The daemon listens on `unix:///var/run/docker.sock` but you can + [Bind Docker to another host/port or a Unix socket]( + /articles/basics/#bind-docker-to-another-hostport-or-a-unix-socket). + - The API tends to be REST, but for some complex commands, like `attach` + or `pull`, the HTTP connection is hijacked to transport `STDOUT`, + `STDIN` and `STDERR`. + +# 2. Endpoints + +## 2.1 Containers + +### List containers + +`GET /containers/json` + +List containers + +**Example request**: + + GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id": "8dfafdbc3a40", + "Image": "ubuntu:latest", + "Command": "echo 1", + "Created": 1367854155, + "Status": "Exit 0", + "Ports": [{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], + "SizeRw": 12288, + "SizeRootFs": 0 + }, + { + "Id": "9cd87474be90", + "Image": "ubuntu:latest", + "Command": "echo 222222", + "Created": 1367854155, + "Status": "Exit 0", + "Ports": [], + "SizeRw": 12288, + "SizeRootFs": 0 + }, + { + "Id": "3176a2479c92", + "Image": "ubuntu:latest", + "Command": "echo 3333333333333333", + "Created": 1367854154, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "4cb07b47f9fb", + "Image": "ubuntu:latest", + "Command": "echo 444444444444444444444444444444444", + "Created": 1367854152, + "Status": "Exit 0", + "Ports": [], + "SizeRw": 12288, + "SizeRootFs": 0 + } + ] + +Query Parameters: + +- **all** – 1/True/true or 0/False/false, Show all containers. + Only running containers are shown by default (i.e., this defaults to false) +- **limit** – Show `limit` last created + containers, include non-running ones. +- **since** – Show only containers created since Id, include + non-running ones. +- **before** – Show only containers created before Id, include + non-running ones. +- **size** – 1/True/true or 0/False/false, Show the containers + sizes +- **filters** - a json encoded value of the filters (a map[string][]string) to process on the containers list. Available filters: + - exited=<int> -- containers with exit code of <int> + - status=(restarting|running|paused|exited) + +Status Codes: + +- **200** – no error +- **400** – bad parameter +- **500** – server error + +### Create a container + +`POST /containers/create` + +Create a container + +**Example request**: + + POST /containers/create HTTP/1.1 + Content-Type: application/json + + { + "Hostname": "", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Entrypoint": "", + "Image": "ubuntu", + "Labels": { + "com.example.vendor": "Acme", + "com.example.license": "GPL", + "com.example.version": "1.0" + }, + "Volumes": { + "/tmp": {} + }, + "WorkingDir": "", + "NetworkDisabled": false, + "MacAddress": "12:34:56:78:9a:bc", + "ExposedPorts": { + "22/tcp": {} + }, + "HostConfig": { + "Binds": ["/tmp:/tmp"], + "Links": ["redis3:redis"], + "LxcConf": {"lxc.utsname":"docker"}, + "Memory": 0, + "MemorySwap": 0, + "CpuShares": 512, + "CpusetCpus": "0,1", + "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts": false, + "Privileged": false, + "ReadonlyRootfs": false, + "Dns": ["8.8.8.8"], + "DnsSearch": [""], + "ExtraHosts": null, + "VolumesFrom": ["parent", "other:ro"], + "CapAdd": ["NET_ADMIN"], + "CapDrop": ["MKNOD"], + "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, + "NetworkMode": "bridge", + "Devices": [], + "Ulimits": [{}], + "LogConfig": { "Type": "json-file", Config: {} }, + "SecurityOpt": [""], + "CgroupParent": "" + } + } + +**Example response**: + + HTTP/1.1 201 Created + Content-Type: application/json + + { + "Id":"e90e34656806" + "Warnings":[] + } + +Json Parameters: + +- **Hostname** - A string value containing the desired hostname to use for the + container. +- **Domainname** - A string value containing the desired domain name to use + for the container. +- **User** - A string value containg the user to use inside the container. +- **Memory** - Memory limit in bytes. +- **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap, + always use this with `memory`, and make the value larger than `memory`. +- **CpuShares** - An integer value containing the CPU Shares for container + (ie. the relative weight vs othercontainers). +- **Cpuset** - The same as CpusetCpus, but deprecated, please don't use. +- **CpusetCpus** - String value containg the cgroups CpusetCpus to use. +- **AttachStdin** - Boolean value, attaches to stdin. +- **AttachStdout** - Boolean value, attaches to stdout. +- **AttachStderr** - Boolean value, attaches to stderr. +- **Tty** - Boolean value, Attach standard streams to a tty, including stdin if it is not closed. +- **OpenStdin** - Boolean value, opens stdin, +- **StdinOnce** - Boolean value, close stdin after the 1 attached client disconnects. +- **Env** - A list of environment variables in the form of `VAR=value` +- **Labels** - Adds a map of labels that to a container. To specify a map: `{"key":"value"[,"key2":"value2"]}` +- **Cmd** - Command to run specified as a string or an array of strings. +- **Entrypoint** - Set the entrypoint for the container a a string or an array + of strings +- **Image** - String value containing the image name to use for the container +- **Volumes** – An object mapping mountpoint paths (strings) inside the + container to empty objects. +- **WorkingDir** - A string value containing the working dir for commands to + run in. +- **NetworkDisabled** - Boolean value, when true disables neworking for the + container +- **ExposedPorts** - An object mapping ports to an empty object in the form of: + `"ExposedPorts": { "/: {}" }` +- **HostConfig** + - **Binds** – A list of volume bindings for this container. Each volume + binding is a string of the form `container_path` (to create a new + volume for the container), `host_path:container_path` (to bind-mount + a host path into the container), or `host_path:container_path:ro` + (to make the bind-mount read-only inside the container). + - **Links** - A list of links for the container. Each link entry should be of + of the form "container_name:alias". + - **LxcConf** - LXC specific configurations. These configurations will only + work when using the `lxc` execution driver. + - **PortBindings** - A map of exposed container ports and the host port they + should map to. It should be specified in the form + `{ /: [{ "HostPort": "" }] }` + Take note that `port` is specified as a string and not an integer value. + - **PublishAllPorts** - Allocates a random host port for all of a container's + exposed ports. Specified as a boolean value. + - **Privileged** - Gives the container full access to the host. Specified as + a boolean value. + - **ReadonlyRootfs** - Mount the container's root filesystem as read only. + Specified as a boolean value. + - **Dns** - A list of dns servers for the container to use. + - **DnsSearch** - A list of DNS search domains + - **ExtraHosts** - A list of hostnames/IP mappings to be added to the + container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. + - **VolumesFrom** - A list of volumes to inherit from another container. + Specified in the form `[:]` + - **CapAdd** - A list of kernel capabilties to add to the container. + - **Capdrop** - A list of kernel capabilties to drop from the container. + - **RestartPolicy** – The behavior to apply when the container exits. The + value is an object with a `Name` property of either `"always"` to + always restart or `"on-failure"` to restart only when the container + exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` + controls the number of times to retry before giving up. + The default is not to restart. (optional) + An ever increasing delay (double the previous delay, starting at 100mS) + is added before each restart to prevent flooding the server. + - **NetworkMode** - Sets the networking mode for the container. Supported + values are: `bridge`, `host`, and `container:` + - **Devices** - A list of devices to add to the container specified in the + form + `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` + - **Ulimits** - A list of ulimits to be set in the container, specified as + `{ "Name": , "Soft": , "Hard": }`, for example: + `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` + - **SecurityOpt**: A list of string values to customize labels for MLS + systems, such as SELinux. + - **LogConfig** - Logging configuration to container, format + `{ "Type": "", "Config": {"key1": "val1"}} + Available types: `json-file`, `syslog`, `none`. + `json-file` logging driver. + - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. + +Query Parameters: + +- **name** – Assign the specified name to the container. Must + match `/?[a-zA-Z0-9_-]+`. + +Status Codes: + +- **201** – no error +- **404** – no such container +- **406** – impossible to attach (container not running) +- **500** – server error + +### Inspect a container + +`GET /containers/(id)/json` + +Return low-level information on the container `id` + + +**Example request**: + + GET /containers/4fa6e0f0c678/json HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "AppArmorProfile": "", + "Args": [ + "-c", + "exit 9" + ], + "Config": { + "AttachStderr": true, + "AttachStdin": false, + "AttachStdout": true, + "Cmd": [ + "/bin/sh", + "-c", + "exit 9" + ], + "Domainname": "", + "Entrypoint": null, + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "ExposedPorts": null, + "Hostname": "ba033ac44011", + "Image": "ubuntu", + "Labels": { + "com.example.vendor": "Acme", + "com.example.license": "GPL", + "com.example.version": "1.0" + }, + "MacAddress": "", + "NetworkDisabled": false, + "OnBuild": null, + "OpenStdin": false, + "PortSpecs": null, + "StdinOnce": false, + "Tty": false, + "User": "", + "Volumes": null, + "WorkingDir": "" + }, + "Created": "2015-01-06T15:47:31.485331387Z", + "Driver": "devicemapper", + "ExecDriver": "native-0.2", + "ExecIDs": null, + "HostConfig": { + "Binds": null, + "CapAdd": null, + "CapDrop": null, + "ContainerIDFile": "", + "CpusetCpus": "", + "CpuShares": 0, + "Devices": [], + "Dns": null, + "DnsSearch": null, + "ExtraHosts": null, + "IpcMode": "", + "Links": null, + "LxcConf": [], + "Memory": 0, + "MemorySwap": 0, + "NetworkMode": "bridge", + "PortBindings": {}, + "Privileged": false, + "ReadonlyRootfs": false, + "PublishAllPorts": false, + "RestartPolicy": { + "MaximumRetryCount": 2, + "Name": "on-failure" + }, + "LogConfig": { "Type": "json-file", Config: {} }, + "SecurityOpt": null, + "VolumesFrom": null, + "Ulimits": [{}] + }, + "HostnamePath": "/var/lib/docker/containers/ba033ac4401106a3b513bc9d639eee123ad78ca3616b921167cd74b20e25ed39/hostname", + "HostsPath": "/var/lib/docker/containers/ba033ac4401106a3b513bc9d639eee123ad78ca3616b921167cd74b20e25ed39/hosts", + "LogPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b-json.log", + "Id": "ba033ac4401106a3b513bc9d639eee123ad78ca3616b921167cd74b20e25ed39", + "Image": "04c5d3b7b0656168630d3ba35d8889bd0e9caafcaeb3004d2bfbc47e7c5d35d2", + "MountLabel": "", + "Name": "/boring_euclid", + "NetworkSettings": { + "Bridge": "", + "Gateway": "", + "IPAddress": "", + "IPPrefixLen": 0, + "MacAddress": "", + "PortMapping": null, + "Ports": null + }, + "Path": "/bin/sh", + "ProcessLabel": "", + "ResolvConfPath": "/var/lib/docker/containers/ba033ac4401106a3b513bc9d639eee123ad78ca3616b921167cd74b20e25ed39/resolv.conf", + "RestartCount": 1, + "State": { + "Error": "", + "ExitCode": 9, + "FinishedAt": "2015-01-06T15:47:32.080254511Z", + "OOMKilled": false, + "Paused": false, + "Pid": 0, + "Restarting": false, + "Running": false, + "StartedAt": "2015-01-06T15:47:32.072697474Z" + }, + "Volumes": {}, + "VolumesRW": {} + } + +Status Codes: + +- **200** – no error +- **404** – no such container +- **500** – server error + +### List processes running inside a container + +`GET /containers/(id)/top` + +List processes running inside the container `id` + +**Example request**: + + GET /containers/4fa6e0f0c678/top HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Titles": [ + "USER", + "PID", + "%CPU", + "%MEM", + "VSZ", + "RSS", + "TTY", + "STAT", + "START", + "TIME", + "COMMAND" + ], + "Processes": [ + ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], + ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] + ] + } + +Query Parameters: + +- **ps_args** – ps arguments to use (e.g., aux) + +Status Codes: + +- **200** – no error +- **404** – no such container +- **500** – server error + +### Get container logs + +`GET /containers/(id)/logs` + +Get stdout and stderr logs from the container ``id`` + +> **Note**: +> This endpoint works only for containers with `json-file` logging driver. + +**Example request**: + + GET /containers/4fa6e0f0c678/logs?stderr=1&stdout=1×tamps=1&follow=1&tail=10 HTTP/1.1 + +**Example response**: + + HTTP/1.1 101 UPGRADED + Content-Type: application/vnd.docker.raw-stream + Connection: Upgrade + Upgrade: tcp + + {{ STREAM }} + +Query Parameters: + +- **follow** – 1/True/true or 0/False/false, return stream. Default false +- **stdout** – 1/True/true or 0/False/false, show stdout log. Default false +- **stderr** – 1/True/true or 0/False/false, show stderr log. Default false +- **timestamps** – 1/True/true or 0/False/false, print timestamps for + every log line. Default false +- **tail** – Output specified number of lines at the end of logs: `all` or ``. Default all + +Status Codes: + +- **101** – no error, hints proxy about hijacking +- **200** – no error, no upgrade header found +- **404** – no such container +- **500** – server error + +### Inspect changes on a container's filesystem + +`GET /containers/(id)/changes` + +Inspect changes on container `id`'s filesystem + +**Example request**: + + GET /containers/4fa6e0f0c678/changes HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Path": "/dev", + "Kind": 0 + }, + { + "Path": "/dev/kmsg", + "Kind": 1 + }, + { + "Path": "/test", + "Kind": 1 + } + ] + +Values for `Kind`: + +- `0`: Modify +- `1`: Add +- `2`: Delete + +Status Codes: + +- **200** – no error +- **404** – no such container +- **500** – server error + +### Export a container + +`GET /containers/(id)/export` + +Export the contents of container `id` + +**Example request**: + + GET /containers/4fa6e0f0c678/export HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ TAR STREAM }} + +Status Codes: + +- **200** – no error +- **404** – no such container +- **500** – server error + +### Get container stats based on resource usage + +`GET /containers/(id)/stats` + +This endpoint returns a live stream of a container's resource usage statistics. + +> **Note**: this functionality currently only works when using the *libcontainer* exec-driver. + +**Example request**: + + GET /containers/redis1/stats HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "read" : "2015-01-08T22:57:31.547920715Z", + "network" : { + "rx_dropped" : 0, + "rx_bytes" : 648, + "rx_errors" : 0, + "tx_packets" : 8, + "tx_dropped" : 0, + "rx_packets" : 8, + "tx_errors" : 0, + "tx_bytes" : 648 + }, + "memory_stats" : { + "stats" : { + "total_pgmajfault" : 0, + "cache" : 0, + "mapped_file" : 0, + "total_inactive_file" : 0, + "pgpgout" : 414, + "rss" : 6537216, + "total_mapped_file" : 0, + "writeback" : 0, + "unevictable" : 0, + "pgpgin" : 477, + "total_unevictable" : 0, + "pgmajfault" : 0, + "total_rss" : 6537216, + "total_rss_huge" : 6291456, + "total_writeback" : 0, + "total_inactive_anon" : 0, + "rss_huge" : 6291456, + "hierarchical_memory_limit" : 67108864, + "total_pgfault" : 964, + "total_active_file" : 0, + "active_anon" : 6537216, + "total_active_anon" : 6537216, + "total_pgpgout" : 414, + "total_cache" : 0, + "inactive_anon" : 0, + "active_file" : 0, + "pgfault" : 964, + "inactive_file" : 0, + "total_pgpgin" : 477 + }, + "max_usage" : 6651904, + "usage" : 6537216, + "failcnt" : 0, + "limit" : 67108864 + }, + "blkio_stats" : {}, + "cpu_stats" : { + "cpu_usage" : { + "percpu_usage" : [ + 16970827, + 1839451, + 7107380, + 10571290 + ], + "usage_in_usermode" : 10000000, + "total_usage" : 36488948, + "usage_in_kernelmode" : 20000000 + }, + "system_cpu_usage" : 20091722000000000, + "throttling_data" : {} + } + } + +Status Codes: + +- **200** – no error +- **404** – no such container +- **500** – server error + +### Resize a container TTY + +`POST /containers/(id)/resize?h=&w=` + +Resize the TTY for container with `id`. The container must be restarted for the resize to take effect. + +**Example request**: + + POST /containers/4fa6e0f0c678/resize?h=40&w=80 HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Length: 0 + Content-Type: text/plain; charset=utf-8 + +Status Codes: + +- **200** – no error +- **404** – No such container +- **500** – Cannot resize container + +### Start a container + +`POST /containers/(id)/start` + +Start the container `id` + +**Example request**: + + POST /containers/(id)/start HTTP/1.1 + Content-Type: application/json + +**Example response**: + + HTTP/1.1 204 No Content + +Json Parameters: + +Status Codes: + +- **204** – no error +- **304** – container already started +- **404** – no such container +- **500** – server error + +### Stop a container + +`POST /containers/(id)/stop` + +Stop the container `id` + +**Example request**: + + POST /containers/e90e34656806/stop?t=5 HTTP/1.1 + +**Example response**: + + HTTP/1.1 204 No Content + +Query Parameters: + +- **t** – number of seconds to wait before killing the container + +Status Codes: + +- **204** – no error +- **304** – container already stopped +- **404** – no such container +- **500** – server error + +### Restart a container + +`POST /containers/(id)/restart` + +Restart the container `id` + +**Example request**: + + POST /containers/e90e34656806/restart?t=5 HTTP/1.1 + +**Example response**: + + HTTP/1.1 204 No Content + +Query Parameters: + +- **t** – number of seconds to wait before killing the container + +Status Codes: + +- **204** – no error +- **404** – no such container +- **500** – server error + +### Kill a container + +`POST /containers/(id)/kill` + +Kill the container `id` + +**Example request**: + + POST /containers/e90e34656806/kill HTTP/1.1 + +**Example response**: + + HTTP/1.1 204 No Content + +Query Parameters + +- **signal** - Signal to send to the container: integer or string like "SIGINT". + When not set, SIGKILL is assumed and the call will waits for the container to exit. + +Status Codes: + +- **204** – no error +- **404** – no such container +- **500** – server error + +### Rename a container + +`POST /containers/(id)/rename` + +Rename the container `id` to a `new_name` + +**Example request**: + + POST /containers/e90e34656806/rename?name=new_name HTTP/1.1 + +**Example response**: + + HTTP/1.1 204 No Content + +Query Parameters: + +- **name** – new name for the container + +Status Codes: + +- **204** – no error +- **404** – no such container +- **409** - conflict name already assigned +- **500** – server error + +### Pause a container + +`POST /containers/(id)/pause` + +Pause the container `id` + +**Example request**: + + POST /containers/e90e34656806/pause HTTP/1.1 + +**Example response**: + + HTTP/1.1 204 No Content + +Status Codes: + +- **204** – no error +- **404** – no such container +- **500** – server error + +### Unpause a container + +`POST /containers/(id)/unpause` + +Unpause the container `id` + +**Example request**: + + POST /containers/e90e34656806/unpause HTTP/1.1 + +**Example response**: + + HTTP/1.1 204 No Content + +Status Codes: + +- **204** – no error +- **404** – no such container +- **500** – server error + +### Attach to a container + +`POST /containers/(id)/attach` + +Attach to the container `id` + +**Example request**: + + POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 + +**Example response**: + + HTTP/1.1 101 UPGRADED + Content-Type: application/vnd.docker.raw-stream + Connection: Upgrade + Upgrade: tcp + + {{ STREAM }} + +Query Parameters: + +- **logs** – 1/True/true or 0/False/false, return logs. Default false +- **stream** – 1/True/true or 0/False/false, return stream. + Default false +- **stdin** – 1/True/true or 0/False/false, if stream=true, attach + to stdin. Default false +- **stdout** – 1/True/true or 0/False/false, if logs=true, return + stdout log, if stream=true, attach to stdout. Default false +- **stderr** – 1/True/true or 0/False/false, if logs=true, return + stderr log, if stream=true, attach to stderr. Default false + +Status Codes: + +- **101** – no error, hints proxy about hijacking +- **200** – no error, no upgrade header found +- **400** – bad parameter +- **404** – no such container +- **500** – server error + + **Stream details**: + + When using the TTY setting is enabled in + [`POST /containers/create` + ](/reference/api/docker_remote_api_v1.9/#create-a-container "POST /containers/create"), + the stream is the raw data from the process PTY and client's stdin. + When the TTY is disabled, then the stream is multiplexed to separate + stdout and stderr. + + The format is a **Header** and a **Payload** (frame). + + **HEADER** + + The header will contain the information on which stream write the + stream (stdout or stderr). It also contain the size of the + associated frame encoded on the last 4 bytes (uint32). + + It is encoded on the first 8 bytes like this: + + header := [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4} + + `STREAM_TYPE` can be: + +- 0: stdin (will be written on stdout) +- 1: stdout +- 2: stderr + + `SIZE1, SIZE2, SIZE3, SIZE4` are the 4 bytes of + the uint32 size encoded as big endian. + + **PAYLOAD** + + The payload is the raw stream. + + **IMPLEMENTATION** + + The simplest way to implement the Attach protocol is the following: + + 1. Read 8 bytes + 2. chose stdout or stderr depending on the first byte + 3. Extract the frame size from the last 4 byets + 4. Read the extracted size and output it on the correct output + 5. Goto 1 + +### Attach to a container (websocket) + +`GET /containers/(id)/attach/ws` + +Attach to the container `id` via websocket + +Implements websocket protocol handshake according to [RFC 6455](http://tools.ietf.org/html/rfc6455) + +**Example request** + + GET /containers/e90e34656806/attach/ws?logs=0&stream=1&stdin=1&stdout=1&stderr=1 HTTP/1.1 + +**Example response** + + {{ STREAM }} + +Query Parameters: + +- **logs** – 1/True/true or 0/False/false, return logs. Default false +- **stream** – 1/True/true or 0/False/false, return stream. + Default false +- **stdin** – 1/True/true or 0/False/false, if stream=true, attach + to stdin. Default false +- **stdout** – 1/True/true or 0/False/false, if logs=true, return + stdout log, if stream=true, attach to stdout. Default false +- **stderr** – 1/True/true or 0/False/false, if logs=true, return + stderr log, if stream=true, attach to stderr. Default false + +Status Codes: + +- **200** – no error +- **400** – bad parameter +- **404** – no such container +- **500** – server error + +### Wait a container + +`POST /containers/(id)/wait` + +Block until container `id` stops, then returns the exit code + +**Example request**: + + POST /containers/16253994b7c4/wait HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + {"StatusCode": 0} + +Status Codes: + +- **200** – no error +- **404** – no such container +- **500** – server error + +### Remove a container + +`DELETE /containers/(id)` + +Remove the container `id` from the filesystem + +**Example request**: + + DELETE /containers/16253994b7c4?v=1 HTTP/1.1 + +**Example response**: + + HTTP/1.1 204 No Content + +Query Parameters: + +- **v** – 1/True/true or 0/False/false, Remove the volumes + associated to the container. Default false +- **force** - 1/True/true or 0/False/false, Kill then remove the container. + Default false + +Status Codes: + +- **204** – no error +- **400** – bad parameter +- **404** – no such container +- **500** – server error + +### Copy files or folders from a container + +`POST /containers/(id)/copy` + +Copy files or folders of container `id` + +**Example request**: + + POST /containers/4fa6e0f0c678/copy HTTP/1.1 + Content-Type: application/json + + { + "Resource": "test.txt" + } + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/x-tar + + {{ TAR STREAM }} + +Status Codes: + +- **200** – no error +- **404** – no such container +- **500** – server error + +## 2.2 Images + +### List Images + +`GET /images/json` + +**Example request**: + + GET /images/json?all=0 HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "RepoTags": [ + "ubuntu:12.04", + "ubuntu:precise", + "ubuntu:latest" + ], + "Id": "8dbd9e392a964056420e5d58ca5cc376ef18e2de93b5cc90e868a1bbc8318c1c", + "Created": 1365714795, + "Size": 131506275, + "VirtualSize": 131506275 + }, + { + "RepoTags": [ + "ubuntu:12.10", + "ubuntu:quantal" + ], + "ParentId": "27cf784147099545", + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Created": 1364102658, + "Size": 24653, + "VirtualSize": 180116135 + } + ] + +**Example request, with digest information**: + + GET /images/json?digests=1 HTTP/1.1 + +**Example response, with digest information**: + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Created": 1420064636, + "Id": "4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125", + "ParentId": "ea13149945cb6b1e746bf28032f02e9b5a793523481a0a18645fc77ad53c4ea2", + "RepoDigests": [ + "localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf" + ], + "RepoTags": [ + "localhost:5000/test/busybox:latest", + "playdate:latest" + ], + "Size": 0, + "VirtualSize": 2429728 + } + ] + +The response shows a single image `Id` associated with two repositories +(`RepoTags`): `localhost:5000/test/busybox`: and `playdate`. A caller can use +either of the `RepoTags` values `localhost:5000/test/busybox:latest` or +`playdate:latest` to reference the image. + +You can also use `RepoDigests` values to reference an image. In this response, +the array has only one reference and that is to the +`localhost:5000/test/busybox` repository; the `playdate` repository has no +digest. You can reference this digest using the value: +`localhost:5000/test/busybox@sha256:cbbf2f9a99b47fc460d...` + +See the `docker run` and `docker build` commands for examples of digest and tag +references on the command line. + +Query Parameters: + +- **all** – 1/True/true or 0/False/false, default false +- **filters** – a json encoded value of the filters (a map[string][]string) to process on the images list. Available filters: + - dangling=true + +### Build image from a Dockerfile + +`POST /build` + +Build an image from a Dockerfile + +**Example request**: + + POST /build HTTP/1.1 + + {{ TAR STREAM }} + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + {"stream": "Step 1..."} + {"stream": "..."} + {"error": "Error...", "errorDetail": {"code": 123, "message": "Error..."}} + +The input stream must be a tar archive compressed with one of the +following algorithms: identity (no compression), gzip, bzip2, xz. + +The archive must include a build instructions file, typically called +`Dockerfile` at the root of the archive. The `dockerfile` parameter may be +used to specify a different build instructions file by having its value be +the path to the alternate build instructions file to use. + +The archive may include any number of other files, +which will be accessible in the build context (See the [*ADD build +command*](/reference/builder/#dockerbuilder)). + +The build will also be canceled if the client drops the connection by quitting +or being killed. + +Query Parameters: + +- **dockerfile** - path within the build context to the Dockerfile. This is + ignored if `remote` is specified and points to an individual filename. +- **t** – repository name (and optionally a tag) to be applied to + the resulting image in case of success +- **remote** – A Git repository URI or HTTP/HTTPS URI build source. If the + URI specifies a filename, the file's contents are placed into a file + called `Dockerfile`. +- **q** – suppress verbose build output +- **nocache** – do not use the cache when building the image +- **pull** - attempt to pull the image even if an older image exists locally +- **rm** - remove intermediate containers after a successful build (default behavior) +- **forcerm** - always remove intermediate containers (includes rm) +- **memory** - set memory limit for build +- **memswap** - Total memory (memory + swap), `-1` to disable swap +- **cpushares** - CPU shares (relative weight) +- **cpusetcpus** - CPUs in which to allow exection, e.g., `0-3`, `0,1` + + Request Headers: + +- **Content-type** – should be set to `"application/tar"`. +- **X-Registry-Config** – base64-encoded ConfigFile objec + +Status Codes: + +- **200** – no error +- **500** – server error + +### Create an image + +`POST /images/create` + +Create an image, either by pulling it from the registry or by importing it + +**Example request**: + + POST /images/create?fromImage=ubuntu HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status": "Pulling..."} + {"status": "Pulling", "progress": "1 B/ 100 B", "progressDetail": {"current": 1, "total": 100}} + {"error": "Invalid..."} + ... + + When using this endpoint to pull an image from the registry, the + `X-Registry-Auth` header can be used to include + a base64-encoded AuthConfig object. + +Query Parameters: + +- **fromImage** – name of the image to pull +- **fromSrc** – source to import. The value may be a URL from which the image + can be retrieved or `-` to read the image from the request body. +- **repo** – repository +- **tag** – tag +- **registry** – the registry to pull from + + Request Headers: + +- **X-Registry-Auth** – base64-encoded AuthConfig object + +Status Codes: + +- **200** – no error +- **500** – server error + + + +### Inspect an image + +`GET /images/(name)/json` + +Return low-level information on the image `name` + +**Example request**: + + GET /images/ubuntu/json HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Created": "2013-03-23T22:24:18.818426-07:00", + "Container": "3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", + "ContainerConfig": + { + "Hostname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "PortSpecs": null, + "Tty": true, + "OpenStdin": true, + "StdinOnce": false, + "Env": null, + "Cmd": ["/bin/bash"], + "Dns": null, + "Image": "ubuntu", + "Labels": { + "com.example.vendor": "Acme", + "com.example.license": "GPL", + "com.example.version": "1.0" + }, + "Volumes": null, + "VolumesFrom": "", + "WorkingDir": "" + }, + "Id": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "Parent": "27cf784147099545", + "Size": 6824592 + } + +Status Codes: + +- **200** – no error +- **404** – no such image +- **500** – server error + +### Get the history of an image + +`GET /images/(name)/history` + +Return the history of the image `name` + +**Example request**: + + GET /images/ubuntu/history HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id": "b750fe79269d", + "Created": 1364102658, + "CreatedBy": "/bin/bash" + }, + { + "Id": "27cf78414709", + "Created": 1364068391, + "CreatedBy": "" + } + ] + +Status Codes: + +- **200** – no error +- **404** – no such image +- **500** – server error + +### Push an image on the registry + +`POST /images/(name)/push` + +Push the image `name` on the registry + +**Example request**: + + POST /images/test/push HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status": "Pushing..."} + {"status": "Pushing", "progress": "1/? (n/a)", "progressDetail": {"current": 1}}} + {"error": "Invalid..."} + ... + + If you wish to push an image on to a private registry, that image must already have been tagged + into a repository which references that registry host name and port. This repository name should + then be used in the URL. This mirrors the flow of the CLI. + +**Example request**: + + POST /images/registry.acme.com:5000/test/push HTTP/1.1 + + +Query Parameters: + +- **tag** – the tag to associate with the image on the registry, optional + +Request Headers: + +- **X-Registry-Auth** – include a base64-encoded AuthConfig + object. + +Status Codes: + +- **200** – no error +- **404** – no such image +- **500** – server error + +### Tag an image into a repository + +`POST /images/(name)/tag` + +Tag the image `name` into a repository + +**Example request**: + + POST /images/test/tag?repo=myrepo&force=0&tag=v42 HTTP/1.1 + +**Example response**: + + HTTP/1.1 201 OK + +Query Parameters: + +- **repo** – The repository to tag in +- **force** – 1/True/true or 0/False/false, default false +- **tag** - The new tag name + +Status Codes: + +- **201** – no error +- **400** – bad parameter +- **404** – no such image +- **409** – conflict +- **500** – server error + +### Remove an image + +`DELETE /images/(name)` + +Remove the image `name` from the filesystem + +**Example request**: + + DELETE /images/test HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-type: application/json + + [ + {"Untagged": "3e2f21a89f"}, + {"Deleted": "3e2f21a89f"}, + {"Deleted": "53b4f83ac9"} + ] + +Query Parameters: + +- **force** – 1/True/true or 0/False/false, default false +- **noprune** – 1/True/true or 0/False/false, default false + +Status Codes: + +- **200** – no error +- **404** – no such image +- **409** – conflict +- **500** – server error + +### Search images + +`GET /images/search` + +Search for an image on [Docker Hub](https://hub.docker.com). + +> **Note**: +> The response keys have changed from API v1.6 to reflect the JSON +> sent by the registry server to the docker daemon's request. + +**Example request**: + + GET /images/search?term=sshd HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "description": "", + "is_official": false, + "is_automated": false, + "name": "wma55/u1210sshd", + "star_count": 0 + }, + { + "description": "", + "is_official": false, + "is_automated": false, + "name": "jdswinbank/sshd", + "star_count": 0 + }, + { + "description": "", + "is_official": false, + "is_automated": false, + "name": "vgauthier/sshd", + "star_count": 0 + } + ... + ] + +Query Parameters: + +- **term** – term to search + +Status Codes: + +- **200** – no error +- **500** – server error + +## 2.3 Misc + +### Check auth configuration + +`POST /auth` + +Get the default username and email + +**Example request**: + + POST /auth HTTP/1.1 + Content-Type: application/json + + { + "username":" hannibal", + "password: "xxxx", + "email": "hannibal@a-team.com", + "serveraddress": "https://index.docker.io/v1/" + } + +**Example response**: + + HTTP/1.1 200 OK + +Status Codes: + +- **200** – no error +- **204** – no error +- **500** – server error + +### Display system-wide information + +`GET /info` + +Display system-wide information + +**Example request**: + + GET /info HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Containers":11, + "Images":16, + "Driver":"btrfs", + "DriverStatus": [[""]], + "ExecutionDriver":"native-0.1", + "KernelVersion":"3.12.0-1-amd64" + "NCPU":1, + "MemTotal":2099236864, + "Name":"prod-server-42", + "ID":"7TRN:IPZB:QYBB:VPBQ:UMPP:KARE:6ZNR:XE6T:7EWV:PKF4:ZOJD:TPYS", + "Debug":false, + "NFd": 11, + "NGoroutines":21, + "SystemTime": "2015-03-10T11:11:23.730591467-07:00" + "NEventsListener":0, + "InitPath":"/usr/bin/docker", + "InitSha1":"", + "IndexServerAddress":["https://index.docker.io/v1/"], + "MemoryLimit":true, + "SwapLimit":false, + "IPv4Forwarding":true, + "Labels":["storage=ssd"], + "DockerRootDir": "/var/lib/docker", + "HttpProxy": "http://test:test@localhost:8080" + "HttpsProxy": "https://test:test@localhost:8080" + "NoProxy": "9.81.1.160" + "OperatingSystem": "Boot2Docker", + } + +Status Codes: + +- **200** – no error +- **500** – server error + +### Show the docker version information + +`GET /version` + +Show the docker version information + +**Example request**: + + GET /version HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Version": "1.5.0", + "Os": "linux", + "KernelVersion": "3.18.5-tinycore64", + "GoVersion": "go1.4.1", + "GitCommit": "a8a31ef", + "Arch": "amd64", + "ApiVersion": "1.19" + } + +Status Codes: + +- **200** – no error +- **500** – server error + +### Ping the docker server + +`GET /_ping` + +Ping the docker server + +**Example request**: + + GET /_ping HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: text/plain + + OK + +Status Codes: + +- **200** - no error +- **500** - server error + +### Create a new image from a container's changes + +`POST /commit` + +Create a new image from a container's changes + +**Example request**: + + POST /commit?container=44c004db4b17&comment=message&repo=myrepo HTTP/1.1 + Content-Type: application/json + + { + "Hostname": "", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "PortSpecs": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Volumes": { + "/tmp": {} + }, + "WorkingDir": "", + "NetworkDisabled": false, + "ExposedPorts": { + "22/tcp": {} + } + } + +**Example response**: + + HTTP/1.1 201 Created + Content-Type: application/vnd.docker.raw-stream + + {"Id": "596069db4bf5"} + +Json Parameters: + +- **config** - the container's configuration + +Query Parameters: + +- **container** – source container +- **repo** – repository +- **tag** – tag +- **comment** – commit message +- **author** – author (e.g., "John Hannibal Smith + <[hannibal@a-team.com](mailto:hannibal%40a-team.com)>") + +Status Codes: + +- **201** – no error +- **404** – no such container +- **500** – server error + +### Monitor Docker's events + +`GET /events` + +Get container events from docker, either in real time via streaming, or via +polling (using since). + +Docker containers will report the following events: + + create, destroy, die, exec_create, exec_start, export, kill, oom, pause, restart, start, stop, unpause + +and Docker images will report: + + untag, delete + +**Example request**: + + GET /events?since=1374067924 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status": "create", "id": "dfdf82bd3881","from": "ubuntu:latest", "time":1374067924} + {"status": "start", "id": "dfdf82bd3881","from": "ubuntu:latest", "time":1374067924} + {"status": "stop", "id": "dfdf82bd3881","from": "ubuntu:latest", "time":1374067966} + {"status": "destroy", "id": "dfdf82bd3881","from": "ubuntu:latest", "time":1374067970} + +Query Parameters: + +- **since** – timestamp used for polling +- **until** – timestamp used for polling +- **filters** – a json encoded value of the filters (a map[string][]string) to process on the event list. Available filters: + - event=<string> -- event to filter + - image=<string> -- image to filter + - container=<string> -- container to filter + +Status Codes: + +- **200** – no error +- **500** – server error + +### Get a tarball containing all images in a repository + +`GET /images/(name)/get` + +Get a tarball containing all images and metadata for the repository specified +by `name`. + +If `name` is a specific name and tag (e.g. ubuntu:latest), then only that image +(and its parents) are returned. If `name` is an image ID, similarly only tha +image (and its parents) are returned, but with the exclusion of the +'repositories' file in the tarball, as there were no image names referenced. + +See the [image tarball format](#image-tarball-format) for more details. + +**Example request** + + GET /images/ubuntu/get + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/x-tar + + Binary data stream + +Status Codes: + +- **200** – no error +- **500** – server error + +### Get a tarball containing all images. + +`GET /images/get` + +Get a tarball containing all images and metadata for one or more repositories. + +For each value of the `names` parameter: if it is a specific name and tag (e.g. +ubuntu:latest), then only that image (and its parents) are returned; if it is +an image ID, similarly only that image (and its parents) are returned and there +would be no names referenced in the 'repositories' file for this image ID. + +See the [image tarball format](#image-tarball-format) for more details. + +**Example request** + + GET /images/get?names=myname%2Fmyapp%3Alatest&names=busybox + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: application/x-tar + + Binary data stream + +Status Codes: + +- **200** – no error +- **500** – server error + +### Load a tarball with a set of images and tags into docker + +`POST /images/load` + +Load a set of images and tags into the docker repository. +See the [image tarball format](#image-tarball-format) for more details. + +**Example request** + + POST /images/load + + Tarball in body + +**Example response**: + + HTTP/1.1 200 OK + +Status Codes: + +- **200** – no error +- **500** – server error + +### Image tarball format + +An image tarball contains one directory per image layer (named using its long ID), +each containing three files: + +1. `VERSION`: currently `1.0` - the file format version +2. `json`: detailed layer information, similar to `docker inspect layer_id` +3. `layer.tar`: A tarfile containing the filesystem changes in this layer + +The `layer.tar` file will contain `aufs` style `.wh..wh.aufs` files and directories +for storing attribute changes and deletions. + +If the tarball defines a repository, there will also be a `repositories` file at +the root that contains a list of repository and tag names mapped to layer IDs. + +``` +{"hello-world": + {"latest": "565a9d68a73f6706862bfe8409a7f659776d4d60a8d096eb4a3cbce6999cc2a1"} +} +``` + +### Exec Create + +`POST /containers/(id)/exec` + +Sets up an exec instance in a running container `id` + +**Example request**: + + POST /containers/e90e34656806/exec HTTP/1.1 + Content-Type: application/json + + { + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "Tty": false, + "Cmd": [ + "date" + ], + } + +**Example response**: + + HTTP/1.1 201 OK + Content-Type: application/json + + { + "Id": "f90e34656806" + "Warnings":[] + } + +Json Parameters: + +- **AttachStdin** - Boolean value, attaches to stdin of the exec command. +- **AttachStdout** - Boolean value, attaches to stdout of the exec command. +- **AttachStderr** - Boolean value, attaches to stderr of the exec command. +- **Tty** - Boolean value to allocate a pseudo-TTY +- **Cmd** - Command to run specified as a string or an array of strings. + + +Status Codes: + +- **201** – no error +- **404** – no such container + +### Exec Start + +`POST /exec/(id)/start` + +Starts a previously set up exec instance `id`. If `detach` is true, this API +returns after starting the `exec` command. Otherwise, this API sets up an +interactive session with the `exec` command. + +**Example request**: + + POST /exec/e90e34656806/start HTTP/1.1 + Content-Type: application/json + + { + "Detach": false, + "Tty": false, + } + +**Example response**: + + HTTP/1.1 201 OK + Content-Type: application/json + + {{ STREAM }} + +Json Parameters: + +- **Detach** - Detach from the exec command +- **Tty** - Boolean value to allocate a pseudo-TTY + +Status Codes: + +- **201** – no error +- **404** – no such exec instance + + **Stream details**: + Similar to the stream behavior of `POST /container/(id)/attach` API + +### Exec Resize + +`POST /exec/(id)/resize` + +Resizes the tty session used by the exec command `id`. +This API is valid only if `tty` was specified as part of creating and starting the exec command. + +**Example request**: + + POST /exec/e90e34656806/resize HTTP/1.1 + Content-Type: text/plain + +**Example response**: + + HTTP/1.1 201 OK + Content-Type: text/plain + +Query Parameters: + +- **h** – height of tty session +- **w** – width + +Status Codes: + +- **201** – no error +- **404** – no such exec instance + +### Exec Inspect + +`GET /exec/(id)/json` + +Return low-level information about the exec command `id`. + +**Example request**: + + GET /exec/11fb006128e8ceb3942e7c58d77750f24210e35f879dd204ac975c184b820b39/json HTTP/1.1 + +**Example response**: + + HTTP/1.1 200 OK + Content-Type: plain/text + + { + "ID" : "11fb006128e8ceb3942e7c58d77750f24210e35f879dd204ac975c184b820b39", + "Running" : false, + "ExitCode" : 2, + "ProcessConfig" : { + "privileged" : false, + "user" : "", + "tty" : false, + "entrypoint" : "sh", + "arguments" : [ + "-c", + "exit 2" + ] + }, + "OpenStdin" : false, + "OpenStderr" : false, + "OpenStdout" : false, + "Container" : { + "State" : { + "Running" : true, + "Paused" : false, + "Restarting" : false, + "OOMKilled" : false, + "Pid" : 3650, + "ExitCode" : 0, + "Error" : "", + "StartedAt" : "2014-11-17T22:26:03.717657531Z", + "FinishedAt" : "0001-01-01T00:00:00Z" + }, + "ID" : "8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c", + "Created" : "2014-11-17T22:26:03.626304998Z", + "Path" : "date", + "Args" : [], + "Config" : { + "Hostname" : "8f177a186b97", + "Domainname" : "", + "User" : "", + "AttachStdin" : false, + "AttachStdout" : false, + "AttachStderr" : false, + "PortSpecs" : null, + "ExposedPorts" : null, + "Tty" : false, + "OpenStdin" : false, + "StdinOnce" : false, + "Env" : [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" ], + "Cmd" : [ + "date" + ], + "Image" : "ubuntu", + "Volumes" : null, + "WorkingDir" : "", + "Entrypoint" : null, + "NetworkDisabled" : false, + "MacAddress" : "", + "OnBuild" : null, + "SecurityOpt" : null + }, + "Image" : "5506de2b643be1e6febbf3b8a240760c6843244c41e12aa2f60ccbb7153d17f5", + "NetworkSettings" : { + "IPAddress" : "172.17.0.2", + "IPPrefixLen" : 16, + "MacAddress" : "02:42:ac:11:00:02", + "Gateway" : "172.17.42.1", + "Bridge" : "docker0", + "PortMapping" : null, + "Ports" : {} + }, + "ResolvConfPath" : "/var/lib/docker/containers/8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c/resolv.conf", + "HostnamePath" : "/var/lib/docker/containers/8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c/hostname", + "HostsPath" : "/var/lib/docker/containers/8f177a186b977fb451136e0fdf182abff5599a08b3c7f6ef0d36a55aaf89634c/hosts", + "LogPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b-json.log", + "Name" : "/test", + "Driver" : "aufs", + "ExecDriver" : "native-0.2", + "MountLabel" : "", + "ProcessLabel" : "", + "AppArmorProfile" : "", + "RestartCount" : 0, + "Volumes" : {}, + "VolumesRW" : {} + } + } + +Status Codes: + +- **200** – no error +- **404** – no such exec instance +- **500** - server error + +# 3. Going further + +## 3.1 Inside `docker run` + +As an example, the `docker run` command line makes the following API calls: + +- Create the container + +- If the status code is 404, it means the image doesn't exist: + - Try to pull it + - Then retry to create the container + +- Start the container + +- If you are not in detached mode: +- Attach to the container, using logs=1 (to have stdout and + stderr from the container's start) and stream=1 + +- If in detached mode or only stdin is attached: +- Display the container's id + +## 3.2 Hijacking + +In this version of the API, /attach, uses hijacking to transport stdin, +stdout and stderr on the same socket. + +To hint potential proxies about connection hijacking, Docker client sends +connection upgrade headers similarly to websocket. + + Upgrade: tcp + Connection: Upgrade + +When Docker daemon detects the `Upgrade` header, it will switch its status code +from **200 OK** to **101 UPGRADED** and resend the same headers. + +This might change in the future. + +## 3.3 CORS Requests + +To set cross origin requests to the remote api please give values to +"--api-cors-header" when running docker in daemon mode. Set * will allow all, +default or blank means CORS disabled + + $ docker -d -H="192.168.1.9:2375" --api-cors-header="http://foo.bar" From 1a4d2a5c8b4b130e93c652743bab4547b8bf9aba Mon Sep 17 00:00:00 2001 From: Nick Irvine Date: Thu, 26 Mar 2015 21:08:17 -0700 Subject: [PATCH 222/999] Add some new graphics for installation and understanding docs Fixes: #11552 - scalable SVG - uses colours from the rest of the page - emphasizes that boot2docker is the ordinary Linux setup in a VM - Add new graphic for architecture diagram - Add windows boot2docker diagram - Add windows diagram to documentation - Remove old PNGs; replaced with SVGs - Add redirects for new SVG versions of installation diagrams Signed-off-by: Nick Irvine --- docs/s3_website.json | 3 + docs/sources/article-img/architecture.svg | 2600 ++++++++++++++++- .../installation/images/linux_docker_host.png | Bin 13938 -> 0 bytes .../installation/images/linux_docker_host.svg | 1195 ++++++++ .../installation/images/mac_docker_host.png | Bin 16930 -> 0 bytes .../installation/images/mac_docker_host.svg | 1243 ++++++++ .../installation/images/win_docker_host.svg | 1259 ++++++++ docs/sources/installation/mac.md | 6 +- docs/sources/installation/windows.md | 2 + 9 files changed, 6302 insertions(+), 6 deletions(-) delete mode 100644 docs/sources/installation/images/linux_docker_host.png create mode 100644 docs/sources/installation/images/linux_docker_host.svg delete mode 100644 docs/sources/installation/images/mac_docker_host.png create mode 100644 docs/sources/installation/images/mac_docker_host.svg create mode 100644 docs/sources/installation/images/win_docker_host.svg diff --git a/docs/s3_website.json b/docs/s3_website.json index 490c492ea..1142fc0d8 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -17,6 +17,9 @@ { "Condition": { "KeyPrefixEquals": "docker-hub/invite.png" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "docker-hub/hub-images/invite.png" } }, { "Condition": { "KeyPrefixEquals": "docker-hub/orgs.png" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "docker-hub/hub-images/orgs.png" } }, { "Condition": { "KeyPrefixEquals": "docker-hub/repos.png" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "docker-hub/hub-images/repos.png" } }, + { "Condition": { "KeyPrefixEquals": "installation/images/linux_docker_host.png" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "installation/images/linux_docker_host.svg" } }, + { "Condition": { "KeyPrefixEquals": "installation/images/osx_docker_host.png" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "installation/images/osx_docker_host.svg" } }, + { "Condition": { "KeyPrefixEquals": "installation/images/win_docker_host.png" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "installation/images/win_docker_host.svg" } }, { "Condition": { "KeyPrefixEquals": "examples/hello_world/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerizing/" } }, { "Condition": { "KeyPrefixEquals": "examples/python_web_app/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockerizing/" } }, { "Condition": { "KeyPrefixEquals": "use/working_with_volumes/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "userguide/dockervolumes/" } }, diff --git a/docs/sources/article-img/architecture.svg b/docs/sources/article-img/architecture.svg index 607cc3c18..afe563ae8 100644 --- a/docs/sources/article-img/architecture.svg +++ b/docs/sources/article-img/architecture.svg @@ -1,3 +1,2597 @@ - - -2014-04-15 00:37ZCanvas 1Layer 1HostContainer 1Container 2Container 3Container ...Docker Clientdocker pulldocker rundocker ...Docker IndexDocker Daemon + + + + + 2014-04-15 00:37Z + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/sources/installation/images/linux_docker_host.png b/docs/sources/installation/images/linux_docker_host.png deleted file mode 100644 index 42895c2f768458b8a3289868ad1032fea0c7c2c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13938 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 L6`4{gL;wE|N{@Cu diff --git a/docs/sources/installation/images/linux_docker_host.svg b/docs/sources/installation/images/linux_docker_host.svg new file mode 100644 index 000000000..0ad7240b7 --- /dev/null +++ b/docs/sources/installation/images/linux_docker_host.svg @@ -0,0 +1,1195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/sources/installation/images/mac_docker_host.png b/docs/sources/installation/images/mac_docker_host.png deleted file mode 100644 index 9aa71a4ebc3098de8480db1131dff08dbe724502..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16930 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 F{{`=hooE06 diff --git a/docs/sources/installation/images/mac_docker_host.svg b/docs/sources/installation/images/mac_docker_host.svg new file mode 100644 index 000000000..a885a32cb --- /dev/null +++ b/docs/sources/installation/images/mac_docker_host.svg @@ -0,0 +1,1243 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/sources/installation/images/win_docker_host.svg b/docs/sources/installation/images/win_docker_host.svg new file mode 100644 index 000000000..eef284e75 --- /dev/null +++ b/docs/sources/installation/images/win_docker_host.svg @@ -0,0 +1,1259 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/sources/installation/mac.md b/docs/sources/installation/mac.md index 9bf763268..9326f5fc4 100644 --- a/docs/sources/installation/mac.md +++ b/docs/sources/installation/mac.md @@ -42,12 +42,12 @@ containers run directly on your localhost. This means you can address ports on a Docker container using standard localhost addressing such as `localhost:8000` or `0.0.0.0:8376`. -![Linux Architecture Diagram](/installation/images/linux_docker_host.png) +![Linux Architecture Diagram](/installation/images/linux_docker_host.svg) In an OS X installation, the `docker` daemon is running inside a Linux virtual machine provided by Boot2Docker. -![OSX Architecture Diagram](/installation/images/mac_docker_host.png) +![OSX Architecture Diagram](/installation/images/mac_docker_host.svg) In OS X, the Docker host address is the address of the Linux VM. When you start the `boot2docker` process, the VM is assigned an IP address. Under @@ -324,4 +324,4 @@ at [Boot2Docker repository](https://github.com/boot2docker/boot2docker). Thanks to Chris Jones whose [blog](http://goo.gl/Be6cCk) inspired me to redo this page. -Continue with the [Docker User Guide](/userguide/). \ No newline at end of file +Continue with the [Docker User Guide](/userguide/). diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index 95e5906cf..5779bcb85 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -20,6 +20,8 @@ Although you will be using Windows Docker client, the docker engine hosting the containers will still be running on Linux. Until the Docker engine for Windows is developed, you can launch only Linux containers from your Windows machine. +![Windows Architecture Diagram](/installation/images/win_docker_host.svg) + ## Demonstration From 7bd250c74d1dbc64be2ccb0978e1babd55d18afa Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Wed, 1 Apr 2015 14:38:49 -0700 Subject: [PATCH 223/999] Add Kitematic to README.md Signed-off-by: Arnaud Porterie --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 079713ced..c70398a3a 100644 --- a/README.md +++ b/README.md @@ -232,9 +232,6 @@ There are a number of projects under development that are based on Docker's core technology. These projects expand the tooling built around the Docker platform to broaden its application and utility. -If you know of another project underway that should be listed here, please help -us keep this list up-to-date by submitting a PR. - * [Docker Registry](https://github.com/docker/distribution): Registry server for Docker (hosting/delivery of repositories and images) * [Docker Machine](https://github.com/docker/machine): Machine management @@ -243,4 +240,8 @@ for a container-centric world system * [Docker Compose](https://github.com/docker/compose) (formerly Fig): Define and run multi-container apps +* [Kitematic](https://github.com/kitematic/kitematic): The easiest way to use +Docker on a Mac +If you know of another project underway that should be listed here, please help +us keep this list up-to-date by submitting a PR. From 7061a993c5b620d6e68450f1b90f3458bfa1add0 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 1 Apr 2015 15:30:48 -0700 Subject: [PATCH 224/999] Return closed channel if oom notification fails When working with Go channels you must not set it to nil or else the channel will block forever. It will not panic reading from a nil chan but it blocks. The correct way to do this is to create the channel then close it as the correct results to the caller will be returned. Signed-off-by: Michael Crosby --- daemon/execdriver/native/driver.go | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 816207ed3..6afd02321 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -156,11 +156,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba startCallback(&c.ProcessConfig, pid) } - oomKillNotification, err := cont.NotifyOOM() - if err != nil { - oomKillNotification = nil - logrus.Warnf("Your kernel does not support OOM notifications: %s", err) - } + oom := notifyOnOOM(cont) waitF := p.Wait if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) { // we need such hack for tracking processes with inerited fds, @@ -176,12 +172,24 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba ps = execErr.ProcessState } cont.Destroy() - - _, oomKill := <-oomKillNotification - + _, oomKill := <-oom return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil } +// notifyOnOOM returns a channel that signals if the container received an OOM notification +// for any process. If it is unable to subscribe to OOM notifications then a closed +// channel is returned as it will be non-blocking and return the correct result when read. +func notifyOnOOM(container libcontainer.Container) <-chan struct{} { + oom, err := container.NotifyOOM() + if err != nil { + logrus.Warnf("Your kernel does not support OOM notifications: %s", err) + c := make(chan struct{}) + close(c) + return c + } + return oom +} + func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) { return func() (*os.ProcessState, error) { pid, err := p.Pid() From 664004ed0c6c99369720a00f5673f1e106d9496d Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Thu, 12 Feb 2015 10:51:51 -0800 Subject: [PATCH 225/999] Mounting a directory of devices like /dev/snd should mount all child devices. I have seen a lot of people try to do this and reach out to me on how to mount /dev/snd because it is returning "not a device node". The docs imply you can _just_ mount /dev/snd and that is not the case. This fixes that. It also allows for coolness if you want to mount say /dev/usb. Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) Docker-DCO-1.1-Signed-off-by: Jessica Frazelle (github: jfrazelle) --- daemon/container.go | 51 ++++++++++++++++++--- integration-cli/docker_cli_run_unix_test.go | 27 +++++++++++ 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 47ff70645..228944d2d 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -215,6 +215,45 @@ func (container *Container) getRootResourcePath(path string) (string, error) { return symlink.FollowSymlinkInScope(filepath.Join(container.root, cleanPath), container.root) } +func getDevicesFromPath(deviceMapping runconfig.DeviceMapping) (devs []*configs.Device, err error) { + device, err := devices.DeviceFromPath(deviceMapping.PathOnHost, deviceMapping.CgroupPermissions) + // if there was no error, return the device + if err == nil { + device.Path = deviceMapping.PathInContainer + return append(devs, device), nil + } + + // if the device is not a device node + // try to see if it's a directory holding many devices + if err == devices.ErrNotADevice { + + // check if it is a directory + if src, e := os.Stat(deviceMapping.PathOnHost); e == nil && src.IsDir() { + + // mount the internal devices recursively + filepath.Walk(deviceMapping.PathOnHost, func(dpath string, f os.FileInfo, e error) error { + childDevice, e := devices.DeviceFromPath(dpath, deviceMapping.CgroupPermissions) + if e != nil { + // ignore the device + return nil + } + + // add the device to userSpecified devices + childDevice.Path = strings.Replace(dpath, deviceMapping.PathOnHost, deviceMapping.PathInContainer, 1) + devs = append(devs, childDevice) + + return nil + }) + } + } + + if len(devs) > 0 { + return devs, nil + } + + return devs, fmt.Errorf("error gathering device information while adding custom device %q: %s", deviceMapping.PathOnHost, err) +} + func populateCommand(c *Container, env []string) error { en := &execdriver.Network{ Mtu: c.daemon.config.Mtu, @@ -267,14 +306,14 @@ func populateCommand(c *Container, env []string) error { pid.HostPid = c.hostConfig.PidMode.IsHost() // Build lists of devices allowed and created within the container. - userSpecifiedDevices := make([]*configs.Device, len(c.hostConfig.Devices)) - for i, deviceMapping := range c.hostConfig.Devices { - device, err := devices.DeviceFromPath(deviceMapping.PathOnHost, deviceMapping.CgroupPermissions) + var userSpecifiedDevices []*configs.Device + for _, deviceMapping := range c.hostConfig.Devices { + devs, err := getDevicesFromPath(deviceMapping) if err != nil { - return fmt.Errorf("error gathering device information while adding custom device %q: %s", deviceMapping.PathOnHost, err) + return err } - device.Path = deviceMapping.PathInContainer - userSpecifiedDevices[i] = device + + userSpecifiedDevices = append(userSpecifiedDevices, devs...) } allowedDevices := append(configs.DefaultAllowedDevices, userSpecifiedDevices...) diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 9327ac240..e577b142b 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -173,3 +173,30 @@ func TestRunContainerWithCgroupParentAbsPath(t *testing.T) { logDone("run - cgroup parent with absolute cgroup path") } + +func TestRunDeviceDirectory(t *testing.T) { + defer deleteAllContainers() + cmd := exec.Command(dockerBinary, "run", "--device", "/dev/snd:/dev/snd", "busybox", "sh", "-c", "ls /dev/snd/") + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + if actual := strings.Trim(out, "\r\n"); !strings.Contains(out, "timer") { + t.Fatalf("expected output /dev/snd/timer, received %s", actual) + } + + cmd = exec.Command(dockerBinary, "run", "--device", "/dev/snd:/dev/othersnd", "busybox", "sh", "-c", "ls /dev/othersnd/") + + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + if actual := strings.Trim(out, "\r\n"); !strings.Contains(out, "seq") { + t.Fatalf("expected output /dev/othersnd/timer, received %s", actual) + } + + logDone("run - test --device directory mounts all internal devices") +} From 37c7f3a2046dc01c6c58ed64638faf136dd0682c Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Wed, 1 Apr 2015 00:16:43 +0200 Subject: [PATCH 226/999] docs: fix bullet list and missing label filter Some bullet lists didn't render as bullet-lists because of a missing newline. Also added missing "label" filter for `docker ps` and slightly re-worded the header above the supported filters. Signed-off-by: Sebastiaan van Stijn --- docs/sources/reference/commandline/cli.md | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index e3344991b..5ba261374 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -986,7 +986,7 @@ Using multiple filters will be handled as a *AND*; for example `--filter container=588a23dac085 --filter event=start` will display events for container container 588a23dac085 *AND* the event type is *start* -Current filters: +The currently supported filters are: * container * event @@ -1233,9 +1233,10 @@ also reference by digest in `create`, `run`, and `rmi` commands, as well as the The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`) -Current filters: - * dangling (boolean - true or false) - * label (`label=` or `label==`) +The currently supported filters are: + +* dangling (boolean - true or false) +* label (`label=` or `label==`) ##### Untagged images @@ -1584,11 +1585,13 @@ Running `docker ps --no-trunc` showing 2 linked containers. The filtering flag (`-f` or `--filter)` format is a `key=value` pair. If there is more than one filter, then pass multiple flags (e.g. `--filter "foo=bar" --filter "bif=baz"`) -Current filters: - * id (container's id) - * name (container's name) - * exited (int - the code of exited containers. Only useful with '--all') - * status (restarting|running|paused|exited) +The currently supported filters are: + +* id (container's id) +* label (`label=` or `label==`) +* name (container's name) +* exited (int - the code of exited containers. Only useful with `--all`) +* status (restarting|running|paused|exited) ##### Successfully exited containers From 34f44c642faa94ae5d5677d49f690533c67cdf5e Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Wed, 1 Apr 2015 17:25:56 -0700 Subject: [PATCH 227/999] Infer type Signed-off-by: Darren Shepherd --- api/server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/server/server.go b/api/server/server.go index c541c7624..fc2acc4c0 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -32,7 +32,7 @@ import ( ) var ( - activationLock chan struct{} = make(chan struct{}) + activationLock = make(chan struct{}) ) type HttpServer struct { From 7433c9c92a9d2b53331f729249ddbbfa70cb76de Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Wed, 1 Apr 2015 17:26:22 -0700 Subject: [PATCH 228/999] Make server_windows.go consistent with server_linux.go Signed-off-by: Darren Shepherd --- api/server/server_windows.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/api/server/server_windows.go b/api/server/server_windows.go index e7feb55a2..ad7b3c48a 100644 --- a/api/server/server_windows.go +++ b/api/server/server_windows.go @@ -39,10 +39,12 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { } // Called through eng.Job("acceptconnections") -func AcceptConnections(job *engine.Job) engine.Status { +func AcceptConnections(job *engine.Job) error { // close the lock so the listeners start accepting connections - if activationLock != nil { + select { + case <-activationLock: + default: close(activationLock) } - return engine.StatusOK + return nil } From a9588158b54bc8866bddfa0445c14831e02ebbbc Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Thu, 2 Apr 2015 08:38:39 +0800 Subject: [PATCH 229/999] Add MEMCG_SWAP_ENABLED to check-config.sh Signed-off-by: Lei Jitang --- contrib/check-config.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/contrib/check-config.sh b/contrib/check-config.sh index ac5df62c2..59649d6c6 100755 --- a/contrib/check-config.sh +++ b/contrib/check-config.sh @@ -151,8 +151,14 @@ check_flags "${flags[@]}" echo echo 'Optional Features:' +{ + check_flags MEMCG_SWAP + check_flags MEMCG_SWAP_ENABLED + if is_set MEMCG_SWAP && ! is_set MEMCG_SWAP_ENABLED; then + echo " $(wrap_color '(note that cgroup swap accounting is not enabled in your kernel config, you can enable it by setting boot option "swapaccount=1")' bold black)" + fi +} flags=( - MEMCG_SWAP RESOURCE_COUNTERS CGROUP_PERF ) From f472c7236f7f7ea069b2997391569e5ec60981ab Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 1 Apr 2015 17:47:36 -0700 Subject: [PATCH 230/999] fix device test on lxc, on lxc in contianers there are no dirs with devices in /dev, but this works outside a container... Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) --- integration-cli/docker_cli_run_unix_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index e577b142b..8e0cc9be3 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -175,6 +175,7 @@ func TestRunContainerWithCgroupParentAbsPath(t *testing.T) { } func TestRunDeviceDirectory(t *testing.T) { + testRequires(t, NativeExecDriver) defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "--device", "/dev/snd:/dev/snd", "busybox", "sh", "-c", "ls /dev/snd/") From c447fd339a9293ea528ba0c8f4154cf1f953186c Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 1 Apr 2015 18:06:28 -0700 Subject: [PATCH 231/999] TestVolumesFromHasPriority fix race Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) --- integration-cli/docker_api_containers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index a85fcbdf6..02d069f59 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -232,7 +232,7 @@ func TestContainerApiStartVolumesFrom(t *testing.T) { // This is pretty much the same as TestRunApplyVolumesFromBeforeVolumes, except with passing the VolumesFrom and the bind on start func TestVolumesFromHasPriority(t *testing.T) { defer deleteAllContainers() - volName := "voltst" + volName := "voltst2" volPath := "/tmp" if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil { From b6d55ebcbc93ce66aa9906aa87e1081f21ba1650 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Wed, 1 Apr 2015 12:20:59 -0700 Subject: [PATCH 232/999] Updating with man pages for distribution Went through the man pages to update for the v2 instance. Checked against the commands. Signed-off-by: Mary Anthony --- docs/Dockerfile | 16 +++++++++++++++- docs/man/docker-login.1.md | 17 ++++++++++------- docs/man/docker-logout.1.md | 12 +++++++----- docs/man/docker-pull.1.md | 11 +++++++---- docs/man/docker-push.1.md | 24 ++++++++++++------------ docs/man/docker-rmi.1.md | 8 ++++---- docs/man/docker-search.1.md | 17 +++++++++-------- docs/man/docker-tag.1.md | 11 ++++++++--- docs/man/docker.1.md | 8 ++++---- 9 files changed, 76 insertions(+), 48 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index dd61fa8df..b8dbd04ec 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -8,7 +8,7 @@ MAINTAINER Sven Dowideit (@SvenDowideit) COPY . /src # Reset the /docs dir so we can replace the theme meta with the new repo's git info -RUN git reset --hard +# RUN git reset --hard # Then copy the desired docs into the /docs/sources/ dir COPY ./sources/ /docs/sources @@ -23,6 +23,20 @@ COPY ./mkdocs.yml mkdocs.yml COPY ./s3_website.json s3_website.json COPY ./release.sh release.sh + +# Docker Distribution +#ADD https://raw.githubusercontent.com/moxiegirl/distribution/doc-tooling-changes/docs/mkdocs.yml /docs/mkdocs-distribution.yml + +ADD https://raw.githubusercontent.com/moxiegirl/distribution/doc-tooling-changes/docs/overview.md /docs/sources/distribution/overview.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/overview.md + +ADD https://raw.githubusercontent.com/moxiegirl/distribution/doc-tooling-changes/docs/install.md /docs/sources/distribution/install.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/install.md + +ADD https://raw.githubusercontent.com/moxiegirl/distribution/doc-tooling-changes/docs/architecture.md /docs/sources/distribution/architecture.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/architecture.md + + # Docker Swarm #ADD https://raw.githubusercontent.com/docker/swarm/master/docs/mkdocs.yml /docs/mkdocs-swarm.yml ADD https://raw.githubusercontent.com/docker/swarm/master/docs/index.md /docs/sources/swarm/index.md diff --git a/docs/man/docker-login.1.md b/docs/man/docker-login.1.md index 5ff9403a8..f73df77ed 100644 --- a/docs/man/docker-login.1.md +++ b/docs/man/docker-login.1.md @@ -2,7 +2,7 @@ % Docker Community % JUNE 2014 # NAME -docker-login - Register or log in to a Docker registry server, if no server is specified "https://index.docker.io/v1/" is the default. +docker-login - Register or log in to a Docker registry. # SYNOPSIS **docker login** @@ -13,12 +13,14 @@ docker-login - Register or log in to a Docker registry server, if no server is s [SERVER] # DESCRIPTION -Register or Login to a docker registry server, if no server is -specified "https://index.docker.io/v1/" is the default. If you want to -login to a private registry you can specify this by adding the server name. +Register or log in to a Docker Registry Service located on the specified +`SERVER`. You can specify a URL or a `hostname` for the `SERVER` value. If you +do not specify a `SERVER`, the command uses Docker's public registry located at +`https://registry-1.docker.io/` by default. To get a username/password for Docker's public registry, create an account on Docker Hub. -This stores encoded credentials in `$HOME/.dockercfg` on Linux or `%USERPROFILE%/.dockercfg` -on Windows. +You can log into any public or private repository for which you have +credentials. When you log in, the command stores encoded credentials in +`$HOME/.dockercfg` on Linux or `%USERPROFILE%/.dockercfg` on Windows. # OPTIONS **-e**, **--email**="" @@ -35,7 +37,7 @@ on Windows. # EXAMPLES -## Login to a local registry +## Login to a registry on your localhost # docker login localhost:8080 @@ -46,3 +48,4 @@ on Windows. April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on docker.com source material and internal work. June 2014, updated by Sven Dowideit +April 2015, updated by Mary Anthony for v2 diff --git a/docs/man/docker-logout.1.md b/docs/man/docker-logout.1.md index ac6dc7e84..d464f00fd 100644 --- a/docs/man/docker-logout.1.md +++ b/docs/man/docker-logout.1.md @@ -2,23 +2,24 @@ % Docker Community % JUNE 2014 # NAME -docker-logout - Log out from a Docker registry, if no server is specified "https://index.docker.io/v1/" is the default. +docker-logout - Log out from a Docker Registry Service. # SYNOPSIS **docker logout** [SERVER] # DESCRIPTION -Log the user out from a Docker registry, if no server is -specified "https://index.docker.io/v1/" is the default. If you want to -log out from a private registry you can specify this by adding the server name. +Log out of a Docker Registry Service located on the specified `SERVER`. You can +specify a URL or a `hostname` for the `SERVER` value. If you do not specify a +`SERVER`, the command attempts to log you out of Docker's public registry +located at `https://registry-1.docker.io/` by default. # OPTIONS There are no available options. # EXAMPLES -## Log out from a local registry +## Log out from a registry on your localhost # docker logout localhost:8080 @@ -28,3 +29,4 @@ There are no available options. # HISTORY June 2014, Originally compiled by Daniel, Dao Quang Minh (daniel at nitrous dot io) July 2014, updated by Sven Dowideit +April 2015, updated by Mary Anthony for v2 diff --git a/docs/man/docker-pull.1.md b/docs/man/docker-pull.1.md index 5572542e4..30a949e81 100644 --- a/docs/man/docker-pull.1.md +++ b/docs/man/docker-pull.1.md @@ -2,7 +2,7 @@ % Docker Community % JUNE 2014 # NAME -docker-pull - Pull an image or a repository from the registry +docker-pull - Pull an image or a repository from a registry # SYNOPSIS **docker pull** @@ -12,10 +12,12 @@ NAME[:TAG] | [REGISTRY_HOST[:REGISTRY_PORT]/]NAME[:TAG] # DESCRIPTION -This command pulls down an image or a repository from the registry. If +This command pulls down an image or a repository from a registry. If there is more than one image for a repository (e.g., fedora) then all images for that repository name are pulled down including any tags. -It is also possible to specify a non-default registry to pull from. + +If you do not specify a `REGISTRY_HOST`, the command uses Docker's public +registry located at `registry-1.docker.io` by default. # OPTIONS **-a**, **--all-tags**=*true*|*false* @@ -45,7 +47,7 @@ It is also possible to specify a non-default registry to pull from. fedora heisenbug 105182bb5e8b 5 days ago 372.7 MB fedora latest 105182bb5e8b 5 days ago 372.7 MB -# Pull an image, manually specifying path to the registry and tag +# Pull an image, manually specifying path to Docker's public registry and tag # Note that if the image is previously downloaded then the status would be # 'Status: Image is up to date for registry.hub.docker.com/fedora:20' @@ -68,3 +70,4 @@ based on docker.com source material and internal work. June 2014, updated by Sven Dowideit August 2014, updated by Sven Dowideit April 2015, updated by John Willis +April 2015, updated by Mary Anthony for v2 diff --git a/docs/man/docker-push.1.md b/docs/man/docker-push.1.md index 2d4dc8f89..b51bf5d28 100644 --- a/docs/man/docker-push.1.md +++ b/docs/man/docker-push.1.md @@ -2,18 +2,18 @@ % Docker Community % JUNE 2014 # NAME -docker-push - Push an image or a repository to the registry +docker-push - Push an image or a repository to a registry # SYNOPSIS **docker push** [**--help**] -NAME[:TAG] +NAME[:TAG] | [REGISTRY_HOST[:REGISTRY_PORT]/]NAME[:TAG] # DESCRIPTION -Push an image or a repository to a registry. The default registry is the Docker -Hub located at [hub.docker.com](https://hub.docker.com/). However the -image can be pushed to another, perhaps private, registry as demonstrated in -the example below. + +This command pushes an image or a repository to a registry. If you do not +specify a `REGISTRY_HOST`, the command uses Docker's public registry located at +`registry-1.docker.io` by default. # OPTIONS **--help** @@ -28,12 +28,10 @@ and then committing it to a new image name: # docker commit c16378f943fe rhel-httpd -Now push the image to the registry using the image ID. In this example -the registry is on host named registry-host and listening on port 5000. -Default Docker commands will push to the default `hub.docker.com` -registry. Instead, push to the local registry, which is on a host called -registry-host*. To do this, tag the image with the host name or IP -address, and the port of the registry: +Now, push the image to the registry using the image ID. In this example the +registry is on host named `registry-host` and listening on port `5000`. To do +this, tag the image with the host name or IP address, and the port of the +registry: # docker tag rhel-httpd registry-host:5000/myadmin/rhel-httpd # docker push registry-host:5000/myadmin/rhel-httpd @@ -49,3 +47,5 @@ listed. April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on docker.com source material and internal work. June 2014, updated by Sven Dowideit +April 2015, updated by Mary Anthony for v2 + diff --git a/docs/man/docker-rmi.1.md b/docs/man/docker-rmi.1.md index c1f131f40..c288a4e85 100644 --- a/docs/man/docker-rmi.1.md +++ b/docs/man/docker-rmi.1.md @@ -13,10 +13,9 @@ IMAGE [IMAGE...] # 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 **-f** option. To see all images on a host -use the **docker images** command. +Removes 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 +**-f** option. To see all images on a host use the **docker images** command. # OPTIONS **-f**, **--force**=*true*|*false* @@ -40,3 +39,4 @@ Here is an example of removing and image: April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on docker.com source material and internal work. June 2014, updated by Sven Dowideit +April 2015, updated by Mary Anthony for v2 diff --git a/docs/man/docker-search.1.md b/docs/man/docker-search.1.md index 38fa92f17..6316008f5 100644 --- a/docs/man/docker-search.1.md +++ b/docs/man/docker-search.1.md @@ -14,10 +14,9 @@ TERM # DESCRIPTION -Search an index for an image with that matches the term TERM. The table -of images returned displays the name, description (truncated by default), -number of stars awarded, whether the image is official, and whether it -is automated. +Search Docker Hub for an image with that matches the specified `TERM`. The table +of images returned displays the name, description (truncated by default), number +of stars awarded, whether the image is official, and whether it is automated. *Note* - Search queries will only return up to 25 results @@ -36,9 +35,9 @@ is automated. # EXAMPLES -## Search the registry for ranked images +## Search Docker Hub for ranked images -Search the registry for the term 'fedora' and only display those images +Search a registry for the term 'fedora' and only display those images ranked 3 or higher: $ docker search -s 3 fedora @@ -48,9 +47,9 @@ ranked 3 or higher: mattdm/fedora-small A small Fedora image on which to build. Co... 8 goldmann/wildfly A WildFly application server running on a ... 3 [OK] -## Search the registry for automated images +## Search Docker Hub for automated images -Search the registry for the term 'fedora' and only display automated images +Search Docker Hub for the term 'fedora' and only display automated images ranked 1 or higher: $ docker search -s 1 -t fedora @@ -62,3 +61,5 @@ ranked 1 or higher: April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on docker.com source material and internal work. June 2014, updated by Sven Dowideit +April 2015, updated by Mary Anthony for v2 + diff --git a/docs/man/docker-tag.1.md b/docs/man/docker-tag.1.md index 20125e5df..898ecd8a1 100644 --- a/docs/man/docker-tag.1.md +++ b/docs/man/docker-tag.1.md @@ -8,11 +8,14 @@ docker-tag - Tag an image into a repository **docker tag** [**-f**|**--force**[=*false*]] [**--help**] -IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG] +IMAGE[:TAG] [REGISTRY_HOST/][USERNAME/]NAME[:TAG] # DESCRIPTION -This will give a new alias to an image in the repository. This refers to the -entire image name including the optional TAG after the ':'. +Assigns a new alias to an image in a registry. An alias refers to the +entire image name including the optional `TAG` after the ':'. + +If you do not specify a `REGISTRY_HOST`, the command uses Docker's public +registry located at `registry-1.docker.io` by default. # "OPTIONS" **-f**, **--force**=*true*|*false* @@ -58,3 +61,5 @@ April 2014, Originally compiled by William Henry (whenry at redhat dot com) based on docker.com source material and internal work. June 2014, updated by Sven Dowideit July 2014, updated by Sven Dowideit +April 2015, updated by Mary Anthony for v2 + diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index c9fe3eae9..bcb9d2541 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -172,10 +172,10 @@ inside it) Load an image from a tar archive **docker-login(1)** - Register or Login to a Docker registry server + Register or login to a Docker Registry Service **docker-logout(1)** - Log the user out of a Docker registry server + Log the user out of a Docker Registry Service **docker-logs(1)** Fetch the logs of a container @@ -190,10 +190,10 @@ inside it) List containers **docker-pull(1)** - Pull an image or a repository from a Docker registry server + Pull an image or a repository from a Docker Registry Service **docker-push(1)** - Push an image or a repository to a Docker registry server + Push an image or a repository to a Docker Registry Service **docker-restart(1)** Restart a running container From 3bea892d5458fc627f2ebe26f44a09df018d1fcc Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 1 Apr 2015 11:56:30 +0300 Subject: [PATCH 233/999] api/server: fix profiler HTTP serving Signed-off-by: Cristian Staretu --- api/server/profiler.go | 22 ++++------------------ api/server/server.go | 2 +- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/api/server/profiler.go b/api/server/profiler.go index f27dc6cca..eebfe6933 100644 --- a/api/server/profiler.go +++ b/api/server/profiler.go @@ -9,12 +9,9 @@ import ( "github.com/gorilla/mux" ) -func NewProfiler() http.Handler { - var ( - p = &Profiler{} - r = mux.NewRouter() - ) - r.HandleFunc("/vars", p.expVars) +func ProfilerSetup(mainRouter *mux.Router, path string) { + var r = mainRouter.PathPrefix(path).Subrouter() + r.HandleFunc("/vars", expVars) r.HandleFunc("/pprof/", pprof.Index) r.HandleFunc("/pprof/cmdline", pprof.Cmdline) r.HandleFunc("/pprof/profile", pprof.Profile) @@ -23,21 +20,10 @@ func NewProfiler() http.Handler { r.HandleFunc("/pprof/heap", pprof.Handler("heap").ServeHTTP) r.HandleFunc("/pprof/goroutine", pprof.Handler("goroutine").ServeHTTP) r.HandleFunc("/pprof/threadcreate", pprof.Handler("threadcreate").ServeHTTP) - p.r = r - return p -} - -// Profiler enables pprof and expvar support via a HTTP API. -type Profiler struct { - r *mux.Router -} - -func (p *Profiler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - p.r.ServeHTTP(w, r) } // Replicated from expvar.go as not public. -func (p *Profiler) expVars(w http.ResponseWriter, r *http.Request) { +func expVars(w http.ResponseWriter, r *http.Request) { first := true w.Header().Set("Content-Type", "application/json; charset=utf-8") fmt.Fprintf(w, "{\n") diff --git a/api/server/server.go b/api/server/server.go index c541c7624..eef77cc5d 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1300,7 +1300,7 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local func createRouter(eng *engine.Engine, logging, enableCors bool, corsHeaders string, dockerVersion string) *mux.Router { r := mux.NewRouter() if os.Getenv("DEBUG") != "" { - r.Handle("/debug", NewProfiler()) + ProfilerSetup(r, "/debug/") } m := map[string]map[string]HttpApiFunc{ "GET": { From 8fa03df8850c6eb3ecdce1266c72ed8d7af3a5bb Mon Sep 17 00:00:00 2001 From: Chen Hanxiao Date: Thu, 2 Apr 2015 05:01:00 -0400 Subject: [PATCH 234/999] docs: keep the style of docker-commit consistent with docker-import add a pair of accent mark around dockerfile commands in docker-commit, as the same thing in docker-import. Signed-off-by: Chen Hanxiao --- docs/man/docker-commit.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/man/docker-commit.1.md b/docs/man/docker-commit.1.md index e3459197a..be70fdaa7 100644 --- a/docs/man/docker-commit.1.md +++ b/docs/man/docker-commit.1.md @@ -22,7 +22,7 @@ Using an existing container's name or ID you can create a new image. **-c** , **--change**=[] Apply specified Dockerfile instructions while committing the image - Supported Dockerfile instructions: ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|LABEL|VOLUME|WORKDIR|COPY + Supported Dockerfile instructions: `ADD`|`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`FROM`|`MAINTAINER`|`RUN`|`USER`|`LABEL`|`VOLUME`|`WORKDIR`|`COPY` **--help** Print usage statement From 6ac845edadbf6374a9ee53f105c34265aed32d42 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Thu, 2 Apr 2015 08:39:12 -0400 Subject: [PATCH 235/999] Add cpuguy83 to maintainers.people Signed-off-by: Brian Goff --- MAINTAINERS | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 052b7e783..546e456b0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -500,6 +500,11 @@ made through a pull request. Email = "ben@firshman.co.uk" GitHub = "bfirsh" + [people.cpuguy83] + Name = "Brian Goff" + Email = "cpuguy83@gmail.com" + Github = "cpuguy83" + [people.crosbymichael] Name = "Michael Crosby" Email = "crosbymichael@gmail.com" From 11287f77144954fe82f75ade671da739559c664f Mon Sep 17 00:00:00 2001 From: Peter Esbensen Date: Tue, 31 Mar 2015 22:38:23 -0700 Subject: [PATCH 236/999] Added unit tests for stringutils GenerateRandomAlphaOnlyString and GenerateRandomAsciiString Signed-off-by: Peter Esbensen --- pkg/stringutils/stringutils_test.go | 55 +++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/pkg/stringutils/stringutils_test.go b/pkg/stringutils/stringutils_test.go index 60b848ff5..e9da58b82 100644 --- a/pkg/stringutils/stringutils_test.go +++ b/pkg/stringutils/stringutils_test.go @@ -23,3 +23,58 @@ func TestRandomStringUniqueness(t *testing.T) { set[str] = struct{}{} } } + +func testLengthHelper(generator func(int) string, t *testing.T) { + expectedLength := 20 + s := generator(expectedLength) + if len(s) != expectedLength { + t.Fatalf("Length of %s was %d but expected length %d", s, len(s), expectedLength) + } +} + +func testUniquenessHelper(generator func(int) string, t *testing.T) { + repeats := 25 + set := make(map[string]struct{}, repeats) + for i := 0; i < repeats; i = i + 1 { + str := generator(64) + if len(str) != 64 { + t.Fatalf("Id returned is incorrect: %s", str) + } + if _, ok := set[str]; ok { + t.Fatalf("Random number is repeated") + } + set[str] = struct{}{} + } +} + +func isASCII(s string) bool { + for _, c := range s { + if c > 127 { + return false + } + } + return true +} + +func TestGenerateRandomAlphaOnlyStringLength(t *testing.T) { + testLengthHelper(GenerateRandomAlphaOnlyString, t) +} + +func TestGenerateRandomAlphaOnlyStringUniqueness(t *testing.T) { + testUniquenessHelper(GenerateRandomAlphaOnlyString, t) +} + +func TestGenerateRandomAsciiStringLength(t *testing.T) { + testLengthHelper(GenerateRandomAsciiString, t) +} + +func TestGenerateRandomAsciiStringUniqueness(t *testing.T) { + testUniquenessHelper(GenerateRandomAsciiString, t) +} + +func TestGenerateRandomAsciiStringIsAscii(t *testing.T) { + str := GenerateRandomAsciiString(64) + if !isASCII(str) { + t.Fatalf("%s contained non-ascii characters", str) + } +} From 6896016b7c7a95ac33c77a222c359cf35a471eb9 Mon Sep 17 00:00:00 2001 From: Peter Esbensen Date: Wed, 1 Apr 2015 07:21:07 -0700 Subject: [PATCH 237/999] Fixes #11721 removed GenerateRandomString Signed-off-by: Peter Esbensen gofmt Signed-off-by: Peter Esbensen --- engine/engine.go | 4 ++-- pkg/stringutils/stringutils.go | 13 ------------- pkg/stringutils/stringutils_test.go | 26 ++------------------------ utils/utils.go | 4 ++-- 4 files changed, 6 insertions(+), 41 deletions(-) diff --git a/engine/engine.go b/engine/engine.go index 1090675df..79fae51cc 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -11,7 +11,7 @@ import ( "time" "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/stringutils" + "github.com/docker/docker/pkg/stringid" ) // Installer is a standard interface for objects which can "install" themselves @@ -78,7 +78,7 @@ func (eng *Engine) RegisterCatchall(catchall Handler) { func New() *Engine { eng := &Engine{ handlers: make(map[string]Handler), - id: stringutils.GenerateRandomString(), + id: stringid.GenerateRandomID(), Stdout: os.Stdout, Stderr: os.Stderr, Stdin: os.Stdin, diff --git a/pkg/stringutils/stringutils.go b/pkg/stringutils/stringutils.go index bcb0ece57..f5f07dd18 100644 --- a/pkg/stringutils/stringutils.go +++ b/pkg/stringutils/stringutils.go @@ -1,23 +1,10 @@ package stringutils import ( - "crypto/rand" - "encoding/hex" - "io" mathrand "math/rand" "time" ) -// Generate 32 chars random string -func GenerateRandomString() string { - id := make([]byte, 32) - - if _, err := io.ReadFull(rand.Reader, id); err != nil { - panic(err) // This shouldn't happen - } - return hex.EncodeToString(id) -} - // Generate alpha only random stirng with length n func GenerateRandomAlphaOnlyString(n int) string { // make a really long string diff --git a/pkg/stringutils/stringutils_test.go b/pkg/stringutils/stringutils_test.go index e9da58b82..a5a01b4a0 100644 --- a/pkg/stringutils/stringutils_test.go +++ b/pkg/stringutils/stringutils_test.go @@ -2,34 +2,12 @@ package stringutils import "testing" -func TestRandomString(t *testing.T) { - str := GenerateRandomString() - if len(str) != 64 { - t.Fatalf("Id returned is incorrect: %s", str) - } -} - -func TestRandomStringUniqueness(t *testing.T) { - repeats := 25 - set := make(map[string]struct{}, repeats) - for i := 0; i < repeats; i = i + 1 { - str := GenerateRandomString() - if len(str) != 64 { - t.Fatalf("Id returned is incorrect: %s", str) - } - if _, ok := set[str]; ok { - t.Fatalf("Random number is repeated") - } - set[str] = struct{}{} - } -} - func testLengthHelper(generator func(int) string, t *testing.T) { expectedLength := 20 s := generator(expectedLength) if len(s) != expectedLength { t.Fatalf("Length of %s was %d but expected length %d", s, len(s), expectedLength) - } + } } func testUniquenessHelper(generator func(int) string, t *testing.T) { @@ -65,7 +43,7 @@ func TestGenerateRandomAlphaOnlyStringUniqueness(t *testing.T) { } func TestGenerateRandomAsciiStringLength(t *testing.T) { - testLengthHelper(GenerateRandomAsciiString, t) + testLengthHelper(GenerateRandomAsciiString, t) } func TestGenerateRandomAsciiStringUniqueness(t *testing.T) { diff --git a/utils/utils.go b/utils/utils.go index d0e76bf23..5084639db 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -24,7 +24,7 @@ import ( "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/jsonmessage" - "github.com/docker/docker/pkg/stringutils" + "github.com/docker/docker/pkg/stringid" ) type KeyValuePair struct { @@ -313,7 +313,7 @@ var globalTestID string // new directory. func TestDirectory(templateDir string) (dir string, err error) { if globalTestID == "" { - globalTestID = stringutils.GenerateRandomString()[:4] + globalTestID = stringid.GenerateRandomID()[:4] } prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, GetCallerName(2)) if prefix == "" { From cfa8aaf16f1f3b777851555afc83a1112c4f8879 Mon Sep 17 00:00:00 2001 From: unclejack Date: Thu, 2 Apr 2015 18:26:29 +0300 Subject: [PATCH 238/999] integration-cli: make TestPsGroupPortRange fast Signed-off-by: Cristian Staretu --- integration-cli/docker_cli_ps_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index 1d32a9320..8e634e3b0 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -638,7 +638,7 @@ func TestPsLinkedWithNoTrunc(t *testing.T) { func TestPsGroupPortRange(t *testing.T) { defer deleteAllContainers() - portRange := "3300-3900" + portRange := "3800-3900" out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "porttest", "-p", portRange+":"+portRange, "busybox", "top")) if err != nil { t.Fatal(out, err) From 132da3f036287896cb3ba9f9f2573ad36bbb69cc Mon Sep 17 00:00:00 2001 From: unclejack Date: Wed, 1 Apr 2015 20:15:50 +0300 Subject: [PATCH 239/999] daemon/logger/jsonfilelog: avoid some allocations Signed-off-by: Cristian Staretu --- daemon/logger/jsonfilelog/jsonfilelog.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/daemon/logger/jsonfilelog/jsonfilelog.go b/daemon/logger/jsonfilelog/jsonfilelog.go index faa6bf92e..50293181f 100644 --- a/daemon/logger/jsonfilelog/jsonfilelog.go +++ b/daemon/logger/jsonfilelog/jsonfilelog.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/daemon/logger" "github.com/docker/docker/pkg/jsonlog" + "github.com/docker/docker/pkg/timeutils" ) // JSONFileLogger is Logger implementation for default docker logging: @@ -33,7 +34,11 @@ func New(filename string) (logger.Logger, error) { func (l *JSONFileLogger) Log(msg *logger.Message) error { l.mu.Lock() defer l.mu.Unlock() - err := (&jsonlog.JSONLog{Log: string(msg.Line) + "\n", Stream: msg.Source, Created: msg.Timestamp}).MarshalJSONBuf(l.buf) + timestamp, err := timeutils.FastMarshalJSON(msg.Timestamp) + if err != nil { + return err + } + err = (&jsonlog.JSONLogBytes{Log: append(msg.Line, '\n'), Stream: msg.Source, Created: timestamp}).MarshalJSONBuf(l.buf) if err != nil { return err } From 02c2308e39930677a7f4bb7f4215331a4d87566f Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Wed, 1 Apr 2015 10:21:15 -0700 Subject: [PATCH 240/999] Properly stop test daemon in integration-cli Always stop the test daemon in an attempt to fix race conditions in subsequent tests. Signed-off-by: Arnaud Porterie --- integration-cli/docker_cli_daemon_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 7531e431c..aae7e374c 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -329,6 +329,7 @@ func TestDaemonVolumesBindsRefs(t *testing.T) { if err := d.StartWithBusybox(); err != nil { t.Fatal(err) } + defer d.Stop() tmp, err := ioutil.TempDir(os.TempDir(), "") if err != nil { @@ -418,6 +419,7 @@ func TestDaemonUpgradeWithVolumes(t *testing.T) { if err := d.StartWithBusybox("-g", graphDir); err != nil { t.Fatal(err) } + defer d.Stop() tmpDir := filepath.Join(os.TempDir(), "test") defer os.RemoveAll(tmpDir) @@ -516,6 +518,7 @@ func TestDaemonUlimitDefaults(t *testing.T) { if err := d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil { t.Fatal(err) } + defer d.Stop() out, err := d.Cmd("run", "--ulimit", "nproc=2048", "--name=test", "busybox", "/bin/sh", "-c", "echo $(ulimit -n); echo $(ulimit -p)") if err != nil { @@ -569,6 +572,7 @@ func TestDaemonRestartRenameContainer(t *testing.T) { if err := d.StartWithBusybox(); err != nil { t.Fatal(err) } + defer d.Stop() if out, err := d.Cmd("run", "--name=test", "busybox"); err != nil { t.Fatal(err, out) @@ -760,6 +764,7 @@ func TestDaemonDots(t *testing.T) { if err := d.StartWithBusybox(); err != nil { t.Fatal(err) } + defer d.Stop() // Now create 4 containers if _, err := d.Cmd("create", "busybox"); err != nil { @@ -813,6 +818,7 @@ func TestDaemonUnixSockCleanedUp(t *testing.T) { if err := d.Start("--host", "unix://"+sockPath); err != nil { t.Fatal(err) } + defer d.Stop() if _, err := os.Stat(sockPath); err != nil { t.Fatal("socket does not exist") From e07d3cd9acf14219f33e12375fb8c2e3fe02ad0c Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Thu, 2 Apr 2015 16:47:14 -0400 Subject: [PATCH 241/999] devmapper: Fix libdm logging There are issues with libdm logging. Right now if docker daemon is run in debug mode, logging by libdm is too verbose. And if a device can't be removed, thousands of messages fill the console and one can not see what's going on. This patch removes devicemapper.LogInitVerbose() call as that call will only work if docker was not registering its own log handler with libdm. For some reason docker registers one with libdm and libdm hands over all the messages to docker (including debug ones). And now it is up to devmapper backend to figure out which ones should go to console and which ones should not. So by default log only fatal messages from libdm. One can easily modify the code to change it for debugging purposes. Signed-off-by: Vivek Goyal --- daemon/graphdriver/devmapper/deviceset.go | 27 ++++++++++++++--------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 4d35adabc..5515df996 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -33,6 +33,10 @@ var ( DefaultThinpBlockSize uint32 = 128 // 64K = 128 512b sectors MaxDeviceId int = 0xffffff // 24 bit, pool limit DeviceIdMapSz int = (MaxDeviceId + 1) / 8 + // We retry device removal so many a times that even error messages + // will fill up console during normal operation. So only log Fatal + // messages by default. + DMLogLevel int = devicemapper.LogLevelFatal ) const deviceSetMetaFile string = "deviceset-metadata" @@ -723,14 +727,22 @@ func setCloseOnExec(name string) { } func (devices *DeviceSet) DMLog(level int, file string, line int, dmError int, message string) { - if level >= devicemapper.LogLevelDebug { - // (vbatts) libdm debug is very verbose. If you're debugging libdm, you can - // comment out this check yourself - level = devicemapper.LogLevelInfo + // By default libdm sends us all the messages including debug ones. + // We need to filter out messages here and figure out which one + // should be printed. + if level > DMLogLevel { + return } // FIXME(vbatts) push this back into ./pkg/devicemapper/ - logrus.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) + if level <= devicemapper.LogLevelErr { + logrus.Errorf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) + } else if level <= devicemapper.LogLevelInfo { + logrus.Infof("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) + } else { + // FIXME(vbatts) push this back into ./pkg/devicemapper/ + logrus.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) + } } func major(device uint64) uint64 { @@ -947,11 +959,6 @@ func (devices *DeviceSet) closeTransaction() error { } func (devices *DeviceSet) initDevmapper(doInit bool) error { - if os.Getenv("DEBUG") != "" { - devicemapper.LogInitVerbose(devicemapper.LogLevelDebug) - } else { - devicemapper.LogInitVerbose(devicemapper.LogLevelWarn) - } // give ourselves to libdm as a log handler devicemapper.LogInit(devices) From cb7c893275c32ddfa775c3f22869a9c211024c71 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Thu, 2 Apr 2015 16:47:14 -0400 Subject: [PATCH 242/999] devicemapper: Remove debug messages from RemoveDevice() devmapper graph driver retries device removal 1000 times in case of failure and if this fills up console with 1000 messages (when daemon is running in debug mode). So remove these debug messages. Signed-off-by: Vivek Goyal --- daemon/graphdriver/devmapper/deviceset.go | 3 +++ pkg/devicemapper/devmapper.go | 2 -- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 5515df996..5923811d2 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -1249,6 +1249,9 @@ func (devices *DeviceSet) deactivateDevice(info *DevInfo) error { func (devices *DeviceSet) removeDeviceAndWait(devname string) error { var err error + logrus.Debugf("[devmapper] removeDeviceAndWait START(%s)", devname) + defer logrus.Debugf("[devmapper] removeDeviceAndWait END(%s)", devname) + for i := 0; i < 1000; i++ { err = devicemapper.RemoveDevice(devname) if err == nil { diff --git a/pkg/devicemapper/devmapper.go b/pkg/devicemapper/devmapper.go index e8341692a..f75031996 100644 --- a/pkg/devicemapper/devmapper.go +++ b/pkg/devicemapper/devmapper.go @@ -349,8 +349,6 @@ func CookieSupported() bool { // Useful helper for cleanup func RemoveDevice(name string) error { - logrus.Debugf("[devmapper] RemoveDevice START(%s)", name) - defer logrus.Debugf("[devmapper] RemoveDevice END(%s)", name) task, err := TaskCreateNamed(DeviceRemove, name) if task == nil { return err From 665656afbb8932a11c69c0cd79e21a768aa46d38 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Thu, 2 Apr 2015 16:47:14 -0400 Subject: [PATCH 243/999] devmapper: Use a pointer as argument to deferred function UdevWait() UdevWait() is deferred and takes uint cookie as an argument. As arguments to deferred functions are calculated at the time of call, it is possible that any update to cookie later by libdm are not taken into account when UdevWait() is called. Hence use a pointer to uint as argument to UdevWait() function. Signed-off-by: Vivek Goyal --- pkg/devicemapper/devmapper.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/devicemapper/devmapper.go b/pkg/devicemapper/devmapper.go index f75031996..bb89f7fac 100644 --- a/pkg/devicemapper/devmapper.go +++ b/pkg/devicemapper/devmapper.go @@ -284,9 +284,9 @@ func FindLoopDeviceFor(file *os.File) *os.File { return nil } -func UdevWait(cookie uint) error { - if res := DmUdevWait(cookie); res != 1 { - logrus.Debugf("Failed to wait on udev cookie %d", cookie) +func UdevWait(cookie *uint) error { + if res := DmUdevWait(*cookie); res != 1 { + logrus.Debugf("Failed to wait on udev cookie %d", *cookie) return ErrUdevWait } return nil @@ -358,7 +358,7 @@ func RemoveDevice(name string) error { if err := task.SetCookie(&cookie, 0); err != nil { return fmt.Errorf("Can not set cookie: %s", err) } - defer UdevWait(cookie) + defer UdevWait(&cookie) dmSawBusy = false // reset before the task is run if err = task.Run(); err != nil { @@ -425,7 +425,7 @@ func CreatePool(poolName string, dataFile, metadataFile *os.File, poolBlockSize if err := task.SetCookie(&cookie, flags); err != nil { return fmt.Errorf("Can't set cookie %s", err) } - defer UdevWait(cookie) + defer UdevWait(&cookie) if err := task.Run(); err != nil { return fmt.Errorf("Error running DeviceCreate (CreatePool) %s", err) @@ -556,7 +556,7 @@ func ResumeDevice(name string) error { if err := task.SetCookie(&cookie, 0); err != nil { return fmt.Errorf("Can't set cookie %s", err) } - defer UdevWait(cookie) + defer UdevWait(&cookie) if err := task.Run(); err != nil { return fmt.Errorf("Error running DeviceResume %s", err) @@ -632,7 +632,7 @@ func ActivateDevice(poolName string, name string, deviceId int, size uint64) err return fmt.Errorf("Can't set cookie %s", err) } - defer UdevWait(cookie) + defer UdevWait(&cookie) if err := task.Run(); err != nil { return fmt.Errorf("Error running DeviceCreate (ActivateDevice) %s", err) From dbf04ec4e2a6b4fe73f7f300918a906c0ff1a37b Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Thu, 2 Apr 2015 16:47:14 -0400 Subject: [PATCH 244/999] devmapper: Remove extra wait after device removal Currently in device removal path (device deactivation), we wait for 10 seconds for devive to actually go away. waitRemove(). In current code this is not required. If dm removal task has completed and one has done the wait on udev cookie, then device is gone and there is no need to write another loop to wait for device removal. This patch removes the waitRemove() which waits for 10 seconds after device removal. This seems unnecessary. Signed-off-by: Vivek Goyal --- daemon/graphdriver/devmapper/deviceset.go | 52 +++-------------------- 1 file changed, 7 insertions(+), 45 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 5923811d2..221c87eb4 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -1157,7 +1157,7 @@ func (devices *DeviceSet) deleteDevice(info *DevInfo) error { devinfo, _ := devicemapper.GetInfo(info.Name()) if devinfo != nil && devinfo.Exists != 0 { - if err := devices.removeDeviceAndWait(info.Name()); err != nil { + if err := devices.removeDevice(info.Name()); err != nil { logrus.Debugf("Error removing device: %s", err) return err } @@ -1236,7 +1236,7 @@ func (devices *DeviceSet) deactivateDevice(info *DevInfo) error { return err } if devinfo.Exists != 0 { - if err := devices.removeDeviceAndWait(info.Name()); err != nil { + if err := devices.removeDevice(info.Name()); err != nil { return err } } @@ -1244,13 +1244,12 @@ func (devices *DeviceSet) deactivateDevice(info *DevInfo) error { return nil } -// Issues the underlying dm remove operation and then waits -// for it to finish. -func (devices *DeviceSet) removeDeviceAndWait(devname string) error { +// Issues the underlying dm remove operation. +func (devices *DeviceSet) removeDevice(devname string) error { var err error - logrus.Debugf("[devmapper] removeDeviceAndWait START(%s)", devname) - defer logrus.Debugf("[devmapper] removeDeviceAndWait END(%s)", devname) + logrus.Debugf("[devmapper] removeDevice START(%s)", devname) + defer logrus.Debugf("[devmapper] removeDevice END(%s)", devname) for i := 0; i < 1000; i++ { err = devicemapper.RemoveDevice(devname) @@ -1267,45 +1266,8 @@ func (devices *DeviceSet) removeDeviceAndWait(devname string) error { time.Sleep(10 * time.Millisecond) devices.Lock() } - if err != nil { - return err - } - if err := devices.waitRemove(devname); err != nil { - return err - } - return nil -} - -// waitRemove blocks until either: -// a) the device registered at - is removed, -// or b) the 10 second timeout expires. -func (devices *DeviceSet) waitRemove(devname string) error { - logrus.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, devname) - defer logrus.Debugf("[deviceset %s] waitRemove(%s) END", devices.devicePrefix, devname) - i := 0 - for ; i < 1000; i++ { - devinfo, err := devicemapper.GetInfo(devname) - if err != nil { - // If there is an error we assume the device doesn't exist. - // The error might actually be something else, but we can't differentiate. - return nil - } - if i%100 == 0 { - logrus.Debugf("Waiting for removal of %s: exists=%d", devname, devinfo.Exists) - } - if devinfo.Exists == 0 { - break - } - - 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) - } - return nil + return err } // waitClose blocks until either: From f74d12012c21349b2bd51d9c395a99331ff0a9a5 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Thu, 2 Apr 2015 16:47:14 -0400 Subject: [PATCH 245/999] devmapper: Remove call to waitClose() During device removal, we are first waiting for device to close() in a tight loop for 10 seconds. I am not sure why do we need it. First of all we come here once the umount() is successful so device should be free. For some reason of device is temporarily busy, then removeDevice() logic retries device removal logic in a loop for 10 seconds and that should cover it. Can't see why one more 10 seoncds loop is required before attempting device removal. One loop should be able to cover all the temporary device busy conditions and if condition is not temporary then 10 seconds loop is not going to help anyway. So instead of two loops of 10 seconds each, I am converting it to a single loop of 20 seconds. May be 10 second loop is good enough but for now I am keeping it 20 seconds to avoid any regressions. Signed-off-by: Vivek Goyal --- daemon/graphdriver/devmapper/deviceset.go | 34 +---------------------- 1 file changed, 1 insertion(+), 33 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 221c87eb4..183ad9b6f 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -1225,12 +1225,6 @@ func (devices *DeviceSet) deactivateDevice(info *DevInfo) error { logrus.Debugf("[devmapper] deactivateDevice(%s)", info.Hash) defer logrus.Debugf("[devmapper] deactivateDevice END(%s)", info.Hash) - // Wait for the unmount to be effective, - // by watching the value of Info.OpenCount for the device - if err := devices.waitClose(info); err != nil { - logrus.Errorf("Error waiting for device %s to close: %s", info.Hash, err) - } - devinfo, err := devicemapper.GetInfo(info.Name()) if err != nil { return err @@ -1251,7 +1245,7 @@ func (devices *DeviceSet) removeDevice(devname string) error { logrus.Debugf("[devmapper] removeDevice START(%s)", devname) defer logrus.Debugf("[devmapper] removeDevice END(%s)", devname) - for i := 0; i < 1000; i++ { + for i := 0; i < 2000; i++ { err = devicemapper.RemoveDevice(devname) if err == nil { break @@ -1270,32 +1264,6 @@ func (devices *DeviceSet) removeDevice(devname string) error { return err } -// waitClose blocks until either: -// a) the device registered at - is closed, -// or b) the 10 second timeout expires. -func (devices *DeviceSet) waitClose(info *DevInfo) error { - i := 0 - for ; i < 1000; i++ { - devinfo, err := devicemapper.GetInfo(info.Name()) - if err != nil { - return err - } - if i%100 == 0 { - logrus.Debugf("Waiting for unmount of %s: opencount=%d", info.Hash, devinfo.OpenCount) - } - if devinfo.OpenCount == 0 { - break - } - devices.Unlock() - time.Sleep(10 * time.Millisecond) - devices.Lock() - } - if i == 1000 { - return fmt.Errorf("Timeout while waiting for device %s to close", info.Hash) - } - return nil -} - func (devices *DeviceSet) Shutdown() error { logrus.Debugf("[deviceset %s] Shutdown()", devices.devicePrefix) logrus.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) From c737800b7faced4b53854c8cb6766ebe58a3c3e9 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Thu, 2 Apr 2015 16:47:14 -0400 Subject: [PATCH 246/999] devmapper: Retry device removal after 100ms instead of 10ms Right now we try device removal at the interval of 10ms and keep on trying till either device is removed or 10 seconds are over. That means if device is busy, we will try 1000 times in those 10 seconds. Sounds too high a frequency of deivce removal retrial. All the logs are filled easily. I think it is a good idea to slow down a bit and retry at the interval of 100ms instead of 10ms. Signed-off-by: Vivek Goyal --- daemon/graphdriver/devmapper/deviceset.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 183ad9b6f..a5ad0e676 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -1245,7 +1245,7 @@ func (devices *DeviceSet) removeDevice(devname string) error { logrus.Debugf("[devmapper] removeDevice START(%s)", devname) defer logrus.Debugf("[devmapper] removeDevice END(%s)", devname) - for i := 0; i < 2000; i++ { + for i := 0; i < 200; i++ { err = devicemapper.RemoveDevice(devname) if err == nil { break @@ -1257,7 +1257,7 @@ func (devices *DeviceSet) removeDevice(devname string) error { // If we see EBUSY it may be a transient error, // sleep a bit a retry a few times. devices.Unlock() - time.Sleep(10 * time.Millisecond) + time.Sleep(100 * time.Millisecond) devices.Lock() } From d12fef1515cb3f0938ea6ed8cab8351e2df2753e Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 2 Apr 2015 14:12:14 -0700 Subject: [PATCH 247/999] Update libcontainaer to d00b8369852285d6a830a8d3b9 Fixes #12015 Signed-off-by: Michael Crosby --- hack/vendor.sh | 2 +- .../docker/libcontainer/apparmor/gen.go | 8 ++-- .../docker/libcontainer/cgroups/fs/freezer.go | 5 ++ .../libcontainer/cgroups/fs/freezer_test.go | 45 +++++++++++++++++ .../cgroups/systemd/apply_systemd.go | 11 +---- .../docker/libcontainer/container_linux.go | 4 +- .../docker/libcontainer/init_linux.go | 3 +- .../docker/libcontainer/nsinit/README.md | 48 +++++++++++-------- .../libcontainer/standard_init_linux.go | 10 ++-- 9 files changed, 96 insertions(+), 40 deletions(-) create mode 100644 vendor/src/github.com/docker/libcontainer/cgroups/fs/freezer_test.go diff --git a/hack/vendor.sh b/hack/vendor.sh index 0246f03cc..6552cab23 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -75,7 +75,7 @@ rm -rf src/github.com/docker/distribution mkdir -p src/github.com/docker/distribution mv tmp-digest src/github.com/docker/distribution/digest -clone git github.com/docker/libcontainer c8512754166539461fd860451ff1a0af7491c197 +clone git github.com/docker/libcontainer d00b8369852285d6a830a8d3b966608b2ed89705 # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) rm -rf src/github.com/docker/libcontainer/vendor eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')" diff --git a/vendor/src/github.com/docker/libcontainer/apparmor/gen.go b/vendor/src/github.com/docker/libcontainer/apparmor/gen.go index 825e646d9..4565f6dfe 100644 --- a/vendor/src/github.com/docker/libcontainer/apparmor/gen.go +++ b/vendor/src/github.com/docker/libcontainer/apparmor/gen.go @@ -67,12 +67,12 @@ func generateProfile(out io.Writer) error { data := &data{ Name: "docker-default", } - if tuntablesExists() { + if tunablesExists() { data.Imports = append(data.Imports, "#include ") } else { data.Imports = append(data.Imports, "@{PROC}=/proc/") } - if abstrctionsEsists() { + if abstractionsExists() { data.InnerImports = append(data.InnerImports, "#include ") } if err := compiled.Execute(out, data); err != nil { @@ -82,13 +82,13 @@ func generateProfile(out io.Writer) error { } // check if the tunables/global exist -func tuntablesExists() bool { +func tunablesExists() bool { _, err := os.Stat("/etc/apparmor.d/tunables/global") return err == nil } // check if abstractions/base exist -func abstrctionsEsists() bool { +func abstractionsExists() bool { _, err := os.Stat("/etc/apparmor.d/abstractions/base") return err == nil } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/freezer.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/freezer.go index fc8241d1b..1110e5ff1 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/freezer.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/freezer.go @@ -1,6 +1,7 @@ package fs import ( + "fmt" "strings" "time" @@ -41,6 +42,10 @@ func (s *FreezerGroup) Set(path string, cgroup *configs.Cgroup) error { } time.Sleep(1 * time.Millisecond) } + case configs.Undefined: + return nil + default: + return fmt.Errorf("Invalid argument '%s' to freezer.state", string(cgroup.Freezer)) } return nil diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/freezer_test.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/freezer_test.go new file mode 100644 index 000000000..9ff1886d2 --- /dev/null +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/freezer_test.go @@ -0,0 +1,45 @@ +package fs + +import ( + "testing" + + "github.com/docker/libcontainer/configs" +) + +func TestFreezerSetState(t *testing.T) { + helper := NewCgroupTestUtil("freezer", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "freezer.state": string(configs.Frozen), + }) + + helper.CgroupData.c.Freezer = configs.Thawed + freezer := &FreezerGroup{} + if err := freezer.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "freezer.state") + if err != nil { + t.Fatalf("Failed to parse freezer.state - %s", err) + } + if value != string(configs.Thawed) { + t.Fatal("Got the wrong value, set freezer.state failed.") + } +} + +func TestFreezerSetInvalidState(t *testing.T) { + helper := NewCgroupTestUtil("freezer", t) + defer helper.cleanup() + + const ( + invalidArg configs.FreezerState = "Invalid" + ) + + helper.CgroupData.c.Freezer = invalidArg + freezer := &FreezerGroup{} + if err := freezer.Set(helper.CgroupPath, helper.CgroupData.c); err == nil { + t.Fatal("Failed to return invalid argument error") + } +} diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go index 85ee5db06..dea196bd0 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go @@ -218,16 +218,7 @@ func (m *Manager) Apply(pid int) error { } paths := make(map[string]string) - for _, sysname := range []string{ - "devices", - "memory", - "cpu", - "cpuset", - "cpuacct", - "blkio", - "perf_event", - "freezer", - } { + for sysname := range subsystems { subsystemPath, err := getSubsystemPath(m.Cgroups, sysname) if err != nil { // Don't fail if a cgroup hierarchy was not found, just skip this subsystem diff --git a/vendor/src/github.com/docker/libcontainer/container_linux.go b/vendor/src/github.com/docker/libcontainer/container_linux.go index 54d40617e..3c077afbd 100644 --- a/vendor/src/github.com/docker/libcontainer/container_linux.go +++ b/vendor/src/github.com/docker/libcontainer/container_linux.go @@ -140,7 +140,9 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec. cmd.SysProcAttr = &syscall.SysProcAttr{} } cmd.ExtraFiles = []*os.File{childPipe} - cmd.SysProcAttr.Pdeathsig = syscall.SIGKILL + // NOTE: when running a container with no PID namespace and the parent process spawning the container is + // PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason + // even with the parent still running. if c.config.ParentDeathSignal > 0 { cmd.SysProcAttr.Pdeathsig = syscall.Signal(c.config.ParentDeathSignal) } diff --git a/vendor/src/github.com/docker/libcontainer/init_linux.go b/vendor/src/github.com/docker/libcontainer/init_linux.go index 0468b2e93..1786b1ed7 100644 --- a/vendor/src/github.com/docker/libcontainer/init_linux.go +++ b/vendor/src/github.com/docker/libcontainer/init_linux.go @@ -69,7 +69,8 @@ func newContainerInit(t initType, pipe *os.File) (initer, error) { }, nil case initStandard: return &linuxStandardInit{ - config: config, + parentPid: syscall.Getppid(), + config: config, }, nil } return nil, fmt.Errorf("unknown init type %q", t) diff --git a/vendor/src/github.com/docker/libcontainer/nsinit/README.md b/vendor/src/github.com/docker/libcontainer/nsinit/README.md index f321e2271..f2e66a866 100644 --- a/vendor/src/github.com/docker/libcontainer/nsinit/README.md +++ b/vendor/src/github.com/docker/libcontainer/nsinit/README.md @@ -5,13 +5,15 @@ It is able to spawn new containers or join existing containers. ### How to build? -First to add the `libcontainer/vendor` into your GOPATH. It's because something related with this [issue](https://github.com/docker/libcontainer/issues/210). +First add the `libcontainer/vendor` into your GOPATH. It's because libcontainer +vendors all its dependencies, so it can be built predictably. ``` export GOPATH=$GOPATH:/your/path/to/libcontainer/vendor ``` -Then get into the nsinit folder and get the imported file. Use `make` command to make the nsinit binary. +Then get into the nsinit folder and get the imported file. Use `make` command +to make the nsinit binary. ``` cd libcontainer/nsinit @@ -19,7 +21,8 @@ go get make ``` -We have finished compiling the nsinit package, but a root filesystem must be provided for use along with a container configuration file. +We have finished compiling the nsinit package, but a root filesystem must be +provided for use along with a container configuration file. Choose a proper place to run your container. For example we use `/busybox`. @@ -28,30 +31,37 @@ mkdir /busybox curl -sSL 'https://github.com/jpetazzo/docker-busybox/raw/buildroot-2014.11/rootfs.tar' | tar -xC /busybox ``` -Then you may need to write a configure file named `container.json` in the `/busybox` folder. -Environment, networking, and different capabilities for the container are specified in this file. -The configuration is used for each process executed inside the container -See the `sample_configs` folder for examples of what the container configuration should look like. +Then you may need to write a configuration file named `container.json` in the +`/busybox` folder. Environment, networking, and different capabilities for +the container are specified in this file. The configuration is used for each +process executed inside the container. + +See the `sample_configs` folder for examples of what the container configuration +should look like. ``` cp libcontainer/sample_configs/minimal.json /busybox/container.json cd /busybox ``` -Now the nsinit is ready to work. -To execute `/bin/bash` in the current directory as a container just run the following **as root**: +You can customize `container.json` per your needs. After that, nsinit is +ready to work. + +To execute `/bin/bash` in the current directory as a container just run the +following **as root**: + ```bash -nsinit exec --tty /bin/bash +nsinit exec --tty --config container.json /bin/bash ``` -If you wish to spawn another process inside the container while your -current bash session is running, run the same command again to -get another bash shell (or change the command). If the original -process (PID 1) dies, all other processes spawned inside the container -will be killed and the namespace will be removed. +If you wish to spawn another process inside the container while your current +bash session is running, run the same command again to get another bash shell +(or change the command). If the original process (PID 1) dies, all other +processes spawned inside the container will be killed and the namespace will +be removed. -You can identify if a process is running in a container by -looking to see if `state.json` is in the root of the directory. +You can identify if a process is running in a container by looking to see if +`state.json` is in the root of the directory. -You may also specify an alternate root place where -the `container.json` file is read and where the `state.json` file will be saved. +You may also specify an alternate root directory from where the `container.json` +file is read and where the `state.json` file will be saved. diff --git a/vendor/src/github.com/docker/libcontainer/standard_init_linux.go b/vendor/src/github.com/docker/libcontainer/standard_init_linux.go index 29619d3cd..282832b56 100644 --- a/vendor/src/github.com/docker/libcontainer/standard_init_linux.go +++ b/vendor/src/github.com/docker/libcontainer/standard_init_linux.go @@ -13,7 +13,8 @@ import ( ) type linuxStandardInit struct { - config *initConfig + parentPid int + config *initConfig } func (l *linuxStandardInit) Init() error { @@ -85,9 +86,10 @@ func (l *linuxStandardInit) Init() error { if err := pdeath.Restore(); err != nil { return err } - // Signal self if parent is already dead. Does nothing if running in a new - // PID namespace, as Getppid will always return 0. - if syscall.Getppid() == 1 { + // compare the parent from the inital start of the init process and make sure that it did not change. + // if the parent changes that means it died and we were reparened to something else so we should + // just kill ourself and not cause problems for someone else. + if syscall.Getppid() != l.parentPid { return syscall.Kill(syscall.Getpid(), syscall.SIGKILL) } return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ()) From 3e51a8147510fb59d6206fe7310feaebc2d69fbc Mon Sep 17 00:00:00 2001 From: Todd Whiteman Date: Thu, 2 Apr 2015 14:44:53 -0700 Subject: [PATCH 248/999] integration-cli: add check for TestPsListContainersSize when no containers are returned * when no containers are returned, go test would then aborts with: panic: runtime error: index out of range Signed-off-by: Todd Whiteman --- integration-cli/docker_cli_ps_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index 8e634e3b0..93d5b4995 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -286,6 +286,9 @@ func TestPsListContainersSize(t *testing.T) { t.Fatal(out, err) } lines := strings.Split(strings.Trim(out, "\n "), "\n") + if len(lines) != 2 { + t.Fatalf("Expected 2 lines for 'ps -s -n=1' output, got %d", len(lines)) + } sizeIndex := strings.Index(lines[0], "SIZE") idIndex := strings.Index(lines[0], "CONTAINER ID") foundID := lines[1][idIndex : idIndex+12] From 49c72506ace177e33d867123ca15f193c38bcf45 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Thu, 2 Apr 2015 15:52:34 -0700 Subject: [PATCH 249/999] Remove use of Table from 'docker diff' Signed-off-by: Doug Davis --- api/client/diff.go | 24 ++++++++++++++---------- api/types/types.go | 6 ++++++ daemon/changes.go | 12 ++---------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/api/client/diff.go b/api/client/diff.go index 0ba7b53f3..3f6c28384 100644 --- a/api/client/diff.go +++ b/api/client/diff.go @@ -1,37 +1,40 @@ package client import ( + "encoding/json" "fmt" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/archive" flag "github.com/docker/docker/pkg/mflag" ) // CmdDiff shows changes on a container's filesystem. // -// Each changed file is printed on a separate line, prefixed with a single character that indicates the status of the file: C (modified), A (added), or D (deleted). +// Each changed file is printed on a separate line, prefixed with a single +// character that indicates the status of the file: C (modified), A (added), +// or D (deleted). // // Usage: docker diff CONTAINER func (cli *DockerCli) CmdDiff(args ...string) error { cmd := cli.Subcmd("diff", "CONTAINER", "Inspect changes on a container's filesystem", true) cmd.Require(flag.Exact, 1) - cmd.ParseFlags(args, true) - body, _, err := readBody(cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil)) - + rdr, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil) if err != nil { return err } - outs := engine.NewTable("", 0) - if _, err := outs.ReadListFrom(body); err != nil { + changes := []types.ContainerChange{} + err = json.NewDecoder(rdr).Decode(&changes) + if err != nil { return err } - for _, change := range outs.Data { + + for _, change := range changes { var kind string - switch change.GetInt("Kind") { + switch change.Kind { case archive.ChangeModify: kind = "C" case archive.ChangeAdd: @@ -39,7 +42,8 @@ func (cli *DockerCli) CmdDiff(args ...string) error { case archive.ChangeDelete: kind = "D" } - fmt.Fprintf(cli.out, "%s %s\n", kind, change.Get("Path")) + fmt.Fprintf(cli.out, "%s %s\n", kind, change.Path) } + return nil } diff --git a/api/types/types.go b/api/types/types.go index 85b300290..f4c6dc34a 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -35,3 +35,9 @@ type ContainerWaitResponse struct { type ContainerCommitResponse struct { ID string `json:"Id"` } + +// GET "/containers/{name:.*}/changes" +type ContainerChange struct { + Kind int + Path string +} diff --git a/daemon/changes.go b/daemon/changes.go index aa9baab0a..7f261a8a7 100644 --- a/daemon/changes.go +++ b/daemon/changes.go @@ -1,6 +1,7 @@ package daemon import ( + "encoding/json" "fmt" "github.com/docker/docker/engine" @@ -17,21 +18,12 @@ func (daemon *Daemon) ContainerChanges(job *engine.Job) error { return err } - outs := engine.NewTable("", 0) changes, err := container.Changes() if err != nil { return err } - for _, change := range changes { - out := &engine.Env{} - if err := out.Import(change); err != nil { - return err - } - outs.Add(out) - } - - if _, err := outs.WriteListTo(job.Stdout); err != nil { + if err = json.NewEncoder(job.Stdout).Encode(changes); err != nil { return err } From 87e0e4eb431693cf1eeed777c6c4648ae0a94395 Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Fri, 3 Apr 2015 08:55:08 +0800 Subject: [PATCH 250/999] If docker search with --starts=${negative number}, it would show the warning. Signed-off-by: Yuan Sun --- api/client/search.go | 6 +++--- integration-cli/docker_cli_search_test.go | 24 +++++++++++++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/api/client/search.go b/api/client/search.go index 6f035bdf6..f3e6de1a7 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -21,7 +21,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds") automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds") - stars := cmd.Int([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least x stars") + stars := cmd.Uint([]string{"s", "#stars", "-stars"}, 0, "Only displays with at least x stars") cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) @@ -53,7 +53,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") for _, out := range outs.Data { - if ((*automated || *trusted) && (!out.GetBool("is_trusted") && !out.GetBool("is_automated"))) || (*stars > out.GetInt("star_count")) { + if ((*automated || *trusted) && (!out.GetBool("is_trusted") && !out.GetBool("is_automated"))) || (*stars > uint(out.GetInt("star_count"))) { continue } desc := strings.Replace(out.Get("description"), "\n", " ", -1) @@ -61,7 +61,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { if !*noTrunc && len(desc) > 45 { desc = utils.Trunc(desc, 42) + "..." } - fmt.Fprintf(w, "%s\t%s\t%d\t", out.Get("name"), desc, out.GetInt("star_count")) + fmt.Fprintf(w, "%s\t%s\t%d\t", out.Get("name"), desc, uint(out.GetInt("star_count"))) if out.GetBool("is_official") { fmt.Fprint(w, "[OK]") diff --git a/integration-cli/docker_cli_search_test.go b/integration-cli/docker_cli_search_test.go index 7d6017e3d..a3546103f 100644 --- a/integration-cli/docker_cli_search_test.go +++ b/integration-cli/docker_cli_search_test.go @@ -21,3 +21,27 @@ func TestSearchOnCentralRegistry(t *testing.T) { logDone("search - search for repositories named (or containing) 'Busybox base image.'") } + +func TestSearchStarsOptionWithWrongParameter(t *testing.T) { + searchCmdStarsChars := exec.Command(dockerBinary, "search", "--stars=a", "busybox") + out, exitCode, err := runCommandWithOutput(searchCmdStarsChars) + if err == nil || exitCode == 0 { + t.Fatalf("Should not get right information: %s, %v", out, err) + } + + if !strings.Contains(out, "invalid value") { + t.Fatal("couldn't find the invalid value warning") + } + + searchCmdStarsNegativeNumber := exec.Command(dockerBinary, "search", "-s=-1", "busybox") + out, exitCode, err = runCommandWithOutput(searchCmdStarsNegativeNumber) + if err == nil || exitCode == 0 { + t.Fatalf("Should not get right information: %s, %v", out, err) + } + + if !strings.Contains(out, "invalid value") { + t.Fatal("couldn't find the invalid value warning") + } + + logDone("search - Verify search with wrong parameter.") +} From a9443de7c5759398804acaa3fd9ccfb196557cfe Mon Sep 17 00:00:00 2001 From: Joey Gibson Date: Fri, 3 Apr 2015 00:03:30 -0400 Subject: [PATCH 251/999] Fix vet warnings in pkg/requestdecorator/requestdecorator_test.go #12041 Signed-off-by: Joey Gibson --- pkg/requestdecorator/requestdecorator_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/requestdecorator/requestdecorator_test.go b/pkg/requestdecorator/requestdecorator_test.go index 12e2194f0..b2c1fb3b9 100644 --- a/pkg/requestdecorator/requestdecorator_test.go +++ b/pkg/requestdecorator/requestdecorator_test.go @@ -180,7 +180,7 @@ func TestRequestFactory(t *testing.T) { requestFactory := NewRequestFactory(ad, uad) - if dlen := requestFactory.GetDecorators(); len(dlen) != 2 { + if dlen := len(requestFactory.GetDecorators()); dlen != 2 { t.Fatalf("Expected to have two decorators, got %d", dlen) } @@ -209,7 +209,7 @@ func TestRequestFactoryNewRequestWithDecorators(t *testing.T) { requestFactory := NewRequestFactory(ad) - if dlen := requestFactory.GetDecorators(); len(dlen) != 1 { + if dlen := len(requestFactory.GetDecorators()); dlen != 1 { t.Fatalf("Expected to have one decorators, got %d", dlen) } @@ -235,14 +235,14 @@ func TestRequestFactoryNewRequestWithDecorators(t *testing.T) { func TestRequestFactoryAddDecorator(t *testing.T) { requestFactory := NewRequestFactory() - if dlen := requestFactory.GetDecorators(); len(dlen) != 0 { + if dlen := len(requestFactory.GetDecorators()); dlen != 0 { t.Fatalf("Expected to have zero decorators, got %d", dlen) } ad := NewAuthDecorator("test", "password") requestFactory.AddDecorator(ad) - if dlen := requestFactory.GetDecorators(); len(dlen) != 1 { + if dlen := len(requestFactory.GetDecorators()); dlen != 1 { t.Fatalf("Expected to have one decorators, got %d", dlen) } } From 8c578b8190a9abd5a48efa723878a12568f91e54 Mon Sep 17 00:00:00 2001 From: Simei He Date: Fri, 3 Apr 2015 14:43:21 +0800 Subject: [PATCH 252/999] minor edits. Signed-off-by: Simei He --- docs/sources/articles/networking.md | 10 ++++++---- docs/sources/reference/commandline/cli.md | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index 95881e280..34ab02f79 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -122,15 +122,17 @@ Finally, several networking options can only be provided when calling [Binding container ports](#binding-ports) To supply networking options to the Docker server at startup, use the -`DOCKER_OPTS` in the Docker upstart configuration file. For Ubuntu, edit the -variable in `/etc/default/docker` and `/etc/sysconfig/docker` for Centos. +`DOCKER_OPTS` variable in the Docker upstart configuration file. For Ubuntu, edit the +variable in `/etc/default/docker` or `/etc/sysconfig/docker` for CentOS. The following example illustrates how to configure Docker on Ubuntu to recognize a -newly build bridge. Edit the `/etc/default/docker` file: +newly built bridge. + +Edit the `/etc/default/docker` file: $ echo 'DOCKER_OPTS="-b=bridge0"' >> /etc/default/docker -Then, restart the Docker server. +Then restart the Docker server. $ sudo service docker start diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index ba30e387b..ce2265774 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -2195,7 +2195,7 @@ available in the default container, you can set these using the `--ulimit` flag. > If you do not provide a `hard limit`, the `soft limit` will be used for both values. If no `ulimits` are set, they will be inherited from the default `ulimits` set on the daemon. -> `as` option is disabled for now. In other words, the following script is not supported: +> `as` option is disabled now. In other words, the following script is not supported: > `$docker run -it --ulimit as=1024 fedora /bin/bash` ## save From 3761955e8c1e7534026c469186c3f66c18e77cbc Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 3 Apr 2015 01:30:12 -0600 Subject: [PATCH 253/999] Change the btrfs_noversion check to be automatic Signed-off-by: Andrew "Tianon" Page --- Dockerfile | 2 +- hack/make.sh | 8 ++++++++ project/PACKAGERS.md | 9 +-------- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6c6e0b5bf..b54fda561 100644 --- a/Dockerfile +++ b/Dockerfile @@ -138,7 +138,7 @@ RUN useradd --create-home --gid docker unprivilegeduser VOLUME /var/lib/docker WORKDIR /go/src/github.com/docker/docker -ENV DOCKER_BUILDTAGS apparmor selinux btrfs_noversion +ENV DOCKER_BUILDTAGS apparmor selinux # Let us use a .bashrc file RUN ln -sfv $PWD/.bashrc ~/.bashrc diff --git a/hack/make.sh b/hack/make.sh index 118d4327f..4117469d6 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -98,6 +98,14 @@ if [ "$DOCKER_EXECDRIVER" = 'lxc' ]; then DOCKER_BUILDTAGS+=' test_no_exec' fi +# test whether "btrfs/version.h" exists and apply btrfs_noversion appropriately +if \ + command -v gcc &> /dev/null \ + && ! gcc -E - &> /dev/null <<<'#include ' \ +; then + DOCKER_BUILDTAGS+=' btrfs_noversion' +fi + # Use these flags when compiling the tests and final binary IAMSTATIC='true' diff --git a/project/PACKAGERS.md b/project/PACKAGERS.md index 701e552d5..5704b0a2b 100644 --- a/project/PACKAGERS.md +++ b/project/PACKAGERS.md @@ -58,8 +58,7 @@ To build the Docker daemon, you will additionally need: * libdevmapper version 1.02.68-cvs (2012-01-26) or later from lvm2 version 2.02.89 or later * btrfs-progs version 3.16.1 or later (unless using an older version is - absolutely necessary, in which case 3.8 is the minimum and the note below - regarding `btrfs_noversion` applies) + absolutely necessary, in which case 3.8 is the minimum) Be sure to also check out Docker's Dockerfile for the most up-to-date list of these build-time dependencies. @@ -163,12 +162,6 @@ SELinux, you will need to use the `selinux` build tag: export DOCKER_BUILDTAGS='selinux' ``` -If your version of btrfs-progs (also called btrfs-tools) is < 3.16.1, then you -will need the following tag to not check for btrfs version headers: -```bash -export DOCKER_BUILDTAGS='btrfs_noversion' -``` - There are build tags for disabling graphdrivers as well. By default, support for all graphdrivers are built in. From 9a98556c2bc355170893568ca20e102cbda7d600 Mon Sep 17 00:00:00 2001 From: Jamie Hannaford Date: Sat, 28 Mar 2015 14:29:33 +0100 Subject: [PATCH 254/999] Add documentation for exported functions and types Signed-off-by: Jamie Hannaford --- pkg/mount/flags_freebsd.go | 23 ++++++-- pkg/mount/flags_linux.go | 93 +++++++++++++++++++++++++------- pkg/mount/flags_unsupported.go | 1 + pkg/mount/mount.go | 22 ++++---- pkg/mount/mountinfo.go | 39 ++++++++++++-- pkg/mount/mountinfo_freebsd.go | 3 +- pkg/mount/mountinfo_linux.go | 7 ++- pkg/mount/sharedsubtree_linux.go | 16 ++++++ 8 files changed, 165 insertions(+), 39 deletions(-) diff --git a/pkg/mount/flags_freebsd.go b/pkg/mount/flags_freebsd.go index a59b58960..f166cb2f7 100644 --- a/pkg/mount/flags_freebsd.go +++ b/pkg/mount/flags_freebsd.go @@ -8,12 +8,25 @@ package mount import "C" const ( - RDONLY = C.MNT_RDONLY - NOSUID = C.MNT_NOSUID - NOEXEC = C.MNT_NOEXEC - SYNCHRONOUS = C.MNT_SYNCHRONOUS - NOATIME = C.MNT_NOATIME + // RDONLY will mount the filesystem as read-only. + RDONLY = C.MNT_RDONLY + // NOSUID will not allow set-user-identifier or set-group-identifier bits to + // take effect. + NOSUID = C.MNT_NOSUID + + // NOEXEC will not allow execution of any binaries on the mounted file system. + NOEXEC = C.MNT_NOEXEC + + // SYNCHRONOUS will allow any I/O to the file system to be done synchronously. + SYNCHRONOUS = C.MNT_SYNCHRONOUS + + // NOATIME will not update the file access time when reading from a file. + NOATIME = C.MNT_NOATIME +) + +// These flags are unsupported. +const ( BIND = 0 DIRSYNC = 0 MANDLOCK = 0 diff --git a/pkg/mount/flags_linux.go b/pkg/mount/flags_linux.go index 9986621c8..2f9f5c58e 100644 --- a/pkg/mount/flags_linux.go +++ b/pkg/mount/flags_linux.go @@ -5,26 +5,81 @@ import ( ) const ( - RDONLY = syscall.MS_RDONLY - NOSUID = syscall.MS_NOSUID - NODEV = syscall.MS_NODEV - NOEXEC = syscall.MS_NOEXEC + // RDONLY will mount the file system read-only. + RDONLY = syscall.MS_RDONLY + + // NOSUID will not allow set-user-identifier or set-group-identifier bits to + // take effect. + NOSUID = syscall.MS_NOSUID + + // NODEV will not interpret character or block special devices on the file + // system. + NODEV = syscall.MS_NODEV + + // NOEXEC will not allow execution of any binaries on the mounted file system. + NOEXEC = syscall.MS_NOEXEC + + // SYNCHRONOUS will allow I/O to the file system to be done synchronously. SYNCHRONOUS = syscall.MS_SYNCHRONOUS - DIRSYNC = syscall.MS_DIRSYNC - REMOUNT = syscall.MS_REMOUNT - MANDLOCK = syscall.MS_MANDLOCK - NOATIME = syscall.MS_NOATIME - NODIRATIME = syscall.MS_NODIRATIME - BIND = syscall.MS_BIND - RBIND = syscall.MS_BIND | syscall.MS_REC - UNBINDABLE = syscall.MS_UNBINDABLE + + // DIRSYNC will force all directory updates within the file system to be done + // synchronously. This affects the following system calls: creat, link, + // unlink, symlink, mkdir, rmdir, mknod and rename. + DIRSYNC = syscall.MS_DIRSYNC + + // REMOUNT will attempt to remount an already-mounted file system. This is + // commonly used to change the mount flags for a file system, especially to + // make a readonly file system writeable. It does not change device or mount + // point. + REMOUNT = syscall.MS_REMOUNT + + // MANDLOCK will force mandatory locks on a filesystem. + MANDLOCK = syscall.MS_MANDLOCK + + // NOATIME will not update the file access time when reading from a file. + NOATIME = syscall.MS_NOATIME + + // NODIRATIME will not update the directory access time. + NODIRATIME = syscall.MS_NODIRATIME + + // BIND remounts a subtree somewhere else. + BIND = syscall.MS_BIND + + // RBIND remounts a subtree and all possible submounts somewhere else. + RBIND = syscall.MS_BIND | syscall.MS_REC + + // UNBINDABLE creates a mount which cannot be cloned through a bind operation. + UNBINDABLE = syscall.MS_UNBINDABLE + + // RUNBINDABLE marks the entire mount tree as UNBINDABLE. RUNBINDABLE = syscall.MS_UNBINDABLE | syscall.MS_REC - PRIVATE = syscall.MS_PRIVATE - RPRIVATE = syscall.MS_PRIVATE | syscall.MS_REC - SLAVE = syscall.MS_SLAVE - RSLAVE = syscall.MS_SLAVE | syscall.MS_REC - SHARED = syscall.MS_SHARED - RSHARED = syscall.MS_SHARED | syscall.MS_REC - RELATIME = syscall.MS_RELATIME + + // PRIVATE creates a mount which carries no propagation abilities. + PRIVATE = syscall.MS_PRIVATE + + // RPRIVATE marks the entire mount tree as PRIVATE. + RPRIVATE = syscall.MS_PRIVATE | syscall.MS_REC + + // SLAVE creates a mount which receives propagation from its master, but not + // vice versa. + SLAVE = syscall.MS_SLAVE + + // RSLAVE marks the entire mount tree as SLAVE. + RSLAVE = syscall.MS_SLAVE | syscall.MS_REC + + // SHARED creates a mount which provides the ability to create mirrors of + // that mount such that mounts and unmounts within any of the mirrors + // propagate to the other mirrors. + SHARED = syscall.MS_SHARED + + // RSHARED marks the entire mount tree as SHARED. + RSHARED = syscall.MS_SHARED | syscall.MS_REC + + // RELATIME updates inode access times relative to modify or change time. + RELATIME = syscall.MS_RELATIME + + // STRICTATIME allows to explicitly request full atime updates. This makes + // it possible for the kernel to default to relatime or noatime but still + // allow userspace to override it. STRICTATIME = syscall.MS_STRICTATIME ) diff --git a/pkg/mount/flags_unsupported.go b/pkg/mount/flags_unsupported.go index c4f82176b..a90d3d115 100644 --- a/pkg/mount/flags_unsupported.go +++ b/pkg/mount/flags_unsupported.go @@ -2,6 +2,7 @@ package mount +// These flags are unsupported. const ( BIND = 0 DIRSYNC = 0 diff --git a/pkg/mount/mount.go b/pkg/mount/mount.go index 5ca731601..9a20df219 100644 --- a/pkg/mount/mount.go +++ b/pkg/mount/mount.go @@ -4,11 +4,12 @@ import ( "time" ) +// GetMounts retrieves a list of mounts for the current running process. func GetMounts() ([]*MountInfo, error) { return parseMountTable() } -// Looks at /proc/self/mountinfo to determine of the specified +// Mounted looks at /proc/self/mountinfo to determine of the specified // mountpoint has been mounted func Mounted(mountpoint string) (bool, error) { entries, err := parseMountTable() @@ -25,9 +26,10 @@ func Mounted(mountpoint string) (bool, error) { return false, nil } -// Mount the specified options at the target path only if -// the target is not mounted -// Options must be specified as fstab style +// Mount will mount filesystem according to the specified configuration, on the +// condition that the target path is *not* already mounted. Options must be +// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See +// flags.go for supported option flags. func Mount(device, target, mType, options string) error { flag, _ := parseOptions(options) if flag&REMOUNT != REMOUNT { @@ -38,9 +40,10 @@ func Mount(device, target, mType, options string) error { return ForceMount(device, target, mType, options) } -// Mount the specified options at the target path -// reguardless if the target is mounted or not -// Options must be specified as fstab style +// ForceMount will mount a filesystem according to the specified configuration, +// *regardless* if the target path is not already mounted. Options must be +// specified like the mount or fstab unix commands: "opt1=val1,opt2=val2". See +// flags.go for supported option flags. func ForceMount(device, target, mType, options string) error { flag, data := parseOptions(options) if err := mount(device, target, mType, uintptr(flag), data); err != nil { @@ -49,7 +52,7 @@ func ForceMount(device, target, mType, options string) error { return nil } -// Unmount the target only if it is mounted +// Unmount will unmount the target filesystem, so long as it is mounted. func Unmount(target string) error { if mounted, err := Mounted(target); err != nil || !mounted { return err @@ -57,7 +60,8 @@ func Unmount(target string) error { return ForceUnmount(target) } -// Unmount the target reguardless if it is mounted or not +// ForceUnmount will force an unmount of the target filesystem, regardless if +// it is mounted or not. func ForceUnmount(target string) (err error) { // Simple retry logic for unmount for i := 0; i < 10; i++ { diff --git a/pkg/mount/mountinfo.go b/pkg/mount/mountinfo.go index ec8e8bca2..8ea08648c 100644 --- a/pkg/mount/mountinfo.go +++ b/pkg/mount/mountinfo.go @@ -1,7 +1,40 @@ package mount +// MountInfo reveals information about a particular mounted filesystem. This +// struct is populated from the content in the /proc//mountinfo file. type MountInfo struct { - Id, Parent, Major, Minor int - Root, Mountpoint, Opts, Optional string - Fstype, Source, VfsOpts string + // Id is a unique identifier of the mount (may be reused after umount). + Id int + + // Parent indicates the ID of the mount parent (or of self for the top of the + // mount tree). + Parent int + + // Major indicates one half of the device ID which identifies the device class. + Major int + + // Minor indicates one half of the device ID which identifies a specific + // instance of device. + Minor int + + // Root of the mount within the filesystem. + Root string + + // Mountpoint indicates the mount point relative to the process's root. + Mountpoint string + + // Opts represents mount-specific options. + Opts string + + // Optional represents optional fields. + Optional string + + // Fstype indicates the type of filesystem, such as EXT3. + Fstype string + + // Source indicates filesystem specific information or "none". + Source string + + // VfsOpts represents per super block options. + VfsOpts string } diff --git a/pkg/mount/mountinfo_freebsd.go b/pkg/mount/mountinfo_freebsd.go index 2fe91862d..add7c3b0e 100644 --- a/pkg/mount/mountinfo_freebsd.go +++ b/pkg/mount/mountinfo_freebsd.go @@ -13,7 +13,8 @@ import ( "unsafe" ) -// Parse /proc/self/mountinfo because comparing Dev and ino does not work from bind mounts +// Parse /proc/self/mountinfo because comparing Dev and ino does not work from +// bind mounts. func parseMountTable() ([]*MountInfo, error) { var rawEntries *C.struct_statfs diff --git a/pkg/mount/mountinfo_linux.go b/pkg/mount/mountinfo_linux.go index 0eb018e23..351a58ea0 100644 --- a/pkg/mount/mountinfo_linux.go +++ b/pkg/mount/mountinfo_linux.go @@ -28,7 +28,8 @@ const ( mountinfoFormat = "%d %d %d:%d %s %s %s %s" ) -// Parse /proc/self/mountinfo because comparing Dev and ino does not work from bind mounts +// Parse /proc/self/mountinfo because comparing Dev and ino does not work from +// bind mounts func parseMountTable() ([]*MountInfo, error) { f, err := os.Open("/proc/self/mountinfo") if err != nil { @@ -80,7 +81,9 @@ func parseInfoFile(r io.Reader) ([]*MountInfo, error) { return out, nil } -// PidMountInfo collects the mounts for a specific Pid +// PidMountInfo collects the mounts for a specific process ID. If the process +// ID is unknown, it is better to use `GetMounts` which will inspect +// "/proc/self/mountinfo" instead. func PidMountInfo(pid int) ([]*MountInfo, error) { f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid)) if err != nil { diff --git a/pkg/mount/sharedsubtree_linux.go b/pkg/mount/sharedsubtree_linux.go index cd9b86cef..47303bbcb 100644 --- a/pkg/mount/sharedsubtree_linux.go +++ b/pkg/mount/sharedsubtree_linux.go @@ -2,34 +2,50 @@ package mount +// MakeShared ensures a mounted filesystem has the SHARED mount option enabled. +// See the supported options in flags.go for further reference. func MakeShared(mountPoint string) error { return ensureMountedAs(mountPoint, "shared") } +// MakeRShared ensures a mounted filesystem has the RSHARED mount option enabled. +// See the supported options in flags.go for further reference. func MakeRShared(mountPoint string) error { return ensureMountedAs(mountPoint, "rshared") } +// MakePrivate ensures a mounted filesystem has the PRIVATE mount option enabled. +// See the supported options in flags.go for further reference. func MakePrivate(mountPoint string) error { return ensureMountedAs(mountPoint, "private") } +// MakeRPrivate ensures a mounted filesystem has the RPRIVATE mount option +// enabled. See the supported options in flags.go for further reference. func MakeRPrivate(mountPoint string) error { return ensureMountedAs(mountPoint, "rprivate") } +// MakeSlave ensures a mounted filesystem has the SLAVE mount option enabled. +// See the supported options in flags.go for further reference. func MakeSlave(mountPoint string) error { return ensureMountedAs(mountPoint, "slave") } +// MakeRSlave ensures a mounted filesystem has the RSLAVE mount option enabled. +// See the supported options in flags.go for further reference. func MakeRSlave(mountPoint string) error { return ensureMountedAs(mountPoint, "rslave") } +// MakeUnbindable ensures a mounted filesystem has the UNBINDABLE mount option +// enabled. See the supported options in flags.go for further reference. func MakeUnbindable(mountPoint string) error { return ensureMountedAs(mountPoint, "unbindable") } +// MakeRUnbindable ensures a mounted filesystem has the RUNBINDABLE mount +// option enabled. See the supported options in flags.go for further reference. func MakeRUnbindable(mountPoint string) error { return ensureMountedAs(mountPoint, "runbindable") } From 57b09068efe3a278145683ff05b5e352ee00bba3 Mon Sep 17 00:00:00 2001 From: eluck Date: Fri, 3 Apr 2015 13:59:31 +0300 Subject: [PATCH 255/999] Update userguide/dockervolumes - fixes issue #12052 Signed-off-by: Evgeny Lukianchikov --- docs/sources/userguide/dockervolumes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/userguide/dockervolumes.md b/docs/sources/userguide/dockervolumes.md index b8204308e..c319ecee5 100644 --- a/docs/sources/userguide/dockervolumes.md +++ b/docs/sources/userguide/dockervolumes.md @@ -205,7 +205,7 @@ elsewhere. Create a new container. Then un-tar the backup file in the new container's data volume. - $ docker run --volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf /backup/backup.tar + $ docker run --volumes-from dbdata2 -v $(pwd):/backup ubuntu cd /dbdata && tar xvf /backup/backup.tar You can use the techniques above to automate backup, migration and restore testing using your preferred tools. From a6b8f2e3fe4bdc0db8b526bff8b91bb1bd3a7bde Mon Sep 17 00:00:00 2001 From: Aaron Welch Date: Wed, 1 Apr 2015 11:40:25 -0400 Subject: [PATCH 256/999] add centos to supported distros Signed-off-by: Aaron Welch --- hack/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/install.sh b/hack/install.sh index 5d8caaba0..fcea11d01 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -81,7 +81,7 @@ fi lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')" case "$lsb_dist" in - amzn|fedora) + amzn|fedora|centos) if [ "$lsb_dist" = 'amzn' ]; then ( set -x From 0b2fa9c707ab06836cf5dd7b3ca9efac64bba925 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 3 Apr 2015 08:31:30 -0700 Subject: [PATCH 257/999] Remove engine.Table from docker history and docker rmi Signed-off-by: Doug Davis --- api/client/history.go | 48 ++++++++++++++++++------------------------ api/client/rmi.go | 20 ++++++++++-------- api/types/types.go | 15 +++++++++++++ daemon/image_delete.go | 27 +++++++++++++----------- graph/history.go | 23 ++++++++++++-------- 5 files changed, 76 insertions(+), 57 deletions(-) diff --git a/api/client/history.go b/api/client/history.go index e8ad19c8a..6e0cdb24c 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -1,11 +1,12 @@ package client import ( + "encoding/json" "fmt" "text/tabwriter" "time" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/units" @@ -20,16 +21,16 @@ func (cli *DockerCli) CmdHistory(args ...string) error { quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") cmd.Require(flag.Exact, 1) - cmd.ParseFlags(args, true) - body, _, err := readBody(cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil)) + rdr, _, err := cli.call("GET", "/images/"+cmd.Arg(0)+"/history", nil, nil) if err != nil { return err } - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { + history := []types.ImageHistory{} + err = json.NewDecoder(rdr).Decode(&history) + if err != nil { return err } @@ -38,30 +39,23 @@ func (cli *DockerCli) CmdHistory(args ...string) error { fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE") } - for _, out := range outs.Data { - outID := out.Get("Id") - if !*quiet { - if *noTrunc { - fmt.Fprintf(w, "%s\t", outID) - } else { - fmt.Fprintf(w, "%s\t", stringid.TruncateID(outID)) - } - - fmt.Fprintf(w, "%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0)))) - - if *noTrunc { - fmt.Fprintf(w, "%s\t", out.Get("CreatedBy")) - } else { - fmt.Fprintf(w, "%s\t", utils.Trunc(out.Get("CreatedBy"), 45)) - } - fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("Size")))) + for _, entry := range history { + if *noTrunc { + fmt.Fprintf(w, entry.ID) } else { - if *noTrunc { - fmt.Fprintln(w, outID) - } else { - fmt.Fprintln(w, stringid.TruncateID(outID)) - } + fmt.Fprintf(w, stringid.TruncateID(entry.ID)) } + if !*quiet { + fmt.Fprintf(w, "\t%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0)))) + + if *noTrunc { + fmt.Fprintf(w, "%s\t", entry.CreatedBy) + } else { + fmt.Fprintf(w, "%s\t", utils.Trunc(entry.CreatedBy, 45)) + } + fmt.Fprintf(w, "%s", units.HumanSize(float64(entry.Size))) + } + fmt.Fprintf(w, "\n") } w.Flush() return nil diff --git a/api/client/rmi.go b/api/client/rmi.go index 580c0b747..11c9ff32d 100644 --- a/api/client/rmi.go +++ b/api/client/rmi.go @@ -1,10 +1,11 @@ package client import ( + "encoding/json" "fmt" "net/url" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" ) @@ -18,7 +19,6 @@ func (cli *DockerCli) CmdRmi(args ...string) error { noprune = cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents") ) cmd.Require(flag.Min, 1) - cmd.ParseFlags(args, true) v := url.Values{} @@ -31,22 +31,24 @@ func (cli *DockerCli) CmdRmi(args ...string) error { var encounteredError error for _, name := range cmd.Args() { - body, _, err := readBody(cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil)) + rdr, _, err := cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") } else { - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { + dels := []types.ImageDelete{} + err = json.NewDecoder(rdr).Decode(&dels) + if err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") continue } - for _, out := range outs.Data { - if out.Get("Deleted") != "" { - fmt.Fprintf(cli.out, "Deleted: %s\n", out.Get("Deleted")) + + for _, del := range dels { + if del.Deleted != "" { + fmt.Fprintf(cli.out, "Deleted: %s\n", del.Deleted) } else { - fmt.Fprintf(cli.out, "Untagged: %s\n", out.Get("Untagged")) + fmt.Fprintf(cli.out, "Untagged: %s\n", del.Untagged) } } } diff --git a/api/types/types.go b/api/types/types.go index f4c6dc34a..ef3dd6fcd 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -41,3 +41,18 @@ type ContainerChange struct { Kind int Path string } + +// GET "/images/{name:.*}/history" +type ImageHistory struct { + ID string `json:"Id"` + Created int64 + CreatedBy string + Tags []string + Size int64 +} + +// DELETE "/images/{name:.*}" +type ImageDelete struct { + Untagged string `json:",omitempty"` + Deleted string `json:",omitempty"` +} diff --git a/daemon/image_delete.go b/daemon/image_delete.go index 075672a4c..bf3d7ba9c 100644 --- a/daemon/image_delete.go +++ b/daemon/image_delete.go @@ -1,9 +1,11 @@ package daemon import ( + "encoding/json" "fmt" "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/image" @@ -16,21 +18,22 @@ func (daemon *Daemon) ImageDelete(job *engine.Job) error { if n := len(job.Args); n != 1 { return fmt.Errorf("Usage: %s IMAGE", job.Name) } - imgs := engine.NewTable("", 0) - if err := daemon.DeleteImage(job.Eng, job.Args[0], imgs, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil { + + list := []types.ImageDelete{} + if err := daemon.DeleteImage(job.Eng, job.Args[0], &list, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil { return err } - if len(imgs.Data) == 0 { + if len(list) == 0 { return fmt.Errorf("Conflict, %s wasn't deleted", job.Args[0]) } - if _, err := imgs.WriteListTo(job.Stdout); err != nil { + if err := json.NewEncoder(job.Stdout).Encode(list); err != nil { return err } return nil } // FIXME: make this private and use the job instead -func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine.Table, first, force, noprune bool) error { +func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, list *[]types.ImageDelete, first, force, noprune bool) error { var ( repoName, tag string tags = []string{} @@ -102,9 +105,9 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine. return err } if tagDeleted { - out := &engine.Env{} - out.Set("Untagged", utils.ImageReference(repoName, tag)) - imgs.Add(out) + *list = append(*list, types.ImageDelete{ + Untagged: utils.ImageReference(repoName, tag), + }) eng.Job("log", "untag", img.ID, "").Run() } } @@ -117,12 +120,12 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, imgs *engine. if err := daemon.Graph().Delete(img.ID); err != nil { return err } - out := &engine.Env{} - out.SetJson("Deleted", img.ID) - imgs.Add(out) + *list = append(*list, types.ImageDelete{ + Deleted: img.ID, + }) eng.Job("log", "delete", img.ID, "").Run() if img.Parent != "" && !noprune { - err := daemon.DeleteImage(eng, img.Parent, imgs, false, force, noprune) + err := daemon.DeleteImage(eng, img.Parent, list, false, force, noprune) if first { return err } diff --git a/graph/history.go b/graph/history.go index 719cdf379..1290de9a3 100644 --- a/graph/history.go +++ b/graph/history.go @@ -1,9 +1,11 @@ package graph import ( + "encoding/json" "fmt" "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/utils" @@ -30,19 +32,22 @@ func (s *TagStore) CmdHistory(job *engine.Job) error { } } - outs := engine.NewTable("Created", 0) + history := []types.ImageHistory{} + err = foundImage.WalkHistory(func(img *image.Image) error { - out := &engine.Env{} - out.SetJson("Id", img.ID) - out.SetInt64("Created", img.Created.Unix()) - out.Set("CreatedBy", strings.Join(img.ContainerConfig.Cmd, " ")) - out.SetList("Tags", lookupMap[img.ID]) - out.SetInt64("Size", img.Size) - outs.Add(out) + history = append(history, types.ImageHistory{ + ID: img.ID, + Created: img.Created.Unix(), + CreatedBy: strings.Join(img.ContainerConfig.Cmd, " "), + Tags: lookupMap[img.ID], + Size: img.Size, + }) return nil }) - if _, err := outs.WriteListTo(job.Stdout); err != nil { + + if err = json.NewEncoder(job.Stdout).Encode(history); err != nil { return err } + return nil } From bcbdf77ddb7dab0bf6346400eeb6a0aaaf3d7142 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Fri, 3 Apr 2015 08:53:37 -0700 Subject: [PATCH 258/999] Specify running `boot2docker ip` on host Signed-off-by: Arnaud Porterie --- docs/sources/userguide/usingdocker.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/userguide/usingdocker.md b/docs/sources/userguide/usingdocker.md index 26a9814b8..70996a210 100644 --- a/docs/sources/userguide/usingdocker.md +++ b/docs/sources/userguide/usingdocker.md @@ -179,10 +179,10 @@ see the application. Our Python application is live! > **Note:** -> If you have used the boot2docker virtual machine on OS X, Windows or Linux, +> If you have used the `boot2docker` virtual machine on OS X, Windows or Linux, > you'll need to get the IP of the virtual host instead of using localhost. -> You can do this by running the following in -> the boot2docker shell. +> You can do this by running the following outside of the `boot2docker` shell +> (i.e., from your comment line or terminal application). > > $ boot2docker ip > The VM's Host only interface IP address is: 192.168.59.103 From bfddad18ce126e1a500ffb968f4ff4a9f2683824 Mon Sep 17 00:00:00 2001 From: Huu Nguyen Date: Fri, 3 Apr 2015 09:50:30 -0700 Subject: [PATCH 259/999] Clean up formatting in the README Signed-off-by: Huu Nguyen --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index c70398a3a..3ab200d46 100644 --- a/README.md +++ b/README.md @@ -171,10 +171,10 @@ Under the hood, Docker is built on the following components: [cgroup](http://blog.dotcloud.com/kernel-secrets-from-the-paas-garage-part-24-c) and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) - capabilities of the Linux kernel; -* The [Go](http://golang.org) programming language. -* The [Docker Image Specification] (https://github.com/docker/docker/blob/master/image/spec/v1.md) -* The [Libcontainer Specification] (https://github.com/docker/libcontainer/blob/master/SPEC.md) + capabilities of the Linux kernel +* The [Go](http://golang.org) programming language +* The [Docker Image Specification](https://github.com/docker/docker/blob/master/image/spec/v1.md) +* The [Libcontainer Specification](https://github.com/docker/libcontainer/blob/master/SPEC.md) Contributing to Docker ====================== From 01724c1cf11ce4a9e3b1978c7c07fd25656ed137 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 29 Mar 2015 21:48:52 +0200 Subject: [PATCH 260/999] Refactor ultis/utils_daemon, fixes #11908 Signed-off-by: Antonio Murdaca --- docker/daemon.go | 15 +++++++++++++-- pkg/fileutils/fileutils.go | 3 ++- utils/utils_daemon.go | 18 ------------------ utils/utils_daemon_test.go | 26 -------------------------- 4 files changed, 15 insertions(+), 47 deletions(-) delete mode 100644 utils/utils_daemon.go delete mode 100644 utils/utils_daemon_test.go diff --git a/docker/daemon.go b/docker/daemon.go index 861bcdbc1..f16c20b90 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -20,9 +20,9 @@ import ( "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/signal" + "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/timeutils" "github.com/docker/docker/registry" - "github.com/docker/docker/utils" ) const CanDaemon = true @@ -41,7 +41,7 @@ func migrateKey() (err error) { // Migrate trust key if exists at ~/.docker/key.json and owned by current user oldPath := filepath.Join(homedir.Get(), ".docker", defaultTrustKeyFile) newPath := filepath.Join(getDaemonConfDir(), defaultTrustKeyFile) - if _, statErr := os.Stat(newPath); os.IsNotExist(statErr) && utils.IsFileOwner(oldPath) { + if _, statErr := os.Stat(newPath); os.IsNotExist(statErr) && currentUserIsOwner(oldPath) { defer func() { // Ensure old path is removed if no error occurred if err == nil { @@ -191,3 +191,14 @@ func mainDaemon() { } } + +// currentUserIsOwner checks whether the current user is the owner of the given +// file. +func currentUserIsOwner(f string) bool { + if fileInfo, err := system.Stat(f); err == nil && fileInfo != nil { + if int(fileInfo.Uid()) == os.Getuid() { + return true + } + } + return false +} diff --git a/pkg/fileutils/fileutils.go b/pkg/fileutils/fileutils.go index 64442e40f..432529765 100644 --- a/pkg/fileutils/fileutils.go +++ b/pkg/fileutils/fileutils.go @@ -1,8 +1,9 @@ package fileutils import ( - "github.com/Sirupsen/logrus" "path/filepath" + + "github.com/Sirupsen/logrus" ) // Matches returns true if relFilePath matches any of the patterns diff --git a/utils/utils_daemon.go b/utils/utils_daemon.go deleted file mode 100644 index 3f8f4d569..000000000 --- a/utils/utils_daemon.go +++ /dev/null @@ -1,18 +0,0 @@ -// +build daemon - -package utils - -import ( - "github.com/docker/docker/pkg/system" - "os" -) - -// IsFileOwner checks whether the current user is the owner of the given file. -func IsFileOwner(f string) bool { - if fileInfo, err := system.Stat(f); err == nil && fileInfo != nil { - if int(fileInfo.Uid()) == os.Getuid() { - return true - } - } - return false -} diff --git a/utils/utils_daemon_test.go b/utils/utils_daemon_test.go deleted file mode 100644 index e8361489b..000000000 --- a/utils/utils_daemon_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package utils - -import ( - "os" - "path" - "testing" -) - -func TestIsFileOwner(t *testing.T) { - var err error - var file *os.File - - if file, err = os.Create(path.Join(os.TempDir(), "testIsFileOwner")); err != nil { - t.Fatalf("failed to create file: %s", err) - } - file.Close() - - if ok := IsFileOwner(path.Join(os.TempDir(), "testIsFileOwner")); !ok { - t.Fatalf("User should be owner of file") - } - - if err = os.Remove(path.Join(os.TempDir(), "testIsFileOwner")); err != nil { - t.Fatalf("failed to remove file: %s", err) - } - -} From 67b4cce0f6c835cf9e53313a026af2e825ba8b10 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 3 Apr 2015 10:29:30 -0700 Subject: [PATCH 261/999] Remove engine.Table from docker search and fix missing field registry/SearchResults was missing the "is_automated" field. I added it back in. Pull this 'table' removal one from the others because it fixed a bug too Signed-off-by: Doug Davis --- api/client/search.go | 33 +++++++++++++++++++++------------ registry/types.go | 1 + 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/api/client/search.go b/api/client/search.go index f3e6de1a7..8f4eb0b30 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -1,18 +1,25 @@ package client import ( + "encoding/json" "fmt" "net/url" + "sort" "strings" "text/tabwriter" - "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/registry" "github.com/docker/docker/utils" ) +type ByStars []registry.SearchResult + +func (r ByStars) Len() int { return len(r) } +func (r ByStars) Swap(i, j int) { r[i], r[j] = r[j], r[i] } +func (r ByStars) Less(i, j int) bool { return r[i].StarCount < r[j].StarCount } + // CmdSearch searches the Docker Hub for images. // // Usage: docker search [OPTIONS] TERM @@ -39,35 +46,37 @@ func (cli *DockerCli) CmdSearch(args ...string) error { cli.LoadConfigFile() - body, statusCode, errReq := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search") - rawBody, _, err := readBody(body, statusCode, errReq) + rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search") if err != nil { return err } - outs := engine.NewTable("star_count", 0) - if _, err := outs.ReadListFrom(rawBody); err != nil { + results := ByStars{} + err = json.NewDecoder(rdr).Decode(&results) + if err != nil { return err } - outs.ReverseSort() + + sort.Sort(sort.Reverse(results)) + w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0) fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n") - for _, out := range outs.Data { - if ((*automated || *trusted) && (!out.GetBool("is_trusted") && !out.GetBool("is_automated"))) || (*stars > uint(out.GetInt("star_count"))) { + for _, res := range results { + if ((*automated || *trusted) && (!res.IsTrusted && !res.IsAutomated)) || (int(*stars) > res.StarCount) { continue } - desc := strings.Replace(out.Get("description"), "\n", " ", -1) + desc := strings.Replace(res.Description, "\n", " ", -1) desc = strings.Replace(desc, "\r", " ", -1) if !*noTrunc && len(desc) > 45 { desc = utils.Trunc(desc, 42) + "..." } - fmt.Fprintf(w, "%s\t%s\t%d\t", out.Get("name"), desc, uint(out.GetInt("star_count"))) - if out.GetBool("is_official") { + fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount) + if res.IsOfficial { fmt.Fprint(w, "[OK]") } fmt.Fprint(w, "\t") - if out.GetBool("is_automated") || out.GetBool("is_trusted") { + if res.IsAutomated || res.IsTrusted { fmt.Fprint(w, "[OK]") } fmt.Fprint(w, "\n") diff --git a/registry/types.go b/registry/types.go index bd0bf8b75..2c8369bd8 100644 --- a/registry/types.go +++ b/registry/types.go @@ -5,6 +5,7 @@ type SearchResult struct { IsOfficial bool `json:"is_official"` Name string `json:"name"` IsTrusted bool `json:"is_trusted"` + IsAutomated bool `json:"is_automated"` Description string `json:"description"` } From 2b320a230979c3222162567b9d1d3193b0806cd5 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Thu, 2 Apr 2015 14:57:39 -0700 Subject: [PATCH 262/999] docs: Add new windows installation tutorials Updated Windows installation documentation with newest screencasts and Chocolatey instructions to install windows client CLI. Signed-off-by: Ahmet Alp Balkan --- docs/sources/installation/windows.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index 95e5906cf..95f55afeb 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -22,7 +22,7 @@ is developed, you can launch only Linux containers from your Windows machine. ## Demonstration - + ## Installation @@ -147,3 +147,10 @@ You can do this with `%USERPROFILE%\.ssh\id_boot2docker` - then click: "Save Private Key". - Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`. + +## References + +If you have Docker hosts running and if you don't wish to do a +Boot2Docker installation, you can install the docker.exe using +unofficial Windows package manager Chocolately. For information +on how to do this, see [Docker package on Chocolatey](http://chocolatey.org/packages/docker). From 370cef012e81f11a87fcf7f1384dbbca6cab0c6c Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Fri, 3 Apr 2015 14:04:44 -0700 Subject: [PATCH 263/999] Move docs approval section Signed-off-by: Arnaud Porterie --- MAINTAINERS | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 546e456b0..2c897902d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -193,6 +193,11 @@ for each. # They should ask for any editorial change that makes the documentation more # consistent and easier to understand. # + # Changes and additions to docs must be reviewed and approved (LGTM'd) by a minimum of + # two docs sub-project maintainers. If the docs change originates with a docs + # maintainer, only one additional LGTM is required (since we assume a docs maintainer + # approves of their own PR). + # # Once documentation is approved (see below), a maintainer should make sure to remove this # label and add the next one. @@ -201,11 +206,6 @@ for each. 1-design-review = "raises design concerns" 4-merge = "general case" - # Docs approval - [Rules.review.docs-approval] - # Changes and additions to docs must be reviewed and approved (LGTM'd) by a minimum of two docs sub-project maintainers. - # If the docs change originates with a docs maintainer, only one additional LGTM is required (since we assume a docs maintainer approves of their own PR). - # Merge [Rules.review.states.4-merge] From 80f3085651717f2f487ae41633490db59716cc04 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Fri, 3 Apr 2015 14:06:46 -0700 Subject: [PATCH 264/999] Sort maintainers alphabetically Signed-off-by: Arnaud Porterie --- MAINTAINERS | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 2c897902d..fe57ea911 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -339,7 +339,6 @@ made through a pull request. people = [ - "unclejack", "crosbymichael", "erikh", "estesp", @@ -347,6 +346,7 @@ made through a pull request. "jfrazelle", "lk4d4", "tibor", + "unclejack", "vbatts", "vieux", "vishh" @@ -408,31 +408,31 @@ made through a pull request. people = [ "fredlf", "james", - "sven", + "mary", "spf13", - "mary" + "sven" ] [Org.Subsystems.libcontainer] people = [ "crosbymichael", - "vmarmol", - "mpatel", "jnagal", - "lk4d4" + "lk4d4", + "mpatel", + "vmarmol" ] [Org.Subsystems.registry] people = [ + "dmcg", "dmp42", - "vbatts", + "jlhawn", "joffrey", "samalba", "sday", - "jlhawn", - "dmcg" + "vbatts" ] [Org.Subsystems."build tools"] @@ -471,9 +471,9 @@ made through a pull request. [Org.Subsystem.builder] people = [ + "duglin", "erikh", - "tibor", - "duglin" + "tibor" ] From 58f07c9d8aa9b54f74af141d74314b607e2c18c5 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Fri, 3 Apr 2015 14:12:38 -0700 Subject: [PATCH 265/999] Rename status labels Signed-off-by: Arnaud Porterie --- MAINTAINERS | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index fe57ea911..8b42f58cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -131,7 +131,7 @@ for each. """ # Triage - [Rules.review.states.0-triage] + [Rules.review.states.0-needs-triage] # Maintainers are expected to triage new incoming pull requests by removing # the `0-triage` label and adding the correct labels (e.g. `1-design-review`) @@ -149,7 +149,7 @@ for each. 1-design-review = "general case" # Design review - [Rules.review.states.1-design-review] + [Rules.review.states.1-needs-design-review] # Maintainers are expected to comment on the design of the pull request. # Review of documentation is expected only in the context of design validation, @@ -166,7 +166,7 @@ for each. 2-code-review = "general case" # Code review - [Rules.review.states.2-code-review] + [Rules.review.states.2-needs-code-review] # Maintainers are expected to review the code and ensure that it is good # quality and in accordance with the documentation in the PR. @@ -184,7 +184,7 @@ for each. 3-docs-review = "general case" # Docs review - [Rules.review.states.3-docs-review] + [Rules.review.states.3-needs-docs-review] # Maintainers are expected to review the documentation in its bigger context, # ensuring consistency, completeness, validity, and breadth of coverage across @@ -207,7 +207,7 @@ for each. 4-merge = "general case" # Merge - [Rules.review.states.4-merge] + [Rules.review.states.4-needs-merge] # Maintainers are expected to merge this pull request as soon as possible. # They can ask for a rebase, or carry the pull request themselves. From 1a36a113d4afc11151c80b111d7357b7c31be32b Mon Sep 17 00:00:00 2001 From: Brendan Dixon Date: Thu, 2 Apr 2015 09:35:13 -0700 Subject: [PATCH 266/999] Windows console fixes Corrected integer size passed to Windows Corrected DisableEcho / SetRawTerminal to not modify state Cleaned up and made routines more idiomatic Corrected raw mode state bits Removed duplicate IsTerminal Corrected off-by-one error Minor idiomatic change Signed-off-by: Brendan Dixon --- pkg/term/term_windows.go | 116 +++++++++++------------ pkg/term/winconsole/console_windows.go | 123 +++++++++++++------------ 2 files changed, 121 insertions(+), 118 deletions(-) diff --git a/pkg/term/term_windows.go b/pkg/term/term_windows.go index abda841cb..f43721c6a 100644 --- a/pkg/term/term_windows.go +++ b/pkg/term/term_windows.go @@ -21,34 +21,48 @@ type Winsize struct { y uint16 } -// GetWinsize gets the window size of the given terminal +func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { + switch { + case os.Getenv("ConEmuANSI") == "ON": + // The ConEmu shell emulates ANSI well by default. + return os.Stdin, os.Stdout, os.Stderr + case os.Getenv("MSYSTEM") != "": + // MSYS (mingw) does not emulate ANSI well. + return winconsole.WinConsoleStreams() + default: + return winconsole.WinConsoleStreams() + } +} + +// GetFdInfo returns file descriptor and bool indicating whether the file is a terminal. +func GetFdInfo(in interface{}) (uintptr, bool) { + return winconsole.GetHandleInfo(in) +} + +// GetWinsize retrieves the window size of the terminal connected to the passed file descriptor. func GetWinsize(fd uintptr) (*Winsize, error) { - ws := &Winsize{} - var info *winconsole.CONSOLE_SCREEN_BUFFER_INFO info, err := winconsole.GetConsoleScreenBufferInfo(fd) if err != nil { return nil, err } - ws.Width = uint16(info.Window.Right - info.Window.Left + 1) - ws.Height = uint16(info.Window.Bottom - info.Window.Top + 1) - - ws.x = 0 // todo azlinux -- this is the pixel size of the Window, and not currently used by any caller - ws.y = 0 - - return ws, nil + // TODO(azlinux): Set the pixel width / height of the console (currently unused by any caller) + return &Winsize{ + Width: uint16(info.Window.Bottom - info.Window.Top + 1), + Height: uint16(info.Window.Right - info.Window.Left + 1), + x: 0, + y: 0}, nil } -// SetWinsize sets the terminal connected to the given file descriptor to a -// given size. +// SetWinsize sets the size of the given terminal connected to the passed file descriptor. func SetWinsize(fd uintptr, ws *Winsize) error { + // TODO(azlinux): Implement SetWinsize return nil } // IsTerminal returns true if the given file descriptor is a terminal. func IsTerminal(fd uintptr) bool { - _, e := winconsole.GetConsoleMode(fd) - return e == nil + return winconsole.IsConsole(fd) } // RestoreTerminal restores the terminal connected to the given file descriptor to a @@ -57,7 +71,7 @@ func RestoreTerminal(fd uintptr, state *State) error { return winconsole.SetConsoleMode(fd, state.mode) } -// SaveState saves the state of the given console +// SaveState saves the state of the terminal connected to the given file descriptor. func SaveState(fd uintptr) (*State, error) { mode, e := winconsole.GetConsoleMode(fd) if e != nil { @@ -66,72 +80,58 @@ func SaveState(fd uintptr) (*State, error) { return &State{mode}, nil } -// DisableEcho disbales the echo for given file descriptor and returns previous state -// see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx for these flag settings +// DisableEcho disables echo for the terminal connected to the given file descriptor. +// -- See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx func DisableEcho(fd uintptr, state *State) error { - state.mode &^= (winconsole.ENABLE_ECHO_INPUT) - state.mode |= (winconsole.ENABLE_PROCESSED_INPUT | winconsole.ENABLE_LINE_INPUT) - return winconsole.SetConsoleMode(fd, state.mode) + mode := state.mode + mode &^= winconsole.ENABLE_ECHO_INPUT + mode |= winconsole.ENABLE_PROCESSED_INPUT | winconsole.ENABLE_LINE_INPUT + // TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state. + return winconsole.SetConsoleMode(fd, mode) } // SetRawTerminal puts 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 SetRawTerminal(fd uintptr) (*State, error) { - oldState, err := MakeRaw(fd) + state, err := MakeRaw(fd) if err != nil { return nil, err } - // TODO (azlinux): implement handling interrupt and restore state of terminal - return oldState, err + // TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state. + return state, err } // MakeRaw puts 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 state *State state, err := SaveState(fd) if err != nil { return nil, err } - // https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx - // All three input modes, along with processed output mode, are designed to work together. - // It is best to either enable or disable all of these modes as a group. - // When all are enabled, the application is said to be in "cooked" mode, which means that most of the processing is handled for the application. - // When all are disabled, the application is in "raw" mode, which means that input is unfiltered and any processing is left to the application. - state.mode = 0 - err = winconsole.SetConsoleMode(fd, state.mode) + // See + // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx + // -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx + mode := state.mode + + // Disable these modes + mode &^= winconsole.ENABLE_ECHO_INPUT + mode &^= winconsole.ENABLE_LINE_INPUT + mode &^= winconsole.ENABLE_MOUSE_INPUT + // TODO(azlinux): Enable window input to handle window resizing + mode |= winconsole.ENABLE_WINDOW_INPUT + + // Enable these modes + mode |= winconsole.ENABLE_PROCESSED_INPUT + mode |= winconsole.ENABLE_EXTENDED_FLAGS + mode |= winconsole.ENABLE_INSERT_MODE + mode |= winconsole.ENABLE_QUICK_EDIT_MODE + + err = winconsole.SetConsoleMode(fd, mode) if err != nil { return nil, err } return state, nil } - -// GetFdInfo returns file descriptor and bool indicating whether the file is a terminal -func GetFdInfo(in interface{}) (uintptr, bool) { - return winconsole.GetHandleInfo(in) -} - -func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { - var shouldEmulateANSI bool - switch { - case os.Getenv("ConEmuANSI") == "ON": - // ConEmu shell, ansi emulated by default and ConEmu does an extensively - // good emulation. - shouldEmulateANSI = false - case os.Getenv("MSYSTEM") != "": - // MSYS (mingw) cannot fully emulate well and still shows escape characters - // mostly because it's still running on cmd.exe window. - shouldEmulateANSI = true - default: - shouldEmulateANSI = true - } - - if shouldEmulateANSI { - return winconsole.StdStreams() - } - - return os.Stdin, os.Stdout, os.Stderr -} diff --git a/pkg/term/winconsole/console_windows.go b/pkg/term/winconsole/console_windows.go index 85544493f..bebf6d7c1 100644 --- a/pkg/term/winconsole/console_windows.go +++ b/pkg/term/winconsole/console_windows.go @@ -16,14 +16,16 @@ import ( const ( // Consts for Get/SetConsoleMode function - // see http://msdn.microsoft.com/en-us/library/windows/desktop/ms683167(v=vs.85).aspx - ENABLE_ECHO_INPUT = 0x0004 - ENABLE_INSERT_MODE = 0x0020 - ENABLE_LINE_INPUT = 0x0002 - ENABLE_MOUSE_INPUT = 0x0010 + // -- See https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx ENABLE_PROCESSED_INPUT = 0x0001 - ENABLE_QUICK_EDIT_MODE = 0x0040 + ENABLE_LINE_INPUT = 0x0002 + ENABLE_ECHO_INPUT = 0x0004 ENABLE_WINDOW_INPUT = 0x0008 + ENABLE_MOUSE_INPUT = 0x0010 + ENABLE_INSERT_MODE = 0x0020 + ENABLE_QUICK_EDIT_MODE = 0x0040 + ENABLE_EXTENDED_FLAGS = 0x0080 + // If parameter is a screen buffer handle, additional values ENABLE_PROCESSED_OUTPUT = 0x0001 ENABLE_WRAP_AT_EOL_OUTPUT = 0x0002 @@ -97,27 +99,27 @@ const ( VK_HOME = 0x24 // HOME key VK_LEFT = 0x25 // LEFT ARROW key VK_UP = 0x26 // UP ARROW key - VK_RIGHT = 0x27 //RIGHT ARROW key - VK_DOWN = 0x28 //DOWN ARROW key - VK_SELECT = 0x29 //SELECT key - VK_PRINT = 0x2A //PRINT key - VK_EXECUTE = 0x2B //EXECUTE key - VK_SNAPSHOT = 0x2C //PRINT SCREEN key - VK_INSERT = 0x2D //INS key - VK_DELETE = 0x2E //DEL key - VK_HELP = 0x2F //HELP key - VK_F1 = 0x70 //F1 key - VK_F2 = 0x71 //F2 key - VK_F3 = 0x72 //F3 key - VK_F4 = 0x73 //F4 key - VK_F5 = 0x74 //F5 key - VK_F6 = 0x75 //F6 key - VK_F7 = 0x76 //F7 key - VK_F8 = 0x77 //F8 key - VK_F9 = 0x78 //F9 key - VK_F10 = 0x79 //F10 key - VK_F11 = 0x7A //F11 key - VK_F12 = 0x7B //F12 key + VK_RIGHT = 0x27 // RIGHT ARROW key + VK_DOWN = 0x28 // DOWN ARROW key + VK_SELECT = 0x29 // SELECT key + VK_PRINT = 0x2A // PRINT key + VK_EXECUTE = 0x2B // EXECUTE key + VK_SNAPSHOT = 0x2C // PRINT SCREEN key + VK_INSERT = 0x2D // INS key + VK_DELETE = 0x2E // DEL key + VK_HELP = 0x2F // HELP key + VK_F1 = 0x70 // F1 key + VK_F2 = 0x71 // F2 key + VK_F3 = 0x72 // F3 key + VK_F4 = 0x73 // F4 key + VK_F5 = 0x74 // F5 key + VK_F6 = 0x75 // F6 key + VK_F7 = 0x76 // F7 key + VK_F8 = 0x77 // F8 key + VK_F9 = 0x78 // F9 key + VK_F10 = 0x79 // F10 key + VK_F11 = 0x7A // F11 key + VK_F12 = 0x7B // F12 key ) var kernel32DLL = syscall.NewLazyDLL("kernel32.dll") @@ -140,7 +142,12 @@ var ( // types for calling various windows API // see http://msdn.microsoft.com/en-us/library/windows/desktop/ms682093(v=vs.85).aspx type ( - SHORT int16 + SHORT int16 + BOOL int32 + WORD uint16 + WCHAR uint16 + DWORD uint32 + SMALL_RECT struct { Left SHORT Top SHORT @@ -153,11 +160,6 @@ type ( Y SHORT } - BOOL int32 - WORD uint16 - WCHAR uint16 - DWORD uint32 - CONSOLE_SCREEN_BUFFER_INFO struct { Size COORD CursorPosition COORD @@ -192,6 +194,10 @@ type ( } ) +// TODO(azlinux): Basic type clean-up +// -- Convert all uses of uintptr to syscall.Handle to be consistent with Windows syscall +// -- Convert, as appropriate, types to use defined Windows types (e.g., DWORD instead of uint32) + // Implements the TerminalEmulator interface type WindowsTerminal struct { outMutex sync.Mutex @@ -211,14 +217,14 @@ func getStdHandle(stdhandle int) uintptr { return uintptr(handle) } -func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) { +func WinConsoleStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) { handler := &WindowsTerminal{ inputBuffer: make([]byte, MAX_INPUT_BUFFER), inputEscapeSequence: []byte(KEY_ESC_CSI), inputEvents: make([]INPUT_RECORD, MAX_INPUT_EVENTS), } - if IsTerminal(os.Stdin.Fd()) { + if IsConsole(os.Stdin.Fd()) { stdIn = &terminalReader{ wrappedReader: os.Stdin, emulator: handler, @@ -229,7 +235,7 @@ func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) { stdIn = os.Stdin } - if IsTerminal(os.Stdout.Fd()) { + if IsConsole(os.Stdout.Fd()) { stdoutHandle := getStdHandle(syscall.STD_OUTPUT_HANDLE) // Save current screen buffer info @@ -253,7 +259,7 @@ func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) { stdOut = os.Stdout } - if IsTerminal(os.Stderr.Fd()) { + if IsConsole(os.Stderr.Fd()) { stdErr = &terminalWriter{ wrappedWriter: os.Stderr, emulator: handler, @@ -267,25 +273,21 @@ func StdStreams() (stdIn io.ReadCloser, stdOut io.Writer, stdErr io.Writer) { return stdIn, stdOut, stdErr } -// GetHandleInfo returns file descriptor and bool indicating whether the file is a terminal +// GetHandleInfo returns file descriptor and bool indicating whether the file is a console. func GetHandleInfo(in interface{}) (uintptr, bool) { var inFd uintptr var isTerminalIn bool + + switch t := in.(type) { + case *terminalReader: + in = t.wrappedReader + case *terminalWriter: + in = t.wrappedWriter + } + if file, ok := in.(*os.File); ok { inFd = file.Fd() - isTerminalIn = IsTerminal(inFd) - } - if tr, ok := in.(*terminalReader); ok { - if file, ok := tr.wrappedReader.(*os.File); ok { - inFd = file.Fd() - isTerminalIn = IsTerminal(inFd) - } - } - if tr, ok := in.(*terminalWriter); ok { - if file, ok := tr.wrappedWriter.(*os.File); ok { - inFd = file.Fd() - isTerminalIn = IsTerminal(inFd) - } + isTerminalIn = IsConsole(inFd) } return inFd, isTerminalIn } @@ -318,12 +320,12 @@ func SetConsoleMode(handle uintptr, mode uint32) error { // SetCursorVisible sets the cursor visbility // http://msdn.microsoft.com/en-us/library/windows/desktop/ms686019(v=vs.85).aspx func SetCursorVisible(handle uintptr, isVisible BOOL) (bool, error) { - var cursorInfo CONSOLE_CURSOR_INFO - if err := getError(getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(&cursorInfo)), 0)); err != nil { + var cursorInfo *CONSOLE_CURSOR_INFO = &CONSOLE_CURSOR_INFO{} + if err := getError(getConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)); err != nil { return false, err } cursorInfo.Visible = isVisible - if err := getError(setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(&cursorInfo)), 0)); err != nil { + if err := getError(setConsoleCursorInfoProc.Call(handle, uintptr(unsafe.Pointer(cursorInfo)), 0)); err != nil { return false, err } return true, nil @@ -493,7 +495,7 @@ func setConsoleCursorPosition(handle uintptr, isRelative bool, column int16, lin // http://msdn.microsoft.com/en-us/library/windows/desktop/ms683207(v=vs.85).aspx func getNumberOfConsoleInputEvents(handle uintptr) (uint16, error) { - var n WORD + var n DWORD if err := getError(getNumberOfConsoleInputEventsProc.Call(handle, uintptr(unsafe.Pointer(&n)))); err != nil { return 0, err } @@ -502,7 +504,7 @@ func getNumberOfConsoleInputEvents(handle uintptr) (uint16, error) { //http://msdn.microsoft.com/en-us/library/windows/desktop/ms684961(v=vs.85).aspx func readConsoleInputKey(handle uintptr, inputBuffer []INPUT_RECORD) (int, error) { - var nr WORD + var nr DWORD if err := getError(readConsoleInputProc.Call(handle, uintptr(unsafe.Pointer(&inputBuffer[0])), uintptr(len(inputBuffer)), uintptr(unsafe.Pointer(&nr)))); err != nil { return 0, err } @@ -636,14 +638,14 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) return n, err } if line > int16(screenBufferInfo.Window.Bottom) { - line = int16(screenBufferInfo.Window.Bottom) + line = int16(screenBufferInfo.Window.Bottom) + 1 } column, err := parseInt16OrDefault(parsedCommand.getParam(1), 1) if err != nil { return n, err } if column > int16(screenBufferInfo.Window.Right) { - column = int16(screenBufferInfo.Window.Right) + column = int16(screenBufferInfo.Window.Right) + 1 } // The numbers are not 0 based, but 1 based if err := setConsoleCursorPosition(handle, false, column-1, line-1); err != nil { @@ -1039,8 +1041,9 @@ func marshal(c COORD) uintptr { return uintptr(uint32(uint32(uint16(c.Y))<<16 | uint32(uint16(c.X)))) } -// IsTerminal returns true if the given file descriptor is a terminal. -func IsTerminal(fd uintptr) bool { +// IsConsole returns true if the given file descriptor is a terminal. +// -- The code assumes that GetConsoleMode will return an error for file descriptors that are not a console. +func IsConsole(fd uintptr) bool { _, e := GetConsoleMode(fd) return e == nil } From 5846914f91f16c9d940701c9bbc0ebb8ab3c4226 Mon Sep 17 00:00:00 2001 From: Johan Euphrosine Date: Fri, 3 Apr 2015 15:05:14 -0700 Subject: [PATCH 267/999] test-unit: fix TESTFLAGS doc Signed-off-by: Johan Euphrosine --- hack/make/test-unit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/make/test-unit b/hack/make/test-unit index 540ba41f3..7a2642820 100644 --- a/hack/make/test-unit +++ b/hack/make/test-unit @@ -12,7 +12,7 @@ TEXTRESET=$'\033[0m' # reset the foreground colour # If $TESTFLAGS is set in the environment, it is passed as extra arguments to 'go test'. # You can use this to select certain tests to run, eg. # -# TESTFLAGS='-run ^TestBuild$' ./hack/make.sh test-unit +# TESTFLAGS='-test.run ^TestBuild$' ./hack/make.sh test-unit # bundle_test_unit() { { From e1ca439828c33a186489da568a29d3f3a2951e03 Mon Sep 17 00:00:00 2001 From: Simon Eskildsen Date: Sun, 22 Mar 2015 02:39:44 +0000 Subject: [PATCH 268/999] daemon: convert version log statement to fields Signed-off-by: Simon Eskildsen --- docker/daemon.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docker/daemon.go b/docker/daemon.go index 534bc3a47..1e5aafbf6 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -114,12 +114,12 @@ func mainDaemon() { return } - logrus.Infof("docker daemon: %s %s; execdriver: %s; graphdriver: %s", - dockerversion.VERSION, - dockerversion.GITCOMMIT, - d.ExecutionDriver().Name(), - d.GraphDriver().String(), - ) + logrus.WithFields(logrus.Fields{ + "version": dockerversion.VERSION, + "commit": dockerversion.GITCOMMIT, + "execdriver": d.ExecutionDriver().Name(), + "graphdriver": d.GraphDriver().String(), + }).Info("Docker daemon") if err := d.Install(eng); err != nil { daemonInitWait <- err From d94e186442ef4ba3103a6ad72666782179d3c2b8 Mon Sep 17 00:00:00 2001 From: Huu Nguyen Date: Fri, 3 Apr 2015 16:36:49 -0700 Subject: [PATCH 269/999] Make NOTICE in the README into a link Signed-off-by: Huu Nguyen --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3ab200d46..86f8bcb0d 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ We are always open to suggestions on process improvements, and are always lookin ### Legal *Brought to you courtesy of our legal counsel. For more context, -please see the "NOTICE" document in this repo.* +please see the [NOTICE](https://github.com/docker/docker/blob/master/NOTICE) document in this repo.* Use and transfer of Docker may be subject to certain restrictions by the United States and other governments. From 886026f99e4b13b0fa2ee4c5582786a863e1d0d1 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Fri, 3 Apr 2015 14:25:39 -0700 Subject: [PATCH 270/999] Add thaJeztah Signed-off-by: Arnaud Porterie --- MAINTAINERS | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 8b42f58cf..d6bcf5a1a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -476,6 +476,22 @@ made through a pull request. "tibor" ] + [Org.Curators] + + # The curators help ensure that incoming issues and pull requests are properly triaged and + # that our various contribution and reviewing processes are respected. With their knowledge of + # the repository activity, they can also guide contributors to relevant material or + # discussions. + # + # They are neither code nor docs reviewers, so they are never expected to merge. They can + # however: + # - close an issue or pull request when it's an exact duplicate + # - close an issue or pull request when it's inappropriate or off-topic + + people = [ + "thajeztah" + ] + [people] @@ -600,6 +616,11 @@ made through a pull request. Email = "SvenDowideit@home.org.au" GitHub = "SvenDowideit" + [people.thajeztah] + Name = "Sebastiaan van Stijn" + Email = "github@gone.nl" + GitHub = "thaJeztah" + [people.tianon] Name = "Tianon Gravi" Email = "admwiggin@gmail.com" From e50636617f10f4a2ca4924260cb67719bf906ec1 Mon Sep 17 00:00:00 2001 From: Tonny Xu Date: Tue, 31 Mar 2015 11:44:58 +0900 Subject: [PATCH 271/999] Updated the explanation of ONBUILD in best-practices doc Signed-off-by: Tonny Xu --- docs/sources/articles/dockerfile_best-practices.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/sources/articles/dockerfile_best-practices.md b/docs/sources/articles/dockerfile_best-practices.md index 2ea796582..83a77fc74 100644 --- a/docs/sources/articles/dockerfile_best-practices.md +++ b/docs/sources/articles/dockerfile_best-practices.md @@ -398,7 +398,15 @@ troubleshoot, and maintain. ### [`ONBUILD`](https://docs.docker.com/reference/builder/#onbuild) -`ONBUILD` is only useful for images that are going to be built `FROM` a given +An `ONBUILD` command executes after the current `Dockerfile` build completes. +`ONBUILD` executes in any child image derived `FROM` the current image. Think +of the `ONBUILD` command as an instruction the parent `Dockerfile` gives +to the child `Dockerfile`. + +A Docker build executes `ONBUILD` commands before any command in a child +`Dockerfile`. + +`ONBUILD` is useful for images that are going to be built `FROM` a given image. For example, you would use `ONBUILD` for a language stack image that builds arbitrary user software written in that language within the `Dockerfile`, as you can see in [Ruby’s `ONBUILD` variants](https://github.com/docker-library/ruby/blob/master/2.1/onbuild/Dockerfile). From ffcc4a1e52def3b1552ae3c7ba4ee1fb47f92cea Mon Sep 17 00:00:00 2001 From: guoxiuyan Date: Sat, 4 Apr 2015 15:36:35 +0800 Subject: [PATCH 272/999] Fix a minor typo Signed-off-by: Guo Xiuyan --- daemon/execdriver/native/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 6afd02321..40eb87cbe 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -159,7 +159,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba oom := notifyOnOOM(cont) waitF := p.Wait if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) { - // we need such hack for tracking processes with inerited fds, + // we need such hack for tracking processes with inherited fds, // because cmd.Wait() waiting for all streams to be copied waitF = waitInPIDHost(p, cont) } From ac12c8053779d36409e248f41c4d80ef0e4fbe9f Mon Sep 17 00:00:00 2001 From: Kirill SIbirev Date: Fri, 3 Apr 2015 15:54:01 +0300 Subject: [PATCH 273/999] Improved "start a container" section in 1.16 & 1.17 docs It seems it was lost or something Signed-off-by: Kirill Sibirev --- .../reference/api/docker_remote_api_v1.16.md | 54 ++++++++++++++++++ .../reference/api/docker_remote_api_v1.17.md | 57 +++++++++++++++++++ 2 files changed, 111 insertions(+) diff --git a/docs/sources/reference/api/docker_remote_api_v1.16.md b/docs/sources/reference/api/docker_remote_api_v1.16.md index 86df97b71..a0c875889 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.16.md +++ b/docs/sources/reference/api/docker_remote_api_v1.16.md @@ -509,12 +509,66 @@ Start the container `id` POST /containers/(id)/start HTTP/1.1 Content-Type: application/json + { + "Binds": ["/tmp:/tmp"], + "Links": ["redis3:redis"], + "LxcConf": {"lxc.utsname":"docker"}, + "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts": false, + "Privileged": false, + "Dns": ["8.8.8.8"], + "DnsSearch": [""], + "VolumesFrom": ["parent", "other:ro"], + "CapAdd": ["NET_ADMIN"], + "CapDrop": ["MKNOD"], + "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, + "NetworkMode": "bridge", + "Devices": [] + } + **Example response**: HTTP/1.1 204 No Content Json Parameters: +- **Binds** – A list of volume bindings for this container. Each volume + binding is a string of the form `container_path` (to create a new + volume for the container), `host_path:container_path` (to bind-mount + a host path into the container), or `host_path:container_path:ro` + (to make the bind-mount read-only inside the container). +- **Links** - A list of links for the container. Each link entry should be of + of the form "container_name:alias". +- **LxcConf** - LXC specific configurations. These configurations will only + work when using the `lxc` execution driver. +- **PortBindings** - A map of exposed container ports and the host port they + should map to. It should be specified in the form + `{ /: [{ "HostPort": "" }] }` + Take note that `port` is specified as a string and not an integer value. +- **PublishAllPorts** - Allocates a random host port for all of a container's + exposed ports. Specified as a boolean value. +- **Privileged** - Gives the container full access to the host. Specified as + a boolean value. +- **Dns** - A list of dns servers for the container to use. +- **DnsSearch** - A list of DNS search domains +- **VolumesFrom** - A list of volumes to inherit from another container. + Specified in the form `[:]` +- **CapAdd** - A list of kernel capabilties to add to the container. +- **Capdrop** - A list of kernel capabilties to drop from the container. +- **RestartPolicy** – The behavior to apply when the container exits. The + value is an object with a `Name` property of either `"always"` to + always restart or `"on-failure"` to restart only when the container + exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` + controls the number of times to retry before giving up. + The default is not to restart. (optional) + An ever increasing delay (double the previous delay, starting at 100mS) + is added before each restart to prevent flooding the server. +- **NetworkMode** - Sets the networking mode for the container. Supported + values are: `bridge`, `host`, and `container:` +- **Devices** - A list of devices to add to the container specified in the + form + `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` + Status Codes: - **204** – no error diff --git a/docs/sources/reference/api/docker_remote_api_v1.17.md b/docs/sources/reference/api/docker_remote_api_v1.17.md index c8b157169..d0abaffd0 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.17.md +++ b/docs/sources/reference/api/docker_remote_api_v1.17.md @@ -639,12 +639,69 @@ Start the container `id` POST /containers/(id)/start HTTP/1.1 Content-Type: application/json + { + "Binds": ["/tmp:/tmp"], + "Links": ["redis3:redis"], + "LxcConf": {"lxc.utsname":"docker"}, + "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts": false, + "Privileged": false, + "ReadonlyRootfs": false, + "Dns": ["8.8.8.8"], + "DnsSearch": [""], + "VolumesFrom": ["parent", "other:ro"], + "CapAdd": ["NET_ADMIN"], + "CapDrop": ["MKNOD"], + "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, + "NetworkMode": "bridge", + "Devices": [] + } + **Example response**: HTTP/1.1 204 No Content Json Parameters: +- **Binds** – A list of volume bindings for this container. Each volume + binding is a string of the form `container_path` (to create a new + volume for the container), `host_path:container_path` (to bind-mount + a host path into the container), or `host_path:container_path:ro` + (to make the bind-mount read-only inside the container). +- **Links** - A list of links for the container. Each link entry should be of + of the form "container_name:alias". +- **LxcConf** - LXC specific configurations. These configurations will only + work when using the `lxc` execution driver. +- **PortBindings** - A map of exposed container ports and the host port they + should map to. It should be specified in the form + `{ /: [{ "HostPort": "" }] }` + Take note that `port` is specified as a string and not an integer value. +- **PublishAllPorts** - Allocates a random host port for all of a container's + exposed ports. Specified as a boolean value. +- **Privileged** - Gives the container full access to the host. Specified as + a boolean value. +- **ReadonlyRootfs** - Mount the container's root filesystem as read only. + Specified as a boolean value. +- **Dns** - A list of dns servers for the container to use. +- **DnsSearch** - A list of DNS search domains +- **VolumesFrom** - A list of volumes to inherit from another container. + Specified in the form `[:]` +- **CapAdd** - A list of kernel capabilties to add to the container. +- **Capdrop** - A list of kernel capabilties to drop from the container. +- **RestartPolicy** – The behavior to apply when the container exits. The + value is an object with a `Name` property of either `"always"` to + always restart or `"on-failure"` to restart only when the container + exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` + controls the number of times to retry before giving up. + The default is not to restart. (optional) + An ever increasing delay (double the previous delay, starting at 100mS) + is added before each restart to prevent flooding the server. +- **NetworkMode** - Sets the networking mode for the container. Supported + values are: `bridge`, `host`, and `container:` +- **Devices** - A list of devices to add to the container specified in the + form + `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` + Status Codes: - **204** – no error From 4cf5a1c2aa2f95a0bca5a0bf47026518b7b2167f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Tom=C3=A1s=20Albornoz?= Date: Sat, 4 Apr 2015 15:22:24 +0200 Subject: [PATCH 274/999] Remove "stupid" wording MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: José Tomás Albornoz --- daemon/networkdriver/bridge/driver.go | 2 +- graph/service.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index 42015ce2f..e974a9f23 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -57,7 +57,7 @@ var ( // Here we don't follow the convention of using the 1st IP of the range for the gateway. // This is to use the same gateway IPs as the /24 ranges, which predate the /16 ranges. // In theory this shouldn't matter - in practice there's bound to be a few scripts relying - // on the internal addressing or other stupid things like that. + // on the internal addressing or other things like that. // They shouldn't, but hey, let's not break them unless we really have to. "172.17.42.1/16", // Don't use 172.16.0.0/16, it conflicts with EC2 DNS 172.16.0.23 "10.0.42.1/16", // Don't even try using the entire /8, that's too intrusive diff --git a/graph/service.go b/graph/service.go index c6d6a0872..98523aef0 100644 --- a/graph/service.go +++ b/graph/service.go @@ -68,7 +68,7 @@ func (s *TagStore) CmdSet(job *engine.Job) error { } // We have to pass an *image.Image object, even though it will be completely // ignored in favor of the redundant json data. - // FIXME: the current prototype of Graph.Register is stupid and redundant. + // FIXME: the current prototype of Graph.Register is redundant. img, err := image.NewImgJSON(imgJSON) if err != nil { return err From ef13dcd4dcbc613a9ea1e4a8af3d0220004ec0ab Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Mon, 6 Apr 2015 08:57:19 +0800 Subject: [PATCH 275/999] add TestDaemonwithwrongkey test case Signed-off-by: Yuan Sun --- integration-cli/docker_cli_daemon_test.go | 62 +++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index aae7e374c..3a10fb004 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -834,3 +834,65 @@ func TestDaemonUnixSockCleanedUp(t *testing.T) { logDone("daemon - unix socket is cleaned up") } + +func TestDaemonwithwrongkey(t *testing.T) { + type Config struct { + Crv string `json:"crv"` + D string `json:"d"` + Kid string `json:"kid"` + Kty string `json:"kty"` + X string `json:"x"` + Y string `json:"y"` + } + + os.Remove("/etc/docker/key.json") + d := NewDaemon(t) + if err := d.Start(); err != nil { + t.Fatalf("Failed to start daemon: %v", err) + } + + if err := d.Stop(); err != nil { + t.Fatalf("Could not stop daemon: %v", err) + } + + config := &Config{} + bytes, err := ioutil.ReadFile("/etc/docker/key.json") + if err != nil { + t.Fatalf("Error reading key.json file: %s", err) + } + + // byte[] to Data-Struct + if err := json.Unmarshal(bytes, &config); err != nil { + t.Fatalf("Error Unmarshal: %s", err) + } + + //replace config.Kid with the fake value + config.Kid = "VSAJ:FUYR:X3H2:B2VZ:KZ6U:CJD5:K7BX:ZXHY:UZXT:P4FT:MJWG:HRJ4" + + // NEW Data-Struct to byte[] + newBytes, err := json.Marshal(&config) + if err != nil { + t.Fatalf("Error Marshal: %s", err) + } + + // write back + if err := ioutil.WriteFile("/etc/docker/key.json", newBytes, 0400); err != nil { + t.Fatalf("Error ioutil.WriteFile: %s", err) + } + + d1 := NewDaemon(t) + + if err := d1.Start(); err == nil { + d1.Stop() + t.Fatalf("It should not be succssful to start daemon with wrong key: %v", err) + } + + content, _ := ioutil.ReadFile(d1.logFile.Name()) + + if !strings.Contains(string(content), "Public Key ID does not match") { + t.Fatal("Missing KeyID message from daemon logs") + } + + os.Remove("/etc/docker/key.json") + logDone("daemon - it should be failed to start daemon with wrong key") +} From 5057d7da132c287d52749b6c73a9cd6129a1c05a Mon Sep 17 00:00:00 2001 From: Simei He Date: Mon, 6 Apr 2015 09:00:59 +0800 Subject: [PATCH 276/999] fix the space thing. Signed-off-by: Simei He --- docs/sources/reference/commandline/cli.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index ce2265774..3b2b76228 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -2196,7 +2196,7 @@ available in the default container, you can set these using the `--ulimit` flag. values. If no `ulimits` are set, they will be inherited from the default `ulimits` set on the daemon. > `as` option is disabled now. In other words, the following script is not supported: -> `$docker run -it --ulimit as=1024 fedora /bin/bash` +> `$ docker run -it --ulimit as=1024 fedora /bin/bash` ## save From bc0cdc0a4c1c83abcf1d8500e8a39f67183963ae Mon Sep 17 00:00:00 2001 From: willhf Date: Sun, 5 Apr 2015 19:38:31 -0700 Subject: [PATCH 277/999] Fix incompatible flags conditional Signed-off-by: willhf --- runconfig/parse.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runconfig/parse.go b/runconfig/parse.go index cf4d4003a..7ada3f632 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -133,7 +133,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe return nil, nil, cmd, ErrConflictHostNetworkAndDns } - if *flNetMode == "container" && flDns.Len() > 0 { + if strings.HasPrefix(*flNetMode, "container") && flDns.Len() > 0 { return nil, nil, cmd, ErrConflictContainerNetworkAndDns } From 9287e70c127b00bddb5a8d49f5ca97c19ac0614b Mon Sep 17 00:00:00 2001 From: Raghuram Devarakonda Date: Sun, 5 Apr 2015 23:00:21 -0400 Subject: [PATCH 278/999] Add the missing option to the wget command for updating Docker. Signed-off-by: Raghuram Devarakonda --- docs/sources/installation/ubuntulinux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index eea2e58f7..c3f0e3958 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -302,5 +302,5 @@ NetworkManager (this might slow your network). To install the latest version of Docker, use the standard `-N` flag with `wget`: - $ wget -N https://get.docker.com/ | sh + $ wget -N -qO- https://get.docker.com/ | sh From 71de711189659847ea18313b7b0a57703960d0da Mon Sep 17 00:00:00 2001 From: Deng Guangxing Date: Mon, 6 Apr 2015 14:37:19 +0800 Subject: [PATCH 279/999] fix typo in cli.md Signed-off-by: Deng Guangxing --- docs/sources/reference/commandline/cli.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 5ba261374..8d4cdcbaa 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -441,7 +441,7 @@ the proxy `--default-ulimit` allows you to set the default `ulimit` options to use for all containers. It takes the same options as `--ulimit` for `docker run`. If these -defaults are not set, `ulimit` settings will be inheritted, if not set on +defaults are not set, `ulimit` settings will be inherited, if not set on `docker run`, from the Docker daemon. Any `--ulimit` options passed to `docker run` will overwrite these defaults. @@ -485,7 +485,7 @@ attaching to a tty-enabled container (i.e.: launched with `-t`). #### Examples - $ docker run -d --name topdemo ubuntu /usr/bin/top -b) + $ docker run -d --name topdemo ubuntu /usr/bin/top -b $ docker attach topdemo 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 @@ -549,7 +549,7 @@ is returned by the `docker attach` command to its caller too: -m, --memory="" Memory limit for all build containers --memory-swap="" Total memory (memory + swap), `-1` to disable swap -c, --cpu-shares CPU Shares (relative weight) - --cpuset-cpus="" CPUs in which to allow exection, e.g. `0-3`, `0,1` + --cpuset-cpus="" CPUs in which to allow execution, e.g. `0-3`, `0,1` Builds Docker images from a Dockerfile and a "context". A build's context is the files located in the specified `PATH` or `URL`. The build process can @@ -1975,7 +1975,7 @@ labels file in the current directory: $ docker run --label-file ./labels ubuntu bash The label-file format is similar to the format for loading environment -variables. (Unlike environment variables, labels are not visislbe to processes +variables. (Unlike environment variables, labels are not visible to processes running inside a container.) The following example illustrates a label-file format: From 7d0053d554c453f0ec426bde2899dd55a265f09a Mon Sep 17 00:00:00 2001 From: Shishir Mahajan Date: Wed, 1 Apr 2015 10:44:40 -0400 Subject: [PATCH 280/999] Applying: Fix for Issue 11285: warning message displayed when updating an existing image Signed-off-by: Shishir Mahajan --- api/server/server.go | 1 + graph/load.go | 2 +- graph/tags.go | 18 ++++++++++++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 87b40d21c..502f1ae9e 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -698,6 +698,7 @@ func getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWr func postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { job := eng.Job("load") job.Stdin.Add(r.Body) + job.Stdout.Add(w) return job.Run() } diff --git a/graph/load.go b/graph/load.go index a3a75bce4..ace222e3f 100644 --- a/graph/load.go +++ b/graph/load.go @@ -68,7 +68,7 @@ func (s *TagStore) CmdLoad(job *engine.Job) error { for imageName, tagMap := range repositories { for tag, address := range tagMap { - if err := s.Set(imageName, tag, address, true); err != nil { + if err := s.SetLoad(imageName, tag, address, true, job.Stdout); err != nil { return err } } diff --git a/graph/tags.go b/graph/tags.go index 4ed63d959..b6a7987ff 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -4,6 +4,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "io/ioutil" "os" "path/filepath" @@ -221,6 +222,10 @@ func (store *TagStore) Delete(repoName, ref string) (bool, error) { } func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { + return store.SetLoad(repoName, tag, imageName, force, nil) +} + +func (store *TagStore) SetLoad(repoName, tag, imageName string, force bool, out io.Writer) error { img, err := store.LookupImage(imageName) store.Lock() defer store.Unlock() @@ -243,8 +248,17 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { repoName = registry.NormalizeLocalName(repoName) if r, exists := store.Repositories[repoName]; exists { repo = r - if old, exists := store.Repositories[repoName][tag]; exists && !force { - return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", tag, old) + if old, exists := store.Repositories[repoName][tag]; exists { + + if !force { + return fmt.Errorf("Conflict: Tag %s is already set to image %s, if you want to replace it, please use -f option", tag, old) + } + + if old != img.ID && out != nil { + + fmt.Fprintf(out, "The image %s:%s already exists, renaming the old one with ID %s to empty string\n", repoName, tag, old[:12]) + + } } } else { repo = make(map[string]string) From 475c65319b4663d630711519e18d0b134c42c7f1 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 6 Apr 2015 09:21:18 -0400 Subject: [PATCH 281/999] Remove `stripTrailingCharacters` from tests This was just an alias to `strings.TrimSpace` Signed-off-by: Brian Goff --- integration-cli/docker_api_attach_test.go | 3 +- integration-cli/docker_api_inspect_test.go | 3 +- integration-cli/docker_api_resize_test.go | 4 +- .../docker_cli_attach_unix_test.go | 2 +- integration-cli/docker_cli_build_test.go | 8 ++-- integration-cli/docker_cli_commit_test.go | 12 +++--- integration-cli/docker_cli_cp_test.go | 40 +++++++++---------- integration-cli/docker_cli_create_test.go | 11 ++--- integration-cli/docker_cli_diff_test.go | 6 +-- integration-cli/docker_cli_events_test.go | 10 ++--- integration-cli/docker_cli_exec_test.go | 8 ++-- .../docker_cli_export_import_test.go | 8 ++-- integration-cli/docker_cli_import_test.go | 2 +- integration-cli/docker_cli_kill_test.go | 4 +- integration-cli/docker_cli_links_test.go | 4 +- integration-cli/docker_cli_logs_test.go | 18 ++++----- integration-cli/docker_cli_nat_test.go | 2 +- integration-cli/docker_cli_port_test.go | 6 +-- integration-cli/docker_cli_ps_test.go | 22 +++++----- integration-cli/docker_cli_rename_test.go | 4 +- integration-cli/docker_cli_restart_test.go | 6 +-- integration-cli/docker_cli_rmi_test.go | 2 +- integration-cli/docker_cli_run_test.go | 14 +++---- integration-cli/docker_cli_save_load_test.go | 20 +++++----- .../docker_cli_save_load_unix_test.go | 3 +- integration-cli/docker_cli_start_test.go | 2 +- integration-cli/docker_cli_tag_test.go | 2 +- integration-cli/docker_cli_top_test.go | 6 +-- integration-cli/docker_cli_wait_test.go | 21 +++++----- integration-cli/docker_utils.go | 2 +- integration-cli/utils.go | 4 -- 31 files changed, 130 insertions(+), 129 deletions(-) diff --git a/integration-cli/docker_api_attach_test.go b/integration-cli/docker_api_attach_test.go index b16a7bb2f..3257798c5 100644 --- a/integration-cli/docker_api_attach_test.go +++ b/integration-cli/docker_api_attach_test.go @@ -3,6 +3,7 @@ package main import ( "bytes" "os/exec" + "strings" "testing" "time" @@ -22,7 +23,7 @@ func TestGetContainersAttachWebsocket(t *testing.T) { t.Fatal(err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) config, err := websocket.NewConfig( "/containers/"+cleanedContainerID+"/attach/ws?stream=1&stdin=1&stdout=1&stderr=1", "http://localhost", diff --git a/integration-cli/docker_api_inspect_test.go b/integration-cli/docker_api_inspect_test.go index ed6f596b2..c6b1a1543 100644 --- a/integration-cli/docker_api_inspect_test.go +++ b/integration-cli/docker_api_inspect_test.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "os/exec" + "strings" "testing" ) @@ -15,7 +16,7 @@ func TestInspectApiContainerResponse(t *testing.T) { t.Fatalf("failed to create a container: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) // test on json marshal version // and latest version diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index 6ba95c305..2e7677d10 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -13,7 +13,7 @@ func TestResizeApiResponse(t *testing.T) { t.Fatalf(out, err) } defer deleteAllContainers() - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" _, err = sockRequest("POST", endpoint, nil) @@ -31,7 +31,7 @@ func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) { t.Fatalf(out, err) } defer deleteAllContainers() - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) // make sure the exited cintainer is not running runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID) diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index a3bfa5b1c..8d1573512 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -21,7 +21,7 @@ func TestAttachClosedOnContainerStop(t *testing.T) { t.Fatalf("failed to start container: %v (%v)", out, err) } - id := stripTrailingCharacters(out) + id := strings.TrimSpace(out) if err := waitRun(id); err != nil { t.Fatal(err) } diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index cec62419b..21252c42d 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -492,7 +492,7 @@ func TestBuildOnBuildForbiddenMaintainerInSourceImage(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) commitCmd := exec.Command(dockerBinary, "commit", "--run", "{\"OnBuild\":[\"MAINTAINER docker.io\"]}", cleanedContainerID, "onbuild") @@ -526,7 +526,7 @@ func TestBuildOnBuildForbiddenFromInSourceImage(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) commitCmd := exec.Command(dockerBinary, "commit", "--run", "{\"OnBuild\":[\"FROM busybox\"]}", cleanedContainerID, "onbuild") @@ -560,7 +560,7 @@ func TestBuildOnBuildForbiddenChainedInSourceImage(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) commitCmd := exec.Command(dockerBinary, "commit", "--run", "{\"OnBuild\":[\"ONBUILD RUN ls\"]}", cleanedContainerID, "onbuild") @@ -5534,7 +5534,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { t.Fatal(err, out) } - cID := stripTrailingCharacters(out) + cID := strings.TrimSpace(out) type hostConfig struct { Memory float64 // Use float64 here since the json decoder sees it that way diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index 8d596bdda..3143c21fc 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -13,7 +13,7 @@ func TestCommitAfterContainerIsDone(t *testing.T) { t.Fatalf("failed to run container: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID) if _, _, err = runCommandWithOutput(waitCmd); err != nil { @@ -26,7 +26,7 @@ func TestCommitAfterContainerIsDone(t *testing.T) { t.Fatalf("failed to commit container to image: %s, %v", out, err) } - cleanedImageID := stripTrailingCharacters(out) + cleanedImageID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedImageID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { @@ -46,7 +46,7 @@ func TestCommitWithoutPause(t *testing.T) { t.Fatalf("failed to run container: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID) if _, _, err = runCommandWithOutput(waitCmd); err != nil { @@ -59,7 +59,7 @@ func TestCommitWithoutPause(t *testing.T) { t.Fatalf("failed to commit container to image: %s, %v", out, err) } - cleanedImageID := stripTrailingCharacters(out) + cleanedImageID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedImageID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { @@ -82,7 +82,7 @@ func TestCommitPausedContainer(t *testing.T) { t.Fatalf("failed to run container: %v, output: %q", err, out) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) cmd = exec.Command(dockerBinary, "pause", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(cmd) if err != nil { @@ -94,7 +94,7 @@ func TestCommitPausedContainer(t *testing.T) { if err != nil { t.Fatalf("failed to commit container to image: %s, %v", out, err) } - cleanedImageID := stripTrailingCharacters(out) + cleanedImageID := strings.TrimSpace(out) defer deleteImages(cleanedImageID) cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Paused}}", cleanedContainerID) diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index c20289261..f448b3d58 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -30,11 +30,11 @@ func TestCpGarbagePath(t *testing.T) { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || stripTrailingCharacters(out) != "0" { + if err != nil || strings.TrimSpace(out) != "0" { t.Fatal("failed to set up container", out, err) } @@ -92,11 +92,11 @@ func TestCpRelativePath(t *testing.T) { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || stripTrailingCharacters(out) != "0" { + if err != nil || strings.TrimSpace(out) != "0" { t.Fatal("failed to set up container", out, err) } @@ -162,11 +162,11 @@ func TestCpAbsolutePath(t *testing.T) { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || stripTrailingCharacters(out) != "0" { + if err != nil || strings.TrimSpace(out) != "0" { t.Fatal("failed to set up container", out, err) } @@ -226,11 +226,11 @@ func TestCpAbsoluteSymlink(t *testing.T) { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || stripTrailingCharacters(out) != "0" { + if err != nil || strings.TrimSpace(out) != "0" { t.Fatal("failed to set up container", out, err) } @@ -290,11 +290,11 @@ func TestCpSymlinkComponent(t *testing.T) { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || stripTrailingCharacters(out) != "0" { + if err != nil || strings.TrimSpace(out) != "0" { t.Fatal("failed to set up container", out, err) } @@ -355,11 +355,11 @@ func TestCpUnprivilegedUser(t *testing.T) { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || stripTrailingCharacters(out) != "0" { + if err != nil || strings.TrimSpace(out) != "0" { t.Fatal("failed to set up container", out, err) } @@ -398,11 +398,11 @@ func TestCpSpecialFiles(t *testing.T) { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || stripTrailingCharacters(out) != "0" { + if err != nil || strings.TrimSpace(out) != "0" { t.Fatal("failed to set up container", out, err) } @@ -471,11 +471,11 @@ func TestCpVolumePath(t *testing.T) { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || stripTrailingCharacters(out) != "0" { + if err != nil || strings.TrimSpace(out) != "0" { t.Fatal("failed to set up container", out, err) } @@ -562,11 +562,11 @@ func TestCpToDot(t *testing.T) { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || stripTrailingCharacters(out) != "0" { + if err != nil || strings.TrimSpace(out) != "0" { t.Fatal("failed to set up container", out, err) } @@ -600,11 +600,11 @@ func TestCpToStdout(t *testing.T) { t.Fatalf("failed to create a container:%s\n%s", out, err) } - cID := stripTrailingCharacters(out) + cID := strings.TrimSpace(out) defer deleteContainer(cID) out, _, err = dockerCmd(t, "wait", cID) - if err != nil || stripTrailingCharacters(out) != "0" { + if err != nil || strings.TrimSpace(out) != "0" { t.Fatalf("failed to set up container:%s\n%s", out, err) } diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index e32400e60..3a3c2f07d 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -5,6 +5,7 @@ import ( "os" "os/exec" "reflect" + "strings" "testing" "time" @@ -21,7 +22,7 @@ func TestCreateArgs(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) @@ -73,7 +74,7 @@ func TestCreateHostConfig(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) @@ -114,7 +115,7 @@ func TestCreateWithPortRange(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) @@ -163,7 +164,7 @@ func TestCreateWithiLargePortRange(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) @@ -213,7 +214,7 @@ func TestCreateEchoStdout(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "start", "-ai", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(runCmd) diff --git a/integration-cli/docker_cli_diff_test.go b/integration-cli/docker_cli_diff_test.go index 36881a572..f7f8cd7a9 100644 --- a/integration-cli/docker_cli_diff_test.go +++ b/integration-cli/docker_cli_diff_test.go @@ -15,7 +15,7 @@ func TestDiffFilenameShownInOutput(t *testing.T) { t.Fatalf("failed to start the container: %s, %v", out, err) } - cleanCID := stripTrailingCharacters(out) + cleanCID := strings.TrimSpace(out) diffCmd := exec.Command(dockerBinary, "diff", cleanCID) out, _, err = runCommandWithOutput(diffCmd) @@ -52,7 +52,7 @@ func TestDiffEnsureDockerinitFilesAreIgnored(t *testing.T) { t.Fatal(out, err) } - cleanCID := stripTrailingCharacters(out) + cleanCID := strings.TrimSpace(out) diffCmd := exec.Command(dockerBinary, "diff", cleanCID) out, _, err = runCommandWithOutput(diffCmd) @@ -79,7 +79,7 @@ func TestDiffEnsureOnlyKmsgAndPtmx(t *testing.T) { t.Fatal(out, err) } - cleanCID := stripTrailingCharacters(out) + cleanCID := strings.TrimSpace(out) diffCmd := exec.Command(dockerBinary, "diff", cleanCID) out, _, err = runCommandWithOutput(diffCmd) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 3cd5edd4c..359175f82 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -182,7 +182,7 @@ func TestEventsImageImport(t *testing.T) { if err != nil { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) out, _, err = runCommandPipelineWithOutput( exec.Command(dockerBinary, "export", cleanedContainerID), @@ -258,13 +258,13 @@ func TestEventsFilterImageName(t *testing.T) { if err != nil { t.Fatal(out, err) } - container1 := stripTrailingCharacters(out) + container1 := strings.TrimSpace(out) out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_2", "-d", "busybox", "true")) if err != nil { t.Fatal(out, err) } - container2 := stripTrailingCharacters(out) + container2 := strings.TrimSpace(out) for _, s := range []string{"busybox", "busybox:latest"} { eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("image=%s", s)) @@ -302,13 +302,13 @@ func TestEventsFilterContainerID(t *testing.T) { if err != nil { t.Fatal(out, err) } - container1 := stripTrailingCharacters(out) + container1 := strings.TrimSpace(out) out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "true")) if err != nil { t.Fatal(out, err) } - container2 := stripTrailingCharacters(out) + container2 := strings.TrimSpace(out) for _, s := range []string{container1, container2, container1[:12], container2[:12]} { eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("container=%s", s)) diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 01adc43c0..f06e20a84 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -141,7 +141,7 @@ func TestExecAfterContainerRestart(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "restart", cleanedContainerID) if out, _, err = runCommandWithOutput(runCmd); err != nil { @@ -253,7 +253,7 @@ func TestExecPausedContainer(t *testing.T) { t.Fatal(out, err) } - ContainerID := stripTrailingCharacters(out) + ContainerID := strings.TrimSpace(out) pausedCmd := exec.Command(dockerBinary, "pause", "testing") out, _, _, err = runCommandWithStdoutStderr(pausedCmd) @@ -501,12 +501,12 @@ func TestLinksPingLinkedContainersOnRename(t *testing.T) { var out string out, _, _ = dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "sleep", "10") - idA := stripTrailingCharacters(out) + idA := strings.TrimSpace(out) if idA == "" { t.Fatal(out, "id should not be nil") } out, _, _ = dockerCmd(t, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "sleep", "10") - idB := stripTrailingCharacters(out) + idB := strings.TrimSpace(out) if idB == "" { t.Fatal(out, "id should not be nil") } diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go index e1aa1d667..2d03179ac 100644 --- a/integration-cli/docker_cli_export_import_test.go +++ b/integration-cli/docker_cli_export_import_test.go @@ -15,7 +15,7 @@ func TestExportContainerAndImportImage(t *testing.T) { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) @@ -35,7 +35,7 @@ func TestExportContainerAndImportImage(t *testing.T) { t.Fatalf("failed to import image: %s, %v", out, err) } - cleanedImageID := stripTrailingCharacters(out) + cleanedImageID := strings.TrimSpace(out) inspectCmd = exec.Command(dockerBinary, "inspect", cleanedImageID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { @@ -56,7 +56,7 @@ func TestExportContainerWithOutputAndImportImage(t *testing.T) { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) @@ -81,7 +81,7 @@ func TestExportContainerWithOutputAndImportImage(t *testing.T) { t.Fatalf("failed to import image: %s, %v", out, err) } - cleanedImageID := stripTrailingCharacters(out) + cleanedImageID := strings.TrimSpace(out) inspectCmd = exec.Command(dockerBinary, "inspect", cleanedImageID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { diff --git a/integration-cli/docker_cli_import_test.go b/integration-cli/docker_cli_import_test.go index 1a7ee6f20..087d08bd5 100644 --- a/integration-cli/docker_cli_import_test.go +++ b/integration-cli/docker_cli_import_test.go @@ -12,7 +12,7 @@ func TestImportDisplay(t *testing.T) { if err != nil { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) out, _, err = runCommandPipelineWithOutput( diff --git a/integration-cli/docker_cli_kill_test.go b/integration-cli/docker_cli_kill_test.go index 33135a3be..7a0d000a0 100644 --- a/integration-cli/docker_cli_kill_test.go +++ b/integration-cli/docker_cli_kill_test.go @@ -13,7 +13,7 @@ func TestKillContainer(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { @@ -47,7 +47,7 @@ func TestKillDifferentUserContainer(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index 1b46da50a..80bdfc955 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -111,9 +111,9 @@ func TestLinksPingLinkedContainersAfterRename(t *testing.T) { defer deleteAllContainers() out, _, _ := dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "sleep", "10") - idA := stripTrailingCharacters(out) + idA := strings.TrimSpace(out) out, _, _ = dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "sleep", "10") - idB := stripTrailingCharacters(out) + idB := strings.TrimSpace(out) dockerCmd(t, "rename", "container1", "container_new") dockerCmd(t, "run", "--rm", "--link", "container_new:alias1", "--link", "container2:alias2", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1") dockerCmd(t, "kill", idA) diff --git a/integration-cli/docker_cli_logs_test.go b/integration-cli/docker_cli_logs_test.go index b86a50480..c236ef085 100644 --- a/integration-cli/docker_cli_logs_test.go +++ b/integration-cli/docker_cli_logs_test.go @@ -20,7 +20,7 @@ func TestLogsContainerSmallerThanPage(t *testing.T) { t.Fatalf("run failed with errors: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) exec.Command(dockerBinary, "wait", cleanedContainerID).Run() logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) @@ -47,7 +47,7 @@ func TestLogsContainerBiggerThanPage(t *testing.T) { t.Fatalf("run failed with errors: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) exec.Command(dockerBinary, "wait", cleanedContainerID).Run() logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) @@ -74,7 +74,7 @@ func TestLogsContainerMuchBiggerThanPage(t *testing.T) { t.Fatalf("run failed with errors: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) exec.Command(dockerBinary, "wait", cleanedContainerID).Run() logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) @@ -101,7 +101,7 @@ func TestLogsTimestamps(t *testing.T) { t.Fatalf("run failed with errors: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) exec.Command(dockerBinary, "wait", cleanedContainerID).Run() logsCmd := exec.Command(dockerBinary, "logs", "-t", cleanedContainerID) @@ -144,7 +144,7 @@ func TestLogsSeparateStderr(t *testing.T) { t.Fatalf("run failed with errors: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) exec.Command(dockerBinary, "wait", cleanedContainerID).Run() logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) @@ -176,7 +176,7 @@ func TestLogsStderrInStdout(t *testing.T) { t.Fatalf("run failed with errors: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) exec.Command(dockerBinary, "wait", cleanedContainerID).Run() logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) @@ -208,7 +208,7 @@ func TestLogsTail(t *testing.T) { t.Fatalf("run failed with errors: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) exec.Command(dockerBinary, "wait", cleanedContainerID).Run() logsCmd := exec.Command(dockerBinary, "logs", "--tail", "5", cleanedContainerID) @@ -259,7 +259,7 @@ func TestLogsFollowStopped(t *testing.T) { t.Fatalf("run failed with errors: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) exec.Command(dockerBinary, "wait", cleanedContainerID).Run() logsCmd := exec.Command(dockerBinary, "logs", "-f", cleanedContainerID) @@ -294,7 +294,7 @@ func TestLogsFollowSlowStdoutConsumer(t *testing.T) { t.Fatalf("run failed with errors: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) stopSlowRead := make(chan bool) diff --git a/integration-cli/docker_cli_nat_test.go b/integration-cli/docker_cli_nat_test.go index 729977bce..35bd378e4 100644 --- a/integration-cli/docker_cli_nat_test.go +++ b/integration-cli/docker_cli_nat_test.go @@ -33,7 +33,7 @@ func TestNetworkNat(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "run", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s 8080", ifaceIP)) out, _, err = runCommandWithOutput(runCmd) diff --git a/integration-cli/docker_cli_port_test.go b/integration-cli/docker_cli_port_test.go index 6b68346d4..91c1ee300 100644 --- a/integration-cli/docker_cli_port_test.go +++ b/integration-cli/docker_cli_port_test.go @@ -16,7 +16,7 @@ func TestPortList(t *testing.T) { if err != nil { t.Fatal(out, err) } - firstID := stripTrailingCharacters(out) + firstID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", firstID, "80") out, _, err = runCommandWithOutput(runCmd) @@ -52,7 +52,7 @@ func TestPortList(t *testing.T) { if err != nil { t.Fatal(out, err) } - ID := stripTrailingCharacters(out) + ID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", ID, "80") out, _, err = runCommandWithOutput(runCmd) @@ -93,7 +93,7 @@ func TestPortList(t *testing.T) { if err != nil { t.Fatal(out, err) } - ID = stripTrailingCharacters(out) + ID = strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", ID, "80") out, _, err = runCommandWithOutput(runCmd) diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index 1d32a9320..fc0c06875 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -18,14 +18,14 @@ func TestPsListContainers(t *testing.T) { if err != nil { t.Fatal(out, err) } - firstID := stripTrailingCharacters(out) + firstID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err = runCommandWithOutput(runCmd) if err != nil { t.Fatal(out, err) } - secondID := stripTrailingCharacters(out) + secondID := strings.TrimSpace(out) // not long running runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "true") @@ -33,14 +33,14 @@ func TestPsListContainers(t *testing.T) { if err != nil { t.Fatal(out, err) } - thirdID := stripTrailingCharacters(out) + thirdID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err = runCommandWithOutput(runCmd) if err != nil { t.Fatal(out, err) } - fourthID := stripTrailingCharacters(out) + fourthID := strings.TrimSpace(out) // make sure third one is not running runCmd = exec.Command(dockerBinary, "wait", thirdID) @@ -312,7 +312,7 @@ func TestPsListContainersFilterStatus(t *testing.T) { if err != nil { t.Fatal(out, err) } - firstID := stripTrailingCharacters(out) + firstID := strings.TrimSpace(out) // make sure the exited cintainer is not running runCmd = exec.Command(dockerBinary, "wait", firstID) @@ -326,7 +326,7 @@ func TestPsListContainersFilterStatus(t *testing.T) { if err != nil { t.Fatal(out, err) } - secondID := stripTrailingCharacters(out) + secondID := strings.TrimSpace(out) // filter containers by exited runCmd = exec.Command(dockerBinary, "ps", "-q", "--filter=status=exited") @@ -361,7 +361,7 @@ func TestPsListContainersFilterID(t *testing.T) { if err != nil { t.Fatal(out, err) } - firstID := stripTrailingCharacters(out) + firstID := strings.TrimSpace(out) // start another container runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 360") @@ -391,7 +391,7 @@ func TestPsListContainersFilterName(t *testing.T) { if err != nil { t.Fatal(out, err) } - firstID := stripTrailingCharacters(out) + firstID := strings.TrimSpace(out) // start another container runCmd = exec.Command(dockerBinary, "run", "-d", "--name=b_name_to_match", "busybox", "sh", "-c", "sleep 360") @@ -419,21 +419,21 @@ func TestPsListContainersFilterLabel(t *testing.T) { if err != nil { t.Fatal(out, err) } - firstID := stripTrailingCharacters(out) + firstID := strings.TrimSpace(out) // start another container runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "match=me too", "busybox") if out, _, err = runCommandWithOutput(runCmd); err != nil { t.Fatal(out, err) } - secondID := stripTrailingCharacters(out) + secondID := strings.TrimSpace(out) // start third container runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "nomatch=me", "busybox") if out, _, err = runCommandWithOutput(runCmd); err != nil { t.Fatal(out, err) } - thirdID := stripTrailingCharacters(out) + thirdID := strings.TrimSpace(out) // filter containers by exact match runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me") diff --git a/integration-cli/docker_cli_rename_test.go b/integration-cli/docker_cli_rename_test.go index 3aaf79522..8142b03c4 100644 --- a/integration-cli/docker_cli_rename_test.go +++ b/integration-cli/docker_cli_rename_test.go @@ -15,7 +15,7 @@ func TestRenameStoppedContainer(t *testing.T) { t.Fatalf(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) @@ -51,7 +51,7 @@ func TestRenameRunningContainer(t *testing.T) { t.Fatalf(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") out, _, err = runCommandWithOutput(runCmd) if err != nil { diff --git a/integration-cli/docker_cli_restart_test.go b/integration-cli/docker_cli_restart_test.go index 99b559244..dde450dcd 100644 --- a/integration-cli/docker_cli_restart_test.go +++ b/integration-cli/docker_cli_restart_test.go @@ -16,7 +16,7 @@ func TestRestartStoppedContainer(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID) if out, _, err = runCommandWithOutput(runCmd); err != nil { @@ -60,7 +60,7 @@ func TestRestartRunningContainer(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) time.Sleep(1 * time.Second) @@ -104,7 +104,7 @@ func TestRestartWithVolumes(t *testing.T) { t.Fatal(out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ len .Volumes }}", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index fd34c2242..49bf93d52 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -16,7 +16,7 @@ func TestRmiWithContainerFails(t *testing.T) { t.Fatalf("failed to create a container: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) // try to delete the image runCmd = exec.Command(dockerBinary, "rmi", "busybox") diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 043716cae..ef7a9dee8 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -199,7 +199,7 @@ func TestRunStdinPipe(t *testing.T) { t.Fatalf("failed to run container: %v, output: %q", err, out) } - out = stripTrailingCharacters(out) + out = strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", out) if out, _, err := runCommandWithOutput(inspectCmd); err != nil { @@ -217,7 +217,7 @@ func TestRunStdinPipe(t *testing.T) { t.Fatalf("error thrown while trying to get container logs: %s, %v", logsOut, err) } - containerLogs := stripTrailingCharacters(logsOut) + containerLogs := strings.TrimSpace(logsOut) if containerLogs != "blahblah" { t.Errorf("logs didn't print the container's logs %s", containerLogs) @@ -241,7 +241,7 @@ func TestRunDetachedContainerIDPrinting(t *testing.T) { t.Fatalf("failed to run container: %v, output: %q", err, out) } - out = stripTrailingCharacters(out) + out = strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", out) if inspectOut, _, err := runCommandWithOutput(inspectCmd); err != nil { @@ -259,7 +259,7 @@ func TestRunDetachedContainerIDPrinting(t *testing.T) { t.Fatalf("rm failed to remove container: %s, %v", rmOut, err) } - rmOut = stripTrailingCharacters(rmOut) + rmOut = strings.TrimSpace(rmOut) if rmOut != out { t.Errorf("rm didn't print the container ID %s %s", out, rmOut) } @@ -277,7 +277,7 @@ func TestRunWorkingDirectory(t *testing.T) { t.Fatalf("failed to run container: %v, output: %q", err, out) } - out = stripTrailingCharacters(out) + out = strings.TrimSpace(out) if out != "/root" { t.Errorf("-w failed to set working directory") @@ -289,7 +289,7 @@ func TestRunWorkingDirectory(t *testing.T) { t.Fatal(out, err) } - out = stripTrailingCharacters(out) + out = strings.TrimSpace(out) if out != "/root" { t.Errorf("--workdir failed to set working directory") @@ -2215,7 +2215,7 @@ func TestRunWriteHostsFileAndNotCommit(t *testing.T) { func eqToBaseDiff(out string, t *testing.T) bool { cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello") out1, _, err := runCommandWithOutput(cmd) - cID := stripTrailingCharacters(out1) + cID := strings.TrimSpace(out1) cmd = exec.Command(dockerBinary, "diff", cID) baseDiff, _, err := runCommandWithOutput(cmd) if err != nil { diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go index 1dd71ee5f..c7bfb945d 100644 --- a/integration-cli/docker_cli_save_load_test.go +++ b/integration-cli/docker_cli_save_load_test.go @@ -20,7 +20,7 @@ func TestSaveXzAndLoadRepoStdout(t *testing.T) { t.Fatalf("failed to create a container: %v %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) repoName := "foobar-save-load-test-xz-gz" @@ -77,7 +77,7 @@ func TestSaveXzGzAndLoadRepoStdout(t *testing.T) { t.Fatalf("failed to create a container: %v %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) repoName := "foobar-save-load-test-xz-gz" @@ -142,7 +142,7 @@ func TestSaveSingleTag(t *testing.T) { if err != nil { t.Fatalf("failed to get repo ID: %s, %v", out, err) } - cleanedImageID := stripTrailingCharacters(out) + cleanedImageID := strings.TrimSpace(out) out, _, err = runCommandPipelineWithOutput( exec.Command(dockerBinary, "save", fmt.Sprintf("%v:latest", repoName)), @@ -170,7 +170,7 @@ func TestSaveImageId(t *testing.T) { t.Fatalf("failed to get repo ID: %s, %v", out, err) } - cleanedLongImageID := stripTrailingCharacters(out) + cleanedLongImageID := strings.TrimSpace(out) idShortCmd := exec.Command(dockerBinary, "images", "-q", repoName) out, _, err = runCommandWithOutput(idShortCmd) @@ -178,7 +178,7 @@ func TestSaveImageId(t *testing.T) { t.Fatalf("failed to get repo short ID: %s, %v", out, err) } - cleanedShortImageID := stripTrailingCharacters(out) + cleanedShortImageID := strings.TrimSpace(out) saveCmd := exec.Command(dockerBinary, "save", cleanedShortImageID) tarCmd := exec.Command("tar", "t") @@ -218,7 +218,7 @@ func TestSaveAndLoadRepoFlags(t *testing.T) { t.Fatalf("failed to create a container: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) repoName := "foobar-save-load-test" @@ -302,14 +302,14 @@ func TestSaveRepoWithMultipleImages(t *testing.T) { if out, _, err = runCommandWithOutput(runCmd); err != nil { t.Fatalf("failed to create a container: %v %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, tag) if out, _, err = runCommandWithOutput(commitCmd); err != nil { t.Fatalf("failed to commit container: %v %v", out, err) } - imageID := stripTrailingCharacters(out) + imageID := strings.TrimSpace(out) return imageID } @@ -333,7 +333,7 @@ func TestSaveRepoWithMultipleImages(t *testing.T) { if err != nil { t.Fatalf("failed to save multiple images: %s, %v", out, err) } - actual := strings.Split(stripTrailingCharacters(out), "\n") + actual := strings.Split(strings.TrimSpace(out), "\n") // make the list of expected layers out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "history", "-q", "--no-trunc", "busybox:latest")) @@ -341,7 +341,7 @@ func TestSaveRepoWithMultipleImages(t *testing.T) { t.Fatalf("failed to get history: %s, %v", out, err) } - expected := append(strings.Split(stripTrailingCharacters(out), "\n"), idFoo, idBar) + expected := append(strings.Split(strings.TrimSpace(out), "\n"), idFoo, idBar) sort.Strings(actual) sort.Strings(expected) diff --git a/integration-cli/docker_cli_save_load_unix_test.go b/integration-cli/docker_cli_save_load_unix_test.go index 29e756c04..7eb948d7a 100644 --- a/integration-cli/docker_cli_save_load_unix_test.go +++ b/integration-cli/docker_cli_save_load_unix_test.go @@ -7,6 +7,7 @@ import ( "fmt" "os" "os/exec" + "strings" "testing" "github.com/docker/docker/vendor/src/github.com/kr/pty" @@ -20,7 +21,7 @@ func TestSaveAndLoadRepoStdout(t *testing.T) { t.Fatalf("failed to create a container: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) repoName := "foobar-save-load-test" diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index 1e3253e84..25b23e888 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -49,7 +49,7 @@ func TestStartAttachCorrectExitCode(t *testing.T) { t.Fatalf("failed to run container: %v, output: %q", err, out) } - out = stripTrailingCharacters(out) + out = strings.TrimSpace(out) // make sure the container has exited before trying the "start -a" waitCmd := exec.Command(dockerBinary, "wait", out) diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go index 0b2cfc148..8a5d32271 100644 --- a/integration-cli/docker_cli_tag_test.go +++ b/integration-cli/docker_cli_tag_test.go @@ -32,7 +32,7 @@ func TestTagUnprefixedRepoByID(t *testing.T) { t.Fatalf("failed to get the image ID of busybox: %s, %v", out, err) } - cleanedImageID := stripTrailingCharacters(out) + cleanedImageID := strings.TrimSpace(out) tagCmd := exec.Command(dockerBinary, "tag", cleanedImageID, "testfoobarbaz") if out, _, err = runCommandWithOutput(tagCmd); err != nil { t.Fatal(out, err) diff --git a/integration-cli/docker_cli_top_test.go b/integration-cli/docker_cli_top_test.go index de0d3d2e8..cd996af76 100644 --- a/integration-cli/docker_cli_top_test.go +++ b/integration-cli/docker_cli_top_test.go @@ -13,7 +13,7 @@ func TestTopMultipleArgs(t *testing.T) { t.Fatalf("failed to start the container: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) topCmd := exec.Command(dockerBinary, "top", cleanedContainerID, "-o", "pid") @@ -36,7 +36,7 @@ func TestTopNonPrivileged(t *testing.T) { t.Fatalf("failed to start the container: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) topCmd := exec.Command(dockerBinary, "top", cleanedContainerID) out1, _, err := runCommandWithOutput(topCmd) @@ -75,7 +75,7 @@ func TestTopPrivileged(t *testing.T) { t.Fatalf("failed to start the container: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) + cleanedContainerID := strings.TrimSpace(out) topCmd := exec.Command(dockerBinary, "top", cleanedContainerID) out1, _, err := runCommandWithOutput(topCmd) diff --git a/integration-cli/docker_cli_wait_test.go b/integration-cli/docker_cli_wait_test.go index aece88e33..cc0e778ea 100644 --- a/integration-cli/docker_cli_wait_test.go +++ b/integration-cli/docker_cli_wait_test.go @@ -2,6 +2,7 @@ package main import ( "os/exec" + "strings" "testing" "time" ) @@ -15,7 +16,7 @@ func TestWaitNonBlockedExitZero(t *testing.T) { if err != nil { t.Fatal(out, err) } - containerID := stripTrailingCharacters(out) + containerID := strings.TrimSpace(out) status := "true" for i := 0; status != "false"; i++ { @@ -24,7 +25,7 @@ func TestWaitNonBlockedExitZero(t *testing.T) { if err != nil { t.Fatal(status, err) } - status = stripTrailingCharacters(status) + status = strings.TrimSpace(status) time.Sleep(time.Second) if i >= 60 { @@ -35,7 +36,7 @@ func TestWaitNonBlockedExitZero(t *testing.T) { runCmd = exec.Command(dockerBinary, "wait", containerID) out, _, err = runCommandWithOutput(runCmd) - if err != nil || stripTrailingCharacters(out) != "0" { + if err != nil || strings.TrimSpace(out) != "0" { t.Fatal("failed to set up container", out, err) } @@ -51,12 +52,12 @@ func TestWaitBlockedExitZero(t *testing.T) { if err != nil { t.Fatal(out, err) } - containerID := stripTrailingCharacters(out) + containerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "wait", containerID) out, _, err = runCommandWithOutput(runCmd) - if err != nil || stripTrailingCharacters(out) != "0" { + if err != nil || strings.TrimSpace(out) != "0" { t.Fatal("failed to set up container", out, err) } @@ -72,7 +73,7 @@ func TestWaitNonBlockedExitRandom(t *testing.T) { if err != nil { t.Fatal(out, err) } - containerID := stripTrailingCharacters(out) + containerID := strings.TrimSpace(out) status := "true" for i := 0; status != "false"; i++ { @@ -81,7 +82,7 @@ func TestWaitNonBlockedExitRandom(t *testing.T) { if err != nil { t.Fatal(status, err) } - status = stripTrailingCharacters(status) + status = strings.TrimSpace(status) time.Sleep(time.Second) if i >= 60 { @@ -92,7 +93,7 @@ func TestWaitNonBlockedExitRandom(t *testing.T) { runCmd = exec.Command(dockerBinary, "wait", containerID) out, _, err = runCommandWithOutput(runCmd) - if err != nil || stripTrailingCharacters(out) != "99" { + if err != nil || strings.TrimSpace(out) != "99" { t.Fatal("failed to set up container", out, err) } @@ -108,12 +109,12 @@ func TestWaitBlockedExitRandom(t *testing.T) { if err != nil { t.Fatal(out, err) } - containerID := stripTrailingCharacters(out) + containerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "wait", containerID) out, _, err = runCommandWithOutput(runCmd) - if err != nil || stripTrailingCharacters(out) != "99" { + if err != nil || strings.TrimSpace(out) != "99" { t.Fatal("failed to set up container", out, err) } diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index fabd21492..84adc374e 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -526,7 +526,7 @@ func getContainerCount() (int, error) { lines := strings.Split(out, "\n") for _, line := range lines { if strings.Contains(line, containers) { - output := stripTrailingCharacters(line) + output := strings.TrimSpace(line) output = strings.TrimLeft(output, containers) output = strings.Trim(output, " ") containerCount, err := strconv.Atoi(output) diff --git a/integration-cli/utils.go b/integration-cli/utils.go index 7beb28974..c4394095d 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -169,10 +169,6 @@ func logDone(message string) { fmt.Printf("[PASSED]: %.69s\n", message) } -func stripTrailingCharacters(target string) string { - return strings.TrimSpace(target) -} - func unmarshalJSON(data []byte, result interface{}) error { err := json.Unmarshal(data, result) if err != nil { From 7986c37996aa7321eaf3e86dd844a3e5c36c8c68 Mon Sep 17 00:00:00 2001 From: Eohyung Lee Date: Mon, 6 Apr 2015 23:27:53 +0900 Subject: [PATCH 282/999] Minor spelling fix Signed-off-by: Eohyung Lee --- integration-cli/docker_api_inspect_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_api_inspect_test.go b/integration-cli/docker_api_inspect_test.go index ed6f596b2..712acc7c8 100644 --- a/integration-cli/docker_api_inspect_test.go +++ b/integration-cli/docker_api_inspect_test.go @@ -46,7 +46,7 @@ func TestInspectApiContainerResponse(t *testing.T) { for _, key := range keys { if _, ok := inspectJSON[key]; !ok { - t.Fatalf("%s does not exist in reponse for %s version", key, testVersion) + t.Fatalf("%s does not exist in response for %s version", key, testVersion) } } //Issue #6830: type not properly converted to JSON/back From d6ada45f45c6743ec0ab3f049a0742278634a22a Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Mon, 6 Apr 2015 08:54:35 -0700 Subject: [PATCH 283/999] Closes #12042 - fix logDone format Signed-off-by: Megan Kostick --- integration-cli/docker_cli_rmi_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index fd34c2242..6e909c0c6 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -36,7 +36,7 @@ func TestRmiWithContainerFails(t *testing.T) { deleteContainer(cleanedContainerID) - logDone("rmi- container using image while rmi, should not remove image name") + logDone("rmi - container using image while rmi, should not remove image name") } func TestRmiTag(t *testing.T) { @@ -74,7 +74,7 @@ func TestRmiTag(t *testing.T) { } } - logDone("rmi - tag,rmi- tagging the same images multiple times then removing tags") + logDone("rmi - tag,rmi - tagging the same images multiple times then removing tags") } func TestRmiTagWithExistingContainers(t *testing.T) { @@ -169,5 +169,5 @@ func TestRmiBlank(t *testing.T) { if strings.Contains(out, "No such image") { t.Fatalf("Wrong error message generated: %s", out) } - logDone("rmi- blank image name") + logDone("rmi - blank image name") } From eee1efcfd6c46dbdc5da02ca12722e399a56bb12 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 3 Apr 2015 01:38:46 -0600 Subject: [PATCH 284/999] Add "builder-deb" base images for building ".deb" packages properly Signed-off-by: Andrew "Tianon" Page --- contrib/builder/deb/README.md | 5 ++ contrib/builder/deb/build.sh | 10 +++ contrib/builder/deb/debian-jessie/Dockerfile | 14 ++++ contrib/builder/deb/debian-wheezy/Dockerfile | 15 ++++ contrib/builder/deb/generate.sh | 69 ++++++++++++++++ .../deb/ubuntu-debootstrap-trusty/Dockerfile | 14 ++++ .../deb/ubuntu-debootstrap-utopic/Dockerfile | 14 ++++ .../deb/ubuntu-debootstrap-vivid/Dockerfile | 14 ++++ hack/make/.build-deb/compat | 1 + hack/make/.build-deb/control | 27 ++++++ .../.build-deb/docker-core.bash-completion | 1 + .../.build-deb/docker-core.docker.default | 1 + hack/make/.build-deb/docker-core.docker.init | 1 + .../.build-deb/docker-core.docker.upstart | 1 + hack/make/.build-deb/docker-core.install | 10 +++ hack/make/.build-deb/docker-core.manpages | 1 + hack/make/.build-deb/docker-core.postinst | 20 +++++ hack/make/.build-deb/docker-core.udev | 1 + hack/make/.build-deb/docs | 1 + hack/make/.build-deb/rules | 36 ++++++++ hack/make/build-deb | 82 +++++++++++++++++++ 21 files changed, 338 insertions(+) create mode 100644 contrib/builder/deb/README.md create mode 100755 contrib/builder/deb/build.sh create mode 100644 contrib/builder/deb/debian-jessie/Dockerfile create mode 100644 contrib/builder/deb/debian-wheezy/Dockerfile create mode 100755 contrib/builder/deb/generate.sh create mode 100644 contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile create mode 100644 contrib/builder/deb/ubuntu-debootstrap-utopic/Dockerfile create mode 100644 contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile create mode 100644 hack/make/.build-deb/compat create mode 100644 hack/make/.build-deb/control create mode 100644 hack/make/.build-deb/docker-core.bash-completion create mode 120000 hack/make/.build-deb/docker-core.docker.default create mode 120000 hack/make/.build-deb/docker-core.docker.init create mode 120000 hack/make/.build-deb/docker-core.docker.upstart create mode 100644 hack/make/.build-deb/docker-core.install create mode 100644 hack/make/.build-deb/docker-core.manpages create mode 100644 hack/make/.build-deb/docker-core.postinst create mode 120000 hack/make/.build-deb/docker-core.udev create mode 100644 hack/make/.build-deb/docs create mode 100755 hack/make/.build-deb/rules create mode 100644 hack/make/build-deb diff --git a/contrib/builder/deb/README.md b/contrib/builder/deb/README.md new file mode 100644 index 000000000..a6fd70dca --- /dev/null +++ b/contrib/builder/deb/README.md @@ -0,0 +1,5 @@ +# `dockercore/builder-deb` + +This image's tags contain the dependencies for building Docker `.deb`s for each of the Debian-based platforms Docker targets. + +To add new tags, see [`contrib/builder/deb` in https://github.com/docker/docker](https://github.com/docker/docker/tree/master/contrib/builder/deb), specifically the `generate.sh` script, whose usage is described in a comment at the top of the file. diff --git a/contrib/builder/deb/build.sh b/contrib/builder/deb/build.sh new file mode 100755 index 000000000..8271d9dc4 --- /dev/null +++ b/contrib/builder/deb/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +cd "$(dirname "$(readlink -f "$BASH_SOURCE")")" + +set -x +./generate.sh +for d in */; do + docker build -t "dockercore/builder-deb:$(basename "$d")" "$d" +done diff --git a/contrib/builder/deb/debian-jessie/Dockerfile b/contrib/builder/deb/debian-jessie/Dockerfile new file mode 100644 index 000000000..ad90a2118 --- /dev/null +++ b/contrib/builder/deb/debian-jessie/Dockerfile @@ -0,0 +1,14 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! +# + +FROM debian:jessie + +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS apparmor selinux diff --git a/contrib/builder/deb/debian-wheezy/Dockerfile b/contrib/builder/deb/debian-wheezy/Dockerfile new file mode 100644 index 000000000..87274d409 --- /dev/null +++ b/contrib/builder/deb/debian-wheezy/Dockerfile @@ -0,0 +1,15 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! +# + +FROM debian:wheezy +RUN echo deb http://http.debian.net/debian wheezy-backports main > /etc/apt/sources.list.d/wheezy-backports.list + +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS apparmor selinux diff --git a/contrib/builder/deb/generate.sh b/contrib/builder/deb/generate.sh new file mode 100755 index 000000000..cd187c7ce --- /dev/null +++ b/contrib/builder/deb/generate.sh @@ -0,0 +1,69 @@ +#!/bin/bash +set -e + +# usage: ./generate.sh [versions] +# ie: ./generate.sh +# to update all Dockerfiles in this directory +# or: ./generate.sh debian-jessie +# to only update debian-jessie/Dockerfile +# or: ./generate.sh debian-newversion +# to create a new folder and a Dockerfile within it + +cd "$(dirname "$(readlink -f "$BASH_SOURCE")")" + +versions=( "$@" ) +if [ ${#versions[@]} -eq 0 ]; then + versions=( */ ) +fi +versions=( "${versions[@]%/}" ) + +for version in "${versions[@]}"; do + distro="${version%-*}" + suite="${version##*-}" + from="${distro}:${suite}" + + mkdir -p "$version" + echo "$version -> FROM $from" + cat > "$version/Dockerfile" <<-EOF + # + # THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! + # + + FROM $from + EOF + + case "$from" in + debian:wheezy) + # add -backports, like our users have to + echo "RUN echo deb http://http.debian.net/debian $suite-backports main > /etc/apt/sources.list.d/$suite-backports.list" >> "$version/Dockerfile" + ;; + esac + + echo >> "$version/Dockerfile" + + # this list is sorted alphabetically; please keep it that way + packages=( + bash-completion # for bash-completion debhelper integration + btrfs-tools # for "btrfs/ioctl.h" (and "version.h" if possible) + build-essential # "essential for building Debian packages" + curl ca-certificates # for downloading Go + debhelper # for easy ".deb" building + dh-systemd # for systemd debhelper integration + git # for "git commit" info in "docker -v" + libapparmor-dev # for "sys/apparmor.h" + libdevmapper-dev # for "libdevmapper.h" + libsqlite3-dev # for "sqlite3.h" + ) + echo "RUN apt-get update && apt-get install -y ${packages[*]} --no-install-recommends && rm -rf /var/lib/apt/lists/*" >> "$version/Dockerfile" + + echo >> "$version/Dockerfile" + + awk '$1 == "ENV" && $2 == "GO_VERSION" { print; exit }' ../../../Dockerfile >> "$version/Dockerfile" + echo 'RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local' >> "$version/Dockerfile" + echo 'ENV PATH $PATH:/usr/local/go/bin' >> "$version/Dockerfile" + + echo >> "$version/Dockerfile" + + echo 'ENV AUTO_GOPATH 1' >> "$version/Dockerfile" + awk '$1 == "ENV" && $2 == "DOCKER_BUILDTAGS" { print; exit }' ../../../Dockerfile >> "$version/Dockerfile" +done diff --git a/contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile b/contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile new file mode 100644 index 000000000..5715b2698 --- /dev/null +++ b/contrib/builder/deb/ubuntu-debootstrap-trusty/Dockerfile @@ -0,0 +1,14 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! +# + +FROM ubuntu-debootstrap:trusty + +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS apparmor selinux diff --git a/contrib/builder/deb/ubuntu-debootstrap-utopic/Dockerfile b/contrib/builder/deb/ubuntu-debootstrap-utopic/Dockerfile new file mode 100644 index 000000000..3862b8370 --- /dev/null +++ b/contrib/builder/deb/ubuntu-debootstrap-utopic/Dockerfile @@ -0,0 +1,14 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! +# + +FROM ubuntu-debootstrap:utopic + +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS apparmor selinux diff --git a/contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile b/contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile new file mode 100644 index 000000000..15911b268 --- /dev/null +++ b/contrib/builder/deb/ubuntu-debootstrap-vivid/Dockerfile @@ -0,0 +1,14 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! +# + +FROM ubuntu-debootstrap:vivid + +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS apparmor selinux diff --git a/hack/make/.build-deb/compat b/hack/make/.build-deb/compat new file mode 100644 index 000000000..ec635144f --- /dev/null +++ b/hack/make/.build-deb/compat @@ -0,0 +1 @@ +9 diff --git a/hack/make/.build-deb/control b/hack/make/.build-deb/control new file mode 100644 index 000000000..03caae834 --- /dev/null +++ b/hack/make/.build-deb/control @@ -0,0 +1,27 @@ +Source: docker-core +Maintainer: Docker +Homepage: https://dockerproject.com +Vcs-Browser: https://github.com/docker/docker +Vcs-Git: git://github.com/docker/docker.git + +Package: docker-core +Architecture: linux-any +Depends: iptables, ${misc:Depends}, ${perl:Depends}, ${shlibs:Depends} +Recommends: aufs-tools, + ca-certificates, + cgroupfs-mount | cgroup-lite, + git, + xz-utils, + ${apparmor:Recommends} +Conflicts: docker (<< 1.5~), docker.io, lxc-docker, lxc-docker-virtual-package +Description: Docker: the open-source application container engine + Docker is an open source project to pack, ship and run any application as a + lightweight container + . + Docker containers are both hardware-agnostic and platform-agnostic. This means + they can run anywhere, from your laptop to the largest EC2 compute instance and + they can run anywhere, from your laptop to the largest EC2 compute instance and + everything in between - and they don't require you to use a particular + language, framework or packaging system. That makes them great building blocks + for deploying and scaling web apps, databases, and backend services without + depending on a particular stack or provider. diff --git a/hack/make/.build-deb/docker-core.bash-completion b/hack/make/.build-deb/docker-core.bash-completion new file mode 100644 index 000000000..6ea111930 --- /dev/null +++ b/hack/make/.build-deb/docker-core.bash-completion @@ -0,0 +1 @@ +contrib/completion/bash/docker diff --git a/hack/make/.build-deb/docker-core.docker.default b/hack/make/.build-deb/docker-core.docker.default new file mode 120000 index 000000000..4278533d6 --- /dev/null +++ b/hack/make/.build-deb/docker-core.docker.default @@ -0,0 +1 @@ +../../../contrib/init/sysvinit-debian/docker.default \ No newline at end of file diff --git a/hack/make/.build-deb/docker-core.docker.init b/hack/make/.build-deb/docker-core.docker.init new file mode 120000 index 000000000..8cb89d30d --- /dev/null +++ b/hack/make/.build-deb/docker-core.docker.init @@ -0,0 +1 @@ +../../../contrib/init/sysvinit-debian/docker \ No newline at end of file diff --git a/hack/make/.build-deb/docker-core.docker.upstart b/hack/make/.build-deb/docker-core.docker.upstart new file mode 120000 index 000000000..7e1b64a3e --- /dev/null +++ b/hack/make/.build-deb/docker-core.docker.upstart @@ -0,0 +1 @@ +../../../contrib/init/upstart/docker.conf \ No newline at end of file diff --git a/hack/make/.build-deb/docker-core.install b/hack/make/.build-deb/docker-core.install new file mode 100644 index 000000000..c3f4eb146 --- /dev/null +++ b/hack/make/.build-deb/docker-core.install @@ -0,0 +1,10 @@ +#contrib/syntax/vim/doc/* /usr/share/vim/vimfiles/doc/ +#contrib/syntax/vim/ftdetect/* /usr/share/vim/vimfiles/ftdetect/ +#contrib/syntax/vim/syntax/* /usr/share/vim/vimfiles/syntax/ +contrib/*-integration usr/share/docker-core/contrib/ +contrib/check-config.sh usr/share/docker-core/contrib/ +contrib/completion/zsh/_docker usr/share/zsh/vendor-completions/ +contrib/init/systemd/docker.service lib/systemd/system/ +contrib/init/systemd/docker.socket lib/systemd/system/ +contrib/mk* usr/share/docker-core/contrib/ +contrib/nuke-graph-directory.sh usr/share/docker-core/contrib/ diff --git a/hack/make/.build-deb/docker-core.manpages b/hack/make/.build-deb/docker-core.manpages new file mode 100644 index 000000000..d5cff8a47 --- /dev/null +++ b/hack/make/.build-deb/docker-core.manpages @@ -0,0 +1 @@ +docs/man/man*/* diff --git a/hack/make/.build-deb/docker-core.postinst b/hack/make/.build-deb/docker-core.postinst new file mode 100644 index 000000000..eeef6ca80 --- /dev/null +++ b/hack/make/.build-deb/docker-core.postinst @@ -0,0 +1,20 @@ +#!/bin/sh +set -e + +case "$1" in + configure) + if [ -z "$2" ]; then + if ! getent group docker > /dev/null; then + groupadd --system docker + fi + fi + ;; + abort-*) + # How'd we get here?? + exit 1 + ;; + *) + ;; +esac + +#DEBHELPER# diff --git a/hack/make/.build-deb/docker-core.udev b/hack/make/.build-deb/docker-core.udev new file mode 120000 index 000000000..914a36195 --- /dev/null +++ b/hack/make/.build-deb/docker-core.udev @@ -0,0 +1 @@ +../../../contrib/udev/80-docker.rules \ No newline at end of file diff --git a/hack/make/.build-deb/docs b/hack/make/.build-deb/docs new file mode 100644 index 000000000..b43bf86b5 --- /dev/null +++ b/hack/make/.build-deb/docs @@ -0,0 +1 @@ +README.md diff --git a/hack/make/.build-deb/rules b/hack/make/.build-deb/rules new file mode 100755 index 000000000..3369f4fc5 --- /dev/null +++ b/hack/make/.build-deb/rules @@ -0,0 +1,36 @@ +#!/usr/bin/make -f + +VERSION = $(shell cat VERSION) + +override_dh_gencontrol: + # if we're on Ubuntu, we need to Recommends: apparmor + echo 'apparmor:Recommends=$(shell dpkg-vendor --is Ubuntu && echo apparmor)' >> debian/docker-core.substvars + dh_gencontrol + +override_dh_auto_build: + ./hack/make.sh dynbinary + # ./docs/man/md2man-all.sh runs outside the build container (if at all), since we don't have go-md2man here + +override_dh_auto_test: + ./bundles/$(VERSION)/dynbinary/docker -v + +override_dh_strip: + # the SHA1 of dockerinit is important: don't strip it + # also, Go has lots of problems with stripping, so just don't + +override_dh_auto_install: + mkdir -p debian/docker-core/usr/bin + cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/docker)" debian/docker-core/usr/bin/docker + mkdir -p debian/docker-core/usr/libexec/docker + cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/dockerinit)" debian/docker-core/usr/libexec/docker/dockerinit + +override_dh_installinit: + # use "docker" as our service name, not "docker-core" + dh_installinit --name=docker + +override_dh_installudev: + # match our existing priority + dh_installudev --priority=z80 + +%: + dh $@ --with=systemd,bash-completion diff --git a/hack/make/build-deb b/hack/make/build-deb new file mode 100644 index 000000000..657aa04cc --- /dev/null +++ b/hack/make/build-deb @@ -0,0 +1,82 @@ +#!/bin/bash +set -e + +DEST=$1 + +# subshell so that we can export PATH without breaking other things +( + source "$(dirname "$BASH_SOURCE")/.integration-daemon-start" + + # we need to wrap up everything in between integration-daemon-start and + # integration-daemon-stop to make sure we kill the daemon and don't hang, + # even and especially on test failures + didFail= + if ! { + set -e + + # TODO consider using frozen images for the dockercore/builder-deb tags + + debVersion="${VERSION//-/'~'}" + # if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better + if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then + gitUnix="$(git log -1 --pretty='%at')" + gitDate="$(date --date "@$gitUnix" +'%Y%m%d.%H%M%S')" + gitCommit="$(git log -1 --pretty='%h')" + gitVersion="git${gitDate}.0.${gitCommit}" + # gitVersion is now something like 'git20150128.112847.0.17e840a' + debVersion="$debVersion~$gitVersion" + + # $ dpkg --compare-versions 1.5.0 gt 1.5.0~rc1 && echo true || echo false + # true + # $ dpkg --compare-versions 1.5.0~rc1 gt 1.5.0~git20150128.112847.17e840a && echo true || echo false + # true + # $ dpkg --compare-versions 1.5.0~git20150128.112847.17e840a gt 1.5.0~dev~git20150128.112847.17e840a && echo true || echo false + # true + + # ie, 1.5.0 > 1.5.0~rc1 > 1.5.0~git20150128.112847.17e840a > 1.5.0~dev~git20150128.112847.17e840a + fi + + debSource="$(awk -F ': ' '$1 == "Source" { print $2; exit }' hack/make/.build-deb/control)" + debMaintainer="$(awk -F ': ' '$1 == "Maintainer" { print $2; exit }' hack/make/.build-deb/control)" + debDate="$(date --rfc-2822)" + + # if go-md2man is available, pre-generate the man pages + ./docs/man/md2man-all.sh -q || true + # TODO decide if it's worth getting go-md2man in _each_ builder environment to avoid this + + # TODO add a configurable knob for _which_ debs to build so we don't have to modify the file or build all of them every time we need to test + for dir in contrib/builder/deb/*/; do + version="$(basename "$dir")" + suite="${version##*-}" + + image="dockercore/builder-deb:$version" + if ! docker inspect "$image" &> /dev/null; then + ( set -x && docker build -t "$image" "$dir" ) + fi + + mkdir -p "$DEST/$version" + cat > "$DEST/$version/Dockerfile.build" <<-EOF + FROM $image + WORKDIR /usr/src/docker + COPY . /usr/src/docker + RUN ln -sfv hack/make/.build-deb debian + RUN { echo '$debSource (${debVersion}-0~${suite}) $suite; urgency=low'; echo; echo ' * Version: $VERSION'; echo; echo " -- $debMaintainer $debDate"; } > debian/changelog && cat >&2 debian/changelog + RUN dpkg-buildpackage -uc -us + EOF + cp -a "$DEST/$version/Dockerfile.build" . # can't use $DEST because it's in .dockerignore... + tempImage="docker-temp/build-deb:$version" + ( set -x && docker build -t "$tempImage" -f Dockerfile.build . ) + docker run --rm "$tempImage" bash -c 'cd .. && tar -c *_*' | tar -xvC "$DEST/$version" + docker rmi "$tempImage" + done + }; then + didFail=1 + fi + + # clean up after ourselves + rm -f Dockerfile.build + + source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop" + + [ -z "$didFail" ] # "set -e" ftw +) 2>&1 | tee -a $DEST/test.log From d1c4439b5aa9bfc6767372b64e3f3a1fdb7f7b65 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 5 Apr 2015 15:01:52 +0200 Subject: [PATCH 285/999] Test events streaming, fixes #12079 Signed-off-by: Antonio Murdaca --- integration-cli/docker_cli_events_test.go | 100 ++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 6c5a41356..a41b753af 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "fmt" "os/exec" "regexp" @@ -411,3 +412,102 @@ func checkEvents(t *testing.T, events []string) { } } + +func TestEventsStreaming(t *testing.T) { + start := daemonTime(t).Unix() + + finish := make(chan struct{}) + defer close(finish) + id := make(chan string) + eventCreate := make(chan struct{}) + eventStart := make(chan struct{}) + eventDie := make(chan struct{}) + eventDestroy := make(chan struct{}) + + go func() { + eventsCmd := exec.Command(dockerBinary, "events", "--since", string(start)) + stdout, err := eventsCmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + err = eventsCmd.Start() + if err != nil { + t.Fatalf("failed to start 'docker events': %s", err) + } + + go func() { + <-finish + eventsCmd.Process.Kill() + }() + + containerID := <-id + + matchCreate := regexp.MustCompile(containerID + `: \(from busybox:latest\) create$`) + matchStart := regexp.MustCompile(containerID + `: \(from busybox:latest\) start$`) + matchDie := regexp.MustCompile(containerID + `: \(from busybox:latest\) die$`) + matchDestroy := regexp.MustCompile(containerID + `: \(from busybox:latest\) destroy$`) + + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + switch { + case matchCreate.MatchString(scanner.Text()): + close(eventCreate) + case matchStart.MatchString(scanner.Text()): + close(eventStart) + case matchDie.MatchString(scanner.Text()): + close(eventDie) + case matchDestroy.MatchString(scanner.Text()): + close(eventDestroy) + } + } + + err = eventsCmd.Wait() + if err != nil && !IsKilled(err) { + t.Fatalf("docker events had bad exit status: %s", err) + } + }() + + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + cleanedContainerID := strings.TrimSpace(out) + id <- cleanedContainerID + + select { + case <-time.After(30 * time.Second): + t.Fatal("failed to observe container create in timely fashion") + case <-eventCreate: + // ignore, done + } + + select { + case <-time.After(30 * time.Second): + t.Fatal("failed to observe container start in timely fashion") + case <-eventStart: + // ignore, done + } + + select { + case <-time.After(30 * time.Second): + t.Fatal("failed to observe container die in timely fashion") + case <-eventDie: + // ignore, done + } + + rmCmd := exec.Command(dockerBinary, "rm", cleanedContainerID) + out, _, err = runCommandWithOutput(rmCmd) + if err != nil { + t.Fatal(out, err) + } + + select { + case <-time.After(30 * time.Second): + t.Fatal("failed to observe container destroy in timely fashion") + case <-eventDestroy: + // ignore, done + } + + logDone("events - streamed to stdout") +} From 3148063af16e5c14ea543f615d136f477a4e9537 Mon Sep 17 00:00:00 2001 From: malnick Date: Sun, 5 Apr 2015 10:30:17 -0700 Subject: [PATCH 286/999] Since `COPY` has been deprecated in the recent release of Docker I updated the best practices section to inform users that `ADD` is the way forward and to not use `COPY`. Signed-off-by: malnick --- docs/sources/articles/dockerfile_best-practices.md | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/docs/sources/articles/dockerfile_best-practices.md b/docs/sources/articles/dockerfile_best-practices.md index 2ea796582..e7cb2e203 100644 --- a/docs/sources/articles/dockerfile_best-practices.md +++ b/docs/sources/articles/dockerfile_best-practices.md @@ -248,12 +248,7 @@ auto-magically bump the version of the software in your container. ### [`ADD`](https://docs.docker.com/reference/builder/#add) or [`COPY`](https://docs.docker.com/reference/builder/#copy) -Although `ADD` and `COPY` are functionally similar, generally speaking, `COPY` -is preferred. That’s because it’s more transparent than `ADD`. `COPY` only -supports the basic copying of local files into the container, while `ADD` has -some features (like local-only tar extraction and remote URL support) that are -not immediately obvious. Consequently, the best use for `ADD` is local tar file -auto-extraction into the image, as in `ADD rootfs.tar.xz /`. +Note that `COPY` has been deprecated in the most recent release of Docker and you'll be prompted as such when you use it. It is recommended to use `ADD` from this point onward. If you have multiple `Dockerfile` steps that use different files from your context, `COPY` them individually, rather than all at once. This will ensure that From 065648a8324a0df5d5d05b3df223e6b058260425 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 3 Apr 2015 17:39:06 -0700 Subject: [PATCH 287/999] Last step in removing engine.Tabel from api/client/* Signed-off-by: Doug Davis --- api/client/images.go | 107 +++++++++++++++++++++---------------------- api/client/ps.go | 46 +++++++++---------- api/common.go | 58 +++++++++++++++++++++++ api/types/types.go | 33 +++++++++++++ daemon/list.go | 73 +++++++++++++++++++---------- graph/list.go | 72 +++++++++++++++++------------ 6 files changed, 258 insertions(+), 131 deletions(-) diff --git a/api/client/images.go b/api/client/images.go index 4cfa3abaf..b47c6d65f 100644 --- a/api/client/images.go +++ b/api/client/images.go @@ -1,13 +1,14 @@ package client import ( + "encoding/json" "fmt" "net/url" "strings" "text/tabwriter" "time" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" @@ -18,26 +19,26 @@ import ( ) // 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() +func (cli *DockerCli) WalkTree(noTrunc bool, images []*types.Image, byParent map[string][]*types.Image, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *types.Image, prefix string)) { + length := len(images) if length > 1 { - for index, image := range images.Data { + for index, image := range images { if index+1 == length { printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.Get("Id")]; exists { + if subimages, exists := byParent[image.ID]; exists { cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) } } else { printNode(cli, noTrunc, image, prefix+"\u251C─") - if subimages, exists := byParent[image.Get("Id")]; exists { + if subimages, exists := byParent[image.ID]; exists { cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) } } } } else { - for _, image := range images.Data { + for _, image := range images { printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.Get("Id")]; exists { + if subimages, exists := byParent[image.ID]; exists { cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) } } @@ -45,41 +46,41 @@ 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) { +func (cli *DockerCli) printVizNode(noTrunc bool, image *types.Image, prefix string) { var ( imageID string parentID string ) if noTrunc { - imageID = image.Get("Id") - parentID = image.Get("ParentId") + imageID = image.ID + parentID = image.ParentId } else { - imageID = stringid.TruncateID(image.Get("Id")) - parentID = stringid.TruncateID(image.Get("ParentId")) + imageID = stringid.TruncateID(image.ID) + parentID = stringid.TruncateID(image.ParentId) } if parentID == "" { fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) } else { fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", parentID, imageID) } - if image.GetList("RepoTags")[0] != ":" { + if image.RepoTags[0] != ":" { fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", - imageID, imageID, strings.Join(image.GetList("RepoTags"), "\\n")) + imageID, imageID, strings.Join(image.RepoTags, "\\n")) } } // FIXME: --viz and --tree are deprecated. Remove them in a future version. -func (cli *DockerCli) printTreeNode(noTrunc bool, image *engine.Env, prefix string) { +func (cli *DockerCli) printTreeNode(noTrunc bool, image *types.Image, prefix string) { var imageID string if noTrunc { - imageID = image.Get("Id") + imageID = image.ID } else { - imageID = stringid.TruncateID(image.Get("Id")) + imageID = stringid.TruncateID(image.ID) } - fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(float64(image.GetInt64("VirtualSize")))) - if image.GetList("RepoTags")[0] != ":" { - fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.GetList("RepoTags"), ", ")) + fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(float64(image.VirtualSize))) + if image.RepoTags[0] != ":" { + fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ", ")) } else { fmt.Fprint(cli.out, "\n") } @@ -101,7 +102,6 @@ func (cli *DockerCli) CmdImages(args ...string) error { flFilter := opts.NewListOpts(nil) cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") cmd.Require(flag.Max, 1) - cmd.ParseFlags(args, true) // Consolidate all filter flags, and sanity check them early. @@ -129,44 +129,44 @@ func (cli *DockerCli) CmdImages(args ...string) error { v.Set("filters", filterJSON) } - body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil)) + rdr, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil) if err != nil { return err } - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { + images := []types.Image{} + err = json.NewDecoder(rdr).Decode(&images) + if err != nil { return err } var ( - printNode func(cli *DockerCli, noTrunc bool, image *engine.Env, prefix string) - startImage *engine.Env + printNode func(cli *DockerCli, noTrunc bool, image *types.Image, prefix string) + startImage *types.Image - roots = engine.NewTable("Created", outs.Len()) - byParent = make(map[string]*engine.Table) + roots = []*types.Image{} + byParent = make(map[string][]*types.Image) ) - for _, image := range outs.Data { - if image.Get("ParentId") == "" { - roots.Add(image) + for _, image := range images { + if image.ParentId == "" { + roots = append(roots, &image) } else { - if children, exists := byParent[image.Get("ParentId")]; exists { - children.Add(image) + if children, exists := byParent[image.ParentId]; exists { + children = append(children, &image) } else { - byParent[image.Get("ParentId")] = engine.NewTable("Created", 1) - byParent[image.Get("ParentId")].Add(image) + byParent[image.ParentId] = []*types.Image{&image} } } if matchName != "" { - if matchName == image.Get("Id") || matchName == stringid.TruncateID(image.Get("Id")) { - startImage = image + if matchName == image.ID || matchName == stringid.TruncateID(image.ID) { + startImage = &image } - for _, repotag := range image.GetList("RepoTags") { + for _, repotag := range image.RepoTags { if repotag == matchName { - startImage = image + startImage = &image } } } @@ -180,8 +180,7 @@ func (cli *DockerCli) CmdImages(args ...string) error { } if startImage != nil { - root := engine.NewTable("Created", 1) - root.Add(startImage) + root := []*types.Image{startImage} cli.WalkTree(*noTrunc, root, byParent, "", printNode) } else if matchName == "" { cli.WalkTree(*noTrunc, roots, byParent, "", printNode) @@ -207,14 +206,14 @@ func (cli *DockerCli) CmdImages(args ...string) error { v.Set("all", "1") } - body, _, err := readBody(cli.call("GET", "/images/json?"+v.Encode(), nil, nil)) - + rdr, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil) if err != nil { return err } - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { + images := []types.Image{} + err = json.NewDecoder(rdr).Decode(&images) + if err != nil { return err } @@ -227,14 +226,14 @@ func (cli *DockerCli) CmdImages(args ...string) error { } } - for _, out := range outs.Data { - outID := out.Get("Id") + for _, image := range images { + ID := image.ID if !*noTrunc { - outID = stringid.TruncateID(outID) + ID = stringid.TruncateID(ID) } - repoTags := out.GetList("RepoTags") - repoDigests := out.GetList("RepoDigests") + repoTags := image.RepoTags + repoDigests := image.RepoDigests if len(repoTags) == 1 && repoTags[0] == ":" && len(repoDigests) == 1 && repoDigests[0] == "@" { // dangling image - clear out either repoTags or repoDigsts so we only show it once below @@ -256,12 +255,12 @@ func (cli *DockerCli) CmdImages(args ...string) error { if !*quiet { if *showDigests { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, outID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), units.HumanSize(float64(out.GetInt64("VirtualSize")))) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) } } else { - fmt.Fprintln(w, outID) + fmt.Fprintln(w, ID) } } } diff --git a/api/client/ps.go b/api/client/ps.go index 35d4279a6..fdc8ef5a9 100644 --- a/api/client/ps.go +++ b/api/client/ps.go @@ -1,6 +1,7 @@ package client import ( + "encoding/json" "fmt" "net/url" "strconv" @@ -9,7 +10,7 @@ import ( "time" "github.com/docker/docker/api" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers/filters" @@ -85,13 +86,14 @@ func (cli *DockerCli) CmdPs(args ...string) error { v.Set("filters", filterJSON) } - body, _, err := readBody(cli.call("GET", "/containers/json?"+v.Encode(), nil, nil)) + rdr, _, err := cli.call("GET", "/containers/json?"+v.Encode(), nil, nil) if err != nil { return err } - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(body); err != nil { + containers := []types.Container{} + err = json.NewDecoder(rdr).Decode(&containers) + if err != nil { return err } @@ -114,54 +116,50 @@ func (cli *DockerCli) CmdPs(args ...string) error { return ss } - for _, out := range outs.Data { - outID := out.Get("Id") + for _, container := range containers { + ID := container.ID if !*noTrunc { - outID = stringid.TruncateID(outID) + ID = stringid.TruncateID(ID) } if *quiet { - fmt.Fprintln(w, outID) + fmt.Fprintln(w, ID) continue } var ( - outNames = stripNamePrefix(out.GetList("Names")) - outCommand = strconv.Quote(out.Get("Command")) - ports = engine.NewTable("", 0) + names = stripNamePrefix(container.Names) + command = strconv.Quote(container.Command) ) if !*noTrunc { - outCommand = utils.Trunc(outCommand, 20) + command = utils.Trunc(command, 20) // only display the default name for the container with notrunc is passed - for _, name := range outNames { + for _, name := range names { if len(strings.Split(name, "/")) == 1 { - outNames = []string{name} - + names = []string{name} break } } } - ports.ReadListFrom([]byte(out.Get("Ports"))) - - image := out.Get("Image") + image := container.Image if image == "" { image = "" } - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", outID, image, outCommand, - units.HumanDuration(time.Now().UTC().Sub(time.Unix(out.GetInt64("Created"), 0))), - out.Get("Status"), api.DisplayablePorts(ports), strings.Join(outNames, ",")) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", ID, image, command, + units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(container.Created), 0))), + container.Status, api.NewDisplayablePorts(container.Ports), strings.Join(names, ",")) if *size { - if out.GetInt("SizeRootFs") > 0 { - fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(float64(out.GetInt64("SizeRw"))), units.HumanSize(float64(out.GetInt64("SizeRootFs")))) + if container.SizeRootFs > 0 { + fmt.Fprintf(w, "%s (virtual %s)\n", units.HumanSize(float64(container.SizeRw)), units.HumanSize(float64(container.SizeRootFs))) } else { - fmt.Fprintf(w, "%s\n", units.HumanSize(float64(out.GetInt64("SizeRw")))) + fmt.Fprintf(w, "%s\n", units.HumanSize(float64(container.SizeRw))) } continue diff --git a/api/common.go b/api/common.go index 8251fdcf8..39224a9c1 100644 --- a/api/common.go +++ b/api/common.go @@ -5,9 +5,11 @@ import ( "mime" "os" "path/filepath" + "sort" "strings" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/version" @@ -31,6 +33,7 @@ func ValidateHost(val string) (string, error) { } // TODO remove, used on < 1.5 in getContainersJSON +// TODO this can go away when we get rid of engine.table func DisplayablePorts(ports *engine.Table) string { var ( result = []string{} @@ -80,6 +83,61 @@ func DisplayablePorts(ports *engine.Table) string { return strings.Join(result, ", ") } +type ByPrivatePort []types.Port + +func (r ByPrivatePort) Len() int { return len(r) } +func (r ByPrivatePort) Swap(i, j int) { r[i], r[j] = r[j], r[i] } +func (r ByPrivatePort) Less(i, j int) bool { return r[i].PrivatePort < r[j].PrivatePort } + +// TODO Rename to DisplayablePorts (remove "New") when engine.Table goes away +func NewDisplayablePorts(ports []types.Port) string { + var ( + result = []string{} + hostMappings = []string{} + firstInGroupMap map[string]int + lastInGroupMap map[string]int + ) + firstInGroupMap = make(map[string]int) + lastInGroupMap = make(map[string]int) + sort.Sort(ByPrivatePort(ports)) + for _, port := range ports { + var ( + current = port.PrivatePort + portKey = port.Type + firstInGroup int + lastInGroup int + ) + if port.IP != "" { + if port.PublicPort != current { + hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type)) + continue + } + portKey = fmt.Sprintf("%s/%s", port.IP, port.Type) + } + firstInGroup = firstInGroupMap[portKey] + lastInGroup = lastInGroupMap[portKey] + + if firstInGroup == 0 { + firstInGroupMap[portKey] = current + lastInGroupMap[portKey] = current + continue + } + + if current == (lastInGroup + 1) { + lastInGroupMap[portKey] = current + continue + } + result = append(result, FormGroup(portKey, firstInGroup, lastInGroup)) + firstInGroupMap[portKey] = current + lastInGroupMap[portKey] = current + } + for portKey, firstInGroup := range firstInGroupMap { + result = append(result, FormGroup(portKey, firstInGroup, lastInGroupMap[portKey])) + } + result = append(result, hostMappings...) + return strings.Join(result, ", ") +} + func FormGroup(key string, start, last int) string { var ( group string diff --git a/api/types/types.go b/api/types/types.go index ef3dd6fcd..931523b0b 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -56,3 +56,36 @@ type ImageDelete struct { Untagged string `json:",omitempty"` Deleted string `json:",omitempty"` } + +// GET "/images/json" +type Image struct { + ID string `json:"Id"` + ParentId string + RepoTags []string + RepoDigests []string + Created int + Size int + VirtualSize int + Labels map[string]string +} + +// GET "/containers/json" +type Port struct { + IP string + PrivatePort int + PublicPort int + Type string +} + +type Container struct { + ID string `json:"Id"` + Names []string `json:,omitempty"` + Image string `json:,omitempty"` + Command string `json:,omitempty"` + Created int `json:,omitempty"` + Ports []Port `json:,omitempty"` + SizeRw int `json:,omitempty"` + SizeRootFs int `json:,omitempty"` + Labels map[string]string `json:,omitempty"` + Status string `json:,omitempty"` +} diff --git a/daemon/list.go b/daemon/list.go index b1c134375..d18e4407b 100644 --- a/daemon/list.go +++ b/daemon/list.go @@ -1,18 +1,21 @@ package daemon import ( + "encoding/json" "errors" "fmt" + "sort" "strconv" "strings" - "github.com/docker/docker/graph" - "github.com/docker/docker/pkg/graphdb" - "github.com/docker/docker/utils" - + "github.com/docker/docker/api/types" "github.com/docker/docker/engine" + "github.com/docker/docker/graph" + "github.com/docker/docker/nat" + "github.com/docker/docker/pkg/graphdb" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/filters" + "github.com/docker/docker/utils" ) // List returns an array of all containers registered in the daemon. @@ -20,6 +23,12 @@ func (daemon *Daemon) List() []*Container { return daemon.containers.List() } +type ByCreated []types.Container + +func (r ByCreated) Len() int { return len(r) } +func (r ByCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] } +func (r ByCreated) Less(i, j int) bool { return r[i].Created < r[j].Created } + func (daemon *Daemon) Containers(job *engine.Job) error { var ( foundBefore bool @@ -32,7 +41,7 @@ func (daemon *Daemon) Containers(job *engine.Job) error { psFilters filters.Args filtExited []int ) - outs := engine.NewTable("Created", 0) + containers := []types.Container{} psFilters, err := filters.FromParam(job.Getenv("filters")) if err != nil { @@ -126,15 +135,16 @@ func (daemon *Daemon) Containers(job *engine.Job) error { return nil } displayed++ - out := &engine.Env{} - out.SetJson("Id", container.ID) - out.SetList("Names", names[container.ID]) + newC := types.Container{ + ID: container.ID, + Names: names[container.ID], + } img := container.Config.Image _, tag := parsers.ParseRepositoryTag(container.Config.Image) if tag == "" { img = utils.ImageReference(img, graph.DEFAULTTAG) } - out.SetJson("Image", img) + newC.Image = img if len(container.Args) > 0 { args := []string{} for _, arg := range container.Args { @@ -146,24 +156,41 @@ func (daemon *Daemon) Containers(job *engine.Job) error { } argsAsString := strings.Join(args, " ") - out.Set("Command", fmt.Sprintf("\"%s %s\"", container.Path, argsAsString)) + newC.Command = fmt.Sprintf("%s %s", container.Path, argsAsString) } else { - out.Set("Command", fmt.Sprintf("\"%s\"", container.Path)) + newC.Command = fmt.Sprintf("%s", container.Path) } - out.SetInt64("Created", container.Created.Unix()) - out.Set("Status", container.State.String()) - str, err := container.NetworkSettings.PortMappingAPI().ToListString() - if err != nil { - return err + newC.Created = int(container.Created.Unix()) + newC.Status = container.State.String() + + newC.Ports = []types.Port{} + for port, bindings := range container.NetworkSettings.Ports { + p, _ := nat.ParsePort(port.Port()) + if len(bindings) == 0 { + newC.Ports = append(newC.Ports, types.Port{ + PrivatePort: p, + Type: port.Proto(), + }) + continue + } + for _, binding := range bindings { + h, _ := nat.ParsePort(binding.HostPort) + newC.Ports = append(newC.Ports, types.Port{ + PrivatePort: p, + PublicPort: h, + Type: port.Proto(), + IP: binding.HostIp, + }) + } } - out.Set("Ports", str) + if size { sizeRw, sizeRootFs := container.GetSize() - out.SetInt64("SizeRw", sizeRw) - out.SetInt64("SizeRootFs", sizeRootFs) + newC.SizeRw = int(sizeRw) + newC.SizeRootFs = int(sizeRootFs) } - out.SetJson("Labels", container.Config.Labels) - outs.Add(out) + newC.Labels = container.Config.Labels + containers = append(containers, newC) return nil } @@ -175,8 +202,8 @@ func (daemon *Daemon) Containers(job *engine.Job) error { break } } - outs.ReverseSort() - if _, err := outs.WriteListTo(job.Stdout); err != nil { + sort.Sort(sort.Reverse(ByCreated(containers))) + if err = json.NewEncoder(job.Stdout).Encode(containers); err != nil { return err } return nil diff --git a/graph/list.go b/graph/list.go index 4d269e011..5af4b87e3 100644 --- a/graph/list.go +++ b/graph/list.go @@ -1,11 +1,14 @@ package graph import ( + "encoding/json" "fmt" "log" "path" + "sort" "strings" + "github.com/docker/docker/api/types" "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers/filters" @@ -17,6 +20,12 @@ var acceptedImageFilterTags = map[string]struct{}{ "label": {}, } +type ByCreated []*types.Image + +func (r ByCreated) Len() int { return len(r) } +func (r ByCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] } +func (r ByCreated) Less(i, j int) bool { return r[i].Created < r[j].Created } + func (s *TagStore) CmdImages(job *engine.Job) error { var ( allImages map[string]*image.Image @@ -53,7 +62,8 @@ func (s *TagStore) CmdImages(job *engine.Job) error { if err != nil { return err } - lookup := make(map[string]*engine.Env) + + lookup := make(map[string]*types.Image) s.Lock() for repoName, repository := range s.Repositories { if job.Getenv("filter") != "" { @@ -69,12 +79,12 @@ func (s *TagStore) CmdImages(job *engine.Job) error { continue } - if out, exists := lookup[id]; exists { + if lImage, exists := lookup[id]; exists { if filtTagged { if utils.DigestReference(ref) { - out.SetList("RepoDigests", append(out.GetList("RepoDigests"), imgRef)) + lImage.RepoDigests = append(lImage.RepoDigests, imgRef) } else { // Tag Ref. - out.SetList("RepoTags", append(out.GetList("RepoTags"), imgRef)) + lImage.RepoTags = append(lImage.RepoTags, imgRef) } } } else { @@ -84,23 +94,23 @@ func (s *TagStore) CmdImages(job *engine.Job) error { continue } if filtTagged { - out := &engine.Env{} - out.SetJson("ParentId", image.Parent) - out.SetJson("Id", image.ID) - out.SetInt64("Created", image.Created.Unix()) - out.SetInt64("Size", image.Size) - out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size) - out.SetJson("Labels", image.ContainerConfig.Labels) + newImage := new(types.Image) + newImage.ParentId = image.Parent + newImage.ID = image.ID + newImage.Created = int(image.Created.Unix()) + newImage.Size = int(image.Size) + newImage.VirtualSize = int(image.GetParentsSize(0) + image.Size) + newImage.Labels = image.ContainerConfig.Labels if utils.DigestReference(ref) { - out.SetList("RepoTags", []string{}) - out.SetList("RepoDigests", []string{imgRef}) + newImage.RepoTags = []string{} + newImage.RepoDigests = []string{imgRef} } else { - out.SetList("RepoTags", []string{imgRef}) - out.SetList("RepoDigests", []string{}) + newImage.RepoTags = []string{imgRef} + newImage.RepoDigests = []string{} } - lookup[id] = out + lookup[id] = newImage } } @@ -108,9 +118,9 @@ func (s *TagStore) CmdImages(job *engine.Job) error { } s.Unlock() - outs := engine.NewTable("Created", len(lookup)) + images := []*types.Image{} for _, value := range lookup { - outs.Add(value) + images = append(images, value) } // Display images which aren't part of a repository/tag @@ -119,21 +129,23 @@ func (s *TagStore) CmdImages(job *engine.Job) error { if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) { continue } - out := &engine.Env{} - out.SetJson("ParentId", image.Parent) - out.SetList("RepoTags", []string{":"}) - out.SetList("RepoDigests", []string{"@"}) - out.SetJson("Id", image.ID) - out.SetInt64("Created", image.Created.Unix()) - out.SetInt64("Size", image.Size) - out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size) - out.SetJson("Labels", image.ContainerConfig.Labels) - outs.Add(out) + newImage := new(types.Image) + newImage.ParentId = image.Parent + newImage.RepoTags = []string{":"} + newImage.RepoDigests = []string{"@"} + newImage.ID = image.ID + newImage.Created = int(image.Created.Unix()) + newImage.Size = int(image.Size) + newImage.VirtualSize = int(image.GetParentsSize(0) + image.Size) + newImage.Labels = image.ContainerConfig.Labels + + images = append(images, newImage) } } - outs.ReverseSort() - if _, err := outs.WriteListTo(job.Stdout); err != nil { + sort.Sort(sort.Reverse(ByCreated(images))) + + if err = json.NewEncoder(job.Stdout).Encode(images); err != nil { return err } return nil From ca628c6216ade64790dfbfe388fe4769433ec4aa Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 16 Mar 2015 13:18:40 -0400 Subject: [PATCH 288/999] devmapper: udev sync is a requirement closes #10664 closes #4036 Signed-off-by: Vincent Batts --- daemon/graphdriver/devmapper/deviceset.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 4d35adabc..7e0a13a95 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -963,9 +963,9 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // https://github.com/docker/docker/issues/4036 if supported := devicemapper.UdevSetSyncSupport(true); !supported { - logrus.Warnf("Udev sync is not supported. This will lead to unexpected behavior, data loss and errors") + logrus.Errorf("Udev sync is not supported. This will lead to unexpected behavior, data loss and errors") + return graphdriver.ErrNotSupported } - logrus.Debugf("devicemapper: udev sync support: %v", devicemapper.UdevSyncSupported()) if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) { return err From d2992c78dc1f71b0844b3c17d5ea9be8fff8b15e Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Mon, 6 Apr 2015 15:34:10 -0400 Subject: [PATCH 289/999] Revert "Since `COPY` has been deprecated in the recent release of Docker I updated the best practices section to inform users that `ADD` is the way forward and to not use `COPY`." COPY was not deprecated. This reverts commit 3148063af16e5c14ea543f615d136f477a4e9537. Signed-off-by: Tibor Vass --- docs/sources/articles/dockerfile_best-practices.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/sources/articles/dockerfile_best-practices.md b/docs/sources/articles/dockerfile_best-practices.md index e7cb2e203..2ea796582 100644 --- a/docs/sources/articles/dockerfile_best-practices.md +++ b/docs/sources/articles/dockerfile_best-practices.md @@ -248,7 +248,12 @@ auto-magically bump the version of the software in your container. ### [`ADD`](https://docs.docker.com/reference/builder/#add) or [`COPY`](https://docs.docker.com/reference/builder/#copy) -Note that `COPY` has been deprecated in the most recent release of Docker and you'll be prompted as such when you use it. It is recommended to use `ADD` from this point onward. +Although `ADD` and `COPY` are functionally similar, generally speaking, `COPY` +is preferred. That’s because it’s more transparent than `ADD`. `COPY` only +supports the basic copying of local files into the container, while `ADD` has +some features (like local-only tar extraction and remote URL support) that are +not immediately obvious. Consequently, the best use for `ADD` is local tar file +auto-extraction into the image, as in `ADD rootfs.tar.xz /`. If you have multiple `Dockerfile` steps that use different files from your context, `COPY` them individually, rather than all at once. This will ensure that From 5fa3a6f2482521d5731c43e076926f2f5318aa94 Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Mon, 6 Apr 2015 13:39:02 -0700 Subject: [PATCH 290/999] Pass right timestamp instead of crap from string(int64) Problem is that --since can consume anything not near looking as timestamp. Signed-off-by: Alexandr Morozov --- integration-cli/docker_cli_events_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index a41b753af..f69904dba 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -425,7 +425,7 @@ func TestEventsStreaming(t *testing.T) { eventDestroy := make(chan struct{}) go func() { - eventsCmd := exec.Command(dockerBinary, "events", "--since", string(start)) + eventsCmd := exec.Command(dockerBinary, "events", "--since", strconv.FormatInt(start, 10)) stdout, err := eventsCmd.StdoutPipe() if err != nil { t.Fatal(err) From e45bf8d2e9b43a4f5c5ed844b333733f95ccbafb Mon Sep 17 00:00:00 2001 From: Alexandr Morozov Date: Mon, 6 Apr 2015 13:40:56 -0700 Subject: [PATCH 291/999] Decrease timeouts in TestEventsStreaming Signed-off-by: Alexandr Morozov --- integration-cli/docker_cli_events_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index f69904dba..7076ab198 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -476,21 +476,21 @@ func TestEventsStreaming(t *testing.T) { id <- cleanedContainerID select { - case <-time.After(30 * time.Second): + case <-time.After(5 * time.Second): t.Fatal("failed to observe container create in timely fashion") case <-eventCreate: // ignore, done } select { - case <-time.After(30 * time.Second): + case <-time.After(5 * time.Second): t.Fatal("failed to observe container start in timely fashion") case <-eventStart: // ignore, done } select { - case <-time.After(30 * time.Second): + case <-time.After(5 * time.Second): t.Fatal("failed to observe container die in timely fashion") case <-eventDie: // ignore, done @@ -503,7 +503,7 @@ func TestEventsStreaming(t *testing.T) { } select { - case <-time.After(30 * time.Second): + case <-time.After(5 * time.Second): t.Fatal("failed to observe container destroy in timely fashion") case <-eventDestroy: // ignore, done From 6e44246fed43341938a41c6db9b115e1e64d332a Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Mon, 6 Apr 2015 14:31:42 -0700 Subject: [PATCH 292/999] Swap width/height in GetWinsize and monitorTtySize Signed-off-by: Ahmet Alp Balkan --- api/client/utils.go | 6 +++--- pkg/term/term_windows.go | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/api/client/utils.go b/api/client/utils.go index 3f8b91c36..cf11fefe5 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -283,16 +283,16 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { if runtime.GOOS == "windows" { go func() { - prevW, prevH := cli.getTtySize() + prevH, prevW := cli.getTtySize() for { time.Sleep(time.Millisecond * 250) - w, h := cli.getTtySize() + h, w := cli.getTtySize() if prevW != w || prevH != h { cli.resizeTty(id, isExec) } - prevW = w prevH = h + prevW = w } }() } else { diff --git a/pkg/term/term_windows.go b/pkg/term/term_windows.go index f43721c6a..5b637928f 100644 --- a/pkg/term/term_windows.go +++ b/pkg/term/term_windows.go @@ -48,8 +48,8 @@ func GetWinsize(fd uintptr) (*Winsize, error) { // TODO(azlinux): Set the pixel width / height of the console (currently unused by any caller) return &Winsize{ - Width: uint16(info.Window.Bottom - info.Window.Top + 1), - Height: uint16(info.Window.Right - info.Window.Left + 1), + Width: uint16(info.Window.Right - info.Window.Left + 1), + Height: uint16(info.Window.Bottom - info.Window.Top + 1), x: 0, y: 0}, nil } From 650bc2ffe545f874adeb3c1d6f54e9158a7d647e Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Mon, 6 Apr 2015 12:56:50 -0700 Subject: [PATCH 293/999] Remove engine.Table from more daemon side stuff Signed-off-by: Doug Davis --- api/server/server_unit_test.go | 26 ++++++++++++++------------ daemon/network_settings.go | 25 ------------------------- integration/api_test.go | 11 +++++++---- integration/utils_test.go | 4 ---- 4 files changed, 21 insertions(+), 45 deletions(-) diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index f83b5cc54..871f6981f 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -12,6 +12,7 @@ import ( "testing" "github.com/docker/docker/api" + "github.com/docker/docker/api/types" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/version" ) @@ -122,7 +123,7 @@ func TestGetImagesJSON(t *testing.T) { eng.Register("images", func(job *engine.Job) error { called = true v := createEnvFromGetImagesJSONStruct(sampleImage) - if _, err := v.WriteTo(job.Stdout); err != nil { + if err := json.NewEncoder(job.Stdout).Encode(v); err != nil { return err } return nil @@ -186,9 +187,10 @@ func TestGetImagesJSONLegacyFormat(t *testing.T) { var called bool eng.Register("images", func(job *engine.Job) error { called = true - outsLegacy := engine.NewTable("Created", 0) - outsLegacy.Add(createEnvFromGetImagesJSONStruct(sampleImage)) - if _, err := outsLegacy.WriteListTo(job.Stdout); err != nil { + images := []types.Image{ + createEnvFromGetImagesJSONStruct(sampleImage), + } + if err := json.NewEncoder(job.Stdout).Encode(images); err != nil { return err } return nil @@ -526,14 +528,14 @@ func assertHttpNotError(r *httptest.ResponseRecorder, t *testing.T) { } } -func createEnvFromGetImagesJSONStruct(data getImagesJSONStruct) *engine.Env { - v := &engine.Env{} - v.SetList("RepoTags", data.RepoTags) - v.Set("Id", data.Id) - v.SetInt64("Created", data.Created) - v.SetInt64("Size", data.Size) - v.SetInt64("VirtualSize", data.VirtualSize) - return v +func createEnvFromGetImagesJSONStruct(data getImagesJSONStruct) types.Image { + return types.Image{ + RepoTags: data.RepoTags, + ID: data.Id, + Created: int(data.Created), + Size: int(data.Size), + VirtualSize: int(data.VirtualSize), + } } type getImagesJSONStruct struct { diff --git a/daemon/network_settings.go b/daemon/network_settings.go index 97c2e3ab4..bf683b2f0 100644 --- a/daemon/network_settings.go +++ b/daemon/network_settings.go @@ -1,7 +1,6 @@ package daemon import ( - "github.com/docker/docker/engine" "github.com/docker/docker/nat" ) @@ -22,27 +21,3 @@ type NetworkSettings struct { PortMapping map[string]PortMapping // Deprecated Ports nat.PortMap } - -func (settings *NetworkSettings) PortMappingAPI() *engine.Table { - var outs = engine.NewTable("", 0) - for port, bindings := range settings.Ports { - p, _ := nat.ParsePort(port.Port()) - if len(bindings) == 0 { - out := &engine.Env{} - out.SetInt("PrivatePort", p) - out.Set("Type", port.Proto()) - outs.Add(out) - continue - } - for _, binding := range bindings { - out := &engine.Env{} - h, _ := nat.ParsePort(binding.HostPort) - out.SetInt("PrivatePort", p) - out.SetInt("PublicPort", h) - out.Set("Type", port.Proto()) - out.Set("IP", binding.HostIp) - outs.Add(out) - } - } - return outs -} diff --git a/integration/api_test.go b/integration/api_test.go index e978c311b..cd6b9669b 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -16,6 +16,7 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/api/server" + "github.com/docker/docker/api/types" "github.com/docker/docker/builder" "github.com/docker/docker/engine" "github.com/docker/docker/runconfig" @@ -793,12 +794,14 @@ func TestDeleteImages(t *testing.T) { t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) } - outs := engine.NewTable("Created", 0) - if _, err := outs.ReadListFrom(r2.Body.Bytes()); err != nil { + delImages := []types.ImageDelete{} + err = json.Unmarshal(r2.Body.Bytes(), &delImages) + if err != nil { t.Fatal(err) } - if len(outs.Data) != 1 { - t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs.Data)) + + if len(delImages) != 1 { + t.Fatalf("Expected %d event (untagged), got %d", 1, len(delImages)) } images = getImages(eng, t, false, "") diff --git a/integration/utils_test.go b/integration/utils_test.go index 706ac6484..c0e826a0f 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -329,10 +329,6 @@ func fakeTar() (io.ReadCloser, error) { return ioutil.NopCloser(buf), nil } -func getAllImages(eng *engine.Engine, t *testing.T) *engine.Table { - return getImages(eng, t, true, "") -} - func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) *engine.Table { job := eng.Job("images") job.SetenvBool("all", all) From 7165089f34a1ff6d1c350dcd46bdcef15667cab1 Mon Sep 17 00:00:00 2001 From: Brent Salisbury Date: Sun, 5 Apr 2015 05:57:19 -0400 Subject: [PATCH 294/999] Replaced level with layer for OSI model references - ty to @moxiegirl for the info workflow for patching images. -link to the v6 svg (grey L2 switch in the top /level/layer/) http://docs.master.dockerproject.com/article-img/ipv6_switched_network_example.svg Signed-off-by: Brent Salisbury --- docs/sources/article-img/ipv6_routed_network_example.gliffy | 2 +- docs/sources/article-img/ipv6_routed_network_example.svg | 2 +- docs/sources/articles/networking.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/article-img/ipv6_routed_network_example.gliffy b/docs/sources/article-img/ipv6_routed_network_example.gliffy index 81ab0ed87..544fd52df 100644 --- a/docs/sources/article-img/ipv6_routed_network_example.gliffy +++ b/docs/sources/article-img/ipv6_routed_network_example.gliffy @@ -1 +1 @@ -{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":903,"height":598,"nodeIndex":174,"autoFit":true,"exportBorder":false,"gridOn":false,"snapToGrid":false,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":-9.000680271168676,"y":-4.75},"max":{"x":756.0183424505415,"y":502.5}},"objects":[{"x":765.0,"y":250.0,"rotation":0.0,"id":169,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":47,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-12.982306425886122,0.0],[-41.25,0.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":663.0,"y":362.5,"rotation":270.0,"id":168,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":46,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

managed by Docker

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":747.0,"y":472.0,"rotation":0.0,"id":166,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":45,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":2,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[0.0,14.008510484195028],[0.0,-221.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":25.5,"y":254.0,"rotation":0.0,"id":162,"width":194.49999999999997,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":43,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add 2001:db8:1:1::/64 \\

    dev docker0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":239.28932188134524,"y":150.0,"rotation":0.0,"id":32,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":4,"py":0.0,"px":0.2928932188134524}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":0,"py":1.0,"px":0.7071067811865476}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cccccc","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[196.5,47.5],[151.9213562373095,-37.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":195.0,"y":261.5,"rotation":0.0,"id":35,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":11,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":2,"py":0.9999999999999998,"px":0.29289321881345254}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":13,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cccccc","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[66.28932188134524,11.0],[-92.0,91.5]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":182.0,"y":272.5,"rotation":0.0,"id":34,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":10,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":2,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":15,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cccccc","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[100.0,0.0],[82.0,80.5]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":11.5,"y":464.0,"rotation":0.0,"id":53,"width":346.49999999999994,"height":16.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":33,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add default via fe80::1 dev eth0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":11.5,"y":323.5,"rotation":0.0,"id":56,"width":346.49999999999994,"height":163.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":5,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":"2,2","dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":245.0,"y":109.0,"rotation":0.0,"id":33,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":9,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":0,"py":0.9999999999999998,"px":0.29289321881345254}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":2,"py":0.0,"px":0.7071067811865476}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cccccc","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[104.78932188134524,3.999999999999986],[57.710678118654755,88.5]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":76.5,"y":141.5,"rotation":0.0,"id":31,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":4,"py":1.0,"px":0.7071067811865476}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":25,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cccccc","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[400.71067811865476,131.0],[560.0,211.5]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":37.5,"y":145.5,"rotation":0.0,"id":30,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":6,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":4,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":27,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cccccc","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[419.0,127.0],[431.0,207.5]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":296.0,"y":21.0,"rotation":0.0,"id":87,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":41,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8::1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":293.0,"y":120.0,"rotation":0.0,"id":83,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth1 fe80::1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":433.5,"y":46.5,"rotation":0.0,"id":82,"width":291.0,"height":78.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":39,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add default via fe80::1 dev eth0

 

 

ip -6 route add 2001:db8:1::/48 via fe80::1:1 dev eth1

ip -6 route add 2001:db8:2::/48 via fe80::2:1 dev eth1

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":320.5,"y":38.0,"rotation":0.0,"id":0,"width":100.0,"height":75.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#fff2cc","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":1,"width":96.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Router

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":369.0,"y":40.0,"rotation":0.0,"id":89,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":1,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":0,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#d9d9d9","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":10.0,"controlPath":[[1.5,-2.0],[1.5,-21.125],[1.5,-21.125],[1.5,-40.25]],"lockSegments":{},"ortho":true}},"linkMap":[],"children":[]},{"x":297.75,"y":10.5,"rotation":0.0,"id":80,"width":425.99999999999994,"height":133.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":"2,2","dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":528.5,"y":199.0,"rotation":0.0,"id":73,"width":195.25,"height":31.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":35,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add default via fe80::1 \\

    dev eth0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":793.0,"y":250.0,"rotation":0.0,"id":64,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":34,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":60,"py":0.6205673758865248,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-69.25,0.0],[-798.0006802711687,-3.410605131648481E-13]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":25.5,"y":201.0,"rotation":0.0,"id":47,"width":291.0,"height":31.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":31,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add default via fe80::1 \\

   dev eth0 

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":207.0,"y":281.0,"rotation":0.0,"id":11,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":21,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

docker0 fe80::1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":220.0,"y":168.0,"rotation":0.0,"id":6,"width":150.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":18,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8:1:0::1/64

        fe80::1:1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":232.0,"y":197.5,"rotation":0.0,"id":2,"width":100.0,"height":75.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":14,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#a4c2f4","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":3,"width":96.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host1

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":11.5,"y":162.5,"rotation":0.0,"id":59,"width":346.50000000000006,"height":141.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":"2,2","dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":384.75,"y":162.5,"rotation":0.0,"id":60,"width":339.0,"height":141.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":"2,2","dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":189.0,"y":336.0,"rotation":0.0,"id":74,"width":150.0,"height":16.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":36,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8:1:1::2/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":28.000000000000014,"y":336.0,"rotation":0.0,"id":19,"width":149.99999999999997,"height":16.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":26,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8:1:1::1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":214.0,"y":353.0,"rotation":0.0,"id":15,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":24,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#ead1dc","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":16,"width":96.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Container1-2

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":53.0,"y":353.0,"rotation":0.0,"id":13,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":22,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#ead1dc","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":14,"width":96.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Container1-1

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":395.0,"y":336.0,"rotation":0.0,"id":77,"width":150.0,"height":16.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":37,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8:2:1::1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":384.75,"y":323.5,"rotation":0.0,"id":58,"width":339.75,"height":163.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":4,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":"2,2","dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":384.75,"y":463.0,"rotation":0.0,"id":51,"width":339.75,"height":16.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":32,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add default via fe80::1 dev eth0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":418.5,"y":353.0,"rotation":0.0,"id":27,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":27,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#ead1dc","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":28,"width":96.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Container2-1

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":563.0,"y":336.0,"rotation":0.0,"id":78,"width":150.0,"height":16.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8:2:1::2/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":586.5,"y":353.0,"rotation":0.0,"id":25,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":29,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#ead1dc","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":26,"width":96.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Container2-2

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":259.0,"y":491.5,"rotation":0.0,"id":107,"width":223.00000000000003,"height":11.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

containers' link-local addresses are not displayed

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":394.5,"y":168.0,"rotation":0.0,"id":7,"width":150.0,"height":31.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":19,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8:2:0::1/64
        fe80::2:1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":381.5,"y":280.0,"rotation":0.0,"id":9,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":20,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

docker0 fe80::1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":406.5,"y":197.5,"rotation":0.0,"id":4,"width":100.0,"height":75.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":16,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#a4c2f4","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":5,"width":96.0,"height":14.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host2

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":528.5,"y":252.0,"rotation":0.0,"id":164,"width":194.49999999999997,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":44,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add 2001:db8:2:1::/64 \\

    dev docker0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":766.0,"y":487.0,"rotation":0.0,"id":171,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":48,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-13.981657549458532,0.0],[-41.25,0.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#fff2cc","stroke":"#333333","strokeWidth":2,"dashStyle":"2.0,2.0","gradient":true,"shadow":true}},"lineStyles":{"global":{"stroke":"#000000","strokeWidth":1}},"textStyles":{"global":{"size":"12px"}}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.ui.ui_v3.containers_content","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.images"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file +{"contentType":"application/gliffy+json","version":"1.3","stage":{"background":"#FFFFFF","width":893,"height":447,"nodeIndex":185,"autoFit":true,"exportBorder":false,"gridOn":false,"snapToGrid":false,"drawingGuidesOn":true,"pageBreaksOn":false,"printGridOn":false,"printPaper":"LETTER","printShrinkToFit":false,"printPortrait":true,"maxWidth":5000,"maxHeight":5000,"themeData":null,"viewportType":"default","fitBB":{"min":{"x":-17.000680271168676,"y":7},"max":{"x":892.767693574114,"y":447}},"objects":[{"x":17.5,"y":205.5,"rotation":0.0,"id":167,"width":238.5,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":38,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add 2001:db8:1::/64 dev docker0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":231.28932188134524,"y":95.0,"rotation":0.0,"id":120,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":6,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":161,"py":0.0,"px":0.2928932188134524}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":131,"py":1.0,"px":0.7071067811865476}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cccccc","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[267.5,47.5],[217.9213562373095,-13.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":187.0,"y":206.5,"rotation":0.0,"id":121,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":9,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":140,"py":0.9999999999999998,"px":0.29289321881345254}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":148,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cccccc","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[130.28932188134524,11.0],[-79.0,91.5]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":174.0,"y":217.5,"rotation":0.0,"id":122,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":8,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":140,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":146,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cccccc","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[164.0,0.0],[120.0,81.5]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":33.50000000000003,"y":409.0,"rotation":0.0,"id":123,"width":346.49999999999994,"height":16.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":31,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add default via fe80::1 dev eth0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":3.5000000000000284,"y":268.5,"rotation":0.0,"id":124,"width":411.00000000000006,"height":163.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":3,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":"2,2","dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":237.0,"y":54.0,"rotation":0.0,"id":125,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":7,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":131,"py":0.9999999999999998,"px":0.29289321881345254}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":140,"py":0.0,"px":0.7071067811865476}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cccccc","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[170.78932188134524,27.999999999999986],[121.71067811865476,88.5]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":378.5,"y":7.0,"rotation":0.0,"id":131,"width":100.0,"height":75.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":10,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#e2e2e2","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":132,"width":96.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Layer 2 Switch

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":785.0,"y":195.0,"rotation":0.0,"id":136,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":32,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":143,"py":0.6187943262411347,"px":1.0}}},"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":"8.0,8.0","startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[78.75000000000011,-0.25],[-798.0006802711687,-3.410605131648481E-13]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":262.0,"y":224.0,"rotation":0.0,"id":138,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":19,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

docker0 fe80::1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":278.0,"y":126.0,"rotation":0.0,"id":139,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":16,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8:0::1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":288.0,"y":142.5,"rotation":0.0,"id":140,"width":100.0,"height":75.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":12,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#a4c2f4","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":141,"width":96.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host1

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":3.4999999999999716,"y":107.5,"rotation":0.0,"id":142,"width":411.0,"height":141.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":1,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":"2,2","dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":221.0,"y":283.0,"rotation":0.0,"id":144,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":34,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8:1::2/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":34.000000000000014,"y":283.0,"rotation":0.0,"id":145,"width":149.99999999999997,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":24,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8:1::1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":244.0,"y":299.0,"rotation":0.0,"id":146,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":22,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#ead1dc","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":147,"width":96.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Container1-2

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":58.0,"y":298.0,"rotation":0.0,"id":148,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":20,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#ead1dc","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":149,"width":96.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Container1-1

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":317.0,"y":436.5,"rotation":0.0,"id":158,"width":223.00000000000003,"height":11.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":37,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

containers' link-local addresses are not displayed

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":17.5,"y":148.0,"rotation":0.0,"id":137,"width":291.0,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":29,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add 2001:db8:0::/64 dev eth0

ip -6 route add 2001:db8:2::/64 via 2001:db8:0::2

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":901.7500000000001,"y":195.0,"rotation":0.0,"id":172,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":43,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-12.982306425886122,0.0],[-41.25,0.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":670.0,"y":284.0,"rotation":0.0,"id":155,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":36,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8:2::2/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":479.0,"y":284.0,"rotation":0.0,"id":150,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":35,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8:2::1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":488.75,"y":408.0,"rotation":0.0,"id":152,"width":339.75,"height":16.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":30,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add default via fe80::1 dev eth0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":694.5,"y":298.0,"rotation":0.0,"id":156,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":27,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#ead1dc","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":157,"width":96.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Container2-2

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":501.5,"y":298.0,"rotation":0.0,"id":153,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.square","order":25,"lockAspectRatio":true,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#ead1dc","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":154,"width":96.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Container2-1

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":444.5,"y":223.0,"rotation":0.0,"id":160,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":18,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

docker0 fe80::1/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":460.5,"y":128.0,"rotation":0.0,"id":159,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":17,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

eth0 2001:db8:0::2/64

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":469.5,"y":142.5,"rotation":0.0,"id":161,"width":100.0,"height":75.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":14,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#a4c2f4","gradient":true,"dashStyle":null,"dropShadow":true,"state":0,"opacity":1.0,"shadowX":4.0,"shadowY":4.0}},"linkMap":[],"children":[{"x":2.0,"y":0.0,"rotation":0.0,"id":162,"width":96.0,"height":13.0,"uid":null,"order":"auto","lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":8,"paddingRight":8,"paddingBottom":8,"paddingLeft":8,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

Host2

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"children":[]}]},{"x":139.5,"y":86.5,"rotation":0.0,"id":126,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":5,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":161,"py":1.0,"px":0.7071067811865476}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":156,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cccccc","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[400.71067811865476,131.0],[605.0,211.5]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":100.5,"y":90.5,"rotation":0.0,"id":127,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":4,"lockAspectRatio":false,"lockShape":false,"constraints":{"constraints":[],"startConstraint":{"type":"StartPositionConstraint","StartPositionConstraint":{"nodeId":161,"py":1.0,"px":0.5}},"endConstraint":{"type":"EndPositionConstraint","EndPositionConstraint":{"nodeId":153,"py":0.0,"px":0.5}}},"graphic":{"type":"Line","Line":{"strokeWidth":2.0,"strokeColor":"#cccccc","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[419.0,127.0],[451.0,207.5]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":447.75,"y":268.5,"rotation":0.0,"id":151,"width":416.0000000000001,"height":163.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":2,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":"2,2","dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":447.75,"y":107.5,"rotation":0.0,"id":143,"width":416.0000000000001,"height":141.0,"uid":"com.gliffy.shape.basic.basic_v1.default.rectangle","order":0,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Shape","Shape":{"tid":"com.gliffy.stencil.rectangle.basic_v1","strokeWidth":2.0,"strokeColor":"#333333","fillColor":"#FFFFFF","gradient":false,"dashStyle":"2,2","dropShadow":false,"state":0,"opacity":1.0,"shadowX":0.0,"shadowY":0.0}},"linkMap":[],"children":[]},{"x":795.7500000000001,"y":307.5,"rotation":270.0,"id":173,"width":150.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":41,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

managed by Docker

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":879.7500000000001,"y":417.0,"rotation":0.0,"id":174,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":40,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":2,"endArrow":2,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[0.0,14.008510484195028],[0.0,-221.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":898.7500000000001,"y":432.0,"rotation":0.0,"id":171,"width":100.0,"height":100.0,"uid":"com.gliffy.shape.basic.basic_v1.default.line","order":42,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Line","Line":{"strokeWidth":1.0,"strokeColor":"#000000","fillColor":"none","dashStyle":null,"startArrow":0,"endArrow":0,"startArrowRotation":"auto","endArrowRotation":"auto","interpolationType":"linear","cornerRadius":null,"controlPath":[[-13.981657549458532,0.0],[-41.25,0.0]],"lockSegments":{},"ortho":false}},"linkMap":[],"children":[]},{"x":582.5,"y":151.0,"rotation":0.0,"id":135,"width":285.25000000000017,"height":28.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":33,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add 2001:db8:0::/64 dev eth0

ip -6 route add 2001:db8:1::/64 via 2001:db8:0::1 

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]},{"x":583.0,"y":204.0,"rotation":0.0,"id":168,"width":272.0,"height":14.0,"uid":"com.gliffy.shape.basic.basic_v1.default.text","order":39,"lockAspectRatio":false,"lockShape":false,"graphic":{"type":"Text","Text":{"overflow":"none","paddingTop":2,"paddingRight":2,"paddingBottom":2,"paddingLeft":2,"outerPaddingTop":6,"outerPaddingRight":6,"outerPaddingBottom":2,"outerPaddingLeft":6,"type":"fixed","lineTValue":null,"linePerpValue":null,"cardinalityType":null,"html":"

ip -6 route add 2001:db8:2::/64 dev docker0

","tid":null,"valign":"middle","vposition":"none","hposition":"none"}},"linkMap":[],"children":[]}],"shapeStyles":{"com.gliffy.shape.basic.basic_v1.default":{"fill":"#e2e2e2","stroke":"#333333","strokeWidth":2,"dashStyle":"2.0,2.0","gradient":true,"shadow":true}},"lineStyles":{"global":{"stroke":"#000000","strokeWidth":1,"dashStyle":"8.0,8.0"}},"textStyles":{}},"metadata":{"title":"untitled","revision":0,"exportBorder":false,"loadPosition":"default","libraries":["com.gliffy.libraries.basic.basic_v1.default","com.gliffy.libraries.flowchart.flowchart_v1.default","com.gliffy.libraries.swimlanes.swimlanes_v1.default","com.gliffy.libraries.uml.uml_v2.class","com.gliffy.libraries.uml.uml_v2.sequence","com.gliffy.libraries.uml.uml_v2.activity","com.gliffy.libraries.erd.erd_v1.default","com.gliffy.libraries.ui.ui_v3.containers_content","com.gliffy.libraries.ui.ui_v3.forms_controls","com.gliffy.libraries.images"],"autosaveDisabled":false},"embeddedResources":{"index":0,"resources":[]}} \ No newline at end of file diff --git a/docs/sources/article-img/ipv6_routed_network_example.svg b/docs/sources/article-img/ipv6_routed_network_example.svg index da621433f..c97b02c26 100644 --- a/docs/sources/article-img/ipv6_routed_network_example.svg +++ b/docs/sources/article-img/ipv6_routed_network_example.svg @@ -1 +1 @@ -RouterHost1Host2eth02001:db8:1:0::1/64fe80::1:1/64eth02001:db8:2:0::1/64fe80::2:1/64docker0fe80::1/64docker0fe80::1/64Container1-1Container1-2eth02001:db8:1:1::1/64Container2-1Container2-2ip -6routeadddefaultviafe80::1 \deveth0ip-6routeadddefaultviafe80::1deveth0ip-6routeadddefaultviafe80::1deveth0ip -6routeadddefaultviafe80::1 \deveth0eth02001:db8:1:1::2/64eth02001:db8:2:1::1/64eth02001:db8:2:1::2/64ip -6routeadddefaultviafe80::1deveth0ip -6routeadd2001:db8:1::/48viafe80::1:1deveth1ip -6routeadd2001:db8:2::/48viafe80::2:1deveth1eth1fe80::1/64eth02001:db8::1/64containers'link-localaddressesarenotdisplayedip -6routeadd2001:db8:1:1::/64 \devdocker0ip -6routeadd2001:db8:2:1::/64 \devdocker0managedbyDocker \ No newline at end of file +Layer 2 SwitchHost1Host2eth0 2001:db8:0::1/64eth0 2001:db8:0::2/64docker0 fe80::1/64docker0 fe80::1/64Container1-1Container1-2eth0 2001:db8:1::1/64Container2-1Container2-2ip -6 route add 2001:db8:0::/64 dev eth0ip -6 route add 2001:db8:2::/64 via 2001:db8:0::2ip -6 route add default via fe80::1 dev eth0ip -6 route add default via fe80::1 dev eth0ip -6 route add 2001:db8:0::/64 dev eth0ip -6 route add 2001:db8:1::/64 via 2001:db8:0::1 eth0 2001:db8:1::2/64eth0 2001:db8:2::1/64eth0 2001:db8:2::2/64containers' link-local addresses are not displayedip -6 route add 2001:db8:1::/64 dev docker0ip -6 route add 2001:db8:2::/64 dev docker0managed by Docker \ No newline at end of file diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index a2bbd80f5..46a907f7e 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -643,7 +643,7 @@ adapted to the individual environment. #### Routed Network Environment -In a routed network environment you replace the level 2 switch with a level 3 +In a routed network environment you replace the layer 2 switch with a layer 3 router. Now the hosts just have to know their default gateway (the router) and the route to their own containers (managed by Docker). The router holds all routing information about the Docker subnets. When you add or remove a host to From 868f56e0839cb47223a7d988f21ae623d4ea9c6e Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 3 Apr 2015 13:58:56 -0700 Subject: [PATCH 295/999] New package daemon/events Signed-off-by: Alexander Morozov --- daemon/events/events.go | 66 +++++++++++++++++ daemon/events/events_test.go | 135 +++++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 daemon/events/events.go create mode 100644 daemon/events/events_test.go diff --git a/daemon/events/events.go b/daemon/events/events.go new file mode 100644 index 000000000..07ee29a34 --- /dev/null +++ b/daemon/events/events.go @@ -0,0 +1,66 @@ +package events + +import ( + "sync" + "time" + + "github.com/docker/docker/pkg/jsonmessage" + "github.com/docker/docker/pkg/pubsub" +) + +const eventsLimit = 64 + +// Events is pubsub channel for *jsonmessage.JSONMessage +type Events struct { + mu sync.Mutex + events []*jsonmessage.JSONMessage + pub *pubsub.Publisher +} + +// New returns new *Events instance +func New() *Events { + return &Events{ + events: make([]*jsonmessage.JSONMessage, 0, eventsLimit), + pub: pubsub.NewPublisher(100*time.Millisecond, 1024), + } +} + +// Subscribe adds new listener to events, returns slice of 64 stored last events +// channel in which you can expect new events in form of interface{}, so you +// need type assertion. +func (e *Events) Subscribe() ([]*jsonmessage.JSONMessage, chan interface{}) { + e.mu.Lock() + current := make([]*jsonmessage.JSONMessage, len(e.events)) + copy(current, e.events) + l := e.pub.Subscribe() + e.mu.Unlock() + return current, l +} + +// Evict evicts listener from pubsub +func (e *Events) Evict(l chan interface{}) { + e.pub.Evict(l) +} + +// Log broadcasts event to listeners. Each listener has 100 millisecond for +// receiving event or it will be skipped. +func (e *Events) Log(action, id, from string) { + go func() { + e.mu.Lock() + jm := &jsonmessage.JSONMessage{Status: action, ID: id, From: from, Time: time.Now().UTC().Unix()} + if len(e.events) == cap(e.events) { + // discard oldest event + copy(e.events, e.events[1:]) + e.events[len(e.events)-1] = jm + } else { + e.events = append(e.events, jm) + } + e.mu.Unlock() + e.pub.Publish(jm) + }() +} + +// SubscribersCount returns number of event listeners +func (e *Events) SubscribersCount() int { + return e.pub.Len() +} diff --git a/daemon/events/events_test.go b/daemon/events/events_test.go new file mode 100644 index 000000000..7aa8d9fac --- /dev/null +++ b/daemon/events/events_test.go @@ -0,0 +1,135 @@ +package events + +import ( + "fmt" + "testing" + "time" + + "github.com/docker/docker/pkg/jsonmessage" +) + +func TestEventsLog(t *testing.T) { + e := New() + _, l1 := e.Subscribe() + _, l2 := e.Subscribe() + defer e.Evict(l1) + defer e.Evict(l2) + count := e.SubscribersCount() + if count != 2 { + t.Fatalf("Must be 2 subscribers, got %d", count) + } + e.Log("test", "cont", "image") + select { + case msg := <-l1: + jmsg, ok := msg.(*jsonmessage.JSONMessage) + if !ok { + t.Fatalf("Unexpected type %T", msg) + } + if len(e.events) != 1 { + t.Fatalf("Must be only one event, got %d", len(e.events)) + } + if jmsg.Status != "test" { + t.Fatalf("Status should be test, got %s", jmsg.Status) + } + if jmsg.ID != "cont" { + t.Fatalf("ID should be cont, got %s", jmsg.ID) + } + if jmsg.From != "image" { + t.Fatalf("From should be image, got %s", jmsg.From) + } + case <-time.After(1 * time.Second): + t.Fatal("Timeout waiting for broadcasted message") + } + select { + case msg := <-l2: + jmsg, ok := msg.(*jsonmessage.JSONMessage) + if !ok { + t.Fatalf("Unexpected type %T", msg) + } + if len(e.events) != 1 { + t.Fatalf("Must be only one event, got %d", len(e.events)) + } + if jmsg.Status != "test" { + t.Fatalf("Status should be test, got %s", jmsg.Status) + } + if jmsg.ID != "cont" { + t.Fatalf("ID should be cont, got %s", jmsg.ID) + } + if jmsg.From != "image" { + t.Fatalf("From should be image, got %s", jmsg.From) + } + case <-time.After(1 * time.Second): + t.Fatal("Timeout waiting for broadcasted message") + } +} + +func TestEventsLogTimeout(t *testing.T) { + e := New() + _, l := e.Subscribe() + defer e.Evict(l) + + c := make(chan struct{}) + go func() { + e.Log("test", "cont", "image") + close(c) + }() + + select { + case <-c: + case <-time.After(time.Second): + t.Fatal("Timeout publishing message") + } +} + +func TestLogEvents(t *testing.T) { + e := New() + + for i := 0; i < eventsLimit+16; i++ { + action := fmt.Sprintf("action_%d", i) + id := fmt.Sprintf("cont_%d", i) + from := fmt.Sprintf("image_%d", i) + e.Log(action, id, from) + } + time.Sleep(50 * time.Millisecond) + current, l := e.Subscribe() + for i := 0; i < 10; i++ { + num := i + eventsLimit + 16 + action := fmt.Sprintf("action_%d", num) + id := fmt.Sprintf("cont_%d", num) + from := fmt.Sprintf("image_%d", num) + e.Log(action, id, from) + } + if len(e.events) != eventsLimit { + t.Fatalf("Must be %d events, got %d", eventsLimit, len(e.events)) + } + + var msgs []*jsonmessage.JSONMessage + for len(msgs) < 10 { + m := <-l + jm, ok := (m).(*jsonmessage.JSONMessage) + if !ok { + t.Fatalf("Unexpected type %T", m) + } + msgs = append(msgs, jm) + } + if len(current) != eventsLimit { + t.Fatalf("Must be %d events, got %d", eventsLimit, len(current)) + } + first := current[0] + if first.Status != "action_16" { + t.Fatalf("First action is %s, must be action_16", first.Status) + } + last := current[len(current)-1] + if last.Status != "action_79" { + t.Fatalf("Last action is %s, must be action_79", last.Status) + } + + firstC := msgs[0] + if firstC.Status != "action_80" { + t.Fatalf("First action is %s, must be action_80", firstC.Status) + } + lastC := msgs[len(msgs)-1] + if lastC.Status != "action_89" { + t.Fatalf("Last action is %s, must be action_89", lastC.Status) + } +} From c9eb37f9752d72d9a4280d703368e5e73adfffa1 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 3 Apr 2015 15:17:49 -0700 Subject: [PATCH 296/999] Remove engine usage for events Signed-off-by: Alexander Morozov --- api/server/server.go | 106 +++++++++++++++++++++++++++++++-- api/server/server_unit_test.go | 41 ------------- builtins/builtins.go | 4 -- daemon/container.go | 8 ++- daemon/daemon.go | 6 +- daemon/image_delete.go | 3 +- daemon/info.go | 7 +-- graph/import.go | 6 +- graph/pull.go | 8 +-- graph/tags.go | 5 +- graph/tags_unit_test.go | 3 +- 11 files changed, 123 insertions(+), 74 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 502f1ae9e..795ca080e 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -3,6 +3,7 @@ package server import ( "bufio" "bytes" + "time" "encoding/base64" "encoding/json" @@ -23,7 +24,9 @@ import ( "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" + "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/parsers/filters" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/version" @@ -324,13 +327,104 @@ func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWrite if err := parseForm(r); err != nil { return err } + var since int64 = -1 + if r.Form.Get("since") != "" { + s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64) + if err != nil { + return err + } + since = s + } - var job = eng.Job("events") - streamJSON(job, w, true) - job.Setenv("since", r.Form.Get("since")) - job.Setenv("until", r.Form.Get("until")) - job.Setenv("filters", r.Form.Get("filters")) - return job.Run() + var until int64 = -1 + if r.Form.Get("until") != "" { + u, err := strconv.ParseInt(r.Form.Get("until"), 10, 64) + if err != nil { + return err + } + until = u + } + timer := time.NewTimer(0) + timer.Stop() + if until > 0 { + dur := time.Unix(until, 0).Sub(time.Now()) + timer = time.NewTimer(dur) + } + + ef, err := filters.FromParam(r.Form.Get("filters")) + if err != nil { + return err + } + + isFiltered := func(field string, filter []string) bool { + if len(filter) == 0 { + return false + } + for _, v := range filter { + if v == field { + return false + } + if strings.Contains(field, ":") { + image := strings.Split(field, ":") + if image[0] == v { + return false + } + } + } + return true + } + + d := getDaemon(eng) + es := d.EventsService + w.Header().Set("Content-Type", "application/json") + enc := json.NewEncoder(utils.NewWriteFlusher(w)) + + getContainerId := func(cn string) string { + c, err := d.Get(cn) + if err != nil { + return "" + } + return c.ID + } + + sendEvent := func(ev *jsonmessage.JSONMessage) error { + //incoming container filter can be name,id or partial id, convert and replace as a full container id + for i, cn := range ef["container"] { + ef["container"][i] = getContainerId(cn) + } + + if isFiltered(ev.Status, ef["event"]) || isFiltered(ev.From, ef["image"]) || + isFiltered(ev.ID, ef["container"]) { + return nil + } + + return enc.Encode(ev) + } + + current, l := es.Subscribe() + defer es.Evict(l) + for _, ev := range current { + if ev.Time < since { + continue + } + if err := sendEvent(ev); err != nil { + return err + } + } + for { + select { + case ev := <-l: + jev, ok := ev.(*jsonmessage.JSONMessage) + if !ok { + continue + } + if err := sendEvent(jev); err != nil { + return err + } + case <-timer.C: + return nil + } + } } func getImagesHistory(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index f83b5cc54..8e8409ffa 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -250,47 +250,6 @@ func TestGetContainersByName(t *testing.T) { } } -func TestGetEvents(t *testing.T) { - eng := engine.New() - var called bool - eng.Register("events", func(job *engine.Job) error { - called = true - since := job.Getenv("since") - if since != "1" { - t.Fatalf("'since' should be 1, found %#v instead", since) - } - until := job.Getenv("until") - if until != "0" { - t.Fatalf("'until' should be 0, found %#v instead", until) - } - v := &engine.Env{} - v.Set("since", since) - v.Set("until", until) - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil - }) - r := serveRequest("GET", "/events?since=1&until=0", nil, eng, t) - if !called { - t.Fatal("handler was not called") - } - assertContentType(r, "application/json", t) - var stdoutJSON struct { - Since int - Until int - } - if err := json.Unmarshal(r.Body.Bytes(), &stdoutJSON); err != nil { - t.Fatal(err) - } - if stdoutJSON.Since != 1 { - t.Errorf("since != 1: %#v", stdoutJSON.Since) - } - if stdoutJSON.Until != 0 { - t.Errorf("until != 0: %#v", stdoutJSON.Until) - } -} - func TestLogs(t *testing.T) { eng := engine.New() var inspect bool diff --git a/builtins/builtins.go b/builtins/builtins.go index d87bdb87a..149d35009 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -8,7 +8,6 @@ import ( "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" - "github.com/docker/docker/events" "github.com/docker/docker/pkg/parsers/kernel" ) @@ -19,9 +18,6 @@ func Register(eng *engine.Engine) error { if err := remote(eng); err != nil { return err } - if err := events.New().Install(eng); err != nil { - return err - } if err := eng.Register("version", dockerVersion); err != nil { return err } diff --git a/daemon/container.go b/daemon/container.go index 228944d2d..2d9487ea6 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -200,9 +200,11 @@ func (container *Container) WriteHostConfig() error { func (container *Container) LogEvent(action string) { d := container.daemon - if err := d.eng.Job("log", action, container.ID, d.Repositories().ImageName(container.ImageID)).Run(); err != nil { - logrus.Errorf("Error logging event %s for %s: %s", action, container.ID, err) - } + d.EventsService.Log( + action, + container.ID, + d.Repositories().ImageName(container.ImageID), + ) } func (container *Container) getResourcePath(path string) (string, error) { diff --git a/daemon/daemon.go b/daemon/daemon.go index a072fc34a..460625c14 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -19,6 +19,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/daemon/events" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/execdriver/execdrivers" "github.com/docker/docker/daemon/execdriver/lxc" @@ -109,6 +110,7 @@ type Daemon struct { statsCollector *statsCollector defaultLogConfig runconfig.LogConfig RegistryService *registry.Service + EventsService *events.Events } // Install installs daemon capabilities to eng. @@ -932,8 +934,9 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService return nil, err } + eventsService := events.New() logrus.Debug("Creating repository list") - repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, trustKey, registryService) + repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, trustKey, registryService, eventsService) if err != nil { return nil, fmt.Errorf("Couldn't create Tag store: %s", err) } @@ -1025,6 +1028,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService statsCollector: newStatsCollector(1 * time.Second), defaultLogConfig: config.LogConfig, RegistryService: registryService, + EventsService: eventsService, } eng.OnShutdown(func() { diff --git a/daemon/image_delete.go b/daemon/image_delete.go index bf3d7ba9c..6323d323e 100644 --- a/daemon/image_delete.go +++ b/daemon/image_delete.go @@ -108,7 +108,7 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, list *[]types *list = append(*list, types.ImageDelete{ Untagged: utils.ImageReference(repoName, tag), }) - eng.Job("log", "untag", img.ID, "").Run() + daemon.EventsService.Log("untag", img.ID, "") } } tags = daemon.Repositories().ByID()[img.ID] @@ -123,6 +123,7 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, list *[]types *list = append(*list, types.ImageDelete{ Deleted: img.ID, }) + daemon.EventsService.Log("delete", img.ID, "") eng.Job("log", "delete", img.ID, "").Run() if img.Parent != "" && !noprune { err := daemon.DeleteImage(eng, img.Parent, list, false, force, noprune) diff --git a/daemon/info.go b/daemon/info.go index 687937e3e..183a9e68b 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -51,11 +51,6 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) error { initPath = daemon.SystemInitPath() } - cjob := job.Eng.Job("subscribers_count") - env, _ := cjob.Stdout.AddEnv() - if err := cjob.Run(); err != nil { - return err - } v := &engine.Env{} v.SetJson("ID", daemon.ID) v.SetInt("Containers", len(daemon.List())) @@ -71,7 +66,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) error { v.Set("SystemTime", time.Now().Format(time.RFC3339Nano)) v.Set("ExecutionDriver", daemon.ExecutionDriver().Name()) v.Set("LoggingDriver", daemon.defaultLogConfig.Type) - v.SetInt("NEventsListener", env.GetInt("count")) + v.SetInt("NEventsListener", daemon.EventsService.SubscribersCount()) v.Set("KernelVersion", kernelVersion) v.Set("OperatingSystem", operatingSystem) v.Set("IndexServerAddress", registry.IndexServerAddress()) diff --git a/graph/import.go b/graph/import.go index 8b9918896..eb63af0b6 100644 --- a/graph/import.go +++ b/graph/import.go @@ -7,7 +7,6 @@ import ( "net/http" "net/url" - "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/progressreader" @@ -92,8 +91,7 @@ func (s *TagStore) CmdImport(job *engine.Job) error { if tag != "" { logID = utils.ImageReference(logID, tag) } - if err = job.Eng.Job("log", "import", logID, "").Run(); err != nil { - logrus.Errorf("Error logging event 'import' for %s: %s", logID, err) - } + + s.eventsService.Log("import", logID, "") return nil } diff --git a/graph/pull.go b/graph/pull.go index 08b688cb2..13c69858f 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -85,9 +85,7 @@ func (s *TagStore) CmdPull(job *engine.Job) error { logrus.Debugf("pulling v2 repository with local name %q", repoInfo.LocalName) if err := s.pullV2Repository(job.Eng, r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err == nil { - if err = job.Eng.Job("log", "pull", logName, "").Run(); err != nil { - logrus.Errorf("Error logging event 'pull' for %s: %s", logName, err) - } + s.eventsService.Log("pull", logName, "") return nil } else if err != registry.ErrDoesNotExist && err != ErrV2RegistryUnavailable { logrus.Errorf("Error from V2 registry: %s", err) @@ -101,9 +99,7 @@ func (s *TagStore) CmdPull(job *engine.Job) error { return err } - if err = job.Eng.Job("log", "pull", logName, "").Run(); err != nil { - logrus.Errorf("Error logging event 'pull' for %s: %s", logName, err) - } + s.eventsService.Log("pull", logName, "") return nil } diff --git a/graph/tags.go b/graph/tags.go index b6a7987ff..6346ea8b5 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -13,6 +13,7 @@ import ( "strings" "sync" + "github.com/docker/docker/daemon/events" "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/stringid" @@ -40,6 +41,7 @@ type TagStore struct { pullingPool map[string]chan struct{} pushingPool map[string]chan struct{} registryService *registry.Service + eventsService *events.Events } type Repository map[string]string @@ -62,7 +64,7 @@ func (r Repository) Contains(u Repository) bool { return true } -func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey, registryService *registry.Service) (*TagStore, error) { +func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey, registryService *registry.Service, eventsService *events.Events) (*TagStore, error) { abspath, err := filepath.Abs(path) if err != nil { return nil, err @@ -76,6 +78,7 @@ func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey, registrySer pullingPool: make(map[string]chan struct{}), pushingPool: make(map[string]chan struct{}), registryService: registryService, + eventsService: eventsService, } // Load the json file if it exists, otherwise create it. if err := store.reload(); os.IsNotExist(err) { diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go index 001a10527..be5624245 100644 --- a/graph/tags_unit_test.go +++ b/graph/tags_unit_test.go @@ -7,6 +7,7 @@ import ( "path" "testing" + "github.com/docker/docker/daemon/events" "github.com/docker/docker/daemon/graphdriver" _ "github.com/docker/docker/daemon/graphdriver/vfs" // import the vfs driver so it is used in the tests "github.com/docker/docker/image" @@ -59,7 +60,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore { if err != nil { t.Fatal(err) } - store, err := NewTagStore(path.Join(root, "tags"), graph, nil, nil) + store, err := NewTagStore(path.Join(root, "tags"), graph, nil, nil, events.New()) if err != nil { t.Fatal(err) } From d487ca03e6e897e4bb5f9ba28b268450f059fc0d Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 3 Apr 2015 15:18:12 -0700 Subject: [PATCH 297/999] Remove events package Signed-off-by: Alexander Morozov --- events/events.go | 231 ------------------------------------------ events/events_test.go | 154 ---------------------------- 2 files changed, 385 deletions(-) delete mode 100644 events/events.go delete mode 100644 events/events_test.go diff --git a/events/events.go b/events/events.go deleted file mode 100644 index 93ea9a039..000000000 --- a/events/events.go +++ /dev/null @@ -1,231 +0,0 @@ -package events - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "strings" - "sync" - "time" - - "github.com/docker/docker/engine" - "github.com/docker/docker/pkg/jsonmessage" - "github.com/docker/docker/pkg/parsers/filters" -) - -const eventsLimit = 64 - -type listener chan<- *jsonmessage.JSONMessage - -type Events struct { - mu sync.RWMutex - events []*jsonmessage.JSONMessage - subscribers []listener -} - -func New() *Events { - return &Events{ - events: make([]*jsonmessage.JSONMessage, 0, eventsLimit), - } -} - -// Install installs events public api in docker engine -func (e *Events) Install(eng *engine.Engine) error { - // Here you should describe public interface - jobs := map[string]engine.Handler{ - "events": e.Get, - "log": e.Log, - "subscribers_count": e.SubscribersCount, - } - for name, job := range jobs { - if err := eng.Register(name, job); err != nil { - return err - } - } - return nil -} - -func (e *Events) Get(job *engine.Job) error { - var ( - since = job.GetenvInt64("since") - until = job.GetenvInt64("until") - timeout = time.NewTimer(time.Unix(until, 0).Sub(time.Now())) - ) - - eventFilters, err := filters.FromParam(job.Getenv("filters")) - if err != nil { - return err - } - - // If no until, disable timeout - if job.Getenv("until") == "" { - timeout.Stop() - } - - listener := make(chan *jsonmessage.JSONMessage) - e.subscribe(listener) - defer e.unsubscribe(listener) - - job.Stdout.Write(nil) - - // Resend every event in the [since, until] time interval. - if job.Getenv("since") != "" { - if err := e.writeCurrent(job, since, until, eventFilters); err != nil { - return err - } - } - - for { - select { - case event, ok := <-listener: - if !ok { - return nil - } - if err := writeEvent(job, event, eventFilters); err != nil { - return err - } - case <-timeout.C: - return nil - } - } -} - -func (e *Events) Log(job *engine.Job) error { - if len(job.Args) != 3 { - return fmt.Errorf("usage: %s ACTION ID FROM", job.Name) - } - // not waiting for receivers - go e.log(job.Args[0], job.Args[1], job.Args[2]) - return nil -} - -func (e *Events) SubscribersCount(job *engine.Job) error { - ret := &engine.Env{} - ret.SetInt("count", e.subscribersCount()) - ret.WriteTo(job.Stdout) - return nil -} - -func writeEvent(job *engine.Job, event *jsonmessage.JSONMessage, eventFilters filters.Args) error { - isFiltered := func(field string, filter []string) bool { - if len(filter) == 0 { - return false - } - for _, v := range filter { - if v == field { - return false - } - if strings.Contains(field, ":") { - image := strings.Split(field, ":") - if image[0] == v { - return false - } - } - } - return true - } - - //incoming container filter can be name,id or partial id, convert and replace as a full container id - for i, cn := range eventFilters["container"] { - eventFilters["container"][i] = GetContainerId(job.Eng, cn) - } - - if isFiltered(event.Status, eventFilters["event"]) || isFiltered(event.From, eventFilters["image"]) || - isFiltered(event.ID, eventFilters["container"]) { - return nil - } - - // When sending an event JSON serialization errors are ignored, but all - // other errors lead to the eviction of the listener. - if b, err := json.Marshal(event); err == nil { - if _, err = job.Stdout.Write(b); err != nil { - return err - } - } - return nil -} - -func (e *Events) writeCurrent(job *engine.Job, since, until int64, eventFilters filters.Args) error { - e.mu.RLock() - for _, event := range e.events { - if event.Time >= since && (event.Time <= until || until == 0) { - if err := writeEvent(job, event, eventFilters); err != nil { - e.mu.RUnlock() - return err - } - } - } - e.mu.RUnlock() - return nil -} - -func (e *Events) subscribersCount() int { - e.mu.RLock() - c := len(e.subscribers) - e.mu.RUnlock() - return c -} - -func (e *Events) log(action, id, from string) { - e.mu.Lock() - now := time.Now().UTC().Unix() - jm := &jsonmessage.JSONMessage{Status: action, ID: id, From: from, Time: now} - if len(e.events) == cap(e.events) { - // discard oldest event - copy(e.events, e.events[1:]) - e.events[len(e.events)-1] = jm - } else { - e.events = append(e.events, jm) - } - for _, s := range e.subscribers { - // We give each subscriber a 100ms time window to receive the event, - // after which we move to the next. - select { - case s <- jm: - case <-time.After(100 * time.Millisecond): - } - } - e.mu.Unlock() -} - -func (e *Events) subscribe(l listener) { - e.mu.Lock() - e.subscribers = append(e.subscribers, l) - e.mu.Unlock() -} - -// unsubscribe closes and removes the specified listener from the list of -// previously registed ones. -// It returns a boolean value indicating if the listener was successfully -// found, closed and unregistered. -func (e *Events) unsubscribe(l listener) bool { - e.mu.Lock() - for i, subscriber := range e.subscribers { - if subscriber == l { - close(l) - e.subscribers = append(e.subscribers[:i], e.subscribers[i+1:]...) - e.mu.Unlock() - return true - } - } - e.mu.Unlock() - return false -} - -func GetContainerId(eng *engine.Engine, name string) string { - var buf bytes.Buffer - job := eng.Job("container_inspect", name) - - var outStream io.Writer - - outStream = &buf - job.Stdout.Set(outStream) - - if err := job.Run(); err != nil { - return "" - } - var out struct{ ID string } - json.NewDecoder(&buf).Decode(&out) - return out.ID -} diff --git a/events/events_test.go b/events/events_test.go deleted file mode 100644 index a232576fe..000000000 --- a/events/events_test.go +++ /dev/null @@ -1,154 +0,0 @@ -package events - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "testing" - "time" - - "github.com/docker/docker/engine" - "github.com/docker/docker/pkg/jsonmessage" -) - -func TestEventsPublish(t *testing.T) { - e := New() - l1 := make(chan *jsonmessage.JSONMessage) - l2 := make(chan *jsonmessage.JSONMessage) - e.subscribe(l1) - e.subscribe(l2) - count := e.subscribersCount() - if count != 2 { - t.Fatalf("Must be 2 subscribers, got %d", count) - } - go e.log("test", "cont", "image") - select { - case msg := <-l1: - if len(e.events) != 1 { - t.Fatalf("Must be only one event, got %d", len(e.events)) - } - if msg.Status != "test" { - t.Fatalf("Status should be test, got %s", msg.Status) - } - if msg.ID != "cont" { - t.Fatalf("ID should be cont, got %s", msg.ID) - } - if msg.From != "image" { - t.Fatalf("From should be image, got %s", msg.From) - } - case <-time.After(1 * time.Second): - t.Fatal("Timeout waiting for broadcasted message") - } - select { - case msg := <-l2: - if len(e.events) != 1 { - t.Fatalf("Must be only one event, got %d", len(e.events)) - } - if msg.Status != "test" { - t.Fatalf("Status should be test, got %s", msg.Status) - } - if msg.ID != "cont" { - t.Fatalf("ID should be cont, got %s", msg.ID) - } - if msg.From != "image" { - t.Fatalf("From should be image, got %s", msg.From) - } - case <-time.After(1 * time.Second): - t.Fatal("Timeout waiting for broadcasted message") - } -} - -func TestEventsPublishTimeout(t *testing.T) { - e := New() - l := make(chan *jsonmessage.JSONMessage) - e.subscribe(l) - - c := make(chan struct{}) - go func() { - e.log("test", "cont", "image") - close(c) - }() - - select { - case <-c: - case <-time.After(time.Second): - t.Fatal("Timeout publishing message") - } -} - -func TestLogEvents(t *testing.T) { - e := New() - eng := engine.New() - if err := e.Install(eng); err != nil { - t.Fatal(err) - } - - for i := 0; i < eventsLimit+16; i++ { - action := fmt.Sprintf("action_%d", i) - id := fmt.Sprintf("cont_%d", i) - from := fmt.Sprintf("image_%d", i) - job := eng.Job("log", action, id, from) - if err := job.Run(); err != nil { - t.Fatal(err) - } - } - time.Sleep(50 * time.Millisecond) - if len(e.events) != eventsLimit { - t.Fatalf("Must be %d events, got %d", eventsLimit, len(e.events)) - } - - job := eng.Job("events") - job.SetenvInt64("since", 1) - job.SetenvInt64("until", time.Now().Unix()) - buf := bytes.NewBuffer(nil) - job.Stdout.Add(buf) - if err := job.Run(); err != nil { - t.Fatal(err) - } - buf = bytes.NewBuffer(buf.Bytes()) - dec := json.NewDecoder(buf) - var msgs []jsonmessage.JSONMessage - for { - var jm jsonmessage.JSONMessage - if err := dec.Decode(&jm); err != nil { - if err == io.EOF { - break - } - t.Fatal(err) - } - msgs = append(msgs, jm) - } - if len(msgs) != eventsLimit { - t.Fatalf("Must be %d events, got %d", eventsLimit, len(msgs)) - } - first := msgs[0] - if first.Status != "action_16" { - t.Fatalf("First action is %s, must be action_15", first.Status) - } - last := msgs[len(msgs)-1] - if last.Status != "action_79" { - t.Fatalf("First action is %s, must be action_79", first.Status) - } -} - -func TestEventsCountJob(t *testing.T) { - e := New() - eng := engine.New() - if err := e.Install(eng); err != nil { - t.Fatal(err) - } - l1 := make(chan *jsonmessage.JSONMessage) - l2 := make(chan *jsonmessage.JSONMessage) - e.subscribe(l1) - e.subscribe(l2) - job := eng.Job("subscribers_count") - env, _ := job.Stdout.AddEnv() - if err := job.Run(); err != nil { - t.Fatal(err) - } - count := env.GetInt("count") - if count != 2 { - t.Fatalf("There must be 2 subscribers, got %d", count) - } -} From 805f34bb5b6f639b3eb361672af2cbc7437e2ce9 Mon Sep 17 00:00:00 2001 From: Yan Feng Date: Tue, 7 Apr 2015 14:15:05 -0400 Subject: [PATCH 298/999] Fix 2 typos in /doc/sources/reference/run.md Signed-off-by: Yan Feng --- docs/sources/reference/run.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 000adcb7a..151892ea7 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -281,7 +281,7 @@ when sharing the host's network stack. Compared to the default `bridge` mode, the `host` mode gives *significantly* better networking performance since it uses the host's native networking stack -wheras the bridge has to go through one level of virtualizaion through the +whereas the bridge has to go through one level of virtualization through the docker daemon. It is recommended to run containers in this mode when their networking performance is critical, for example, a production Load Balancer or a High Performance Web Server. From 05dcf261992ee2fe639977a9e063051cadaf1c50 Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Tue, 7 Apr 2015 13:14:47 -0700 Subject: [PATCH 299/999] Fixing fail message in TestRunDeviceDirectory Signed-off-by: Megan Kostick --- integration-cli/docker_cli_run_unix_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 8e0cc9be3..026f8279e 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -196,7 +196,7 @@ func TestRunDeviceDirectory(t *testing.T) { } if actual := strings.Trim(out, "\r\n"); !strings.Contains(out, "seq") { - t.Fatalf("expected output /dev/othersnd/timer, received %s", actual) + t.Fatalf("expected output /dev/othersnd/seq, received %s", actual) } logDone("run - test --device directory mounts all internal devices") From fdfc36db416663e3c8434c031e01bd6211ce5d8b Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Tue, 7 Apr 2015 13:20:17 -0700 Subject: [PATCH 300/999] Carrying 11409 for a client Signed-off-by: Mary Anthony --- docs/man/Dockerfile.5.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/man/Dockerfile.5.md b/docs/man/Dockerfile.5.md index d29d96197..c64757cce 100644 --- a/docs/man/Dockerfile.5.md +++ b/docs/man/Dockerfile.5.md @@ -69,8 +69,8 @@ A Dockerfile is similar to a Makefile. multiple images. Make a note of the last image ID output by the commit before each new **FROM** command. - -- If no tag is given to the **FROM** instruction, latest is assumed. If the - used tag does not exist, an error is returned. + -- If no tag is given to the **FROM** instruction, Docker applies the + `latest` tag. If the used tag does not exist, an error is returned. **MAINTAINER** -- **MAINTAINER** sets the Author field for the generated images. From 87e70b6831386775a0d6c6b952c947865f1d053b Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Tue, 7 Apr 2015 13:25:48 -0700 Subject: [PATCH 301/999] Carry PR 12008 for contributor Closes #8040 Remove a tic for the hawk Signed-off-by: Mary Anthony --- docs/sources/reference/commandline/cli.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 2d59a64bb..9f8daa03f 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -84,14 +84,20 @@ be set to the non-default value by explicitly setting them to `false`: ### Multi -Options like `-a=[]` indicate they can be specified multiple times: +You can specify options like `-a=[]` multiple times in a single command line, +for example in these commands: - $ docker run -a stdin -a stdout -a stderr -i -t ubuntu /bin/bash + $ docker run -a stdin -a stdout -i -t ubuntu /bin/bash + $ docker run -a stdin -a stdout -a stderr ubuntu /bin/ls -Sometimes this can use a more complex value string, as for `-v`: +Sometimes, multiple options can call for a more complex value string as for `-v`: $ docker run -v /host:/container example/mysql +> **Note**: +> Do not use the `-t` and `-a stderr` options together due to limitations +> in the `pty` implementation. All `stderr` in `pty` mode simply goes to `stdout`. + ### Strings and Integers Options like `--name=""` expect a string, and they From 8fd2b52146b443dd464df5199d79c69047c81eea Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 7 Apr 2015 08:54:00 -0700 Subject: [PATCH 302/999] Fix fail message in TestEventsImageImport Signed-off-by: Alexander Morozov --- integration-cli/docker_cli_events_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 7076ab198..97e309513 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -237,7 +237,7 @@ func TestEventsImageImport(t *testing.T) { event := strings.TrimSpace(events[len(events)-1]) if !strings.HasSuffix(event, ": import") { - t.Fatalf("Missing pull event - got:%q", event) + t.Fatalf("Missing import event - got:%q", event) } logDone("events - image import is logged") From c44f513248a8b40b1b2221726c7441881383e919 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 6 Apr 2015 12:19:38 -0700 Subject: [PATCH 303/999] Remove engine usage from attach Signed-off-by: Alexander Morozov --- api/server/server.go | 44 ++++++++--------------- builder/internals.go | 2 +- daemon/attach.go | 86 +++++++++++++++----------------------------- daemon/daemon.go | 1 - daemon/exec.go | 2 +- 5 files changed, 44 insertions(+), 91 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 502f1ae9e..95642f992 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -904,16 +904,12 @@ func postContainersAttach(eng *engine.Engine, version version.Version, w http.Re return fmt.Errorf("Missing parameter") } - var ( - job = eng.Job("container_inspect", vars["name"]) - c, err = job.Stdout.AddEnv() - ) + d := getDaemon(eng) + + cont, err := d.Get(vars["name"]) if err != nil { return err } - if err = job.Run(); err != nil { - return err - } inStream, outStream, err := hijackServer(w) if err != nil { @@ -929,25 +925,17 @@ func postContainersAttach(eng *engine.Engine, version version.Version, w http.Re fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") } - if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") { + if !cont.Config.Tty && version.GreaterThanOrEqualTo("1.6") { errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) } else { errStream = outStream } + logs := r.Form.Get("logs") != "" + stream := r.Form.Get("stream") != "" - job = eng.Job("attach", vars["name"]) - job.Setenv("logs", r.Form.Get("logs")) - job.Setenv("stream", r.Form.Get("stream")) - job.Setenv("stdin", r.Form.Get("stdin")) - job.Setenv("stdout", r.Form.Get("stdout")) - job.Setenv("stderr", r.Form.Get("stderr")) - job.Stdin.Add(inStream) - job.Stdout.Add(outStream) - job.Stderr.Set(errStream) - if err := job.Run(); err != nil { + if err := cont.AttachWithLogs(inStream, outStream, errStream, logs, stream); err != nil { fmt.Fprintf(outStream, "Error attaching: %s\n", err) - } return nil } @@ -959,23 +947,19 @@ func wsContainersAttach(eng *engine.Engine, version version.Version, w http.Resp if vars == nil { return fmt.Errorf("Missing parameter") } + d := getDaemon(eng) - if err := eng.Job("container_inspect", vars["name"]).Run(); err != nil { + cont, err := d.Get(vars["name"]) + if err != nil { return err } h := websocket.Handler(func(ws *websocket.Conn) { defer ws.Close() - job := eng.Job("attach", vars["name"]) - job.Setenv("logs", r.Form.Get("logs")) - job.Setenv("stream", r.Form.Get("stream")) - job.Setenv("stdin", r.Form.Get("stdin")) - job.Setenv("stdout", r.Form.Get("stdout")) - job.Setenv("stderr", r.Form.Get("stderr")) - job.Stdin.Add(ws) - job.Stdout.Add(ws) - job.Stderr.Set(ws) - if err := job.Run(); err != nil { + logs := r.Form.Get("logs") != "" + stream := r.Form.Get("stream") != "" + + if err := cont.AttachWithLogs(ws, ws, ws, logs, stream); err != nil { logrus.Errorf("Error attaching websocket: %s", err) } }) diff --git a/builder/internals.go b/builder/internals.go index 0ee6f76a6..e0c7987ca 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -573,7 +573,7 @@ func (b *Builder) create() (*daemon.Container, error) { func (b *Builder) run(c *daemon.Container) error { var errCh chan error if b.Verbose { - errCh = b.Daemon.Attach(&c.StreamConfig, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, nil, b.OutStream, b.ErrStream) + errCh = c.Attach(nil, b.OutStream, b.ErrStream) } //start the container diff --git a/daemon/attach.go b/daemon/attach.go index a479c040b..72f38752e 100644 --- a/daemon/attach.go +++ b/daemon/attach.go @@ -2,57 +2,36 @@ package daemon import ( "encoding/json" - "fmt" "io" "os" "sync" "time" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/jsonlog" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/utils" ) -func (daemon *Daemon) ContainerAttach(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) - } - - var ( - name = job.Args[0] - logs = job.GetenvBool("logs") - stream = job.GetenvBool("stream") - stdin = job.GetenvBool("stdin") - stdout = job.GetenvBool("stdout") - stderr = job.GetenvBool("stderr") - ) - - container, err := daemon.Get(name) - if err != nil { - return err - } - - //logs +func (c *Container) AttachWithLogs(stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool) error { if logs { - cLog, err := container.ReadLog("json") + cLog, err := c.ReadLog("json") if err != nil && os.IsNotExist(err) { // Legacy logs logrus.Debugf("Old logs format") - if stdout { - cLog, err := container.ReadLog("stdout") + if stdout != nil { + cLog, err := c.ReadLog("stdout") if err != nil { logrus.Errorf("Error reading logs (stdout): %s", err) - } else if _, err := io.Copy(job.Stdout, cLog); err != nil { + } else if _, err := io.Copy(stdout, cLog); err != nil { logrus.Errorf("Error streaming logs (stdout): %s", err) } } - if stderr { - cLog, err := container.ReadLog("stderr") + if stderr != nil { + cLog, err := c.ReadLog("stderr") if err != nil { logrus.Errorf("Error reading logs (stderr): %s", err) - } else if _, err := io.Copy(job.Stderr, cLog); err != nil { + } else if _, err := io.Copy(stderr, cLog); err != nil { logrus.Errorf("Error streaming logs (stderr): %s", err) } } @@ -69,11 +48,11 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) error { logrus.Errorf("Error streaming logs: %s", err) break } - if l.Stream == "stdout" && stdout { - io.WriteString(job.Stdout, l.Log) + if l.Stream == "stdout" && stdout != nil { + io.WriteString(stdout, l.Log) } - if l.Stream == "stderr" && stderr { - io.WriteString(job.Stderr, l.Log) + if l.Stream == "stderr" && stderr != nil { + io.WriteString(stderr, l.Log) } } } @@ -81,38 +60,29 @@ func (daemon *Daemon) ContainerAttach(job *engine.Job) error { //stream if stream { - var ( - cStdin io.ReadCloser - cStdout, cStderr io.Writer - ) - - if stdin { - r, w := io.Pipe() - go func() { - defer w.Close() - defer logrus.Debugf("Closing buffered stdin pipe") - io.Copy(w, job.Stdin) - }() - cStdin = r - } - if stdout { - cStdout = job.Stdout - } - if stderr { - cStderr = job.Stderr - } - - <-daemon.Attach(&container.StreamConfig, container.Config.OpenStdin, container.Config.StdinOnce, container.Config.Tty, cStdin, cStdout, cStderr) + var stdinPipe io.ReadCloser + r, w := io.Pipe() + go func() { + defer w.Close() + defer logrus.Debugf("Closing buffered stdin pipe") + io.Copy(w, stdin) + }() + stdinPipe = r + <-c.Attach(stdinPipe, stdout, stderr) // If we are in stdinonce mode, wait for the process to end // otherwise, simply return - if container.Config.StdinOnce && !container.Config.Tty { - container.WaitStop(-1 * time.Second) + if c.Config.StdinOnce && !c.Config.Tty { + c.WaitStop(-1 * time.Second) } } return nil } -func (daemon *Daemon) Attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error { +func (c *Container) Attach(stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error { + return attach(&c.StreamConfig, c.Config.OpenStdin, c.Config.StdinOnce, c.Config.Tty, stdin, stdout, stderr) +} + +func attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) chan error { var ( cStdout, cStderr io.ReadCloser cStdin io.WriteCloser diff --git a/daemon/daemon.go b/daemon/daemon.go index a072fc34a..b2d149183 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -115,7 +115,6 @@ type Daemon struct { func (daemon *Daemon) Install(eng *engine.Engine) error { // FIXME: remove ImageDelete's dependency on Daemon, then move to graph/ for name, method := range map[string]engine.Handler{ - "attach": daemon.ContainerAttach, "commit": daemon.ContainerCommit, "container_changes": daemon.ContainerChanges, "container_copy": daemon.ContainerCopy, diff --git a/daemon/exec.go b/daemon/exec.go index c5d446176..f91600da7 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -218,7 +218,7 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) error { execConfig.StreamConfig.stdinPipe = ioutils.NopWriteCloser(ioutil.Discard) // Silently drop stdin } - attachErr := d.Attach(&execConfig.StreamConfig, execConfig.OpenStdin, true, execConfig.ProcessConfig.Tty, cStdin, cStdout, cStderr) + attachErr := attach(&execConfig.StreamConfig, execConfig.OpenStdin, true, execConfig.ProcessConfig.Tty, cStdin, cStdout, cStderr) execErr := make(chan error) From 2f853b94931057d8ebcd6e21f7656d3a04e2acf4 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 7 Apr 2015 15:10:44 -0700 Subject: [PATCH 304/999] Update libcontainer to bd8ec36106086f72b66e1be85a81202b93503e44 Fix #12130 Signed-off-by: Alexander Morozov --- hack/vendor.sh | 2 +- .../github.com/docker/libcontainer/.gitignore | 1 + .../github.com/docker/libcontainer/Makefile | 2 + .../github.com/docker/libcontainer/README.md | 3 + .../cgroups/systemd/apply_systemd.go | 64 ++++++------- .../docker/libcontainer/configs/namespaces.go | 11 +++ .../docker/libcontainer/container_linux.go | 6 ++ .../libcontainer/container_linux_test.go | 3 +- .../libcontainer/integration/exec_test.go | 89 +++++++++++++++++++ .../libcontainer/integration/utils_test.go | 7 +- .../docker/libcontainer/nsinit/main.go | 2 +- 11 files changed, 149 insertions(+), 41 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 6552cab23..8b42ad278 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -75,7 +75,7 @@ rm -rf src/github.com/docker/distribution mkdir -p src/github.com/docker/distribution mv tmp-digest src/github.com/docker/distribution/digest -clone git github.com/docker/libcontainer d00b8369852285d6a830a8d3b966608b2ed89705 +clone git github.com/docker/libcontainer bd8ec36106086f72b66e1be85a81202b93503e44 # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) rm -rf src/github.com/docker/libcontainer/vendor eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')" diff --git a/vendor/src/github.com/docker/libcontainer/.gitignore b/vendor/src/github.com/docker/libcontainer/.gitignore index 4c2914fc7..bf6a664db 100644 --- a/vendor/src/github.com/docker/libcontainer/.gitignore +++ b/vendor/src/github.com/docker/libcontainer/.gitignore @@ -1 +1,2 @@ +bundles nsinit/nsinit diff --git a/vendor/src/github.com/docker/libcontainer/Makefile b/vendor/src/github.com/docker/libcontainer/Makefile index c2c9a98d3..1a2e23e04 100644 --- a/vendor/src/github.com/docker/libcontainer/Makefile +++ b/vendor/src/github.com/docker/libcontainer/Makefile @@ -29,3 +29,5 @@ local: validate: hack/validate.sh +binary: all + docker run --rm --privileged -v $(CURDIR)/bundles:/go/bin dockercore/libcontainer make direct-install diff --git a/vendor/src/github.com/docker/libcontainer/README.md b/vendor/src/github.com/docker/libcontainer/README.md index 984f2c523..6257f9c78 100644 --- a/vendor/src/github.com/docker/libcontainer/README.md +++ b/vendor/src/github.com/docker/libcontainer/README.md @@ -141,6 +141,9 @@ container.Resume() It is able to spawn new containers or join existing containers. A root filesystem must be provided for use along with a container configuration file. +To build `nsinit`, run `make binary`. It will save the binary into +`bundles/nsinit`. + To use `nsinit`, cd into a Linux rootfs and copy a `container.json` file into the directory with your specified configuration. Environment, networking, and different capabilities for the container are specified in this file. diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go index dea196bd0..3609bccae 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go @@ -3,7 +3,6 @@ package systemd import ( - "bytes" "fmt" "io/ioutil" "os" @@ -247,6 +246,21 @@ func writeFile(dir, file, data string) error { return ioutil.WriteFile(filepath.Join(dir, file), []byte(data), 0700) } +func join(c *configs.Cgroup, subsystem string, pid int) (string, error) { + path, err := getSubsystemPath(c, subsystem) + if err != nil { + return "", err + } + if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { + return "", err + } + if err := writeFile(path, "cgroup.procs", strconv.Itoa(pid)); err != nil { + return "", err + } + + return path, nil +} + func joinCpu(c *configs.Cgroup, pid int) error { path, err := getSubsystemPath(c, "cpu") if err != nil { @@ -266,16 +280,11 @@ func joinCpu(c *configs.Cgroup, pid int) error { } func joinFreezer(c *configs.Cgroup, pid int) error { - path, err := getSubsystemPath(c, "freezer") - if err != nil { + if _, err := join(c, "freezer", pid); err != nil { return err } - if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { - return err - } - - return ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700) + return nil } func getSubsystemPath(c *configs.Cgroup, subsystem string) (string, error) { @@ -303,21 +312,15 @@ func (m *Manager) Freeze(state configs.FreezerState) error { return err } - if err := ioutil.WriteFile(filepath.Join(path, "freezer.state"), []byte(state), 0); err != nil { + prevState := m.Cgroups.Freezer + m.Cgroups.Freezer = state + + freezer := subsystems["freezer"] + err = freezer.Set(path, m.Cgroups) + if err != nil { + m.Cgroups.Freezer = prevState return err } - for { - state_, err := ioutil.ReadFile(filepath.Join(path, "freezer.state")) - if err != nil { - return err - } - if string(state) == string(bytes.TrimSpace(state_)) { - break - } - time.Sleep(1 * time.Millisecond) - } - - m.Cgroups.Freezer = state return nil } @@ -366,29 +369,16 @@ func getUnitName(c *configs.Cgroup) string { // because systemd will re-write the device settings if it needs to re-apply the cgroup context. // This happens at least for v208 when any sibling unit is started. func joinDevices(c *configs.Cgroup, pid int) error { - path, err := getSubsystemPath(c, "devices") + path, err := join(c, "devices", pid) if err != nil { return err } - if err := os.MkdirAll(path, 0755); err != nil && !os.IsExist(err) { + devices := subsystems["devices"] + if err := devices.Set(path, c); err != nil { return err } - if err := ioutil.WriteFile(filepath.Join(path, "cgroup.procs"), []byte(strconv.Itoa(pid)), 0700); err != nil { - return err - } - - if !c.AllowAllDevices { - if err := writeFile(path, "devices.deny", "a"); err != nil { - return err - } - } - for _, dev := range c.AllowedDevices { - if err := writeFile(path, "devices.allow", dev.CgroupString()); err != nil { - return err - } - } return nil } diff --git a/vendor/src/github.com/docker/libcontainer/configs/namespaces.go b/vendor/src/github.com/docker/libcontainer/configs/namespaces.go index 9078e6abf..ac6a7fa2c 100644 --- a/vendor/src/github.com/docker/libcontainer/configs/namespaces.go +++ b/vendor/src/github.com/docker/libcontainer/configs/namespaces.go @@ -16,6 +16,17 @@ const ( NEWUSER NamespaceType = "NEWUSER" ) +func NamespaceTypes() []NamespaceType { + return []NamespaceType{ + NEWNET, + NEWPID, + NEWNS, + NEWUTS, + NEWIPC, + NEWUSER, + } +} + // Namespace defines configuration for each namespace. It specifies an // alternate path that is able to be joined via setns. type Namespace struct { diff --git a/vendor/src/github.com/docker/libcontainer/container_linux.go b/vendor/src/github.com/docker/libcontainer/container_linux.go index 3c077afbd..d52610f07 100644 --- a/vendor/src/github.com/docker/libcontainer/container_linux.go +++ b/vendor/src/github.com/docker/libcontainer/container_linux.go @@ -306,5 +306,11 @@ func (c *linuxContainer) currentState() (*State, error) { for _, ns := range c.config.Namespaces { state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid()) } + for _, nsType := range configs.NamespaceTypes() { + if _, ok := state.NamespacePaths[nsType]; !ok { + ns := configs.Namespace{Type: nsType} + state.NamespacePaths[ns.Type] = ns.GetPath(c.initProcess.pid()) + } + } return state, nil } diff --git a/vendor/src/github.com/docker/libcontainer/container_linux_test.go b/vendor/src/github.com/docker/libcontainer/container_linux_test.go index 5ee46ab14..b05733e58 100644 --- a/vendor/src/github.com/docker/libcontainer/container_linux_test.go +++ b/vendor/src/github.com/docker/libcontainer/container_linux_test.go @@ -130,7 +130,8 @@ func TestGetContainerState(t *testing.T) { {Type: configs.NEWNS}, {Type: configs.NEWNET, Path: expectedNetworkPath}, {Type: configs.NEWUTS}, - {Type: configs.NEWIPC}, + // emulate host for IPC + //{Type: configs.NEWIPC}, }, }, initProcess: &mockProcess{ diff --git a/vendor/src/github.com/docker/libcontainer/integration/exec_test.go b/vendor/src/github.com/docker/libcontainer/integration/exec_test.go index 4afff77de..12457ba1a 100644 --- a/vendor/src/github.com/docker/libcontainer/integration/exec_test.go +++ b/vendor/src/github.com/docker/libcontainer/integration/exec_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/docker/libcontainer" + "github.com/docker/libcontainer/cgroups/systemd" "github.com/docker/libcontainer/configs" ) @@ -481,6 +482,17 @@ func TestProcessCaps(t *testing.T) { } func TestFreeze(t *testing.T) { + testFreeze(t, false) +} + +func TestSystemdFreeze(t *testing.T) { + if !systemd.UseSystemd() { + t.Skip("Systemd is unsupported") + } + testFreeze(t, true) +} + +func testFreeze(t *testing.T, systemd bool) { if testing.Short() { return } @@ -497,6 +509,9 @@ func TestFreeze(t *testing.T) { defer remove(rootfs) config := newTemplateConfig(rootfs) + if systemd { + config.Cgroups.Slice = "system.slice" + } factory, err := libcontainer.New(root, libcontainer.Cgroupfs) if err != nil { @@ -559,3 +574,77 @@ func TestFreeze(t *testing.T) { t.Fatal(s.String()) } } + +func TestContainerState(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + l, err := os.Readlink("/proc/1/ns/ipc") + if err != nil { + t.Fatal(err) + } + + config := newTemplateConfig(rootfs) + config.Namespaces = configs.Namespaces([]configs.Namespace{ + {Type: configs.NEWNS}, + {Type: configs.NEWUTS}, + // host for IPC + //{Type: configs.NEWIPC}, + {Type: configs.NEWPID}, + {Type: configs.NEWNET}, + }) + + factory, err := libcontainer.New(root, libcontainer.Cgroupfs) + if err != nil { + t.Fatal(err) + } + + container, err := factory.Create("test", config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + stdinR, stdinW, err := os.Pipe() + if err != nil { + t.Fatal(err) + } + p := &libcontainer.Process{ + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(p) + if err != nil { + t.Fatal(err) + } + stdinR.Close() + defer p.Signal(os.Kill) + + st, err := container.State() + if err != nil { + t.Fatal(err) + } + + l1, err := os.Readlink(st.NamespacePaths[configs.NEWIPC]) + if err != nil { + t.Fatal(err) + } + if l1 != l { + t.Fatal("Container using non-host ipc namespace") + } + stdinW.Close() + p.Wait() +} diff --git a/vendor/src/github.com/docker/libcontainer/integration/utils_test.go b/vendor/src/github.com/docker/libcontainer/integration/utils_test.go index c444eecfc..cf4596864 100644 --- a/vendor/src/github.com/docker/libcontainer/integration/utils_test.go +++ b/vendor/src/github.com/docker/libcontainer/integration/utils_test.go @@ -68,9 +68,14 @@ func copyBusybox(dest string) error { } func newContainer(config *configs.Config) (libcontainer.Container, error) { + cgm := libcontainer.Cgroupfs + if config.Cgroups != nil && config.Cgroups.Slice == "system.slice" { + cgm = libcontainer.SystemdCgroups + } + factory, err := libcontainer.New(".", libcontainer.InitArgs(os.Args[0], "init", "--"), - libcontainer.Cgroupfs, + cgm, ) if err != nil { return nil, err diff --git a/vendor/src/github.com/docker/libcontainer/nsinit/main.go b/vendor/src/github.com/docker/libcontainer/nsinit/main.go index 922d74ccb..eec064c2c 100644 --- a/vendor/src/github.com/docker/libcontainer/nsinit/main.go +++ b/vendor/src/github.com/docker/libcontainer/nsinit/main.go @@ -13,7 +13,7 @@ func main() { app.Version = "2" app.Author = "libcontainer maintainers" app.Flags = []cli.Flag{ - cli.StringFlag{Name: "root", Value: ".", Usage: "root directory for containers"}, + cli.StringFlag{Name: "root", Value: "/var/run/nsinit", Usage: "root directory for containers"}, cli.StringFlag{Name: "log-file", Value: "", Usage: "set the log file to output logs to"}, cli.BoolFlag{Name: "debug", Usage: "enable debug output in the logs"}, } From 195bebd3a7b6ffab1cfbb15ad9835ad83bca9737 Mon Sep 17 00:00:00 2001 From: Mrunal Patel Date: Tue, 7 Apr 2015 18:15:28 -0400 Subject: [PATCH 305/999] Removes unused function. Signed-off-by: Mrunal Patel --- daemon/execdriver/native/driver.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 40eb87cbe..e5811bb85 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -3,10 +3,8 @@ package native import ( - "encoding/json" "fmt" "io" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -303,14 +301,6 @@ func (d *driver) GetPidsForContainer(id string) ([]int, error) { return active.Processes() } -func (d *driver) writeContainerFile(container *configs.Config, id string) error { - data, err := json.Marshal(container) - if err != nil { - return err - } - return ioutil.WriteFile(filepath.Join(d.root, id, "container.json"), data, 0655) -} - func (d *driver) cleanContainer(id string) error { d.Lock() delete(d.activeContainers, id) From ce69dafe4d0424ddb5df53d9aee7728f0734cce7 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 7 Apr 2015 15:20:39 -0700 Subject: [PATCH 306/999] Test case for network mode chain container -> container -> host Issue #12130 Signed-off-by: Alexander Morozov --- integration-cli/docker_cli_run_test.go | 29 ++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index ef7a9dee8..1be512d53 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3249,6 +3249,35 @@ func TestRunNetHost(t *testing.T) { logDone("run - net host mode") } +func TestRunNetContainerWhichHost(t *testing.T) { + testRequires(t, SameHostDaemon) + defer deleteAllContainers() + + hostNet, err := os.Readlink("/proc/1/ns/net") + if err != nil { + t.Fatal(err) + } + + cmd := exec.Command(dockerBinary, "run", "-d", "--net=host", "--name=test", "busybox", "top") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + cmd = exec.Command(dockerBinary, "run", "--net=container:test", "busybox", "readlink", "/proc/self/ns/net") + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + out = strings.Trim(out, "\n") + if hostNet != out { + t.Fatalf("Container should have host network namespace") + } + + logDone("run - net container mode, where container in host mode") +} + func TestRunAllowPortRangeThroughPublish(t *testing.T) { defer deleteAllContainers() From 24057777af8dc9ee74574d1f5dd47bf6562161b5 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 7 Apr 2015 21:36:30 -0400 Subject: [PATCH 307/999] Remove duplicate config file creation. When `toDisk` is called, it is creating a file already, no need to create it here. Signed-off-by: Brian Goff --- volumes/volume.go | 9 --------- 1 file changed, 9 deletions(-) diff --git a/volumes/volume.go b/volumes/volume.go index c5191c48c..e888f441e 100644 --- a/volumes/volume.go +++ b/volumes/volume.go @@ -102,15 +102,6 @@ func (v *Volume) initialize() error { if err := os.MkdirAll(v.configPath, 0755); err != nil { return err } - jsonPath, err := v.jsonPath() - if err != nil { - return err - } - f, err := os.Create(jsonPath) - if err != nil { - return err - } - defer f.Close() return v.toDisk() } From cc30282e94546048a3957bdf360f99d4626e7d7c Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Wed, 8 Apr 2015 13:33:05 +0800 Subject: [PATCH 308/999] duplicate logDone in TestRenameRunningContainer and TestRenameCheckNames Signed-off-by: Yuan Sun --- integration-cli/docker_cli_rename_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_rename_test.go b/integration-cli/docker_cli_rename_test.go index 8142b03c4..ed24d971d 100644 --- a/integration-cli/docker_cli_rename_test.go +++ b/integration-cli/docker_cli_rename_test.go @@ -97,7 +97,7 @@ func TestRenameCheckNames(t *testing.T) { t.Fatal(err) } - logDone("rename - running container") + logDone("rename - old name released") } func TestRenameInvalidName(t *testing.T) { From a40f37987859327c33643bb12067e3ece9e3294f Mon Sep 17 00:00:00 2001 From: Deshi Xiao Date: Tue, 7 Apr 2015 13:01:34 +0800 Subject: [PATCH 309/999] fixed #11500 Add tip about filter proxy fixed #11500 Add tip about filter proxy to Docker installation Guides Signed-off-by: Deshi Xiao --- docs/sources/installation/debian.md | 9 ++++++++- docs/sources/installation/ubuntulinux.md | 6 ++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 4644a2440..709a44d41 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -62,9 +62,16 @@ which is officially supported by Docker. 2. Restart your system. This is necessary for Debian to use your new kernel. 3. Install Docker using the get.docker.com script: - + `curl -sSL https://get.docker.com/ | sh` +>**Note**: If your company is behind a filtering proxy, you may find that the +>`apt-key` +>command fails for the Docker repo during installation. To work around this, +>add the key directly using the following: +> +> $ wget -qO- https://get.docker.com/gpg | sudo apt-key add - + ## Giving non-root access The `docker` daemon always runs as the `root` user and the `docker` diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index c3f0e3958..6400fdb59 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -114,6 +114,12 @@ install Docker using the following: The system prompts you for your `sudo` password. Then, it downloads and installs Docker and its dependencies. +>**Note**: If your company is behind a filtering proxy, you may find that the +>`apt-key` +>command fails for the Docker repo during installation. To work around this, +>add the key directly using the following: +> +> $ wget -qO- https://get.docker.com/gpg | sudo apt-key add - 4. Verify `docker` is installed correctly. From 8afd5cad317920cceed7536d278974d9251b0325 Mon Sep 17 00:00:00 2001 From: Felix Schindler Date: Wed, 8 Apr 2015 10:30:42 +0200 Subject: [PATCH 310/999] Fixing typo. Added missing white space. Signed-off-by: Felix Schindler --- docs/sources/userguide/dockerhub.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/userguide/dockerhub.md b/docs/sources/userguide/dockerhub.md index bbdd1b6f6..3d4007d30 100644 --- a/docs/sources/userguide/dockerhub.md +++ b/docs/sources/userguide/dockerhub.md @@ -10,7 +10,7 @@ including how to create an account. The [Docker Hub](https://hub.docker.com) is a centralized resource for working with Docker and its components. Docker Hub helps you collaborate with colleagues and get the -most out of Docker.To do this, it provides services such as: +most out of Docker. To do this, it provides services such as: * Docker image hosting. * User authentication. From e379e2668cecf8a0f9ef29eb57ee881f4d313d58 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 8 Apr 2015 11:14:16 +0200 Subject: [PATCH 311/999] Remove engine.Job from diff Signed-off-by: Antonio Murdaca --- api/server/server.go | 20 +++++++++++++++++--- daemon/changes.go | 31 ------------------------------- daemon/daemon.go | 1 - 3 files changed, 17 insertions(+), 35 deletions(-) delete mode 100644 daemon/changes.go diff --git a/api/server/server.go b/api/server/server.go index 95642f992..57444a448 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -351,10 +351,24 @@ func getContainersChanges(eng *engine.Engine, version version.Version, w http.Re if vars == nil { return fmt.Errorf("Missing parameter") } - var job = eng.Job("container_changes", vars["name"]) - streamJSON(job, w, false) - return job.Run() + name := vars["name"] + if name == "" { + return fmt.Errorf("Container name cannot be empty") + } + + d := getDaemon(eng) + cont, err := d.Get(name) + if err != nil { + return err + } + + changes, err := cont.Changes() + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, changes) } func getContainersTop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/daemon/changes.go b/daemon/changes.go deleted file mode 100644 index 7f261a8a7..000000000 --- a/daemon/changes.go +++ /dev/null @@ -1,31 +0,0 @@ -package daemon - -import ( - "encoding/json" - "fmt" - - "github.com/docker/docker/engine" -) - -func (daemon *Daemon) ContainerChanges(job *engine.Job) error { - if n := len(job.Args); n != 1 { - return fmt.Errorf("Usage: %s CONTAINER", job.Name) - } - name := job.Args[0] - - container, err := daemon.Get(name) - if err != nil { - return err - } - - changes, err := container.Changes() - if err != nil { - return err - } - - if err = json.NewEncoder(job.Stdout).Encode(changes); err != nil { - return err - } - - return nil -} diff --git a/daemon/daemon.go b/daemon/daemon.go index b2d149183..67b693844 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -116,7 +116,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { // FIXME: remove ImageDelete's dependency on Daemon, then move to graph/ for name, method := range map[string]engine.Handler{ "commit": daemon.ContainerCommit, - "container_changes": daemon.ContainerChanges, "container_copy": daemon.ContainerCopy, "container_rename": daemon.ContainerRename, "container_inspect": daemon.ContainerInspect, From 64fd944e30c8c045f6d998cc5ca950ea272541b5 Mon Sep 17 00:00:00 2001 From: Yan Feng Date: Wed, 8 Apr 2015 11:23:47 -0400 Subject: [PATCH 312/999] Fix a typo in /doc/sources/reference/run.md Signed-off-by: Yan Feng --- docs/sources/reference/run.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 151892ea7..daf26bff8 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -562,7 +562,7 @@ the number of containers running on the system. For example, consider three containers, one has a cpu-share of 1024 and two others have a cpu-share setting of 512. When processes in all three containers attempt to use 100% of CPU, the first container would receive -50% of the total CPU time. If you add a fouth container with a cpu-share +50% of the total CPU time. If you add a fourth container with a cpu-share of 1024, the first container only gets 33% of the CPU. The remaining containers receive 16.5%, 16.5% and 33% of the CPU. From f9d323d712cc0b9163dbe890a0ee20bda3b2087c Mon Sep 17 00:00:00 2001 From: David Davis Date: Tue, 7 Apr 2015 18:22:52 -0400 Subject: [PATCH 313/999] Fixing up the README Fixing some small issues I found in the README like missing punctuation, trailing whitespace, etc. Signed-off-by: David Davis --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 86f8bcb0d..6d259c5ed 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Docker: the Linux container engine ================================== Docker is an open source project to pack, ship and run any application -as a lightweight container +as a lightweight container. Docker containers are both *hardware-agnostic* and *platform-agnostic*. This means they can run anywhere, from your laptop to the largest @@ -105,7 +105,7 @@ This is usually difficult for several reasons: these situations with various degrees of ease - but they all handle them in different and incompatible ways, which again forces the developer to do extra work. - + * *Custom dependencies*. A developer may need to prepare a custom version of their application's dependency. Some packaging systems can handle custom versions of a dependency, others can't - and all @@ -156,7 +156,7 @@ Usage examples ============== Docker can be used to run short-lived commands, long-running daemons -(app servers, databases etc.), interactive shell sessions, etc. +(app servers, databases, etc.), interactive shell sessions, etc. You can find a [list of real-world examples](http://docs.docker.com/examples/) in the @@ -213,9 +213,10 @@ We are always open to suggestions on process improvements, and are always lookin please see the [NOTICE](https://github.com/docker/docker/blob/master/NOTICE) document in this repo.* Use and transfer of Docker may be subject to certain restrictions by the -United States and other governments. +United States and other governments. + It is your responsibility to ensure that your use and/or transfer does not -violate applicable laws. +violate applicable laws. For more information, please see http://www.bis.doc.gov @@ -230,14 +231,14 @@ Other Docker Related Projects ============================= There are a number of projects under development that are based on Docker's core technology. These projects expand the tooling built around the -Docker platform to broaden its application and utility. +Docker platform to broaden its application and utility. * [Docker Registry](https://github.com/docker/distribution): Registry server for Docker (hosting/delivery of repositories and images) * [Docker Machine](https://github.com/docker/machine): Machine management -for a container-centric world +for a container-centric world * [Docker Swarm](https://github.com/docker/swarm): A Docker-native clustering -system +system * [Docker Compose](https://github.com/docker/compose) (formerly Fig): Define and run multi-container apps * [Kitematic](https://github.com/kitematic/kitematic): The easiest way to use From 1bfa80bdd9ac05b01867492d2e6dda668aa7715c Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Tue, 7 Apr 2015 12:34:30 -0700 Subject: [PATCH 314/999] Remove Job from PS API Signed-off-by: Doug Davis --- api/client/ps.go | 2 +- api/common.go | 55 +----------------------------- api/server/server.go | 48 +++++++++++--------------- daemon/daemon.go | 1 - daemon/list.go | 63 ++++++++++++++++------------------ integration/server_test.go | 69 +++++++++++++------------------------- 6 files changed, 72 insertions(+), 166 deletions(-) diff --git a/api/client/ps.go b/api/client/ps.go index fdc8ef5a9..be20d7a6f 100644 --- a/api/client/ps.go +++ b/api/client/ps.go @@ -153,7 +153,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t%s\t", ID, image, command, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(container.Created), 0))), - container.Status, api.NewDisplayablePorts(container.Ports), strings.Join(names, ",")) + container.Status, api.DisplayablePorts(container.Ports), strings.Join(names, ",")) if *size { if container.SizeRootFs > 0 { diff --git a/api/common.go b/api/common.go index 39224a9c1..693df3887 100644 --- a/api/common.go +++ b/api/common.go @@ -10,7 +10,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/version" "github.com/docker/libtrust" @@ -32,65 +31,13 @@ func ValidateHost(val string) (string, error) { return host, nil } -// TODO remove, used on < 1.5 in getContainersJSON -// TODO this can go away when we get rid of engine.table -func DisplayablePorts(ports *engine.Table) string { - var ( - result = []string{} - hostMappings = []string{} - firstInGroupMap map[string]int - lastInGroupMap map[string]int - ) - firstInGroupMap = make(map[string]int) - lastInGroupMap = make(map[string]int) - ports.SetKey("PrivatePort") - ports.Sort() - for _, port := range ports.Data { - var ( - current = port.GetInt("PrivatePort") - portKey = port.Get("Type") - firstInGroup int - lastInGroup int - ) - if port.Get("IP") != "" { - if port.GetInt("PublicPort") != current { - hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.Get("IP"), port.GetInt("PublicPort"), port.GetInt("PrivatePort"), port.Get("Type"))) - continue - } - portKey = fmt.Sprintf("%s/%s", port.Get("IP"), port.Get("Type")) - } - firstInGroup = firstInGroupMap[portKey] - lastInGroup = lastInGroupMap[portKey] - - if firstInGroup == 0 { - firstInGroupMap[portKey] = current - lastInGroupMap[portKey] = current - continue - } - - if current == (lastInGroup + 1) { - lastInGroupMap[portKey] = current - continue - } - result = append(result, FormGroup(portKey, firstInGroup, lastInGroup)) - firstInGroupMap[portKey] = current - lastInGroupMap[portKey] = current - } - for portKey, firstInGroup := range firstInGroupMap { - result = append(result, FormGroup(portKey, firstInGroup, lastInGroupMap[portKey])) - } - result = append(result, hostMappings...) - return strings.Join(result, ", ") -} - type ByPrivatePort []types.Port func (r ByPrivatePort) Len() int { return len(r) } func (r ByPrivatePort) Swap(i, j int) { r[i], r[j] = r[j], r[i] } func (r ByPrivatePort) Less(i, j int) bool { return r[i].PrivatePort < r[j].PrivatePort } -// TODO Rename to DisplayablePorts (remove "New") when engine.Table goes away -func NewDisplayablePorts(ports []types.Port) string { +func DisplayablePorts(ports []types.Port) string { var ( result = []string{} hostMappings = []string{} diff --git a/api/server/server.go b/api/server/server.go index 502f1ae9e..feabe700f 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -374,42 +374,32 @@ func getContainersTop(eng *engine.Engine, version version.Version, w http.Respon } func getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if err := parseForm(r); err != nil { + var err error + if err = parseForm(r); err != nil { return err } - var ( - err error - outs *engine.Table - job = eng.Job("containers") - ) - job.Setenv("all", r.Form.Get("all")) - job.Setenv("size", r.Form.Get("size")) - job.Setenv("since", r.Form.Get("since")) - job.Setenv("before", r.Form.Get("before")) - job.Setenv("limit", r.Form.Get("limit")) - job.Setenv("filters", r.Form.Get("filters")) + config := &daemon.ContainersConfig{ + All: r.Form.Get("all") == "1", + Size: r.Form.Get("size") == "1", + Since: r.Form.Get("since"), + Before: r.Form.Get("before"), + Filters: r.Form.Get("filters"), + } - if version.GreaterThanOrEqualTo("1.5") { - streamJSON(job, w, false) - } else if outs, err = job.Stdout.AddTable(); err != nil { - return err - } - if err = job.Run(); err != nil { - return err - } - if version.LessThan("1.5") { // Convert to legacy format - for _, out := range outs.Data { - ports := engine.NewTable("", 0) - ports.ReadListFrom([]byte(out.Get("Ports"))) - out.Set("Ports", api.DisplayablePorts(ports)) - } - w.Header().Set("Content-Type", "application/json") - if _, err = outs.WriteListTo(w); err != nil { + if tmpLimit := r.Form.Get("limit"); tmpLimit != "" { + config.Limit, err = strconv.Atoi(tmpLimit) + if err != nil { return err } } - return nil + + containers, err := getDaemon(eng).Containers(config) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, containers) } func getContainersStats(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/daemon/daemon.go b/daemon/daemon.go index a072fc34a..a01ba4a17 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -122,7 +122,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "container_rename": daemon.ContainerRename, "container_inspect": daemon.ContainerInspect, "container_stats": daemon.ContainerStats, - "containers": daemon.Containers, "create": daemon.ContainerCreate, "rm": daemon.ContainerRm, "export": daemon.ContainerExport, diff --git a/daemon/list.go b/daemon/list.go index d18e4407b..511b49605 100644 --- a/daemon/list.go +++ b/daemon/list.go @@ -1,15 +1,12 @@ package daemon import ( - "encoding/json" "errors" "fmt" - "sort" "strconv" "strings" "github.com/docker/docker/api/types" - "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/nat" "github.com/docker/docker/pkg/graphdb" @@ -23,35 +20,35 @@ func (daemon *Daemon) List() []*Container { return daemon.containers.List() } -type ByCreated []types.Container +type ContainersConfig struct { + All bool + Since string + Before string + Limit int + Size bool + Filters string +} -func (r ByCreated) Len() int { return len(r) } -func (r ByCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] } -func (r ByCreated) Less(i, j int) bool { return r[i].Created < r[j].Created } - -func (daemon *Daemon) Containers(job *engine.Job) error { +func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container, error) { var ( foundBefore bool displayed int - all = job.GetenvBool("all") - since = job.Getenv("since") - before = job.Getenv("before") - n = job.GetenvInt("limit") - size = job.GetenvBool("size") + all = config.All + n = config.Limit psFilters filters.Args filtExited []int ) - containers := []types.Container{} + containers := []*types.Container{} - psFilters, err := filters.FromParam(job.Getenv("filters")) + psFilters, err := filters.FromParam(config.Filters) if err != nil { - return err + return nil, err } if i, ok := psFilters["exited"]; ok { for _, value := range i { code, err := strconv.Atoi(value) if err != nil { - return err + return nil, err } filtExited = append(filtExited, code) } @@ -71,17 +68,17 @@ func (daemon *Daemon) Containers(job *engine.Job) error { }, 1) var beforeCont, sinceCont *Container - if before != "" { - beforeCont, err = daemon.Get(before) + if config.Before != "" { + beforeCont, err = daemon.Get(config.Before) if err != nil { - return err + return nil, err } } - if since != "" { - sinceCont, err = daemon.Get(since) + if config.Since != "" { + sinceCont, err = daemon.Get(config.Since) if err != nil { - return err + return nil, err } } @@ -89,7 +86,7 @@ func (daemon *Daemon) Containers(job *engine.Job) error { writeCont := func(container *Container) error { container.Lock() defer container.Unlock() - if !container.Running && !all && n <= 0 && since == "" && before == "" { + if !container.Running && !all && n <= 0 && config.Since == "" && config.Before == "" { return nil } if !psFilters.Match("name", container.Name) { @@ -104,7 +101,7 @@ func (daemon *Daemon) Containers(job *engine.Job) error { return nil } - if before != "" && !foundBefore { + if config.Before != "" && !foundBefore { if container.ID == beforeCont.ID { foundBefore = true } @@ -113,7 +110,7 @@ func (daemon *Daemon) Containers(job *engine.Job) error { if n > 0 && displayed == n { return errLast } - if since != "" { + if config.Since != "" { if container.ID == sinceCont.ID { return errLast } @@ -135,7 +132,7 @@ func (daemon *Daemon) Containers(job *engine.Job) error { return nil } displayed++ - newC := types.Container{ + newC := &types.Container{ ID: container.ID, Names: names[container.ID], } @@ -184,7 +181,7 @@ func (daemon *Daemon) Containers(job *engine.Job) error { } } - if size { + if config.Size { sizeRw, sizeRootFs := container.GetSize() newC.SizeRw = int(sizeRw) newC.SizeRootFs = int(sizeRootFs) @@ -197,14 +194,10 @@ func (daemon *Daemon) Containers(job *engine.Job) error { for _, container := range daemon.List() { if err := writeCont(container); err != nil { if err != errLast { - return err + return nil, err } break } } - sort.Sort(sort.Reverse(ByCreated(containers))) - if err = json.NewEncoder(job.Stdout).Encode(containers); err != nil { - return err - } - return nil + return containers, nil } diff --git a/integration/server_test.go b/integration/server_test.go index 6d12ad35a..acbec8c2c 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/docker/docker/builder" + "github.com/docker/docker/daemon" "github.com/docker/docker/engine" ) @@ -114,21 +115,17 @@ func TestRestartKillWait(t *testing.T) { id := createTestContainer(eng, config, t) - job := eng.Job("containers") - job.SetenvBool("all", true) - outs, err := job.Stdout.AddListTable() + containers, err := runtime.Containers(&daemon.ContainersConfig{All: true}) + if err != nil { - t.Fatal(err) - } - if err := job.Run(); err != nil { - t.Fatal(err) + t.Errorf("Error getting containers1: %q", err) } - if len(outs.Data) != 1 { - t.Errorf("Expected 1 container, %v found", len(outs.Data)) + if len(containers) != 1 { + t.Errorf("Expected 1 container, %v found", len(containers)) } - job = eng.Job("start", id) + job := eng.Job("start", id) if err := job.ImportEnv(hostConfig); err != nil { t.Fatal(err) } @@ -141,23 +138,19 @@ func TestRestartKillWait(t *testing.T) { } eng = newTestEngine(t, false, runtime.Config().Root) + runtime = mkDaemonFromEngine(eng, t) + + containers, err = runtime.Containers(&daemon.ContainersConfig{All: true}) - job = eng.Job("containers") - job.SetenvBool("all", true) - outs, err = job.Stdout.AddListTable() if err != nil { - t.Fatal(err) + t.Errorf("Error getting containers1: %q", err) } - if err := job.Run(); err != nil { - t.Fatal(err) - } - - if len(outs.Data) != 1 { - t.Errorf("Expected 1 container, %v found", len(outs.Data)) + if len(containers) != 1 { + t.Errorf("Expected 1 container, %v found", len(containers)) } setTimeout(t, "Waiting on stopped container timedout", 5*time.Second, func() { - job = eng.Job("wait", outs.Data[0].Get("Id")) + job = eng.Job("wait", containers[0].ID) if err := job.Run(); err != nil { t.Fatal(err) } @@ -166,7 +159,8 @@ func TestRestartKillWait(t *testing.T) { func TestCreateStartRestartStopStartKillRm(t *testing.T) { eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() + runtime := mkDaemonFromEngine(eng, t) + defer runtime.Nuke() config, hostConfig, _, err := parseRun([]string{"-i", unitTestImageID, "/bin/cat"}) if err != nil { @@ -174,22 +168,13 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { } id := createTestContainer(eng, config, t) + containers, err := runtime.Containers(&daemon.ContainersConfig{All: true}) - job := eng.Job("containers") - job.SetenvBool("all", true) - outs, err := job.Stdout.AddListTable() - if err != nil { - t.Fatal(err) - } - if err := job.Run(); err != nil { - t.Fatal(err) + if len(containers) != 1 { + t.Errorf("Expected 1 container, %v found", len(containers)) } - if len(outs.Data) != 1 { - t.Errorf("Expected 1 container, %v found", len(outs.Data)) - } - - job = eng.Job("start", id) + job := eng.Job("start", id) if err := job.ImportEnv(hostConfig); err != nil { t.Fatal(err) } @@ -228,18 +213,10 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { t.Fatal(err) } - job = eng.Job("containers") - job.SetenvBool("all", true) - outs, err = job.Stdout.AddListTable() - if err != nil { - t.Fatal(err) - } - if err := job.Run(); err != nil { - t.Fatal(err) - } + containers, err = runtime.Containers(&daemon.ContainersConfig{All: true}) - if len(outs.Data) != 0 { - t.Errorf("Expected 0 container, %v found", len(outs.Data)) + if len(containers) != 0 { + t.Errorf("Expected 0 container, %v found", len(containers)) } } From 88119bb276aec5a5b5fd271a2ef6ebd8ad0fa042 Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Wed, 8 Apr 2015 12:05:12 -0700 Subject: [PATCH 315/999] Fixing JSON Tags Signed-off-by: Megan Kostick --- api/types/types.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/api/types/types.go b/api/types/types.go index 931523b0b..79e6f1e8d 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -79,13 +79,13 @@ type Port struct { type Container struct { ID string `json:"Id"` - Names []string `json:,omitempty"` - Image string `json:,omitempty"` - Command string `json:,omitempty"` - Created int `json:,omitempty"` - Ports []Port `json:,omitempty"` - SizeRw int `json:,omitempty"` - SizeRootFs int `json:,omitempty"` - Labels map[string]string `json:,omitempty"` - Status string `json:,omitempty"` + Names []string `json:",omitempty"` + Image string `json:",omitempty"` + Command string `json:",omitempty"` + Created int `json:",omitempty"` + Ports []Port `json:",omitempty"` + SizeRw int `json:",omitempty"` + SizeRootFs int `json:",omitempty"` + Labels map[string]string `json:",omitempty"` + Status string `json:",omitempty"` } From ef8b917fac0b3d98146cadd890234d9179ae2021 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Wed, 8 Apr 2015 09:55:58 -0700 Subject: [PATCH 316/999] Adding environment variables for sub projects Fixes issue #12186 Fixing variables per Jess Signed-off-by: Mary Anthony --- docs/Dockerfile | 60 ++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index b8dbd04ec..7914abf38 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -4,6 +4,15 @@ FROM docs/base:latest MAINTAINER Sven Dowideit (@SvenDowideit) +# This section ensures we pull the correct version of each +# sub project +ENV COMPOSE_BRANCH release +ENV SWARM_BRANCH v0.1.0 +ENV MACHINE_BRANCH v0.1.0 +ENV DISTRIB_BRANCH master + + + # TODO: need the full repo source to get the git version info COPY . /src @@ -25,56 +34,57 @@ COPY ./release.sh release.sh # Docker Distribution -#ADD https://raw.githubusercontent.com/moxiegirl/distribution/doc-tooling-changes/docs/mkdocs.yml /docs/mkdocs-distribution.yml +# +#ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/mkdocs.yml /docs/mkdocs-distribution.yml -ADD https://raw.githubusercontent.com/moxiegirl/distribution/doc-tooling-changes/docs/overview.md /docs/sources/distribution/overview.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/overview.md +#ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md /docs/sources/distribution/overview.md +#RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/overview.md -ADD https://raw.githubusercontent.com/moxiegirl/distribution/doc-tooling-changes/docs/install.md /docs/sources/distribution/install.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/install.md +#ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/install.md /docs/sources/distribution/install.md +#RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/install.md -ADD https://raw.githubusercontent.com/moxiegirl/distribution/doc-tooling-changes/docs/architecture.md /docs/sources/distribution/architecture.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/architecture.md +#ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/architecture.md /docs/sources/distribution/architecture.md +#RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/architecture.md # Docker Swarm -#ADD https://raw.githubusercontent.com/docker/swarm/master/docs/mkdocs.yml /docs/mkdocs-swarm.yml -ADD https://raw.githubusercontent.com/docker/swarm/master/docs/index.md /docs/sources/swarm/index.md +#ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/mkdocs.yml /docs/mkdocs-swarm.yml +ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/index.md /docs/sources/swarm/index.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/index.md -ADD https://raw.githubusercontent.com/docker/swarm/master/discovery/README.md /docs/sources/swarm/discovery.md +ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/discovery/README.md /docs/sources/swarm/discovery.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/discovery.md -ADD https://raw.githubusercontent.com/docker/swarm/master/api/README.md /docs/sources/swarm/API.md +ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/api/README.md /docs/sources/swarm/API.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/API.md -ADD https://raw.githubusercontent.com/docker/swarm/master/scheduler/filter/README.md /docs/sources/swarm/scheduler/filter.md +ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/filter/README.md /docs/sources/swarm/scheduler/filter.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/filter.md -ADD https://raw.githubusercontent.com/docker/swarm/master/scheduler/strategy/README.md /docs/sources/swarm/scheduler/strategy.md +ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/strategy/README.md /docs/sources/swarm/scheduler/strategy.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/strategy.md # Docker Machine -#ADD https://raw.githubusercontent.com/docker/machine/master/docs/mkdocs.yml /docs/mkdocs-machine.yml -ADD https://raw.githubusercontent.com/docker/machine/master/docs/index.md /docs/sources/machine/index.md +#ADD https://raw.githubusercontent.com/docker/machine/${MACHINE_BRANCH}/docs/mkdocs.yml /docs/mkdocs-machine.yml +ADD https://raw.githubusercontent.com/docker/machine/${MACHINE_BRANCH}/docs/index.md /docs/sources/machine/index.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/machine/index.md # Docker Compose -#ADD https://raw.githubusercontent.com/docker/compose/master/docs/mkdocs.yml /docs/mkdocs-compose.yml -ADD https://raw.githubusercontent.com/docker/compose/master/docs/index.md /docs/sources/compose/index.md +#ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/mkdocs.yml /docs/mkdocs-compose.yml +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/index.md /docs/sources/compose/index.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/index.md -ADD https://raw.githubusercontent.com/docker/compose/master/docs/install.md /docs/sources/compose/install.md +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/install.md /docs/sources/compose/install.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/install.md -ADD https://raw.githubusercontent.com/docker/compose/master/docs/cli.md /docs/sources/compose/cli.md +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/cli.md /docs/sources/compose/cli.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/cli.md -ADD https://raw.githubusercontent.com/docker/compose/master/docs/yml.md /docs/sources/compose/yml.md +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/yml.md /docs/sources/compose/yml.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/yml.md -ADD https://raw.githubusercontent.com/docker/compose/master/docs/env.md /docs/sources/compose/env.md +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/env.md /docs/sources/compose/env.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/env.md -ADD https://raw.githubusercontent.com/docker/compose/master/docs/completion.md /docs/sources/compose/completion.md +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/completion.md /docs/sources/compose/completion.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/completion.md -ADD https://raw.githubusercontent.com/docker/compose/master/docs/django.md /docs/sources/compose/django.md +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/django.md /docs/sources/compose/django.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/django.md -ADD https://raw.githubusercontent.com/docker/compose/master/docs/rails.md /docs/sources/compose/rails.md +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/rails.md /docs/sources/compose/rails.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/rails.md -ADD https://raw.githubusercontent.com/docker/compose/master/docs/wordpress.md /docs/sources/compose/wordpress.md +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/wordpress.md /docs/sources/compose/wordpress.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/wordpress.md # Then build everything together, ready for mkdocs From 5206f76f44f3a55e52d3c0878b0ec780484595c0 Mon Sep 17 00:00:00 2001 From: Chris Stivers Date: Tue, 7 Apr 2015 17:08:28 -0700 Subject: [PATCH 317/999] Proposing Seymour Cray Signed-off-by: Chris Stivers --- pkg/namesgenerator/names-generator.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/namesgenerator/names-generator.go b/pkg/namesgenerator/names-generator.go index 0a1eee3b1..38dced39f 100644 --- a/pkg/namesgenerator/names-generator.go +++ b/pkg/namesgenerator/names-generator.go @@ -119,6 +119,9 @@ var ( // Gerty Theresa Cori - American biochemist who became the third woman—and first American woman—to win a Nobel Prize in science, and the first woman to be awarded the Nobel Prize in Physiology or Medicine. Cori was born in Prague. https://en.wikipedia.org/wiki/Gerty_Cori "cori", + // Seymour Roger Cray was an American electrical engineer and supercomputer architect who designed a series of computers that were the fastest in the world for decades. https://en.wikipedia.org/wiki/Seymour_Cray + "cray", + // Marie Curie discovered radioactivity. https://en.wikipedia.org/wiki/Marie_Curie. "curie", From d0c1fba73edfe502d216cb4537036c92137bdb92 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Wed, 8 Apr 2015 13:31:42 -0700 Subject: [PATCH 318/999] Drop shin from MAINTAINERS Signed-off-by: Arnaud Porterie --- MAINTAINERS | 6 ------ 1 file changed, 6 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index d6bcf5a1a..e0ddc9f1a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -429,7 +429,6 @@ made through a pull request. "dmcg", "dmp42", "jlhawn", - "joffrey", "samalba", "sday", "vbatts" @@ -581,11 +580,6 @@ made through a pull request. Email = "josh.hawn@docker.com" Github = "jlhawn" - [people.joffrey] - Name = "Joffrey Fuhrer" - Email = "joffrey@docker.com" - Github = "shin-" - [people.lk4d4] Name = "Alexander Morozov" Email = "lk4d4@docker.com" From 7bb4b055abab5f5b561a970f7235c2d113a4d85f Mon Sep 17 00:00:00 2001 From: Yestin Sun Date: Wed, 8 Apr 2015 13:58:08 -0700 Subject: [PATCH 319/999] Improve test accuracy for pkg/chrootarchive (part 1) Check test correctness of untar by comparing destination with source. For part one, it only compares the directories. This is a supplement to the #11601 fix. Signed-off-by: Yestin Sun --- pkg/chrootarchive/archive_test.go | 64 +++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/pkg/chrootarchive/archive_test.go b/pkg/chrootarchive/archive_test.go index 45397d38f..183091933 100644 --- a/pkg/chrootarchive/archive_test.go +++ b/pkg/chrootarchive/archive_test.go @@ -59,15 +59,15 @@ func TestChrootUntarEmptyArchive(t *testing.T) { } } -func prepareSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool) (int, error) { +func prepareSourceDirectory(numberOfFiles int, targetPath string, makeSymLinks bool) (int, error) { fileData := []byte("fooo") for n := 0; n < numberOfFiles; n++ { fileName := fmt.Sprintf("file-%d", n) if err := ioutil.WriteFile(path.Join(targetPath, fileName), fileData, 0700); err != nil { return 0, err } - if makeLinks { - if err := os.Link(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil { + if makeSymLinks { + if err := os.Symlink(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil { return 0, err } } @@ -76,8 +76,19 @@ func prepareSourceDirectory(numberOfFiles int, targetPath string, makeLinks bool return totalSize, nil } -func TestChrootTarUntarWithSoftLink(t *testing.T) { - tmpdir, err := ioutil.TempDir("", "docker-TestChrootTarUntarWithSoftLink") +func compareDirectories(src string, dest string) error { + changes, err := archive.ChangesDirs(dest, src) + if err != nil { + return err + } + if len(changes) > 0 { + return fmt.Errorf("Unexpected differences after untar: %v", changes) + } + return nil +} + +func TestChrootTarUntarWithSymlink(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "docker-TestChrootTarUntarWithSymlink") if err != nil { t.Fatal(err) } @@ -93,6 +104,9 @@ func TestChrootTarUntarWithSoftLink(t *testing.T) { if err := TarUntar(src, dest); err != nil { t.Fatal(err) } + if err := compareDirectories(src, dest); err != nil { + t.Fatal(err) + } } func TestChrootCopyWithTar(t *testing.T) { @@ -108,19 +122,29 @@ func TestChrootCopyWithTar(t *testing.T) { if _, err := prepareSourceDirectory(10, src, true); err != nil { t.Fatal(err) } - dest := filepath.Join(tmpdir, "dest") + // Copy directory + dest := filepath.Join(tmpdir, "dest") if err := CopyWithTar(src, dest); err != nil { t.Fatal(err) } - // Copy file - srcfile := filepath.Join(src, "file-1") - if err := CopyWithTar(srcfile, dest); err != nil { + if err := compareDirectories(src, dest); err != nil { t.Fatal(err) } + + // Copy file + srcfile := filepath.Join(src, "file-1") + dest = filepath.Join(tmpdir, "destFile") + destfile := filepath.Join(dest, "file-1") + if err := CopyWithTar(srcfile, destfile); err != nil { + t.Fatal(err) + } + // Copy symbolic link - linkfile := filepath.Join(src, "file-1-link") - if err := CopyWithTar(linkfile, dest); err != nil { + srcLinkfile := filepath.Join(src, "file-1-link") + dest = filepath.Join(tmpdir, "destSymlink") + destLinkfile := filepath.Join(dest, "file-1-link") + if err := CopyWithTar(srcLinkfile, destLinkfile); err != nil { t.Fatal(err) } } @@ -138,19 +162,26 @@ func TestChrootCopyFileWithTar(t *testing.T) { if _, err := prepareSourceDirectory(10, src, true); err != nil { t.Fatal(err) } - dest := filepath.Join(tmpdir, "dest") + // Copy directory + dest := filepath.Join(tmpdir, "dest") if err := CopyFileWithTar(src, dest); err == nil { t.Fatal("Expected error on copying directory") } + // Copy file srcfile := filepath.Join(src, "file-1") - if err := CopyFileWithTar(srcfile, dest); err != nil { + dest = filepath.Join(tmpdir, "destFile") + destfile := filepath.Join(dest, "file-1") + if err := CopyFileWithTar(srcfile, destfile); err != nil { t.Fatal(err) } + // Copy symbolic link - linkfile := filepath.Join(src, "file-1-link") - if err := CopyFileWithTar(linkfile, dest); err != nil { + srcLinkfile := filepath.Join(src, "file-1-link") + dest = filepath.Join(tmpdir, "destSymlink") + destLinkfile := filepath.Join(dest, "file-1-link") + if err := CopyFileWithTar(srcLinkfile, destLinkfile); err != nil { t.Fatal(err) } } @@ -188,6 +219,9 @@ func TestChrootUntarPath(t *testing.T) { if err := UntarPath(tarfile, dest); err != nil { t.Fatal(err) } + if err := compareDirectories(src, dest); err != nil { + t.Fatal(err) + } } type slowEmptyTarReader struct { From 53582321ee502335a9c3be4789bef984e09f77c4 Mon Sep 17 00:00:00 2001 From: Tibor Vass Date: Sat, 4 Apr 2015 00:06:48 -0400 Subject: [PATCH 320/999] Remove jobs from daemon/networkdriver/bridge Signed-off-by: Tibor Vass --- api/client/port.go | 17 +- builtins/builtins.go | 23 -- daemon/config.go | 73 +++--- daemon/container.go | 65 ++--- daemon/daemon.go | 32 +-- .../settings.go} | 13 +- daemon/networkdriver/bridge/driver.go | 231 ++++++++---------- daemon/networkdriver/bridge/driver_test.go | 176 ++++--------- integration/utils_test.go | 9 +- links/links.go | 24 +- nat/nat.go | 3 + 11 files changed, 233 insertions(+), 433 deletions(-) rename daemon/{network_settings.go => network/settings.go} (56%) diff --git a/api/client/port.go b/api/client/port.go index a683db3f6..4c3931470 100644 --- a/api/client/port.go +++ b/api/client/port.go @@ -1,10 +1,10 @@ package client import ( + "encoding/json" "fmt" "strings" - "github.com/docker/docker/engine" "github.com/docker/docker/nat" flag "github.com/docker/docker/pkg/mflag" ) @@ -23,12 +23,13 @@ func (cli *DockerCli) CmdPort(args ...string) error { return err } - env := engine.Env{} - if err := env.Decode(stream); err != nil { - return err + var c struct { + NetworkSettings struct { + Ports nat.PortMap + } } - ports := nat.PortMap{} - if err := env.GetSubEnv("NetworkSettings").GetJson("Ports", &ports); err != nil { + + if err := json.NewDecoder(stream).Decode(&c); err != nil { return err } @@ -44,7 +45,7 @@ func (cli *DockerCli) CmdPort(args ...string) error { proto = parts[1] } natPort := port + "/" + proto - if frontends, exists := ports[nat.Port(port+"/"+proto)]; exists && frontends != nil { + if frontends, exists := c.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil { for _, frontend := range frontends { fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) } @@ -53,7 +54,7 @@ func (cli *DockerCli) CmdPort(args ...string) error { return fmt.Errorf("Error: No public port '%s' published for %s", natPort, cmd.Arg(0)) } - for from, frontends := range ports { + for from, frontends := range c.NetworkSettings.Ports { for _, frontend := range frontends { fmt.Fprintf(cli.out, "%s -> %s:%s\n", from, frontend.HostIp, frontend.HostPort) } diff --git a/builtins/builtins.go b/builtins/builtins.go index 149d35009..8957b5833 100644 --- a/builtins/builtins.go +++ b/builtins/builtins.go @@ -6,15 +6,11 @@ import ( "github.com/docker/docker/api" apiserver "github.com/docker/docker/api/server" "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/parsers/kernel" ) func Register(eng *engine.Engine) error { - if err := daemon(eng); err != nil { - return err - } if err := remote(eng); err != nil { return err } @@ -33,25 +29,6 @@ func remote(eng *engine.Engine) error { return eng.Register("acceptconnections", apiserver.AcceptConnections) } -// daemon: a default execution and storage backend for Docker on Linux, -// with the following underlying components: -// -// * Pluggable storage drivers including aufs, vfs, lvm and btrfs. -// * Pluggable execution drivers including lxc and chroot. -// -// In practice `daemon` still includes most core Docker components, including: -// -// * The reference registry client implementation -// * Image management -// * The build facility -// * Logging -// -// These components should be broken off into plugins of their own. -// -func daemon(eng *engine.Engine) error { - return eng.Register("init_networkdriver", bridge.InitDriver) -} - // builtins jobs independent of any subsystem func dockerVersion(job *engine.Job) error { v := &engine.Env{} diff --git a/daemon/config.go b/daemon/config.go index 9b38fde4e..b46895c58 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -1,9 +1,8 @@ package daemon import ( - "net" - "github.com/docker/docker/daemon/networkdriver" + "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/ulimit" @@ -20,35 +19,27 @@ const ( // to the docker daemon when you launch it with say: `docker -d -e lxc` // FIXME: separate runtime configuration from http api configuration type Config struct { - Pidfile string - Root string - AutoRestart bool - Dns []string - DnsSearch []string - EnableIPv6 bool - EnableIptables bool - EnableIpForward bool - EnableIpMasq bool - DefaultIp net.IP - BridgeIface string - BridgeIP string - FixedCIDR string - FixedCIDRv6 string - InterContainerCommunication bool - GraphDriver string - GraphOptions []string - ExecDriver string - Mtu int - SocketGroup string - EnableCors bool - CorsHeaders string - DisableNetwork bool - EnableSelinuxSupport bool - Context map[string][]string - TrustKeyPath string - Labels []string - Ulimits map[string]*ulimit.Ulimit - LogConfig runconfig.LogConfig + Bridge bridge.Config + + Pidfile string + Root string + AutoRestart bool + Dns []string + DnsSearch []string + GraphDriver string + GraphOptions []string + ExecDriver string + Mtu int + SocketGroup string + EnableCors bool + CorsHeaders string + DisableNetwork bool + EnableSelinuxSupport bool + Context map[string][]string + TrustKeyPath string + Labels []string + Ulimits map[string]*ulimit.Ulimit + LogConfig runconfig.LogConfig } // InstallFlags adds command-line options to the top-level flag parser for @@ -59,15 +50,15 @@ func (config *Config) InstallFlags() { flag.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, "/var/run/docker.pid", "Path to use for daemon PID file") flag.StringVar(&config.Root, []string{"g", "-graph"}, "/var/lib/docker", "Root of the Docker runtime") flag.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, "--restart on the daemon has been deprecated in favor of --restart policies on docker run") - flag.BoolVar(&config.EnableIptables, []string{"#iptables", "-iptables"}, true, "Enable addition of iptables rules") - flag.BoolVar(&config.EnableIpForward, []string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward") - flag.BoolVar(&config.EnableIpMasq, []string{"-ip-masq"}, true, "Enable IP masquerading") - flag.BoolVar(&config.EnableIPv6, []string{"-ipv6"}, false, "Enable IPv6 networking") - flag.StringVar(&config.BridgeIP, []string{"#bip", "-bip"}, "", "Specify network bridge IP") - flag.StringVar(&config.BridgeIface, []string{"b", "-bridge"}, "", "Attach containers to a network bridge") - flag.StringVar(&config.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs") - flag.StringVar(&config.FixedCIDRv6, []string{"-fixed-cidr-v6"}, "", "IPv6 subnet for fixed IPs") - flag.BoolVar(&config.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication") + flag.BoolVar(&config.Bridge.EnableIptables, []string{"#iptables", "-iptables"}, true, "Enable addition of iptables rules") + flag.BoolVar(&config.Bridge.EnableIpForward, []string{"#ip-forward", "-ip-forward"}, true, "Enable net.ipv4.ip_forward") + flag.BoolVar(&config.Bridge.EnableIpMasq, []string{"-ip-masq"}, true, "Enable IP masquerading") + flag.BoolVar(&config.Bridge.EnableIPv6, []string{"-ipv6"}, false, "Enable IPv6 networking") + flag.StringVar(&config.Bridge.IP, []string{"#bip", "-bip"}, "", "Specify network bridge IP") + flag.StringVar(&config.Bridge.Iface, []string{"b", "-bridge"}, "", "Attach containers to a network bridge") + flag.StringVar(&config.Bridge.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs") + flag.StringVar(&config.Bridge.FixedCIDRv6, []string{"-fixed-cidr-v6"}, "", "IPv6 subnet for fixed IPs") + flag.BoolVar(&config.Bridge.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication") flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Storage driver to use") flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Exec driver to use") flag.BoolVar(&config.EnableSelinuxSupport, []string{"-selinux-enabled"}, false, "Enable selinux support") @@ -75,7 +66,7 @@ func (config *Config) InstallFlags() { flag.StringVar(&config.SocketGroup, []string{"G", "-group"}, "docker", "Group for the unix socket") flag.BoolVar(&config.EnableCors, []string{"#api-enable-cors", "#-api-enable-cors"}, false, "Enable CORS headers in the remote API, this is deprecated by --api-cors-header") flag.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", "Set CORS headers in the remote API") - opts.IPVar(&config.DefaultIp, []string{"#ip", "-ip"}, "0.0.0.0", "Default IP when binding container ports") + opts.IPVar(&config.Bridge.DefaultIp, []string{"#ip", "-ip"}, "0.0.0.0", "Default IP when binding container ports") opts.ListVar(&config.GraphOptions, []string{"-storage-opt"}, "Set storage driver options") // FIXME: why the inconsistency between "hosts" and "sockets"? opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use") diff --git a/daemon/container.go b/daemon/container.go index 2d9487ea6..6f62f8b72 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -24,6 +24,8 @@ import ( "github.com/docker/docker/daemon/logger" "github.com/docker/docker/daemon/logger/jsonfilelog" "github.com/docker/docker/daemon/logger/syslog" + "github.com/docker/docker/daemon/network" + "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/links" @@ -73,7 +75,7 @@ type Container struct { Config *runconfig.Config ImageID string `json:"Image"` - NetworkSettings *NetworkSettings + NetworkSettings *network.Settings ResolvConfPath string HostnamePath string @@ -571,17 +573,12 @@ func (container *Container) AllocateNetwork() error { } var ( - env *engine.Env err error eng = container.daemon.eng ) - job := eng.Job("allocate_interface", container.ID) - job.Setenv("RequestedMac", container.Config.MacAddress) - if env, err = job.Stdout.AddEnv(); err != nil { - return err - } - if err = job.Run(); err != nil { + networkSettings, err := bridge.Allocate(container.ID, container.Config.MacAddress, "", "") + if err != nil { return err } @@ -591,12 +588,12 @@ func (container *Container) AllocateNetwork() error { if container.Config.PortSpecs != nil { if err = migratePortMappings(container.Config, container.hostConfig); err != nil { - eng.Job("release_interface", container.ID).Run() + bridge.Release(container.ID) return err } container.Config.PortSpecs = nil if err = container.WriteHostConfig(); err != nil { - eng.Job("release_interface", container.ID).Run() + bridge.Release(container.ID) return err } } @@ -626,23 +623,14 @@ func (container *Container) AllocateNetwork() error { for port := range portSpecs { if err = container.allocatePort(eng, port, bindings); err != nil { - eng.Job("release_interface", container.ID).Run() + bridge.Release(container.ID) return err } } container.WriteHostConfig() - container.NetworkSettings.Ports = bindings - container.NetworkSettings.Bridge = env.Get("Bridge") - container.NetworkSettings.IPAddress = env.Get("IP") - container.NetworkSettings.IPPrefixLen = env.GetInt("IPPrefixLen") - container.NetworkSettings.MacAddress = env.Get("MacAddress") - container.NetworkSettings.Gateway = env.Get("Gateway") - container.NetworkSettings.LinkLocalIPv6Address = env.Get("LinkLocalIPv6") - container.NetworkSettings.LinkLocalIPv6PrefixLen = 64 - container.NetworkSettings.GlobalIPv6Address = env.Get("GlobalIPv6") - container.NetworkSettings.GlobalIPv6PrefixLen = env.GetInt("GlobalIPv6PrefixLen") - container.NetworkSettings.IPv6Gateway = env.Get("IPv6Gateway") + networkSettings.Ports = bindings + container.NetworkSettings = networkSettings return nil } @@ -651,12 +639,10 @@ func (container *Container) ReleaseNetwork() { if container.Config.NetworkDisabled || !container.hostConfig.NetworkMode.IsPrivate() { return } - eng := container.daemon.eng - job := eng.Job("release_interface", container.ID) - job.SetenvBool("overrideShutdown", true) - job.Run() - container.NetworkSettings = &NetworkSettings{} + bridge.Release(container.ID) + + container.NetworkSettings = &network.Settings{} } func (container *Container) isNetworkAllocated() bool { @@ -675,10 +661,7 @@ func (container *Container) RestoreNetwork() error { eng := container.daemon.eng // Re-allocate the interface with the same IP and MAC address. - job := eng.Job("allocate_interface", container.ID) - job.Setenv("RequestedIP", container.NetworkSettings.IPAddress) - job.Setenv("RequestedMac", container.NetworkSettings.MacAddress) - if err := job.Run(); err != nil { + if _, err := bridge.Allocate(container.ID, container.NetworkSettings.MacAddress, container.NetworkSettings.IPAddress, ""); err != nil { return err } @@ -1077,7 +1060,7 @@ func (container *Container) setupContainerDns() error { latestResolvConf, latestHash := resolvconf.GetLastModified() // clean container resolv.conf re: localhost nameservers and IPv6 NS (if IPv6 disabled) - updatedResolvConf, modified := resolvconf.FilterResolvDns(latestResolvConf, container.daemon.config.EnableIPv6) + updatedResolvConf, modified := resolvconf.FilterResolvDns(latestResolvConf, container.daemon.config.Bridge.EnableIPv6) if modified { // changes have occurred during resolv.conf localhost cleanup: generate an updated hash newHash, err := utils.HashData(bytes.NewReader(updatedResolvConf)) @@ -1131,7 +1114,7 @@ func (container *Container) setupContainerDns() error { } // replace any localhost/127.*, and remove IPv6 nameservers if IPv6 disabled in daemon - resolvConf, _ = resolvconf.FilterResolvDns(resolvConf, daemon.config.EnableIPv6) + resolvConf, _ = resolvconf.FilterResolvDns(resolvConf, daemon.config.Bridge.EnableIPv6) } //get a sha256 hash of the resolv conf at this point so we can check //for changes when the host resolv.conf changes (e.g. network update) @@ -1481,24 +1464,10 @@ func (container *Container) allocatePort(eng *engine.Engine, port nat.Port, bind } for i := 0; i < len(binding); i++ { - b := binding[i] - - job := eng.Job("allocate_port", container.ID) - job.Setenv("HostIP", b.HostIp) - job.Setenv("HostPort", b.HostPort) - job.Setenv("Proto", port.Proto()) - job.Setenv("ContainerPort", port.Port()) - - portEnv, err := job.Stdout.AddEnv() + b, err := bridge.AllocatePort(container.ID, port, binding[i]) if err != nil { return err } - if err := job.Run(); err != nil { - return err - } - b.HostIp = portEnv.Get("HostIP") - b.HostPort = portEnv.Get("HostPort") - binding[i] = b } bindings[port] = binding diff --git a/daemon/daemon.go b/daemon/daemon.go index 5aa6250a8..21a834e71 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -25,7 +25,8 @@ import ( "github.com/docker/docker/daemon/execdriver/lxc" "github.com/docker/docker/daemon/graphdriver" _ "github.com/docker/docker/daemon/graphdriver/vfs" - _ "github.com/docker/docker/daemon/networkdriver/bridge" + "github.com/docker/docker/daemon/network" + "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/image" @@ -445,7 +446,7 @@ func (daemon *Daemon) setupResolvconfWatcher() error { logrus.Debugf("Error retrieving updated host resolv.conf: %v", err) } else if updatedResolvConf != nil { // because the new host resolv.conf might have localhost nameservers.. - updatedResolvConf, modified := resolvconf.FilterResolvDns(updatedResolvConf, daemon.config.EnableIPv6) + updatedResolvConf, modified := resolvconf.FilterResolvDns(updatedResolvConf, daemon.config.Bridge.EnableIPv6) if modified { // changes have occurred during localhost cleanup: generate an updated hash newHash, err := utils.HashData(bytes.NewReader(updatedResolvConf)) @@ -653,7 +654,7 @@ func (daemon *Daemon) newContainer(name string, config *runconfig.Config, imgID Config: config, hostConfig: &runconfig.HostConfig{}, ImageID: imgID, - NetworkSettings: &NetworkSettings{}, + NetworkSettings: &network.Settings{}, Name: name, Driver: daemon.driver.String(), ExecDriver: daemon.execDriver.Name(), @@ -807,16 +808,16 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService config.Mtu = getDefaultNetworkMtu() } // Check for mutually incompatible config options - if config.BridgeIface != "" && config.BridgeIP != "" { + if config.Bridge.Iface != "" && config.Bridge.IP != "" { return nil, fmt.Errorf("You specified -b & --bip, mutually exclusive options. Please specify only one.") } - if !config.EnableIptables && !config.InterContainerCommunication { + if !config.Bridge.EnableIptables && !config.Bridge.InterContainerCommunication { return nil, fmt.Errorf("You specified --iptables=false with --icc=false. ICC uses iptables to function. Please set --icc or --iptables to true.") } - if !config.EnableIptables && config.EnableIpMasq { - config.EnableIpMasq = false + if !config.Bridge.EnableIptables && config.Bridge.EnableIpMasq { + config.Bridge.EnableIpMasq = false } - config.DisableNetwork = config.BridgeIface == disableNetworkBridge + config.DisableNetwork = config.Bridge.Iface == disableNetworkBridge // Claim the pidfile first, to avoid any and all unexpected race conditions. // Some of the init doesn't need a pidfile lock - but let's not try to be smart. @@ -948,20 +949,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService } if !config.DisableNetwork { - job := eng.Job("init_networkdriver") - - job.SetenvBool("EnableIptables", config.EnableIptables) - job.SetenvBool("InterContainerCommunication", config.InterContainerCommunication) - job.SetenvBool("EnableIpForward", config.EnableIpForward) - job.SetenvBool("EnableIpMasq", config.EnableIpMasq) - job.SetenvBool("EnableIPv6", config.EnableIPv6) - job.Setenv("BridgeIface", config.BridgeIface) - job.Setenv("BridgeIP", config.BridgeIP) - job.Setenv("FixedCIDR", config.FixedCIDR) - job.Setenv("FixedCIDRv6", config.FixedCIDRv6) - job.Setenv("DefaultBindingIP", config.DefaultIp.String()) - - if err := job.Run(); err != nil { + if err := bridge.InitDriver(&config.Bridge); err != nil { return nil, err } } diff --git a/daemon/network_settings.go b/daemon/network/settings.go similarity index 56% rename from daemon/network_settings.go rename to daemon/network/settings.go index bf683b2f0..f3841f09b 100644 --- a/daemon/network_settings.go +++ b/daemon/network/settings.go @@ -1,13 +1,8 @@ -package daemon +package network -import ( - "github.com/docker/docker/nat" -) +import "github.com/docker/docker/nat" -// FIXME: move deprecated port stuff to nat to clean up the core. -type PortMapping map[string]string // Deprecated - -type NetworkSettings struct { +type Settings struct { IPAddress string IPPrefixLen int MacAddress string @@ -18,6 +13,6 @@ type NetworkSettings struct { Gateway string IPv6Gateway string Bridge string - PortMapping map[string]PortMapping // Deprecated + PortMapping map[string]map[string]string // Deprecated Ports nat.PortMap } diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index e974a9f23..dabb1165e 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -7,14 +7,15 @@ import ( "io/ioutil" "net" "os" + "strconv" "strings" "sync" "github.com/Sirupsen/logrus" + "github.com/docker/docker/daemon/network" "github.com/docker/docker/daemon/networkdriver" "github.com/docker/docker/daemon/networkdriver/ipallocator" "github.com/docker/docker/daemon/networkdriver/portmapper" - "github.com/docker/docker/engine" "github.com/docker/docker/nat" "github.com/docker/docker/pkg/iptables" "github.com/docker/docker/pkg/parsers/kernel" @@ -91,29 +92,34 @@ func initPortMapper() { }) } -func InitDriver(job *engine.Job) error { +type Config struct { + EnableIPv6 bool + EnableIptables bool + EnableIpForward bool + EnableIpMasq bool + DefaultIp net.IP + Iface string + IP string + FixedCIDR string + FixedCIDRv6 string + InterContainerCommunication bool +} + +func InitDriver(config *Config) error { var ( - networkv4 *net.IPNet - networkv6 *net.IPNet - addrv4 net.Addr - addrsv6 []net.Addr - enableIPTables = job.GetenvBool("EnableIptables") - enableIPv6 = job.GetenvBool("EnableIPv6") - icc = job.GetenvBool("InterContainerCommunication") - ipMasq = job.GetenvBool("EnableIpMasq") - ipForward = job.GetenvBool("EnableIpForward") - bridgeIP = job.Getenv("BridgeIP") - bridgeIPv6 = "fe80::1/64" - fixedCIDR = job.Getenv("FixedCIDR") - fixedCIDRv6 = job.Getenv("FixedCIDRv6") + networkv4 *net.IPNet + networkv6 *net.IPNet + addrv4 net.Addr + addrsv6 []net.Addr + bridgeIPv6 = "fe80::1/64" ) initPortMapper() - if defaultIP := job.Getenv("DefaultBindingIP"); defaultIP != "" { - defaultBindingIP = net.ParseIP(defaultIP) + if config.DefaultIp != nil { + defaultBindingIP = config.DefaultIp } - bridgeIface = job.Getenv("BridgeIface") + bridgeIface = config.Iface usingDefaultBridge := false if bridgeIface == "" { usingDefaultBridge = true @@ -130,7 +136,7 @@ func InitDriver(job *engine.Job) error { } // If the iface is not found, try to create it - if err := configureBridge(bridgeIP, bridgeIPv6, enableIPv6); err != nil { + if err := configureBridge(config.IP, bridgeIPv6, config.EnableIPv6); err != nil { return err } @@ -139,19 +145,19 @@ func InitDriver(job *engine.Job) error { return err } - if fixedCIDRv6 != "" { + if config.FixedCIDRv6 != "" { // Setting route to global IPv6 subnet - logrus.Infof("Adding route to IPv6 network %q via device %q", fixedCIDRv6, bridgeIface) - if err := netlink.AddRoute(fixedCIDRv6, "", "", bridgeIface); err != nil { - logrus.Fatalf("Could not add route to IPv6 network %q via device %q", fixedCIDRv6, bridgeIface) + logrus.Infof("Adding route to IPv6 network %q via device %q", config.FixedCIDRv6, bridgeIface) + if err := netlink.AddRoute(config.FixedCIDRv6, "", "", bridgeIface); err != nil { + logrus.Fatalf("Could not add route to IPv6 network %q via device %q", config.FixedCIDRv6, bridgeIface) } } } else { // Bridge exists already, getting info... // Validate that the bridge ip matches the ip specified by BridgeIP - if bridgeIP != "" { + if config.IP != "" { networkv4 = addrv4.(*net.IPNet) - bip, _, err := net.ParseCIDR(bridgeIP) + bip, _, err := net.ParseCIDR(config.IP) if err != nil { return err } @@ -164,7 +170,7 @@ func InitDriver(job *engine.Job) error { // (for example, an existing Docker installation that has only been used // with IPv4 and docker0 already is set up) In that case, we can perform // the bridge init for IPv6 here, else we will error out below if --ipv6=true - if len(addrsv6) == 0 && enableIPv6 { + if len(addrsv6) == 0 && config.EnableIPv6 { if err := setupIPv6Bridge(bridgeIPv6); err != nil { return err } @@ -175,10 +181,10 @@ func InitDriver(job *engine.Job) error { } } - // TODO: Check if route to fixedCIDRv6 is set + // TODO: Check if route to config.FixedCIDRv6 is set } - if enableIPv6 { + if config.EnableIPv6 { bip6, _, err := net.ParseCIDR(bridgeIPv6) if err != nil { return err @@ -198,7 +204,7 @@ func InitDriver(job *engine.Job) error { networkv4 = addrv4.(*net.IPNet) - if enableIPv6 { + if config.EnableIPv6 { if len(addrsv6) == 0 { return errors.New("IPv6 enabled but no IPv6 detected") } @@ -206,20 +212,20 @@ func InitDriver(job *engine.Job) error { } // Configure iptables for link support - if enableIPTables { - if err := setupIPTables(addrv4, icc, ipMasq); err != nil { + if config.EnableIptables { + if err := setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq); err != nil { return err } } - if ipForward { + if config.EnableIpForward { // Enable IPv4 forwarding if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil { logrus.Warnf("WARNING: unable to enable IPv4 forwarding: %s\n", err) } - if fixedCIDRv6 != "" { + if config.FixedCIDRv6 != "" { // Enable IPv6 forwarding if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/default/forwarding", []byte{'1', '\n'}, 0644); err != nil { logrus.Warnf("WARNING: unable to enable IPv6 default forwarding: %s\n", err) @@ -235,7 +241,7 @@ func InitDriver(job *engine.Job) error { return err } - if enableIPTables { + if config.EnableIptables { _, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Nat) if err != nil { return err @@ -248,8 +254,8 @@ func InitDriver(job *engine.Job) error { } bridgeIPv4Network = networkv4 - if fixedCIDR != "" { - _, subnet, err := net.ParseCIDR(fixedCIDR) + if config.FixedCIDR != "" { + _, subnet, err := net.ParseCIDR(config.FixedCIDR) if err != nil { return err } @@ -259,8 +265,8 @@ func InitDriver(job *engine.Job) error { } } - if fixedCIDRv6 != "" { - _, subnet, err := net.ParseCIDR(fixedCIDRv6) + if config.FixedCIDRv6 != "" { + _, subnet, err := net.ParseCIDR(config.FixedCIDRv6) if err != nil { return err } @@ -274,19 +280,6 @@ func InitDriver(job *engine.Job) error { // Block BridgeIP in IP allocator ipAllocator.RequestIP(bridgeIPv4Network, bridgeIPv4Network.IP) - // https://github.com/docker/docker/issues/2768 - job.Eng.HackSetGlobalVar("httpapi.bridgeIP", bridgeIPv4Network.IP) - - for name, f := range map[string]engine.Handler{ - "allocate_interface": Allocate, - "release_interface": Release, - "allocate_port": AllocatePort, - "link": LinkContainers, - } { - if err := job.Eng.Register(name, f); err != nil { - return err - } - } return nil } @@ -513,70 +506,67 @@ func linkLocalIPv6FromMac(mac string) (string, error) { } // Allocate a network interface -func Allocate(job *engine.Job) error { +func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Settings, error) { var ( - ip net.IP - mac net.HardwareAddr - err error - id = job.Args[0] - requestedIP = net.ParseIP(job.Getenv("RequestedIP")) - requestedIPv6 = net.ParseIP(job.Getenv("RequestedIPv6")) - globalIPv6 net.IP + ip net.IP + mac net.HardwareAddr + err error + globalIPv6 net.IP ) - ip, err = ipAllocator.RequestIP(bridgeIPv4Network, requestedIP) + ip, err = ipAllocator.RequestIP(bridgeIPv4Network, net.ParseIP(requestedIP)) if err != nil { - return err + return nil, err } // If no explicit mac address was given, generate a random one. - if mac, err = net.ParseMAC(job.Getenv("RequestedMac")); err != nil { + if mac, err = net.ParseMAC(requestedMac); err != nil { mac = generateMacAddr(ip) } if globalIPv6Network != nil { // If globalIPv6Network Size is at least a /80 subnet generate IPv6 address from MAC address netmaskOnes, _ := globalIPv6Network.Mask.Size() - if requestedIPv6 == nil && netmaskOnes <= 80 { - requestedIPv6 = make(net.IP, len(globalIPv6Network.IP)) - copy(requestedIPv6, globalIPv6Network.IP) + ipv6 := net.ParseIP(requestedIPv6) + if ipv6 == nil && netmaskOnes <= 80 { + ipv6 = make(net.IP, len(globalIPv6Network.IP)) + copy(ipv6, globalIPv6Network.IP) for i, h := range mac { - requestedIPv6[i+10] = h + ipv6[i+10] = h } } - globalIPv6, err = ipAllocator.RequestIP(globalIPv6Network, requestedIPv6) + globalIPv6, err = ipAllocator.RequestIP(globalIPv6Network, ipv6) if err != nil { logrus.Errorf("Allocator: RequestIP v6: %v", err) - return err + return nil, err } logrus.Infof("Allocated IPv6 %s", globalIPv6) } - out := engine.Env{} - out.Set("IP", ip.String()) - out.Set("Mask", bridgeIPv4Network.Mask.String()) - out.Set("Gateway", bridgeIPv4Network.IP.String()) - out.Set("MacAddress", mac.String()) - out.Set("Bridge", bridgeIface) - - size, _ := bridgeIPv4Network.Mask.Size() - out.SetInt("IPPrefixLen", size) + maskSize, _ := bridgeIPv4Network.Mask.Size() // If linklocal IPv6 localIPv6Net, err := linkLocalIPv6FromMac(mac.String()) if err != nil { - return err + return nil, err } localIPv6, _, _ := net.ParseCIDR(localIPv6Net) - out.Set("LinkLocalIPv6", localIPv6.String()) - out.Set("MacAddress", mac.String()) + + networkSettings := &network.Settings{ + IPAddress: ip.String(), + Gateway: bridgeIPv4Network.IP.String(), + MacAddress: mac.String(), + Bridge: bridgeIface, + IPPrefixLen: maskSize, + LinkLocalIPv6Address: localIPv6.String(), + } if globalIPv6Network != nil { - out.Set("GlobalIPv6", globalIPv6.String()) - sizev6, _ := globalIPv6Network.Mask.Size() - out.SetInt("GlobalIPv6PrefixLen", sizev6) - out.Set("IPv6Gateway", bridgeIPv6Addr.String()) + networkSettings.GlobalIPv6Address = globalIPv6.String() + maskV6Size, _ := globalIPv6Network.Mask.Size() + networkSettings.GlobalIPv6PrefixLen = maskV6Size + networkSettings.IPv6Gateway = bridgeIPv6Addr.String() } currentInterfaces.Set(id, &networkInterface{ @@ -584,20 +574,15 @@ func Allocate(job *engine.Job) error { IPv6: globalIPv6, }) - out.WriteTo(job.Stdout) - - return nil + return networkSettings, nil } // Release an interface for a select ip -func Release(job *engine.Job) error { - var ( - id = job.Args[0] - containerInterface = currentInterfaces.Get(id) - ) +func Release(id string) { + var containerInterface = currentInterfaces.Get(id) if containerInterface == nil { - return fmt.Errorf("No network information to release for %s", id) + logrus.Warnf("No network information to release for %s", id) } for _, nat := range containerInterface.PortMappings { @@ -614,27 +599,21 @@ func Release(job *engine.Job) error { logrus.Infof("Unable to release IPv6 %s", err) } } - return nil } // Allocate an external port and map it to the interface -func AllocatePort(job *engine.Job) error { +func AllocatePort(id string, port nat.Port, binding nat.PortBinding) (nat.PortBinding, error) { var ( - err error - ip = defaultBindingIP - id = job.Args[0] - hostIP = job.Getenv("HostIP") - hostPort = job.GetenvInt("HostPort") - containerPort = job.GetenvInt("ContainerPort") - proto = job.Getenv("Proto") + proto = port.Proto() + containerPort = port.Int() network = currentInterfaces.Get(id) ) - if hostIP != "" { - ip = net.ParseIP(hostIP) + if binding.HostIp != "" { + ip = net.ParseIP(binding.HostIp) if ip == nil { - return fmt.Errorf("Bad parameter: invalid host ip %s", hostIP) + return nat.PortBinding{}, fmt.Errorf("Bad parameter: invalid host ip %s", binding.HostIp) } } @@ -646,7 +625,7 @@ func AllocatePort(job *engine.Job) error { case "udp": container = &net.UDPAddr{IP: network.IP, Port: containerPort} default: - return fmt.Errorf("unsupported address type %s", proto) + return nat.PortBinding{}, fmt.Errorf("unsupported address type %s", proto) } // @@ -656,7 +635,14 @@ func AllocatePort(job *engine.Job) error { // yields. // - var host net.Addr + var ( + host net.Addr + err error + ) + hostPort, err := nat.ParsePort(binding.HostPort) + if err != nil { + return nat.PortBinding{}, err + } for i := 0; i < MaxAllocatedPortAttempts; i++ { if host, err = portMapper.Map(container, ip, hostPort); err == nil { break @@ -671,36 +657,24 @@ func AllocatePort(job *engine.Job) error { } if err != nil { - return err + return nat.PortBinding{}, err } network.PortMappings = append(network.PortMappings, host) - out := engine.Env{} switch netAddr := host.(type) { case *net.TCPAddr: - out.Set("HostIP", netAddr.IP.String()) - out.SetInt("HostPort", netAddr.Port) + return nat.PortBinding{HostIp: netAddr.IP.String(), HostPort: strconv.Itoa(netAddr.Port)}, nil case *net.UDPAddr: - out.Set("HostIP", netAddr.IP.String()) - out.SetInt("HostPort", netAddr.Port) + return nat.PortBinding{HostIp: netAddr.IP.String(), HostPort: strconv.Itoa(netAddr.Port)}, nil + default: + return nat.PortBinding{}, fmt.Errorf("unsupported address type %T", netAddr) } - if _, err := out.WriteTo(job.Stdout); err != nil { - return err - } - - return nil } -func LinkContainers(job *engine.Job) error { - var ( - action = job.Args[0] - nfAction iptables.Action - childIP = job.Getenv("ChildIP") - parentIP = job.Getenv("ParentIP") - ignoreErrors = job.GetenvBool("IgnoreErrors") - ports = job.GetenvList("Ports") - ) +//TODO: should it return something more than just an error? +func LinkContainers(action, parentIP, childIP string, ports []nat.Port, ignoreErrors bool) error { + var nfAction iptables.Action switch action { case "-A": @@ -723,8 +697,7 @@ func LinkContainers(job *engine.Job) error { } chain := iptables.Chain{Name: "DOCKER", Bridge: bridgeIface} - for _, p := range ports { - port := nat.Port(p) + for _, port := range ports { if err := chain.Link(nfAction, ip1, ip2, port.Int(), port.Proto()); !ignoreErrors && err != nil { return err } diff --git a/daemon/networkdriver/bridge/driver_test.go b/daemon/networkdriver/bridge/driver_test.go index b646dfd71..d18882e66 100644 --- a/daemon/networkdriver/bridge/driver_test.go +++ b/daemon/networkdriver/bridge/driver_test.go @@ -6,8 +6,9 @@ import ( "strconv" "testing" + "github.com/docker/docker/daemon/network" "github.com/docker/docker/daemon/networkdriver/portmapper" - "github.com/docker/docker/engine" + "github.com/docker/docker/nat" "github.com/docker/docker/pkg/iptables" ) @@ -16,7 +17,7 @@ func init() { portmapper.NewProxy = portmapper.NewMockProxyCommand } -func findFreePort(t *testing.T) int { +func findFreePort(t *testing.T) string { l, err := net.Listen("tcp", ":0") if err != nil { t.Fatal("Failed to find a free port") @@ -27,143 +28,85 @@ func findFreePort(t *testing.T) int { if err != nil { t.Fatal("Failed to resolve address to identify free port") } - return result.Port -} - -func newPortAllocationJob(eng *engine.Engine, port int) (job *engine.Job) { - strPort := strconv.Itoa(port) - - job = eng.Job("allocate_port", "container_id") - job.Setenv("HostIP", "127.0.0.1") - job.Setenv("HostPort", strPort) - job.Setenv("Proto", "tcp") - job.Setenv("ContainerPort", strPort) - return -} - -func newPortAllocationJobWithInvalidHostIP(eng *engine.Engine, port int) (job *engine.Job) { - strPort := strconv.Itoa(port) - - job = eng.Job("allocate_port", "container_id") - job.Setenv("HostIP", "localhost") - job.Setenv("HostPort", strPort) - job.Setenv("Proto", "tcp") - job.Setenv("ContainerPort", strPort) - return + return strconv.Itoa(result.Port) } func TestAllocatePortDetection(t *testing.T) { - eng := engine.New() - eng.Logging = false - freePort := findFreePort(t) - // Init driver - job := eng.Job("initdriver") - if res := InitDriver(job); res != nil { + if err := InitDriver(new(Config)); err != nil { t.Fatal("Failed to initialize network driver") } // Allocate interface - job = eng.Job("allocate_interface", "container_id") - if res := Allocate(job); res != nil { + if _, err := Allocate("container_id", "", "", ""); err != nil { t.Fatal("Failed to allocate network interface") } + port := nat.Port(freePort + "/tcp") + binding := nat.PortBinding{HostIp: "127.0.0.1", HostPort: freePort} + // Allocate same port twice, expect failure on second call - job = newPortAllocationJob(eng, freePort) - if res := AllocatePort(job); res != nil { + if _, err := AllocatePort("container_id", port, binding); err != nil { t.Fatal("Failed to find a free port to allocate") } - if res := AllocatePort(job); res == nil { + if _, err := AllocatePort("container_id", port, binding); err == nil { t.Fatal("Duplicate port allocation granted by AllocatePort") } } func TestHostnameFormatChecking(t *testing.T) { - eng := engine.New() - eng.Logging = false - freePort := findFreePort(t) - // Init driver - job := eng.Job("initdriver") - if res := InitDriver(job); res != nil { + if err := InitDriver(new(Config)); err != nil { t.Fatal("Failed to initialize network driver") } // Allocate interface - job = eng.Job("allocate_interface", "container_id") - if res := Allocate(job); res != nil { + if _, err := Allocate("container_id", "", "", ""); err != nil { t.Fatal("Failed to allocate network interface") } - // Allocate port with invalid HostIP, expect failure with Bad Request http status - job = newPortAllocationJobWithInvalidHostIP(eng, freePort) - if res := AllocatePort(job); res == nil { + port := nat.Port(freePort + "/tcp") + binding := nat.PortBinding{HostIp: "localhost", HostPort: freePort} + + if _, err := AllocatePort("container_id", port, binding); err == nil { t.Fatal("Failed to check invalid HostIP") } } -func newInterfaceAllocation(t *testing.T, input engine.Env) (output engine.Env) { - eng := engine.New() - eng.Logging = false - - done := make(chan bool) - +func newInterfaceAllocation(t *testing.T, globalIPv6 *net.IPNet, requestedMac, requestedIP, requestedIPv6 string, expectFail bool) *network.Settings { // set IPv6 global if given - if input.Exists("globalIPv6Network") { - _, globalIPv6Network, _ = net.ParseCIDR(input.Get("globalIPv6Network")) + if globalIPv6 != nil { + globalIPv6Network = globalIPv6 } - job := eng.Job("allocate_interface", "container_id") - job.Env().Init(&input) - reader, _ := job.Stdout.AddPipe() - go func() { - output.Decode(reader) - done <- true - }() + networkSettings, err := Allocate("container_id", requestedMac, requestedIP, requestedIPv6) + if err == nil && expectFail { + t.Fatal("Doesn't fail to allocate network interface") + } else if err != nil && !expectFail { + t.Fatal("Failed to allocate network interface") - res := Allocate(job) - job.Stdout.Close() - <-done - - if input.Exists("expectFail") && input.GetBool("expectFail") { - if res == nil { - t.Fatal("Doesn't fail to allocate network interface") - } - } else { - if res != nil { - t.Fatal("Failed to allocate network interface") - } } - if input.Exists("globalIPv6Network") { + if globalIPv6 != nil { // check for bug #11427 - _, subnet, _ := net.ParseCIDR(input.Get("globalIPv6Network")) - if globalIPv6Network.IP.String() != subnet.IP.String() { + if globalIPv6Network.IP.String() != globalIPv6.IP.String() { t.Fatal("globalIPv6Network was modified during allocation") } // clean up IPv6 global globalIPv6Network = nil } - return + return networkSettings } func TestIPv6InterfaceAllocationAutoNetmaskGt80(t *testing.T) { - - input := engine.Env{} - _, subnet, _ := net.ParseCIDR("2001:db8:1234:1234:1234::/81") - - // set global ipv6 - input.Set("globalIPv6Network", subnet.String()) - - output := newInterfaceAllocation(t, input) + networkSettings := newInterfaceAllocation(t, subnet, "", "", "", false) // ensure low manually assigend global ip - ip := net.ParseIP(output.Get("GlobalIPv6")) + ip := net.ParseIP(networkSettings.GlobalIPv6Address) _, subnet, _ = net.ParseCIDR(fmt.Sprintf("%s/%d", subnet.IP.String(), 120)) if !subnet.Contains(ip) { t.Fatalf("Error ip %s not in subnet %s", ip.String(), subnet.String()) @@ -171,26 +114,18 @@ func TestIPv6InterfaceAllocationAutoNetmaskGt80(t *testing.T) { } func TestIPv6InterfaceAllocationAutoNetmaskLe80(t *testing.T) { - - input := engine.Env{} - _, subnet, _ := net.ParseCIDR("2001:db8:1234:1234:1234::/80") - - // set global ipv6 - input.Set("globalIPv6Network", subnet.String()) - input.Set("RequestedMac", "ab:cd:ab:cd:ab:cd") - - output := newInterfaceAllocation(t, input) + networkSettings := newInterfaceAllocation(t, subnet, "ab:cd:ab:cd:ab:cd", "", "", false) // ensure global ip with mac - ip := net.ParseIP(output.Get("GlobalIPv6")) + ip := net.ParseIP(networkSettings.GlobalIPv6Address) expectedIP := net.ParseIP("2001:db8:1234:1234:1234:abcd:abcd:abcd") if ip.String() != expectedIP.String() { t.Fatalf("Error ip %s should be %s", ip.String(), expectedIP.String()) } // ensure link local format - ip = net.ParseIP(output.Get("LinkLocalIPv6")) + ip = net.ParseIP(networkSettings.LinkLocalIPv6Address) expectedIP = net.ParseIP("fe80::a9cd:abff:fecd:abcd") if ip.String() != expectedIP.String() { t.Fatalf("Error ip %s should be %s", ip.String(), expectedIP.String()) @@ -199,27 +134,19 @@ func TestIPv6InterfaceAllocationAutoNetmaskLe80(t *testing.T) { } func TestIPv6InterfaceAllocationRequest(t *testing.T) { - - input := engine.Env{} - _, subnet, _ := net.ParseCIDR("2001:db8:1234:1234:1234::/80") - expectedIP := net.ParseIP("2001:db8:1234:1234:1234::1328") + expectedIP := "2001:db8:1234:1234:1234::1328" - // set global ipv6 - input.Set("globalIPv6Network", subnet.String()) - input.Set("RequestedIPv6", expectedIP.String()) - - output := newInterfaceAllocation(t, input) + networkSettings := newInterfaceAllocation(t, subnet, "", "", expectedIP, false) // ensure global ip with mac - ip := net.ParseIP(output.Get("GlobalIPv6")) - if ip.String() != expectedIP.String() { - t.Fatalf("Error ip %s should be %s", ip.String(), expectedIP.String()) + ip := net.ParseIP(networkSettings.GlobalIPv6Address) + if ip.String() != expectedIP { + t.Fatalf("Error ip %s should be %s", ip.String(), expectedIP) } // retry -> fails for duplicated address - input.SetBool("expectFail", true) - output = newInterfaceAllocation(t, input) + _ = newInterfaceAllocation(t, subnet, "", "", expectedIP, true) } func TestMacAddrGeneration(t *testing.T) { @@ -239,40 +166,27 @@ func TestMacAddrGeneration(t *testing.T) { } func TestLinkContainers(t *testing.T) { - eng := engine.New() - eng.Logging = false - // Init driver - job := eng.Job("initdriver") - if res := InitDriver(job); res != nil { + if err := InitDriver(new(Config)); err != nil { t.Fatal("Failed to initialize network driver") } // Allocate interface - job = eng.Job("allocate_interface", "container_id") - if res := Allocate(job); res != nil { + if _, err := Allocate("container_id", "", "", ""); err != nil { t.Fatal("Failed to allocate network interface") } - job.Args[0] = "-I" - - job.Setenv("ChildIP", "172.17.0.2") - job.Setenv("ParentIP", "172.17.0.1") - job.SetenvBool("IgnoreErrors", false) - job.SetenvList("Ports", []string{"1234"}) - bridgeIface = "lo" - _, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter) - if err != nil { + if _, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter); err != nil { t.Fatal(err) } - if res := LinkContainers(job); res != nil { - t.Fatalf("LinkContainers failed") + if err := LinkContainers("-I", "172.17.0.1", "172.17.0.2", []nat.Port{nat.Port("1234")}, false); err != nil { + t.Fatal("LinkContainers failed") } // flush rules - if _, err = iptables.Raw([]string{"-F", "DOCKER"}...); err != nil { + if _, err := iptables.Raw([]string{"-F", "DOCKER"}...); err != nil { t.Fatal(err) } diff --git a/integration/utils_test.go b/integration/utils_test.go index c0e826a0f..ee7293034 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -18,6 +18,7 @@ import ( "github.com/docker/docker/builtins" "github.com/docker/docker/daemon" + "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/registry" @@ -185,9 +186,11 @@ func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine { ExecDriver: "native", // Either InterContainerCommunication or EnableIptables must be set, // otherwise NewDaemon will fail because of conflicting settings. - InterContainerCommunication: true, - TrustKeyPath: filepath.Join(root, "key.json"), - LogConfig: runconfig.LogConfig{Type: "json-file"}, + Bridge: bridge.Config{ + InterContainerCommunication: true, + }, + TrustKeyPath: filepath.Join(root, "key.json"), + LogConfig: runconfig.LogConfig{Type: "json-file"}, } d, err := daemon.NewDaemon(cfg, eng, registry.NewService(nil)) if err != nil { diff --git a/links/links.go b/links/links.go index 96c18cc24..0e5e806e5 100644 --- a/links/links.go +++ b/links/links.go @@ -2,10 +2,12 @@ package links import ( "fmt" - "github.com/docker/docker/engine" - "github.com/docker/docker/nat" "path" "strings" + + "github.com/docker/docker/daemon/networkdriver/bridge" + "github.com/docker/docker/engine" + "github.com/docker/docker/nat" ) type Link struct { @@ -158,21 +160,5 @@ func (l *Link) Disable() { } func (l *Link) toggle(action string, ignoreErrors bool) error { - job := l.eng.Job("link", action) - - job.Setenv("ParentIP", l.ParentIP) - job.Setenv("ChildIP", l.ChildIP) - job.SetenvBool("IgnoreErrors", ignoreErrors) - - out := make([]string, len(l.Ports)) - for i, p := range l.Ports { - out[i] = string(p) - } - job.SetenvList("Ports", out) - - if err := job.Run(); err != nil { - // TODO: get ouput from job - return err - } - return nil + return bridge.LinkContainers(action, l.ParentIP, l.ChildIP, l.Ports, ignoreErrors) } diff --git a/nat/nat.go b/nat/nat.go index fdecf3f9d..2cec2e86c 100644 --- a/nat/nat.go +++ b/nat/nat.go @@ -34,6 +34,9 @@ func NewPort(proto, port string) Port { } func ParsePort(rawPort string) (int, error) { + if len(rawPort) == 0 { + return 0, nil + } port, err := strconv.ParseUint(rawPort, 10, 16) if err != nil { return 0, err From b66597eef18a02014e0b634dc3dc55dff3de4c20 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Wed, 8 Apr 2015 16:16:37 -0700 Subject: [PATCH 321/999] docs: Markdown format fix around LogConfig param Signed-off-by: Ahmet Alp Balkan --- docs/sources/reference/api/docker_remote_api_v1.18.md | 5 ++--- docs/sources/reference/api/docker_remote_api_v1.19.md | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index 6c49fb91f..75eed99da 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -257,10 +257,9 @@ Json Parameters: `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` - **SecurityOpt**: A list of string values to customize labels for MLS systems, such as SELinux. - - **LogConfig** - Logging configuration to container, format - `{ "Type": "", "Config": {"key1": "val1"}} + - **LogConfig** - Logging configuration to container, format: + `{ "Type": "", "Config": {"key1": "val1"}}`. Available types: `json-file`, `syslog`, `none`. - `json-file` logging driver. - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. Query Parameters: diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index a98206cbc..b643d4490 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -257,10 +257,9 @@ Json Parameters: `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` - **SecurityOpt**: A list of string values to customize labels for MLS systems, such as SELinux. - - **LogConfig** - Logging configuration to container, format - `{ "Type": "", "Config": {"key1": "val1"}} + - **LogConfig** - Logging configuration to container, format: + `{ "Type": "", "Config": {"key1": "val1"}}`. Available types: `json-file`, `syslog`, `none`. - `json-file` logging driver. - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. Query Parameters: From 255b84444a86201995d8f688d71b83822f146c3e Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 8 Apr 2015 16:20:42 -0700 Subject: [PATCH 322/999] cleanup/fix integration-cli for overlay in overlay Signed-off-by: Jessica Frazelle --- integration-cli/docker_cli_cp_test.go | 2 +- integration-cli/docker_cli_ps_test.go | 44 ++++++++++++++++---------- integration-cli/docker_cli_run_test.go | 6 ++-- integration-cli/docker_utils.go | 15 +++------ 4 files changed, 37 insertions(+), 30 deletions(-) diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index f448b3d58..37e4659e9 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -472,7 +472,7 @@ func TestCpVolumePath(t *testing.T) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) + defer dockerCmd(t, "rm", "-fv", cleanedContainerID) out, _, err = dockerCmd(t, "wait", cleanedContainerID) if err != nil || strings.TrimSpace(out) != "0" { diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index 4d417a77e..f97da5be3 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -42,12 +42,22 @@ func TestPsListContainers(t *testing.T) { } fourthID := strings.TrimSpace(out) + // make sure the second is running + if err := waitRun(secondID); err != nil { + t.Fatalf("waiting for container failed: %v", err) + } + // make sure third one is not running runCmd = exec.Command(dockerBinary, "wait", thirdID) if out, _, err = runCommandWithOutput(runCmd); err != nil { t.Fatal(out, err) } + // make sure the forth is running + if err := waitRun(fourthID); err != nil { + t.Fatalf("waiting for container failed: %v", err) + } + // all runCmd = exec.Command(dockerBinary, "ps", "-a") out, _, err = runCommandWithOutput(runCmd) @@ -56,7 +66,7 @@ func TestPsListContainers(t *testing.T) { } if !assertContainerList(out, []string{fourthID, thirdID, secondID, firstID}) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } // running @@ -67,7 +77,7 @@ func TestPsListContainers(t *testing.T) { } if !assertContainerList(out, []string{fourthID, secondID, firstID}) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } // from here all flag '-a' is ignored @@ -81,7 +91,7 @@ func TestPsListContainers(t *testing.T) { expected := []string{fourthID, thirdID} if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "-n=2") @@ -91,7 +101,7 @@ func TestPsListContainers(t *testing.T) { } if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } // since @@ -103,7 +113,7 @@ func TestPsListContainers(t *testing.T) { expected = []string{fourthID, thirdID, secondID} if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--since", firstID) @@ -113,7 +123,7 @@ func TestPsListContainers(t *testing.T) { } if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } // before @@ -125,7 +135,7 @@ func TestPsListContainers(t *testing.T) { expected = []string{secondID, firstID} if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--before", thirdID) @@ -135,7 +145,7 @@ func TestPsListContainers(t *testing.T) { } if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } // since & before @@ -147,7 +157,7 @@ func TestPsListContainers(t *testing.T) { expected = []string{thirdID, secondID} if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID) @@ -156,7 +166,7 @@ func TestPsListContainers(t *testing.T) { t.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } // since & limit @@ -168,7 +178,7 @@ func TestPsListContainers(t *testing.T) { expected = []string{fourthID, thirdID} if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "-n=2") @@ -178,7 +188,7 @@ func TestPsListContainers(t *testing.T) { } if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } // before & limit @@ -190,7 +200,7 @@ func TestPsListContainers(t *testing.T) { expected = []string{thirdID} if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--before", fourthID, "-n=1") @@ -200,7 +210,7 @@ func TestPsListContainers(t *testing.T) { } if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } // since & before & limit @@ -212,7 +222,7 @@ func TestPsListContainers(t *testing.T) { expected = []string{thirdID} if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID, "-n=1") @@ -222,7 +232,7 @@ func TestPsListContainers(t *testing.T) { } if !assertContainerList(out, expected) { - t.Error("Container list is not in the correct order") + t.Errorf("Container list is not in the correct order: %s", out) } logDone("ps - test ps options") @@ -535,7 +545,7 @@ func TestPsListContainersFilterExited(t *testing.T) { } ids := strings.Split(strings.TrimSpace(out), "\n") if len(ids) != 2 { - t.Fatalf("Should be 2 zero exited containerst got %d", len(ids)) + t.Fatalf("Should be 2 zero exited containers got %d: %s", len(ids), out) } if ids[0] != secondZero { t.Fatalf("First in list should be %q, got %q", secondZero, ids[0]) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 1be512d53..a5d1e3e07 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3428,7 +3428,8 @@ func TestRunVolumesFromRestartAfterRemoved(t *testing.T) { func TestRunContainerWithRmFlagExitCodeNotEqualToZero(t *testing.T) { defer deleteAllContainers() - runCmd := exec.Command(dockerBinary, "run", "--rm", "busybox", "ls", "/notexists") + name := "flowers" + runCmd := exec.Command(dockerBinary, "run", "--name", name, "--rm", "busybox", "ls", "/notexists") out, _, err := runCommandWithOutput(runCmd) if err == nil { t.Fatal("Expected docker run to fail", out, err) @@ -3449,7 +3450,8 @@ func TestRunContainerWithRmFlagExitCodeNotEqualToZero(t *testing.T) { func TestRunContainerWithRmFlagCannotStartContainer(t *testing.T) { defer deleteAllContainers() - runCmd := exec.Command(dockerBinary, "run", "--rm", "busybox", "commandNotFound") + name := "sparkles" + runCmd := exec.Command(dockerBinary, "run", "--name", name, "--rm", "busybox", "commandNotFound") out, _, err := runCommandWithOutput(runCmd) if err == nil { t.Fatal("Expected docker run to fail", out, err) diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 84adc374e..843a07a20 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -340,16 +340,11 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, } func deleteContainer(container string) error { - container = strings.Replace(container, "\n", " ", -1) - container = strings.Trim(container, " ") - killArgs := fmt.Sprintf("kill %v", container) - killSplitArgs := strings.Split(killArgs, " ") - killCmd := exec.Command(dockerBinary, killSplitArgs...) - runCommand(killCmd) - rmArgs := fmt.Sprintf("rm -v %v", container) - rmSplitArgs := strings.Split(rmArgs, " ") - rmCmd := exec.Command(dockerBinary, rmSplitArgs...) - exitCode, err := runCommand(rmCmd) + container = strings.TrimSpace(strings.Replace(container, "\n", " ", -1)) + killArgs := strings.Split(fmt.Sprintf("kill %v", container), " ") + runCommand(exec.Command(dockerBinary, killArgs...)) + rmArgs := strings.Split(fmt.Sprintf("rm -v %v", container), " ") + exitCode, err := runCommand(exec.Command(dockerBinary, rmArgs...)) // set error manually if not set if exitCode != 0 && err == nil { err = fmt.Errorf("failed to remove container: `docker rm` exit is non-zero") From c2c45d77691d1ca501a68d20885d040415477c92 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Wed, 8 Apr 2015 18:13:07 -0700 Subject: [PATCH 323/999] execdriver/lxc: use local rand.Random in test Preventing the test execution to pollute the deterministic runtime environment by seeding the global rand.Random. Signed-off-by: Ahmet Alp Balkan --- daemon/execdriver/lxc/lxc_template_unit_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/execdriver/lxc/lxc_template_unit_test.go b/daemon/execdriver/lxc/lxc_template_unit_test.go index 78760f600..fcac6a3e5 100644 --- a/daemon/execdriver/lxc/lxc_template_unit_test.go +++ b/daemon/execdriver/lxc/lxc_template_unit_test.go @@ -29,14 +29,14 @@ func TestLXCConfig(t *testing.T) { os.MkdirAll(path.Join(root, "containers", "1"), 0777) // Memory is allocated randomly for testing - rand.Seed(time.Now().UTC().UnixNano()) + r := rand.New(rand.NewSource(time.Now().UTC().UnixNano())) var ( memMin = 33554432 memMax = 536870912 - mem = memMin + rand.Intn(memMax-memMin) + mem = memMin + r.Intn(memMax-memMin) cpuMin = 100 cpuMax = 10000 - cpu = cpuMin + rand.Intn(cpuMax-cpuMin) + cpu = cpuMin + r.Intn(cpuMax-cpuMin) ) driver, err := NewDriver(root, root, "", false) From 663d9130118548c648c4463bae088fd983099e08 Mon Sep 17 00:00:00 2001 From: Liu Hua Date: Sun, 29 Mar 2015 10:17:17 +0800 Subject: [PATCH 324/999] Show the right image name/ID in job log When we tag an Image with several names and we run one of them, The "create" job will log this event with +job log(create, containerID, Imagename). And the "Imagename" is always the first one (sorted). It is the same to "start/stop/rm" jobs. So use the correct name instand. This PR refer to #10479 Signed-off-by: Liu Hua --- daemon/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/container.go b/daemon/container.go index 2d9487ea6..d18db430a 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -203,7 +203,7 @@ func (container *Container) LogEvent(action string) { d.EventsService.Log( action, container.ID, - d.Repositories().ImageName(container.ImageID), + container.Config.Image, ) } From d045b9776b5dc16e12b3d7c7558a24cdc5d1aba7 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Tue, 7 Apr 2015 18:57:54 -0700 Subject: [PATCH 325/999] Remove Job from `docker images` Also removes engine.Table Signed-off-by: Doug Davis --- api/server/server.go | 72 ++++++----- api/server/server_unit_test.go | 101 ---------------- api/types/types.go | 9 ++ engine/streams.go | 37 ------ engine/table.go | 140 ---------------------- engine/table_test.go | 112 ----------------- graph/list.go | 31 ++--- graph/service.go | 1 - integration-cli/docker_api_images_test.go | 26 ++++ integration/api_test.go | 8 +- integration/runtime_test.go | 13 +- integration/server_test.go | 8 +- integration/utils_test.go | 22 ++-- 13 files changed, 112 insertions(+), 468 deletions(-) delete mode 100644 engine/table.go delete mode 100644 engine/table_test.go create mode 100644 integration-cli/docker_api_images_test.go diff --git a/api/server/server.go b/api/server/server.go index e59aec5a7..7b794be53 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -24,6 +24,7 @@ import ( "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" + "github.com/docker/docker/graph" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/filters" @@ -264,48 +265,40 @@ func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseW return err } - var ( - err error - outs *engine.Table - job = eng.Job("images") - ) + imagesConfig := graph.ImagesConfig{ + Filters: r.Form.Get("filters"), + // FIXME this parameter could just be a match filter + Filter: r.Form.Get("filter"), + All: toBool(r.Form.Get("all")), + } - job.Setenv("filters", r.Form.Get("filters")) - // FIXME this parameter could just be a match filter - job.Setenv("filter", r.Form.Get("filter")) - job.Setenv("all", r.Form.Get("all")) + images, err := getDaemon(eng).Repositories().Images(&imagesConfig) + if err != nil { + return err + } if version.GreaterThanOrEqualTo("1.7") { - streamJSON(job, w, false) - } else if outs, err = job.Stdout.AddListTable(); err != nil { - return err + return writeJSON(w, http.StatusOK, images) } - if err := job.Run(); err != nil { - return err - } + legacyImages := []types.LegacyImage{} - if version.LessThan("1.7") && outs != nil { // Convert to legacy format - outsLegacy := engine.NewTable("Created", 0) - for _, out := range outs.Data { - for _, repoTag := range out.GetList("RepoTags") { - repo, tag := parsers.ParseRepositoryTag(repoTag) - outLegacy := &engine.Env{} - outLegacy.Set("Repository", repo) - outLegacy.SetJson("Tag", tag) - outLegacy.Set("Id", out.Get("Id")) - outLegacy.SetInt64("Created", out.GetInt64("Created")) - outLegacy.SetInt64("Size", out.GetInt64("Size")) - outLegacy.SetInt64("VirtualSize", out.GetInt64("VirtualSize")) - outsLegacy.Add(outLegacy) + for _, image := range images { + for _, repoTag := range image.RepoTags { + repo, tag := parsers.ParseRepositoryTag(repoTag) + legacyImage := types.LegacyImage{ + Repository: repo, + Tag: tag, + ID: image.ID, + Created: image.Created, + Size: image.Size, + VirtualSize: image.VirtualSize, } - } - w.Header().Set("Content-Type", "application/json") - if _, err := outsLegacy.WriteListTo(w); err != nil { - return err + legacyImages = append(legacyImages, legacyImage) } } - return nil + + return writeJSON(w, http.StatusOK, legacyImages) } func getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -488,8 +481,8 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo } config := &daemon.ContainersConfig{ - All: r.Form.Get("all") == "1", - Size: r.Form.Get("size") == "1", + All: toBool(r.Form.Get("all")), + Size: toBool(r.Form.Get("size")), Since: r.Form.Get("since"), Before: r.Form.Get("before"), Filters: r.Form.Get("filters"), @@ -1140,14 +1133,14 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite job.Stdout.Add(utils.NewWriteFlusher(w)) } - if r.FormValue("forcerm") == "1" && version.GreaterThanOrEqualTo("1.12") { + if toBool(r.FormValue("forcerm")) && version.GreaterThanOrEqualTo("1.12") { job.Setenv("rm", "1") } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { job.Setenv("rm", "1") } else { job.Setenv("rm", r.FormValue("rm")) } - if r.FormValue("pull") == "1" && version.GreaterThanOrEqualTo("1.16") { + if toBool(r.FormValue("pull")) && version.GreaterThanOrEqualTo("1.16") { job.Setenv("pull", "1") } job.Stdin.Add(r.Body) @@ -1557,3 +1550,8 @@ func ServeApi(job *engine.Job) error { return nil } + +func toBool(s string) bool { + s = strings.ToLower(strings.TrimSpace(s)) + return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") +} diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index 6441b42cd..ee0fa6bc2 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -7,7 +7,6 @@ import ( "io" "net/http" "net/http/httptest" - "reflect" "strings" "testing" @@ -117,106 +116,6 @@ func TestGetInfo(t *testing.T) { assertContentType(r, "application/json", t) } -func TestGetImagesJSON(t *testing.T) { - eng := engine.New() - var called bool - eng.Register("images", func(job *engine.Job) error { - called = true - v := createEnvFromGetImagesJSONStruct(sampleImage) - if err := json.NewEncoder(job.Stdout).Encode(v); err != nil { - return err - } - return nil - }) - r := serveRequest("GET", "/images/json", nil, eng, t) - if !called { - t.Fatal("handler was not called") - } - assertHttpNotError(r, t) - assertContentType(r, "application/json", t) - var observed getImagesJSONStruct - if err := json.Unmarshal(r.Body.Bytes(), &observed); err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(observed, sampleImage) { - t.Errorf("Expected %#v but got %#v", sampleImage, observed) - } -} - -func TestGetImagesJSONFilter(t *testing.T) { - eng := engine.New() - filter := "nothing" - eng.Register("images", func(job *engine.Job) error { - filter = job.Getenv("filter") - return nil - }) - serveRequest("GET", "/images/json?filter=aaaa", nil, eng, t) - if filter != "aaaa" { - t.Errorf("%#v", filter) - } -} - -func TestGetImagesJSONFilters(t *testing.T) { - eng := engine.New() - filter := "nothing" - eng.Register("images", func(job *engine.Job) error { - filter = job.Getenv("filters") - return nil - }) - serveRequest("GET", "/images/json?filters=nnnn", nil, eng, t) - if filter != "nnnn" { - t.Errorf("%#v", filter) - } -} - -func TestGetImagesJSONAll(t *testing.T) { - eng := engine.New() - allFilter := "-1" - eng.Register("images", func(job *engine.Job) error { - allFilter = job.Getenv("all") - return nil - }) - serveRequest("GET", "/images/json?all=1", nil, eng, t) - if allFilter != "1" { - t.Errorf("%#v", allFilter) - } -} - -func TestGetImagesJSONLegacyFormat(t *testing.T) { - eng := engine.New() - var called bool - eng.Register("images", func(job *engine.Job) error { - called = true - images := []types.Image{ - createEnvFromGetImagesJSONStruct(sampleImage), - } - if err := json.NewEncoder(job.Stdout).Encode(images); err != nil { - return err - } - return nil - }) - r := serveRequestUsingVersion("GET", "/images/json", "1.6", nil, eng, t) - if !called { - t.Fatal("handler was not called") - } - assertHttpNotError(r, t) - assertContentType(r, "application/json", t) - images := engine.NewTable("Created", 0) - if _, err := images.ReadListFrom(r.Body.Bytes()); err != nil { - t.Fatal(err) - } - if images.Len() != 1 { - t.Fatalf("Expected 1 image, %d found", images.Len()) - } - image := images.Data[0] - if image.Get("Tag") != "test-tag" { - t.Errorf("Expected tag 'test-tag', found '%s'", image.Get("Tag")) - } - if image.Get("Repository") != "test-name" { - t.Errorf("Expected repository 'test-name', found '%s'", image.Get("Repository")) - } -} - func TestGetContainersByName(t *testing.T) { eng := engine.New() name := "container_name" diff --git a/api/types/types.go b/api/types/types.go index 79e6f1e8d..36983f68d 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -69,6 +69,15 @@ type Image struct { Labels map[string]string } +type LegacyImage struct { + ID string `json:"Id"` + Repository string + Tag string + Created int + Size int + VirtualSize int +} + // GET "/containers/json" type Port struct { IP string diff --git a/engine/streams.go b/engine/streams.go index 216fb8980..2863e9448 100644 --- a/engine/streams.go +++ b/engine/streams.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" "io" - "io/ioutil" "strings" "sync" "unicode" @@ -187,39 +186,3 @@ func (o *Output) AddEnv() (dst *Env, err error) { }() return dst, nil } - -func (o *Output) AddListTable() (dst *Table, err error) { - src, err := o.AddPipe() - if err != nil { - return nil, err - } - dst = NewTable("", 0) - o.tasks.Add(1) - go func() { - defer o.tasks.Done() - content, err := ioutil.ReadAll(src) - if err != nil { - return - } - if _, err := dst.ReadListFrom(content); err != nil { - return - } - }() - return dst, nil -} - -func (o *Output) AddTable() (dst *Table, err error) { - src, err := o.AddPipe() - if err != nil { - return nil, err - } - dst = NewTable("", 0) - o.tasks.Add(1) - go func() { - defer o.tasks.Done() - if _, err := dst.ReadFrom(src); err != nil { - return - } - }() - return dst, nil -} diff --git a/engine/table.go b/engine/table.go deleted file mode 100644 index 4498bdf1e..000000000 --- a/engine/table.go +++ /dev/null @@ -1,140 +0,0 @@ -package engine - -import ( - "bytes" - "encoding/json" - "io" - "sort" - "strconv" -) - -type Table struct { - Data []*Env - sortKey string - Chan chan *Env -} - -func NewTable(sortKey string, sizeHint int) *Table { - return &Table{ - make([]*Env, 0, sizeHint), - sortKey, - make(chan *Env), - } -} - -func (t *Table) SetKey(sortKey string) { - t.sortKey = sortKey -} - -func (t *Table) Add(env *Env) { - t.Data = append(t.Data, env) -} - -func (t *Table) Len() int { - return len(t.Data) -} - -func (t *Table) Less(a, b int) bool { - return t.lessBy(a, b, t.sortKey) -} - -func (t *Table) lessBy(a, b int, by string) bool { - keyA := t.Data[a].Get(by) - keyB := t.Data[b].Get(by) - intA, errA := strconv.ParseInt(keyA, 10, 64) - intB, errB := strconv.ParseInt(keyB, 10, 64) - if errA == nil && errB == nil { - return intA < intB - } - return keyA < keyB -} - -func (t *Table) Swap(a, b int) { - tmp := t.Data[a] - t.Data[a] = t.Data[b] - t.Data[b] = tmp -} - -func (t *Table) Sort() { - sort.Sort(t) -} - -func (t *Table) ReverseSort() { - sort.Sort(sort.Reverse(t)) -} - -func (t *Table) WriteListTo(dst io.Writer) (n int64, err error) { - if _, err := dst.Write([]byte{'['}); err != nil { - return -1, err - } - n = 1 - for i, env := range t.Data { - bytes, err := env.WriteTo(dst) - if err != nil { - return -1, err - } - n += bytes - if i != len(t.Data)-1 { - if _, err := dst.Write([]byte{','}); err != nil { - return -1, err - } - n++ - } - } - if _, err := dst.Write([]byte{']'}); err != nil { - return -1, err - } - return n + 1, nil -} - -func (t *Table) ToListString() (string, error) { - buffer := bytes.NewBuffer(nil) - if _, err := t.WriteListTo(buffer); err != nil { - return "", err - } - return buffer.String(), nil -} - -func (t *Table) WriteTo(dst io.Writer) (n int64, err error) { - for _, env := range t.Data { - bytes, err := env.WriteTo(dst) - if err != nil { - return -1, err - } - n += bytes - } - return n, nil -} - -func (t *Table) ReadListFrom(src []byte) (n int64, err error) { - var array []interface{} - - if err := json.Unmarshal(src, &array); err != nil { - return -1, err - } - - for _, item := range array { - if m, ok := item.(map[string]interface{}); ok { - env := &Env{} - for key, value := range m { - env.SetAuto(key, value) - } - t.Add(env) - } - } - - return int64(len(src)), nil -} - -func (t *Table) ReadFrom(src io.Reader) (n int64, err error) { - decoder := NewDecoder(src) - for { - env, err := decoder.Decode() - if err == io.EOF { - return 0, nil - } else if err != nil { - return -1, err - } - t.Add(env) - } -} diff --git a/engine/table_test.go b/engine/table_test.go deleted file mode 100644 index 9a32ac9cd..000000000 --- a/engine/table_test.go +++ /dev/null @@ -1,112 +0,0 @@ -package engine - -import ( - "bytes" - "encoding/json" - "testing" -) - -func TestTableWriteTo(t *testing.T) { - table := NewTable("", 0) - e := &Env{} - e.Set("foo", "bar") - table.Add(e) - var buf bytes.Buffer - if _, err := table.WriteTo(&buf); err != nil { - t.Fatal(err) - } - output := make(map[string]string) - if err := json.Unmarshal(buf.Bytes(), &output); err != nil { - t.Fatal(err) - } - if len(output) != 1 { - t.Fatalf("Incorrect output: %v", output) - } - if val, exists := output["foo"]; !exists || val != "bar" { - t.Fatalf("Inccorect output: %v", output) - } -} - -func TestTableSortStringValue(t *testing.T) { - table := NewTable("Key", 0) - - e := &Env{} - e.Set("Key", "A") - table.Add(e) - - e = &Env{} - e.Set("Key", "D") - table.Add(e) - - e = &Env{} - e.Set("Key", "B") - table.Add(e) - - e = &Env{} - e.Set("Key", "C") - table.Add(e) - - table.Sort() - - if len := table.Len(); len != 4 { - t.Fatalf("Expected 4, got %d", len) - } - - if value := table.Data[0].Get("Key"); value != "A" { - t.Fatalf("Expected A, got %s", value) - } - - if value := table.Data[1].Get("Key"); value != "B" { - t.Fatalf("Expected B, got %s", value) - } - - if value := table.Data[2].Get("Key"); value != "C" { - t.Fatalf("Expected C, got %s", value) - } - - if value := table.Data[3].Get("Key"); value != "D" { - t.Fatalf("Expected D, got %s", value) - } -} - -func TestTableReverseSortStringValue(t *testing.T) { - table := NewTable("Key", 0) - - e := &Env{} - e.Set("Key", "A") - table.Add(e) - - e = &Env{} - e.Set("Key", "D") - table.Add(e) - - e = &Env{} - e.Set("Key", "B") - table.Add(e) - - e = &Env{} - e.Set("Key", "C") - table.Add(e) - - table.ReverseSort() - - if len := table.Len(); len != 4 { - t.Fatalf("Expected 4, got %d", len) - } - - if value := table.Data[0].Get("Key"); value != "D" { - t.Fatalf("Expected D, got %s", value) - } - - if value := table.Data[1].Get("Key"); value != "C" { - t.Fatalf("Expected B, got %s", value) - } - - if value := table.Data[2].Get("Key"); value != "B" { - t.Fatalf("Expected C, got %s", value) - } - - if value := table.Data[3].Get("Key"); value != "A" { - t.Fatalf("Expected A, got %s", value) - } -} diff --git a/graph/list.go b/graph/list.go index 5af4b87e3..f95508e95 100644 --- a/graph/list.go +++ b/graph/list.go @@ -1,7 +1,6 @@ package graph import ( - "encoding/json" "fmt" "log" "path" @@ -9,7 +8,6 @@ import ( "strings" "github.com/docker/docker/api/types" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers/filters" "github.com/docker/docker/utils" @@ -20,13 +18,19 @@ var acceptedImageFilterTags = map[string]struct{}{ "label": {}, } +type ImagesConfig struct { + Filters string + Filter string + All bool +} + type ByCreated []*types.Image func (r ByCreated) Len() int { return len(r) } func (r ByCreated) Swap(i, j int) { r[i], r[j] = r[j], r[i] } func (r ByCreated) Less(i, j int) bool { return r[i].Created < r[j].Created } -func (s *TagStore) CmdImages(job *engine.Job) error { +func (s *TagStore) Images(config *ImagesConfig) ([]*types.Image, error) { var ( allImages map[string]*image.Image err error @@ -34,13 +38,13 @@ func (s *TagStore) CmdImages(job *engine.Job) error { filtLabel = false ) - imageFilters, err := filters.FromParam(job.Getenv("filters")) + imageFilters, err := filters.FromParam(config.Filters) if err != nil { - return err + return nil, err } for name := range imageFilters { if _, ok := acceptedImageFilterTags[name]; !ok { - return fmt.Errorf("Invalid filter '%s'", name) + return nil, fmt.Errorf("Invalid filter '%s'", name) } } @@ -54,20 +58,20 @@ func (s *TagStore) CmdImages(job *engine.Job) error { _, filtLabel = imageFilters["label"] - if job.GetenvBool("all") && filtTagged { + if config.All && filtTagged { allImages, err = s.graph.Map() } else { allImages, err = s.graph.Heads() } if err != nil { - return err + return nil, err } lookup := make(map[string]*types.Image) s.Lock() for repoName, repository := range s.Repositories { - if job.Getenv("filter") != "" { - if match, _ := path.Match(job.Getenv("filter"), repoName); !match { + if config.Filter != "" { + if match, _ := path.Match(config.Filter, repoName); !match { continue } } @@ -124,7 +128,7 @@ func (s *TagStore) CmdImages(job *engine.Job) error { } // Display images which aren't part of a repository/tag - if job.Getenv("filter") == "" || filtLabel { + if config.Filter == "" || filtLabel { for _, image := range allImages { if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) { continue @@ -145,8 +149,5 @@ func (s *TagStore) CmdImages(job *engine.Job) error { sort.Sort(sort.Reverse(ByCreated(images))) - if err = json.NewEncoder(job.Stdout).Encode(images); err != nil { - return err - } - return nil + return images, nil } diff --git a/graph/service.go b/graph/service.go index 98523aef0..a51d106e1 100644 --- a/graph/service.go +++ b/graph/service.go @@ -18,7 +18,6 @@ func (s *TagStore) Install(eng *engine.Engine) error { "image_tarlayer": s.CmdTarLayer, "image_export": s.CmdImageExport, "history": s.CmdHistory, - "images": s.CmdImages, "viz": s.CmdViz, "load": s.CmdLoad, "import": s.CmdImport, diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go new file mode 100644 index 000000000..38d891fd5 --- /dev/null +++ b/integration-cli/docker_api_images_test.go @@ -0,0 +1,26 @@ +package main + +import ( + "encoding/json" + "testing" + + "github.com/docker/docker/api/types" +) + +func TestLegacyImages(t *testing.T) { + body, err := sockRequest("GET", "/v1.6/images/json", nil) + if err != nil { + t.Fatalf("Error on GET: %s", err) + } + + images := []types.LegacyImage{} + if err = json.Unmarshal(body, &images); err != nil { + t.Fatalf("Error on unmarshal: %s", err) + } + + if len(images) == 0 || images[0].Tag == "" || images[0].Repository == "" { + t.Fatalf("Bad data: %q", images) + } + + logDone("images - checking legacy json") +} diff --git a/integration/api_test.go b/integration/api_test.go index cd6b9669b..98e683d00 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -767,8 +767,8 @@ func TestDeleteImages(t *testing.T) { images := getImages(eng, t, true, "") - if len(images.Data[0].GetList("RepoTags")) != len(initialImages.Data[0].GetList("RepoTags"))+1 { - t.Errorf("Expected %d images, %d found", len(initialImages.Data[0].GetList("RepoTags"))+1, len(images.Data[0].GetList("RepoTags"))) + if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 { + t.Errorf("Expected %d images, %d found", len(initialImages[0].RepoTags)+1, len(images[0].RepoTags)) } req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil) @@ -805,8 +805,8 @@ func TestDeleteImages(t *testing.T) { } images = getImages(eng, t, false, "") - if images.Len() != initialImages.Len() { - t.Errorf("Expected %d image, %d found", initialImages.Len(), images.Len()) + if len(images) != len(initialImages) { + t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) } } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 07d9de7c6..b5e404d59 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -20,6 +20,7 @@ import ( "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/engine" + "github.com/docker/docker/graph" "github.com/docker/docker/image" "github.com/docker/docker/nat" "github.com/docker/docker/pkg/ioutils" @@ -65,17 +66,13 @@ func cleanup(eng *engine.Engine, t *testing.T) error { container.Kill() daemon.Rm(container) } - job := eng.Job("images") - images, err := job.Stdout.AddTable() + images, err := daemon.Repositories().Images(&graph.ImagesConfig{}) if err != nil { t.Fatal(err) } - if err := job.Run(); err != nil { - t.Fatal(err) - } - for _, image := range images.Data { - if image.Get("Id") != unitTestImageID { - eng.Job("image_delete", image.Get("Id")).Run() + for _, image := range images { + if image.ID != unitTestImageID { + eng.Job("image_delete", image.ID).Run() } } return nil diff --git a/integration/server_test.go b/integration/server_test.go index acbec8c2c..9cdd3d774 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -253,25 +253,25 @@ func TestImagesFilter(t *testing.T) { images := getImages(eng, t, false, "utest*/*") - if len(images.Data[0].GetList("RepoTags")) != 2 { + if len(images[0].RepoTags) != 2 { t.Fatal("incorrect number of matches returned") } images = getImages(eng, t, false, "utest") - if len(images.Data[0].GetList("RepoTags")) != 1 { + if len(images[0].RepoTags) != 1 { t.Fatal("incorrect number of matches returned") } images = getImages(eng, t, false, "utest*") - if len(images.Data[0].GetList("RepoTags")) != 1 { + if len(images[0].RepoTags) != 1 { t.Fatal("incorrect number of matches returned") } images = getImages(eng, t, false, "*5000*/*") - if len(images.Data[0].GetList("RepoTags")) != 1 { + if len(images[0].RepoTags) != 1 { t.Fatal("incorrect number of matches returned") } } diff --git a/integration/utils_test.go b/integration/utils_test.go index c0e826a0f..f8afe62b6 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -16,9 +16,11 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" + "github.com/docker/docker/api/types" "github.com/docker/docker/builtins" "github.com/docker/docker/daemon" "github.com/docker/docker/engine" + "github.com/docker/docker/graph" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" @@ -329,19 +331,17 @@ func fakeTar() (io.ReadCloser, error) { return ioutil.NopCloser(buf), nil } -func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) *engine.Table { - job := eng.Job("images") - job.SetenvBool("all", all) - job.Setenv("filter", filter) - images, err := job.Stdout.AddListTable() +func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) []*types.Image { + config := graph.ImagesConfig{ + Filter: filter, + All: all, + } + images, err := getDaemon(eng).Repositories().Images(&config) if err != nil { t.Fatal(err) } - if err := job.Run(); err != nil { - t.Fatal(err) - } - return images + return images } func parseRun(args []string) (*runconfig.Config, *runconfig.HostConfig, *flag.FlagSet, error) { @@ -350,3 +350,7 @@ func parseRun(args []string) (*runconfig.Config, *runconfig.HostConfig, *flag.Fl cmd.Usage = nil return runconfig.Parse(cmd, args) } + +func getDaemon(eng *engine.Engine) *daemon.Daemon { + return eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon) +} From 0fc75a3136456621c074124819567816420f55bf Mon Sep 17 00:00:00 2001 From: WiseTrem Date: Thu, 9 Apr 2015 02:27:49 +0300 Subject: [PATCH 326/999] Removing unused getBoolParam func from api/server/server.go Fix #12199 Signed-off-by: Gleb Shepelev --- api/server/server.go | 11 ----------- api/server/server_unit_test.go | 25 ------------------------- 2 files changed, 36 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index e59aec5a7..70cb5d834 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -161,17 +161,6 @@ func streamJSON(job *engine.Job, w http.ResponseWriter, flush bool) { } } -func getBoolParam(value string) (bool, error) { - if value == "" { - return false, nil - } - ret, err := strconv.ParseBool(value) - if err != nil { - return false, fmt.Errorf("Bad parameter") - } - return ret, nil -} - func getDaemon(eng *engine.Engine) *daemon.Daemon { return eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon) } diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index 6441b42cd..607f254c7 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -17,31 +17,6 @@ import ( "github.com/docker/docker/pkg/version" ) -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() From 809d99ad913fd73ae5da637c26f94879a6eb189c Mon Sep 17 00:00:00 2001 From: Chen Hanxiao Date: Thu, 9 Apr 2015 04:37:23 -0400 Subject: [PATCH 327/999] docs: add missing accent mark around docker command As we did everywhere. Signed-off-by: Chen Hanxiao --- docs/man/docker-commit.1.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/man/docker-commit.1.md b/docs/man/docker-commit.1.md index be70fdaa7..003cb6f69 100644 --- a/docs/man/docker-commit.1.md +++ b/docs/man/docker-commit.1.md @@ -38,7 +38,7 @@ Using an existing container's name or ID you can create a new image. ## Creating a new image from an existing container An existing Fedora based container has had Apache installed while running in interactive mode with the bash shell. Apache is also running. To -create a new image run docker ps to find the container's ID and then run: +create a new image run `docker ps` to find the container's ID and then run: # docker commit -m="Added Apache to Fedora base image" \ -a="A D Ministrator" 98bd7fc99854 fedora/fedora_httpd:20 @@ -46,7 +46,7 @@ create a new image run docker ps to find the container's ID and then run: ## Apply specified Dockerfile instructions while committing the image If an existing container was created without the DEBUG environment variable set to "true", you can create a new image based on that -container by first getting the container's ID with docker ps and +container by first getting the container's ID with `docker ps` and then running: # docker commit -c="ENV DEBUG true" 98bd7fc99854 debug-image From a4b7a9e1e5505983aea3f6d7e246c57a6f4f6170 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Wed, 8 Apr 2015 16:37:39 -0700 Subject: [PATCH 328/999] cli: Better wording for daemon --log-driver This flag is passed to the daemon CLI. In my opinion, "Container's logging driver" is not accurate and refers to 'one container'. Also the `syslog` driver was missing from the list. Having the list of all logging drivers won't scale here (should be <80 chars per line) and we have `rotation` driver coming up in the pipeline as well (gh11485). Signed-off-by: Ahmet Alp Balkan --- daemon/config.go | 2 +- docs/man/docker.1.md | 2 +- docs/sources/reference/commandline/cli.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/config.go b/daemon/config.go index 9b38fde4e..10608b769 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -83,7 +83,7 @@ func (config *Config) InstallFlags() { opts.LabelListVar(&config.Labels, []string{"-label"}, "Set key=value labels to the daemon") config.Ulimits = make(map[string]*ulimit.Ulimit) opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers") - flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Containers logging driver") + flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Default driver for container logs") } func getDefaultNetworkMtu() int { diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index bcb9d2541..ec79850de 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -90,7 +90,7 @@ unix://[/path/to/socket] to use. Set key=value labels to the daemon (displayed in `docker info`) **--log-driver**="*json-file*|*syslog*|*none*" - Container's logging driver. Default is `default`. + Default driver for container logs. Default is `json-file`. **Warning**: `docker logs` command works only for `json-file` logging driver. **--mtu**=VALUE diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 9f8daa03f..4658a97ed 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -134,7 +134,7 @@ expect an integer, and they can only be specified once. --ipv6=false Enable IPv6 networking -l, --log-level="info" Set the logging level --label=[] Set key=value labels to the daemon - --log-driver="json-file" Container's logging driver (json-file/none) + --log-driver="json-file" Default driver for container logs --mtu=0 Set the containers network MTU -p, --pidfile="/var/run/docker.pid" Path to use for daemon PID file --registry-mirror=[] Preferred Docker registry mirror From 4c69d0dd8ade90b6602147b8dfb8c1f4c267d3bc Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Thu, 9 Apr 2015 04:01:39 -0700 Subject: [PATCH 329/999] cli_info_test: Check all required fields `TestInfoEnsureSucceeds` is supposed to check existence of all expected fields that are going to be shown in `docker info` command. If this list was complete, it could have helped catching the missing `"Logging Driver:"` regression. Signed-off-by: Ahmet Alp Balkan --- integration-cli/docker_cli_info_test.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go index 68c24f292..e6b79f01f 100644 --- a/integration-cli/docker_cli_info_test.go +++ b/integration-cli/docker_cli_info_test.go @@ -14,7 +14,18 @@ func TestInfoEnsureSucceeds(t *testing.T) { t.Fatalf("failed to execute docker info: %s, %v", out, err) } - stringsToCheck := []string{"Containers:", "Execution Driver:", "Logging Driver:", "Kernel Version:"} + // always shown fields + stringsToCheck := []string{ + "ID:", + "Containers:", + "Images:", + "Execution Driver:", + "Logging Driver:", + "Operating System:", + "CPUs:", + "Total Memory:", + "Kernel Version:", + "Storage Driver:"} for _, linePrefix := range stringsToCheck { if !strings.Contains(out, linePrefix) { From 197ec4a6377373b4a0c2eaf90c9fde9cede2074b Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Thu, 9 Apr 2015 04:11:06 -0700 Subject: [PATCH 330/999] namesgenerator: Proposing Kilby/Noyce Signed-off-by: Ahmet Alp Balkan --- pkg/namesgenerator/names-generator.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/namesgenerator/names-generator.go b/pkg/namesgenerator/names-generator.go index 38dced39f..40d4e5374 100644 --- a/pkg/namesgenerator/names-generator.go +++ b/pkg/namesgenerator/names-generator.go @@ -188,6 +188,12 @@ var ( // Karen Spärck Jones came up with the concept of inverse document frequency, which is used in most search engines today. https://en.wikipedia.org/wiki/Karen_Sp%C3%A4rck_Jones "jones", + // Jack Kilby and Robert Noyce have invented silicone integrated circuits and gave Silicon Valley its name. + // - https://en.wikipedia.org/wiki/Jack_Kilby + // - https://en.wikipedia.org/wiki/Robert_Noyce + "kilby", + "noyce", + // Maria Kirch - German astronomer and first woman to discover a comet - https://en.wikipedia.org/wiki/Maria_Margarethe_Kirch "kirch", From 7e70998bb80d47e3f3a363fb49feaeaf50d64cbc Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 6 Apr 2015 13:18:38 -0400 Subject: [PATCH 331/999] Cleanup events filter by container test This also seemed to be checking the ordering of the events, which doesn't seem like something we sould be interested in this particular test. Added check to make sure the filtered events have the expected ID's. Signed-off-by: Brian Goff --- integration-cli/docker_cli_events_test.go | 117 +++++++++------------- 1 file changed, 49 insertions(+), 68 deletions(-) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 6c5a41356..363e13d1a 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -329,85 +329,66 @@ func TestEventsFilterImageName(t *testing.T) { logDone("events - filters using image") } -func TestEventsFilterContainerID(t *testing.T) { - since := daemonTime(t).Unix() +func TestEventsFilterContainer(t *testing.T) { defer deleteAllContainers() + since := fmt.Sprintf("%d", daemonTime(t).Unix()) + nameID := make(map[string]string) - out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "true")) - if err != nil { - t.Fatal(out, err) - } - container1 := strings.TrimSpace(out) - - out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "true")) - if err != nil { - t.Fatal(out, err) - } - container2 := strings.TrimSpace(out) - - for _, s := range []string{container1, container2, container1[:12], container2[:12]} { - if err := waitInspect(s, "{{.State.Running}}", "false", 5); err != nil { - t.Fatalf("Failed to get container %s state, error: %s", s, err) + for _, name := range []string{"container_1", "container_2"} { + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "true")) + if err != nil { + t.Fatal(err) } + nameID[name] = strings.TrimSpace(out) + waitInspect(name, "{{.State.Runing }}", "false", 5) + } - eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("container=%s", s)) + until := fmt.Sprintf("%d", daemonTime(t).Unix()) + + checkEvents := func(id string, events []string) error { + if len(events) != 3 { // create, start, die + return fmt.Errorf("expected 3 events, got %v", events) + } + for _, event := range events { + e := strings.Fields(event) + if len(e) < 3 { + return fmt.Errorf("got malformed event: %s", event) + } + + // Check the id + parsedID := strings.TrimSuffix(e[1], ":") + if parsedID != id { + return fmt.Errorf("expected event for container id %s: %s - parsed container id: %s", id, event, parsedID) + } + } + return nil + } + + for name, ID := range nameID { + // filter by names + eventsCmd := exec.Command(dockerBinary, "events", "--since", since, "--until", until, "--filter", "container="+name) out, _, err := runCommandWithOutput(eventsCmd) if err != nil { - t.Fatalf("Failed to get events, error: %s(%s)", err, out) - } - events := strings.Split(out, "\n") - checkEvents(t, events[:len(events)-1]) - } - - logDone("events - filters using container id") -} - -func TestEventsFilterContainerName(t *testing.T) { - since := daemonTime(t).Unix() - defer deleteAllContainers() - - _, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_1", "busybox", "true")) - if err != nil { - t.Fatal(err) - } - - _, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_2", "busybox", "true")) - if err != nil { - t.Fatal(err) - } - - for _, s := range []string{"container_1", "container_2"} { - if err := waitInspect(s, "{{.State.Running}}", "false", 5); err != nil { - t.Fatalf("Failed to get container %s state, error: %s", s, err) + t.Fatal(err) } - eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("container=%s", s)) - out, _, err := runCommandWithOutput(eventsCmd) + events := strings.Split(strings.TrimSuffix(out, "\n"), "\n") + if err := checkEvents(ID, events); err != nil { + t.Fatal(err) + } + + // filter by ID's + eventsCmd = exec.Command(dockerBinary, "events", "--since", since, "--until", until, "--filter", "container="+ID) + out, _, err = runCommandWithOutput(eventsCmd) if err != nil { - t.Fatalf("Failed to get events, error : %s(%s)", err, out) + t.Fatal(err) + } + + events = strings.Split(strings.TrimSuffix(out, "\n"), "\n") + if err := checkEvents(ID, events); err != nil { + t.Fatal(err) } - events := strings.Split(out, "\n") - checkEvents(t, events[:len(events)-1]) } logDone("events - filters using container name") } - -func checkEvents(t *testing.T, events []string) { - if len(events) != 3 { - t.Fatalf("Expected 3 events, got %d: %v", len(events), events) - } - createEvent := strings.Fields(events[0]) - if createEvent[len(createEvent)-1] != "create" { - t.Fatalf("first event should be create, not %#v", createEvent) - } - startEvent := strings.Fields(events[1]) - if startEvent[len(startEvent)-1] != "start" { - t.Fatalf("second event should be start, not %#v", startEvent) - } - dieEvent := strings.Fields(events[len(events)-1]) - if dieEvent[len(dieEvent)-1] != "die" { - t.Fatalf("event should be die, not %#v", dieEvent) - } - -} From 645c020f5ab7119cd06d7c6a790e9d99fe1cd309 Mon Sep 17 00:00:00 2001 From: Liu Hua Date: Wed, 1 Apr 2015 22:08:00 +0800 Subject: [PATCH 332/999] fix up Image-name related issues in docker ps and CI This patch include the following fixs: - fix image name error when docker ps - fix docker events test failure: use the exact image name for filter - fix docker build CI test failure due to "docker events" change Because of change of daemon log behavior. Now we record the exact Image name as you typed. So docker run -d busybux sh and docker run -d busybox:latest are not the same in the log. So it will affect the docker events. So change the related CI Signed-off-by: Liu Hua --- daemon/list.go | 10 +---- integration-cli/docker_cli_build_test.go | 12 +++++- integration-cli/docker_cli_events_test.go | 48 +++++++++++------------ integration-cli/docker_cli_ps_test.go | 29 +++++++++++--- 4 files changed, 59 insertions(+), 40 deletions(-) diff --git a/daemon/list.go b/daemon/list.go index 511b49605..99242988b 100644 --- a/daemon/list.go +++ b/daemon/list.go @@ -7,12 +7,9 @@ import ( "strings" "github.com/docker/docker/api/types" - "github.com/docker/docker/graph" "github.com/docker/docker/nat" "github.com/docker/docker/pkg/graphdb" - "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/filters" - "github.com/docker/docker/utils" ) // List returns an array of all containers registered in the daemon. @@ -136,12 +133,7 @@ func (daemon *Daemon) Containers(config *ContainersConfig) ([]*types.Container, ID: container.ID, Names: names[container.ID], } - img := container.Config.Image - _, tag := parsers.ParseRepositoryTag(container.Config.Image) - if tag == "" { - img = utils.ImageReference(img, graph.DEFAULTTAG) - } - newC.Image = img + newC.Image = container.Config.Image if len(container.Args) > 0 { args := []string{} for _, arg := range container.Args { diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index bea9bf937..e3f7ec7f1 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -2009,8 +2009,16 @@ func TestBuildCancelationKillsSleep(t *testing.T) { }() var started, died bool - matchStart := regexp.MustCompile(" \\(from busybox\\:latest\\) start$") - matchDie := regexp.MustCompile(" \\(from busybox\\:latest\\) die$") + var imageID string + + if out, err := exec.Command(dockerBinary, "inspect", "-f", "{{.Id}}", "busybox").CombinedOutput(); err != nil { + t.Fatalf("failed to get the image ID of busybox: %s, %v", out, err) + } else { + imageID = strings.TrimSpace(string(out)) + } + + matchStart := regexp.MustCompile(" \\(from " + imageID + "\\) start$") + matchDie := regexp.MustCompile(" \\(from " + imageID + "\\) die$") // // Read lines of `docker events` looking for container start and stop. diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 97e309513..2021abc2a 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -290,7 +290,7 @@ func TestEventsFilterImageName(t *testing.T) { since := daemonTime(t).Unix() defer deleteAllContainers() - out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_1", "-d", "busybox", "true")) + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_1", "-d", "busybox:latest", "true")) if err != nil { t.Fatal(out, err) } @@ -302,30 +302,30 @@ func TestEventsFilterImageName(t *testing.T) { } container2 := strings.TrimSpace(out) - for _, s := range []string{"busybox", "busybox:latest"} { - eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("image=%s", s)) - out, _, err := runCommandWithOutput(eventsCmd) - if err != nil { - t.Fatalf("Failed to get events, error: %s(%s)", err, out) - } - events := strings.Split(out, "\n") - events = events[:len(events)-1] - if len(events) == 0 { - t.Fatalf("Expected events but found none for the image busybox:latest") - } - count1 := 0 - count2 := 0 - for _, e := range events { - if strings.Contains(e, container1) { - count1++ - } else if strings.Contains(e, container2) { - count2++ - } - } - if count1 == 0 || count2 == 0 { - t.Fatalf("Expected events from each container but got %d from %s and %d from %s", count1, container1, count2, container2) + s := "busybox" + eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("image=%s", s)) + out, _, err = runCommandWithOutput(eventsCmd) + if err != nil { + t.Fatalf("Failed to get events, error: %s(%s)", err, out) + } + events := strings.Split(out, "\n") + events = events[:len(events)-1] + if len(events) == 0 { + t.Fatalf("Expected events but found none for the image busybox:latest") + } + count1 := 0 + count2 := 0 + + for _, e := range events { + if strings.Contains(e, container1) { + count1++ + } else if strings.Contains(e, container2) { + count2++ } } + if count1 == 0 || count2 == 0 { + t.Fatalf("Expected events from each container but got %d from %s and %d from %s", count1, container1, count2, container2) + } logDone("events - filters using image") } @@ -467,7 +467,7 @@ func TestEventsStreaming(t *testing.T) { } }() - runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox:latest", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { t.Fatal(out, err) diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index f97da5be3..9ac347410 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -594,6 +594,21 @@ func TestPsRightTagName(t *testing.T) { } else { id2 = strings.TrimSpace(string(out)) } + + var imageID string + if out, err := exec.Command(dockerBinary, "inspect", "-f", "{{.Id}}", "busybox").CombinedOutput(); err != nil { + t.Fatalf("failed to get the image ID of busybox: %s, %v", out, err) + } else { + imageID = strings.TrimSpace(string(out)) + } + + var id3 string + if out, err := exec.Command(dockerBinary, "run", "-d", imageID, "top").CombinedOutput(); err != nil { + t.Fatalf("Failed to run container: %s, out: %q", err, out) + } else { + id3 = strings.TrimSpace(string(out)) + } + out, err := exec.Command(dockerBinary, "ps", "--no-trunc").CombinedOutput() if err != nil { t.Fatalf("Failed to run 'ps': %s, out: %q", err, out) @@ -601,22 +616,26 @@ func TestPsRightTagName(t *testing.T) { lines := strings.Split(strings.TrimSpace(string(out)), "\n") // skip header lines = lines[1:] - if len(lines) != 2 { - t.Fatalf("There should be 2 running container, got %d", len(lines)) + if len(lines) != 3 { + t.Fatalf("There should be 3 running container, got %d", len(lines)) } for _, line := range lines { f := strings.Fields(line) switch f[0] { case id1: - if f[1] != "busybox:latest" { + if f[1] != "busybox" { t.Fatalf("Expected %s tag for id %s, got %s", "busybox", id1, f[1]) } case id2: if f[1] != tag { - t.Fatalf("Expected %s tag for id %s, got %s", tag, id1, f[1]) + t.Fatalf("Expected %s tag for id %s, got %s", tag, id2, f[1]) + } + case id3: + if f[1] != imageID { + t.Fatalf("Expected %s imageID for id %s, got %s", tag, id3, f[1]) } default: - t.Fatalf("Unexpected id %s, expected %s and %s", f[0], id1, id2) + t.Fatalf("Unexpected id %s, expected %s and %s and %s", f[0], id1, id2, id3) } } logDone("ps - right tags for containers") From 49c4de4aebe0ecc3005e56a9ab06b5e43f5b312c Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Fri, 10 Apr 2015 01:52:55 +0800 Subject: [PATCH 333/999] Remove Job from rename A part of ISSUE#12151-Remove engine.Job mechanism Signed-off-by: Hu Keping --- api/server/server.go | 8 ++++---- daemon/daemon.go | 1 - daemon/rename.go | 10 +++------- 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index e59aec5a7..c3e2fd0a6 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -859,10 +859,10 @@ func postContainerRename(eng *engine.Engine, version version.Version, w http.Res return fmt.Errorf("Missing parameter") } - newName := r.URL.Query().Get("name") - job := eng.Job("container_rename", vars["name"], newName) - job.Setenv("t", r.Form.Get("t")) - if err := job.Run(); err != nil { + d := getDaemon(eng) + name := vars["name"] + newName := r.Form.Get("name") + if err := d.ContainerRename(name, newName); err != nil { return err } w.WriteHeader(http.StatusNoContent) diff --git a/daemon/daemon.go b/daemon/daemon.go index 5aa6250a8..44a59eac3 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -119,7 +119,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "commit": daemon.ContainerCommit, "container_copy": daemon.ContainerCopy, - "container_rename": daemon.ContainerRename, "container_inspect": daemon.ContainerInspect, "container_stats": daemon.ContainerStats, "create": daemon.ContainerCreate, diff --git a/daemon/rename.go b/daemon/rename.go index 66e7ac080..72e14a1af 100644 --- a/daemon/rename.go +++ b/daemon/rename.go @@ -2,16 +2,12 @@ package daemon import ( "fmt" - - "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerRename(job *engine.Job) error { - if len(job.Args) != 2 { - return fmt.Errorf("usage: %s OLD_NAME NEW_NAME", job.Name) +func (daemon *Daemon) ContainerRename(oldName, newName string) error { + if oldName == "" || newName == "" { + return fmt.Errorf("usage: docker rename OLD_NAME NEW_NAME") } - oldName := job.Args[0] - newName := job.Args[1] container, err := daemon.Get(oldName) if err != nil { From ab11d605556749e56e9dc5b4b071375765ad60bf Mon Sep 17 00:00:00 2001 From: Yan Feng Date: Thu, 9 Apr 2015 11:46:09 -0400 Subject: [PATCH 334/999] Fix a typo in docker/daemon/execdriver/native/exec.go Signed-off-by: Yan Feng --- daemon/execdriver/native/exec.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/execdriver/native/exec.go b/daemon/execdriver/native/exec.go index af6dcd2ad..2edd3313b 100644 --- a/daemon/execdriver/native/exec.go +++ b/daemon/execdriver/native/exec.go @@ -14,7 +14,7 @@ import ( "github.com/docker/libcontainer/utils" ) -// TODO(vishh): Add support for running in priviledged mode and running as a different user. +// TODO(vishh): Add support for running in privileged mode and running as a different user. func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { active := d.activeContainers[c.ID] if active == nil { From a264e1e83debfaefcb99f5554b8eda9c088a09d1 Mon Sep 17 00:00:00 2001 From: Brendan Dixon Date: Wed, 8 Apr 2015 16:35:37 -0700 Subject: [PATCH 335/999] Corrected int16 overflow and buffer sizes Signed-off-by: Brendan Dixon --- pkg/term/winconsole/console_windows.go | 51 +++++++++++++------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/pkg/term/winconsole/console_windows.go b/pkg/term/winconsole/console_windows.go index bebf6d7c1..e6e3f9bcb 100644 --- a/pkg/term/winconsole/console_windows.go +++ b/pkg/term/winconsole/console_windows.go @@ -410,25 +410,25 @@ func getNumberOfChars(fromCoord COORD, toCoord COORD, screenSize COORD) uint32 { var buffer []CHAR_INFO -func clearDisplayRect(handle uintptr, fillChar rune, attributes WORD, fromCoord COORD, toCoord COORD, windowSize COORD) (uint32, error) { +func clearDisplayRect(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) { var writeRegion SMALL_RECT - writeRegion.Top = fromCoord.Y writeRegion.Left = fromCoord.X + writeRegion.Top = fromCoord.Y writeRegion.Right = toCoord.X writeRegion.Bottom = toCoord.Y // allocate and initialize buffer width := toCoord.X - fromCoord.X + 1 height := toCoord.Y - fromCoord.Y + 1 - size := width * height + size := uint32(width) * uint32(height) if size > 0 { - for i := 0; i < int(size); i++ { - buffer[i].UnicodeChar = WCHAR(fillChar) - buffer[i].Attributes = attributes + buffer := make([]CHAR_INFO, size) + for i := range buffer { + buffer[i] = CHAR_INFO{WCHAR(' '), attributes} } // Write to buffer - r, err := writeConsoleOutput(handle, buffer[:size], windowSize, COORD{X: 0, Y: 0}, &writeRegion) + r, err := writeConsoleOutput(handle, buffer, COORD{X: width, Y: height}, COORD{X: 0, Y: 0}, &writeRegion) if !r { if err != nil { return 0, err @@ -439,18 +439,18 @@ func clearDisplayRect(handle uintptr, fillChar rune, attributes WORD, fromCoord return uint32(size), nil } -func clearDisplayRange(handle uintptr, fillChar rune, attributes WORD, fromCoord COORD, toCoord COORD, windowSize COORD) (uint32, error) { +func clearDisplayRange(handle uintptr, attributes WORD, fromCoord COORD, toCoord COORD) (uint32, error) { nw := uint32(0) // start and end on same line if fromCoord.Y == toCoord.Y { - return clearDisplayRect(handle, fillChar, attributes, fromCoord, toCoord, windowSize) + return clearDisplayRect(handle, attributes, fromCoord, toCoord) } // TODO(azlinux): if full screen, optimize // spans more than one line if fromCoord.Y < toCoord.Y { // from start position till end of line for first line - n, err := clearDisplayRect(handle, fillChar, attributes, fromCoord, COORD{X: windowSize.X - 1, Y: fromCoord.Y}, windowSize) + n, err := clearDisplayRect(handle, attributes, fromCoord, COORD{X: toCoord.X, Y: fromCoord.Y}) if err != nil { return nw, err } @@ -458,14 +458,14 @@ func clearDisplayRange(handle uintptr, fillChar rune, attributes WORD, fromCoord // lines between linesBetween := toCoord.Y - fromCoord.Y - 1 if linesBetween > 0 { - n, err = clearDisplayRect(handle, fillChar, attributes, COORD{X: 0, Y: fromCoord.Y + 1}, COORD{X: windowSize.X - 1, Y: toCoord.Y - 1}, windowSize) + n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: fromCoord.Y + 1}, COORD{X: toCoord.X, Y: toCoord.Y - 1}) if err != nil { return nw, err } nw += n } // lines at end - n, err = clearDisplayRect(handle, fillChar, attributes, COORD{X: 0, Y: toCoord.Y}, toCoord, windowSize) + n, err = clearDisplayRect(handle, attributes, COORD{X: 0, Y: toCoord.Y}, toCoord) if err != nil { return nw, err } @@ -715,9 +715,9 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) switch value { case 0: start = screenBufferInfo.CursorPosition - // end of the screen - end.X = screenBufferInfo.MaximumWindowSize.X - 1 - end.Y = screenBufferInfo.MaximumWindowSize.Y - 1 + // end of the buffer + end.X = screenBufferInfo.Size.X - 1 + end.Y = screenBufferInfo.Size.Y - 1 // cursor cursor = screenBufferInfo.CursorPosition case 1: @@ -733,20 +733,21 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) // start of the screen start.X = 0 start.Y = 0 - // end of the screen - end.X = screenBufferInfo.MaximumWindowSize.X - 1 - end.Y = screenBufferInfo.MaximumWindowSize.Y - 1 + // end of the buffer + end.X = screenBufferInfo.Size.X - 1 + end.Y = screenBufferInfo.Size.Y - 1 // cursor cursor.X = 0 cursor.Y = 0 } - if _, err := clearDisplayRange(uintptr(handle), ' ', term.screenBufferInfo.Attributes, start, end, screenBufferInfo.MaximumWindowSize); err != nil { + if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil { return n, err } // remember the the cursor position is 1 based if err := setConsoleCursorPosition(handle, false, int16(cursor.X), int16(cursor.Y)); err != nil { return n, err } + case "K": // [K // Clears all characters from the cursor position to the end of the line (including the character at the cursor position). @@ -766,7 +767,7 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) // start is where cursor is start = screenBufferInfo.CursorPosition // end of line - end.X = screenBufferInfo.MaximumWindowSize.X - 1 + end.X = screenBufferInfo.Size.X - 1 end.Y = screenBufferInfo.CursorPosition.Y // cursor remains the same cursor = screenBufferInfo.CursorPosition @@ -782,15 +783,15 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) case 2: // start of the line start.X = 0 - start.Y = screenBufferInfo.MaximumWindowSize.Y - 1 + start.Y = screenBufferInfo.CursorPosition.Y - 1 // end of the line - end.X = screenBufferInfo.MaximumWindowSize.X - 1 - end.Y = screenBufferInfo.MaximumWindowSize.Y - 1 + end.X = screenBufferInfo.Size.X - 1 + end.Y = screenBufferInfo.CursorPosition.Y - 1 // cursor cursor.X = 0 - cursor.Y = screenBufferInfo.MaximumWindowSize.Y - 1 + cursor.Y = screenBufferInfo.CursorPosition.Y - 1 } - if _, err := clearDisplayRange(uintptr(handle), ' ', term.screenBufferInfo.Attributes, start, end, screenBufferInfo.MaximumWindowSize); err != nil { + if _, err := clearDisplayRange(uintptr(handle), term.screenBufferInfo.Attributes, start, end); err != nil { return n, err } // remember the the cursor position is 1 based From db13cb7f98a30169311cf2e4a80867a94f49d377 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 8 Apr 2015 23:38:53 +0200 Subject: [PATCH 336/999] Remove job from rm Signed-off-by: Antonio Murdaca --- api/server/server.go | 19 +++++++--- api/server/server_unit_test.go | 23 ------------- daemon/daemon.go | 1 - daemon/delete.go | 21 +++++------- integration/server_test.go | 63 ---------------------------------- 5 files changed, 22 insertions(+), 105 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 6c20e1d73..38dbfc1e1 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -858,16 +858,25 @@ func deleteContainers(eng *engine.Engine, version version.Version, w http.Respon if vars == nil { return fmt.Errorf("Missing parameter") } - job := eng.Job("rm", vars["name"]) - job.Setenv("forceRemove", r.Form.Get("force")) + name := vars["name"] + if name == "" { + return fmt.Errorf("Container name cannot be empty") + } - job.Setenv("removeVolume", r.Form.Get("v")) - job.Setenv("removeLink", r.Form.Get("link")) - if err := job.Run(); err != nil { + d := getDaemon(eng) + config := &daemon.ContainerRmConfig{ + ForceRemove: toBool(r.Form.Get("force")), + RemoveVolume: toBool(r.Form.Get("v")), + RemoveLink: toBool(r.Form.Get("link")), + } + + if err := d.ContainerRm(name, config); err != nil { return err } + w.WriteHeader(http.StatusNoContent) + return nil } diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index 607afbab4..88dadab6f 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -286,29 +286,6 @@ func TestGetImagesByName(t *testing.T) { } } -func TestDeleteContainers(t *testing.T) { - eng := engine.New() - name := "foo" - var called bool - eng.Register("rm", func(job *engine.Job) error { - called = true - if len(job.Args) == 0 { - t.Fatalf("Job arguments is empty") - } - if job.Args[0] != name { - t.Fatalf("name != '%s': %#v", name, job.Args[0]) - } - return nil - }) - r := serveRequest("DELETE", "/containers/"+name, nil, eng, t) - if !called { - t.Fatalf("handler was not called") - } - if r.Code != http.StatusNoContent { - t.Fatalf("Got status %d, expected %d", r.Code, http.StatusNoContent) - } -} - func serveRequest(method, target string, body io.Reader, eng *engine.Engine, t *testing.T) *httptest.ResponseRecorder { return serveRequestUsingVersion(method, target, api.APIVERSION, body, eng, t) } diff --git a/daemon/daemon.go b/daemon/daemon.go index 44a59eac3..5493d2393 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -122,7 +122,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "container_inspect": daemon.ContainerInspect, "container_stats": daemon.ContainerStats, "create": daemon.ContainerCreate, - "rm": daemon.ContainerRm, "export": daemon.ContainerExport, "info": daemon.CmdInfo, "kill": daemon.ContainerKill, diff --git a/daemon/delete.go b/daemon/delete.go index eb93973aa..d398741d7 100644 --- a/daemon/delete.go +++ b/daemon/delete.go @@ -6,24 +6,19 @@ import ( "path" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerRm(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) - } - name := job.Args[0] - removeVolume := job.GetenvBool("removeVolume") - removeLink := job.GetenvBool("removeLink") - forceRemove := job.GetenvBool("forceRemove") +type ContainerRmConfig struct { + ForceRemove, RemoveVolume, RemoveLink bool +} +func (daemon *Daemon) ContainerRm(name string, config *ContainerRmConfig) error { container, err := daemon.Get(name) if err != nil { return err } - if removeLink { + if config.RemoveLink { name, err := GetFullContainerName(name) if err != nil { return err @@ -55,7 +50,7 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) error { // if stats are currently getting collected. daemon.statsCollector.stopCollection(container) if container.IsRunning() { - if forceRemove { + if config.ForceRemove { if err := container.Kill(); err != nil { return fmt.Errorf("Could not kill running container, cannot remove - %v", err) } @@ -64,7 +59,7 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) error { } } - if forceRemove { + if config.ForceRemove { if err := daemon.ForceRm(container); err != nil { logrus.Errorf("Cannot destroy container %s: %v", name, err) } @@ -74,7 +69,7 @@ func (daemon *Daemon) ContainerRm(job *engine.Job) error { } } container.LogEvent("destroy") - if removeVolume { + if config.RemoveVolume { daemon.DeleteVolumes(container.VolumePaths()) } } diff --git a/integration/server_test.go b/integration/server_test.go index 9cdd3d774..b2c4dd80a 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -157,69 +157,6 @@ func TestRestartKillWait(t *testing.T) { }) } -func TestCreateStartRestartStopStartKillRm(t *testing.T) { - eng := NewTestEngine(t) - runtime := mkDaemonFromEngine(eng, t) - defer runtime.Nuke() - - config, hostConfig, _, err := parseRun([]string{"-i", unitTestImageID, "/bin/cat"}) - if err != nil { - t.Fatal(err) - } - - id := createTestContainer(eng, config, t) - containers, err := runtime.Containers(&daemon.ContainersConfig{All: true}) - - if len(containers) != 1 { - t.Errorf("Expected 1 container, %v found", len(containers)) - } - - 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("restart", id) - job.SetenvInt("t", 2) - if err := job.Run(); err != nil { - t.Fatal(err) - } - - job = eng.Job("stop", id) - job.SetenvInt("t", 2) - if err := job.Run(); err != nil { - t.Fatal(err) - } - - job = eng.Job("start", id) - if err := job.ImportEnv(hostConfig); err != nil { - t.Fatal(err) - } - if err := job.Run(); err != nil { - t.Fatal(err) - } - - if err := eng.Job("kill", id).Run(); err != nil { - t.Fatal(err) - } - - // FIXME: this failed once with a race condition ("Unable to remove filesystem for xxx: directory not empty") - job = eng.Job("rm", id) - job.SetenvBool("removeVolume", true) - if err := job.Run(); err != nil { - t.Fatal(err) - } - - containers, err = runtime.Containers(&daemon.ContainersConfig{All: true}) - - if len(containers) != 0 { - t.Errorf("Expected 0 container, %v found", len(containers)) - } -} - func TestRunWithTooLowMemoryLimit(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From 0fec3e19dbff15b3cd8fa3693f694e34d8dcc5a9 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Thu, 9 Apr 2015 09:58:44 -0700 Subject: [PATCH 337/999] Fix regressions in attach * Wrong bool parsing * Attach always all streams Were introduced in #12120 Signed-off-by: Alexander Morozov --- api/server/server.go | 19 ++++++++++++++++--- daemon/attach.go | 16 +++++++++------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 6c20e1d73..ed7dcfa5f 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1011,10 +1011,23 @@ func postContainersAttach(eng *engine.Engine, version version.Version, w http.Re } else { errStream = outStream } - logs := r.Form.Get("logs") != "" - stream := r.Form.Get("stream") != "" + logs := toBool(r.Form.Get("logs")) + stream := toBool(r.Form.Get("stream")) - if err := cont.AttachWithLogs(inStream, outStream, errStream, logs, stream); err != nil { + var stdin io.ReadCloser + var stdout, stderr io.Writer + + if toBool(r.Form.Get("stdin")) { + stdin = inStream + } + if toBool(r.Form.Get("stdout")) { + stdout = outStream + } + if toBool(r.Form.Get("stderr")) { + stderr = errStream + } + + if err := cont.AttachWithLogs(stdin, stdout, stderr, logs, stream); err != nil { fmt.Fprintf(outStream, "Error attaching: %s\n", err) } return nil diff --git a/daemon/attach.go b/daemon/attach.go index 72f38752e..f95de41d5 100644 --- a/daemon/attach.go +++ b/daemon/attach.go @@ -61,13 +61,15 @@ func (c *Container) AttachWithLogs(stdin io.ReadCloser, stdout, stderr io.Writer //stream if stream { var stdinPipe io.ReadCloser - r, w := io.Pipe() - go func() { - defer w.Close() - defer logrus.Debugf("Closing buffered stdin pipe") - io.Copy(w, stdin) - }() - stdinPipe = r + if stdin != nil { + r, w := io.Pipe() + go func() { + defer w.Close() + defer logrus.Debugf("Closing buffered stdin pipe") + io.Copy(w, stdin) + }() + stdinPipe = r + } <-c.Attach(stdinPipe, stdout, stderr) // If we are in stdinonce mode, wait for the process to end // otherwise, simply return From e4afc379dcee66475f3becb34cd2675f3ee9279c Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 9 Apr 2015 13:59:50 +0200 Subject: [PATCH 338/999] Remove job from rmi Signed-off-by: Antonio Murdaca --- api/client/build.go | 2 +- api/server/server.go | 16 +++++++++++----- daemon/daemon.go | 2 -- daemon/image_delete.go | 27 +++++++++------------------ 4 files changed, 21 insertions(+), 26 deletions(-) diff --git a/api/client/build.go b/api/client/build.go index 53601763d..f1bceb4a1 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -155,7 +155,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // And canonicalize dockerfile name to a platform-independent one *dockerfileName, err = archive.CanonicalTarNameForPath(*dockerfileName) if err != nil { - return fmt.Errorf("Cannot canonicalize dockerfile path %s: %v", dockerfileName, err) + return fmt.Errorf("Cannot canonicalize dockerfile path %s: %v", *dockerfileName, err) } if _, err = os.Lstat(filename); os.IsNotExist(err) { diff --git a/api/server/server.go b/api/server/server.go index 6c20e1d73..5b5ca1590 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -878,12 +878,18 @@ func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWr if vars == nil { return fmt.Errorf("Missing parameter") } - 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() + d := getDaemon(eng) + name := vars["name"] + force := toBool(r.Form.Get("force")) + noprune := toBool(r.Form.Get("noprune")) + + list, err := d.ImageDelete(name, force, noprune) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, list) } func postContainersStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/daemon/daemon.go b/daemon/daemon.go index 44a59eac3..fbe6a2042 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -115,7 +115,6 @@ type Daemon struct { // Install installs daemon capabilities to eng. func (daemon *Daemon) Install(eng *engine.Engine) error { - // FIXME: remove ImageDelete's dependency on Daemon, then move to graph/ for name, method := range map[string]engine.Handler{ "commit": daemon.ContainerCommit, "container_copy": daemon.ContainerCopy, @@ -135,7 +134,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "top": daemon.ContainerTop, "unpause": daemon.ContainerUnpause, "wait": daemon.ContainerWait, - "image_delete": daemon.ImageDelete, // FIXME: see above "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, "execResize": daemon.ContainerExecResize, diff --git a/daemon/image_delete.go b/daemon/image_delete.go index 6323d323e..a44eb1bfa 100644 --- a/daemon/image_delete.go +++ b/daemon/image_delete.go @@ -1,12 +1,10 @@ package daemon import ( - "encoding/json" "fmt" "strings" "github.com/docker/docker/api/types" - "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers" @@ -14,26 +12,20 @@ import ( "github.com/docker/docker/utils" ) -func (daemon *Daemon) ImageDelete(job *engine.Job) error { - if n := len(job.Args); n != 1 { - return fmt.Errorf("Usage: %s IMAGE", job.Name) - } - +// FIXME: remove ImageDelete's dependency on Daemon, then move to graph/ +func (daemon *Daemon) ImageDelete(name string, force, noprune bool) ([]types.ImageDelete, error) { list := []types.ImageDelete{} - if err := daemon.DeleteImage(job.Eng, job.Args[0], &list, true, job.GetenvBool("force"), job.GetenvBool("noprune")); err != nil { - return err + if err := daemon.imgDeleteHelper(name, &list, true, force, noprune); err != nil { + return nil, err } if len(list) == 0 { - return fmt.Errorf("Conflict, %s wasn't deleted", job.Args[0]) + return nil, fmt.Errorf("Conflict, %s wasn't deleted", name) } - if err := json.NewEncoder(job.Stdout).Encode(list); err != nil { - return err - } - return nil + + return list, nil } -// FIXME: make this private and use the job instead -func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, list *[]types.ImageDelete, first, force, noprune bool) error { +func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, first, force, noprune bool) error { var ( repoName, tag string tags = []string{} @@ -124,9 +116,8 @@ func (daemon *Daemon) DeleteImage(eng *engine.Engine, name string, list *[]types Deleted: img.ID, }) daemon.EventsService.Log("delete", img.ID, "") - eng.Job("log", "delete", img.ID, "").Run() if img.Parent != "" && !noprune { - err := daemon.DeleteImage(eng, img.Parent, list, false, force, noprune) + err := daemon.imgDeleteHelper(img.Parent, list, false, force, noprune) if first { return err } From 42d47c31367d288c7a9ae3acff635e995254469c Mon Sep 17 00:00:00 2001 From: Todd Whiteman Date: Wed, 8 Apr 2015 16:32:45 -0700 Subject: [PATCH 339/999] fix #12188 integration-cli: tests using "sleep" can timeout too early - change to "top" instead Signed-off-by: Todd Whiteman --- integration-cli/docker_cli_exec_test.go | 8 +++--- integration-cli/docker_cli_kill_test.go | 8 +++--- integration-cli/docker_cli_links_test.go | 18 ++++++------- integration-cli/docker_cli_pause_test.go | 16 ++--------- integration-cli/docker_cli_ps_test.go | 4 +-- integration-cli/docker_cli_top_test.go | 34 ++++++++++++------------ 6 files changed, 38 insertions(+), 50 deletions(-) diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index f06e20a84..9fcee32a7 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -19,7 +19,7 @@ import ( func TestExec(t *testing.T) { defer deleteAllContainers() - runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && sleep 100") + runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil { t.Fatal(out, err) } @@ -82,7 +82,7 @@ func TestExecInteractiveStdinClose(t *testing.T) { func TestExecInteractive(t *testing.T) { defer deleteAllContainers() - runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && sleep 100") + runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil { t.Fatal(out, err) } @@ -500,12 +500,12 @@ func TestLinksPingLinkedContainersOnRename(t *testing.T) { defer deleteAllContainers() var out string - out, _, _ = dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "sleep", "10") + out, _, _ = dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") idA := strings.TrimSpace(out) if idA == "" { t.Fatal(out, "id should not be nil") } - out, _, _ = dockerCmd(t, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "sleep", "10") + out, _, _ = dockerCmd(t, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") idB := strings.TrimSpace(out) if idB == "" { t.Fatal(out, "id should not be nil") diff --git a/integration-cli/docker_cli_kill_test.go b/integration-cli/docker_cli_kill_test.go index 7a0d000a0..cd86c0c56 100644 --- a/integration-cli/docker_cli_kill_test.go +++ b/integration-cli/docker_cli_kill_test.go @@ -7,7 +7,7 @@ import ( ) func TestKillContainer(t *testing.T) { - runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10") + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { t.Fatal(out, err) @@ -37,11 +37,11 @@ func TestKillContainer(t *testing.T) { deleteContainer(cleanedContainerID) - logDone("kill - kill container running sleep 10") + logDone("kill - kill container running top") } func TestKillDifferentUserContainer(t *testing.T) { - runCmd := exec.Command(dockerBinary, "run", "-u", "daemon", "-d", "busybox", "sh", "-c", "sleep 10") + runCmd := exec.Command(dockerBinary, "run", "-u", "daemon", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { t.Fatal(out, err) @@ -71,5 +71,5 @@ func TestKillDifferentUserContainer(t *testing.T) { deleteContainer(cleanedContainerID) - logDone("kill - kill container running sleep 10 from a different user") + logDone("kill - kill container running top from a different user") } diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index 80bdfc955..04718c23f 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -110,9 +110,9 @@ func TestLinksPingLinkedContainers(t *testing.T) { func TestLinksPingLinkedContainersAfterRename(t *testing.T) { defer deleteAllContainers() - out, _, _ := dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "sleep", "10") + out, _, _ := dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") idA := strings.TrimSpace(out) - out, _, _ = dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "sleep", "10") + out, _, _ = dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") idB := strings.TrimSpace(out) dockerCmd(t, "rename", "container1", "container_new") dockerCmd(t, "run", "--rm", "--link", "container_new:alias1", "--link", "container2:alias2", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1") @@ -126,8 +126,8 @@ func TestLinksIpTablesRulesWhenLinkAndUnlink(t *testing.T) { testRequires(t, SameHostDaemon) defer deleteAllContainers() - dockerCmd(t, "run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "sleep", "10") - dockerCmd(t, "run", "-d", "--name", "parent", "--link", "child:http", "busybox", "sleep", "10") + dockerCmd(t, "run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "top") + dockerCmd(t, "run", "-d", "--name", "parent", "--link", "child:http", "busybox", "top") childIP := findContainerIP(t, "child") parentIP := findContainerIP(t, "parent") @@ -155,9 +155,9 @@ func TestLinksInspectLinksStarted(t *testing.T) { result []string ) defer deleteAllContainers() - dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "sleep", "10") - dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "sleep", "10") - dockerCmd(t, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "sleep", "10") + dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") + dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") + dockerCmd(t, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "top") links, err := inspectFieldJSON("testinspectlink", "HostConfig.Links") if err != nil { t.Fatal(err) @@ -184,8 +184,8 @@ func TestLinksInspectLinksStopped(t *testing.T) { result []string ) defer deleteAllContainers() - dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "sleep", "10") - dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "sleep", "10") + dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") + dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") dockerCmd(t, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "true") links, err := inspectFieldJSON("testinspectlink", "HostConfig.Links") if err != nil { diff --git a/integration-cli/docker_cli_pause_test.go b/integration-cli/docker_cli_pause_test.go index 2ba8cb0ae..41147b206 100644 --- a/integration-cli/docker_cli_pause_test.go +++ b/integration-cli/docker_cli_pause_test.go @@ -14,7 +14,7 @@ func TestPause(t *testing.T) { name := "testeventpause" out, _, _ := dockerCmd(t, "images", "-q") image := strings.Split(out, "\n")[0] - dockerCmd(t, "run", "-d", "--name", name, image, "sleep", "2") + dockerCmd(t, "run", "-d", "--name", name, image, "top") dockerCmd(t, "pause", name) pausedContainers, err := getSliceOfPausedContainers() @@ -44,11 +44,6 @@ func TestPause(t *testing.T) { t.Fatalf("event should be unpause, not %#v", unpauseEvent) } - waitCmd := exec.Command(dockerBinary, "wait", name) - if waitOut, _, err := runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err) - } - logDone("pause - pause/unpause is logged") } @@ -63,7 +58,7 @@ func TestPauseMultipleContainers(t *testing.T) { out, _, _ := dockerCmd(t, "images", "-q") image := strings.Split(out, "\n")[0] for _, name := range containers { - dockerCmd(t, "run", "-d", "--name", name, image, "sleep", "2") + dockerCmd(t, "run", "-d", "--name", name, image, "top") } dockerCmd(t, append([]string{"pause"}, containers...)...) pausedContainers, err := getSliceOfPausedContainers() @@ -101,12 +96,5 @@ func TestPauseMultipleContainers(t *testing.T) { } } - for _, name := range containers { - waitCmd := exec.Command(dockerBinary, "wait", name) - if waitOut, _, err := runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err) - } - } - logDone("pause - multi pause/unpause is logged") } diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index 9ac347410..deb426fce 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -377,7 +377,7 @@ func TestPsListContainersFilterID(t *testing.T) { firstID := strings.TrimSpace(out) // start another container - runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 360") + runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top") if out, _, err = runCommandWithOutput(runCmd); err != nil { t.Fatal(out, err) } @@ -407,7 +407,7 @@ func TestPsListContainersFilterName(t *testing.T) { firstID := strings.TrimSpace(out) // start another container - runCmd = exec.Command(dockerBinary, "run", "-d", "--name=b_name_to_match", "busybox", "sh", "-c", "sleep 360") + runCmd = exec.Command(dockerBinary, "run", "-d", "--name=b_name_to_match", "busybox", "top") if out, _, err = runCommandWithOutput(runCmd); err != nil { t.Fatal(out, err) } diff --git a/integration-cli/docker_cli_top_test.go b/integration-cli/docker_cli_top_test.go index cd996af76..b5dca0be9 100644 --- a/integration-cli/docker_cli_top_test.go +++ b/integration-cli/docker_cli_top_test.go @@ -7,7 +7,7 @@ import ( ) func TestTopMultipleArgs(t *testing.T) { - runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "sleep", "20") + runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { t.Fatalf("failed to start the container: %s, %v", out, err) @@ -30,7 +30,7 @@ func TestTopMultipleArgs(t *testing.T) { } func TestTopNonPrivileged(t *testing.T) { - runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "sleep", "20") + runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { t.Fatalf("failed to start the container: %s, %v", out, err) @@ -57,19 +57,19 @@ func TestTopNonPrivileged(t *testing.T) { deleteContainer(cleanedContainerID) - if !strings.Contains(out1, "sleep 20") && !strings.Contains(out2, "sleep 20") { - t.Fatal("top should've listed `sleep 20` in the process list, but failed twice") - } else if !strings.Contains(out1, "sleep 20") { - t.Fatal("top should've listed `sleep 20` in the process list, but failed the first time") - } else if !strings.Contains(out2, "sleep 20") { - t.Fatal("top should've listed `sleep 20` in the process list, but failed the second itime") + if !strings.Contains(out1, "top") && !strings.Contains(out2, "top") { + t.Fatal("top should've listed `top` in the process list, but failed twice") + } else if !strings.Contains(out1, "top") { + t.Fatal("top should've listed `top` in the process list, but failed the first time") + } else if !strings.Contains(out2, "top") { + t.Fatal("top should've listed `top` in the process list, but failed the second itime") } - logDone("top - sleep process should be listed in non privileged mode") + logDone("top - top process should be listed in non privileged mode") } func TestTopPrivileged(t *testing.T) { - runCmd := exec.Command(dockerBinary, "run", "--privileged", "-i", "-d", "busybox", "sleep", "20") + runCmd := exec.Command(dockerBinary, "run", "--privileged", "-i", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { t.Fatalf("failed to start the container: %s, %v", out, err) @@ -96,13 +96,13 @@ func TestTopPrivileged(t *testing.T) { deleteContainer(cleanedContainerID) - if !strings.Contains(out1, "sleep 20") && !strings.Contains(out2, "sleep 20") { - t.Fatal("top should've listed `sleep 20` in the process list, but failed twice") - } else if !strings.Contains(out1, "sleep 20") { - t.Fatal("top should've listed `sleep 20` in the process list, but failed the first time") - } else if !strings.Contains(out2, "sleep 20") { - t.Fatal("top should've listed `sleep 20` in the process list, but failed the second itime") + if !strings.Contains(out1, "top") && !strings.Contains(out2, "top") { + t.Fatal("top should've listed `top` in the process list, but failed twice") + } else if !strings.Contains(out1, "top") { + t.Fatal("top should've listed `top` in the process list, but failed the first time") + } else if !strings.Contains(out2, "top") { + t.Fatal("top should've listed `top` in the process list, but failed the second itime") } - logDone("top - sleep process should be listed in privileged mode") + logDone("top - top process should be listed in privileged mode") } From fa961ce0463ebd738da875c3a6da8171373d7723 Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Thu, 9 Apr 2015 13:55:26 -0400 Subject: [PATCH 340/999] Wrap installer in a function This will assure that the install script will not begin executing until after it has been downloaded should it be utilized in a 'curl | bash' workflow. Signed-off-by: Eric Windisch --- hack/install.sh | 397 ++++++++++++++++++++++++------------------------ 1 file changed, 200 insertions(+), 197 deletions(-) diff --git a/hack/install.sh b/hack/install.sh index fcea11d01..305d8fb0f 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -20,213 +20,216 @@ command_exists() { command -v "$@" > /dev/null 2>&1 } -case "$(uname -m)" in - *64) - ;; - *) - echo >&2 'Error: you are not using a 64bit platform.' - echo >&2 'Docker currently only supports 64bit platforms.' - exit 1 - ;; -esac +do_docker_install() { + case "$(uname -m)" in + *64) + ;; + *) + echo >&2 'Error: you are not using a 64bit platform.' + echo >&2 'Docker currently only supports 64bit platforms.' + exit 1 + ;; + esac -if command_exists docker || command_exists lxc-docker; then - echo >&2 'Warning: "docker" or "lxc-docker" command appears to already exist.' - echo >&2 'Please ensure that you do not already have docker installed.' - echo >&2 'You may press Ctrl+C now to abort this process and rectify this situation.' - ( set -x; sleep 20 ) -fi - -user="$(id -un 2>/dev/null || true)" - -sh_c='sh -c' -if [ "$user" != 'root' ]; then - if command_exists sudo; then - sh_c='sudo -E sh -c' - elif command_exists su; then - sh_c='su -c' - else - echo >&2 'Error: this installer needs the ability to run commands as root.' - echo >&2 'We are unable to find either "sudo" or "su" available to make this happen.' - exit 1 + if command_exists docker || command_exists lxc-docker; then + echo >&2 'Warning: "docker" or "lxc-docker" command appears to already exist.' + echo >&2 'Please ensure that you do not already have docker installed.' + echo >&2 'You may press Ctrl+C now to abort this process and rectify this situation.' + ( set -x; sleep 20 ) fi -fi -curl='' -if command_exists curl; then - curl='curl -sSL' -elif command_exists wget; then - curl='wget -qO-' -elif command_exists busybox && busybox --list-modules | grep -q wget; then - curl='busybox wget -qO-' -fi + user="$(id -un 2>/dev/null || true)" -# perform some very rudimentary platform detection -lsb_dist='' -if command_exists lsb_release; then - lsb_dist="$(lsb_release -si)" -fi -if [ -z "$lsb_dist" ] && [ -r /etc/lsb-release ]; then - lsb_dist="$(. /etc/lsb-release && echo "$DISTRIB_ID")" -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 -if [ -z "$lsb_dist" ] && [ -r /etc/os-release ]; then - lsb_dist="$(. /etc/os-release && echo "$ID")" -fi - -lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')" -case "$lsb_dist" in - amzn|fedora|centos) - if [ "$lsb_dist" = 'amzn' ]; then - ( - set -x - $sh_c 'sleep 3; yum -y -q install docker' - ) + sh_c='sh -c' + if [ "$user" != 'root' ]; then + if command_exists sudo; then + sh_c='sudo -E sh -c' + elif command_exists su; then + sh_c='su -c' else - ( - set -x - $sh_c 'sleep 3; yum -y -q install docker-io' - ) - fi - if command_exists docker && [ -e /var/run/docker.sock ]; then - ( - set -x - $sh_c 'docker version' - ) || 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|linuxmint) - export DEBIAN_FRONTEND=noninteractive - - did_apt_get_update= - apt_get_update() { - if [ -z "$did_apt_get_update" ]; then - ( set -x; $sh_c 'sleep 3; apt-get update' ) - 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 - if uname -r | grep -q -- '-generic' && dpkg -l 'linux-image-*-generic' | grep -q '^ii' 2>/dev/null; then - kern_extras="linux-image-extra-$(uname -r) linux-image-extra-virtual" - - 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 - else - echo >&2 'Warning: current kernel is not supported by the linux-image-extra-virtual' - echo >&2 ' package. We have no AUFS support. Consider installing the packages' - echo >&2 ' linux-image-virtual kernel and linux-image-extra-virtual for AUFS support.' - ( set -x; sleep 10 ) - fi - fi - - # install apparmor utils if they're missing and apparmor is enabled in the kernel - # otherwise Docker will fail to start - if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = 'Y' ]; then - if command -v apparmor_parser &> /dev/null; then - echo 'apparmor is enabled in the kernel and apparmor utils were already installed' - else - echo 'apparmor is enabled in the kernel, but apparmor_parser missing' - apt_get_update - ( set -x; $sh_c 'sleep 3; apt-get install -y -q apparmor' ) - 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 ca-certificates' ) - fi - if [ -z "$curl" ]; then - apt_get_update - ( set -x; $sh_c 'sleep 3; apt-get install -y -q curl ca-certificates' ) - curl='curl -sSL' - fi - ( - set -x - if [ "https://get.docker.com/" = "$url" ]; then - $sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9" - elif [ "https://test.docker.com/" = "$url" ]; then - $sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 740B314AE3941731B942C66ADF4FD13717AAD7D6" - else - $sh_c "$curl ${url}gpg | apt-key add -" - fi - $sh_c "echo deb ${url}ubuntu docker main > /etc/apt/sources.list.d/docker.list" - $sh_c 'sleep 3; apt-get update; apt-get install -y -q lxc-docker' - ) - if command_exists docker && [ -e /var/run/docker.sock ]; then - ( - set -x - $sh_c 'docker version' - ) || 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 - ;; - - gentoo) - if [ "$url" = "https://test.docker.com/" ]; then - echo >&2 - echo >&2 ' You appear to be trying to install the latest nightly build in Gentoo.' - echo >&2 ' The portage tree should contain the latest stable release of Docker, but' - echo >&2 ' if you want something more recent, you can always use the live ebuild' - echo >&2 ' provided in the "docker" overlay available via layman. For more' - echo >&2 ' instructions, please see the following URL:' - echo >&2 ' https://github.com/tianon/docker-overlay#using-this-overlay' - echo >&2 ' After adding the "docker" overlay, you should be able to:' - echo >&2 ' emerge -av =app-emulation/docker-9999' - echo >&2 + echo >&2 'Error: this installer needs the ability to run commands as root.' + echo >&2 'We are unable to find either "sudo" or "su" available to make this happen.' exit 1 fi + fi - ( - set -x - $sh_c 'sleep 3; emerge app-emulation/docker' - ) - exit 0 - ;; -esac + curl='' + if command_exists curl; then + curl='curl -sSL' + elif command_exists wget; then + curl='wget -qO-' + elif command_exists busybox && busybox --list-modules | grep -q wget; then + curl='busybox wget -qO-' + fi -cat >&2 <<'EOF' + # perform some very rudimentary platform detection + lsb_dist='' + if command_exists lsb_release; then + lsb_dist="$(lsb_release -si)" + fi + if [ -z "$lsb_dist" ] && [ -r /etc/lsb-release ]; then + lsb_dist="$(. /etc/lsb-release && echo "$DISTRIB_ID")" + 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 + if [ -z "$lsb_dist" ] && [ -r /etc/os-release ]; then + lsb_dist="$(. /etc/os-release && echo "$ID")" + fi - Either your platform is not easily detectable, is not supported by this - installer script (yet - PRs welcome! [hack/install.sh]), or does not yet have - a package for Docker. Please visit the following URL for more detailed - installation instructions: + lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')" + case "$lsb_dist" in + amzn|fedora|centos) + if [ "$lsb_dist" = 'amzn' ]; then + ( + set -x + $sh_c 'sleep 3; yum -y -q install docker' + ) + else + ( + set -x + $sh_c 'sleep 3; yum -y -q install docker-io' + ) + fi + if command_exists docker && [ -e /var/run/docker.sock ]; then + ( + set -x + $sh_c 'docker version' + ) || 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 + ;; - https://docs.docker.com/en/latest/installation/ + ubuntu|debian|linuxmint) + export DEBIAN_FRONTEND=noninteractive -EOF + did_apt_get_update= + apt_get_update() { + if [ -z "$did_apt_get_update" ]; then + ( set -x; $sh_c 'sleep 3; apt-get update' ) + 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 + if uname -r | grep -q -- '-generic' && dpkg -l 'linux-image-*-generic' | grep -q '^ii' 2>/dev/null; then + kern_extras="linux-image-extra-$(uname -r) linux-image-extra-virtual" + + 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 + else + echo >&2 'Warning: current kernel is not supported by the linux-image-extra-virtual' + echo >&2 ' package. We have no AUFS support. Consider installing the packages' + echo >&2 ' linux-image-virtual kernel and linux-image-extra-virtual for AUFS support.' + ( set -x; sleep 10 ) + fi + fi + + # install apparmor utils if they're missing and apparmor is enabled in the kernel + # otherwise Docker will fail to start + if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = 'Y' ]; then + if command -v apparmor_parser &> /dev/null; then + echo 'apparmor is enabled in the kernel and apparmor utils were already installed' + else + echo 'apparmor is enabled in the kernel, but apparmor_parser missing' + apt_get_update + ( set -x; $sh_c 'sleep 3; apt-get install -y -q apparmor' ) + 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 ca-certificates' ) + fi + if [ -z "$curl" ]; then + apt_get_update + ( set -x; $sh_c 'sleep 3; apt-get install -y -q curl ca-certificates' ) + curl='curl -sSL' + fi + ( + set -x + if [ "https://get.docker.com/" = "$url" ]; then + $sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9" + elif [ "https://test.docker.com/" = "$url" ]; then + $sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 740B314AE3941731B942C66ADF4FD13717AAD7D6" + else + $sh_c "$curl ${url}gpg | apt-key add -" + fi + $sh_c "echo deb ${url}ubuntu docker main > /etc/apt/sources.list.d/docker.list" + $sh_c 'sleep 3; apt-get update; apt-get install -y -q lxc-docker' + ) + if command_exists docker && [ -e /var/run/docker.sock ]; then + ( + set -x + $sh_c 'docker version' + ) || 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 + ;; + + gentoo) + if [ "$url" = "https://test.docker.com/" ]; then + echo >&2 + echo >&2 ' You appear to be trying to install the latest nightly build in Gentoo.' + echo >&2 ' The portage tree should contain the latest stable release of Docker, but' + echo >&2 ' if you want something more recent, you can always use the live ebuild' + echo >&2 ' provided in the "docker" overlay available via layman. For more' + echo >&2 ' instructions, please see the following URL:' + echo >&2 ' https://github.com/tianon/docker-overlay#using-this-overlay' + echo >&2 ' After adding the "docker" overlay, you should be able to:' + echo >&2 ' emerge -av =app-emulation/docker-9999' + echo >&2 + exit 1 + fi + + ( + set -x + $sh_c 'sleep 3; emerge app-emulation/docker' + ) + exit 0 + ;; + esac + + echo >&2 + echo >&2 'Either your platform is not easily detectable, is not supported by this' + echo >&2 'installer script (yet - PRs welcome! [hack/install.sh]), or does not yet have' + echo >&2 'a package for Docker. Please visit the following URL for more detailed' + echo >&2 'installation instructions:' + echo >&2 + echo >&2 ' https://docs.docker.com/en/latest/installation/' + echo >&2 + exit 1 +} + +do_docker_install exit 1 From bbdf045ac1dfa8fc78b1c932736fe6400eecdf63 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 9 Apr 2015 11:43:05 -0700 Subject: [PATCH 341/999] Fix typo in builder/dispatchers.go Signed-off-by: David Calavera --- builder/dispatchers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/dispatchers.go b/builder/dispatchers.go index e47991194..820ac17e3 100644 --- a/builder/dispatchers.go +++ b/builder/dispatchers.go @@ -171,7 +171,7 @@ func from(b *Builder, args []string, attributes map[string]bool, original string } // note that the top level err will still be !nil here if IsNotExist is - // not the error. This approach just simplifies hte logic a bit. + // not the error. This approach just simplifies the logic a bit. if err != nil { return err } From d38c90140b8330bd355c40a0c8d1e9bd2b3664ec Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 6 Apr 2015 16:28:39 +0200 Subject: [PATCH 342/999] Fix TestBuildCancelationKillsSleep to not fail on Windows Signed-off-by: Antonio Murdaca --- integration-cli/docker_cli_build_test.go | 61 +++++++++++------------- integration-cli/utils.go | 8 ++-- 2 files changed, 32 insertions(+), 37 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index e3f7ec7f1..01e6d1d3d 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -1963,8 +1963,8 @@ func TestBuildForceRm(t *testing.T) { // * When docker events sees container start, close the "docker build" command // * Wait for docker events to emit a dying event. func TestBuildCancelationKillsSleep(t *testing.T) { - // TODO(jfrazelle): Make this work on Windows. - testRequires(t, SameHostDaemon) + var wg sync.WaitGroup + defer wg.Wait() name := "testbuildcancelation" defer deleteImages(name) @@ -1977,26 +1977,23 @@ func TestBuildCancelationKillsSleep(t *testing.T) { } defer ctx.Close() - var wg sync.WaitGroup - defer wg.Wait() - finish := make(chan struct{}) defer close(finish) eventStart := make(chan struct{}) eventDie := make(chan struct{}) + containerID := make(chan string) - // Start one second ago, to avoid rounding problems - startEpoch := time.Now().Add(-1 * time.Second) + startEpoch := daemonTime(t).Unix() - // Goroutine responsible for watching start/die events from `docker events` wg.Add(1) + // Goroutine responsible for watching start/die events from `docker events` go func() { defer wg.Done() - // Watch for events since epoch. - eventsCmd := exec.Command(dockerBinary, "events", - "-since", fmt.Sprint(startEpoch.Unix())) + eventsCmd := exec.Command( + dockerBinary, "events", + "--since", strconv.FormatInt(startEpoch, 10)) stdout, err := eventsCmd.StdoutPipe() err = eventsCmd.Start() if err != nil { @@ -2008,36 +2005,21 @@ func TestBuildCancelationKillsSleep(t *testing.T) { eventsCmd.Process.Kill() }() - var started, died bool - var imageID string + cid := <-containerID - if out, err := exec.Command(dockerBinary, "inspect", "-f", "{{.Id}}", "busybox").CombinedOutput(); err != nil { - t.Fatalf("failed to get the image ID of busybox: %s, %v", out, err) - } else { - imageID = strings.TrimSpace(string(out)) - } - - matchStart := regexp.MustCompile(" \\(from " + imageID + "\\) start$") - matchDie := regexp.MustCompile(" \\(from " + imageID + "\\) die$") + matchStart := regexp.MustCompile(cid + `(.*) start$`) + matchDie := regexp.MustCompile(cid + `(.*) die$`) // // Read lines of `docker events` looking for container start and stop. // scanner := bufio.NewScanner(stdout) for scanner.Scan() { - if ok := matchStart.MatchString(scanner.Text()); ok { - if started { - t.Fatal("assertion fail: more than one container started") - } + switch { + case matchStart.MatchString(scanner.Text()): close(eventStart) - started = true - } - if ok := matchDie.MatchString(scanner.Text()); ok { - if died { - t.Fatal("assertion fail: more than one container died") - } + case matchDie.MatchString(scanner.Text()): close(eventDie) - died = true } } @@ -2050,13 +2032,24 @@ func TestBuildCancelationKillsSleep(t *testing.T) { buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".") buildCmd.Dir = ctx.Dir + stdoutBuild, err := buildCmd.StdoutPipe() err = buildCmd.Start() if err != nil { t.Fatalf("failed to run build: %s", err) } + matchCID := regexp.MustCompile("Running in ") + scanner := bufio.NewScanner(stdoutBuild) + for scanner.Scan() { + line := scanner.Text() + if ok := matchCID.MatchString(line); ok { + containerID <- line[len(line)-12:] + break + } + } + select { - case <-time.After(30 * time.Second): + case <-time.After(5 * time.Second): t.Fatal("failed to observe build container start in timely fashion") case <-eventStart: // Proceeds from here when we see the container fly past in the @@ -2078,7 +2071,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) { } select { - case <-time.After(30 * time.Second): + case <-time.After(5 * time.Second): // If we don't get here in a timely fashion, it wasn't killed. t.Fatal("container cancel did not succeed") case <-eventDie: diff --git a/integration-cli/utils.go b/integration-cli/utils.go index c4394095d..536f6984e 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -44,12 +44,14 @@ func processExitCode(err error) (exitCode int) { func IsKilled(err error) bool { if exitErr, ok := err.(*exec.ExitError); ok { - sys := exitErr.ProcessState.Sys() - status, ok := sys.(syscall.WaitStatus) + status, ok := exitErr.Sys().(syscall.WaitStatus) if !ok { return false } - return status.Signaled() && status.Signal() == os.Kill + // status.ExitStatus() is required on Windows because it does not + // implement Signal() nor Signaled(). Just check it had a bad exit + // status could mean it was killed (and in tests we do kill) + return (status.Signaled() && status.Signal() == os.Kill) || status.ExitStatus() != 0 } return false } From ccdef895def5e42706e792b4891ad2d06bbf488a Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Thu, 9 Apr 2015 13:05:31 -0700 Subject: [PATCH 343/999] Removed engine from /copy * Client and server use types.CopyConfig * API calls container.Copy directly Signed-off-by: Alexander Morozov --- api/client/cp.go | 18 ++++++++---------- api/server/server.go | 37 ++++++++++++++++++++++++------------- api/types/types.go | 5 +++++ daemon/copy.go | 35 ----------------------------------- daemon/daemon.go | 1 - 5 files changed, 37 insertions(+), 59 deletions(-) delete mode 100644 daemon/copy.go diff --git a/api/client/cp.go b/api/client/cp.go index 9cc1b3be6..f32e55187 100644 --- a/api/client/cp.go +++ b/api/client/cp.go @@ -5,7 +5,7 @@ import ( "io" "strings" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/archive" flag "github.com/docker/docker/pkg/mflag" ) @@ -21,17 +21,16 @@ func (cli *DockerCli) CmdCp(args ...string) error { cmd.ParseFlags(args, true) - var copyData engine.Env info := strings.Split(cmd.Arg(0), ":") if len(info) != 2 { return fmt.Errorf("Error: Path not specified") } - copyData.Set("Resource", info[1]) - copyData.Set("HostPath", cmd.Arg(1)) - - stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", copyData, nil) + cfg := &types.CopyConfig{ + Resource: info[1], + } + stream, statusCode, err := cli.call("POST", "/containers/"+info[0]+"/copy", cfg, nil) if stream != nil { defer stream.Close() } @@ -42,13 +41,12 @@ func (cli *DockerCli) CmdCp(args ...string) error { return err } + hostPath := cmd.Arg(1) if statusCode == 200 { - dest := copyData.Get("HostPath") - - if dest == "-" { + if hostPath == "-" { _, err = io.Copy(cli.out, stream) } else { - err = archive.Untar(stream, dest, &archive.TarOptions{NoLchown: true}) + err = archive.Untar(stream, hostPath, &archive.TarOptions{NoLchown: true}) } if err != nil { return err diff --git a/api/server/server.go b/api/server/server.go index d8b40f02f..824c3cce7 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1203,37 +1203,48 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp return fmt.Errorf("Missing parameter") } - var copyData engine.Env - if err := checkForJson(r); err != nil { return err } - if err := copyData.Decode(r.Body); err != nil { + cfg := types.CopyConfig{} + if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil { return err } - if copyData.Get("Resource") == "" { + if cfg.Resource == "" { return fmt.Errorf("Path cannot be empty") } - origResource := copyData.Get("Resource") + res := cfg.Resource - if copyData.Get("Resource")[0] == '/' { - copyData.Set("Resource", copyData.Get("Resource")[1:]) + if res[0] == '/' { + res = res[1:] } - job := eng.Job("container_copy", vars["name"], copyData.Get("Resource")) - job.Stdout.Add(w) - w.Header().Set("Content-Type", "application/x-tar") - if err := job.Run(); err != nil { + cont, err := getDaemon(eng).Get(vars["name"]) + if err != nil { logrus.Errorf("%v", err) if strings.Contains(strings.ToLower(err.Error()), "no such id") { 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 } } + + data, err := cont.Copy(res) + if err != nil { + logrus.Errorf("%v", err) + if os.IsNotExist(err) { + return fmt.Errorf("Could not find the file %s in container %s", cfg.Resource, vars["name"]) + } + return err + } + defer data.Close() + w.Header().Set("Content-Type", "application/x-tar") + if _, err := io.Copy(w, data); err != nil { + return err + } + return nil } diff --git a/api/types/types.go b/api/types/types.go index 36983f68d..77b211705 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -98,3 +98,8 @@ type Container struct { Labels map[string]string `json:",omitempty"` Status string `json:",omitempty"` } + +// POST "/containers/"+containerID+"/copy" +type CopyConfig struct { + Resource string +} diff --git a/daemon/copy.go b/daemon/copy.go deleted file mode 100644 index aaa725263..000000000 --- a/daemon/copy.go +++ /dev/null @@ -1,35 +0,0 @@ -package daemon - -import ( - "fmt" - "io" - - "github.com/docker/docker/engine" -) - -func (daemon *Daemon) ContainerCopy(job *engine.Job) error { - if len(job.Args) != 2 { - return fmt.Errorf("Usage: %s CONTAINER RESOURCE\n", job.Name) - } - - var ( - name = job.Args[0] - resource = job.Args[1] - ) - - container, err := daemon.Get(name) - if err != nil { - return err - } - - data, err := container.Copy(resource) - if err != nil { - return err - } - defer data.Close() - - if _, err := io.Copy(job.Stdout, data); err != nil { - return err - } - return nil -} diff --git a/daemon/daemon.go b/daemon/daemon.go index c9b5285f0..fc94e0a3a 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -117,7 +117,6 @@ type Daemon struct { func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "commit": daemon.ContainerCommit, - "container_copy": daemon.ContainerCopy, "container_inspect": daemon.ContainerInspect, "container_stats": daemon.ContainerStats, "create": daemon.ContainerCreate, From 5ccb1c764b04449811aa4d8095a9ee609b901cf7 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 9 Apr 2015 22:50:48 +0200 Subject: [PATCH 344/999] Remove job from pause/unpause Signed-off-by: Antonio Murdaca --- api/server/server.go | 28 ++++++++++++++++++++++++---- daemon/daemon.go | 2 -- daemon/pause.go | 39 --------------------------------------- 3 files changed, 24 insertions(+), 45 deletions(-) delete mode 100644 daemon/pause.go diff --git a/api/server/server.go b/api/server/server.go index d8b40f02f..76f0147bb 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -214,11 +214,21 @@ func postContainersPause(eng *engine.Engine, version version.Version, w http.Res if err := parseForm(r); err != nil { return err } - job := eng.Job("pause", vars["name"]) - if err := job.Run(); err != nil { + + name := vars["name"] + d := getDaemon(eng) + cont, err := d.Get(name) + if err != nil { return err } + + if err := cont.Pause(); err != nil { + return fmt.Errorf("Cannot pause container %s: %s", name, err) + } + cont.LogEvent("pause") + w.WriteHeader(http.StatusNoContent) + return nil } @@ -229,11 +239,21 @@ func postContainersUnpause(eng *engine.Engine, version version.Version, w http.R if err := parseForm(r); err != nil { return err } - job := eng.Job("unpause", vars["name"]) - if err := job.Run(); err != nil { + + name := vars["name"] + d := getDaemon(eng) + cont, err := d.Get(name) + if err != nil { return err } + + if err := cont.Unpause(); err != nil { + return fmt.Errorf("Cannot unpause container %s: %s", name, err) + } + cont.LogEvent("unpause") + w.WriteHeader(http.StatusNoContent) + return nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index c9b5285f0..89083093b 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -125,13 +125,11 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "info": daemon.CmdInfo, "kill": daemon.ContainerKill, "logs": daemon.ContainerLogs, - "pause": daemon.ContainerPause, "resize": daemon.ContainerResize, "restart": daemon.ContainerRestart, "start": daemon.ContainerStart, "stop": daemon.ContainerStop, "top": daemon.ContainerTop, - "unpause": daemon.ContainerUnpause, "wait": daemon.ContainerWait, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, diff --git a/daemon/pause.go b/daemon/pause.go deleted file mode 100644 index 448c521e4..000000000 --- a/daemon/pause.go +++ /dev/null @@ -1,39 +0,0 @@ -package daemon - -import ( - "fmt" - - "github.com/docker/docker/engine" -) - -func (daemon *Daemon) ContainerPause(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s CONTAINER", job.Name) - } - name := job.Args[0] - container, err := daemon.Get(name) - if err != nil { - return err - } - if err := container.Pause(); err != nil { - return fmt.Errorf("Cannot pause container %s: %s", name, err) - } - container.LogEvent("pause") - return nil -} - -func (daemon *Daemon) ContainerUnpause(job *engine.Job) error { - if n := len(job.Args); n < 1 || n > 2 { - return fmt.Errorf("Usage: %s CONTAINER", job.Name) - } - name := job.Args[0] - container, err := daemon.Get(name) - if err != nil { - return err - } - if err := container.Unpause(); err != nil { - return fmt.Errorf("Cannot unpause container %s: %s", name, err) - } - container.LogEvent("unpause") - return nil -} From e4addf1c016dd4b6510e17e3f0d23032f880ba6f Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Thu, 9 Apr 2015 14:20:03 -0700 Subject: [PATCH 345/999] Docs cleanup - Contributor Guide Signed-off-by: Megan Kostick --- docs/sources/project/find-an-issue.md | 2 +- docs/sources/project/work-issue.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/project/find-an-issue.md b/docs/sources/project/find-an-issue.md index 0cfe7d7b5..a5a3c8bfe 100644 --- a/docs/sources/project/find-an-issue.md +++ b/docs/sources/project/find-an-issue.md @@ -158,7 +158,7 @@ To sync your repository: origin https://github.com/moxiegirl/docker.git (fetch) origin https://github.com/moxiegirl/docker.git (push) upstream https://github.com/docker/docker.git (fetch) - upstream https://github.com/docker/docker.git ( + upstream https://github.com/docker/docker.git (push) If the `upstream` is missing, add it. diff --git a/docs/sources/project/work-issue.md b/docs/sources/project/work-issue.md index 5e70bc32c..1719195cd 100644 --- a/docs/sources/project/work-issue.md +++ b/docs/sources/project/work-issue.md @@ -149,7 +149,7 @@ You should pull and rebase frequently as you work. 2. Make sure you are in your branch. - $ git branch 11038-fix-rhel-link + $ git checkout 11038-fix-rhel-link 3. Fetch all the changes from the `upstream master` branch. From e290a22dc935c2472e08be7362b7d3b0f6303615 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 9 Apr 2015 23:51:22 +0200 Subject: [PATCH 346/999] Remove job from resize&execResize Signed-off-by: Antonio Murdaca --- api/server/server.go | 33 +++++++++++++++++++++++++++++++-- daemon/daemon.go | 2 -- daemon/resize.go | 44 +------------------------------------------- 3 files changed, 32 insertions(+), 47 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 97bf08bb0..912af638c 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1005,9 +1005,26 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re if vars == nil { return fmt.Errorf("Missing parameter") } - if err := eng.Job("resize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { + + height, err := strconv.Atoi(r.Form.Get("h")) + if err != nil { + return nil + } + width, err := strconv.Atoi(r.Form.Get("w")) + if err != nil { + return nil + } + + d := getDaemon(eng) + cont, err := d.Get(vars["name"]) + if err != nil { return err } + + if err := cont.Resize(height, width); err != nil { + return err + } + return nil } @@ -1363,9 +1380,21 @@ func postContainerExecResize(eng *engine.Engine, version version.Version, w http if vars == nil { return fmt.Errorf("Missing parameter") } - if err := eng.Job("execResize", vars["name"], r.Form.Get("h"), r.Form.Get("w")).Run(); err != nil { + + height, err := strconv.Atoi(r.Form.Get("h")) + if err != nil { + return nil + } + width, err := strconv.Atoi(r.Form.Get("w")) + if err != nil { + return nil + } + + d := getDaemon(eng) + if err := d.ContainerExecResize(vars["name"], height, width); err != nil { return err } + return nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index 36d05cd92..06e9bb440 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -125,7 +125,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "info": daemon.CmdInfo, "kill": daemon.ContainerKill, "logs": daemon.ContainerLogs, - "resize": daemon.ContainerResize, "restart": daemon.ContainerRestart, "start": daemon.ContainerStart, "stop": daemon.ContainerStop, @@ -133,7 +132,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "wait": daemon.ContainerWait, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, - "execResize": daemon.ContainerExecResize, "execInspect": daemon.ContainerExecInspect, } { if err := eng.Register(name, method); err != nil { diff --git a/daemon/resize.go b/daemon/resize.go index fce06753e..060634b13 100644 --- a/daemon/resize.go +++ b/daemon/resize.go @@ -1,48 +1,6 @@ package daemon -import ( - "fmt" - "strconv" - - "github.com/docker/docker/engine" -) - -func (daemon *Daemon) ContainerResize(job *engine.Job) error { - if len(job.Args) != 3 { - return fmt.Errorf("Not enough arguments. Usage: %s CONTAINER HEIGHT WIDTH\n", job.Name) - } - name := job.Args[0] - height, err := strconv.Atoi(job.Args[1]) - if err != nil { - return err - } - width, err := strconv.Atoi(job.Args[2]) - if err != nil { - return err - } - container, err := daemon.Get(name) - if err != nil { - return err - } - if err := container.Resize(height, width); err != nil { - return err - } - return nil -} - -func (daemon *Daemon) ContainerExecResize(job *engine.Job) error { - if len(job.Args) != 3 { - return fmt.Errorf("Not enough arguments. Usage: %s EXEC HEIGHT WIDTH\n", job.Name) - } - name := job.Args[0] - height, err := strconv.Atoi(job.Args[1]) - if err != nil { - return err - } - width, err := strconv.Atoi(job.Args[2]) - if err != nil { - return err - } +func (daemon *Daemon) ContainerExecResize(name string, height, width int) error { execConfig, err := daemon.getExecConfig(name) if err != nil { return err From 3cb751906a8a0397dcf57d8fca97c0e9c0c418e8 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Thu, 9 Apr 2015 11:56:47 -0700 Subject: [PATCH 347/999] Remove Job from `docker kill` Signed-off-by: Doug Davis --- api/server/server.go | 29 ++++++++++++++++++++++++----- daemon/daemon.go | 1 - daemon/kill.go | 34 ++-------------------------------- integration/server_test.go | 4 ++-- integration/utils_test.go | 2 +- 5 files changed, 29 insertions(+), 41 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index d8b40f02f..f8eba047e 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -28,6 +28,7 @@ import ( "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/filters" + "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/version" @@ -193,16 +194,34 @@ func postContainersKill(eng *engine.Engine, version version.Version, w http.Resp if vars == nil { return fmt.Errorf("Missing parameter") } - if err := parseForm(r); err != nil { + err := parseForm(r) + if err != nil { return err } - job := eng.Job("kill", vars["name"]) - if sig := r.Form.Get("signal"); sig != "" { - job.Args = append(job.Args, sig) + + var sig uint64 + name := vars["name"] + + // If we have a signal, look at it. Otherwise, do nothing + if sigStr := vars["signal"]; sigStr != "" { + // 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(sigStr, 10, 5) + if err != nil { + // The signal is not a number, treat it as a string (either like + // "KILL" or like "SIGKILL") + sig = uint64(signal.SignalMap[strings.TrimPrefix(sigStr, "SIG")]) + } + + if sig == 0 { + return fmt.Errorf("Invalid signal: %s", sigStr) + } } - if err := job.Run(); err != nil { + + if err = getDaemon(eng).ContainerKill(name, sig); err != nil { return err } + w.WriteHeader(http.StatusNoContent) return nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index c9b5285f0..b5d11f04c 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -123,7 +123,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "create": daemon.ContainerCreate, "export": daemon.ContainerExport, "info": daemon.CmdInfo, - "kill": daemon.ContainerKill, "logs": daemon.ContainerLogs, "pause": daemon.ContainerPause, "resize": daemon.ContainerResize, diff --git a/daemon/kill.go b/daemon/kill.go index 56bcad900..5d828f16b 100644 --- a/daemon/kill.go +++ b/daemon/kill.go @@ -2,43 +2,14 @@ package daemon import ( "fmt" - "strconv" - "strings" "syscall" - - "github.com/docker/docker/engine" - "github.com/docker/docker/pkg/signal" ) // ContainerKill send signal to the container // If no signal is given (sig 0), then Kill with SIGKILL and wait // for the container to exit. // If a signal is given, then just send it to the container and return. -func (daemon *Daemon) ContainerKill(job *engine.Job) error { - if n := len(job.Args); n < 1 || n > 2 { - return fmt.Errorf("Usage: %s CONTAINER [SIGNAL]", job.Name) - } - 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] != "" { - // 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 { - // 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 fmt.Errorf("Invalid signal: %s", job.Args[1]) - } - } - +func (daemon *Daemon) ContainerKill(name string, sig uint64) error { container, err := daemon.Get(name) if err != nil { return err @@ -49,13 +20,12 @@ func (daemon *Daemon) ContainerKill(job *engine.Job) error { if err := container.Kill(); err != nil { return fmt.Errorf("Cannot kill container %s: %s", name, err) } - container.LogEvent("kill") } else { // Otherwise, just send the requested signal if err := container.KillSig(int(sig)); err != nil { return fmt.Errorf("Cannot kill container %s: %s", name, err) } - // FIXME: Add event for signals } + container.LogEvent("kill") return nil } diff --git a/integration/server_test.go b/integration/server_test.go index b2c4dd80a..5dc4f1aa4 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -132,8 +132,8 @@ func TestRestartKillWait(t *testing.T) { if err := job.Run(); err != nil { t.Fatal(err) } - job = eng.Job("kill", id) - if err := job.Run(); err != nil { + + if err := runtime.ContainerKill(id, 0); err != nil { t.Fatal(err) } diff --git a/integration/utils_test.go b/integration/utils_test.go index f8afe62b6..3e16165db 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -104,7 +104,7 @@ func containerWaitTimeout(eng *engine.Engine, id string, t Fataler) error { } func containerKill(eng *engine.Engine, id string, t Fataler) { - if err := eng.Job("kill", id).Run(); err != nil { + if err := getDaemon(eng).ContainerKill(id, 0); err != nil { t.Fatal(err) } } From f01d755cd0af7b17596cc084871f7f032995cac3 Mon Sep 17 00:00:00 2001 From: WiseTrem Date: Thu, 9 Apr 2015 23:26:36 +0300 Subject: [PATCH 348/999] Remove pools_nopool.go & build tag from pools.go Fix #11576 Signed-off-by: Gleb Shepelev --- pkg/pools/pools.go | 2 -- pkg/pools/pools_nopool.go | 73 --------------------------------------- 2 files changed, 75 deletions(-) delete mode 100644 pkg/pools/pools_nopool.go diff --git a/pkg/pools/pools.go b/pkg/pools/pools.go index 5338a0cfb..f366fa67a 100644 --- a/pkg/pools/pools.go +++ b/pkg/pools/pools.go @@ -1,5 +1,3 @@ -// +build go1.3 - // Package pools provides a collection of pools which provide various // data types with buffers. These can be used to lower the number of // memory allocations and reuse buffers. diff --git a/pkg/pools/pools_nopool.go b/pkg/pools/pools_nopool.go deleted file mode 100644 index 48903c239..000000000 --- a/pkg/pools/pools_nopool.go +++ /dev/null @@ -1,73 +0,0 @@ -// +build !go1.3 - -package pools - -import ( - "bufio" - "io" - - "github.com/docker/docker/pkg/ioutils" -) - -var ( - BufioReader32KPool *BufioReaderPool - BufioWriter32KPool *BufioWriterPool -) - -const buffer32K = 32 * 1024 - -type BufioReaderPool struct { - size int -} - -func init() { - BufioReader32KPool = newBufioReaderPoolWithSize(buffer32K) - BufioWriter32KPool = newBufioWriterPoolWithSize(buffer32K) -} - -func newBufioReaderPoolWithSize(size int) *BufioReaderPool { - return &BufioReaderPool{size: size} -} - -func (bufPool *BufioReaderPool) Get(r io.Reader) *bufio.Reader { - return bufio.NewReaderSize(r, bufPool.size) -} - -func (bufPool *BufioReaderPool) Put(b *bufio.Reader) { - b.Reset(nil) -} - -func (bufPool *BufioReaderPool) NewReadCloserWrapper(buf *bufio.Reader, r io.Reader) io.ReadCloser { - return ioutils.NewReadCloserWrapper(r, func() error { - if readCloser, ok := r.(io.ReadCloser); ok { - return readCloser.Close() - } - return nil - }) -} - -type BufioWriterPool struct { - size int -} - -func newBufioWriterPoolWithSize(size int) *BufioWriterPool { - return &BufioWriterPool{size: size} -} - -func (bufPool *BufioWriterPool) Get(w io.Writer) *bufio.Writer { - return bufio.NewWriterSize(w, bufPool.size) -} - -func (bufPool *BufioWriterPool) Put(b *bufio.Writer) { - b.Reset(nil) -} - -func (bufPool *BufioWriterPool) NewWriteCloserWrapper(buf *bufio.Writer, w io.Writer) io.WriteCloser { - return ioutils.NewWriteCloserWrapper(w, func() error { - buf.Flush() - if writeCloser, ok := w.(io.WriteCloser); ok { - return writeCloser.Close() - } - return nil - }) -} From 8636a219911536123decb547dab9bf50ebb2c8f8 Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Fri, 10 Apr 2015 09:14:01 +0800 Subject: [PATCH 349/999] add TestContainerApiPause case Signed-off-by: Yuan Sun --- integration-cli/docker_api_containers_test.go | 48 +++++++++++++++++-- integration-cli/docker_utils.go | 3 ++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 02d069f59..07793a8fc 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -3,14 +3,14 @@ package main import ( "bytes" "encoding/json" + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "os/exec" "strings" "testing" "time" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) func TestContainerApiGetAll(t *testing.T) { @@ -553,3 +553,45 @@ func TestPostContainerBindNormalVolume(t *testing.T) { logDone("container REST API - can use path from normal volume as bind-mount to overwrite another volume") } + +func TestContainerApiPause(t *testing.T) { + defer deleteAllContainers() + defer unpauseAllContainers() + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sleep", "30") + out, _, err := runCommandWithOutput(runCmd) + + if err != nil { + t.Fatalf("failed to create a container: %s, %v", out, err) + } + ContainerID := strings.TrimSpace(out) + + if _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { + t.Fatalf("POST a container pause: sockRequest failed: %v", err) + } + + pausedContainers, err := getSliceOfPausedContainers() + + if err != nil { + t.Fatalf("error thrown while checking if containers were paused: %v", err) + } + + if len(pausedContainers) != 1 || stringid.TruncateID(ContainerID) != pausedContainers[0] { + t.Fatalf("there should be one paused container and not %d", len(pausedContainers)) + } + + if _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { + t.Fatalf("POST a container pause: sockRequest failed: %v", err) + } + + pausedContainers, err = getSliceOfPausedContainers() + + if err != nil { + t.Fatalf("error thrown while checking if containers were paused: %v", err) + } + + if pausedContainers != nil { + t.Fatalf("There should be no paused container.") + } + + logDone("container REST API - check POST containers/pause nad unpause") +} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 843a07a20..943c1e02a 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -389,6 +389,9 @@ func getPausedContainers() (string, error) { func getSliceOfPausedContainers() ([]string, error) { out, err := getPausedContainers() if err == nil { + if len(out) == 0 { + return nil, err + } slice := strings.Split(strings.TrimSpace(out), "\n") return slice, err } From 3e096cb9c9e9d708df7982be5694daaa62bb4849 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Thu, 9 Apr 2015 15:13:01 -0700 Subject: [PATCH 350/999] Remove Job from `docker top` Signed-off-by: Doug Davis --- api/client/top.go | 19 +++++++++--------- api/server/server.go | 11 +++++++--- api/types/types.go | 6 ++++++ daemon/daemon.go | 1 - daemon/top.go | 48 ++++++++++++++++++-------------------------- 5 files changed, 44 insertions(+), 41 deletions(-) diff --git a/api/client/top.go b/api/client/top.go index 9de04cac6..4975f4759 100644 --- a/api/client/top.go +++ b/api/client/top.go @@ -1,12 +1,13 @@ package client import ( + "encoding/json" "fmt" "net/url" "strings" "text/tabwriter" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" ) @@ -28,17 +29,17 @@ func (cli *DockerCli) CmdTop(args ...string) error { if err != nil { return err } - var procs engine.Env - if err := procs.Decode(stream); err != nil { + + procList := types.ContainerProcessList{} + err = json.NewDecoder(stream).Decode(&procList) + if err != nil { return err } + w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - fmt.Fprintln(w, strings.Join(procs.GetList("Titles"), "\t")) - processes := [][]string{} - if err := procs.GetJson("Processes", &processes); err != nil { - return err - } - for _, proc := range processes { + fmt.Fprintln(w, strings.Join(procList.Titles, "\t")) + + for _, proc := range procList.Processes { fmt.Fprintln(w, strings.Join(proc, "\t")) } w.Flush() diff --git a/api/server/server.go b/api/server/server.go index 912af638c..7b38ea30c 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -471,16 +471,21 @@ func getContainersTop(eng *engine.Engine, version version.Version, w http.Respon if version.LessThan("1.4") { return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.") } + if vars == nil { return fmt.Errorf("Missing parameter") } + if err := parseForm(r); err != nil { return err } - job := eng.Job("top", vars["name"], r.Form.Get("ps_args")) - streamJSON(job, w, false) - return job.Run() + procList, err := getDaemon(eng).ContainerTop(vars["name"], r.Form.Get("ps_args")) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, procList) } func getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api/types/types.go b/api/types/types.go index 77b211705..48c9265a5 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -103,3 +103,9 @@ type Container struct { type CopyConfig struct { Resource string } + +// GET "/containers/{name:.*}/top" +type ContainerProcessList struct { + Processes [][]string + Titles []string +} diff --git a/daemon/daemon.go b/daemon/daemon.go index 06e9bb440..ea49a8b87 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -128,7 +128,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "restart": daemon.ContainerRestart, "start": daemon.ContainerStart, "stop": daemon.ContainerStop, - "top": daemon.ContainerTop, "wait": daemon.ContainerWait, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, diff --git a/daemon/top.go b/daemon/top.go index 1e8c39987..14b252370 100644 --- a/daemon/top.go +++ b/daemon/top.go @@ -6,54 +6,48 @@ import ( "strconv" "strings" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" ) -func (daemon *Daemon) ContainerTop(job *engine.Job) error { - if len(job.Args) != 1 && len(job.Args) != 2 { - return fmt.Errorf("Not enough arguments. Usage: %s CONTAINER [PS_ARGS]\n", job.Name) - } - var ( - name = job.Args[0] +func (daemon *Daemon) ContainerTop(name string, psArgs string) (*types.ContainerProcessList, error) { + if psArgs == "" { psArgs = "-ef" - ) - - if len(job.Args) == 2 && job.Args[1] != "" { - psArgs = job.Args[1] } container, err := daemon.Get(name) if err != nil { - return err + return nil, err } + if !container.IsRunning() { - return fmt.Errorf("Container %s is not running", name) + return nil, fmt.Errorf("Container %s is not running", name) } + pids, err := daemon.ExecutionDriver().GetPidsForContainer(container.ID) if err != nil { - return err + return nil, err } + output, err := exec.Command("ps", strings.Split(psArgs, " ")...).Output() if err != nil { - return fmt.Errorf("Error running ps: %s", err) + return nil, fmt.Errorf("Error running ps: %s", err) } + procList := &types.ContainerProcessList{} + lines := strings.Split(string(output), "\n") - header := strings.Fields(lines[0]) - out := &engine.Env{} - out.SetList("Titles", header) + procList.Titles = strings.Fields(lines[0]) pidIndex := -1 - for i, name := range header { + for i, name := range procList.Titles { if name == "PID" { pidIndex = i } } if pidIndex == -1 { - return fmt.Errorf("Couldn't find PID field in ps output") + return nil, fmt.Errorf("Couldn't find PID field in ps output") } - processes := [][]string{} for _, line := range lines[1:] { if len(line) == 0 { continue @@ -61,20 +55,18 @@ func (daemon *Daemon) ContainerTop(job *engine.Job) error { fields := strings.Fields(line) p, err := strconv.Atoi(fields[pidIndex]) if err != nil { - return fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) + return nil, fmt.Errorf("Unexpected pid '%s': %s", fields[pidIndex], err) } for _, pid := range pids { if pid == p { // Make sure number of fields equals number of header titles // merging "overhanging" fields - process := fields[:len(header)-1] - process = append(process, strings.Join(fields[len(header)-1:], " ")) - processes = append(processes, process) + process := fields[:len(procList.Titles)-1] + process = append(process, strings.Join(fields[len(procList.Titles)-1:], " ")) + procList.Processes = append(procList.Processes, process) } } } - out.SetJson("Processes", processes) - out.WriteTo(job.Stdout) - return nil + return procList, nil } From 6842bba163de753a5ff3ddbaf9b408c89235022b Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 9 Apr 2015 12:14:53 -0600 Subject: [PATCH 351/999] Commonalize more bits of install.sh (especially standardizing around "cat <<-EOF") Signed-off-by: Andrew "Tianon" Page --- hack/install.sh | 112 +++++++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 49 deletions(-) diff --git a/hack/install.sh b/hack/install.sh index 305d8fb0f..b0177e670 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -20,21 +20,41 @@ command_exists() { command -v "$@" > /dev/null 2>&1 } -do_docker_install() { +echo_docker_as_nonroot() { + your_user=your-user + [ "$user" != 'root' ] && your_user="$user" + # intentionally mixed spaces and tabs here -- tabs are stripped by "<<-EOF", spaces are kept in the output + cat <<-EOF + + If you would like to use Docker as a non-root user, you should now consider + adding your user to the "docker" group with something like: + + sudo usermod -aG docker $your_user + + Remember that you will have to log out and back in for this to take effect! + + EOF +} + +do_install() { case "$(uname -m)" in *64) ;; *) - echo >&2 'Error: you are not using a 64bit platform.' - echo >&2 'Docker currently only supports 64bit platforms.' + cat >&2 <<-'EOF' + Error: you are not using a 64bit platform. + Docker currently only supports 64bit platforms. + EOF exit 1 ;; esac if command_exists docker || command_exists lxc-docker; then - echo >&2 'Warning: "docker" or "lxc-docker" command appears to already exist.' - echo >&2 'Please ensure that you do not already have docker installed.' - echo >&2 'You may press Ctrl+C now to abort this process and rectify this situation.' + cat >&2 <<-'EOF' + Warning: "docker" or "lxc-docker" command appears to already exist. + Please ensure that you do not already have docker installed. + You may press Ctrl+C now to abort this process and rectify this situation. + EOF ( set -x; sleep 20 ) fi @@ -47,8 +67,10 @@ do_docker_install() { elif command_exists su; then sh_c='su -c' else - echo >&2 'Error: this installer needs the ability to run commands as root.' - echo >&2 'We are unable to find either "sudo" or "su" available to make this happen.' + cat >&2 <<-'EOF' + Error: this installer needs the ability to run commands as root. + We are unable to find either "sudo" or "su" available to make this happen. + EOF exit 1 fi fi @@ -100,16 +122,7 @@ do_docker_install() { $sh_c 'docker version' ) || 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 + echo_docker_as_nonroot exit 0 ;; @@ -184,31 +197,28 @@ do_docker_install() { $sh_c 'docker version' ) || 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 + echo_docker_as_nonroot exit 0 ;; gentoo) if [ "$url" = "https://test.docker.com/" ]; then - echo >&2 - echo >&2 ' You appear to be trying to install the latest nightly build in Gentoo.' - echo >&2 ' The portage tree should contain the latest stable release of Docker, but' - echo >&2 ' if you want something more recent, you can always use the live ebuild' - echo >&2 ' provided in the "docker" overlay available via layman. For more' - echo >&2 ' instructions, please see the following URL:' - echo >&2 ' https://github.com/tianon/docker-overlay#using-this-overlay' - echo >&2 ' After adding the "docker" overlay, you should be able to:' - echo >&2 ' emerge -av =app-emulation/docker-9999' - echo >&2 + # intentionally mixed spaces and tabs here -- tabs are stripped by "<<-'EOF'", spaces are kept in the output + cat >&2 <<-'EOF' + + You appear to be trying to install the latest nightly build in Gentoo.' + The portage tree should contain the latest stable release of Docker, but' + if you want something more recent, you can always use the live ebuild' + provided in the "docker" overlay available via layman. For more' + instructions, please see the following URL:' + + https://github.com/tianon/docker-overlay#using-this-overlay' + + After adding the "docker" overlay, you should be able to:' + + emerge -av =app-emulation/docker-9999' + + EOF exit 1 fi @@ -220,16 +230,20 @@ do_docker_install() { ;; esac - echo >&2 - echo >&2 'Either your platform is not easily detectable, is not supported by this' - echo >&2 'installer script (yet - PRs welcome! [hack/install.sh]), or does not yet have' - echo >&2 'a package for Docker. Please visit the following URL for more detailed' - echo >&2 'installation instructions:' - echo >&2 - echo >&2 ' https://docs.docker.com/en/latest/installation/' - echo >&2 - exit 1 + # intentionally mixed spaces and tabs here -- tabs are stripped by "<<-'EOF'", spaces are kept in the output + cat >&2 <<-'EOF' + + Either your platform is not easily detectable, is not supported by this + installer script (yet - PRs welcome! [hack/install.sh]), or does not yet have + a package for Docker. Please visit the following URL for more detailed + installation instructions: + + https://docs.docker.com/en/latest/installation/ + + EOF + exit 1 } -do_docker_install -exit 1 +# wrapped up in a function so that we have some protection against only getting +# half the file during "curl | sh" +do_install From bf57339527f153b502a6443a495824a40768e39f Mon Sep 17 00:00:00 2001 From: David Young Date: Sun, 4 Jan 2015 14:47:01 +0800 Subject: [PATCH 352/999] Add comment column in docker history command output Signed-off-by: David Young --- api/client/history.go | 5 ++- api/types/types.go | 1 + docs/man/docker-history.1.md | 20 ++++++++- docs/sources/reference/commandline/cli.md | 21 ++++++++++ graph/history.go | 1 + integration-cli/docker_cli_history_test.go | 47 ++++++++++++++++++++++ 6 files changed, 92 insertions(+), 3 deletions(-) diff --git a/api/client/history.go b/api/client/history.go index 6e0cdb24c..8736b057b 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -36,7 +36,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) if !*quiet { - fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE") + fmt.Fprintln(w, "IMAGE\tCREATED\tCREATED BY\tSIZE\tCOMMENT") } for _, entry := range history { @@ -53,7 +53,8 @@ func (cli *DockerCli) CmdHistory(args ...string) error { } else { fmt.Fprintf(w, "%s\t", utils.Trunc(entry.CreatedBy, 45)) } - fmt.Fprintf(w, "%s", units.HumanSize(float64(entry.Size))) + fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size))) + fmt.Fprintf(w, "%s\n", entry.Comment) } fmt.Fprintf(w, "\n") } diff --git a/api/types/types.go b/api/types/types.go index 77b211705..774203d02 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -49,6 +49,7 @@ type ImageHistory struct { CreatedBy string Tags []string Size int64 + Comment string } // DELETE "/images/{name:.*}" diff --git a/docs/man/docker-history.1.md b/docs/man/docker-history.1.md index 24f928c29..0c322d795 100644 --- a/docs/man/docker-history.1.md +++ b/docs/man/docker-history.1.md @@ -26,11 +26,29 @@ Show the history of when and how an image was created. Only show numeric IDs. The default is *false*. # EXAMPLES +<<<<<<< HEAD $ docker history fedora IMAGE CREATED CREATED BY SIZE +======= + +## Show the history of images created through docker build command + + $ sudo docker history fedora + IMAGE CREATED CREATED BY SIZE COMMENT +>>>>>>> Add comment column in docker history command output 105182bb5e8b 5 days ago /bin/sh -c #(nop) ADD file:71356d2ad59aa3119d 372.7 MB 73bd853d2ea5 13 days ago /bin/sh -c #(nop) MAINTAINER Lokesh Mandvekar 0 B - 511136ea3c5a 10 months ago 0 B + 511136ea3c5a 10 months ago 0 B Imported from - + +## Show the history of images created through docker commit command +`docker commit` command accepts a **-m** parameter to provide comment messages to the image. You can see these messages in image history. + + $ sudo docker history docker:scm + IMAGE CREATED CREATED BY SIZE COMMENT + 2ac9d1098bf1 3 months ago /bin/bash 241.4 MB Added Apache to Fedora base image + 88b42ffd1f7c 5 months ago /bin/sh -c #(nop) ADD file:1fd8d7f9f6557cafc7 373.7 MB + c69cab00d6ef 5 months ago /bin/sh -c #(nop) MAINTAINER Lokesh Mandvekar 0 B + 511136ea3c5a 19 months ago 0 B Imported from - # HISTORY April 2014, Originally compiled by William Henry (whenry at redhat dot com) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 9f8daa03f..f7d47edb0 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1151,6 +1151,7 @@ This will create a new Bash session in the container `ubuntu_bash`. To see how the `docker:latest` image was built: +<<<<<<< HEAD $ docker history docker IMAGE CREATED CREATED BY SIZE 3e23a5875458790b7a806f95f7ec0d0b2a5c1659bfc899c89f939f6d5b8f7094 8 days ago /bin/sh -c #(nop) ENV LC_ALL=C.UTF-8 0 B @@ -1159,6 +1160,26 @@ To see how the `docker:latest` image was built: 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 +======= + $ sudo docker history docker + IMAGE CREATED CREATED BY SIZE COMMENT + 3e23a5875458 8 days ago /bin/sh -c #(nop) ENV LC_ALL=C.UTF-8 0 B + 8578938dd170 8 days ago /bin/sh -c dpkg-reconfigure locales && loc 1.245 MB + be51b77efb42 8 days ago /bin/sh -c apt-get update && apt-get install 338.3 MB + 4b137612be55 6 weeks ago /bin/sh -c #(nop) ADD jessie.tar.xz in / 121 MB + 750d58736b4b 6 weeks ago /bin/sh -c #(nop) MAINTAINER Tianon Gravi >>>>>> Add comment column in docker history command output ## images diff --git a/graph/history.go b/graph/history.go index 1290de9a3..9c3efabaf 100644 --- a/graph/history.go +++ b/graph/history.go @@ -41,6 +41,7 @@ func (s *TagStore) CmdHistory(job *engine.Job) error { CreatedBy: strings.Join(img.ContainerConfig.Cmd, " "), Tags: lookupMap[img.ID], Size: img.Size, + Comment: img.Comment, }) return nil }) diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go index ecb0a3a07..89bb53e71 100644 --- a/integration-cli/docker_cli_history_test.go +++ b/integration-cli/docker_cli_history_test.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os/exec" + "regexp" "strings" "testing" ) @@ -82,3 +83,49 @@ func TestHistoryNonExistentImage(t *testing.T) { } logDone("history - history on non-existent image must pass") } + +func TestHistoryImageWithComment(t *testing.T) { + + // make a image through docker commit [ -m messages ] + runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + if err != nil { + t.Fatalf("failed to run container: %s, %v", out, err) + } + + cleanedContainerID := stripTrailingCharacters(out) + + waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID) + if _, _, err = runCommandWithOutput(waitCmd); err != nil { + t.Fatalf("error thrown while waiting for container: %s, %v", out, err) + } + + commitCmd := exec.Command(dockerBinary, "commit", "-m=This is a comment", cleanedContainerID) + out, _, err = runCommandWithOutput(commitCmd) + if err != nil { + t.Fatalf("failed to commit container to image: %s, %v", out, err) + } + + cleanedImageID := stripTrailingCharacters(out) + deleteContainer(cleanedContainerID) + defer deleteImages(cleanedImageID) + + // test docker history to check comment messages + historyCmd := exec.Command(dockerBinary, "history", cleanedImageID) + out, exitCode, err := runCommandWithOutput(historyCmd) + if err != nil || exitCode != 0 { + t.Fatalf("failed to get image history: %s, %v", out, err) + } + + expectedValue := "This is a comment" + + outputLine := strings.Split(out, "\n")[1] + outputTabs := regexp.MustCompile(" +").Split(outputLine, -1) + actualValue := outputTabs[len(outputTabs)-1] + + if !strings.Contains(actualValue, expectedValue) { + t.Fatalf("Expected comments \"%s\", but found \"%s\"", expectedValue, actualValue) + } + + logDone("history - history on image with comment") +} From 8d682bf734539ade2d618349f82b8b5e83a87167 Mon Sep 17 00:00:00 2001 From: David Young Date: Thu, 26 Mar 2015 12:39:50 +0800 Subject: [PATCH 353/999] Refine document by review comments Signed-off-by: David Young --- docs/man/docker-history.1.md | 2 +- docs/sources/reference/commandline/cli.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/man/docker-history.1.md b/docs/man/docker-history.1.md index 0c322d795..cd8fb6e6d 100644 --- a/docs/man/docker-history.1.md +++ b/docs/man/docker-history.1.md @@ -41,7 +41,7 @@ Show the history of when and how an image was created. 511136ea3c5a 10 months ago 0 B Imported from - ## Show the history of images created through docker commit command -`docker commit` command accepts a **-m** parameter to provide comment messages to the image. You can see these messages in image history. +The `docker commit` command has a **-m** flag for adding comments to the image. These comments will be displayed in the image history. $ sudo docker history docker:scm IMAGE CREATED CREATED BY SIZE COMMENT diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index f7d47edb0..ec26d0418 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1170,7 +1170,7 @@ To see how the `docker:latest` image was built: 750d58736b4b 6 weeks ago /bin/sh -c #(nop) MAINTAINER Tianon Gravi Date: Wed, 8 Apr 2015 19:43:25 -0400 Subject: [PATCH 354/999] Rebase + some fixes Signed-off-by: Tibor Vass --- api/client/history.go | 2 +- docs/man/docker-history.1.md | 10 +----- docs/sources/reference/commandline/cli.md | 13 +------- integration-cli/docker_cli_history_test.go | 38 ++++++++++------------ 4 files changed, 20 insertions(+), 43 deletions(-) diff --git a/api/client/history.go b/api/client/history.go index 8736b057b..844a6fb77 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -54,7 +54,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { fmt.Fprintf(w, "%s\t", utils.Trunc(entry.CreatedBy, 45)) } fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size))) - fmt.Fprintf(w, "%s\n", entry.Comment) + fmt.Fprintf(w, "%s", entry.Comment) } fmt.Fprintf(w, "\n") } diff --git a/docs/man/docker-history.1.md b/docs/man/docker-history.1.md index cd8fb6e6d..2b38d83e6 100644 --- a/docs/man/docker-history.1.md +++ b/docs/man/docker-history.1.md @@ -26,21 +26,13 @@ Show the history of when and how an image was created. Only show numeric IDs. The default is *false*. # EXAMPLES -<<<<<<< HEAD $ docker history fedora - IMAGE CREATED CREATED BY SIZE -======= - -## Show the history of images created through docker build command - - $ sudo docker history fedora IMAGE CREATED CREATED BY SIZE COMMENT ->>>>>>> Add comment column in docker history command output 105182bb5e8b 5 days ago /bin/sh -c #(nop) ADD file:71356d2ad59aa3119d 372.7 MB 73bd853d2ea5 13 days ago /bin/sh -c #(nop) MAINTAINER Lokesh Mandvekar 0 B 511136ea3c5a 10 months ago 0 B Imported from - -## Show the history of images created through docker commit command +## Display comments in the image history The `docker commit` command has a **-m** flag for adding comments to the image. These comments will be displayed in the image history. $ sudo docker history docker:scm diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index ec26d0418..507f2990b 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1151,17 +1151,7 @@ This will create a new Bash session in the container `ubuntu_bash`. To see how the `docker:latest` image was built: -<<<<<<< HEAD $ docker history docker - 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 -======= - $ sudo docker history docker IMAGE CREATED CREATED BY SIZE COMMENT 3e23a5875458 8 days ago /bin/sh -c #(nop) ENV LC_ALL=C.UTF-8 0 B 8578938dd170 8 days ago /bin/sh -c dpkg-reconfigure locales && loc 1.245 MB @@ -1172,14 +1162,13 @@ To see how the `docker:latest` image was built: To see how the `docker:apache` image was added to a container's base image: - $ sudo docker history docker:scm + $ docker history docker:scm IMAGE CREATED CREATED BY SIZE COMMENT 2ac9d1098bf1 3 months ago /bin/bash 241.4 MB Added Apache to Fedora base image 88b42ffd1f7c 5 months ago /bin/sh -c #(nop) ADD file:1fd8d7f9f6557cafc7 373.7 MB c69cab00d6ef 5 months ago /bin/sh -c #(nop) MAINTAINER Lokesh Mandvekar 0 B 511136ea3c5a 19 months ago 0 B Imported from - ->>>>>>> Add comment column in docker history command output ## images diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go index 89bb53e71..9fd2180d3 100644 --- a/integration-cli/docker_cli_history_test.go +++ b/integration-cli/docker_cli_history_test.go @@ -3,7 +3,6 @@ package main import ( "fmt" "os/exec" - "regexp" "strings" "testing" ) @@ -85,46 +84,43 @@ func TestHistoryNonExistentImage(t *testing.T) { } func TestHistoryImageWithComment(t *testing.T) { + name := "testhistoryimagewithcomment" + defer deleteContainer(name) + defer deleteImages(name) // make a image through docker commit [ -m messages ] - runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") - out, _, _, err := runCommandWithStdoutStderr(runCmd) + //runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") + runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true") + out, _, err := runCommandWithOutput(runCmd) if err != nil { t.Fatalf("failed to run container: %s, %v", out, err) } - cleanedContainerID := stripTrailingCharacters(out) - - waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID) - if _, _, err = runCommandWithOutput(waitCmd); err != nil { + waitCmd := exec.Command(dockerBinary, "wait", name) + if out, _, err := runCommandWithOutput(waitCmd); err != nil { t.Fatalf("error thrown while waiting for container: %s, %v", out, err) } - commitCmd := exec.Command(dockerBinary, "commit", "-m=This is a comment", cleanedContainerID) - out, _, err = runCommandWithOutput(commitCmd) - if err != nil { + comment := "This_is_a_comment" + + commitCmd := exec.Command(dockerBinary, "commit", "-m="+comment, name, name) + if out, _, err := runCommandWithOutput(commitCmd); err != nil { t.Fatalf("failed to commit container to image: %s, %v", out, err) } - cleanedImageID := stripTrailingCharacters(out) - deleteContainer(cleanedContainerID) - defer deleteImages(cleanedImageID) - // test docker history to check comment messages - historyCmd := exec.Command(dockerBinary, "history", cleanedImageID) + historyCmd := exec.Command(dockerBinary, "history", name) out, exitCode, err := runCommandWithOutput(historyCmd) if err != nil || exitCode != 0 { t.Fatalf("failed to get image history: %s, %v", out, err) } - expectedValue := "This is a comment" - - outputLine := strings.Split(out, "\n")[1] - outputTabs := regexp.MustCompile(" +").Split(outputLine, -1) + outputTabs := strings.Fields(strings.Split(out, "\n")[1]) + //outputTabs := regexp.MustCompile(" +").Split(outputLine, -1) actualValue := outputTabs[len(outputTabs)-1] - if !strings.Contains(actualValue, expectedValue) { - t.Fatalf("Expected comments \"%s\", but found \"%s\"", expectedValue, actualValue) + if !strings.Contains(actualValue, comment) { + t.Fatalf("Expected comments %q, but found %q", comment, actualValue) } logDone("history - history on image with comment") From 50372973884d96ee115094336ed1952b1e71250a Mon Sep 17 00:00:00 2001 From: Chen Hanxiao Date: Fri, 10 Apr 2015 00:08:05 -0400 Subject: [PATCH 355/999] cp: add support for copy filename with ":" We use ":" as separator CONTAINER:PATH. This patch enables copy filename with ":" to host. Signed-off-by: Chen Hanxiao --- api/client/cp.go | 3 ++- integration-cli/docker_cli_cp_test.go | 32 +++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/api/client/cp.go b/api/client/cp.go index f32e55187..392e36292 100644 --- a/api/client/cp.go +++ b/api/client/cp.go @@ -21,7 +21,8 @@ func (cli *DockerCli) CmdCp(args ...string) error { cmd.ParseFlags(args, true) - info := strings.Split(cmd.Arg(0), ":") + // deal with path name with `:` + info := strings.SplitN(cmd.Arg(0), ":", 2) if len(info) != 2 { return fmt.Errorf("Error: Path not specified") diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index 37e4659e9..12da76abc 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -620,3 +620,35 @@ func TestCpToStdout(t *testing.T) { } logDone("cp - to stdout") } + +func TestCpNameHasColon(t *testing.T) { + testRequires(t, SameHostDaemon) + + out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t") + if err != nil || exitCode != 0 { + t.Fatal("failed to create a container", out, err) + } + + cleanedContainerID := strings.TrimSpace(out) + defer deleteContainer(cleanedContainerID) + + out, _, err = dockerCmd(t, "wait", cleanedContainerID) + if err != nil || strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out, err) + } + + tmpdir, err := ioutil.TempDir("", "docker-integration") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/te:s:t", tmpdir) + if err != nil { + t.Fatalf("couldn't docker cp to %s: %s", tmpdir, err) + } + content, err := ioutil.ReadFile(tmpdir + "/te:s:t") + if string(content) != "lololol\n" { + t.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") + } + logDone("cp - copy filename has ':'") +} From 4ddc721f234ebb721b9540af3a9358da2f3e6e58 Mon Sep 17 00:00:00 2001 From: Chen Hanxiao Date: Fri, 10 Apr 2015 03:09:26 -0400 Subject: [PATCH 356/999] api_resize_test: fix a typo s/cintainer/container Signed-off-by: Chen Hanxiao --- integration-cli/docker_api_resize_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index 2e7677d10..be36be8c1 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -33,7 +33,7 @@ func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) { defer deleteAllContainers() cleanedContainerID := strings.TrimSpace(out) - // make sure the exited cintainer is not running + // make sure the exited container is not running runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { From 5c7c3fea6caf43d51344faaf190b869db1b44f46 Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Fri, 10 Apr 2015 18:55:07 +0800 Subject: [PATCH 357/999] Remove Job from History API a part of issue #12151 Signed-off-by: Hu Keping --- api/server/server.go | 10 +++++----- api/server/server_unit_test.go | 30 ------------------------------ graph/history.go | 21 +++++---------------- graph/service.go | 1 - 4 files changed, 10 insertions(+), 52 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 912af638c..0c92b4b2a 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -434,13 +434,13 @@ func getImagesHistory(eng *engine.Engine, version version.Version, w http.Respon return fmt.Errorf("Missing parameter") } - var job = eng.Job("history", vars["name"]) - streamJSON(job, w, false) - - if err := job.Run(); err != nil { + name := vars["name"] + history, err := getDaemon(eng).Repositories().History(name) + if err != nil { return err } - return nil + + return writeJSON(w, http.StatusOK, history) } func getContainersChanges(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index 88dadab6f..b5dd984b0 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -219,36 +219,6 @@ func TestLogsNoStreams(t *testing.T) { } } -func TestGetImagesHistory(t *testing.T) { - eng := engine.New() - imageName := "docker-test-image" - var called bool - eng.Register("history", func(job *engine.Job) error { - called = true - if len(job.Args) == 0 { - t.Fatal("Job arguments is empty") - } - if job.Args[0] != imageName { - t.Fatalf("name != '%s': %#v", imageName, job.Args[0]) - } - v := &engine.Env{} - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil - }) - r := serveRequest("GET", "/images/"+imageName+"/history", nil, eng, t) - if !called { - t.Fatalf("handler was not called") - } - if r.Code != http.StatusOK { - t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK) - } - if r.HeaderMap.Get("Content-Type") != "application/json" { - t.Fatalf("%#v\n", r) - } -} - func TestGetImagesByName(t *testing.T) { eng := engine.New() name := "image_name" diff --git a/graph/history.go b/graph/history.go index 1290de9a3..5c27dbd92 100644 --- a/graph/history.go +++ b/graph/history.go @@ -1,24 +1,17 @@ package graph import ( - "encoding/json" - "fmt" "strings" "github.com/docker/docker/api/types" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/utils" ) -func (s *TagStore) CmdHistory(job *engine.Job) error { - if n := len(job.Args); n != 1 { - return fmt.Errorf("Usage: %s IMAGE", job.Name) - } - name := job.Args[0] +func (s *TagStore) History(name string) ([]*types.ImageHistory, error) { foundImage, err := s.LookupImage(name) if err != nil { - return err + return nil, err } lookupMap := make(map[string][]string) @@ -32,10 +25,10 @@ func (s *TagStore) CmdHistory(job *engine.Job) error { } } - history := []types.ImageHistory{} + history := []*types.ImageHistory{} err = foundImage.WalkHistory(func(img *image.Image) error { - history = append(history, types.ImageHistory{ + history = append(history, &types.ImageHistory{ ID: img.ID, Created: img.Created.Unix(), CreatedBy: strings.Join(img.ContainerConfig.Cmd, " "), @@ -45,9 +38,5 @@ func (s *TagStore) CmdHistory(job *engine.Job) error { return nil }) - if err = json.NewEncoder(job.Stdout).Encode(history); err != nil { - return err - } - - return nil + return history, err } diff --git a/graph/service.go b/graph/service.go index a51d106e1..46f83103d 100644 --- a/graph/service.go +++ b/graph/service.go @@ -17,7 +17,6 @@ func (s *TagStore) Install(eng *engine.Engine) error { "image_inspect": s.CmdLookup, "image_tarlayer": s.CmdTarLayer, "image_export": s.CmdImageExport, - "history": s.CmdHistory, "viz": s.CmdViz, "load": s.CmdLoad, "import": s.CmdImport, From bfc68d10ed778bc0c2e2caedb80b65bd961c76b0 Mon Sep 17 00:00:00 2001 From: Yan Feng Date: Fri, 10 Apr 2015 11:10:26 -0400 Subject: [PATCH 358/999] A wrong key.json would remain if the TestDaemonwithwrongkey case fails. The issue would lead to failure of other cases. Signed-off-by: Yan Feng --- integration-cli/docker_cli_daemon_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 3a10fb004..c4746bf5e 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -884,8 +884,10 @@ func TestDaemonwithwrongkey(t *testing.T) { if err := d1.Start(); err == nil { d1.Stop() + os.Remove("/etc/docker/key.json") t.Fatalf("It should not be succssful to start daemon with wrong key: %v", err) } + os.Remove("/etc/docker/key.json") content, _ := ioutil.ReadFile(d1.logFile.Name()) @@ -893,6 +895,5 @@ func TestDaemonwithwrongkey(t *testing.T) { t.Fatal("Missing KeyID message from daemon logs") } - os.Remove("/etc/docker/key.json") logDone("daemon - it should be failed to start daemon with wrong key") } From 202e0380f36be93ace139cb52e6c4752d195a034 Mon Sep 17 00:00:00 2001 From: Raghuram Devarakonda Date: Wed, 8 Apr 2015 23:07:03 -0400 Subject: [PATCH 359/999] Adds example request and Json parameter information for container start API. Closes #10304. Signed-off-by: Raghuram Devarakonda --- .../reference/api/docker_remote_api_v1.18.md | 177 +++++++++++++----- .../reference/api/docker_remote_api_v1.19.md | 177 +++++++++++++----- 2 files changed, 256 insertions(+), 98 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index 75eed99da..c4cb15718 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -212,55 +212,56 @@ Json Parameters: - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` - **HostConfig** - - **Binds** – A list of volume bindings for this container. Each volume - binding is a string of the form `container_path` (to create a new - volume for the container), `host_path:container_path` (to bind-mount - a host path into the container), or `host_path:container_path:ro` - (to make the bind-mount read-only inside the container). - - **Links** - A list of links for the container. Each link entry should be of - of the form "container_name:alias". - - **LxcConf** - LXC specific configurations. These configurations will only - work when using the `lxc` execution driver. - - **PortBindings** - A map of exposed container ports and the host port they - should map to. It should be specified in the form - `{ /: [{ "HostPort": "" }] }` - Take note that `port` is specified as a string and not an integer value. - - **PublishAllPorts** - Allocates a random host port for all of a container's - exposed ports. Specified as a boolean value. - - **Privileged** - Gives the container full access to the host. Specified as - a boolean value. - - **ReadonlyRootfs** - Mount the container's root filesystem as read only. - Specified as a boolean value. - - **Dns** - A list of dns servers for the container to use. - - **DnsSearch** - A list of DNS search domains - - **ExtraHosts** - A list of hostnames/IP mappings to be added to the - container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - - **VolumesFrom** - A list of volumes to inherit from another container. - Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. - - **RestartPolicy** – The behavior to apply when the container exits. The - value is an object with a `Name` property of either `"always"` to - always restart or `"on-failure"` to restart only when the container - exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` - controls the number of times to retry before giving up. - The default is not to restart. (optional) - An ever increasing delay (double the previous delay, starting at 100mS) - is added before each restart to prevent flooding the server. - - **NetworkMode** - Sets the networking mode for the container. Supported - values are: `bridge`, `host`, and `container:` - - **Devices** - A list of devices to add to the container specified in the - form - `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` - - **Ulimits** - A list of ulimits to be set in the container, specified as - `{ "Name": , "Soft": , "Hard": }`, for example: - `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` - - **SecurityOpt**: A list of string values to customize labels for MLS - systems, such as SELinux. - - **LogConfig** - Logging configuration to container, format: - `{ "Type": "", "Config": {"key1": "val1"}}`. - Available types: `json-file`, `syslog`, `none`. - - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. + - **Binds** – A list of volume bindings for this container. Each volume + binding is a string of the form `container_path` (to create a new + volume for the container), `host_path:container_path` (to bind-mount + a host path into the container), or `host_path:container_path:ro` + (to make the bind-mount read-only inside the container). + - **Links** - A list of links for the container. Each link entry should be of + of the form `container_name:alias`. + - **LxcConf** - LXC specific configurations. These configurations will only + work when using the `lxc` execution driver. + - **PortBindings** - A map of exposed container ports and the host port they + should map to. It should be specified in the form + `{ /: [{ "HostPort": "" }] }` + Take note that `port` is specified as a string and not an integer value. + - **PublishAllPorts** - Allocates a random host port for all of a container's + exposed ports. Specified as a boolean value. + - **Privileged** - Gives the container full access to the host. Specified as + a boolean value. + - **ReadonlyRootfs** - Mount the container's root filesystem as read only. + Specified as a boolean value. + - **Dns** - A list of dns servers for the container to use. + - **DnsSearch** - A list of DNS search domains + - **ExtraHosts** - A list of hostnames/IP mappings to be added to the + container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. + - **VolumesFrom** - A list of volumes to inherit from another container. + Specified in the form `[:]` + - **CapAdd** - A list of kernel capabilties to add to the container. + - **Capdrop** - A list of kernel capabilties to drop from the container. + - **RestartPolicy** – The behavior to apply when the container exits. The + value is an object with a `Name` property of either `"always"` to + always restart or `"on-failure"` to restart only when the container + exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` + controls the number of times to retry before giving up. + The default is not to restart. (optional) + An ever increasing delay (double the previous delay, starting at 100mS) + is added before each restart to prevent flooding the server. + - **NetworkMode** - Sets the networking mode for the container. Supported + values are: `bridge`, `host`, and `container:` + - **Devices** - A list of devices to add to the container specified in the + form + `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` + - **Ulimits** - A list of ulimits to be set in the container, specified as + `{ "Name": , "Soft": , "Hard": }`, for example: + `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` + - **SecurityOpt**: A list of string values to customize labels for MLS + systems, such as SELinux. + - **LogConfig** - Log configuration for the container, specified as + `{ "Type": "", "Config": {"key1": "val1"}}`. + Available types: `json-file`, `syslog`, `none`. + `json-file` logging driver. + - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. Query Parameters: @@ -675,12 +676,90 @@ Start the container `id` POST /containers/(id)/start HTTP/1.1 Content-Type: application/json + { + "Binds": ["/tmp:/tmp"], + "Links": ["redis3:redis"], + "LxcConf": {"lxc.utsname":"docker"}, + "Memory": 0, + "MemorySwap": 0, + "CpuShares": 512, + "CpusetCpus": "0,1", + "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts": false, + "Privileged": false, + "ReadonlyRootfs": false, + "Dns": ["8.8.8.8"], + "DnsSearch": [""], + "ExtraHosts": null, + "VolumesFrom": ["parent", "other:ro"], + "CapAdd": ["NET_ADMIN"], + "CapDrop": ["MKNOD"], + "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, + "NetworkMode": "bridge", + "Devices": [], + "Ulimits": [{}], + "LogConfig": { "Type": "json-file", Config: {} }, + "SecurityOpt": [""], + "CgroupParent": "" + } + **Example response**: HTTP/1.1 204 No Content Json Parameters: +- **Binds** – A list of volume bindings for this container. Each volume + binding is a string of the form `container_path` (to create a new + volume for the container), `host_path:container_path` (to bind-mount + a host path into the container), or `host_path:container_path:ro` + (to make the bind-mount read-only inside the container). +- **Links** - A list of links for the container. Each link entry should be of + of the form `container_name:alias`. +- **LxcConf** - LXC specific configurations. These configurations will only + work when using the `lxc` execution driver. +- **PortBindings** - A map of exposed container ports and the host port they + should map to. It should be specified in the form + `{ /: [{ "HostPort": "" }] }` + Take note that `port` is specified as a string and not an integer value. +- **PublishAllPorts** - Allocates a random host port for all of a container's + exposed ports. Specified as a boolean value. +- **Privileged** - Gives the container full access to the host. Specified as + a boolean value. +- **ReadonlyRootfs** - Mount the container's root filesystem as read only. + Specified as a boolean value. +- **Dns** - A list of dns servers for the container to use. +- **DnsSearch** - A list of DNS search domains +- **ExtraHosts** - A list of hostnames/IP mappings to be added to the + container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. +- **VolumesFrom** - A list of volumes to inherit from another container. + Specified in the form `[:]` +- **CapAdd** - A list of kernel capabilties to add to the container. +- **Capdrop** - A list of kernel capabilties to drop from the container. +- **RestartPolicy** – The behavior to apply when the container exits. The + value is an object with a `Name` property of either `"always"` to + always restart or `"on-failure"` to restart only when the container + exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` + controls the number of times to retry before giving up. + The default is not to restart. (optional) + An ever increasing delay (double the previous delay, starting at 100mS) + is added before each restart to prevent flooding the server. +- **NetworkMode** - Sets the networking mode for the container. Supported + values are: `bridge`, `host`, and `container:` +- **Devices** - A list of devices to add to the container specified in the + form + `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` +- **Ulimits** - A list of ulimits to be set in the container, specified as + `{ "Name": , "Soft": , "Hard": }`, for example: + `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` +- **SecurityOpt**: A list of string values to customize labels for MLS + systems, such as SELinux. +- **LogConfig** - Log configuration for the container, specified as + `{ "Type": "", "Config": {"key1": "val1"}}`. + Available types: `json-file`, `syslog`, `none`. + `json-file` logging driver. +- **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. + Status Codes: - **204** – no error diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index b643d4490..3543f7430 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -212,55 +212,56 @@ Json Parameters: - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` - **HostConfig** - - **Binds** – A list of volume bindings for this container. Each volume - binding is a string of the form `container_path` (to create a new - volume for the container), `host_path:container_path` (to bind-mount - a host path into the container), or `host_path:container_path:ro` - (to make the bind-mount read-only inside the container). - - **Links** - A list of links for the container. Each link entry should be of - of the form "container_name:alias". - - **LxcConf** - LXC specific configurations. These configurations will only - work when using the `lxc` execution driver. - - **PortBindings** - A map of exposed container ports and the host port they - should map to. It should be specified in the form - `{ /: [{ "HostPort": "" }] }` - Take note that `port` is specified as a string and not an integer value. - - **PublishAllPorts** - Allocates a random host port for all of a container's - exposed ports. Specified as a boolean value. - - **Privileged** - Gives the container full access to the host. Specified as - a boolean value. - - **ReadonlyRootfs** - Mount the container's root filesystem as read only. - Specified as a boolean value. - - **Dns** - A list of dns servers for the container to use. - - **DnsSearch** - A list of DNS search domains - - **ExtraHosts** - A list of hostnames/IP mappings to be added to the - container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - - **VolumesFrom** - A list of volumes to inherit from another container. - Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. - - **RestartPolicy** – The behavior to apply when the container exits. The - value is an object with a `Name` property of either `"always"` to - always restart or `"on-failure"` to restart only when the container - exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` - controls the number of times to retry before giving up. - The default is not to restart. (optional) - An ever increasing delay (double the previous delay, starting at 100mS) - is added before each restart to prevent flooding the server. - - **NetworkMode** - Sets the networking mode for the container. Supported - values are: `bridge`, `host`, and `container:` - - **Devices** - A list of devices to add to the container specified in the - form - `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` - - **Ulimits** - A list of ulimits to be set in the container, specified as - `{ "Name": , "Soft": , "Hard": }`, for example: - `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` - - **SecurityOpt**: A list of string values to customize labels for MLS - systems, such as SELinux. - - **LogConfig** - Logging configuration to container, format: - `{ "Type": "", "Config": {"key1": "val1"}}`. - Available types: `json-file`, `syslog`, `none`. - - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. + - **Binds** – A list of volume bindings for this container. Each volume + binding is a string of the form `container_path` (to create a new + volume for the container), `host_path:container_path` (to bind-mount + a host path into the container), or `host_path:container_path:ro` + (to make the bind-mount read-only inside the container). + - **Links** - A list of links for the container. Each link entry should be of + of the form `container_name:alias`. + - **LxcConf** - LXC specific configurations. These configurations will only + work when using the `lxc` execution driver. + - **PortBindings** - A map of exposed container ports and the host port they + should map to. It should be specified in the form + `{ /: [{ "HostPort": "" }] }` + Take note that `port` is specified as a string and not an integer value. + - **PublishAllPorts** - Allocates a random host port for all of a container's + exposed ports. Specified as a boolean value. + - **Privileged** - Gives the container full access to the host. Specified as + a boolean value. + - **ReadonlyRootfs** - Mount the container's root filesystem as read only. + Specified as a boolean value. + - **Dns** - A list of dns servers for the container to use. + - **DnsSearch** - A list of DNS search domains + - **ExtraHosts** - A list of hostnames/IP mappings to be added to the + container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. + - **VolumesFrom** - A list of volumes to inherit from another container. + Specified in the form `[:]` + - **CapAdd** - A list of kernel capabilties to add to the container. + - **Capdrop** - A list of kernel capabilties to drop from the container. + - **RestartPolicy** – The behavior to apply when the container exits. The + value is an object with a `Name` property of either `"always"` to + always restart or `"on-failure"` to restart only when the container + exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` + controls the number of times to retry before giving up. + The default is not to restart. (optional) + An ever increasing delay (double the previous delay, starting at 100mS) + is added before each restart to prevent flooding the server. + - **NetworkMode** - Sets the networking mode for the container. Supported + values are: `bridge`, `host`, and `container:` + - **Devices** - A list of devices to add to the container specified in the + form + `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` + - **Ulimits** - A list of ulimits to be set in the container, specified as + `{ "Name": , "Soft": , "Hard": }`, for example: + `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` + - **SecurityOpt**: A list of string values to customize labels for MLS + systems, such as SELinux. + - **LogConfig** - Log configuration for the container, specified as + `{ "Type": "", "Config": {"key1": "val1"}}`. + Available types: `json-file`, `syslog`, `none`. + `json-file` logging driver. + - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. Query Parameters: @@ -675,12 +676,90 @@ Start the container `id` POST /containers/(id)/start HTTP/1.1 Content-Type: application/json + { + "Binds": ["/tmp:/tmp"], + "Links": ["redis3:redis"], + "LxcConf": {"lxc.utsname":"docker"}, + "Memory": 0, + "MemorySwap": 0, + "CpuShares": 512, + "CpusetCpus": "0,1", + "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts": false, + "Privileged": false, + "ReadonlyRootfs": false, + "Dns": ["8.8.8.8"], + "DnsSearch": [""], + "ExtraHosts": null, + "VolumesFrom": ["parent", "other:ro"], + "CapAdd": ["NET_ADMIN"], + "CapDrop": ["MKNOD"], + "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, + "NetworkMode": "bridge", + "Devices": [], + "Ulimits": [{}], + "LogConfig": { "Type": "json-file", Config: {} }, + "SecurityOpt": [""], + "CgroupParent": "" + } + **Example response**: HTTP/1.1 204 No Content Json Parameters: +- **Binds** – A list of volume bindings for this container. Each volume + binding is a string of the form `container_path` (to create a new + volume for the container), `host_path:container_path` (to bind-mount + a host path into the container), or `host_path:container_path:ro` + (to make the bind-mount read-only inside the container). +- **Links** - A list of links for the container. Each link entry should be of + of the form `container_name:alias`. +- **LxcConf** - LXC specific configurations. These configurations will only + work when using the `lxc` execution driver. +- **PortBindings** - A map of exposed container ports and the host port they + should map to. It should be specified in the form + `{ /: [{ "HostPort": "" }] }` + Take note that `port` is specified as a string and not an integer value. +- **PublishAllPorts** - Allocates a random host port for all of a container's + exposed ports. Specified as a boolean value. +- **Privileged** - Gives the container full access to the host. Specified as + a boolean value. +- **ReadonlyRootfs** - Mount the container's root filesystem as read only. + Specified as a boolean value. +- **Dns** - A list of dns servers for the container to use. +- **DnsSearch** - A list of DNS search domains +- **ExtraHosts** - A list of hostnames/IP mappings to be added to the + container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. +- **VolumesFrom** - A list of volumes to inherit from another container. + Specified in the form `[:]` +- **CapAdd** - A list of kernel capabilties to add to the container. +- **Capdrop** - A list of kernel capabilties to drop from the container. +- **RestartPolicy** – The behavior to apply when the container exits. The + value is an object with a `Name` property of either `"always"` to + always restart or `"on-failure"` to restart only when the container + exit code is non-zero. If `on-failure` is used, `MaximumRetryCount` + controls the number of times to retry before giving up. + The default is not to restart. (optional) + An ever increasing delay (double the previous delay, starting at 100mS) + is added before each restart to prevent flooding the server. +- **NetworkMode** - Sets the networking mode for the container. Supported + values are: `bridge`, `host`, and `container:` +- **Devices** - A list of devices to add to the container specified in the + form + `{ "PathOnHost": "/dev/deviceName", "PathInContainer": "/dev/deviceName", "CgroupPermissions": "mrw"}` +- **Ulimits** - A list of ulimits to be set in the container, specified as + `{ "Name": , "Soft": , "Hard": }`, for example: + `Ulimits: { "Name": "nofile", "Soft": 1024, "Hard", 2048 }}` +- **SecurityOpt**: A list of string values to customize labels for MLS + systems, such as SELinux. +- **LogConfig** - Log configuration for the container, specified as + `{ "Type": "", "Config": {"key1": "val1"}}`. + Available types: `json-file`, `syslog`, `none`. + `json-file` logging driver. +- **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. + Status Codes: - **204** – no error From 8d1a500303a9dd892301e343c94821207c715fbb Mon Sep 17 00:00:00 2001 From: Richard Burnison Date: Fri, 10 Apr 2015 13:09:08 -0400 Subject: [PATCH 360/999] Only use fallback to short IDs when obvious. As reported in #11294, the Docker daemon will execute contains it shouldn't run in the event that a requested tag is present in an image's ID. This leads to the wrong image being started up silently. This change reduces the risk of such a collision by using the short ID iff the actual revOrTag looks like a short ID (not that it necessarily is). Signed-off-by: Richard Burnison --- graph/tags.go | 9 ++++++--- pkg/stringid/stringid.go | 14 +++++++++++--- pkg/stringid/stringid_test.go | 23 ++++++++++++++++++++++- 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/graph/tags.go b/graph/tags.go index 6346ea8b5..74d86141d 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -335,9 +335,12 @@ func (store *TagStore) GetImage(repoName, refOrID string) (*image.Image, error) } // If no matching tag is found, search through images for a matching image id - for _, revision := range repo { - if strings.HasPrefix(revision, refOrID) { - return store.graph.Get(revision) + // iff it looks like a short ID or would look like a short ID + if stringid.IsShortID(stringid.TruncateID(refOrID)) { + for _, revision := range repo { + if strings.HasPrefix(revision, refOrID) { + return store.graph.Get(revision) + } } } diff --git a/pkg/stringid/stringid.go b/pkg/stringid/stringid.go index bf39df9b7..3e6ff2a92 100644 --- a/pkg/stringid/stringid.go +++ b/pkg/stringid/stringid.go @@ -4,19 +4,27 @@ import ( "crypto/rand" "encoding/hex" "io" + "regexp" "strconv" ) +const shortLen = 12 + +// Determine if an arbitrary string *looks like* a short ID. +func IsShortID(id string) bool { + return regexp.MustCompile("^[a-z0-9]{12}$").MatchString(id) +} + // TruncateID returns a shorthand version of a string identifier for convenience. // A collision with other shorthands is very unlikely, but possible. // In case of a collision a lookup with TruncIndex.Get() will fail, and the caller // will need to use a langer prefix, or the full-length Id. func TruncateID(id string) string { - shortLen := 12 + trimTo := shortLen if len(id) < shortLen { - shortLen = len(id) + trimTo = len(id) } - return id[:shortLen] + return id[:trimTo] } // GenerateRandomID returns an unique id diff --git a/pkg/stringid/stringid_test.go b/pkg/stringid/stringid_test.go index 21f8f8a2f..bcb136549 100644 --- a/pkg/stringid/stringid_test.go +++ b/pkg/stringid/stringid_test.go @@ -1,6 +1,9 @@ package stringid -import "testing" +import ( + "strings" + "testing" +) func TestGenerateRandomID(t *testing.T) { id := GenerateRandomID() @@ -33,3 +36,21 @@ func TestShortenIdInvalid(t *testing.T) { t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID) } } + +func TestIsShortIDNonHex(t *testing.T) { + id := "some non-hex value" + if IsShortID(id) { + t.Fatalf("%s is not a short ID", id) + } +} + +func TestIsShortIDNotCorrectSize(t *testing.T) { + id := strings.Repeat("a", shortLen+1) + if IsShortID(id) { + t.Fatalf("%s is not a short ID", id) + } + id = strings.Repeat("a", shortLen-1) + if IsShortID(id) { + t.Fatalf("%s is not a short ID", id) + } +} From e7a2e2bf7e5adbbed0443cc99ab728d5118b3b7b Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Fri, 10 Apr 2015 10:45:38 -0700 Subject: [PATCH 361/999] Rename TestStartSilentAttach to TestStartAttachSilent Signed-off-by: Megan Kostick --- integration-cli/docker_cli_start_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index 25b23e888..c703e434c 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -69,7 +69,7 @@ func TestStartAttachCorrectExitCode(t *testing.T) { logDone("start - correct exit code returned with -a") } -func TestStartSilentAttach(t *testing.T) { +func TestStartAttachSilent(t *testing.T) { defer deleteAllContainers() name := "teststartattachcorrectexitcode" From db0ffba3b92aeda667501aaa10926943a7738f82 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 9 Apr 2015 23:11:11 +0200 Subject: [PATCH 362/999] Remove job from wait Signed-off-by: Antonio Murdaca --- api/server/server.go | 18 ++++---- daemon/daemon.go | 1 - daemon/wait.go | 22 --------- integration-cli/docker_cli_daemon_test.go | 41 +++++++++++++++++ integration/server_test.go | 56 ----------------------- 5 files changed, 49 insertions(+), 89 deletions(-) delete mode 100644 daemon/wait.go diff --git a/api/server/server.go b/api/server/server.go index de3e86181..ffbef8cee 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1005,20 +1005,18 @@ func postContainersWait(eng *engine.Engine, version version.Version, w http.Resp if vars == nil { return fmt.Errorf("Missing parameter") } - var ( - stdoutBuffer = bytes.NewBuffer(nil) - job = eng.Job("wait", vars["name"]) - ) - job.Stdout.Add(stdoutBuffer) - if err := job.Run(); err != nil { - return err - } - statusCode, err := strconv.Atoi(engine.Tail(stdoutBuffer, 1)) + + name := vars["name"] + d := getDaemon(eng) + cont, err := d.Get(name) if err != nil { return err } + + status, _ := cont.WaitStop(-1 * time.Second) + return writeJSON(w, http.StatusOK, &types.ContainerWaitResponse{ - StatusCode: statusCode, + StatusCode: status, }) } diff --git a/daemon/daemon.go b/daemon/daemon.go index e22790e93..86ed71e23 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -127,7 +127,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "restart": daemon.ContainerRestart, "start": daemon.ContainerStart, "stop": daemon.ContainerStop, - "wait": daemon.ContainerWait, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, "execInspect": daemon.ContainerExecInspect, diff --git a/daemon/wait.go b/daemon/wait.go deleted file mode 100644 index 5c1f44beb..000000000 --- a/daemon/wait.go +++ /dev/null @@ -1,22 +0,0 @@ -package daemon - -import ( - "fmt" - "time" - - "github.com/docker/docker/engine" -) - -func (daemon *Daemon) ContainerWait(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s", job.Name) - } - name := job.Args[0] - container, err := daemon.Get(name) - if err != nil { - return fmt.Errorf("%s: %v", job.Name, err) - } - status, _ := container.WaitStop(-1 * time.Second) - job.Printf("%d\n", status) - return nil -} diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 3a10fb004..684f1978e 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -896,3 +896,44 @@ func TestDaemonwithwrongkey(t *testing.T) { os.Remove("/etc/docker/key.json") logDone("daemon - it should be failed to start daemon with wrong key") } + +func TestDaemonRestartKillWait(t *testing.T) { + d := NewDaemon(t) + if err := d.StartWithBusybox(); err != nil { + t.Fatalf("Could not start daemon with busybox: %v", err) + } + defer d.Stop() + + out, err := d.Cmd("run", "-d", "busybox", "/bin/cat") + if err != nil { + t.Fatalf("Could not run /bin/cat: err=%v\n%s", err, out) + } + containerID := strings.TrimSpace(out) + + if out, err := d.Cmd("kill", containerID); err != nil { + t.Fatalf("Could not kill %s: err=%v\n%s", containerID, err, out) + } + + if err := d.Restart(); err != nil { + t.Fatalf("Could not restart daemon: %v", err) + } + + errchan := make(chan error) + go func() { + if out, err := d.Cmd("wait", containerID); err != nil { + errchan <- fmt.Errorf("%v:\n%s", err, out) + } + close(errchan) + }() + + select { + case <-time.After(5 * time.Second): + t.Fatal("Waiting on a stopped (killed) container timed out") + case err := <-errchan: + if err != nil { + t.Fatal(err) + } + } + + logDone("wait - wait on a stopped container doesn't timeout") +} diff --git a/integration/server_test.go b/integration/server_test.go index 5dc4f1aa4..34c56f4a8 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -3,10 +3,8 @@ package docker import ( "bytes" "testing" - "time" "github.com/docker/docker/builder" - "github.com/docker/docker/daemon" "github.com/docker/docker/engine" ) @@ -103,60 +101,6 @@ func TestMergeConfigOnCommit(t *testing.T) { } } -func TestRestartKillWait(t *testing.T) { - eng := NewTestEngine(t) - runtime := mkDaemonFromEngine(eng, t) - defer runtime.Nuke() - - config, hostConfig, _, err := parseRun([]string{"-i", unitTestImageID, "/bin/cat"}) - if err != nil { - t.Fatal(err) - } - - id := createTestContainer(eng, config, t) - - containers, err := runtime.Containers(&daemon.ContainersConfig{All: true}) - - if err != nil { - t.Errorf("Error getting containers1: %q", err) - } - - if len(containers) != 1 { - t.Errorf("Expected 1 container, %v found", len(containers)) - } - - job := eng.Job("start", id) - if err := job.ImportEnv(hostConfig); err != nil { - t.Fatal(err) - } - if err := job.Run(); err != nil { - t.Fatal(err) - } - - if err := runtime.ContainerKill(id, 0); err != nil { - t.Fatal(err) - } - - eng = newTestEngine(t, false, runtime.Config().Root) - runtime = mkDaemonFromEngine(eng, t) - - containers, err = runtime.Containers(&daemon.ContainersConfig{All: true}) - - if err != nil { - t.Errorf("Error getting containers1: %q", err) - } - if len(containers) != 1 { - t.Errorf("Expected 1 container, %v found", len(containers)) - } - - setTimeout(t, "Waiting on stopped container timedout", 5*time.Second, func() { - job = eng.Job("wait", containers[0].ID) - if err := job.Run(); err != nil { - t.Fatal(err) - } - }) -} - func TestRunWithTooLowMemoryLimit(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From 0e21782de5c038dfa3cfdfc7655b9e6b143baa7b Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Tue, 17 Mar 2015 10:44:42 -0400 Subject: [PATCH 363/999] devmapper: storage-opt override for udev sync This provides an override for forcing the daemon to still attempt running the devicemapper driver even when udev sync is not supported. Intended to be a very clear impairment for those choosing to use it. If udev sync is false, there will still be an error in the daemon logs, even when the override is in place. The docs have an explicit WARNING. Including link to the docs for users that encounter this daemon error during an upgrade. Signed-off-by: Vincent Batts --- daemon/graphdriver/devmapper/README.md | 38 +++++++++++- daemon/graphdriver/devmapper/deviceset.go | 62 +++++++++++-------- .../graphdriver/devmapper/devmapper_test.go | 1 + docs/sources/reference/commandline/cli.md | 35 +++++++++++ 4 files changed, 109 insertions(+), 27 deletions(-) diff --git a/daemon/graphdriver/devmapper/README.md b/daemon/graphdriver/devmapper/README.md index 1dc918016..a090b731f 100644 --- a/daemon/graphdriver/devmapper/README.md +++ b/daemon/graphdriver/devmapper/README.md @@ -186,7 +186,7 @@ Here is the list of supported options: can be achieved by zeroing the first 4k to indicate empty metadata, like this: - ``dd if=/dev/zero of=$metadata_dev bs=4096 count=1``` + ``dd if=/dev/zero of=$metadata_dev bs=4096 count=1`` Example use: @@ -216,3 +216,39 @@ Here is the list of supported options: Example use: ``docker -d --storage-opt dm.blkdiscard=false`` + + * `dm.override_udev_sync_check` + + Overrides the `udev` synchronization checks between `devicemapper` and `udev`. + `udev` is the device manager for the Linux kernel. + + To view the `udev` sync support of a Docker daemon that is using the + `devicemapper` driver, run: + + $ docker info + [...] + Udev Sync Supported: true + [...] + + When `udev` sync support is `true`, then `devicemapper` and udev can + coordinate the activation and deactivation of devices for containers. + + When `udev` sync support is `false`, a race condition occurs between + the`devicemapper` and `udev` during create and cleanup. The race condition + results in errors and failures. (For information on these failures, see + [docker#4036](https://github.com/docker/docker/issues/4036)) + + To allow the `docker` daemon to start, regardless of `udev` sync not being + supported, set `dm.override_udev_sync_check` to true: + + $ docker -d --storage-opt dm.override_udev_sync_check=true + + When this value is `true`, the `devicemapper` continues and simply warns + you the errors are happening. + + > **Note**: The ideal is to pursue a `docker` daemon and environment that + > does support synchronizing with `udev`. For further discussion on this + > topic, see [docker#4036](https://github.com/docker/docker/issues/4036). + > Otherwise, set this flag for migrating existing Docker daemons to a + > daemon with a supported environment. + diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 7e0a13a95..4d6ce5a2a 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -30,7 +30,8 @@ var ( DefaultDataLoopbackSize int64 = 100 * 1024 * 1024 * 1024 DefaultMetaDataLoopbackSize int64 = 2 * 1024 * 1024 * 1024 DefaultBaseFsSize uint64 = 10 * 1024 * 1024 * 1024 - DefaultThinpBlockSize uint32 = 128 // 64K = 128 512b sectors + DefaultThinpBlockSize uint32 = 128 // 64K = 128 512b sectors + DefaultUdevSyncOverride bool = false MaxDeviceId int = 0xffffff // 24 bit, pool limit DeviceIdMapSz int = (MaxDeviceId + 1) / 8 ) @@ -83,20 +84,21 @@ type DeviceSet struct { deviceIdMap []byte // Options - dataLoopbackSize int64 - metaDataLoopbackSize int64 - baseFsSize uint64 - filesystem string - mountOptions string - mkfsArgs []string - dataDevice string // block or loop dev - dataLoopFile string // loopback file, if used - metadataDevice string // block or loop dev - metadataLoopFile string // loopback file, if used - doBlkDiscard bool - thinpBlockSize uint32 - thinPoolDevice string - Transaction `json:"-"` + dataLoopbackSize int64 + metaDataLoopbackSize int64 + baseFsSize uint64 + filesystem string + mountOptions string + mkfsArgs []string + dataDevice string // block or loop dev + dataLoopFile string // loopback file, if used + metadataDevice string // block or loop dev + metadataLoopFile string // loopback file, if used + doBlkDiscard bool + thinpBlockSize uint32 + thinPoolDevice string + Transaction `json:"-"` + overrideUdevSyncCheck bool } type DiskUsage struct { @@ -963,8 +965,10 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // https://github.com/docker/docker/issues/4036 if supported := devicemapper.UdevSetSyncSupport(true); !supported { - logrus.Errorf("Udev sync is not supported. This will lead to unexpected behavior, data loss and errors") - return graphdriver.ErrNotSupported + logrus.Errorf("Udev sync is not supported. This will lead to unexpected behavior, data loss and errors. For more information, see https://docs.docker.com/reference/commandline/cli/#daemon-storage-driver-option") + if !devices.overrideUdevSyncCheck { + return graphdriver.ErrNotSupported + } } if err := os.MkdirAll(devices.metadataDir(), 0700); err != nil && !os.IsExist(err) { @@ -1656,15 +1660,16 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error devicemapper.SetDevDir("/dev") devices := &DeviceSet{ - root: root, - MetaData: MetaData{Devices: make(map[string]*DevInfo)}, - dataLoopbackSize: DefaultDataLoopbackSize, - metaDataLoopbackSize: DefaultMetaDataLoopbackSize, - baseFsSize: DefaultBaseFsSize, - filesystem: "ext4", - doBlkDiscard: true, - thinpBlockSize: DefaultThinpBlockSize, - deviceIdMap: make([]byte, DeviceIdMapSz), + root: root, + MetaData: MetaData{Devices: make(map[string]*DevInfo)}, + dataLoopbackSize: DefaultDataLoopbackSize, + metaDataLoopbackSize: DefaultMetaDataLoopbackSize, + baseFsSize: DefaultBaseFsSize, + overrideUdevSyncCheck: DefaultUdevSyncOverride, + filesystem: "ext4", + doBlkDiscard: true, + thinpBlockSize: DefaultThinpBlockSize, + deviceIdMap: make([]byte, DeviceIdMapSz), } foundBlkDiscard := false @@ -1721,6 +1726,11 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error } // convert to 512b sectors devices.thinpBlockSize = uint32(size) >> 9 + case "dm.override_udev_sync_check": + devices.overrideUdevSyncCheck, err = strconv.ParseBool(val) + if err != nil { + return nil, err + } default: return nil, fmt.Errorf("Unknown option %s\n", key) } diff --git a/daemon/graphdriver/devmapper/devmapper_test.go b/daemon/graphdriver/devmapper/devmapper_test.go index 6cb757238..60006af5a 100644 --- a/daemon/graphdriver/devmapper/devmapper_test.go +++ b/daemon/graphdriver/devmapper/devmapper_test.go @@ -13,6 +13,7 @@ func init() { DefaultDataLoopbackSize = 300 * 1024 * 1024 DefaultMetaDataLoopbackSize = 200 * 1024 * 1024 DefaultBaseFsSize = 300 * 1024 * 1024 + DefaultUdevSyncOverride = true if err := graphtest.InitLoopbacks(); err != nil { panic(err) } diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 2d59a64bb..b71360a9f 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -370,6 +370,41 @@ Currently supported options are: $ docker -d --storage-opt dm.blkdiscard=false + * `dm.override_udev_sync_check` + + Overrides the `udev` synchronization checks between `devicemapper` and `udev`. + `udev` is the device manager for the Linux kernel. + + To view the `udev` sync support of a Docker daemon that is using the + `devicemapper` driver, run: + + $ docker info + [...] + Udev Sync Supported: true + [...] + + When `udev` sync support is `true`, then `devicemapper` and udev can + coordinate the activation and deactivation of devices for containers. + + When `udev` sync support is `false`, a race condition occurs between + the`devicemapper` and `udev` during create and cleanup. The race condition + results in errors and failures. (For information on these failures, see + [docker#4036](https://github.com/docker/docker/issues/4036)) + + To allow the `docker` daemon to start, regardless of `udev` sync not being + supported, set `dm.override_udev_sync_check` to true: + + $ docker -d --storage-opt dm.override_udev_sync_check=true + + When this value is `true`, the `devicemapper` continues and simply warns + you the errors are happening. + + > **Note**: The ideal is to pursue a `docker` daemon and environment that + > does support synchronizing with `udev`. For further discussion on this + > topic, see [docker#4036](https://github.com/docker/docker/issues/4036). + > Otherwise, set this flag for migrating existing Docker daemons to a + > daemon with a supported environment. + ### Docker exec-driver option The Docker daemon uses a specifically built `libcontainer` execution driver as its From 73d08528ea9b043cbe941b1219b1c55625c636a8 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 10 Apr 2015 18:00:33 +0000 Subject: [PATCH 364/999] Unwanted declaration causing compilation issues with gccgo Signed-off-by: Srini Brahmaroutu --- engine/shutdown_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/engine/shutdown_test.go b/engine/shutdown_test.go index cde177e39..d2ef0339d 100644 --- a/engine/shutdown_test.go +++ b/engine/shutdown_test.go @@ -18,9 +18,7 @@ func TestShutdownEmpty(t *testing.T) { func TestShutdownAfterRun(t *testing.T) { eng := New() - var called bool eng.Register("foo", func(job *Job) error { - called = true return nil }) if err := eng.Job("foo").Run(); err != nil { From c8529fde5f6f2e4b62f9c1b3382fd814c11a7639 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 10 Apr 2015 22:41:43 +0200 Subject: [PATCH 365/999] Remove job from commit Signed-off-by: Antonio Murdaca --- api/server/server.go | 38 ++++++++-------- daemon/commit.go | 43 +++++++++++------- daemon/daemon.go | 1 - integration/server_test.go | 89 +------------------------------------- 4 files changed, 46 insertions(+), 125 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index ffbef8cee..8a9132092 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -618,39 +618,37 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit if err := parseForm(r); err != nil { return err } - var ( - config engine.Env - job = eng.Job("commit", r.Form.Get("container")) - stdoutBuffer = bytes.NewBuffer(nil) - ) if err := checkForJson(r); err != nil { return err } - if err := config.Decode(r.Body); err != nil { - logrus.Errorf("%s", err) - } + cont := r.Form.Get("container") + pause := toBool(r.Form.Get("pause")) if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { - job.Setenv("pause", "1") - } else { - job.Setenv("pause", r.FormValue("pause")) + pause = true } - job.Setenv("repo", r.Form.Get("repo")) - job.Setenv("tag", r.Form.Get("tag")) - job.Setenv("author", r.Form.Get("author")) - job.Setenv("comment", r.Form.Get("comment")) - job.SetenvList("changes", r.Form["changes"]) - job.SetenvSubEnv("config", &config) + containerCommitConfig := &daemon.ContainerCommitConfig{ + Pause: pause, + Repo: r.Form.Get("repo"), + Tag: r.Form.Get("tag"), + Author: r.Form.Get("author"), + Comment: r.Form.Get("comment"), + Changes: r.Form["changes"], + Config: r.Body, + } - job.Stdout.Add(stdoutBuffer) - if err := job.Run(); err != nil { + d := getDaemon(eng) + + imgID, err := d.ContainerCommit(cont, containerCommitConfig) + if err != nil { return err } + return writeJSON(w, http.StatusCreated, &types.ContainerCommitResponse{ - ID: engine.Tail(stdoutBuffer, 1), + ID: imgID, }) } diff --git a/daemon/commit.go b/daemon/commit.go index 1daf57a4f..1e534cf62 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -3,53 +3,64 @@ package daemon import ( "bytes" "encoding/json" - "fmt" + "io" + "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/runconfig" ) -func (daemon *Daemon) ContainerCommit(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Not enough arguments. Usage: %s CONTAINER\n", job.Name) - } - name := job.Args[0] +type ContainerCommitConfig struct { + Pause bool + Repo string + Tag string + Author string + Comment string + Changes []string + Config io.ReadCloser +} +func (daemon *Daemon) ContainerCommit(name string, c *ContainerCommitConfig) (string, error) { container, err := daemon.Get(name) if err != nil { - return err + return "", err } var ( + subenv engine.Env config = container.Config stdoutBuffer = bytes.NewBuffer(nil) newConfig runconfig.Config ) + if err := subenv.Decode(c.Config); err != nil { + logrus.Errorf("%s", err) + } + buildConfigJob := daemon.eng.Job("build_config") buildConfigJob.Stdout.Add(stdoutBuffer) - buildConfigJob.Setenv("changes", job.Getenv("changes")) + buildConfigJob.SetenvList("changes", c.Changes) // FIXME this should be remove when we remove deprecated config param - buildConfigJob.Setenv("config", job.Getenv("config")) + buildConfigJob.SetenvSubEnv("config", &subenv) if err := buildConfigJob.Run(); err != nil { - return err + return "", err } if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil { - return err + return "", err } if err := runconfig.Merge(&newConfig, config); err != nil { - return err + return "", err } - img, err := daemon.Commit(container, job.Getenv("repo"), job.Getenv("tag"), job.Getenv("comment"), job.Getenv("author"), job.GetenvBool("pause"), &newConfig) + img, err := daemon.Commit(container, c.Repo, c.Tag, c.Comment, c.Author, c.Pause, &newConfig) if err != nil { - return err + return "", err } - job.Printf("%s\n", img.ID) - return nil + + return img.ID, nil } // Commit creates a new filesystem image from the current state of a container. diff --git a/daemon/daemon.go b/daemon/daemon.go index 86ed71e23..f24d7dc04 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -117,7 +117,6 @@ type Daemon struct { // Install installs daemon capabilities to eng. func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ - "commit": daemon.ContainerCommit, "container_inspect": daemon.ContainerInspect, "container_stats": daemon.ContainerStats, "create": daemon.ContainerCreate, diff --git a/integration/server_test.go b/integration/server_test.go index 34c56f4a8..9745d9ce0 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -1,12 +1,6 @@ package docker -import ( - "bytes" - "testing" - - "github.com/docker/docker/builder" - "github.com/docker/docker/engine" -) +import "testing" func TestCreateNumberHostname(t *testing.T) { eng := NewTestEngine(t) @@ -20,87 +14,6 @@ func TestCreateNumberHostname(t *testing.T) { createTestContainer(eng, config, t) } -func TestCommit(t *testing.T) { - eng := NewTestEngine(t) - b := &builder.BuilderJob{Engine: eng} - b.Install() - defer mkDaemonFromEngine(eng, t).Nuke() - - config, _, _, err := parseRun([]string{unitTestImageID, "/bin/cat"}) - if err != nil { - t.Fatal(err) - } - - id := createTestContainer(eng, config, t) - - job := eng.Job("commit", id) - job.Setenv("repo", "testrepo") - job.Setenv("tag", "testtag") - job.SetenvJson("config", config) - if err := job.Run(); err != nil { - t.Fatal(err) - } -} - -func TestMergeConfigOnCommit(t *testing.T) { - eng := NewTestEngine(t) - b := &builder.BuilderJob{Engine: eng} - b.Install() - runtime := mkDaemonFromEngine(eng, t) - defer runtime.Nuke() - - container1, _, _ := mkContainer(runtime, []string{"-e", "FOO=bar", unitTestImageID, "echo test > /tmp/foo"}, t) - defer runtime.Rm(container1) - - config, _, _, err := parseRun([]string{container1.ID, "cat /tmp/foo"}) - 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 outputBuffer = bytes.NewBuffer(nil) - job.Stdout.Add(outputBuffer) - if err := job.Run(); err != nil { - t.Error(err) - } - - container2, _, _ := mkContainer(runtime, []string{engine.Tail(outputBuffer, 1)}, t) - defer runtime.Rm(container2) - - job = eng.Job("container_inspect", container1.Name) - baseContainer, _ := job.Stdout.AddEnv() - if err := job.Run(); err != nil { - t.Error(err) - } - - job = eng.Job("container_inspect", container2.Name) - 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 TestRunWithTooLowMemoryLimit(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From de03f4797b614fb192a72c83812c2a04a1939c87 Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Fri, 10 Apr 2015 10:57:43 -0700 Subject: [PATCH 366/999] Allow SEO crawling from docs site Signed-off-by: Nathan LeClaire Docker-DCO-1.1-Signed-off-by: Nathan LeClaire (github: nathanleclaire) --- docs/release.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/docs/release.sh b/docs/release.sh index 09a85016c..d01bc0293 100755 --- a/docs/release.sh +++ b/docs/release.sh @@ -22,10 +22,17 @@ EOF } create_robots_txt() { - cat > ./sources/robots.txt <<'EOF' -User-agent: * -Disallow: / -EOF + if [ "$AWS_S3_BUCKET" == "docs.docker.com" ]; then + cat > ./sources/robots.txt <<-'EOF' + User-agent: * + Allow: / + EOF + else + cat > ./sources/robots.txt <<-'EOF' + User-agent: * + Disallow: / + EOF + fi } setup_s3() { From c337bfd2e0b0293dbca478a13c29a469493782eb Mon Sep 17 00:00:00 2001 From: Brendan Dixon Date: Fri, 10 Apr 2015 15:43:35 -0700 Subject: [PATCH 367/999] Turned off Ctrl+C processing by Windows shell Signed-off-by: Brendan Dixon --- pkg/term/term_windows.go | 7 ++++--- pkg/term/winconsole/console_windows.go | 7 +++++-- pkg/term/winconsole/term_emulator.go | 16 ++++++++++++++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/pkg/term/term_windows.go b/pkg/term/term_windows.go index 5b637928f..f46c9c8ac 100644 --- a/pkg/term/term_windows.go +++ b/pkg/term/term_windows.go @@ -5,6 +5,7 @@ import ( "io" "os" + "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/term/winconsole" ) @@ -57,6 +58,7 @@ func GetWinsize(fd uintptr) (*Winsize, error) { // SetWinsize sets the size of the given terminal connected to the passed file descriptor. func SetWinsize(fd uintptr, ws *Winsize) error { // TODO(azlinux): Implement SetWinsize + logrus.Debugf("[windows] SetWinsize: WARNING -- Unsupported method invoked") return nil } @@ -120,11 +122,10 @@ func MakeRaw(fd uintptr) (*State, error) { mode &^= winconsole.ENABLE_ECHO_INPUT mode &^= winconsole.ENABLE_LINE_INPUT mode &^= winconsole.ENABLE_MOUSE_INPUT - // TODO(azlinux): Enable window input to handle window resizing - mode |= winconsole.ENABLE_WINDOW_INPUT + mode &^= winconsole.ENABLE_WINDOW_INPUT + mode &^= winconsole.ENABLE_PROCESSED_INPUT // Enable these modes - mode |= winconsole.ENABLE_PROCESSED_INPUT mode |= winconsole.ENABLE_EXTENDED_FLAGS mode |= winconsole.ENABLE_INSERT_MODE mode |= winconsole.ENABLE_QUICK_EDIT_MODE diff --git a/pkg/term/winconsole/console_windows.go b/pkg/term/winconsole/console_windows.go index e6e3f9bcb..ce40a9316 100644 --- a/pkg/term/winconsole/console_windows.go +++ b/pkg/term/winconsole/console_windows.go @@ -12,6 +12,8 @@ import ( "sync" "syscall" "unsafe" + + "github.com/Sirupsen/logrus" ) const ( @@ -593,6 +595,7 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) n = len(command) parsedCommand := parseAnsiCommand(command) + logrus.Debugf("[windows] HandleOutputCommand: %v", parsedCommand) // console settings changes need to happen in atomic way term.outMutex.Lock() @@ -648,6 +651,7 @@ func (term *WindowsTerminal) HandleOutputCommand(handle uintptr, command []byte) column = int16(screenBufferInfo.Window.Right) + 1 } // The numbers are not 0 based, but 1 based + logrus.Debugf("[windows] HandleOutputCommmand: Moving cursor to (%v,%v)", column-1, line-1) if err := setConsoleCursorPosition(handle, false, column-1, line-1); err != nil { return n, err } @@ -1038,8 +1042,7 @@ func (term *WindowsTerminal) HandleInputSequence(fd uintptr, command []byte) (n } func marshal(c COORD) uintptr { - // works only on intel-endian machines - return uintptr(uint32(uint32(uint16(c.Y))<<16 | uint32(uint16(c.X)))) + return uintptr(*((*DWORD)(unsafe.Pointer(&c)))) } // IsConsole returns true if the given file descriptor is a terminal. diff --git a/pkg/term/winconsole/term_emulator.go b/pkg/term/winconsole/term_emulator.go index 8c9f34284..2d5edc039 100644 --- a/pkg/term/winconsole/term_emulator.go +++ b/pkg/term/winconsole/term_emulator.go @@ -1,6 +1,7 @@ package winconsole import ( + "fmt" "io" "strconv" "strings" @@ -206,6 +207,21 @@ func (c *ansiCommand) getParam(index int) string { return "" } +func (ac *ansiCommand) String() string { + return fmt.Sprintf("0x%v \"%v\" (\"%v\")", + bytesToHex(ac.CommandBytes), + ac.Command, + strings.Join(ac.Parameters, "\",\"")) +} + +func bytesToHex(b []byte) string { + hex := make([]string, len(b)) + for i, ch := range b { + hex[i] = fmt.Sprintf("%X", ch) + } + return strings.Join(hex, "") +} + func parseInt16OrDefault(s string, defaultValue int16) (n int16, err error) { if s == "" { return defaultValue, nil From ac8bd12b39d39a9361adc174bdff7837e771460d Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 10 Apr 2015 11:04:30 -0700 Subject: [PATCH 368/999] Get process list after PID 1 dead Fix #11087 Signed-off-by: Alexander Morozov --- daemon/execdriver/native/driver.go | 44 +++++++++++++++++--------- integration-cli/docker_cli_run_test.go | 25 +++++++++++++++ 2 files changed, 54 insertions(+), 15 deletions(-) diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index e5811bb85..6caa78390 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -188,6 +188,34 @@ func notifyOnOOM(container libcontainer.Container) <-chan struct{} { return oom } +func killCgroupProcs(c libcontainer.Container) { + var procs []*os.Process + if err := c.Pause(); err != nil { + logrus.Warn(err) + } + pids, err := c.Processes() + if err != nil { + // don't care about childs if we can't get them, this is mostly because cgroup already deleted + logrus.Warnf("Failed to get processes from container %s: %v", c.ID(), err) + } + for _, pid := range pids { + if p, err := os.FindProcess(pid); err == nil { + procs = append(procs, p) + if err := p.Kill(); err != nil { + logrus.Warn(err) + } + } + } + if err := c.Resume(); err != nil { + logrus.Warn(err) + } + for _, p := range procs { + if _, err := p.Wait(); err != nil { + logrus.Warn(err) + } + } +} + func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) { return func() (*os.ProcessState, error) { pid, err := p.Pid() @@ -195,8 +223,6 @@ func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*o return nil, err } - processes, err := c.Processes() - process, err := os.FindProcess(pid) s, err := process.Wait() if err != nil { @@ -206,19 +232,7 @@ func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*o } s = execErr.ProcessState } - if err != nil { - return s, err - } - - for _, pid := range processes { - process, err := os.FindProcess(pid) - if err != nil { - logrus.Errorf("Failed to kill process: %d", pid) - continue - } - process.Kill() - } - + killCgroupProcs(c) p.Wait() return s, err } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index a5d1e3e07..302286146 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3468,3 +3468,28 @@ func TestRunContainerWithRmFlagCannotStartContainer(t *testing.T) { logDone("run - container is removed if run with --rm and cannot start") } + +func TestRunPidHostWithChildIsKillable(t *testing.T) { + defer deleteAllContainers() + name := "ibuildthecloud" + if out, err := exec.Command(dockerBinary, "run", "-d", "--pid=host", "--name", name, "busybox", "sh", "-c", "sleep 30; echo hi").CombinedOutput(); err != nil { + t.Fatal(err, out) + } + time.Sleep(1 * time.Second) + errchan := make(chan error) + go func() { + if out, err := exec.Command(dockerBinary, "kill", name).CombinedOutput(); err != nil { + errchan <- fmt.Errorf("%v:\n%s", err, out) + } + close(errchan) + }() + select { + case err := <-errchan: + if err != nil { + t.Fatal(err) + } + case <-time.After(5 * time.Second): + t.Fatal("Kill container timed out") + } + logDone("run - can kill container with pid-host and some childs of pid 1") +} From a8fddbdeae6dfb8f6366cc476b37c84ed49f2732 Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Sat, 11 Apr 2015 08:58:23 +0800 Subject: [PATCH 369/999] update ubuntulinux.md Signed-off-by: Yuan Sun --- docs/sources/installation/ubuntulinux.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index 6400fdb59..dbf86f310 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -94,7 +94,7 @@ prerequisite installed, Docker's installation process adds it. ##Installing Docker on Ubuntu -Make sure you have intalled the prerequisites for your Ubuntu version. Then, +Make sure you have installed the prerequisites for your Ubuntu version. Then, install Docker using the following: 1. Log into your Ubuntu installation as a user with `sudo` privileges. @@ -253,7 +253,7 @@ The warning occurs because Docker containers can't use the local DNS nameserver. Instead, Docker defaults to using an external nameserver. To avoid this warning, you can specify a DNS server for use by Docker -containers. Or, you can disable `dnsmasq` in NetworkManager. Though, disabiling +containers. Or, you can disable `dnsmasq` in NetworkManager. Though, disabling `dnsmasq` might make DNS resolution slower on some networks. To specify a DNS server for use by Docker: From 795a58fb44a2bd18ec37d78c82d75c025f786c50 Mon Sep 17 00:00:00 2001 From: Deng Guangxing Date: Sat, 11 Apr 2015 09:24:21 +0800 Subject: [PATCH 370/999] 'docker rmi -f IMAGE_ID' untag all names and delete the image If an image has been tagged to multiple repos and tags, 'docker rmi -f IMAGE_ID' will just untag one random repo instead of untagging all and deleting the image. This patch implement this. This commit is composed of: *untag all names and delete the image *add test to this feature *modify commandline/cli.md to explain this Signed-off-by: Deng Guangxing --- daemon/image_delete.go | 35 +++++++++++++-------- docs/sources/reference/commandline/cli.md | 15 +++++++++ integration-cli/docker_cli_rmi_test.go | 37 +++++++++++++++++++++++ 3 files changed, 74 insertions(+), 13 deletions(-) diff --git a/daemon/image_delete.go b/daemon/image_delete.go index a44eb1bfa..ece33a3c7 100644 --- a/daemon/image_delete.go +++ b/daemon/image_delete.go @@ -30,6 +30,7 @@ func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, fi repoName, tag string tags = []string{} ) + repoAndTags := make(map[string][]string) // FIXME: please respect DRY and centralize repo+tag parsing in a single central place! -- shykes repoName, tag = parsers.ParseRepositoryTag(name) @@ -68,19 +69,25 @@ func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, fi if repoName == "" || repoName == parsedRepo { repoName = parsedRepo if parsedTag != "" { - tags = append(tags, parsedTag) + repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag) } } else if repoName != parsedRepo && !force && first { // the id belongs to multiple repos, like base:latest and user:test, // in that case return conflict return fmt.Errorf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", name) + } else { + //the id belongs to multiple repos, with -f just delete all + repoName = parsedRepo + if parsedTag != "" { + repoAndTags[repoName] = append(repoAndTags[repoName], parsedTag) + } } } } else { - tags = append(tags, tag) + repoAndTags[repoName] = append(repoAndTags[repoName], tag) } - if !first && len(tags) > 0 { + if !first && len(repoAndTags) > 0 { return nil } @@ -91,16 +98,18 @@ func (daemon *Daemon) imgDeleteHelper(name string, list *[]types.ImageDelete, fi } // Untag the current image - for _, tag := range tags { - tagDeleted, err := daemon.Repositories().Delete(repoName, tag) - if err != nil { - return err - } - if tagDeleted { - *list = append(*list, types.ImageDelete{ - Untagged: utils.ImageReference(repoName, tag), - }) - daemon.EventsService.Log("untag", img.ID, "") + for repoName, tags := range repoAndTags { + for _, tag := range tags { + tagDeleted, err := daemon.Repositories().Delete(repoName, tag) + if err != nil { + return err + } + if tagDeleted { + *list = append(*list, types.ImageDelete{ + Untagged: utils.ImageReference(repoName, tag), + }) + daemon.EventsService.Log("untag", img.ID, "") + } } } tags = daemon.Repositories().ByID()[img.ID] diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 79f59f5f2..b9f506a6c 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1794,6 +1794,21 @@ before the image is removed. Untagged: test:latest Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 +If you use the `-f` flag and specify the image's short or long ID, then this +command untags and removes all images that match the specified ID. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED SIZE + test1 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) + test latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) + test2 latest fd484f19954f 23 seconds ago 7 B (virtual 4.964 MB) + + $ docker rmi -f fd484f19954f + Untagged: test1:latest + Untagged: test:latest + Untagged: test2:latest + Deleted: fd484f19954f4920da7ff372b5067f5b7ddb2fd3830cecd17b96ea9e286ba5b8 + An image pulled by digest has no tag associated with it: $ docker images --digests diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index 277004d2e..7161a0922 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -77,6 +77,43 @@ func TestRmiTag(t *testing.T) { logDone("rmi - tag,rmi - tagging the same images multiple times then removing tags") } +func TestRmiImgIDForce(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir '/busybox-test'") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf("failed to create a container:%s, %v", out, err) + } + containerID := strings.TrimSpace(out) + runCmd = exec.Command(dockerBinary, "commit", containerID, "busybox-test") + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf("failed to commit a new busybox-test:%s, %v", out, err) + } + + imagesBefore, _, _ := dockerCmd(t, "images", "-a") + dockerCmd(t, "tag", "busybox-test", "utest:tag1") + dockerCmd(t, "tag", "busybox-test", "utest:tag2") + dockerCmd(t, "tag", "busybox-test", "utest/docker:tag3") + dockerCmd(t, "tag", "busybox-test", "utest:5000/docker:tag4") + { + imagesAfter, _, _ := dockerCmd(t, "images", "-a") + if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+4 { + t.Fatalf("tag busybox to create 4 more images with same imageID; docker images shows: %q\n", imagesAfter) + } + } + out, _, _ = dockerCmd(t, "inspect", "-f", "{{.Id}}", "busybox-test") + imgID := strings.TrimSpace(out) + dockerCmd(t, "rmi", "-f", imgID) + { + imagesAfter, _, _ := dockerCmd(t, "images", "-a") + if strings.Contains(imagesAfter, imgID[:12]) { + t.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter) + } + + } + logDone("rmi - imgID,rmi -f imgID delete all tagged repos of specific imgID") +} + func TestRmiTagWithExistingContainers(t *testing.T) { defer deleteAllContainers() From 24dd8a4698315a4aafee5071f4fe4d74fa06c377 Mon Sep 17 00:00:00 2001 From: y00277921 Date: Sat, 11 Apr 2015 10:34:21 +0800 Subject: [PATCH 371/999] Fix a typo in comment of parseMaybeJSONToList Signed-off-by: Yu Changchun --- builder/parser/line_parsers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/parser/line_parsers.go b/builder/parser/line_parsers.go index 6e284d6fc..5f65a8762 100644 --- a/builder/parser/line_parsers.go +++ b/builder/parser/line_parsers.go @@ -279,7 +279,7 @@ func parseMaybeJSON(rest string) (*Node, map[string]bool, error) { } // parseMaybeJSONToList determines if the argument appears to be a JSON array. If -// so, passes to parseJSON; if not, attmpts to parse it as a whitespace +// so, passes to parseJSON; if not, attempts to parse it as a whitespace // delimited string. func parseMaybeJSONToList(rest string) (*Node, map[string]bool, error) { node, attrs, err := parseJSON(rest) From 2cce4791b0e75201cb65daad07d4203d1c4c2996 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Sat, 11 Apr 2015 11:04:24 +0800 Subject: [PATCH 372/999] Add `-u|--user` flag to docker exec for running command as a different user Signed-off-by: Lei Jitang --- contrib/completion/bash/docker | 2 +- daemon/exec.go | 1 + daemon/execdriver/native/exec.go | 4 ++-- docs/man/docker-exec.1.md | 9 +++++++ docs/sources/reference/commandline/cli.md | 1 + integration-cli/docker_cli_exec_test.go | 29 +++++++++++++++++++++++ runconfig/exec.go | 7 +++--- 7 files changed, 46 insertions(+), 7 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index ad48f2886..ef7f0f335 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -407,7 +407,7 @@ _docker_events() { _docker_exec() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--detach -d --help --interactive -i -t --tty" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--detach -d --help --interactive -i -t --tty -u --user" -- "$cur" ) ) ;; *) __docker_containers_running diff --git a/daemon/exec.go b/daemon/exec.go index f91600da7..fa26dca7d 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -138,6 +138,7 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) error { Tty: config.Tty, Entrypoint: entrypoint, Arguments: args, + User: config.User, } execConfig := &execConfig{ diff --git a/daemon/execdriver/native/exec.go b/daemon/execdriver/native/exec.go index 2edd3313b..ed89269e2 100644 --- a/daemon/execdriver/native/exec.go +++ b/daemon/execdriver/native/exec.go @@ -14,7 +14,7 @@ import ( "github.com/docker/libcontainer/utils" ) -// TODO(vishh): Add support for running in privileged mode and running as a different user. +// TODO(vishh): Add support for running in privileged mode. func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { active := d.activeContainers[c.ID] if active == nil { @@ -28,7 +28,7 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo Args: append([]string{processConfig.Entrypoint}, processConfig.Arguments...), Env: c.ProcessConfig.Env, Cwd: c.WorkingDir, - User: c.ProcessConfig.User, + User: processConfig.User, } if processConfig.Tty { diff --git a/docs/man/docker-exec.1.md b/docs/man/docker-exec.1.md index e7554419e..c1de7b59e 100644 --- a/docs/man/docker-exec.1.md +++ b/docs/man/docker-exec.1.md @@ -10,6 +10,7 @@ docker-exec - Run a command in a running container [**--help**] [**-i**|**--interactive**[=*false*]] [**-t**|**--tty**[=*false*]] +[**-u**|**--user**[=*USER*]] CONTAINER COMMAND [ARG...] # DESCRIPTION @@ -35,6 +36,14 @@ container is unpaused, and then run **-t**, **--tty**=*true*|*false* Allocate a pseudo-TTY. The default is *false*. +**-u**, **--user**="" + Sets the username or UID used and optionally the groupname or GID for the specified command. + + The followings examples are all valid: + --user [user | user:group | uid | uid:gid | user:gid | uid:group ] + + Without this argument the command will be run as root in the container. + The **-t** option is incompatible with a redirection of the docker client standard input. diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index b9f506a6c..855e40d08 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1115,6 +1115,7 @@ You'll need two shells for this example. -d, --detach=false Detached mode: run command in the background -i, --interactive=false Keep STDIN open even if not attached -t, --tty=false Allocate a pseudo-TTY + -u, --user= Username or UID (format: [:]) The `docker exec` command runs a new command in a running container. diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 9fcee32a7..505690a11 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -665,3 +665,32 @@ func TestRunMutableNetworkFiles(t *testing.T) { } logDone("run - mutable network files") } + +func TestExecWithUser(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "busybox", "top") + if out, _, err := runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + cmd := exec.Command(dockerBinary, "exec", "-u", "1", "parent", "id") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { + t.Fatalf("exec with user by id expected daemon user got %s", out) + } + + cmd = exec.Command(dockerBinary, "exec", "-u", "root", "parent", "id") + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + if !strings.Contains(out, "uid=0(root) gid=0(root)") { + t.Fatalf("exec with user by root expected root user got %s", out) + } + + logDone("exec - with user") +} diff --git a/runconfig/exec.go b/runconfig/exec.go index 1bcdad159..01ddeaf54 100644 --- a/runconfig/exec.go +++ b/runconfig/exec.go @@ -21,8 +21,7 @@ type ExecConfig struct { func ExecConfigFromJob(job *engine.Job) (*ExecConfig, error) { execConfig := &ExecConfig{ - // TODO(vishh): Expose 'User' once it is supported. - //User: job.Getenv("User"), + User: job.Getenv("User"), // TODO(vishh): Expose 'Privileged' once it is supported. //Privileged: job.GetenvBool("Privileged"), Tty: job.GetenvBool("Tty"), @@ -45,6 +44,7 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background") + flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: [:])") execCmd []string container string ) @@ -57,8 +57,7 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { execCmd = parsedArgs[1:] execConfig := &ExecConfig{ - // TODO(vishh): Expose '-u' flag once it is supported. - User: "", + User: *flUser, // TODO(vishh): Expose '-p' flag once it is supported. Privileged: false, Tty: *flTty, From 72a500e9e5929b038816d8bd18d462a19e571c99 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Sat, 11 Apr 2015 11:26:37 +0800 Subject: [PATCH 373/999] Add docker exec run a command in privileged mode Signed-off-by: Lei Jitang --- contrib/completion/bash/docker | 2 +- daemon/exec.go | 1 + daemon/execdriver/native/exec.go | 5 +++- docs/man/docker-exec.1.md | 8 +++++++ docs/sources/reference/commandline/cli.md | 1 + integration-cli/docker_cli_exec_test.go | 28 +++++++++++++++++++++++ runconfig/exec.go | 21 ++++++++--------- 7 files changed, 53 insertions(+), 13 deletions(-) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index ef7f0f335..f66935211 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -407,7 +407,7 @@ _docker_events() { _docker_exec() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--detach -d --help --interactive -i -t --tty -u --user" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--detach -d --help --interactive -i --privileged -t --tty -u --user" -- "$cur" ) ) ;; *) __docker_containers_running diff --git a/daemon/exec.go b/daemon/exec.go index fa26dca7d..46c255a7c 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -139,6 +139,7 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) error { Entrypoint: entrypoint, Arguments: args, User: config.User, + Privileged: config.Privileged, } execConfig := &execConfig{ diff --git a/daemon/execdriver/native/exec.go b/daemon/execdriver/native/exec.go index ed89269e2..04239fdac 100644 --- a/daemon/execdriver/native/exec.go +++ b/daemon/execdriver/native/exec.go @@ -14,7 +14,6 @@ import ( "github.com/docker/libcontainer/utils" ) -// TODO(vishh): Add support for running in privileged mode. func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (int, error) { active := d.activeContainers[c.ID] if active == nil { @@ -31,6 +30,10 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo User: processConfig.User, } + if processConfig.Privileged { + p.Capabilities = execdriver.GetAllCapabilities() + } + if processConfig.Tty { config := active.Config() rootuid, err := config.HostUID() diff --git a/docs/man/docker-exec.1.md b/docs/man/docker-exec.1.md index c1de7b59e..312fa397f 100644 --- a/docs/man/docker-exec.1.md +++ b/docs/man/docker-exec.1.md @@ -9,6 +9,7 @@ docker-exec - Run a command in a running container [**-d**|**--detach**[=*false*]] [**--help**] [**-i**|**--interactive**[=*false*]] +[**--privileged**[=*false*]] [**-t**|**--tty**[=*false*]] [**-u**|**--user**[=*USER*]] CONTAINER COMMAND [ARG...] @@ -33,6 +34,13 @@ container is unpaused, and then run **-i**, **--interactive**=*true*|*false* Keep STDIN open even if not attached. The default is *false*. +**--privileged**=*true*|*false* + Give extended privileges to the process to run in a running container. The default is *false*. + + By default, the process run by docker exec in a running container +have the same capabilities of the container. By setting --privileged will give +all the capabilities to the process. + **-t**, **--tty**=*true*|*false* Allocate a pseudo-TTY. The default is *false*. diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 855e40d08..cfbd3398e 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1114,6 +1114,7 @@ You'll need two shells for this example. -d, --detach=false Detached mode: run command in the background -i, --interactive=false Keep STDIN open even if not attached + --privileged=false Give extended privileges to the command -t, --tty=false Allocate a pseudo-TTY -u, --user= Username or UID (format: [:]) diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 505690a11..8906da252 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -694,3 +694,31 @@ func TestExecWithUser(t *testing.T) { logDone("exec - with user") } + +func TestExecWithPrivileged(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "--cap-drop=ALL", "busybox", "top") + if out, _, err := runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + cmd := exec.Command(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sda b 8 0") + out, _, err := runCommandWithOutput(cmd) + fmt.Printf("%s", out) + if err == nil || !strings.Contains(out, "Operation not permitted") { + t.Fatalf("exec mknod in --cap-drop=ALL container without --privileged should failed") + } + + cmd = exec.Command(dockerBinary, "exec", "--privileged", "parent", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatal(err, out) + } + + if actual := strings.TrimSpace(out); actual != "ok" { + t.Fatalf("exec mknod in --cap-drop=ALL container with --privileged failed: %v, output: %q", err, out) + } + + logDone("exec - exec command in a container with privileged") +} diff --git a/runconfig/exec.go b/runconfig/exec.go index 01ddeaf54..e634d3081 100644 --- a/runconfig/exec.go +++ b/runconfig/exec.go @@ -22,8 +22,7 @@ type ExecConfig struct { func ExecConfigFromJob(job *engine.Job) (*ExecConfig, error) { execConfig := &ExecConfig{ User: job.Getenv("User"), - // TODO(vishh): Expose 'Privileged' once it is supported. - //Privileged: job.GetenvBool("Privileged"), + Privileged: job.GetenvBool("Privileged"), Tty: job.GetenvBool("Tty"), AttachStdin: job.GetenvBool("AttachStdin"), AttachStderr: job.GetenvBool("AttachStderr"), @@ -41,12 +40,13 @@ func ExecConfigFromJob(job *engine.Job) (*ExecConfig, error) { func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { var ( - flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") - flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY") - flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background") - flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: [:])") - execCmd []string - container string + flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") + flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY") + flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: run command in the background") + flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: [:])") + flPrivileged = cmd.Bool([]string{"-privileged"}, false, "Give extended privileges to the command") + execCmd []string + container string ) cmd.Require(flag.Min, 2) if err := cmd.ParseFlags(args, true); err != nil { @@ -57,9 +57,8 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { execCmd = parsedArgs[1:] execConfig := &ExecConfig{ - User: *flUser, - // TODO(vishh): Expose '-p' flag once it is supported. - Privileged: false, + User: *flUser, + Privileged: *flPrivileged, Tty: *flTty, Cmd: execCmd, Container: container, From d2d583c53b56a3ce069bf57ede9be9574c58a687 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Sat, 11 Apr 2015 11:39:47 +0800 Subject: [PATCH 374/999] Add CFS_BANDWIDTH to check-config Signed-off-by: Lei Jitang --- contrib/check-config.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/check-config.sh b/contrib/check-config.sh index 59649d6c6..54b1b7eac 100755 --- a/contrib/check-config.sh +++ b/contrib/check-config.sh @@ -161,6 +161,7 @@ echo 'Optional Features:' flags=( RESOURCE_COUNTERS CGROUP_PERF + CFS_BANDWIDTH ) check_flags "${flags[@]}" From 39932511c134938233e8bfe4796cec9a1b30d11e Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Sat, 11 Apr 2015 16:37:28 +0800 Subject: [PATCH 375/999] use hostConfig in verifyDaemonSettings We have moved resource configs to hostConfig. Signed-off-by: Qiang Huang --- daemon/container.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 46defe968..016ee1fdd 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1282,13 +1282,13 @@ func (container *Container) initializeNetworking() error { // Make sure the config is compatible with the current kernel func (container *Container) verifyDaemonSettings() { - if container.Config.Memory > 0 && !container.daemon.sysInfo.MemoryLimit { + if container.hostConfig.Memory > 0 && !container.daemon.sysInfo.MemoryLimit { logrus.Warnf("Your kernel does not support memory limit capabilities. Limitation discarded.") - container.Config.Memory = 0 + container.hostConfig.Memory = 0 } - if container.Config.Memory > 0 && !container.daemon.sysInfo.SwapLimit { + if container.hostConfig.Memory > 0 && !container.daemon.sysInfo.SwapLimit { logrus.Warnf("Your kernel does not support swap limit capabilities. Limitation discarded.") - container.Config.MemorySwap = -1 + container.hostConfig.MemorySwap = -1 } if container.daemon.sysInfo.IPv4ForwardingDisabled { logrus.Warnf("IPv4 forwarding is disabled. Networking will not work") From 4f492e794ae9c9a51edae4bb5873187eadab828e Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Sat, 11 Apr 2015 18:11:49 +0800 Subject: [PATCH 376/999] update docker_remote_api_v1.* Signed-off-by: Yuan Sun --- .../reference/api/docker_remote_api_v1.10.md | 2 +- .../reference/api/docker_remote_api_v1.11.md | 2 +- .../reference/api/docker_remote_api_v1.12.md | 2 +- .../reference/api/docker_remote_api_v1.13.md | 2 +- .../reference/api/docker_remote_api_v1.14.md | 2 +- .../reference/api/docker_remote_api_v1.15.md | 18 ++++++++--------- .../reference/api/docker_remote_api_v1.16.md | 18 ++++++++--------- .../reference/api/docker_remote_api_v1.17.md | 18 ++++++++--------- .../reference/api/docker_remote_api_v1.18.md | 20 +++++++++---------- .../reference/api/docker_remote_api_v1.19.md | 20 +++++++++---------- .../reference/api/docker_remote_api_v1.6.md | 2 +- .../reference/api/docker_remote_api_v1.7.md | 2 +- .../reference/api/docker_remote_api_v1.8.md | 2 +- .../reference/api/docker_remote_api_v1.9.md | 2 +- 14 files changed, 56 insertions(+), 56 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api_v1.10.md b/docs/sources/reference/api/docker_remote_api_v1.10.md index 7837b82ed..cd1a24809 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.10.md +++ b/docs/sources/reference/api/docker_remote_api_v1.10.md @@ -535,7 +535,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1) diff --git a/docs/sources/reference/api/docker_remote_api_v1.11.md b/docs/sources/reference/api/docker_remote_api_v1.11.md index 6bcabfc79..6aa29456d 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.11.md +++ b/docs/sources/reference/api/docker_remote_api_v1.11.md @@ -570,7 +570,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1) diff --git a/docs/sources/reference/api/docker_remote_api_v1.12.md b/docs/sources/reference/api/docker_remote_api_v1.12.md index 58f3bc3a3..439b2a219 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.12.md +++ b/docs/sources/reference/api/docker_remote_api_v1.12.md @@ -618,7 +618,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 diff --git a/docs/sources/reference/api/docker_remote_api_v1.13.md b/docs/sources/reference/api/docker_remote_api_v1.13.md index 1590978f0..f3de203eb 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.13.md +++ b/docs/sources/reference/api/docker_remote_api_v1.13.md @@ -611,7 +611,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 diff --git a/docs/sources/reference/api/docker_remote_api_v1.14.md b/docs/sources/reference/api/docker_remote_api_v1.14.md index f4e1b3edc..e3c559d6c 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.14.md +++ b/docs/sources/reference/api/docker_remote_api_v1.14.md @@ -621,7 +621,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 diff --git a/docs/sources/reference/api/docker_remote_api_v1.15.md b/docs/sources/reference/api/docker_remote_api_v1.15.md index a956d454a..d83275112 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.15.md +++ b/docs/sources/reference/api/docker_remote_api_v1.15.md @@ -174,12 +174,12 @@ Json Parameters: container. - **Domainname** - A string value containing the desired domain name to use for the container. -- **User** - A string value containg the user to use inside the container. +- **User** - A string value containing the user to use inside the container. - **Memory** - Memory limit in bytes. - **MemorySwap**- Total memory usage (memory + swap); set `-1` to disable swap. - **CpuShares** - An integer value containing the CPU Shares for container - (ie. the relative weight vs othercontainers). - **CpuSet** - String value containg the cgroups Cpuset to use. + (ie. the relative weight vs other containers). + **CpuSet** - String value containing the cgroups Cpuset to use. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -195,7 +195,7 @@ Json Parameters: container to empty objects. - **WorkingDir** - A string value containing the working dir for commands to run in. -- **NetworkDisabled** - Boolean value, when true disables neworking for the +- **NetworkDisabled** - Boolean value, when true disables networking for the container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` @@ -225,8 +225,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. + - **CapAdd** - A list of kernel capabilities to add to the container. + - **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -553,8 +553,8 @@ Json Parameters: - **DnsSearch** - A list of DNS search domains - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` -- **CapAdd** - A list of kernel capabilties to add to the container. -- **Capdrop** - A list of kernel capabilties to drop from the container. +- **CapAdd** - A list of kernel capabilities to add to the container. +- **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -766,7 +766,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 diff --git a/docs/sources/reference/api/docker_remote_api_v1.16.md b/docs/sources/reference/api/docker_remote_api_v1.16.md index a0c875889..3110ccbff 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.16.md +++ b/docs/sources/reference/api/docker_remote_api_v1.16.md @@ -174,12 +174,12 @@ Json Parameters: container. - **Domainname** - A string value containing the desired domain name to use for the container. -- **User** - A string value containg the user to use inside the container. +- **User** - A string value containing the user to use inside the container. - **Memory** - Memory limit in bytes. - **MemorySwap**- Total memory usage (memory + swap); set `-1` to disable swap. - **CpuShares** - An integer value containing the CPU Shares for container - (ie. the relative weight vs othercontainers). - **CpuSet** - String value containg the cgroups Cpuset to use. + (ie. the relative weight vs other containers). + **CpuSet** - String value containing the cgroups Cpuset to use. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -195,7 +195,7 @@ Json Parameters: container to empty objects. - **WorkingDir** - A string value containing the working dir for commands to run in. -- **NetworkDisabled** - Boolean value, when true disables neworking for the +- **NetworkDisabled** - Boolean value, when true disables networking for the container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` @@ -225,8 +225,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. + - **CapAdd** - A list of kernel capabilities to add to the container. + - **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -553,8 +553,8 @@ Json Parameters: - **DnsSearch** - A list of DNS search domains - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` -- **CapAdd** - A list of kernel capabilties to add to the container. -- **Capdrop** - A list of kernel capabilties to drop from the container. +- **CapAdd** - A list of kernel capabilities to add to the container. +- **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -766,7 +766,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 diff --git a/docs/sources/reference/api/docker_remote_api_v1.17.md b/docs/sources/reference/api/docker_remote_api_v1.17.md index d0abaffd0..1551ab186 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.17.md +++ b/docs/sources/reference/api/docker_remote_api_v1.17.md @@ -175,13 +175,13 @@ Json Parameters: container. - **Domainname** - A string value containing the desired domain name to use for the container. -- **User** - A string value containg the user to use inside the container. +- **User** - A string value containing the user to use inside the container. - **Memory** - Memory limit in bytes. - **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap, always use this with `memory`, and make the value larger than `memory`. - **CpuShares** - An integer value containing the CPU Shares for container - (ie. the relative weight vs othercontainers). - **CpuSet** - String value containg the cgroups Cpuset to use. + (ie. the relative weight vs other containers). + **CpuSet** - String value containing the cgroups Cpuset to use. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -197,7 +197,7 @@ Json Parameters: container to empty objects. - **WorkingDir** - A string value containing the working dir for commands to run in. -- **NetworkDisabled** - Boolean value, when true disables neworking for the +- **NetworkDisabled** - Boolean value, when true disables networking for the container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` @@ -227,8 +227,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. + - **CapAdd** - A list of kernel capabilities to add to the container. + - **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -686,8 +686,8 @@ Json Parameters: - **DnsSearch** - A list of DNS search domains - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` -- **CapAdd** - A list of kernel capabilties to add to the container. -- **Capdrop** - A list of kernel capabilties to drop from the container. +- **CapAdd** - A list of kernel capabilities to add to the container. +- **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -927,7 +927,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index c4cb15718..c69ade36a 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -183,14 +183,14 @@ Json Parameters: container. - **Domainname** - A string value containing the desired domain name to use for the container. -- **User** - A string value containg the user to use inside the container. +- **User** - A string value containing the user to use inside the container. - **Memory** - Memory limit in bytes. - **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap, always use this with `memory`, and make the value larger than `memory`. - **CpuShares** - An integer value containing the CPU Shares for container - (ie. the relative weight vs othercontainers). + (ie. the relative weight vs other containers). - **Cpuset** - The same as CpusetCpus, but deprecated, please don't use. -- **CpusetCpus** - String value containg the cgroups CpusetCpus to use. +- **CpusetCpus** - String value containing the cgroups CpusetCpus to use. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -207,7 +207,7 @@ Json Parameters: container to empty objects. - **WorkingDir** - A string value containing the working dir for commands to run in. -- **NetworkDisabled** - Boolean value, when true disables neworking for the +- **NetworkDisabled** - Boolean value, when true disables networking for the container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` @@ -237,8 +237,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. + - **CapAdd** - A list of kernel capabilities to add to the container. + - **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -734,8 +734,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` -- **CapAdd** - A list of kernel capabilties to add to the container. -- **Capdrop** - A list of kernel capabilties to drop from the container. +- **CapAdd** - A list of kernel capabilities to add to the container. +- **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -985,7 +985,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 @@ -1242,7 +1242,7 @@ Query Parameters: - **memory** - set memory limit for build - **memswap** - Total memory (memory + swap), `-1` to disable swap - **cpushares** - CPU shares (relative weight) -- **cpusetcpus** - CPUs in which to allow exection, e.g., `0-3`, `0,1` +- **cpusetcpus** - CPUs in which to allow execution, e.g., `0-3`, `0,1` Request Headers: diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 3543f7430..0f63ec310 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -183,14 +183,14 @@ Json Parameters: container. - **Domainname** - A string value containing the desired domain name to use for the container. -- **User** - A string value containg the user to use inside the container. +- **User** - A string value containing the user to use inside the container. - **Memory** - Memory limit in bytes. - **MemorySwap**- Total memory limit (memory + swap); set `-1` to disable swap, always use this with `memory`, and make the value larger than `memory`. - **CpuShares** - An integer value containing the CPU Shares for container - (ie. the relative weight vs othercontainers). + (ie. the relative weight vs other containers). - **Cpuset** - The same as CpusetCpus, but deprecated, please don't use. -- **CpusetCpus** - String value containg the cgroups CpusetCpus to use. +- **CpusetCpus** - String value containing the cgroups CpusetCpus to use. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -207,7 +207,7 @@ Json Parameters: container to empty objects. - **WorkingDir** - A string value containing the working dir for commands to run in. -- **NetworkDisabled** - Boolean value, when true disables neworking for the +- **NetworkDisabled** - Boolean value, when true disables networking for the container - **ExposedPorts** - An object mapping ports to an empty object in the form of: `"ExposedPorts": { "/: {}" }` @@ -237,8 +237,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` - - **CapAdd** - A list of kernel capabilties to add to the container. - - **Capdrop** - A list of kernel capabilties to drop from the container. + - **CapAdd** - A list of kernel capabilities to add to the container. + - **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -734,8 +734,8 @@ Json Parameters: container's `/etc/hosts` file. Specified in the form `["hostname:IP"]`. - **VolumesFrom** - A list of volumes to inherit from another container. Specified in the form `[:]` -- **CapAdd** - A list of kernel capabilties to add to the container. -- **Capdrop** - A list of kernel capabilties to drop from the container. +- **CapAdd** - A list of kernel capabilities to add to the container. +- **Capdrop** - A list of kernel capabilities to drop from the container. - **RestartPolicy** – The behavior to apply when the container exits. The value is an object with a `Name` property of either `"always"` to always restart or `"on-failure"` to restart only when the container @@ -985,7 +985,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1 @@ -1242,7 +1242,7 @@ Query Parameters: - **memory** - set memory limit for build - **memswap** - Total memory (memory + swap), `-1` to disable swap - **cpushares** - CPU shares (relative weight) -- **cpusetcpus** - CPUs in which to allow exection, e.g., `0-3`, `0,1` +- **cpusetcpus** - CPUs in which to allow execution, e.g., `0-3`, `0,1` Request Headers: diff --git a/docs/sources/reference/api/docker_remote_api_v1.6.md b/docs/sources/reference/api/docker_remote_api_v1.6.md index d0f9661e5..cd8a73088 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.6.md +++ b/docs/sources/reference/api/docker_remote_api_v1.6.md @@ -560,7 +560,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1) diff --git a/docs/sources/reference/api/docker_remote_api_v1.7.md b/docs/sources/reference/api/docker_remote_api_v1.7.md index 6cdd60374..dade45fbc 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.7.md +++ b/docs/sources/reference/api/docker_remote_api_v1.7.md @@ -505,7 +505,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1) diff --git a/docs/sources/reference/api/docker_remote_api_v1.8.md b/docs/sources/reference/api/docker_remote_api_v1.8.md index 409e63a16..56260db86 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.8.md +++ b/docs/sources/reference/api/docker_remote_api_v1.8.md @@ -553,7 +553,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1) diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.md b/docs/sources/reference/api/docker_remote_api_v1.9.md index 7ea3fc9ab..b6675dc4e 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/docs/sources/reference/api/docker_remote_api_v1.9.md @@ -557,7 +557,7 @@ Status Codes: 1. Read 8 bytes 2. chose stdout or stderr depending on the first byte - 3. Extract the frame size from the last 4 byets + 3. Extract the frame size from the last 4 bytes 4. Read the extracted size and output it on the correct output 5. Goto 1) From d6d8f45b04a6e6d0b616c85d1885342d3e676ec1 Mon Sep 17 00:00:00 2001 From: jianbosun Date: Wed, 8 Apr 2015 12:25:41 +0800 Subject: [PATCH 377/999] change memory usage display using standard unix postfixes add unit test for display also change doc for memory usage display change for example GiB will be GB Signed-off-by: Sun Jianbo --- api/client/stats.go | 4 ++-- api/client/stats_unit_test.go | 29 +++++++++++++++++++++++ docs/man/docker-stats.1.md | 2 +- docs/sources/reference/commandline/cli.md | 2 +- 4 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 api/client/stats_unit_test.go diff --git a/api/client/stats.go b/api/client/stats.go index bf9d3a814..317a4fd84 100644 --- a/api/client/stats.go +++ b/api/client/stats.go @@ -99,9 +99,9 @@ func (s *containerStats) Display(w io.Writer) error { fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n", s.Name, s.CPUPercentage, - units.BytesSize(s.Memory), units.BytesSize(s.MemoryLimit), + units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit), s.MemoryPercentage, - units.BytesSize(s.NetworkRx), units.BytesSize(s.NetworkTx)) + units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx)) return nil } diff --git a/api/client/stats_unit_test.go b/api/client/stats_unit_test.go new file mode 100644 index 000000000..0831dbcbb --- /dev/null +++ b/api/client/stats_unit_test.go @@ -0,0 +1,29 @@ +package client + +import ( + "bytes" + "sync" + "testing" +) + +func TestDisplay(t *testing.T) { + c := &containerStats{ + Name: "app", + CPUPercentage: 30.0, + Memory: 100 * 1024 * 1024.0, + MemoryLimit: 2048 * 1024 * 1024.0, + MemoryPercentage: 100.0 / 2048.0 * 100.0, + NetworkRx: 100 * 1024 * 1024, + NetworkTx: 800 * 1024 * 1024, + mu: sync.RWMutex{}, + } + var b bytes.Buffer + if err := c.Display(&b); err != nil { + t.Fatalf("c.Display() gave error: %s", err) + } + got := b.String() + want := "app\t30.00%\t104.9 MB/2.147 GB\t4.88%\t104.9 MB/838.9 MB\n" + if got != want { + t.Fatalf("c.Display() = %q, want %q", got, want) + } +} diff --git a/docs/man/docker-stats.1.md b/docs/man/docker-stats.1.md index a1adc7ecb..4cf7a66df 100644 --- a/docs/man/docker-stats.1.md +++ b/docs/man/docker-stats.1.md @@ -24,5 +24,5 @@ Run **docker stats** with multiple containers. $ docker stats redis1 redis2 CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O redis1 0.07% 796 KiB/64 MiB 1.21% 788 B/648 B - redis2 0.07% 2.746 MiB/64 MiB 4.29% 1.266 KiB/648 B + redis2 0.07% 2.746 MB/64 MB 4.29% 1.266 KB/648 B diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index b9f506a6c..c284a9435 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -2336,7 +2336,7 @@ Running `docker stats` on multiple containers $ docker stats redis1 redis2 CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O redis1 0.07% 796 KiB/64 MiB 1.21% 788 B/648 B - redis2 0.07% 2.746 MiB/64 MiB 4.29% 1.266 KiB/648 B + redis2 0.07% 2.746 MB/64 MB 4.29% 1.266 KB/648 B The `docker stats` command will only return a live stream of data for running From 787d774af0eb26aed16cbf24896e4ba051ba4538 Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:18:57 -0400 Subject: [PATCH 378/999] Link to HTTPS URLs Link to HTTPS URLs in top-level documentation / project files. Signed-off-by: Eric Windisch --- CONTRIBUTING.md | 2 +- LICENSE | 4 ++-- MAINTAINERS | 2 +- NOTICE | 6 +++--- README.md | 20 ++++++++++---------- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e6bf6ad5f..395f25923 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -164,7 +164,7 @@ However, there might be a way to implement that feature *on top of* Docker. Stack Overflow Stack Overflow has over 7000K Docker questions listed. We regularly - monitor
Docker questions + monitor Docker questions and so do many other knowledgeable Docker users. diff --git a/LICENSE b/LICENSE index 508036ef4..c7a3f0cfd 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ Apache License Version 2.0, January 2004 - http://www.apache.org/licenses/ + https://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION @@ -182,7 +182,7 @@ 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 + https://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, diff --git a/MAINTAINERS b/MAINTAINERS index e0ddc9f1a..6c79e8b4b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -37,7 +37,7 @@ project from a great one. text = """ Docker follows the timeless, highly efficient and totally unfair system known as [Benevolent dictator for -life](http://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life), with +life](https://en.wikipedia.org/wiki/Benevolent_Dictator_for_Life), with yours truly, Solomon Hykes, in the role of BDFL. This means that all decisions are made, by default, by Solomon. Since making every decision myself would be highly un-scalable, in practice decisions are spread diff --git a/NOTICE b/NOTICE index 8e84d0f3b..6e6f469ab 100644 --- a/NOTICE +++ b/NOTICE @@ -1,7 +1,7 @@ Docker Copyright 2012-2015 Docker, Inc. -This product includes software developed at Docker, Inc. (http://www.docker.com). +This product includes software developed at Docker, Inc. (https://www.docker.com). This product contains software (https://github.com/kr/pty) developed by Keith Rarick, licensed under the MIT License. @@ -14,6 +14,6 @@ United States and other governments. It is your responsibility to ensure that your use and/or transfer does not violate applicable laws. -For more information, please see http://www.bis.doc.gov +For more information, please see https://www.bis.doc.gov -See also http://www.apache.org/dev/crypto.html and/or seek legal counsel. +See also https://www.apache.org/dev/crypto.html and/or seek legal counsel. diff --git a/README.md b/README.md index 6d259c5ed..03aa0139c 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ databases, and backend services without depending on a particular stack or provider. Docker began as an open-source implementation of the deployment engine which -powers [dotCloud](http://dotcloud.com), a popular Platform-as-a-Service. +powers [dotCloud](https://dotcloud.com), a popular Platform-as-a-Service. It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands of applications and databases. @@ -56,12 +56,12 @@ By contrast, Docker relies on a different sandboxing method known as *containerization*. Unlike traditional virtualization, containerization takes place at the kernel level. Most modern operating system kernels now support the primitives necessary for containerization, including -Linux with [openvz](http://openvz.org), +Linux with [openvz](https://openvz.org), [vserver](http://linux-vserver.org) and more recently [lxc](http://lxc.sourceforge.net), Solaris with -[zones](http://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc), +[zones](https://docs.oracle.com/cd/E26502_01/html/E29024/preface-1.html#scrolltoc), and FreeBSD with -[Jails](http://www.freebsd.org/doc/handbook/jails.html). +[Jails](https://www.freebsd.org/doc/handbook/jails.html). Docker builds on top of these low-level primitives to offer developers a portable format and runtime environment that solves all four problems. @@ -115,7 +115,7 @@ This is usually difficult for several reasons: Docker solves the problem of dependency hell by giving the developer a simple way to express *all* their application's dependencies in one place, while streamlining the process of assembling them. If this makes you think of -[XKCD 927](http://xkcd.com/927/), don't worry. Docker doesn't +[XKCD 927](https://xkcd.com/927/), don't worry. Docker doesn't *replace* your favorite packaging systems. It simply orchestrates their use in a simple and repeatable way. How does it do that? With layers. @@ -147,10 +147,10 @@ Docker can be installed on your local machine as well as servers - both bare metal and virtualized. It is available as a binary on most modern Linux systems, or as a VM on Windows, Mac and other systems. -We also offer an [interactive tutorial](http://www.docker.com/tryit/) +We also offer an [interactive tutorial](https://www.docker.com/tryit/) for quickly learning the basics of using Docker. -For up-to-date install instructions, see the [Docs](http://docs.docker.com). +For up-to-date install instructions, see the [Docs](https://docs.docker.com). Usage examples ============== @@ -159,7 +159,7 @@ Docker can be used to run short-lived commands, long-running daemons (app servers, databases, etc.), interactive shell sessions, etc. You can find a [list of real-world -examples](http://docs.docker.com/examples/) in the +examples](https://docs.docker.com/examples/) in the documentation. Under the hood @@ -172,7 +172,7 @@ Under the hood, Docker is built on the following components: and [namespacing](http://blog.dotcloud.com/under-the-hood-linux-kernels-on-dotcloud-part) capabilities of the Linux kernel -* The [Go](http://golang.org) programming language +* The [Go](https://golang.org) programming language * The [Docker Image Specification](https://github.com/docker/docker/blob/master/image/spec/v1.md) * The [Libcontainer Specification](https://github.com/docker/libcontainer/blob/master/SPEC.md) @@ -218,7 +218,7 @@ United States and other governments. It is your responsibility to ensure that your use and/or transfer does not violate applicable laws. -For more information, please see http://www.bis.doc.gov +For more information, please see https://www.bis.doc.gov Licensing From 67a983fc372e7b5fd1c75d1ceafe9b79b84d7e92 Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:21:16 -0400 Subject: [PATCH 379/999] Use HTTPS for package URL Signed-off-by: Eric Windisch --- hack/make/ubuntu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/make/ubuntu b/hack/make/ubuntu index e34369eb1..5bbca8a89 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -23,7 +23,7 @@ fi # ie, 1.5.0 > 1.5.0~rc1 > 1.5.0~git20150128.112847.17e840a > 1.5.0~dev~git20150128.112847.17e840a PACKAGE_ARCHITECTURE="$(dpkg-architecture -qDEB_HOST_ARCH)" -PACKAGE_URL="http://www.docker.com/" +PACKAGE_URL="https://www.docker.com/" PACKAGE_MAINTAINER="support@docker.com" PACKAGE_DESCRIPTION="Linux container runtime Docker complements LXC with a high-level API which operates at the process From 723d43387a5c04ef8588c7e1557aa163e268581c Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:22:16 -0400 Subject: [PATCH 380/999] HTTPS urls for ./hacking Signed-off-by: Eric Windisch --- hack/dind | 2 +- hack/make.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/hack/dind b/hack/dind index 1242cbffe..dfd463731 100755 --- a/hack/dind +++ b/hack/dind @@ -3,7 +3,7 @@ set -e # DinD: a wrapper script which allows docker to be run inside a docker container. # Original version by Jerome Petazzoni -# See the blog post: http://blog.docker.com/2013/09/docker-can-now-run-within-docker/ +# See the blog post: https://blog.docker.com/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). diff --git a/hack/make.sh b/hack/make.sh index 4117469d6..c3d4623af 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -6,7 +6,7 @@ set -e # # Requirements: # - The current directory should be a checkout of the docker source code -# (http://github.com/docker/docker). Whatever version is checked out +# (https://github.com/docker/docker). Whatever version is checked out # will be built. # - The VERSION file, at the root of the repository, should exist, and # will be used as Docker binary version and package version. @@ -85,7 +85,7 @@ if [ "$AUTO_GOPATH" ]; then fi if [ ! "$GOPATH" ]; then - echo >&2 'error: missing GOPATH; please see http://golang.org/doc/code.html#GOPATH' + echo >&2 'error: missing GOPATH; please see https://golang.org/doc/code.html#GOPATH' echo >&2 ' alternatively, set AUTO_GOPATH=1' exit 1 fi From ca37301d54e1525d4522dea266180072d4fd892b Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:31:34 -0400 Subject: [PATCH 381/999] Link to HTTPS URLs in engine comments Updates most of the instances of HTTP urls in the engine's comments. Does not account for any use in the code itself, documentation, contrib, or project files. Signed-off-by: Eric Windisch --- api/server/server.go | 2 +- daemon/daemon.go | 4 ++-- daemon/execdriver/lxc/lxc_template.go | 2 +- engine/env.go | 4 ++-- integration-cli/docker_cli_proxy_test.go | 2 +- integration/api_test.go | 2 +- utils/utils.go | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index ffbef8cee..4074ebb53 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -956,7 +956,7 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res // If contentLength is -1, we can assumed chunked encoding // or more technically that the length is unknown - // http://golang.org/src/pkg/net/http/request.go#L139 + // https://golang.org/src/pkg/net/http/request.go#L139 // net/http otherwise seems to swallow any headers related to chunked encoding // including r.TransferEncoding // allow a nil body for backwards compatibility diff --git a/daemon/daemon.go b/daemon/daemon.go index 86ed71e23..440826051 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -957,7 +957,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService localCopy := path.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", dockerversion.VERSION)) sysInitPath := utils.DockerInitPath(localCopy) if sysInitPath == "" { - return nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See http://docs.docker.com/contributing/devenvironment for official build instructions.") + return nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See https://docs.docker.com/contributing/devenvironment for official build instructions.") } if sysInitPath != localCopy { @@ -1227,7 +1227,7 @@ func checkKernel() error { // Unfortunately we can't test for the feature "does not cause a kernel panic" // without actually causing a kernel panic, so we need this workaround until // the circumstances of pre-3.8 crashes are clearer. - // For details see http://github.com/docker/docker/issues/407 + // For details see https://github.com/docker/docker/issues/407 if k, err := kernel.GetKernelVersion(); err != nil { logrus.Warnf("%s", err) } else { diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 02313d465..6d6decb79 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -62,7 +62,7 @@ lxc.pivotdir = lxc_putold # NOTICE: These mounts must be applied within the namespace {{if .ProcessConfig.Privileged}} # WARNING: mounting procfs and/or sysfs read-write is a known attack vector. -# See e.g. http://blog.zx2c4.com/749 and http://bit.ly/T9CkqJ +# See e.g. http://blog.zx2c4.com/749 and https://bit.ly/T9CkqJ # We mount them read-write here, but later, dockerinit will call the Restrict() function to remount them read-only. # We cannot mount them directly read-only, because that would prevent loading AppArmor profiles. lxc.mount.entry = proc {{escapeFstabSpaces $ROOTFS}}/proc proc nosuid,nodev,noexec 0 0 diff --git a/engine/env.go b/engine/env.go index c6c673271..089bc162c 100644 --- a/engine/env.go +++ b/engine/env.go @@ -210,7 +210,7 @@ func (env *Env) SetAuto(k string, v interface{}) { // 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) + // (See https://golang.org/src/pkg/encoding/json/decode.go#L46) if fval, ok := v.(float64); ok { env.SetInt64(k, int64(fval)) } else if sval, ok := v.(string); ok { @@ -245,7 +245,7 @@ func (env *Env) Encode(dst io.Writer) error { 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) + // (See https://golang.org/src/pkg/encoding/json/decode.go#L46) m[k] = changeFloats(val) } else { m[k] = v diff --git a/integration-cli/docker_cli_proxy_test.go b/integration-cli/docker_cli_proxy_test.go index b39dd5634..55c544003 100644 --- a/integration-cli/docker_cli_proxy_test.go +++ b/integration-cli/docker_cli_proxy_test.go @@ -21,7 +21,7 @@ func TestCliProxyDisableProxyUnixSock(t *testing.T) { } // Can't use localhost here since go has a special case to not use proxy if connecting to localhost -// See http://golang.org/pkg/net/http/#ProxyFromEnvironment +// See https://golang.org/pkg/net/http/#ProxyFromEnvironment func TestCliProxyProxyTCPSock(t *testing.T) { testRequires(t, SameHostDaemon) // get the IP to use to connect since we can't use localhost diff --git a/integration/api_test.go b/integration/api_test.go index 98e683d00..c527bcb92 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -932,7 +932,7 @@ func TestConstainersStartChunkedEncodingHostConfig(t *testing.T) { req.Header.Add("Content-Type", "application/json") // This is a cheat to make the http request do chunked encoding // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite - // http://golang.org/src/pkg/net/http/request.go?s=11980:12172 + // https://golang.org/src/pkg/net/http/request.go?s=11980:12172 req.ContentLength = -1 server.ServeRequest(eng, api.APIVERSION, r, req) assertHttpNotError(r, t) diff --git a/utils/utils.go b/utils/utils.go index d0e76bf23..df93eabbd 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -127,12 +127,12 @@ func DockerInitPath(localCopy string) string { filepath.Join(filepath.Dir(selfPath), "dockerinit"), // FHS 3.0 Draft: "/usr/libexec includes internal binaries that are not intended to be executed directly by users or shell scripts. Applications may use a single subdirectory under /usr/libexec." - // http://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec + // https://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec "/usr/libexec/docker/dockerinit", "/usr/local/libexec/docker/dockerinit", // FHS 2.3: "/usr/lib includes object files, libraries, and internal binaries that are not intended to be executed directly by users or shell scripts." - // http://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA + // https://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA "/usr/lib/docker/dockerinit", "/usr/local/lib/docker/dockerinit", } From df9ee6d6563ace6e382a3bdd4a45b38756a76afb Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:35:08 -0400 Subject: [PATCH 382/999] Link to HTTPS urls in contrib comments/maintainers Updates comments and dockerfile maintainer lines to use HTTPS urls where applicable. Signed-off-by: Eric Windisch --- contrib/check-config.sh | 2 +- contrib/project-stats.sh | 2 +- contrib/syntax/vim/doc/dockerfile.txt | 2 +- contrib/syntax/vim/syntax/dockerfile.vim | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/check-config.sh b/contrib/check-config.sh index 59649d6c6..b3ae169bc 100755 --- a/contrib/check-config.sh +++ b/contrib/check-config.sh @@ -27,7 +27,7 @@ is_set() { zgrep "CONFIG_$1=[y|m]" "$CONFIG" > /dev/null } -# see http://en.wikipedia.org/wiki/ANSI_escape_code#Colors +# see https://en.wikipedia.org/wiki/ANSI_escape_code#Colors declare -A colors=( [black]=30 [red]=31 diff --git a/contrib/project-stats.sh b/contrib/project-stats.sh index 985a77f22..2691c72ff 100755 --- a/contrib/project-stats.sh +++ b/contrib/project-stats.sh @@ -3,7 +3,7 @@ ## Run this script from the root of the docker repository ## to query project stats useful to the maintainers. ## You will need to install `pulls` and `issues` from -## http://github.com/crosbymichael/pulls +## https://github.com/crosbymichael/pulls set -e diff --git a/contrib/syntax/vim/doc/dockerfile.txt b/contrib/syntax/vim/doc/dockerfile.txt index 37cc7be91..e69e2b7b3 100644 --- a/contrib/syntax/vim/doc/dockerfile.txt +++ b/contrib/syntax/vim/doc/dockerfile.txt @@ -1,6 +1,6 @@ *dockerfile.txt* Syntax highlighting for Dockerfiles -Author: Honza Pokorny +Author: Honza Pokorny License: BSD INSTALLATION *installation* diff --git a/contrib/syntax/vim/syntax/dockerfile.vim b/contrib/syntax/vim/syntax/dockerfile.vim index 36691e250..bd0926866 100644 --- a/contrib/syntax/vim/syntax/dockerfile.vim +++ b/contrib/syntax/vim/syntax/dockerfile.vim @@ -1,5 +1,5 @@ " dockerfile.vim - Syntax highlighting for Dockerfiles -" Maintainer: Honza Pokorny +" Maintainer: Honza Pokorny " Version: 0.5 From ac65c8c3801385c8278e0740ef2738b0aa56689a Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:42:17 -0400 Subject: [PATCH 383/999] HTTPS URLs for docs top-level & man pages This updates all of docs outside of sources. Signed-off-by: Eric Windisch --- docs/README.md | 4 ++-- docs/man/README.md | 2 +- docs/man/docker-run.1.md | 2 +- docs/mkdocs.yml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/README.md b/docs/README.md index 15fee1d36..8ff25adab 100755 --- a/docs/README.md +++ b/docs/README.md @@ -3,7 +3,7 @@ The source for Docker documentation is in this directory under `sources/`. Our documentation uses extended Markdown, as implemented by [MkDocs](http://mkdocs.org). The current release of the Docker documentation -resides on [http://docs.docker.com](http://docs.docker.com). +resides on [https://docs.docker.com](https://docs.docker.com). ## Understanding the documentation branches and processes @@ -11,7 +11,7 @@ Docker has two primary branches for documentation: | Branch | Description | URL (published via commit-hook) | |----------|--------------------------------|------------------------------------------------------------------------------| -| `docs` | Official release documentation | [http://docs.docker.com](http://docs.docker.com) | +| `docs` | Official release documentation | [https://docs.docker.com](https://docs.docker.com) | | `master` | Merged but unreleased development work | [http://docs.master.dockerproject.com](http://docs.master.dockerproject.com) | Additions and updates to upcoming releases are made in a feature branch off of diff --git a/docs/man/README.md b/docs/man/README.md index 402178a9c..e25a925ad 100644 --- a/docs/man/README.md +++ b/docs/man/README.md @@ -30,4 +30,4 @@ The `md2man` Docker container will process the Markdown files and generate the man pages inside the `docker/docs/man/man1` directory using Docker volumes. For more information on Docker volumes see the man page for `docker run` and also look at the article [Sharing Directories via Volumes] -(http://docs.docker.com/use/working_with_volumes/). +(https://docs.docker.com/use/working_with_volumes/). diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 53d762cf6..8a856ca60 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -416,7 +416,7 @@ you’d like to connect instead, as in: ## Sharing IPC between containers -Using shm_server.c available here: http://www.cs.cf.ac.uk/Dave/C/node27.html +Using shm_server.c available here: https://www.cs.cf.ac.uk/Dave/C/node27.html Testing `--ipc=host` mode: diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index b5b30d72d..fd56ad15c 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,5 +1,5 @@ site_name: Docker Documentation -#site_url: http://docs.docker.com/ +#site_url: https://docs.docker.com/ site_url: / site_description: Documentation for fast and lightweight Docker container based virtualization framework. site_favicon: img/favicon.png From 5dc83233bc41fa4d8189aec6ce0327b06038c9b5 Mon Sep 17 00:00:00 2001 From: Eric Windisch Date: Sat, 11 Apr 2015 13:58:09 -0400 Subject: [PATCH 384/999] HTTPS urls for ./project Signed-off-by: Eric Windisch --- project/GOVERNANCE.md | 2 +- project/PACKAGERS.md | 6 +++--- project/RELEASE-CHECKLIST.md | 4 ++-- project/TOOLS.md | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/project/GOVERNANCE.md b/project/GOVERNANCE.md index 52a8bf05d..6ae7baf74 100644 --- a/project/GOVERNANCE.md +++ b/project/GOVERNANCE.md @@ -4,7 +4,7 @@ In the spirit of openness, Docker created a Governance Advisory Board, and commi All output from the meetings should be considered proposals only, and are subject to the review and approval of the community and the project leadership. The materials from the first Docker Governance Advisory Board meeting, held on October 28, 2014, are available at -[Google Docs Folder](http://goo.gl/Alfj8r) +[Google Docs Folder](https://goo.gl/Alfj8r) These include: diff --git a/project/PACKAGERS.md b/project/PACKAGERS.md index 5704b0a2b..6acd4aef3 100644 --- a/project/PACKAGERS.md +++ b/project/PACKAGERS.md @@ -47,7 +47,7 @@ To build Docker, you will need the following: * A recent version of Git and Mercurial * Go version 1.3 or later * A clean checkout of the source added to a valid [Go - workspace](http://golang.org/doc/code.html#Workspaces) under the path + workspace](https://golang.org/doc/code.html#Workspaces) under the path *src/github.com/docker/docker* (unless you plan to use `AUTO_GOPATH`, explained in more detail below) @@ -237,9 +237,9 @@ are as follows (in order): installed at "/usr/bin/docker", then "/usr/bin/dockerinit" will be the first place this file is searched for) * "/usr/libexec/docker/dockerinit" or "/usr/local/libexec/docker/dockerinit" - ([FHS 3.0 Draft](http://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec)) + ([FHS 3.0 Draft](https://www.linuxbase.org/betaspecs/fhs/fhs.html#usrlibexec)) * "/usr/lib/docker/dockerinit" or "/usr/local/lib/docker/dockerinit" ([FHS - 2.3](http://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA)) + 2.3](https://refspecs.linuxfoundation.org/FHS_2.3/fhs-2.3.html#USRLIBLIBRARIESFORPROGRAMMINGANDPA)) If (and please, only if) one of the paths above is insufficient due to distro policy or similar issues, you may use the `DOCKER_INITPATH` environment variable diff --git a/project/RELEASE-CHECKLIST.md b/project/RELEASE-CHECKLIST.md index 10af71c81..8a2070e58 100644 --- a/project/RELEASE-CHECKLIST.md +++ b/project/RELEASE-CHECKLIST.md @@ -145,7 +145,7 @@ To test locally: make docs ``` -To make a shared test at http://beta-docs.docker.io: +To make a shared test at https://beta-docs.docker.io: (You will need the `awsconfig` file added to the `docs/` dir) @@ -341,7 +341,7 @@ git push -f origin docs make AWS_S3_BUCKET=docs.docker.com BUILD_ROOT=yes DISTRIBUTION_ID=C2K6......FL2F docs-release ``` -The docs will appear on http://docs.docker.com/ (though there may be cached +The docs will appear on https://docs.docker.com/ (though there may be cached versions, so its worth checking http://docs.docker.com.s3-website-us-east-1.amazonaws.com/). For more information about documentation releases, see `docs/README.md`. diff --git a/project/TOOLS.md b/project/TOOLS.md index f057ccd2b..79bd28374 100644 --- a/project/TOOLS.md +++ b/project/TOOLS.md @@ -14,11 +14,11 @@ we run Docker in Docker to test. Leeroy is a Go application which integrates Jenkins with GitHub pull requests. Leeroy uses -[GitHub hooks](http://developer.github.com/v3/repos/hooks/) +[GitHub hooks](https://developer.github.com/v3/repos/hooks/) to listen for pull request notifications and starts jobs on your Jenkins server. Using the Jenkins [notification plugin][jnp], Leeroy updates the pull request using GitHub's -[status API](http://developer.github.com/v3/repos/statuses/) +[status API](https://developer.github.com/v3/repos/statuses/) with pending, success, failure, or error statuses. The leeroy repository is maintained at From a9843cb739bd30a9e6eeb8841f645008e1fc905f Mon Sep 17 00:00:00 2001 From: John Gossman Date: Sat, 11 Apr 2015 10:40:37 -0700 Subject: [PATCH 385/999] Added some error messages and tracing to bridge network initialization Signed-off-by: John Gossman --- daemon/daemon.go | 1 + daemon/networkdriver/bridge/driver.go | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/daemon/daemon.go b/daemon/daemon.go index 86ed71e23..1acf96a02 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -938,6 +938,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService if !config.DisableNetwork { if err := bridge.InitDriver(&config.Bridge); err != nil { + logrus.Errorf("Error initializing Bridge: %s", err) return nil, err } } diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index dabb1165e..d9698382e 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -135,8 +135,11 @@ func InitDriver(config *Config) error { return err } + logrus.Infof("Bridge interface not found, trying to create it") + // If the iface is not found, try to create it if err := configureBridge(config.IP, bridgeIPv6, config.EnableIPv6); err != nil { + logrus.Errorf("Could not configure Bridge: %s", err) return err } @@ -214,6 +217,7 @@ func InitDriver(config *Config) error { // Configure iptables for link support if config.EnableIptables { if err := setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq); err != nil { + logrus.Errorf("Error configuing iptables: %s", err) return err } @@ -261,6 +265,7 @@ func InitDriver(config *Config) error { } logrus.Debugf("Subnet: %v", subnet) if err := ipAllocator.RegisterSubnet(bridgeIPv4Network, subnet); err != nil { + logrus.Errorf("Error registering subnet for IPv4 bridge network: %s", err) return err } } @@ -272,6 +277,7 @@ func InitDriver(config *Config) error { } logrus.Debugf("Subnet: %v", subnet) if err := ipAllocator.RegisterSubnet(subnet, subnet); err != nil { + logrus.Errorf("Error registering subnet for IPv4 bridge network: %s", err) return err } globalIPv6Network = subnet From c4fe5dad1deb45ecde460d7627523dbf032dc205 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Wed, 8 Apr 2015 13:29:32 +0200 Subject: [PATCH 386/999] Add test on archive.go (#11603) - Trying to add or complete unit test to each ``func`` - Removing dead code (``escapeName``) Signed-off-by: Vincent Demeester --- pkg/archive/archive.go | 16 --- pkg/archive/archive_test.go | 243 +++++++++++++++++++++++++++++++++++- 2 files changed, 242 insertions(+), 17 deletions(-) diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 7082cd908..7f1889750 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -388,22 +388,6 @@ func Tar(path string, compression Compression) (io.ReadCloser, error) { return TarWithOptions(path, &TarOptions{Compression: compression}) } -func escapeName(name string) string { - escaped := make([]byte, 0) - for i, c := range []byte(name) { - if i == 0 && c == '/' { - continue - } - // all printable chars except "-" which is 0x2d - if (0x20 <= c && c <= 0x7E) && c != 0x2d { - escaped = append(escaped, c) - } else { - escaped = append(escaped, fmt.Sprintf("\\%03o", c)...) - } - } - return string(escaped) -} - // TarWithOptions creates an archive from the directory at `path`, only including files whose relative // paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index c127b307e..065e4e47f 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -14,9 +14,150 @@ import ( "testing" "time" + "github.com/docker/docker/pkg/system" "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) +func TestIsArchiveNilHeader(t *testing.T) { + out := IsArchive(nil) + if out { + t.Fatalf("isArchive should return false as nil is not a valid archive header") + } +} + +func TestIsArchiveInvalidHeader(t *testing.T) { + header := []byte{0x00, 0x01, 0x02} + out := IsArchive(header) + if out { + t.Fatalf("isArchive should return false as %s is not a valid archive header", header) + } +} + +func TestIsArchiveBzip2(t *testing.T) { + header := []byte{0x42, 0x5A, 0x68} + out := IsArchive(header) + if !out { + t.Fatalf("isArchive should return true as %s is a bz2 header", header) + } +} + +func TestIsArchive7zip(t *testing.T) { + header := []byte{0x50, 0x4b, 0x03, 0x04} + out := IsArchive(header) + if out { + t.Fatalf("isArchive should return false as %s is a 7z header and it is not supported", header) + } +} + +func TestDecompressStreamGzip(t *testing.T) { + cmd := exec.Command("/bin/sh", "-c", "touch /tmp/archive && gzip -f /tmp/archive") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Fail to create an archive file for test : %s.", output) + } + archive, err := os.Open("/tmp/archive.gz") + _, err = DecompressStream(archive) + if err != nil { + t.Fatalf("Failed to decompress a gzip file.") + } +} + +func TestDecompressStreamBzip2(t *testing.T) { + cmd := exec.Command("/bin/sh", "-c", "touch /tmp/archive && bzip2 -f /tmp/archive") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Fail to create an archive file for test : %s.", output) + } + archive, err := os.Open("/tmp/archive.bz2") + _, err = DecompressStream(archive) + if err != nil { + t.Fatalf("Failed to decompress a bzip2 file.") + } +} + +func TestDecompressStreamXz(t *testing.T) { + cmd := exec.Command("/bin/sh", "-c", "touch /tmp/archive && xz -f /tmp/archive") + output, err := cmd.CombinedOutput() + if err != nil { + t.Fatalf("Fail to create an archive file for test : %s.", output) + } + archive, err := os.Open("/tmp/archive.xz") + _, err = DecompressStream(archive) + if err != nil { + t.Fatalf("Failed to decompress a xz file.") + } +} + +func TestCompressStreamXzUnsuported(t *testing.T) { + dest, err := os.Create("/tmp/dest") + if err != nil { + t.Fatalf("Fail to create the destination file") + } + _, err = CompressStream(dest, Xz) + if err == nil { + t.Fatalf("Should fail as xz is unsupported for compression format.") + } +} + +func TestCompressStreamBzip2Unsupported(t *testing.T) { + dest, err := os.Create("/tmp/dest") + if err != nil { + t.Fatalf("Fail to create the destination file") + } + _, err = CompressStream(dest, Xz) + if err == nil { + t.Fatalf("Should fail as xz is unsupported for compression format.") + } +} + +func TestCompressStreamInvalid(t *testing.T) { + dest, err := os.Create("/tmp/dest") + if err != nil { + t.Fatalf("Fail to create the destination file") + } + _, err = CompressStream(dest, -1) + if err == nil { + t.Fatalf("Should fail as xz is unsupported for compression format.") + } +} + +func TestExtensionInvalid(t *testing.T) { + compression := Compression(-1) + output := compression.Extension() + if output != "" { + t.Fatalf("The extension of an invalid compression should be an empty string.") + } +} + +func TestExtensionUncompressed(t *testing.T) { + compression := Uncompressed + output := compression.Extension() + if output != "tar" { + t.Fatalf("The extension of a uncompressed archive should be 'tar'.") + } +} +func TestExtensionBzip2(t *testing.T) { + compression := Bzip2 + output := compression.Extension() + if output != "tar.bz2" { + t.Fatalf("The extension of a bzip2 archive should be 'tar.bz2'") + } +} +func TestExtensionGzip(t *testing.T) { + compression := Gzip + output := compression.Extension() + if output != "tar.gz" { + t.Fatalf("The extension of a bzip2 archive should be 'tar.gz'") + } +} +func TestExtensionXz(t *testing.T) { + compression := Xz + output := compression.Extension() + if output != "tar.xz" { + t.Fatalf("The extension of a bzip2 archive should be 'tar.xz'") + } +} + func TestCmdStreamLargeStderr(t *testing.T) { cmd := exec.Command("/bin/sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello") out, err := CmdStream(cmd, nil) @@ -179,11 +320,56 @@ func TestTarUntar(t *testing.T) { } } +func TestTarUntarWithXattr(t *testing.T) { + origin, err := ioutil.TempDir("", "docker-test-untar-origin") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(origin) + if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(path.Join(origin, "2"), []byte("welcome!"), 0700); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(path.Join(origin, "3"), []byte("will be ignored"), 0700); err != nil { + t.Fatal(err) + } + if err := system.Lsetxattr(path.Join(origin, "2"), "security.capability", []byte{0x00}, 0); err != nil { + t.Fatal(err) + } + + for _, c := range []Compression{ + Uncompressed, + Gzip, + } { + changes, err := tarUntar(t, origin, &TarOptions{ + Compression: c, + ExcludePatterns: []string{"3"}, + }) + + if err != nil { + t.Fatalf("Error tar/untar for compression %s: %s", c.Extension(), err) + } + + if len(changes) != 1 || changes[0].Path != "/3" { + t.Fatalf("Unexpected differences after tarUntar: %v", changes) + } + capability, _ := system.Lgetxattr(path.Join(origin, "2"), "security.capability") + if capability == nil && capability[0] != 0x00 { + t.Fatalf("Untar should have kept the 'security.capability' xattr.") + } + } +} + func TestTarWithOptions(t *testing.T) { origin, err := ioutil.TempDir("", "docker-test-untar-origin") if err != nil { t.Fatal(err) } + if _, err := ioutil.TempDir(origin, "folder"); err != nil { + t.Fatal(err) + } defer os.RemoveAll(origin) if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil { t.Fatal(err) @@ -196,8 +382,11 @@ func TestTarWithOptions(t *testing.T) { opts *TarOptions numChanges int }{ - {&TarOptions{IncludeFiles: []string{"1"}}, 1}, + {&TarOptions{IncludeFiles: []string{"1"}}, 2}, {&TarOptions{ExcludePatterns: []string{"2"}}, 1}, + {&TarOptions{ExcludePatterns: []string{"1", "folder*"}}, 2}, + {&TarOptions{IncludeFiles: []string{"1", "1"}}, 2}, + {&TarOptions{Name: "test", IncludeFiles: []string{"1"}}, 4}, } for _, testCase := range cases { changes, err := tarUntar(t, origin, testCase.opts) @@ -256,6 +445,58 @@ func TestUntarUstarGnuConflict(t *testing.T) { } } +func TestTarWithBlockCharFifo(t *testing.T) { + origin, err := ioutil.TempDir("", "docker-test-tar-hardlink") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(origin) + if err := ioutil.WriteFile(path.Join(origin, "1"), []byte("hello world"), 0700); err != nil { + t.Fatal(err) + } + if err := system.Mknod(path.Join(origin, "2"), syscall.S_IFBLK, int(system.Mkdev(int64(12), int64(5)))); err != nil { + t.Fatal(err) + } + if err := system.Mknod(path.Join(origin, "3"), syscall.S_IFCHR, int(system.Mkdev(int64(12), int64(5)))); err != nil { + t.Fatal(err) + } + if err := system.Mknod(path.Join(origin, "4"), syscall.S_IFIFO, int(system.Mkdev(int64(12), int64(5)))); err != nil { + t.Fatal(err) + } + + dest, err := ioutil.TempDir("", "docker-test-tar-hardlink-dest") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(dest) + + // we'll do this in two steps to separate failure + fh, err := Tar(origin, Uncompressed) + if err != nil { + t.Fatal(err) + } + + // ensure we can read the whole thing with no error, before writing back out + buf, err := ioutil.ReadAll(fh) + if err != nil { + t.Fatal(err) + } + + bRdr := bytes.NewReader(buf) + err = Untar(bRdr, dest, &TarOptions{Compression: Uncompressed}) + if err != nil { + t.Fatal(err) + } + + changes, err := ChangesDirs(origin, dest) + if err != nil { + t.Fatal(err) + } + if len(changes) > 0 { + t.Fatalf("Tar with special device (block, char, fifo) should keep them (recreate them when untar) : %s", changes) + } +} + func TestTarWithHardLink(t *testing.T) { origin, err := ioutil.TempDir("", "docker-test-tar-hardlink") if err != nil { From 621ee1f6a4cbc8ee8758ce77a0cf6215c88f12f7 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 12 Apr 2015 00:15:34 +0200 Subject: [PATCH 387/999] Remove job from execInspect Signed-off-by: Antonio Murdaca --- api/server/server.go | 11 ++++++++--- daemon/daemon.go | 1 - daemon/inspect.go | 15 +++------------ 3 files changed, 11 insertions(+), 16 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index ffbef8cee..66a0fa812 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1151,9 +1151,14 @@ func getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWri if vars == nil { return fmt.Errorf("Missing parameter 'id'") } - var job = eng.Job("execInspect", vars["id"]) - streamJSON(job, w, false) - return job.Run() + + d := getDaemon(eng) + eConfig, err := d.ContainerExecInspect(vars["id"]) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, eConfig) } func getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/daemon/daemon.go b/daemon/daemon.go index 86ed71e23..b7474fdbf 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -129,7 +129,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "stop": daemon.ContainerStop, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, - "execInspect": daemon.ContainerExecInspect, } { if err := eng.Register(name, method); err != nil { return err diff --git a/daemon/inspect.go b/daemon/inspect.go index 73ce2ea8e..7e25626ef 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -80,20 +80,11 @@ func (daemon *Daemon) ContainerInspect(job *engine.Job) error { return nil } -func (daemon *Daemon) ContainerExecInspect(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("usage: %s ID", job.Name) - } - id := job.Args[0] +func (daemon *Daemon) ContainerExecInspect(id string) (*execConfig, error) { eConfig, err := daemon.getExecConfig(id) if err != nil { - return err + return nil, err } - b, err := json.Marshal(*eConfig) - if err != nil { - return err - } - job.Stdout.Write(b) - return nil + return eConfig, nil } From 04cc6c6aa4f8ea656d23268b7bff0136e4fc6c8d Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 12 Apr 2015 00:41:16 +0200 Subject: [PATCH 388/999] Remove job from stop Signed-off-by: Antonio Murdaca --- api/server/server.go | 12 +++++++++--- daemon/daemon.go | 1 - daemon/stop.go | 20 +++----------------- 3 files changed, 12 insertions(+), 21 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index ffbef8cee..923e6528a 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -988,9 +988,14 @@ func postContainersStop(eng *engine.Engine, version version.Version, w http.Resp if vars == nil { return fmt.Errorf("Missing parameter") } - job := eng.Job("stop", vars["name"]) - job.Setenv("t", r.Form.Get("t")) - if err := job.Run(); err != nil { + + d := getDaemon(eng) + seconds, err := strconv.Atoi(r.Form.Get("t")) + if err != nil { + return err + } + + if err := d.ContainerStop(vars["name"], seconds); err != nil { if err.Error() == "Container already stopped" { w.WriteHeader(http.StatusNotModified) return nil @@ -998,6 +1003,7 @@ func postContainersStop(eng *engine.Engine, version version.Version, w http.Resp return err } w.WriteHeader(http.StatusNoContent) + return nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index 86ed71e23..f79457ada 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -126,7 +126,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "logs": daemon.ContainerLogs, "restart": daemon.ContainerRestart, "start": daemon.ContainerStart, - "stop": daemon.ContainerStop, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, "execInspect": daemon.ContainerExecInspect, diff --git a/daemon/stop.go b/daemon/stop.go index 871683be9..b481f87ef 100644 --- a/daemon/stop.go +++ b/daemon/stop.go @@ -1,22 +1,8 @@ package daemon -import ( - "fmt" +import "fmt" - "github.com/docker/docker/engine" -) - -func (daemon *Daemon) ContainerStop(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) - } - var ( - name = job.Args[0] - t = 10 - ) - if job.EnvExists("t") { - t = job.GetenvInt("t") - } +func (daemon *Daemon) ContainerStop(name string, seconds int) error { container, err := daemon.Get(name) if err != nil { return err @@ -24,7 +10,7 @@ func (daemon *Daemon) ContainerStop(job *engine.Job) error { if !container.IsRunning() { return fmt.Errorf("Container already stopped") } - if err := container.Stop(int(t)); err != nil { + if err := container.Stop(seconds); err != nil { return fmt.Errorf("Cannot stop container %s: %s\n", name, err) } container.LogEvent("stop") From 7560018541192ebdfe16e39515f9a04b44635d84 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 12 Apr 2015 16:25:10 +0200 Subject: [PATCH 389/999] Remove engine from links Signed-off-by: Antonio Murdaca --- daemon/container.go | 2 +- links/links.go | 5 +---- links/links_test.go | 13 +++++++------ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 46defe968..fc9ae1ae5 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1328,7 +1328,7 @@ func (container *Container) setupLinkedContainers() ([]string, error) { linkAlias, child.Config.Env, child.Config.ExposedPorts, - daemon.eng) + ) if err != nil { rollback() diff --git a/links/links.go b/links/links.go index 0e5e806e5..1ae8f23ae 100644 --- a/links/links.go +++ b/links/links.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/docker/docker/daemon/networkdriver/bridge" - "github.com/docker/docker/engine" "github.com/docker/docker/nat" ) @@ -17,10 +16,9 @@ type Link struct { ChildEnvironment []string Ports []nat.Port IsEnabled bool - eng *engine.Engine } -func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[nat.Port]struct{}, eng *engine.Engine) (*Link, error) { +func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[nat.Port]struct{}) (*Link, error) { var ( i int @@ -38,7 +36,6 @@ func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[nat. ParentIP: parentIP, ChildEnvironment: env, Ports: ports, - eng: eng, } return l, nil diff --git a/links/links_test.go b/links/links_test.go index ba548fc5b..e639e2c42 100644 --- a/links/links_test.go +++ b/links/links_test.go @@ -2,16 +2,17 @@ package links import ( "fmt" - "github.com/docker/docker/nat" "strings" "testing" + + "github.com/docker/docker/nat" ) func TestLinkNaming(t *testing.T) { ports := make(nat.PortSet) ports[nat.Port("6379/tcp")] = struct{}{} - link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker-1", nil, ports, nil) + link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker-1", nil, ports) if err != nil { t.Fatal(err) } @@ -41,7 +42,7 @@ func TestLinkNew(t *testing.T) { ports := make(nat.PortSet) ports[nat.Port("6379/tcp")] = struct{}{} - link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", nil, ports, nil) + link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", nil, ports) if err != nil { t.Fatal(err) } @@ -72,7 +73,7 @@ func TestLinkEnv(t *testing.T) { ports := make(nat.PortSet) ports[nat.Port("6379/tcp")] = struct{}{} - link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports, nil) + link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports) if err != nil { t.Fatal(err) } @@ -115,7 +116,7 @@ func TestLinkMultipleEnv(t *testing.T) { ports[nat.Port("6380/tcp")] = struct{}{} ports[nat.Port("6381/tcp")] = struct{}{} - link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports, nil) + link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports) if err != nil { t.Fatal(err) } @@ -164,7 +165,7 @@ func TestLinkPortRangeEnv(t *testing.T) { ports[nat.Port("6380/tcp")] = struct{}{} ports[nat.Port("6381/tcp")] = struct{}{} - link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports, nil) + link, err := NewLink("172.0.17.3", "172.0.17.2", "/db/docker", []string{"PASSWORD=gordon"}, ports) if err != nil { t.Fatal(err) } From 4ce19da739ccdb8337c59f841e790255d21c6f50 Mon Sep 17 00:00:00 2001 From: John Gossman Date: Sun, 12 Apr 2015 15:49:29 -0700 Subject: [PATCH 390/999] Addressed feedback. Will squash after further review Signed-off-by: John Gossman --- daemon/networkdriver/bridge/driver.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index d9698382e..11b26128c 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -135,7 +135,7 @@ func InitDriver(config *Config) error { return err } - logrus.Infof("Bridge interface not found, trying to create it") + logrus.Info("Bridge interface not found, trying to create it") // If the iface is not found, try to create it if err := configureBridge(config.IP, bridgeIPv6, config.EnableIPv6); err != nil { @@ -277,7 +277,7 @@ func InitDriver(config *Config) error { } logrus.Debugf("Subnet: %v", subnet) if err := ipAllocator.RegisterSubnet(subnet, subnet); err != nil { - logrus.Errorf("Error registering subnet for IPv4 bridge network: %s", err) + logrus.Errorf("Error registering subnet for IPv6 bridge network: %s", err) return err } globalIPv6Network = subnet From 80e9f6f83856b8e762f030d5e562f9e9f8d17233 Mon Sep 17 00:00:00 2001 From: John Gossman Date: Sun, 12 Apr 2015 16:26:37 -0700 Subject: [PATCH 391/999] More review feedback addressed Signed-off-by: John Gossman --- daemon/daemon.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 1acf96a02..f68363cb6 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -938,8 +938,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService if !config.DisableNetwork { if err := bridge.InitDriver(&config.Bridge); err != nil { - logrus.Errorf("Error initializing Bridge: %s", err) - return nil, err + return nil, fmt.Errorf("Error initializing Bridge: %v", err) } } From 908db51804635ce002e97e4efb867f7352204f8e Mon Sep 17 00:00:00 2001 From: Phil Estes Date: Fri, 10 Apr 2015 14:23:09 -0400 Subject: [PATCH 392/999] Send archive options via pipe in chrootarchive After finding our initial thinking on env. space versus arg list space was wrong, we need to solve this by using a pipe between the caller and child to marshall the (potentially very large) options array to the archiver. Docker-DCO-1.1-Signed-off-by: Phil Estes (github: estesp) --- pkg/chrootarchive/archive.go | 43 ++++++++++++++++++++----------- pkg/chrootarchive/archive_test.go | 37 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/pkg/chrootarchive/archive.go b/pkg/chrootarchive/archive.go index 17d3739d1..a1454f7b9 100644 --- a/pkg/chrootarchive/archive.go +++ b/pkg/chrootarchive/archive.go @@ -1,6 +1,7 @@ package chrootarchive import ( + "bytes" "encoding/json" "flag" "fmt" @@ -29,7 +30,8 @@ func untar() { var options *archive.TarOptions - if err := json.Unmarshal([]byte(os.Getenv("OPT")), &options); err != nil { + //read the options from the pipe "ExtraFiles" + if err := json.NewDecoder(os.NewFile(3, "options")).Decode(&options); err != nil { fatal(err) } @@ -62,28 +64,39 @@ func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error } } - // We can't pass the exclude list directly via cmd line - // because we easily overrun the shell max argument list length - // when the full image list is passed (e.g. when this is used - // by `docker load`). Instead we will add the JSON marshalled - // and placed in the env, which has significantly larger - // max size - data, err := json.Marshal(options) - if err != nil { - return fmt.Errorf("Untar json encode: %v", err) - } decompressedArchive, err := archive.DecompressStream(tarArchive) if err != nil { return err } defer decompressedArchive.Close() + // We can't pass a potentially large exclude list directly via cmd line + // because we easily overrun the kernel's max argument/environment size + // when the full image list is passed (e.g. when this is used by + // `docker load`). We will marshall the options via a pipe to the + // child + r, w, err := os.Pipe() + if err != nil { + return fmt.Errorf("Untar pipe failure: %v", err) + } cmd := reexec.Command("docker-untar", dest) cmd.Stdin = decompressedArchive - cmd.Env = append(cmd.Env, fmt.Sprintf("OPT=%s", data)) - out, err := cmd.CombinedOutput() - if err != nil { - return fmt.Errorf("Untar %s %s", err, out) + cmd.ExtraFiles = append(cmd.ExtraFiles, r) + var output bytes.Buffer + cmd.Stdout = &output + cmd.Stderr = &output + + if err := cmd.Start(); err != nil { + return fmt.Errorf("Untar error on re-exec cmd: %v", err) + } + //write the options to the pipe for the untar exec to read + if err := json.NewEncoder(w).Encode(options); err != nil { + return fmt.Errorf("Untar json encode to pipe failed: %v", err) + } + w.Close() + + if err := cmd.Wait(); err != nil { + return fmt.Errorf("Untar re-exec error: %v: output: %s", err, output) } return nil } diff --git a/pkg/chrootarchive/archive_test.go b/pkg/chrootarchive/archive_test.go index 45397d38f..18f7a93f9 100644 --- a/pkg/chrootarchive/archive_test.go +++ b/pkg/chrootarchive/archive_test.go @@ -8,6 +8,7 @@ import ( "os" "path" "path/filepath" + "strings" "testing" "time" @@ -48,6 +49,42 @@ func TestChrootTarUntar(t *testing.T) { } } +// gh#10426: Verify the fix for having a huge excludes list (like on `docker load` with large # of +// local images) +func TestChrootUntarWithHugeExcludesList(t *testing.T) { + tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarHugeExcludes") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpdir) + src := filepath.Join(tmpdir, "src") + if err := os.MkdirAll(src, 0700); err != nil { + t.Fatal(err) + } + if err := ioutil.WriteFile(filepath.Join(src, "toto"), []byte("hello toto"), 0644); err != nil { + t.Fatal(err) + } + stream, err := archive.Tar(src, archive.Uncompressed) + if err != nil { + t.Fatal(err) + } + dest := filepath.Join(tmpdir, "dest") + if err := os.MkdirAll(dest, 0700); err != nil { + t.Fatal(err) + } + options := &archive.TarOptions{} + //65534 entries of 64-byte strings ~= 4MB of environment space which should overflow + //on most systems when passed via environment or command line arguments + excludes := make([]string, 65534, 65534) + for i := 0; i < 65534; i++ { + excludes[i] = strings.Repeat(string(i), 64) + } + options.ExcludePatterns = excludes + if err := Untar(stream, dest, options); err != nil { + t.Fatal(err) + } +} + func TestChrootUntarEmptyArchive(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-TestChrootUntarEmptyArchive") if err != nil { From f5a07f0c884fc6d4fa7882cd8d07319c9bcfba1c Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Fri, 10 Apr 2015 15:53:05 +0800 Subject: [PATCH 393/999] Add event log for push Signed-off-by: Ma Shimiao --- graph/push.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graph/push.go b/graph/push.go index 881d24a9b..fedd29498 100644 --- a/graph/push.go +++ b/graph/push.go @@ -536,6 +536,7 @@ func (s *TagStore) CmdPush(job *engine.Job) error { if repoInfo.Index.Official || endpoint.Version == registry.APIVersion2 { err := s.pushV2Repository(r, localRepo, job.Stdout, repoInfo, tag, sf) if err == nil { + s.eventsService.Log("push", repoInfo.LocalName, "") return nil } @@ -547,6 +548,7 @@ func (s *TagStore) CmdPush(job *engine.Job) error { if err := s.pushRepository(r, job.Stdout, repoInfo, localRepo, tag, sf); err != nil { return err } + s.eventsService.Log("push", repoInfo.LocalName, "") return nil } From 91bfed604959c591a076c2e330cb3ded7443f504 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sat, 11 Apr 2015 23:49:14 +0200 Subject: [PATCH 394/999] Remove job from logs Signed-off-by: Antonio Murdaca --- api/server/server.go | 41 +++----- api/server/server_unit_test.go | 94 ------------------- daemon/daemon.go | 1 - daemon/logs.go | 74 ++++++++------- integration-cli/docker_api_containers_test.go | 51 +++++----- integration-cli/docker_api_exec_test.go | 2 +- integration-cli/docker_api_images_test.go | 2 +- integration-cli/docker_api_inspect_test.go | 2 +- integration-cli/docker_api_logs_test.go | 53 +++++++++++ integration-cli/docker_api_resize_test.go | 4 +- integration-cli/docker_cli_rm_test.go | 2 +- integration-cli/docker_utils.go | 20 ++-- integration-cli/requirements.go | 2 +- 13 files changed, 151 insertions(+), 197 deletions(-) create mode 100644 integration-cli/docker_api_logs_test.go diff --git a/api/server/server.go b/api/server/server.go index ffbef8cee..262a5072c 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -557,43 +557,26 @@ func getContainersLogs(eng *engine.Engine, version version.Version, w http.Respo return fmt.Errorf("Missing parameter") } - var ( - inspectJob = eng.Job("container_inspect", vars["name"]) - logsJob = eng.Job("logs", vars["name"]) - c, err = inspectJob.Stdout.AddEnv() - ) - if err != nil { - return err - } - logsJob.Setenv("follow", r.Form.Get("follow")) - logsJob.Setenv("tail", r.Form.Get("tail")) - logsJob.Setenv("stdout", r.Form.Get("stdout")) - logsJob.Setenv("stderr", r.Form.Get("stderr")) - logsJob.Setenv("timestamps", r.Form.Get("timestamps")) // Validate args here, because we can't return not StatusOK after job.Run() call - stdout, stderr := logsJob.GetenvBool("stdout"), logsJob.GetenvBool("stderr") + stdout, stderr := toBool(r.Form.Get("stdout")), toBool(r.Form.Get("stderr")) if !(stdout || stderr) { return fmt.Errorf("Bad parameters: you must choose at least one stream") } - if err = inspectJob.Run(); err != nil { - return err + + logsConfig := &daemon.ContainerLogsConfig{ + Follow: toBool(r.Form.Get("follow")), + Timestamps: toBool(r.Form.Get("timestamps")), + Tail: r.Form.Get("tail"), + UseStdout: stdout, + UseStderr: stderr, + OutStream: utils.NewWriteFlusher(w), } - var outStream, errStream io.Writer - outStream = utils.NewWriteFlusher(w) - - if c.GetSubEnv("Config") != nil && !c.GetSubEnv("Config").GetBool("Tty") && version.GreaterThanOrEqualTo("1.6") { - errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) - outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) - } else { - errStream = outStream + d := getDaemon(eng) + if err := d.ContainerLogs(vars["name"], logsConfig); err != nil { + fmt.Fprintf(w, "Error running logs job: %s\n", err) } - logsJob.Stdout.Add(outStream) - logsJob.Stderr.Set(errStream) - if err := logsJob.Run(); err != nil { - fmt.Fprintf(outStream, "Error running logs job: %s\n", err) - } return nil } diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index b5dd984b0..78b95b26b 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -7,7 +7,6 @@ import ( "io" "net/http" "net/http/httptest" - "strings" "testing" "github.com/docker/docker/api" @@ -126,99 +125,6 @@ func TestGetContainersByName(t *testing.T) { } } -func TestLogs(t *testing.T) { - eng := engine.New() - var inspect bool - var logs bool - eng.Register("container_inspect", func(job *engine.Job) error { - inspect = true - if len(job.Args) == 0 { - t.Fatal("Job arguments is empty") - } - if job.Args[0] != "test" { - t.Fatalf("Container name %s, must be test", job.Args[0]) - } - return nil - }) - expected := "logs" - eng.Register("logs", func(job *engine.Job) error { - logs = true - if len(job.Args) == 0 { - t.Fatal("Job arguments is empty") - } - if job.Args[0] != "test" { - t.Fatalf("Container name %s, must be test", job.Args[0]) - } - follow := job.Getenv("follow") - if follow != "1" { - t.Fatalf("follow: %s, must be 1", follow) - } - stdout := job.Getenv("stdout") - if stdout != "1" { - t.Fatalf("stdout %s, must be 1", stdout) - } - stderr := job.Getenv("stderr") - if stderr != "" { - t.Fatalf("stderr %s, must be empty", stderr) - } - timestamps := job.Getenv("timestamps") - if timestamps != "1" { - t.Fatalf("timestamps %s, must be 1", timestamps) - } - job.Stdout.Write([]byte(expected)) - return nil - }) - r := serveRequest("GET", "/containers/test/logs?follow=1&stdout=1×tamps=1", nil, eng, t) - if r.Code != http.StatusOK { - t.Fatalf("Got status %d, expected %d", r.Code, http.StatusOK) - } - if !inspect { - t.Fatal("container_inspect job was not called") - } - if !logs { - t.Fatal("logs job was not called") - } - res := r.Body.String() - if res != expected { - t.Fatalf("Output %s, expected %s", res, expected) - } -} - -func TestLogsNoStreams(t *testing.T) { - eng := engine.New() - var inspect bool - var logs bool - eng.Register("container_inspect", func(job *engine.Job) error { - inspect = true - if len(job.Args) == 0 { - t.Fatal("Job arguments is empty") - } - if job.Args[0] != "test" { - t.Fatalf("Container name %s, must be test", job.Args[0]) - } - return nil - }) - eng.Register("logs", func(job *engine.Job) error { - logs = true - return nil - }) - r := serveRequest("GET", "/containers/test/logs", nil, eng, t) - if r.Code != http.StatusBadRequest { - t.Fatalf("Got status %d, expected %d", r.Code, http.StatusBadRequest) - } - if inspect { - t.Fatal("container_inspect job was called, but it shouldn't") - } - if logs { - t.Fatal("logs job was called, but it shouldn't") - } - res := strings.TrimSpace(r.Body.String()) - expected := "Bad parameters: you must choose at least one stream" - if !strings.Contains(res, expected) { - t.Fatalf("Output %s, expected %s in it", res, expected) - } -} - func TestGetImagesByName(t *testing.T) { eng := engine.New() name := "image_name" diff --git a/daemon/daemon.go b/daemon/daemon.go index 86ed71e23..505a09e0a 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -123,7 +123,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "create": daemon.ContainerCreate, "export": daemon.ContainerExport, "info": daemon.CmdInfo, - "logs": daemon.ContainerLogs, "restart": daemon.ContainerRestart, "start": daemon.ContainerStart, "stop": daemon.ContainerStop, diff --git a/daemon/logs.go b/daemon/logs.go index c991fa197..79d4044bb 100644 --- a/daemon/logs.go +++ b/daemon/logs.go @@ -10,40 +10,50 @@ import ( "sync" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/jsonlog" + "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/tailfile" "github.com/docker/docker/pkg/timeutils" ) -func (daemon *Daemon) ContainerLogs(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) - } +type ContainerLogsConfig struct { + Follow, Timestamps bool + Tail string + UseStdout, UseStderr bool + OutStream io.Writer +} +func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) error { var ( - name = job.Args[0] - stdout = job.GetenvBool("stdout") - stderr = job.GetenvBool("stderr") - tail = job.Getenv("tail") - follow = job.GetenvBool("follow") - times = job.GetenvBool("timestamps") lines = -1 format string ) - if !(stdout || stderr) { + if !(config.UseStdout || config.UseStderr) { return fmt.Errorf("You must choose at least one stream") } - if times { + if config.Timestamps { format = timeutils.RFC3339NanoFixed } - if tail == "" { - tail = "all" + if config.Tail == "" { + config.Tail = "all" } + container, err := daemon.Get(name) if err != nil { return err } + + var ( + outStream = config.OutStream + errStream io.Writer + ) + if !container.Config.Tty { + errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) + outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) + } else { + errStream = outStream + } + if container.LogDriverType() != "json-file" { return fmt.Errorf("\"logs\" endpoint is supported only for \"json-file\" logging driver") } @@ -51,30 +61,30 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error { if err != nil && os.IsNotExist(err) { // Legacy logs logrus.Debugf("Old logs format") - if stdout { + if config.UseStdout { cLog, err := container.ReadLog("stdout") if err != nil { logrus.Errorf("Error reading logs (stdout): %s", err) - } else if _, err := io.Copy(job.Stdout, cLog); err != nil { + } else if _, err := io.Copy(outStream, cLog); err != nil { logrus.Errorf("Error streaming logs (stdout): %s", err) } } - if stderr { + if config.UseStderr { cLog, err := container.ReadLog("stderr") if err != nil { logrus.Errorf("Error reading logs (stderr): %s", err) - } else if _, err := io.Copy(job.Stderr, cLog); err != nil { + } else if _, err := io.Copy(errStream, cLog); err != nil { logrus.Errorf("Error streaming logs (stderr): %s", err) } } } else if err != nil { logrus.Errorf("Error reading logs (json): %s", err) } else { - if tail != "all" { + if config.Tail != "all" { var err error - lines, err = strconv.Atoi(tail) + lines, err = strconv.Atoi(config.Tail) if err != nil { - logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", tail, err) + logrus.Errorf("Failed to parse tail %s, error: %v, show all logs", config.Tail, err) lines = -1 } } @@ -101,39 +111,39 @@ func (daemon *Daemon) ContainerLogs(job *engine.Job) error { break } logLine := l.Log - if times { + if config.Timestamps { // format can be "" or time format, so here can't be error logLine, _ = l.Format(format) } - if l.Stream == "stdout" && stdout { - io.WriteString(job.Stdout, logLine) + if l.Stream == "stdout" && config.UseStdout { + io.WriteString(outStream, logLine) } - if l.Stream == "stderr" && stderr { - io.WriteString(job.Stderr, logLine) + if l.Stream == "stderr" && config.UseStderr { + io.WriteString(errStream, logLine) } l.Reset() } } } - if follow && container.IsRunning() { + if config.Follow && container.IsRunning() { errors := make(chan error, 2) wg := sync.WaitGroup{} - if stdout { + if config.UseStdout { wg.Add(1) stdoutPipe := container.StdoutLogPipe() defer stdoutPipe.Close() go func() { - errors <- jsonlog.WriteLog(stdoutPipe, job.Stdout, format) + errors <- jsonlog.WriteLog(stdoutPipe, outStream, format) wg.Done() }() } - if stderr { + if config.UseStderr { wg.Add(1) stderrPipe := container.StderrLogPipe() defer stderrPipe.Close() go func() { - errors <- jsonlog.WriteLog(stderrPipe, job.Stderr, format) + errors <- jsonlog.WriteLog(stderrPipe, errStream, format) wg.Done() }() } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 07793a8fc..2771c7c02 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -3,14 +3,15 @@ package main import ( "bytes" "encoding/json" - "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io" "os/exec" "strings" "testing" "time" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) func TestContainerApiGetAll(t *testing.T) { @@ -28,7 +29,7 @@ func TestContainerApiGetAll(t *testing.T) { t.Fatalf("Error on container creation: %v, output: %q", err, out) } - body, err := sockRequest("GET", "/containers/json?all=1", nil) + _, body, err := sockRequest("GET", "/containers/json?all=1", nil) if err != nil { t.Fatalf("GET all containers sockRequest failed: %v", err) } @@ -61,7 +62,7 @@ func TestContainerApiGetExport(t *testing.T) { t.Fatalf("Error on container creation: %v, output: %q", err, out) } - body, err := sockRequest("GET", "/containers/"+name+"/export", nil) + _, body, err := sockRequest("GET", "/containers/"+name+"/export", nil) if err != nil { t.Fatalf("GET containers/export sockRequest failed: %v", err) } @@ -98,7 +99,7 @@ func TestContainerApiGetChanges(t *testing.T) { t.Fatalf("Error on container creation: %v, output: %q", err, out) } - body, err := sockRequest("GET", "/containers/"+name+"/changes", nil) + _, body, err := sockRequest("GET", "/containers/"+name+"/changes", nil) if err != nil { t.Fatalf("GET containers/changes sockRequest failed: %v", err) } @@ -133,7 +134,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) { "Volumes": map[string]struct{}{"/tmp": {}}, } - if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { t.Fatal(err) } @@ -141,7 +142,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) { config = map[string]interface{}{ "Binds": []string{bindPath + ":/tmp"}, } - if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { t.Fatal(err) } @@ -166,7 +167,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) { "Volumes": map[string]struct{}{"/tmp": {}}, } - if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { t.Fatal(err) } @@ -176,7 +177,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) { config = map[string]interface{}{ "Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"}, } - if body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil { + if _, body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil { t.Fatal("expected container start to fail when duplicate volume binds to same container path") } else { if !strings.Contains(string(body), "Duplicate volume") { @@ -201,14 +202,14 @@ func TestContainerApiStartVolumesFrom(t *testing.T) { "Volumes": map[string]struct{}{volPath: {}}, } - if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { t.Fatal(err) } config = map[string]interface{}{ "VolumesFrom": []string{volName}, } - if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { t.Fatal(err) } @@ -245,7 +246,7 @@ func TestVolumesFromHasPriority(t *testing.T) { "Volumes": map[string]struct{}{volPath: {}}, } - if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { t.Fatal(err) } @@ -254,7 +255,7 @@ func TestVolumesFromHasPriority(t *testing.T) { "VolumesFrom": []string{volName}, "Binds": []string{bindPath + ":/tmp"}, } - if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { t.Fatal(err) } @@ -290,7 +291,7 @@ func TestGetContainerStats(t *testing.T) { } bc := make(chan b, 1) go func() { - body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) + _, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) bc <- b{body, err} }() @@ -334,7 +335,7 @@ func TestGetStoppedContainerStats(t *testing.T) { go func() { // We'll never get return for GET stats from sockRequest as of now, // just send request and see if panic or error would happen on daemon side. - _, err := sockRequest("GET", "/containers/"+name+"/stats", nil) + _, _, err := sockRequest("GET", "/containers/"+name+"/stats", nil) if err != nil { t.Fatal(err) } @@ -367,7 +368,7 @@ func TestBuildApiDockerfilePath(t *testing.T) { t.Fatalf("failed to close tar archive: %v", err) } - out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") + _, out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") if err == nil { t.Fatalf("Build was supposed to fail: %s", out) } @@ -391,7 +392,7 @@ RUN find /tmp/`, } defer server.Close() - buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") + _, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") if err != nil { t.Fatalf("Build failed: %s", err) } @@ -417,7 +418,7 @@ RUN echo from dockerfile`, } defer git.Close() - buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + _, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") if err != nil { t.Fatalf("Build failed: %s\n%q", err, buf) } @@ -443,7 +444,7 @@ RUN echo from Dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") + _, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") if err != nil { t.Fatalf("Build failed: %s\n%q", err, buf) } @@ -470,7 +471,7 @@ RUN echo from dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + _, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") if err != nil { t.Fatalf("Build failed: %s", err) } @@ -501,7 +502,7 @@ func TestBuildApiDockerfileSymlink(t *testing.T) { t.Fatalf("failed to close tar archive: %v", err) } - out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") + _, out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") if err == nil { t.Fatalf("Build was supposed to fail: %s", out) } @@ -537,7 +538,7 @@ func TestPostContainerBindNormalVolume(t *testing.T) { } bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}} - _, err = sockRequest("POST", "/containers/two/start", bindSpec) + _, _, err = sockRequest("POST", "/containers/two/start", bindSpec) if err != nil && !strings.Contains(err.Error(), "204 No Content") { t.Fatal(err) } @@ -565,7 +566,7 @@ func TestContainerApiPause(t *testing.T) { } ContainerID := strings.TrimSpace(out) - if _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { t.Fatalf("POST a container pause: sockRequest failed: %v", err) } @@ -579,7 +580,7 @@ func TestContainerApiPause(t *testing.T) { t.Fatalf("there should be one paused container and not %d", len(pausedContainers)) } - if _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { t.Fatalf("POST a container pause: sockRequest failed: %v", err) } diff --git a/integration-cli/docker_api_exec_test.go b/integration-cli/docker_api_exec_test.go index 1ed99a256..f898250a1 100644 --- a/integration-cli/docker_api_exec_test.go +++ b/integration-cli/docker_api_exec_test.go @@ -18,7 +18,7 @@ func TestExecApiCreateNoCmd(t *testing.T) { t.Fatal(out, err) } - body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}) + _, body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}) if err == nil || !bytes.Contains(body, []byte("No exec command specified")) { t.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err) } diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 38d891fd5..49cfb36da 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -8,7 +8,7 @@ import ( ) func TestLegacyImages(t *testing.T) { - body, err := sockRequest("GET", "/v1.6/images/json", nil) + _, body, err := sockRequest("GET", "/v1.6/images/json", nil) if err != nil { t.Fatalf("Error on GET: %s", err) } diff --git a/integration-cli/docker_api_inspect_test.go b/integration-cli/docker_api_inspect_test.go index e43f10fd6..43144f916 100644 --- a/integration-cli/docker_api_inspect_test.go +++ b/integration-cli/docker_api_inspect_test.go @@ -27,7 +27,7 @@ func TestInspectApiContainerResponse(t *testing.T) { if testVersion != "latest" { endpoint = "/" + testVersion + endpoint } - body, err := sockRequest("GET", endpoint, nil) + _, body, err := sockRequest("GET", endpoint, nil) if err != nil { t.Fatalf("sockRequest failed for %s version: %v", testVersion, err) } diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go new file mode 100644 index 000000000..27eb31c33 --- /dev/null +++ b/integration-cli/docker_api_logs_test.go @@ -0,0 +1,53 @@ +package main + +import ( + "bytes" + "fmt" + "net/http" + "os/exec" + "testing" +) + +func TestLogsApiWithStdout(t *testing.T) { + defer deleteAllContainers() + name := "logs_test" + + runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "bin/sh", "-c", "sleep 10 && echo "+name) + if out, _, err := runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", name), nil) + + if err != nil || statusCode != http.StatusOK { + t.Fatalf("Expected %d from logs request, got %d", http.StatusOK, statusCode) + } + + if !bytes.Contains(body, []byte(name)) { + t.Fatalf("Expected %s, got %s", name, string(body[:])) + } + + logDone("logs API - with stdout ok") +} + +func TestLogsApiNoStdoutNorStderr(t *testing.T) { + defer deleteAllContainers() + name := "logs_test" + runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh") + if out, _, err := runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs", name), nil) + + if err == nil || statusCode != http.StatusBadRequest { + t.Fatalf("Expected %d from logs request, got %d", http.StatusBadRequest, statusCode) + } + + expected := "Bad parameters: you must choose at least one stream" + if !bytes.Contains(body, []byte(expected)) { + t.Fatalf("Expected %s, got %s", expected, string(body[:])) + } + + logDone("logs API - returns error when no stdout nor stderr specified") +} diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index be36be8c1..27d7d10a0 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -16,7 +16,7 @@ func TestResizeApiResponse(t *testing.T) { cleanedContainerID := strings.TrimSpace(out) endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" - _, err = sockRequest("POST", endpoint, nil) + _, _, err = sockRequest("POST", endpoint, nil) if err != nil { t.Fatalf("resize Request failed %v", err) } @@ -41,7 +41,7 @@ func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) { } endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" - body, err := sockRequest("POST", endpoint, nil) + _, body, err := sockRequest("POST", endpoint, nil) if err == nil { t.Fatalf("resize should fail when container is not started") } diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index d01b36d45..8f8ea7b66 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -64,7 +64,7 @@ func TestRmRunningContainerCheckError409(t *testing.T) { createRunningContainer(t, "foo") endpoint := "/containers/foo" - _, err := sockRequest("DELETE", endpoint, nil) + _, _, err := sockRequest("DELETE", endpoint, nil) if err == nil { t.Fatalf("Expected error, can't rm a running container") diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 943c1e02a..4984b578b 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -298,19 +298,19 @@ func sockConn(timeout time.Duration) (net.Conn, error) { } } -func sockRequest(method, endpoint string, data interface{}) ([]byte, error) { +func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) { jsonData := bytes.NewBuffer(nil) if err := json.NewEncoder(jsonData).Encode(data); err != nil { - return nil, err + return -1, nil, err } return sockRequestRaw(method, endpoint, jsonData, "application/json") } -func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, error) { +func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, []byte, error) { c, err := sockConn(time.Duration(10 * time.Second)) if err != nil { - return nil, fmt.Errorf("could not dial docker daemon: %v", err) + return -1, nil, fmt.Errorf("could not dial docker daemon: %v", err) } client := httputil.NewClientConn(c, nil) @@ -318,7 +318,7 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, req, err := http.NewRequest(method, endpoint, data) if err != nil { - return nil, fmt.Errorf("could not create new request: %v", err) + return -1, nil, fmt.Errorf("could not create new request: %v", err) } if ct == "" { @@ -328,15 +328,17 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, resp, err := client.Do(req) if err != nil { - return nil, fmt.Errorf("could not perform request: %v", err) + return -1, nil, fmt.Errorf("could not perform request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := ioutil.ReadAll(resp.Body) - return body, fmt.Errorf("received status != 200 OK: %s", resp.Status) + return resp.StatusCode, body, fmt.Errorf("received status != 200 OK: %s", resp.Status) } - return ioutil.ReadAll(resp.Body) + b, err := ioutil.ReadAll(resp.Body) + + return resp.StatusCode, b, err } func deleteContainer(container string) error { @@ -1041,7 +1043,7 @@ func daemonTime(t *testing.T) time.Time { return time.Now() } - body, err := sockRequest("GET", "/info", nil) + _, body, err := sockRequest("GET", "/info", nil) if err != nil { t.Fatalf("daemonTime: failed to get /info: %v", err) } diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go index cdd999187..9769e2d3a 100644 --- a/integration-cli/requirements.go +++ b/integration-cli/requirements.go @@ -57,7 +57,7 @@ var ( func() bool { if daemonExecDriver == "" { // get daemon info - body, err := sockRequest("GET", "/info", nil) + _, body, err := sockRequest("GET", "/info", nil) if err != nil { log.Fatalf("sockRequest failed for /info: %v", err) } From 65a056345cec1b85bd41ed70ee814894709ee6c0 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 13 Apr 2015 08:33:53 +0200 Subject: [PATCH 395/999] Remove jobs from stats Signed-off-by: Antonio Murdaca --- api/server/server.go | 8 ++++---- daemon/daemon.go | 1 - daemon/stats.go | 10 +++++----- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index d1f9b1894..840c656b1 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -543,10 +543,10 @@ func getContainersStats(eng *engine.Engine, version version.Version, w http.Resp if vars == nil { return fmt.Errorf("Missing parameter") } - name := vars["name"] - job := eng.Job("container_stats", name) - streamJSON(job, w, true) - return job.Run() + + d := getDaemon(eng) + + return d.ContainerStats(vars["name"], utils.NewWriteFlusher(w)) } func getContainersLogs(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/daemon/daemon.go b/daemon/daemon.go index 40d1ecb41..290a9bebe 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -119,7 +119,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "commit": daemon.ContainerCommit, "container_inspect": daemon.ContainerInspect, - "container_stats": daemon.ContainerStats, "create": daemon.ContainerCreate, "export": daemon.ContainerExport, "info": daemon.CmdInfo, diff --git a/daemon/stats.go b/daemon/stats.go index e40788013..a95168d12 100644 --- a/daemon/stats.go +++ b/daemon/stats.go @@ -2,20 +2,20 @@ package daemon import ( "encoding/json" + "io" "github.com/docker/docker/api/types" "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/engine" "github.com/docker/libcontainer" "github.com/docker/libcontainer/cgroups" ) -func (daemon *Daemon) ContainerStats(job *engine.Job) error { - updates, err := daemon.SubscribeToContainerStats(job.Args[0]) +func (daemon *Daemon) ContainerStats(name string, out io.Writer) error { + updates, err := daemon.SubscribeToContainerStats(name) if err != nil { return err } - enc := json.NewEncoder(job.Stdout) + enc := json.NewEncoder(out) for v := range updates { update := v.(*execdriver.ResourceStats) ss := convertToAPITypes(update.Stats) @@ -24,7 +24,7 @@ func (daemon *Daemon) ContainerStats(job *engine.Job) error { ss.CpuStats.SystemUsage = update.SystemUsage if err := enc.Encode(ss); err != nil { // TODO: handle the specific broken pipe - daemon.UnsubscribeToContainerStats(job.Args[0], updates) + daemon.UnsubscribeToContainerStats(name, updates) return err } } From 3341f3a3554524f0f5e6114dfa0ed988713803ef Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 13 Apr 2015 08:36:04 +0200 Subject: [PATCH 396/999] fix api server resize&execResize Signed-off-by: Antonio Murdaca --- api/server/server.go | 21 ++++++---------- .../docker_api_exec_resize_test.go | 25 +++++++++++++++++++ integration-cli/docker_api_resize_test.go | 18 +++++++++++++ 3 files changed, 50 insertions(+), 14 deletions(-) create mode 100644 integration-cli/docker_api_exec_resize_test.go diff --git a/api/server/server.go b/api/server/server.go index d1f9b1894..d7a57be30 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1036,11 +1036,11 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re height, err := strconv.Atoi(r.Form.Get("h")) if err != nil { - return nil + return err } width, err := strconv.Atoi(r.Form.Get("w")) if err != nil { - return nil + return err } d := getDaemon(eng) @@ -1049,11 +1049,7 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re return err } - if err := cont.Resize(height, width); err != nil { - return err - } - - return nil + return cont.Resize(height, width) } func postContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -1416,19 +1412,16 @@ func postContainerExecResize(eng *engine.Engine, version version.Version, w http height, err := strconv.Atoi(r.Form.Get("h")) if err != nil { - return nil + return err } width, err := strconv.Atoi(r.Form.Get("w")) if err != nil { - return nil - } - - d := getDaemon(eng) - if err := d.ContainerExecResize(vars["name"], height, width); err != nil { return err } - return nil + d := getDaemon(eng) + + return d.ContainerExecResize(vars["name"], height, width) } func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/integration-cli/docker_api_exec_resize_test.go b/integration-cli/docker_api_exec_resize_test.go new file mode 100644 index 000000000..4108e74dc --- /dev/null +++ b/integration-cli/docker_api_exec_resize_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "os/exec" + "strings" + "testing" +) + +func TestExecResizeApiHeightWidthNoInt(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf(out, err) + } + defer deleteAllContainers() + cleanedContainerID := strings.TrimSpace(out) + + endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar" + _, err = sockRequest("POST", endpoint, nil) + if err == nil { + t.Fatal("Expected exec resize Request to fail") + } + + logDone("container exec resize - height, width no int fail") +} diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index be36be8c1..bf8beb791 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -24,6 +24,24 @@ func TestResizeApiResponse(t *testing.T) { logDone("container resize - when started") } +func TestResizeApiHeightWidthNoInt(t *testing.T) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatalf(out, err) + } + defer deleteAllContainers() + cleanedContainerID := strings.TrimSpace(out) + + endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar" + _, err = sockRequest("POST", endpoint, nil) + if err == nil { + t.Fatal("Expected resize Request to fail") + } + + logDone("container resize - height, width no int fail") +} + func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) From c49cc1f2fbd3d0256455750c885eadcaa3d17937 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Mon, 13 Apr 2015 16:24:49 +0800 Subject: [PATCH 397/999] fix build test by adding --no-cache Testcase TestBuildResourceConstraintsAreUsed run build without --no-cache, so if you run this test twice, it will fail the second time. TESTFLAGS='-v -run ^TestBuildResourceConstraintsAreUsed$' ./hack/make.sh binary test-integration-cli [PASSED] TESTFLAGS='-v -run ^TestBuildResourceConstraintsAreUsed$' ./hack/make.sh binary test-integration-cli [FAIL] Because we'll use cID to inspect field and will get empty cID if we have cache. Signed-off-by: Qiang Huang --- integration-cli/docker_cli_build_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 01e6d1d3d..40e038fc4 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5555,7 +5555,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { t.Fatal(err) } - cmd := exec.Command(dockerBinary, "build", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpu-shares=100", "-t", name, ".") + cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpu-shares=100", "-t", name, ".") cmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(cmd) From a715b0e31beabed9ac5702af2ede619b4232cbdb Mon Sep 17 00:00:00 2001 From: Deshi Xiao Date: Mon, 13 Apr 2015 17:21:27 +0800 Subject: [PATCH 398/999] correct pkg/stdcopy NewStdWriter function comments pkg/stdcopy NewStdWriter function has wrong doc comment, utils is not correct, it should be stdcopy Signed-off-by: Deshi Xiao --- pkg/stdcopy/stdcopy.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/stdcopy/stdcopy.go b/pkg/stdcopy/stdcopy.go index ccf1d9dba..4f78f20d1 100644 --- a/pkg/stdcopy/stdcopy.go +++ b/pkg/stdcopy/stdcopy.go @@ -52,7 +52,7 @@ func (w *StdWriter) Write(buf []byte) (n int, err error) { // and written to the underlying `w` stream. // This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection. // `t` indicates the id of the stream to encapsulate. -// It can be utils.Stdin, utils.Stdout, utils.Stderr. +// It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr. func NewStdWriter(w io.Writer, t StdType) *StdWriter { if len(t) != StdWriterPrefixLen { return nil From 8b3548129220a8c79342a12717d87667927df4c9 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Mon, 13 Apr 2015 20:24:10 +0800 Subject: [PATCH 399/999] Fix daemon panic when release a nil network interface Signed-off-by: Lei Jitang --- daemon/networkdriver/bridge/driver.go | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index dabb1165e..4eebb9d04 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -583,6 +583,7 @@ func Release(id string) { if containerInterface == nil { logrus.Warnf("No network information to release for %s", id) + return } for _, nat := range containerInterface.PortMappings { From 1567cf2cdf07bcbafbb1555fd950f5c5ce7a1c66 Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Mon, 13 Apr 2015 23:09:07 +0800 Subject: [PATCH 400/999] Fix typo in testcase Signed-off-by: Hu Keping --- integration-cli/docker_api_containers_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 07793a8fc..145fef508 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -593,5 +593,5 @@ func TestContainerApiPause(t *testing.T) { t.Fatalf("There should be no paused container.") } - logDone("container REST API - check POST containers/pause nad unpause") + logDone("container REST API - check POST containers/pause and unpause") } From 6b737752e342e30dd20417b18c92c9b4e1c4f8da Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 12 Apr 2015 16:04:01 +0200 Subject: [PATCH 401/999] Remove job from export Signed-off-by: Antonio Murdaca --- api/server/server.go | 10 ++++------ daemon/daemon.go | 1 - daemon/export.go | 11 ++--------- 3 files changed, 6 insertions(+), 16 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index d1f9b1894..6d9df561c 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -280,12 +280,10 @@ func getContainersExport(eng *engine.Engine, version version.Version, w http.Res if vars == nil { return fmt.Errorf("Missing parameter") } - job := eng.Job("export", vars["name"]) - job.Stdout.Add(w) - if err := job.Run(); err != nil { - return err - } - return nil + + d := getDaemon(eng) + + return d.ContainerExport(vars["name"], w) } func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/daemon/daemon.go b/daemon/daemon.go index 40d1ecb41..748dd5cf5 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -121,7 +121,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "container_inspect": daemon.ContainerInspect, "container_stats": daemon.ContainerStats, "create": daemon.ContainerCreate, - "export": daemon.ContainerExport, "info": daemon.CmdInfo, "logs": daemon.ContainerLogs, "restart": daemon.ContainerRestart, diff --git a/daemon/export.go b/daemon/export.go index b1417b932..b94b6100c 100644 --- a/daemon/export.go +++ b/daemon/export.go @@ -3,16 +3,9 @@ package daemon import ( "fmt" "io" - - "github.com/docker/docker/engine" ) -func (daemon *Daemon) ContainerExport(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s container_id", job.Name) - } - name := job.Args[0] - +func (daemon *Daemon) ContainerExport(name string, out io.Writer) error { container, err := daemon.Get(name) if err != nil { return err @@ -25,7 +18,7 @@ func (daemon *Daemon) ContainerExport(job *engine.Job) error { defer data.Close() // Stream the entire contents of the container (basically a volatile snapshot) - if _, err := io.Copy(job.Stdout, data); err != nil { + if _, err := io.Copy(out, data); err != nil { return fmt.Errorf("%s: %s", name, err) } // FIXME: factor job-specific LogEvent to engine.Job.Run() From 5ad15479a0f3ce804a44a6931b716b0fae22ac6d Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 13 Apr 2015 17:33:59 +0100 Subject: [PATCH 402/999] Add a note about PID 1 not terminating on SIGINT/SIGTERM. Also re-arranged the description of CTRL-c to make it clearer. Signed-off-by: Bryan Boreham --- docs/sources/reference/commandline/cli.md | 14 ++++++++++---- docs/sources/reference/run.md | 5 +++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index b9f506a6c..b78a169b5 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -516,10 +516,16 @@ interactively. You can attach to the same contained process multiple times simultaneously, screen sharing style, or quickly view the progress of your daemonized process. -You can detach from the container (and leave it running) with `CTRL-p CTRL-q` -(for a quiet exit) or `CTRL-c` which will send a `SIGKILL` to the container. -When you are attached to a container, and exit its main process, the process's -exit code will be returned to the client. +You can detach from the container and leave it running with `CTRL-p +CTRL-q` (for a quiet exit) or with `CTRL-c` if `--sig-proxy` is false. + +If `--sig-proxy` is true (the default),`CTRL-c` sends a `SIGINT` +to the container. + +>**Note**: A process running as PID 1 inside a container is treated +>specially by Linux: it ignores any signal with the default action. +>So, the process will not terminate on `SIGINT` or `SIGTERM` unless it is +>coded to do so. It is forbidden to redirect the standard input of a `docker attach` command while attaching to a tty-enabled container (i.e.: launched with `-t`). diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index daf26bff8..d41b686c4 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -111,6 +111,11 @@ as you'll see in later examples. Specifying `-t` is forbidden when the client standard output is redirected or piped, such as in: `echo test | docker run -i busybox cat`. +>**Note**: A process running as PID 1 inside a container is treated +>specially by Linux: it ignores any signal with the default action. +>So, the process will not terminate on `SIGINT` or `SIGTERM` unless it is +>coded to do so. + ## Container identification ### Name (--name) From d42753485b71f5f26b682a187d1963ef138cd0ab Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 3 Apr 2015 02:07:57 -0600 Subject: [PATCH 403/999] Add "bundles/latest" symlink This is a symlink to the latest "bundle" that was assembled. For example, if `VERSION` is currently `1.5.0-dev`, then `bundles/latest` will be a symlink to `bundles/1.5.0-dev` after an attempted build. One interesting property of this is that after a successful `binary` build, we can `./bundles/latest/binary/docker -v` and get back something like `Docker version 1.5.0-dev, build 3ff6723-dirty`. Signed-off-by: Andrew "Tianon" Page --- hack/make.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/hack/make.sh b/hack/make.sh index 118d4327f..8a9317663 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -255,6 +255,12 @@ main() { rm -fr bundles/$VERSION && mkdir bundles/$VERSION || exit 1 echo fi + + if [ "$(go env GOHOSTOS)" != 'windows' ]; then + # Windows and symlinks don't get along well + ln -sfT $VERSION bundles/latest + fi + SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if [ $# -lt 1 ]; then bundles=(${DEFAULT_BUNDLES[@]}) From 27fccdbabb277e29488de88f9af2e7b83af93132 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 13 Apr 2015 10:30:07 -0700 Subject: [PATCH 404/999] Fix errors due changed sockRequest signature Signed-off-by: Alexander Morozov --- integration-cli/docker_api_exec_resize_test.go | 6 +++++- integration-cli/docker_api_resize_test.go | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_api_exec_resize_test.go b/integration-cli/docker_api_exec_resize_test.go index 4108e74dc..d4290408e 100644 --- a/integration-cli/docker_api_exec_resize_test.go +++ b/integration-cli/docker_api_exec_resize_test.go @@ -1,6 +1,7 @@ package main import ( + "net/http" "os/exec" "strings" "testing" @@ -16,10 +17,13 @@ func TestExecResizeApiHeightWidthNoInt(t *testing.T) { cleanedContainerID := strings.TrimSpace(out) endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar" - _, err = sockRequest("POST", endpoint, nil) + status, _, err := sockRequest("POST", endpoint, nil) if err == nil { t.Fatal("Expected exec resize Request to fail") } + if status != http.StatusInternalServerError { + t.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) + } logDone("container exec resize - height, width no int fail") } diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index 481f5d347..8c7b87eb4 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -1,6 +1,7 @@ package main import ( + "net/http" "os/exec" "strings" "testing" @@ -34,10 +35,13 @@ func TestResizeApiHeightWidthNoInt(t *testing.T) { cleanedContainerID := strings.TrimSpace(out) endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar" - _, err = sockRequest("POST", endpoint, nil) + status, _, err := sockRequest("POST", endpoint, nil) if err == nil { t.Fatal("Expected resize Request to fail") } + if status != http.StatusInternalServerError { + t.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) + } logDone("container resize - height, width no int fail") } From 4f91a333d5c9d66ce109c36e7261dbfd3382ebbf Mon Sep 17 00:00:00 2001 From: Deng Guangxing Date: Mon, 13 Apr 2015 17:56:12 +0800 Subject: [PATCH 405/999] move syslog-tag to syslog.New function Signed-off-by: Deng Guangxing Signed-off-by: Michael Crosby --- daemon/logger/syslog/syslog.go | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/daemon/logger/syslog/syslog.go b/daemon/logger/syslog/syslog.go index afd3dacbb..4de14aacf 100644 --- a/daemon/logger/syslog/syslog.go +++ b/daemon/logger/syslog/syslog.go @@ -11,26 +11,23 @@ import ( type Syslog struct { writer *syslog.Writer - tag string } func New(tag string) (logger.Logger, error) { - log, err := syslog.New(syslog.LOG_USER, path.Base(os.Args[0])) + log, err := syslog.New(syslog.LOG_USER, fmt.Sprintf("%s: <%s> ", path.Base(os.Args[0]), tag)) if err != nil { return nil, err } return &Syslog{ writer: log, - tag: tag, }, nil } func (s *Syslog) Log(msg *logger.Message) error { - logMessage := fmt.Sprintf("%s: %s", s.tag, msg.Line) if msg.Source == "stderr" { - return s.writer.Err(logMessage) + return s.writer.Err(string(msg.Line)) } - return s.writer.Info(logMessage) + return s.writer.Info(string(msg.Line)) } func (s *Syslog) Close() error { From 213eab995a3e6dcdb69b587301cc5008911e3dfe Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 13 Apr 2015 11:43:20 -0700 Subject: [PATCH 406/999] Fix vet warning pkg/archive/archive_test.go:496: arg changes for printf verb %s of wrong type: []archive.Change Signed-off-by: Alexander Morozov --- pkg/archive/archive_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index 065e4e47f..dabb0d504 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -493,7 +493,7 @@ func TestTarWithBlockCharFifo(t *testing.T) { t.Fatal(err) } if len(changes) > 0 { - t.Fatalf("Tar with special device (block, char, fifo) should keep them (recreate them when untar) : %s", changes) + t.Fatalf("Tar with special device (block, char, fifo) should keep them (recreate them when untar) : %v", changes) } } From 7523beff41eca212794e902afa1a614b2672e245 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 10 Apr 2015 17:07:05 -0700 Subject: [PATCH 407/999] Log memory swap capabilities properly. Check whether the swap limit capabilities are disabled or not only when memory swap is set to greater than 0. Signed-off-by: David Calavera --- daemon/container.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/container.go b/daemon/container.go index a0dd36c8d..e831f07a5 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1286,7 +1286,7 @@ func (container *Container) verifyDaemonSettings() { logrus.Warnf("Your kernel does not support memory limit capabilities. Limitation discarded.") container.hostConfig.Memory = 0 } - if container.hostConfig.Memory > 0 && !container.daemon.sysInfo.SwapLimit { + if container.hostConfig.Memory > 0 && container.hostConfig.MemorySwap != -1 && !container.daemon.sysInfo.SwapLimit { logrus.Warnf("Your kernel does not support swap limit capabilities. Limitation discarded.") container.hostConfig.MemorySwap = -1 } From 3280ce651b13866f93440b60a9182f9a4f9f14b9 Mon Sep 17 00:00:00 2001 From: bobby abbott Date: Tue, 31 Mar 2015 21:48:03 -0700 Subject: [PATCH 408/999] Adds validate-vet script resolves #11970 Signed-off-by: bobby abbott --- Dockerfile | 4 ++++ Makefile | 2 +- hack/make.sh | 1 + hack/make/validate-vet | 22 ++++++++++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 hack/make/validate-vet diff --git a/Dockerfile b/Dockerfile index b54fda561..3cf5eb5ce 100644 --- a/Dockerfile +++ b/Dockerfile @@ -105,6 +105,10 @@ RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env # Grab Go's cover tool for dead-simple code coverage testing RUN go get golang.org/x/tools/cmd/cover +# Grab Go's vet tool for examining go code to find suspicious constructs +# and help prevent errors that the compiler might not catch +RUN go get golang.org/x/tools/cmd/vet + # TODO replace FPM with some very minimal debhelper stuff RUN gem install --no-rdoc --no-ri fpm --version 1.3.2 diff --git a/Makefile b/Makefile index 9bf1b16c9..7978b632c 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ test-docker-py: build $(DOCKER_RUN_DOCKER) hack/make.sh binary test-docker-py validate: build - $(DOCKER_RUN_DOCKER) hack/make.sh validate-gofmt validate-dco validate-toml + $(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-toml validate-vet shell: build $(DOCKER_RUN_DOCKER) bash diff --git a/hack/make.sh b/hack/make.sh index 1ab1d8137..3bcb265b3 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -45,6 +45,7 @@ DEFAULT_BUNDLES=( validate-dco validate-gofmt validate-toml + validate-vet binary diff --git a/hack/make/validate-vet b/hack/make/validate-vet new file mode 100644 index 000000000..994a6ac03 --- /dev/null +++ b/hack/make/validate-vet @@ -0,0 +1,22 @@ +#!/bin/bash + +source "$(dirname "$BASH_SOURCE")/.validate" + +IFS=$'\n' +files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) ) +unset IFS + +for f in "${files[@]}"; do + # we use "git show" here to validate that what's committed is vetted + failedVet=$(git show "$VALIDATE_HEAD:$f" | go vet) + if [ $failedVet ]; then + fails=yes + echo $failedVet + fi +done + +if [ $fails ]; then + echo 'Please review and resolve the above issues and commit the result.' +else + echo 'All Go source files have been vetted.' +fi From f3ba0a6a3505f5c5c690b84a4db2255fea9af18f Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 13 Apr 2015 11:31:17 -0700 Subject: [PATCH 409/999] change tabs to spaces Signed-off-by: Jessica Frazelle --- hack/make/validate-vet | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/hack/make/validate-vet b/hack/make/validate-vet index 994a6ac03..e88f7549c 100644 --- a/hack/make/validate-vet +++ b/hack/make/validate-vet @@ -6,17 +6,27 @@ IFS=$'\n' files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) ) unset IFS +errors=() for f in "${files[@]}"; do - # we use "git show" here to validate that what's committed is vetted - failedVet=$(git show "$VALIDATE_HEAD:$f" | go vet) - if [ $failedVet ]; then - fails=yes - echo $failedVet - fi + # we use "git show" here to validate that what's committed passes go vet + failedVet=$(go vet "$f") + if [ "$failedVet" ]; then + errors+=( "$failedVet" ) + fi done -if [ $fails ]; then - echo 'Please review and resolve the above issues and commit the result.' + +if [ ${#errors[@]} -eq 0 ]; then + echo 'Congratulations! All Go source files have been vetted.' else - echo 'All Go source files have been vetted.' + { + echo "Errors from go vet:" + for err in "${errors[@]}"; do + echo " - $err" + done + echo + echo 'Please fix the above errors. You can test via "go vet" and commit the result.' + echo + } >&2 + false fi From 9b4d9a34218bcc506c7080a35c70788f46dc2e4e Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 17:00:45 -0400 Subject: [PATCH 410/999] Move TestRunWithTooLowMemory to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_cli_run_test.go | 12 ++++ integration/server_test.go | 92 +++++++++++++++++++++++--- 2 files changed, 94 insertions(+), 10 deletions(-) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 302286146..7c931f8fa 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3493,3 +3493,15 @@ func TestRunPidHostWithChildIsKillable(t *testing.T) { } logDone("run - can kill container with pid-host and some childs of pid 1") } + +func TestRunWithTooSmallMemoryLimit(t *testing.T) { + defer deleteAllContainers() + // this memory limit is 1 byte less than the min, which is 4MB + // https://github.com/docker/docker/blob/v1.5.0/daemon/create.go#L22 + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-m", "4194303", "busybox")) + if err == nil || !strings.Contains(out, "Minimum memory limit allowed is 4MB") { + t.Fatalf("expected run to fail when using too low a memory limit: %q", out) + } + + logDone("run - can't set too low memory limit") +} diff --git a/integration/server_test.go b/integration/server_test.go index 9745d9ce0..42234e3e9 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -1,6 +1,12 @@ package docker -import "testing" +import ( + "bytes" + "testing" + + "github.com/docker/docker/builder" + "github.com/docker/docker/engine" +) func TestCreateNumberHostname(t *testing.T) { eng := NewTestEngine(t) @@ -14,18 +20,84 @@ func TestCreateNumberHostname(t *testing.T) { createTestContainer(eng, config, t) } -func TestRunWithTooLowMemoryLimit(t *testing.T) { +func TestCommit(t *testing.T) { eng := NewTestEngine(t) + b := &builder.BuilderJob{Engine: eng} + b.Install() defer mkDaemonFromEngine(eng, t).Nuke() - // Try to create a container with a memory limit of 1 byte less than the minimum allowed limit. - job := eng.Job("create") - job.Setenv("Image", unitTestImageID) - job.Setenv("Memory", "524287") - job.Setenv("CpuShares", "1000") - job.SetenvList("Cmd", []string{"/bin/cat"}) - if err := job.Run(); err == nil { - t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") + config, _, _, err := parseRun([]string{unitTestImageID, "/bin/cat"}) + if err != nil { + t.Fatal(err) + } + + id := createTestContainer(eng, config, t) + + job := eng.Job("commit", id) + job.Setenv("repo", "testrepo") + job.Setenv("tag", "testtag") + job.SetenvJson("config", config) + if err := job.Run(); err != nil { + t.Fatal(err) + } +} + +func TestMergeConfigOnCommit(t *testing.T) { + eng := NewTestEngine(t) + b := &builder.BuilderJob{Engine: eng} + b.Install() + runtime := mkDaemonFromEngine(eng, t) + defer runtime.Nuke() + + container1, _, _ := mkContainer(runtime, []string{"-e", "FOO=bar", unitTestImageID, "echo test > /tmp/foo"}, t) + defer runtime.Rm(container1) + + config, _, _, err := parseRun([]string{container1.ID, "cat /tmp/foo"}) + 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 outputBuffer = bytes.NewBuffer(nil) + job.Stdout.Add(outputBuffer) + if err := job.Run(); err != nil { + t.Error(err) + } + + container2, _, _ := mkContainer(runtime, []string{engine.Tail(outputBuffer, 1)}, t) + defer runtime.Rm(container2) + + job = eng.Job("container_inspect", container1.Name) + baseContainer, _ := job.Stdout.AddEnv() + if err := job.Run(); err != nil { + t.Error(err) + } + + job = eng.Job("container_inspect", container2.Name) + 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")) } } From ed6074ea6be17518a3b21e4dd5038f13ca835194 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 17:01:40 -0400 Subject: [PATCH 411/999] Move TestMergeOnCommit to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_cli_commit_test.go | 50 +++++++++++++ integration/server_test.go | 89 +---------------------- 2 files changed, 51 insertions(+), 88 deletions(-) diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index 3143c21fc..a51360a9b 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -279,3 +279,53 @@ func TestCommitChange(t *testing.T) { logDone("commit - commit --change") } + +// TODO: commit --run is deprecated, remove this once --run is removed +func TestCommitMergeConfigRun(t *testing.T) { + defer deleteAllContainers() + name := "commit-test" + out, _, _ := dockerCmd(t, "run", "-d", "-e=FOO=bar", "busybox", "/bin/sh", "-c", "echo testing > /tmp/foo") + id := strings.TrimSpace(out) + + dockerCmd(t, "commit", `--run={"Cmd": ["cat", "/tmp/foo"]}`, id, "commit-test") + defer deleteImages("commit-test") + + out, _, _ = dockerCmd(t, "run", "--name", name, "commit-test") + if strings.TrimSpace(out) != "testing" { + t.Fatal("run config in commited container was not merged") + } + + type cfg struct { + Env []string + Cmd []string + } + config1 := cfg{} + if err := inspectFieldAndMarshall(id, "Config", &config1); err != nil { + t.Fatal(err) + } + config2 := cfg{} + if err := inspectFieldAndMarshall(name, "Config", &config2); err != nil { + t.Fatal(err) + } + + // Env has at least PATH loaded as well here, so let's just grab the FOO one + var env1, env2 string + for _, e := range config1.Env { + if strings.HasPrefix(e, "FOO") { + env1 = e + break + } + } + for _, e := range config2.Env { + if strings.HasPrefix(e, "FOO") { + env2 = e + break + } + } + + if len(config1.Env) != len(config2.Env) || env1 != env2 && env2 != "" { + t.Fatalf("expected envs to match: %v - %v", config1.Env, config2.Env) + } + + logDone("commit - configs are merged with --run") +} diff --git a/integration/server_test.go b/integration/server_test.go index 42234e3e9..2a12244a2 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -1,12 +1,6 @@ package docker -import ( - "bytes" - "testing" - - "github.com/docker/docker/builder" - "github.com/docker/docker/engine" -) +import "testing" func TestCreateNumberHostname(t *testing.T) { eng := NewTestEngine(t) @@ -20,87 +14,6 @@ func TestCreateNumberHostname(t *testing.T) { createTestContainer(eng, config, t) } -func TestCommit(t *testing.T) { - eng := NewTestEngine(t) - b := &builder.BuilderJob{Engine: eng} - b.Install() - defer mkDaemonFromEngine(eng, t).Nuke() - - config, _, _, err := parseRun([]string{unitTestImageID, "/bin/cat"}) - if err != nil { - t.Fatal(err) - } - - id := createTestContainer(eng, config, t) - - job := eng.Job("commit", id) - job.Setenv("repo", "testrepo") - job.Setenv("tag", "testtag") - job.SetenvJson("config", config) - if err := job.Run(); err != nil { - t.Fatal(err) - } -} - -func TestMergeConfigOnCommit(t *testing.T) { - eng := NewTestEngine(t) - b := &builder.BuilderJob{Engine: eng} - b.Install() - runtime := mkDaemonFromEngine(eng, t) - defer runtime.Nuke() - - container1, _, _ := mkContainer(runtime, []string{"-e", "FOO=bar", unitTestImageID, "echo test > /tmp/foo"}, t) - defer runtime.Rm(container1) - - config, _, _, err := parseRun([]string{container1.ID, "cat /tmp/foo"}) - 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 outputBuffer = bytes.NewBuffer(nil) - job.Stdout.Add(outputBuffer) - if err := job.Run(); err != nil { - t.Error(err) - } - - container2, _, _ := mkContainer(runtime, []string{engine.Tail(outputBuffer, 1)}, t) - defer runtime.Rm(container2) - - job = eng.Job("container_inspect", container1.Name) - baseContainer, _ := job.Stdout.AddEnv() - if err := job.Run(); err != nil { - t.Error(err) - } - - job = eng.Job("container_inspect", container2.Name) - 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 TestImagesFilter(t *testing.T) { eng := NewTestEngine(t) defer nuke(mkDaemonFromEngine(eng, t)) From 2c24a8a4ea7ea0571cd4e125158fa59f584d2464 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 17:06:43 -0400 Subject: [PATCH 412/999] Move TestCreateNumberHostname to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_cli_create_test.go | 8 ++++++++ integration/server_test.go | 12 ------------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index 3a3c2f07d..ac10b264e 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -305,3 +305,11 @@ func TestCreateLabelFromImage(t *testing.T) { logDone("create - labels from image") } + +func TestCreateHostnameWithNumber(t *testing.T) { + out, _, _ := dockerCmd(t, "run", "-h", "web.0", "busybox", "hostname") + if strings.TrimSpace(out) != "web.0" { + t.Fatalf("hostname not set, expected `web.0`, got: %s", out) + } + logDone("create - use hostname with number") +} diff --git a/integration/server_test.go b/integration/server_test.go index 2a12244a2..f23d0e675 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -2,18 +2,6 @@ package docker import "testing" -func TestCreateNumberHostname(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - config, _, _, err := parseRun([]string{"-h", "web.0", unitTestImageID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - createTestContainer(eng, config, t) -} - func TestImagesFilter(t *testing.T) { eng := NewTestEngine(t) defer nuke(mkDaemonFromEngine(eng, t)) From 02706a40bb29c6ab7f5ac36ece6fd1073879d95d Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 17:51:40 -0400 Subject: [PATCH 413/999] move TestImagesFilter to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_images_test.go | 43 ++++++++++++++++++++++ integration/server_test.go | 44 ----------------------- 2 files changed, 43 insertions(+), 44 deletions(-) delete mode 100644 integration/server_test.go diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 49cfb36da..ee403a188 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "net/url" "testing" "github.com/docker/docker/api/types" @@ -24,3 +25,45 @@ func TestLegacyImages(t *testing.T) { logDone("images - checking legacy json") } + +func TestApiImagesFilter(t *testing.T) { + name := "utest:tag1" + name2 := "utest/docker:tag2" + name3 := "utest:5000/docker:tag3" + defer deleteImages(name, name2, name3) + dockerCmd(t, "tag", "busybox", name) + dockerCmd(t, "tag", "busybox", name2) + dockerCmd(t, "tag", "busybox", name3) + + type image struct{ RepoTags []string } + getImages := func(filter string) []image { + v := url.Values{} + v.Set("filter", filter) + _, b, err := sockRequest("GET", "/images/json?"+v.Encode(), nil) + if err != nil { + t.Fatal(err) + } + var images []image + if err := json.Unmarshal(b, &images); err != nil { + t.Fatal(err) + } + + return images + } + + errMsg := "incorrect number of matches returned" + if images := getImages("utest*/*"); len(images[0].RepoTags) != 2 { + t.Fatal(errMsg) + } + if images := getImages("utest"); len(images[0].RepoTags) != 1 { + t.Fatal(errMsg) + } + if images := getImages("utest*"); len(images[0].RepoTags) != 1 { + t.Fatal(errMsg) + } + if images := getImages("*5000*/*"); len(images[0].RepoTags) != 1 { + t.Fatal(errMsg) + } + + logDone("images - filter param is applied") +} diff --git a/integration/server_test.go b/integration/server_test.go deleted file mode 100644 index f23d0e675..000000000 --- a/integration/server_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package docker - -import "testing" - -func TestImagesFilter(t *testing.T) { - eng := NewTestEngine(t) - defer nuke(mkDaemonFromEngine(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 := getImages(eng, t, false, "utest*/*") - - if len(images[0].RepoTags) != 2 { - t.Fatal("incorrect number of matches returned") - } - - images = getImages(eng, t, false, "utest") - - if len(images[0].RepoTags) != 1 { - t.Fatal("incorrect number of matches returned") - } - - images = getImages(eng, t, false, "utest*") - - if len(images[0].RepoTags) != 1 { - t.Fatal("incorrect number of matches returned") - } - - images = getImages(eng, t, false, "*5000*/*") - - if len(images[0].RepoTags) != 1 { - t.Fatal("incorrect number of matches returned") - } -} From 579c9ec1d07983edf423d8c7564840d2fcc6f303 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 7 Apr 2015 21:10:39 -0400 Subject: [PATCH 414/999] Don't return error when adding existing volume Error wasn't really doing anything except for making a bunch of extra debug logs. Signed-off-by: Brian Goff --- volumes/repository.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/volumes/repository.go b/volumes/repository.go index 08c584981..0dac3753d 100644 --- a/volumes/repository.go +++ b/volumes/repository.go @@ -77,7 +77,8 @@ func (r *Repository) newVolume(path string, writable bool) (*Volume, error) { return nil, err } - return v, r.add(v) + r.add(v) + return v, nil } func (r *Repository) restore() error { @@ -103,9 +104,7 @@ func (r *Repository) restore() error { continue } } - if err := r.add(vol); err != nil { - logrus.Debugf("Error restoring volume: %v", err) - } + r.add(vol) } return nil } @@ -125,12 +124,11 @@ func (r *Repository) get(path string) *Volume { return r.volumes[filepath.Clean(path)] } -func (r *Repository) add(volume *Volume) error { +func (r *Repository) add(volume *Volume) { if vol := r.get(volume.Path); vol != nil { - return fmt.Errorf("Volume exists: %s", volume.ID) + return } r.volumes[volume.Path] = volume - return nil } func (r *Repository) Delete(path string) error { From d71c929d081c2fb7c33b36ae5db40014163f9b6c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 13 Apr 2015 16:10:31 -0700 Subject: [PATCH 415/999] Fix JSON format in the remote api configuration examples. Signed-off-by: David Calavera --- docs/sources/reference/api/docker_remote_api_v1.19.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 0f63ec310..524f77037 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -161,7 +161,7 @@ Create a container "NetworkMode": "bridge", "Devices": [], "Ulimits": [{}], - "LogConfig": { "Type": "json-file", Config: {} }, + "LogConfig": { "Type": "json-file", "Config": {} }, "SecurityOpt": [""], "CgroupParent": "" } @@ -359,7 +359,7 @@ Return low-level information on the container `id` "MaximumRetryCount": 2, "Name": "on-failure" }, - "LogConfig": { "Type": "json-file", Config: {} }, + "LogConfig": { "Type": "json-file", "Config": {} }, "SecurityOpt": null, "VolumesFrom": null, "Ulimits": [{}] @@ -698,7 +698,7 @@ Start the container `id` "NetworkMode": "bridge", "Devices": [], "Ulimits": [{}], - "LogConfig": { "Type": "json-file", Config: {} }, + "LogConfig": { "Type": "json-file", "Config": {} }, "SecurityOpt": [""], "CgroupParent": "" } From c30a55f14dbbe3971ba0ac716ba69a60868f4490 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 29 Mar 2015 23:17:23 +0200 Subject: [PATCH 416/999] Refactor utils/utils, fixes #11923 Signed-off-by: Antonio Murdaca --- api/client/attach.go | 3 +- api/client/build.go | 2 +- api/client/client.go | 12 + api/client/exec.go | 5 +- api/client/history.go | 4 +- api/client/inspect.go | 5 +- api/client/ps.go | 4 +- api/client/run.go | 3 +- api/client/search.go | 4 +- api/client/start.go | 3 +- builder/internals.go | 4 +- builder/job.go | 4 +- daemon/attach.go | 46 +++- daemon/container.go | 6 +- daemon/daemon.go | 9 +- daemon/execdriver/lxc/driver.go | 4 +- daemon/execdriver/lxc/lxc_template.go | 4 +- daemon/execdriver/utils.go | 14 +- daemon/info.go | 3 +- daemon/utils_test.go | 3 +- docker/docker.go | 3 +- engine/env.go | 4 +- graph/graph.go | 3 +- graph/import.go | 3 +- graph/load.go | 3 +- image/image.go | 14 +- integration/graph_test.go | 3 +- integration/runtime_test.go | 3 +- integration/z_final_test.go | 5 +- pkg/fileutils/fileutils.go | 54 +++++ pkg/fileutils/fileutils_test.go | 81 +++++++ pkg/httputils/httputils.go | 26 ++ pkg/ioutils/readers.go | 10 + pkg/ioutils/writers.go | 21 ++ pkg/ioutils/writers_test.go | 41 ++++ pkg/requestdecorator/requestdecorator_test.go | 16 +- pkg/resolvconf/resolvconf.go | 4 +- pkg/stringutils/stringutils.go | 56 +++++ pkg/stringutils/stringutils_test.go | 29 +++ registry/config.go | 4 +- registry/session.go | 29 ++- registry/session_v2.go | 18 +- runconfig/hostconfig.go | 8 +- runconfig/parse.go | 7 +- utils/utils.go | 224 +----------------- utils/utils_test.go | 146 ++++-------- 46 files changed, 530 insertions(+), 427 deletions(-) create mode 100644 pkg/fileutils/fileutils_test.go create mode 100644 pkg/httputils/httputils.go create mode 100644 pkg/ioutils/writers_test.go diff --git a/api/client/attach.go b/api/client/attach.go index 48cb8b447..77947a294 100644 --- a/api/client/attach.go +++ b/api/client/attach.go @@ -9,7 +9,6 @@ import ( "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/signal" - "github.com/docker/docker/utils" ) // CmdAttach attaches to a running container. @@ -81,7 +80,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return err } if status != 0 { - return &utils.StatusError{StatusCode: status} + return &StatusError{StatusCode: status} } return nil diff --git a/api/client/build.go b/api/client/build.go index f1bceb4a1..dc54c22ff 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -302,7 +302,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if jerr.Code == 0 { jerr.Code = 1 } - return &utils.StatusError{Status: jerr.Message, StatusCode: jerr.Code} + return &StatusError{Status: jerr.Message, StatusCode: jerr.Code} } return err } diff --git a/api/client/client.go b/api/client/client.go index 4cfce5f68..c849fa40f 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -3,3 +3,15 @@ // Run "docker help SUBCOMMAND" or "docker SUBCOMMAND --help" to see more information on any Docker subcommand, including the full list of options supported for the subcommand. // See https://docs.docker.com/installation/ for instructions on installing Docker. package client + +import "fmt" + +// An StatusError reports an unsuccessful exit by a command. +type StatusError struct { + Status string + StatusCode int +} + +func (e *StatusError) Error() string { + return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode) +} diff --git a/api/client/exec.go b/api/client/exec.go index 27e6878df..25b7a85fd 100644 --- a/api/client/exec.go +++ b/api/client/exec.go @@ -9,7 +9,6 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) // CmdExec runs a command in a running container. @@ -21,7 +20,7 @@ func (cli *DockerCli) CmdExec(args ...string) error { execConfig, err := runconfig.ParseExec(cmd, args) // just in case the ParseExec does not exit if execConfig.Container == "" || err != nil { - return &utils.StatusError{StatusCode: 1} + return &StatusError{StatusCode: 1} } stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil) @@ -122,7 +121,7 @@ func (cli *DockerCli) CmdExec(args ...string) error { } if status != 0 { - return &utils.StatusError{StatusCode: status} + return &StatusError{StatusCode: status} } return nil diff --git a/api/client/history.go b/api/client/history.go index 844a6fb77..4ac46d92c 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -9,8 +9,8 @@ import ( "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/pkg/units" - "github.com/docker/docker/utils" ) // CmdHistory shows the history of an image. @@ -51,7 +51,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { if *noTrunc { fmt.Fprintf(w, "%s\t", entry.CreatedBy) } else { - fmt.Fprintf(w, "%s\t", utils.Trunc(entry.CreatedBy, 45)) + fmt.Fprintf(w, "%s\t", stringutils.Truncate(entry.CreatedBy, 45)) } fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size))) fmt.Fprintf(w, "%s", entry.Comment) diff --git a/api/client/inspect.go b/api/client/inspect.go index 0f47480b1..8514b1ecb 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -9,7 +9,6 @@ import ( "text/template" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // CmdInspect displays low-level information on one or more containers or images. @@ -27,7 +26,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { var err error if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil { fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) - return &utils.StatusError{StatusCode: 64, + return &StatusError{StatusCode: 64, Status: "Template parsing error: " + err.Error()} } } @@ -86,7 +85,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { } if status != 0 { - return &utils.StatusError{StatusCode: status} + return &StatusError{StatusCode: status} } return nil } diff --git a/api/client/ps.go b/api/client/ps.go index be20d7a6f..44f5ff0d2 100644 --- a/api/client/ps.go +++ b/api/client/ps.go @@ -15,8 +15,8 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers/filters" "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/pkg/units" - "github.com/docker/docker/utils" ) // CmdPs outputs a list of Docker containers. @@ -135,7 +135,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { ) if !*noTrunc { - command = utils.Trunc(command, 20) + command = stringutils.Truncate(command, 20) // only display the default name for the container with notrunc is passed for _, name := range names { diff --git a/api/client/run.go b/api/client/run.go index 474c88f98..b37b6bab2 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -12,7 +12,6 @@ import ( "github.com/docker/docker/pkg/resolvconf" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) func (cid *cidFile) Close() error { @@ -242,7 +241,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } } if status != 0 { - return &utils.StatusError{StatusCode: status} + return &StatusError{StatusCode: status} } return nil } diff --git a/api/client/search.go b/api/client/search.go index 8f4eb0b30..5e0a22f01 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -10,8 +10,8 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" + "github.com/docker/docker/pkg/stringutils" "github.com/docker/docker/registry" - "github.com/docker/docker/utils" ) type ByStars []registry.SearchResult @@ -68,7 +68,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { desc := strings.Replace(res.Description, "\n", " ", -1) desc = strings.Replace(desc, "\r", " ", -1) if !*noTrunc && len(desc) > 45 { - desc = utils.Trunc(desc, 42) + "..." + desc = stringutils.Truncate(desc, 42) + "..." } fmt.Fprintf(w, "%s\t%s\t%d\t", res.Name, desc, res.StarCount) if res.IsOfficial { diff --git a/api/client/start.go b/api/client/start.go index 66aa5150d..a03b8c1d2 100644 --- a/api/client/start.go +++ b/api/client/start.go @@ -11,7 +11,6 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" - "github.com/docker/docker/utils" ) func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal { @@ -156,7 +155,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { return err } if status != 0 { - return &utils.StatusError{StatusCode: status} + return &StatusError{StatusCode: status} } } return nil diff --git a/builder/internals.go b/builder/internals.go index e0c7987ca..728ccde8a 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -25,6 +25,7 @@ import ( imagepkg "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" + "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/parsers" @@ -35,7 +36,6 @@ import ( "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) func (b *Builder) readContext(context io.Reader) error { @@ -250,7 +250,7 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri *cInfos = append(*cInfos, &ci) // Initiate the download - resp, err := utils.Download(ci.origPath) + resp, err := httputils.Download(ci.origPath) if err != nil { return err } diff --git a/builder/job.go b/builder/job.go index 89ed52f87..b0ce8ddc0 100644 --- a/builder/job.go +++ b/builder/job.go @@ -16,12 +16,12 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) // whitelist of commands allowed for a commit/import @@ -106,7 +106,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { } context = c } else if urlutil.IsURL(remoteURL) { - f, err := utils.Download(remoteURL) + f, err := httputils.Download(remoteURL) if err != nil { return err } diff --git a/daemon/attach.go b/daemon/attach.go index f95de41d5..b2b8d0906 100644 --- a/daemon/attach.go +++ b/daemon/attach.go @@ -10,7 +10,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/jsonlog" "github.com/docker/docker/pkg/promise" - "github.com/docker/docker/utils" ) func (c *Container) AttachWithLogs(stdin io.ReadCloser, stdout, stderr io.Writer, logs, stream bool) error { @@ -131,7 +130,7 @@ func attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io var err error if tty { - _, err = utils.CopyEscapable(cStdin, stdin) + _, err = copyEscapable(cStdin, stdin) } else { _, err = io.Copy(cStdin, stdin) @@ -185,3 +184,46 @@ func attach(streamConfig *StreamConfig, openStdin, stdinOnce, tty bool, stdin io 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) + for { + nr, er := src.Read(buf) + if nr > 0 { + // ---- Docker addition + // char 16 is C-p + if nr == 1 && buf[0] == 16 { + nr, er = src.Read(buf) + // char 17 is C-q + if nr == 1 && buf[0] == 17 { + if err := src.Close(); err != nil { + return 0, err + } + return 0, nil + } + } + // ---- End of docker + nw, ew := dst.Write(buf[0:nr]) + if nw > 0 { + written += int64(nw) + } + if ew != nil { + err = ew + break + } + if nr != nw { + err = io.ErrShortWrite + break + } + } + if er == io.EOF { + break + } + if er != nil { + err = er + break + } + } + return written, err +} diff --git a/daemon/container.go b/daemon/container.go index e831f07a5..99fe157ad 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1063,7 +1063,7 @@ func (container *Container) setupContainerDns() error { updatedResolvConf, modified := resolvconf.FilterResolvDns(latestResolvConf, container.daemon.config.Bridge.EnableIPv6) if modified { // changes have occurred during resolv.conf localhost cleanup: generate an updated hash - newHash, err := utils.HashData(bytes.NewReader(updatedResolvConf)) + newHash, err := ioutils.HashData(bytes.NewReader(updatedResolvConf)) if err != nil { return err } @@ -1118,7 +1118,7 @@ func (container *Container) setupContainerDns() error { } //get a sha256 hash of the resolv conf at this point so we can check //for changes when the host resolv.conf changes (e.g. network update) - resolvHash, err := utils.HashData(bytes.NewReader(resolvConf)) + resolvHash, err := ioutils.HashData(bytes.NewReader(resolvConf)) if err != nil { return err } @@ -1150,7 +1150,7 @@ func (container *Container) updateResolvConf(updatedResolvConf []byte, newResolv if err != nil { return err } - curHash, err := utils.HashData(bytes.NewReader(resolvBytes)) + curHash, err := ioutils.HashData(bytes.NewReader(resolvBytes)) if err != nil { return err } diff --git a/daemon/daemon.go b/daemon/daemon.go index 789cd2b32..e2ca56805 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -32,6 +32,7 @@ import ( "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/broadcastwriter" + "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/graphdb" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/namesgenerator" @@ -431,7 +432,7 @@ func (daemon *Daemon) setupResolvconfWatcher() error { updatedResolvConf, modified := resolvconf.FilterResolvDns(updatedResolvConf, daemon.config.Bridge.EnableIPv6) if modified { // changes have occurred during localhost cleanup: generate an updated hash - newHash, err := utils.HashData(bytes.NewReader(updatedResolvConf)) + newHash, err := ioutils.HashData(bytes.NewReader(updatedResolvConf)) if err != nil { logrus.Debugf("Error generating hash of new resolv.conf: %v", err) } else { @@ -830,7 +831,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService if err != nil { return nil, fmt.Errorf("Unable to get the TempDir under %s: %s", config.Root, err) } - realTmp, err := utils.ReadSymlinkedDirectory(tmp) + realTmp, err := fileutils.ReadSymlinkedDirectory(tmp) if err != nil { return nil, fmt.Errorf("Unable to get the full path to the TempDir (%s): %s", tmp, err) } @@ -841,7 +842,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService if _, err := os.Stat(config.Root); err != nil && os.IsNotExist(err) { realRoot = config.Root } else { - realRoot, err = utils.ReadSymlinkedDirectory(config.Root) + realRoot, err = fileutils.ReadSymlinkedDirectory(config.Root) if err != nil { return nil, fmt.Errorf("Unable to get the full path to root (%s): %s", config.Root, err) } @@ -959,7 +960,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService if err := os.Mkdir(path.Dir(localCopy), 0700); err != nil && !os.IsExist(err) { return nil, err } - if _, err := utils.CopyFile(sysInitPath, localCopy); err != nil { + if _, err := fileutils.CopyFile(sysInitPath, localCopy); err != nil { return nil, err } if err := os.Chmod(localCopy, 0700); err != nil { diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 97b34bb67..1637bc2c6 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -18,10 +18,10 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" + "github.com/docker/docker/pkg/stringutils" sysinfo "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/term" "github.com/docker/docker/pkg/version" - "github.com/docker/docker/utils" "github.com/docker/libcontainer" "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/configs" @@ -187,7 +187,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba // without exec in go we have to do this horrible shell hack... shellString := "mount --make-rslave /; exec " + - utils.ShellQuoteArguments(params) + stringutils.ShellQuoteArguments(params) params = []string{ "unshare", "-m", "--", "/bin/sh", "-c", shellString, diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 6d6decb79..6c182ab39 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -9,7 +9,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" nativeTemplate "github.com/docker/docker/daemon/execdriver/native/template" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/stringutils" "github.com/docker/libcontainer/label" ) @@ -177,7 +177,7 @@ func keepCapabilities(adds []string, drops []string) ([]string, error) { } func dropList(drops []string) ([]string, error) { - if utils.StringsContainsNoCase(drops, "all") { + if stringutils.InSlice(drops, "all") { var newCaps []string for _, capName := range execdriver.GetAllCapabilities() { cap := execdriver.GetCapability(capName) diff --git a/daemon/execdriver/utils.go b/daemon/execdriver/utils.go index e1fc9b901..407c4f4fa 100644 --- a/daemon/execdriver/utils.go +++ b/daemon/execdriver/utils.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/stringutils" "github.com/syndtr/gocapability/capability" ) @@ -89,17 +89,17 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) { if strings.ToLower(cap) == "all" { continue } - if !utils.StringsContainsNoCase(allCaps, cap) { + if !stringutils.InSlice(allCaps, cap) { return nil, fmt.Errorf("Unknown capability drop: %q", cap) } } // handle --cap-add=all - if utils.StringsContainsNoCase(adds, "all") { + if stringutils.InSlice(adds, "all") { basics = allCaps } - if !utils.StringsContainsNoCase(drops, "all") { + if !stringutils.InSlice(drops, "all") { for _, cap := range basics { // skip `all` aready handled above if strings.ToLower(cap) == "all" { @@ -107,7 +107,7 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) { } // if we don't drop `all`, add back all the non-dropped caps - if !utils.StringsContainsNoCase(drops, cap) { + if !stringutils.InSlice(drops, cap) { newCaps = append(newCaps, strings.ToUpper(cap)) } } @@ -119,12 +119,12 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) { continue } - if !utils.StringsContainsNoCase(allCaps, cap) { + if !stringutils.InSlice(allCaps, cap) { return nil, fmt.Errorf("Unknown capability to add: %q", cap) } // add cap if not already in the list - if !utils.StringsContainsNoCase(newCaps, cap) { + if !stringutils.InSlice(newCaps, cap) { newCaps = append(newCaps, strings.ToUpper(cap)) } } diff --git a/daemon/info.go b/daemon/info.go index 183a9e68b..a77e3efd1 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -8,6 +8,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/engine" + "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/parsers/kernel" "github.com/docker/docker/pkg/parsers/operatingsystem" "github.com/docker/docker/pkg/system" @@ -61,7 +62,7 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) error { v.SetBool("SwapLimit", daemon.SystemConfig().SwapLimit) v.SetBool("IPv4Forwarding", !daemon.SystemConfig().IPv4ForwardingDisabled) v.SetBool("Debug", os.Getenv("DEBUG") != "") - v.SetInt("NFd", utils.GetTotalUsedFds()) + v.SetInt("NFd", fileutils.GetTotalUsedFds()) v.SetInt("NGoroutines", runtime.NumGoroutine()) v.Set("SystemTime", time.Now().Format(time.RFC3339Nano)) v.Set("ExecutionDriver", daemon.ExecutionDriver().Name()) diff --git a/daemon/utils_test.go b/daemon/utils_test.go index ff5b082ba..aabbeaf6f 100644 --- a/daemon/utils_test.go +++ b/daemon/utils_test.go @@ -4,12 +4,11 @@ import ( "testing" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) func TestMergeLxcConfig(t *testing.T) { hostConfig := &runconfig.HostConfig{ - LxcConf: []utils.KeyValuePair{ + LxcConf: []runconfig.KeyValuePair{ {Key: "lxc.cgroups.cpuset", Value: "1,2"}, }, } diff --git a/docker/docker.go b/docker/docker.go index c9b2c77b0..cf5b71559 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -15,7 +15,6 @@ import ( flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/term" - "github.com/docker/docker/utils" ) const ( @@ -136,7 +135,7 @@ func main() { } if err := cli.Cmd(flag.Args()...); err != nil { - if sterr, ok := err.(*utils.StatusError); ok { + if sterr, ok := err.(*client.StatusError); ok { if sterr.Status != "" { logrus.Println(sterr.Status) } diff --git a/engine/env.go b/engine/env.go index 089bc162c..107ae4a0d 100644 --- a/engine/env.go +++ b/engine/env.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/ioutils" ) type Env []string @@ -258,7 +258,7 @@ func (env *Env) Encode(dst io.Writer) error { } func (env *Env) WriteTo(dst io.Writer) (int64, error) { - wc := utils.NewWriteCounter(dst) + wc := ioutils.NewWriteCounter(dst) err := env.Encode(wc) return wc.Count, err } diff --git a/graph/graph.go b/graph/graph.go index 087a6f093..5159a9322 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -25,7 +25,6 @@ import ( "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) // A Graph is a store for versioned filesystem images and the relationship between them. @@ -154,7 +153,7 @@ func (graph *Graph) Register(img *image.Image, layerData archive.ArchiveReader) graph.driver.Remove(img.ID) } }() - if err := utils.ValidateID(img.ID); err != nil { + if err := image.ValidateID(img.ID); err != nil { return err } // (This is a convenience to save time. Race conditions are taken care of by os.Rename) diff --git a/graph/import.go b/graph/import.go index eb63af0b6..0ba03d0f5 100644 --- a/graph/import.go +++ b/graph/import.go @@ -9,6 +9,7 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/runconfig" @@ -46,7 +47,7 @@ func (s *TagStore) CmdImport(job *engine.Job) error { u.Path = "" } job.Stdout.Write(sf.FormatStatus("", "Downloading from %s", u)) - resp, err = utils.Download(u.String()) + resp, err = httputils.Download(u.String()) if err != nil { return err } diff --git a/graph/load.go b/graph/load.go index ace222e3f..bf2cc6d70 100644 --- a/graph/load.go +++ b/graph/load.go @@ -13,7 +13,6 @@ import ( "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" - "github.com/docker/docker/utils" ) // Loads a set of images into the repository. This is the complementary of ImageExport. @@ -100,7 +99,7 @@ func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string logrus.Debugf("Error unmarshalling json", err) return err } - if err := utils.ValidateID(img.ID); err != nil { + if err := image.ValidateID(img.ID); err != nil { logrus.Debugf("Error validating ID: %s", err) return err } diff --git a/image/image.go b/image/image.go index a661e3ce7..90714d6db 100644 --- a/image/image.go +++ b/image/image.go @@ -6,12 +6,12 @@ import ( "io/ioutil" "os" "path" + "regexp" "strconv" "time" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" ) // Set the max depth to the aufs default that most @@ -51,7 +51,7 @@ func LoadImage(root string) (*Image, error) { if err := dec.Decode(img); err != nil { return nil, err } - if err := utils.ValidateID(img.ID); err != nil { + if err := ValidateID(img.ID); err != nil { return nil, err } @@ -263,3 +263,13 @@ func NewImgJSON(src []byte) (*Image, error) { } return ret, nil } + +// Check wheather id is a valid image ID or not +func ValidateID(id string) error { + validHex := regexp.MustCompile(`^([a-f0-9]{64})$`) + if ok := validHex.MatchString(id); !ok { + err := fmt.Errorf("image ID '%s' is invalid", id) + return err + } + return nil +} diff --git a/integration/graph_test.go b/integration/graph_test.go index a48115455..8b6d7626f 100644 --- a/integration/graph_test.go +++ b/integration/graph_test.go @@ -15,7 +15,6 @@ import ( "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/utils" ) func TestMount(t *testing.T) { @@ -103,7 +102,7 @@ func TestGraphCreate(t *testing.T) { if err != nil { t.Fatal(err) } - if err := utils.ValidateID(img.ID); err != nil { + if err := image.ValidateID(img.ID); err != nil { t.Fatal(err) } if img.Comment != "Testing" { diff --git a/integration/runtime_test.go b/integration/runtime_test.go index b5e404d59..6881fbea6 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -23,6 +23,7 @@ import ( "github.com/docker/docker/graph" "github.com/docker/docker/image" "github.com/docker/docker/nat" + "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/stringid" @@ -121,7 +122,7 @@ func init() { spawnGlobalDaemon() spawnLegitHttpsDaemon() spawnRogueHttpsDaemon() - startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine() + startFds, startGoroutines = fileutils.GetTotalUsedFds(), runtime.NumGoroutine() } func setupBaseImage() { diff --git a/integration/z_final_test.go b/integration/z_final_test.go index 13cd0c3fd..d6ef2884f 100644 --- a/integration/z_final_test.go +++ b/integration/z_final_test.go @@ -1,13 +1,14 @@ package docker import ( - "github.com/docker/docker/utils" "runtime" "testing" + + "github.com/docker/docker/pkg/fileutils" ) func displayFdGoroutines(t *testing.T) { - t.Logf("File Descriptors: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine()) + t.Logf("File Descriptors: %d, Goroutines: %d", fileutils.GetTotalUsedFds(), runtime.NumGoroutine()) } func TestFinal(t *testing.T) { diff --git a/pkg/fileutils/fileutils.go b/pkg/fileutils/fileutils.go index 432529765..ef2a6523d 100644 --- a/pkg/fileutils/fileutils.go +++ b/pkg/fileutils/fileutils.go @@ -1,6 +1,10 @@ package fileutils import ( + "fmt" + "io" + "io/ioutil" + "os" "path/filepath" "github.com/Sirupsen/logrus" @@ -25,3 +29,53 @@ func Matches(relFilePath string, patterns []string) (bool, error) { } return false, nil } + +func CopyFile(src, dst string) (int64, error) { + if src == dst { + return 0, nil + } + sf, err := os.Open(src) + if err != nil { + return 0, err + } + defer sf.Close() + if err := os.Remove(dst); err != nil && !os.IsNotExist(err) { + return 0, err + } + df, err := os.Create(dst) + if err != nil { + return 0, err + } + defer df.Close() + return io.Copy(df, sf) +} + +func GetTotalUsedFds() int { + if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil { + logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err) + } else { + return len(fds) + } + return -1 +} + +// ReadSymlinkedDirectory returns the target directory of a symlink. +// The target of the symbolic link may not be a file. +func ReadSymlinkedDirectory(path string) (string, error) { + var realPath string + var err error + if realPath, err = filepath.Abs(path); err != nil { + return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err) + } + if realPath, err = filepath.EvalSymlinks(realPath); err != nil { + return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err) + } + realPathInfo, err := os.Stat(realPath) + if err != nil { + return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err) + } + if !realPathInfo.Mode().IsDir() { + return "", fmt.Errorf("canonical path points to a file '%s'", realPath) + } + return realPath, nil +} diff --git a/pkg/fileutils/fileutils_test.go b/pkg/fileutils/fileutils_test.go new file mode 100644 index 000000000..16d00d7b9 --- /dev/null +++ b/pkg/fileutils/fileutils_test.go @@ -0,0 +1,81 @@ +package fileutils + +import ( + "os" + "testing" +) + +// Reading a symlink to a directory must return the directory +func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) { + var err error + if err = os.Mkdir("/tmp/testReadSymlinkToExistingDirectory", 0777); err != nil { + t.Errorf("failed to create directory: %s", err) + } + + if err = os.Symlink("/tmp/testReadSymlinkToExistingDirectory", "/tmp/dirLinkTest"); err != nil { + t.Errorf("failed to create symlink: %s", err) + } + + var path string + if path, err = ReadSymlinkedDirectory("/tmp/dirLinkTest"); err != nil { + t.Fatalf("failed to read symlink to directory: %s", err) + } + + if path != "/tmp/testReadSymlinkToExistingDirectory" { + t.Fatalf("symlink returned unexpected directory: %s", path) + } + + if err = os.Remove("/tmp/testReadSymlinkToExistingDirectory"); err != nil { + t.Errorf("failed to remove temporary directory: %s", err) + } + + if err = os.Remove("/tmp/dirLinkTest"); err != nil { + t.Errorf("failed to remove symlink: %s", err) + } +} + +// Reading a non-existing symlink must fail +func TestReadSymlinkedDirectoryNonExistingSymlink(t *testing.T) { + var path string + var err error + if path, err = ReadSymlinkedDirectory("/tmp/test/foo/Non/ExistingPath"); err == nil { + t.Fatalf("error expected for non-existing symlink") + } + + if path != "" { + t.Fatalf("expected empty path, but '%s' was returned", path) + } +} + +// Reading a symlink to a file must fail +func TestReadSymlinkedDirectoryToFile(t *testing.T) { + var err error + var file *os.File + + if file, err = os.Create("/tmp/testReadSymlinkToFile"); err != nil { + t.Fatalf("failed to create file: %s", err) + } + + file.Close() + + if err = os.Symlink("/tmp/testReadSymlinkToFile", "/tmp/fileLinkTest"); err != nil { + t.Errorf("failed to create symlink: %s", err) + } + + var path string + if path, err = ReadSymlinkedDirectory("/tmp/fileLinkTest"); err == nil { + t.Fatalf("ReadSymlinkedDirectory on a symlink to a file should've failed") + } + + if path != "" { + t.Fatalf("path should've been empty: %s", path) + } + + if err = os.Remove("/tmp/testReadSymlinkToFile"); err != nil { + t.Errorf("failed to remove file: %s", err) + } + + if err = os.Remove("/tmp/fileLinkTest"); err != nil { + t.Errorf("failed to remove symlink: %s", err) + } +} diff --git a/pkg/httputils/httputils.go b/pkg/httputils/httputils.go new file mode 100644 index 000000000..1c922240e --- /dev/null +++ b/pkg/httputils/httputils.go @@ -0,0 +1,26 @@ +package httputils + +import ( + "fmt" + "net/http" + + "github.com/docker/docker/pkg/jsonmessage" +) + +// Request a given URL and return an io.Reader +func Download(url string) (resp *http.Response, err error) { + if resp, err = http.Get(url); err != nil { + return nil, err + } + if resp.StatusCode >= 400 { + return nil, fmt.Errorf("Got HTTP status code >= 400: %s", resp.Status) + } + return resp, nil +} + +func NewHTTPRequestError(msg string, res *http.Response) error { + return &jsonmessage.JSONError{ + Message: msg, + Code: res.StatusCode, + } +} diff --git a/pkg/ioutils/readers.go b/pkg/ioutils/readers.go index 58ff1af63..0e542cbad 100644 --- a/pkg/ioutils/readers.go +++ b/pkg/ioutils/readers.go @@ -3,6 +3,8 @@ package ioutils import ( "bytes" "crypto/rand" + "crypto/sha256" + "encoding/hex" "io" "math/big" "sync" @@ -215,3 +217,11 @@ func (r *bufReader) Close() error { } return closer.Close() } + +func HashData(src io.Reader) (string, error) { + h := sha256.New() + if _, err := io.Copy(h, src); err != nil { + return "", err + } + return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil +} diff --git a/pkg/ioutils/writers.go b/pkg/ioutils/writers.go index c0b3608fe..43fdc44ea 100644 --- a/pkg/ioutils/writers.go +++ b/pkg/ioutils/writers.go @@ -37,3 +37,24 @@ func NewWriteCloserWrapper(r io.Writer, closer func() error) io.WriteCloser { closer: closer, } } + +// Wrap a concrete io.Writer and hold a count of the number +// of bytes written to the writer during a "session". +// This can be convenient when write return is masked +// (e.g., json.Encoder.Encode()) +type WriteCounter struct { + Count int64 + Writer io.Writer +} + +func NewWriteCounter(w io.Writer) *WriteCounter { + return &WriteCounter{ + Writer: w, + } +} + +func (wc *WriteCounter) Write(p []byte) (count int, err error) { + count, err = wc.Writer.Write(p) + wc.Count += int64(count) + return +} diff --git a/pkg/ioutils/writers_test.go b/pkg/ioutils/writers_test.go new file mode 100644 index 000000000..80d7f7f79 --- /dev/null +++ b/pkg/ioutils/writers_test.go @@ -0,0 +1,41 @@ +package ioutils + +import ( + "bytes" + "strings" + "testing" +) + +func TestNopWriter(t *testing.T) { + nw := &NopWriter{} + l, err := nw.Write([]byte{'c'}) + if err != nil { + t.Fatal(err) + } + if l != 1 { + t.Fatalf("Expected 1 got %d", l) + } +} + +func TestWriteCounter(t *testing.T) { + dummy1 := "This is a dummy string." + dummy2 := "This is another dummy string." + totalLength := int64(len(dummy1) + len(dummy2)) + + reader1 := strings.NewReader(dummy1) + reader2 := strings.NewReader(dummy2) + + var buffer bytes.Buffer + wc := NewWriteCounter(&buffer) + + reader1.WriteTo(wc) + reader2.WriteTo(wc) + + if wc.Count != totalLength { + t.Errorf("Wrong count: %d vs. %d", wc.Count, totalLength) + } + + if buffer.String() != dummy1+dummy2 { + t.Error("Wrong message written") + } +} diff --git a/pkg/requestdecorator/requestdecorator_test.go b/pkg/requestdecorator/requestdecorator_test.go index b2c1fb3b9..f1f9ef756 100644 --- a/pkg/requestdecorator/requestdecorator_test.go +++ b/pkg/requestdecorator/requestdecorator_test.go @@ -180,8 +180,8 @@ func TestRequestFactory(t *testing.T) { requestFactory := NewRequestFactory(ad, uad) - if dlen := len(requestFactory.GetDecorators()); dlen != 2 { - t.Fatalf("Expected to have two decorators, got %d", dlen) + if l := len(requestFactory.GetDecorators()); l != 2 { + t.Fatalf("Expected to have two decorators, got %d", l) } req, err := requestFactory.NewRequest("GET", "/test", strings.NewReader("test")) @@ -209,8 +209,8 @@ func TestRequestFactoryNewRequestWithDecorators(t *testing.T) { requestFactory := NewRequestFactory(ad) - if dlen := len(requestFactory.GetDecorators()); dlen != 1 { - t.Fatalf("Expected to have one decorators, got %d", dlen) + if l := len(requestFactory.GetDecorators()); l != 1 { + t.Fatalf("Expected to have one decorators, got %d", l) } ad2 := NewAuthDecorator("test2", "password2") @@ -235,15 +235,15 @@ func TestRequestFactoryNewRequestWithDecorators(t *testing.T) { func TestRequestFactoryAddDecorator(t *testing.T) { requestFactory := NewRequestFactory() - if dlen := len(requestFactory.GetDecorators()); dlen != 0 { - t.Fatalf("Expected to have zero decorators, got %d", dlen) + if l := len(requestFactory.GetDecorators()); l != 0 { + t.Fatalf("Expected to have zero decorators, got %d", l) } ad := NewAuthDecorator("test", "password") requestFactory.AddDecorator(ad) - if dlen := len(requestFactory.GetDecorators()); dlen != 1 { - t.Fatalf("Expected to have one decorators, got %d", dlen) + if l := len(requestFactory.GetDecorators()); l != 1 { + t.Fatalf("Expected to have one decorators, got %d", l) } } diff --git a/pkg/resolvconf/resolvconf.go b/pkg/resolvconf/resolvconf.go index d7d53e16d..5707b16b7 100644 --- a/pkg/resolvconf/resolvconf.go +++ b/pkg/resolvconf/resolvconf.go @@ -9,7 +9,7 @@ import ( "sync" "github.com/Sirupsen/logrus" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/ioutils" ) var ( @@ -59,7 +59,7 @@ func GetIfChanged() ([]byte, string, error) { if err != nil { return nil, "", err } - newHash, err := utils.HashData(bytes.NewReader(resolv)) + newHash, err := ioutils.HashData(bytes.NewReader(resolv)) if err != nil { return nil, "", err } diff --git a/pkg/stringutils/stringutils.go b/pkg/stringutils/stringutils.go index f5f07dd18..e3ebf5d1e 100644 --- a/pkg/stringutils/stringutils.go +++ b/pkg/stringutils/stringutils.go @@ -1,7 +1,9 @@ package stringutils import ( + "bytes" mathrand "math/rand" + "strings" "time" ) @@ -28,3 +30,57 @@ func GenerateRandomAsciiString(n int) string { } return string(res) } + +// Truncate a string to maxlen +func Truncate(s string, maxlen int) string { + if len(s) <= maxlen { + return s + } + return s[:maxlen] +} + +// Test wheather a string is contained in a slice of strings or not. +// Comparison is case insensitive +func InSlice(slice []string, s string) bool { + for _, ss := range slice { + if strings.ToLower(s) == strings.ToLower(ss) { + return true + } + } + return false +} + +func quote(word string, buf *bytes.Buffer) { + // Bail out early for "simple" strings + if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { + buf.WriteString(word) + return + } + + buf.WriteString("'") + + for i := 0; i < len(word); i++ { + b := word[i] + if b == '\'' { + // Replace literal ' with a close ', a \', and a open ' + buf.WriteString("'\\''") + } else { + buf.WriteByte(b) + } + } + + buf.WriteString("'") +} + +// Take a list of strings and escape them so they will be handled right +// when passed as arguments to an program via a shell +func ShellQuoteArguments(args []string) string { + var buf bytes.Buffer + for i, arg := range args { + if i != 0 { + buf.WriteByte(' ') + } + quote(arg, &buf) + } + return buf.String() +} diff --git a/pkg/stringutils/stringutils_test.go b/pkg/stringutils/stringutils_test.go index a5a01b4a0..8dcb4696b 100644 --- a/pkg/stringutils/stringutils_test.go +++ b/pkg/stringutils/stringutils_test.go @@ -56,3 +56,32 @@ func TestGenerateRandomAsciiStringIsAscii(t *testing.T) { t.Fatalf("%s contained non-ascii characters", str) } } + +func TestTruncate(t *testing.T) { + str := "teststring" + newstr := Truncate(str, 4) + if newstr != "test" { + t.Fatalf("Expected test, got %s", newstr) + } + newstr = Truncate(str, 20) + if newstr != "teststring" { + t.Fatalf("Expected teststring, got %s", newstr) + } +} + +func TestInSlice(t *testing.T) { + slice := []string{"test", "in", "slice"} + + test := InSlice(slice, "test") + if !test { + t.Fatalf("Expected string test to be in slice") + } + test = InSlice(slice, "SLICE") + if !test { + t.Fatalf("Expected string SLICE to be in slice") + } + test = InSlice(slice, "notinslice") + if test { + t.Fatalf("Expected string notinslice not to be in slice") + } +} diff --git a/registry/config.go b/registry/config.go index 3515836d1..a0a978cc7 100644 --- a/registry/config.go +++ b/registry/config.go @@ -9,9 +9,9 @@ import ( "regexp" "strings" + "github.com/docker/docker/image" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/utils" ) // Options holds command line options. @@ -213,7 +213,7 @@ func validateRemoteName(remoteName string) error { name = nameParts[0] // the repository name must not be a valid image ID - if err := utils.ValidateID(name); err == nil { + if err := image.ValidateID(name); err == nil { return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name) } } else { diff --git a/registry/session.go b/registry/session.go index 4682a5074..c62745b5b 100644 --- a/registry/session.go +++ b/registry/session.go @@ -21,7 +21,6 @@ import ( "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/requestdecorator" "github.com/docker/docker/pkg/tarsum" - "github.com/docker/docker/utils" ) type Session struct { @@ -86,7 +85,7 @@ func (r *Session) GetRemoteHistory(imgID, registry string, token []string) ([]st if res.StatusCode == 401 { return nil, errLoginRequired } - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) } jsonString, err := ioutil.ReadAll(res.Body) @@ -115,7 +114,7 @@ func (r *Session) LookupRemoteImage(imgID, registry string, token []string) erro } res.Body.Close() if res.StatusCode != 200 { - return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) } return nil } @@ -134,7 +133,7 @@ func (r *Session) GetRemoteImageJSON(imgID, registry string, token []string) ([] } defer res.Body.Close() if res.StatusCode != 200 { - return nil, -1, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) + return nil, -1, httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d", res.StatusCode), res) } // if the size header is not present, then set it to '-1' imageSize := -1 @@ -282,13 +281,13 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) { // TODO: Right now we're ignoring checksums in the response body. // In the future, we need to use them to check image validity. if res.StatusCode == 404 { - return nil, utils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code: %d", res.StatusCode), res) } else if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { logrus.Debugf("Error reading response body: %s", err) } - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res) } var tokens []string @@ -379,12 +378,12 @@ func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regist } 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) + return httputils.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 { - return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) } var jsonBody map[string]string if err := json.Unmarshal(errBody, &jsonBody); err != nil { @@ -392,7 +391,7 @@ func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, regist } else if jsonBody["error"] == "Image already exists" { return ErrAlreadyExists } - return utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata: %q", res.StatusCode, errBody), res) } return nil } @@ -432,9 +431,9 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry if res.StatusCode != 200 { errBody, err := ioutil.ReadAll(res.Body) if err != nil { - return "", "", utils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) + return "", "", httputils.NewHTTPRequestError(fmt.Sprintf("HTTP code %d while uploading metadata and error when trying to parse response body: %s", res.StatusCode, err), res) } - return "", "", utils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res) + return "", "", httputils.NewHTTPRequestError(fmt.Sprintf("Received HTTP code %d while uploading layer: %q", res.StatusCode, errBody), res) } checksumPayload = "sha256:" + hex.EncodeToString(h.Sum(nil)) @@ -461,7 +460,7 @@ func (r *Session) PushRegistryTag(remote, revision, tag, registry string, token } res.Body.Close() if res.StatusCode != 200 && res.StatusCode != 201 { - return utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to push tag %s on %s", res.StatusCode, tag, remote), res) } return nil } @@ -523,7 +522,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate if err != nil { logrus.Debugf("Error reading response body: %s", err) } - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res) } if res.Header.Get("X-Docker-Token") != "" { tokens = res.Header["X-Docker-Token"] @@ -547,7 +546,7 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate if err != nil { logrus.Debugf("Error reading response body: %s", err) } - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote, errBody), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote, errBody), res) } } @@ -595,7 +594,7 @@ func (r *Session) SearchRepositories(term string) (*SearchResults, error) { } defer res.Body.Close() if res.StatusCode != 200 { - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res) } result := new(SearchResults) err = json.NewDecoder(res.Body).Decode(result) diff --git a/registry/session_v2.go b/registry/session_v2.go index fb1d18e8e..a14e434ac 100644 --- a/registry/session_v2.go +++ b/registry/session_v2.go @@ -12,7 +12,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" "github.com/docker/distribution/registry/api/v2" - "github.com/docker/docker/utils" + "github.com/docker/docker/pkg/httputils" ) const DockerDigestHeader = "Docker-Content-Digest" @@ -95,7 +95,7 @@ func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, au } else if res.StatusCode == 404 { return nil, "", ErrDoesNotExist } - return nil, "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res) + return nil, "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s:%s", res.StatusCode, imageName, tagName), res) } manifestBytes, err := ioutil.ReadAll(res.Body) @@ -141,7 +141,7 @@ func (r *Session) HeadV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Di return false, nil } - return false, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s", res.StatusCode, imageName, dgst), res) + return false, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying head request for %s - %s", res.StatusCode, imageName, dgst), res) } func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Digest, blobWrtr io.Writer, auth *RequestAuthorization) error { @@ -168,7 +168,7 @@ func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Dig if res.StatusCode == 401 { return errLoginRequired } - return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob", res.StatusCode, imageName), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob", res.StatusCode, imageName), res) } _, err = io.Copy(blobWrtr, res.Body) @@ -198,7 +198,7 @@ func (r *Session) GetV2ImageBlobReader(ep *Endpoint, imageName string, dgst dige if res.StatusCode == 401 { return nil, 0, errLoginRequired } - return nil, 0, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s", res.StatusCode, imageName, dgst), res) + return nil, 0, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to pull %s blob - %s", res.StatusCode, imageName, dgst), res) } lenStr := res.Header.Get("Content-Length") l, err := strconv.ParseInt(lenStr, 10, 64) @@ -245,7 +245,7 @@ func (r *Session) PutV2ImageBlob(ep *Endpoint, imageName string, dgst digest.Dig return err } logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) - return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s", res.StatusCode, imageName, dgst), res) + return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s", res.StatusCode, imageName, dgst), res) } return nil @@ -286,7 +286,7 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque } logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) - return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: unexpected %d response status trying to initiate upload of %s", res.StatusCode, imageName), res) + return "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: unexpected %d response status trying to initiate upload of %s", res.StatusCode, imageName), res) } if location = res.Header.Get("Location"); location == "" { @@ -328,7 +328,7 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, si return "", err } logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header) - return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res) + return "", httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res) } hdrDigest, err := digest.ParseDigest(res.Header.Get(DockerDigestHeader)) @@ -384,7 +384,7 @@ func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestA } else if res.StatusCode == 404 { return nil, ErrDoesNotExist } - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s", res.StatusCode, imageName), res) + return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s", res.StatusCode, imageName), res) } decoder := json.NewDecoder(res.Body) diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 84d636b5c..9d4eb2641 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -6,9 +6,13 @@ import ( "github.com/docker/docker/engine" "github.com/docker/docker/nat" "github.com/docker/docker/pkg/ulimit" - "github.com/docker/docker/utils" ) +type KeyValuePair struct { + Key string + Value string +} + type NetworkMode string // IsPrivate indicates whether container use it's private network stack @@ -107,7 +111,7 @@ type LogConfig struct { type HostConfig struct { Binds []string ContainerIDFile string - LxcConf []utils.KeyValuePair + LxcConf []KeyValuePair Memory int64 // Memory limit (in bytes) MemorySwap int64 // Total memory usage (memory + swap); set `-1` to disable swap CpuShares int64 // CPU shares (relative weight vs. other containers) diff --git a/runconfig/parse.go b/runconfig/parse.go index 1fb36e4ac..d302330c8 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -12,7 +12,6 @@ import ( "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/ulimit" "github.com/docker/docker/pkg/units" - "github.com/docker/docker/utils" ) var ( @@ -430,14 +429,14 @@ func parseDriverOpts(opts opts.ListOpts) (map[string][]string, error) { return out, nil } -func parseKeyValueOpts(opts opts.ListOpts) ([]utils.KeyValuePair, error) { - out := make([]utils.KeyValuePair, opts.Len()) +func parseKeyValueOpts(opts opts.ListOpts) ([]KeyValuePair, error) { + out := make([]KeyValuePair, opts.Len()) for i, o := range opts.GetAll() { k, v, err := parsers.ParseKeyValueOpt(o) if err != nil { return nil, err } - out[i] = utils.KeyValuePair{Key: k, Value: v} + out[i] = KeyValuePair{Key: k, Value: v} } return out, nil } diff --git a/utils/utils.go b/utils/utils.go index 92ecb9b6c..a151fc3f0 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -2,9 +2,7 @@ package utils import ( "bufio" - "bytes" "crypto/sha1" - "crypto/sha256" "encoding/hex" "fmt" "io" @@ -13,47 +11,17 @@ import ( "os" "os/exec" "path/filepath" - "regexp" "runtime" "strings" "sync" - "github.com/Sirupsen/logrus" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/stringid" ) -type KeyValuePair struct { - Key string - Value string -} - -var ( - validHex = regexp.MustCompile(`^([a-f0-9]{64})$`) -) - -// Request a given URL and return an io.Reader -func Download(url string) (resp *http.Response, err error) { - if resp, err = http.Get(url); err != nil { - return nil, err - } - if resp.StatusCode >= 400 { - return nil, fmt.Errorf("Got HTTP status code >= 400: %s", resp.Status) - } - return resp, nil -} - -func Trunc(s string, maxlen int) string { - if len(s) <= maxlen { - return s - } - return s[:maxlen] -} - // Figure out the absolute path of our own binary (if it's still around). func SelfPath() string { path, err := exec.LookPath(os.Args[0]) @@ -155,74 +123,7 @@ func DockerInitPath(localCopy string) string { return "" } -func GetTotalUsedFds() int { - if fds, err := ioutil.ReadDir(fmt.Sprintf("/proc/%d/fd", os.Getpid())); err != nil { - logrus.Errorf("Error opening /proc/%d/fd: %s", os.Getpid(), err) - } else { - return len(fds) - } - return -1 -} - -func ValidateID(id string) error { - if ok := validHex.MatchString(id); !ok { - err := fmt.Errorf("image ID '%s' is invalid", id) - return err - } - 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) - for { - nr, er := src.Read(buf) - if nr > 0 { - // ---- Docker addition - // char 16 is C-p - if nr == 1 && buf[0] == 16 { - nr, er = src.Read(buf) - // char 17 is C-q - if nr == 1 && buf[0] == 17 { - if err := src.Close(); err != nil { - return 0, err - } - return 0, nil - } - } - // ---- End of docker - nw, ew := dst.Write(buf[0:nr]) - if nw > 0 { - written += int64(nw) - } - if ew != nil { - err = ew - break - } - if nr != nw { - err = io.ErrShortWrite - break - } - } - if er == io.EOF { - break - } - if er != nil { - err = er - break - } - } - return written, err -} - -func HashData(src io.Reader) (string, error) { - h := sha256.New() - if _, err := io.Copy(h, src); err != nil { - return "", err - } - return "sha256:" + hex.EncodeToString(h.Sum(nil)), nil -} - +// FIXME: move to httputils? ioutils? type WriteFlusher struct { sync.Mutex w io.Writer @@ -254,58 +155,6 @@ func NewWriteFlusher(w io.Writer) *WriteFlusher { return &WriteFlusher{w: w, flusher: flusher} } -func NewHTTPRequestError(msg string, res *http.Response) error { - return &jsonmessage.JSONError{ - Message: msg, - Code: res.StatusCode, - } -} - -// An StatusError reports an unsuccessful exit by a command. -type StatusError struct { - Status string - StatusCode int -} - -func (e *StatusError) Error() string { - return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode) -} - -func quote(word string, buf *bytes.Buffer) { - // Bail out early for "simple" strings - if word != "" && !strings.ContainsAny(word, "\\'\"`${[|&;<>()~*?! \t\n") { - buf.WriteString(word) - return - } - - buf.WriteString("'") - - for i := 0; i < len(word); i++ { - b := word[i] - if b == '\'' { - // Replace literal ' with a close ', a \', and a open ' - buf.WriteString("'\\''") - } else { - buf.WriteByte(b) - } - } - - buf.WriteString("'") -} - -// Take a list of strings and escape them so they will be handled right -// when passed as arguments to an program via a shell -func ShellQuoteArguments(args []string) string { - var buf bytes.Buffer - for i, arg := range args { - if i != 0 { - buf.WriteByte(' ') - } - quote(arg, &buf) - } - return buf.String() -} - var globalTestID string // TestDirectory creates a new temporary directory and returns its path. @@ -343,26 +192,6 @@ func GetCallerName(depth int) string { return callerShortName } -func CopyFile(src, dst string) (int64, error) { - if src == dst { - return 0, nil - } - sf, err := os.Open(src) - if err != nil { - return 0, err - } - defer sf.Close() - if err := os.Remove(dst); err != nil && !os.IsNotExist(err) { - return 0, err - } - df, err := os.Create(dst) - if err != nil { - return 0, err - } - defer df.Close() - return io.Copy(df, sf) -} - // ReplaceOrAppendValues returns the defaults with the overrides either // replaced by env key or appended to the list func ReplaceOrAppendEnvValues(defaults, overrides []string) []string { @@ -411,27 +240,6 @@ func DoesEnvExist(name string) bool { return false } -// ReadSymlinkedDirectory returns the target directory of a symlink. -// The target of the symbolic link may not be a file. -func ReadSymlinkedDirectory(path string) (string, error) { - var realPath string - var err error - if realPath, err = filepath.Abs(path); err != nil { - return "", fmt.Errorf("unable to get absolute path for %s: %s", path, err) - } - if realPath, err = filepath.EvalSymlinks(realPath); err != nil { - return "", fmt.Errorf("failed to canonicalise path for %s: %s", path, err) - } - realPathInfo, err := os.Stat(realPath) - if err != nil { - return "", fmt.Errorf("failed to stat target '%s' of '%s': %s", realPath, path, err) - } - if !realPathInfo.Mode().IsDir() { - return "", fmt.Errorf("canonical path points to a file '%s'", realPath) - } - return realPath, nil -} - // ValidateContextDirectory checks if all the contents of the directory // can be read and returns an error if some files can't be read // symlinks which point to non-existing files don't trigger an error @@ -476,15 +284,6 @@ func ValidateContextDirectory(srcPath string, excludes []string) error { }) } -func StringsContainsNoCase(slice []string, s string) bool { - for _, ss := range slice { - if strings.ToLower(s) == strings.ToLower(ss) { - return true - } - } - return false -} - // Reads a .dockerignore file and returns the list of file patterns // to ignore. Note this will trim whitespace from each line as well // as use GO's "clean" func to get the shortest/cleanest path for each. @@ -516,27 +315,6 @@ func ReadDockerIgnore(path string) ([]string, error) { return excludes, nil } -// Wrap a concrete io.Writer and hold a count of the number -// of bytes written to the writer during a "session". -// This can be convenient when write return is masked -// (e.g., json.Encoder.Encode()) -type WriteCounter struct { - Count int64 - Writer io.Writer -} - -func NewWriteCounter(w io.Writer) *WriteCounter { - return &WriteCounter{ - Writer: w, - } -} - -func (wc *WriteCounter) Write(p []byte) (count int, err error) { - count, err = wc.Writer.Write(p) - wc.Count += int64(count) - return -} - // ImageReference combines `repo` and `ref` and returns a string representing // the combination. If `ref` is a digest (meaning it's of the form // :, the returned string is @. Otherwise, diff --git a/utils/utils_test.go b/utils/utils_test.go index 94303a0e9..286300942 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -1,9 +1,10 @@ package utils import ( - "bytes" + "fmt" + "io/ioutil" "os" - "strings" + "path/filepath" "testing" ) @@ -25,104 +26,6 @@ func TestReplaceAndAppendEnvVars(t *testing.T) { } } -// Reading a symlink to a directory must return the directory -func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) { - var err error - if err = os.Mkdir("/tmp/testReadSymlinkToExistingDirectory", 0777); err != nil { - t.Errorf("failed to create directory: %s", err) - } - - if err = os.Symlink("/tmp/testReadSymlinkToExistingDirectory", "/tmp/dirLinkTest"); err != nil { - t.Errorf("failed to create symlink: %s", err) - } - - var path string - if path, err = ReadSymlinkedDirectory("/tmp/dirLinkTest"); err != nil { - t.Fatalf("failed to read symlink to directory: %s", err) - } - - if path != "/tmp/testReadSymlinkToExistingDirectory" { - t.Fatalf("symlink returned unexpected directory: %s", path) - } - - if err = os.Remove("/tmp/testReadSymlinkToExistingDirectory"); err != nil { - t.Errorf("failed to remove temporary directory: %s", err) - } - - if err = os.Remove("/tmp/dirLinkTest"); err != nil { - t.Errorf("failed to remove symlink: %s", err) - } -} - -// Reading a non-existing symlink must fail -func TestReadSymlinkedDirectoryNonExistingSymlink(t *testing.T) { - var path string - var err error - if path, err = ReadSymlinkedDirectory("/tmp/test/foo/Non/ExistingPath"); err == nil { - t.Fatalf("error expected for non-existing symlink") - } - - if path != "" { - t.Fatalf("expected empty path, but '%s' was returned", path) - } -} - -// Reading a symlink to a file must fail -func TestReadSymlinkedDirectoryToFile(t *testing.T) { - var err error - var file *os.File - - if file, err = os.Create("/tmp/testReadSymlinkToFile"); err != nil { - t.Fatalf("failed to create file: %s", err) - } - - file.Close() - - if err = os.Symlink("/tmp/testReadSymlinkToFile", "/tmp/fileLinkTest"); err != nil { - t.Errorf("failed to create symlink: %s", err) - } - - var path string - if path, err = ReadSymlinkedDirectory("/tmp/fileLinkTest"); err == nil { - t.Fatalf("ReadSymlinkedDirectory on a symlink to a file should've failed") - } - - if path != "" { - t.Fatalf("path should've been empty: %s", path) - } - - if err = os.Remove("/tmp/testReadSymlinkToFile"); err != nil { - t.Errorf("failed to remove file: %s", err) - } - - if err = os.Remove("/tmp/fileLinkTest"); err != nil { - t.Errorf("failed to remove symlink: %s", err) - } -} - -func TestWriteCounter(t *testing.T) { - dummy1 := "This is a dummy string." - dummy2 := "This is another dummy string." - totalLength := int64(len(dummy1) + len(dummy2)) - - reader1 := strings.NewReader(dummy1) - reader2 := strings.NewReader(dummy2) - - var buffer bytes.Buffer - wc := NewWriteCounter(&buffer) - - reader1.WriteTo(wc) - reader2.WriteTo(wc) - - if wc.Count != totalLength { - t.Errorf("Wrong count: %d vs. %d", wc.Count, totalLength) - } - - if buffer.String() != dummy1+dummy2 { - t.Error("Wrong message written") - } -} - func TestImageReference(t *testing.T) { tests := []struct { repo string @@ -152,3 +55,46 @@ func TestDigestReference(t *testing.T) { t.Errorf("Unexpected DigestReference=true for input %q", input) } } + +func TestReadDockerIgnore(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "dockerignore-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + diName := filepath.Join(tmpDir, ".dockerignore") + + di, err := ReadDockerIgnore(diName) + if err != nil { + t.Fatalf("Expected not to have error, got %s", err) + } + + if diLen := len(di); diLen != 0 { + t.Fatalf("Expected to have zero dockerignore entry, got %d", diLen) + } + + content := fmt.Sprintf("test1\n/test2\n/a/file/here\n\nlastfile") + err = ioutil.WriteFile(diName, []byte(content), 0777) + if err != nil { + t.Fatal(err) + } + + di, err = ReadDockerIgnore(diName) + if err != nil { + t.Fatal(err) + } + + if di[0] != "test1" { + t.Fatalf("First element is not test1") + } + if di[1] != "/test2" { + t.Fatalf("Second element is not /test2") + } + if di[2] != "/a/file/here" { + t.Fatalf("Third element is not /a/file/here") + } + if di[3] != "lastfile" { + t.Fatalf("Fourth element is not lastfile") + } +} From 9a87553e4fc6c0abdd298893deefc44050208dda Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 13 Apr 2015 16:31:20 -0700 Subject: [PATCH 417/999] cleanup test wrong key.json leading to other failures wait for container to be running before trying to kill it in daemon tests Signed-off-by: Jessica Frazelle --- integration-cli/docker_cli_daemon_test.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index adaccc24c..384c96803 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -881,13 +881,12 @@ func TestDaemonwithwrongkey(t *testing.T) { } d1 := NewDaemon(t) + defer os.Remove("/etc/docker/key.json") if err := d1.Start(); err == nil { d1.Stop() - os.Remove("/etc/docker/key.json") t.Fatalf("It should not be succssful to start daemon with wrong key: %v", err) } - os.Remove("/etc/docker/key.json") content, _ := ioutil.ReadFile(d1.logFile.Name()) @@ -905,7 +904,7 @@ func TestDaemonRestartKillWait(t *testing.T) { } defer d.Stop() - out, err := d.Cmd("run", "-d", "busybox", "/bin/cat") + out, err := d.Cmd("run", "-id", "busybox", "/bin/cat") if err != nil { t.Fatalf("Could not run /bin/cat: err=%v\n%s", err, out) } From 7e2d05b4938c010bf15224bd2857e2dca92ec9b3 Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Mon, 13 Apr 2015 14:14:37 -0700 Subject: [PATCH 418/999] Add detection for F2Fs and JFS Signed-off-by: Megan Kostick Alphabetize FSMagic list to make more human-readable. Signed-off-by: Megan Kostick --- daemon/graphdriver/driver.go | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 26095b05c..79e6b72de 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -14,20 +14,22 @@ import ( type FsMagic uint32 const ( - FsMagicBtrfs = FsMagic(0x9123683E) FsMagicAufs = FsMagic(0x61756673) - FsMagicExtfs = FsMagic(0x0000EF53) + FsMagicBtrfs = FsMagic(0x9123683E) FsMagicCramfs = FsMagic(0x28cd3d45) - FsMagicRamFs = FsMagic(0x858458f6) - FsMagicTmpFs = FsMagic(0x01021994) - FsMagicSquashFs = FsMagic(0x73717368) + FsMagicExtfs = FsMagic(0x0000EF53) + FsMagicF2fs = FsMagic(0xF2F52010) + FsMagicJffs2Fs = FsMagic(0x000072b6) + FsMagicJfs = FsMagic(0x3153464a) FsMagicNfsFs = FsMagic(0x00006969) + FsMagicRamFs = FsMagic(0x858458f6) FsMagicReiserFs = FsMagic(0x52654973) FsMagicSmbFs = FsMagic(0x0000517B) - FsMagicJffs2Fs = FsMagic(0x000072b6) - FsMagicZfs = FsMagic(0x2fc12fc1) - FsMagicXfs = FsMagic(0x58465342) + FsMagicSquashFs = FsMagic(0x73717368) + FsMagicTmpFs = FsMagic(0x01021994) FsMagicUnsupported = FsMagic(0x00000000) + FsMagicXfs = FsMagic(0x58465342) + FsMagicZfs = FsMagic(0x2fc12fc1) ) var ( @@ -50,18 +52,20 @@ var ( FsNames = map[FsMagic]string{ FsMagicAufs: "aufs", FsMagicBtrfs: "btrfs", - FsMagicExtfs: "extfs", FsMagicCramfs: "cramfs", - FsMagicRamFs: "ramfs", - FsMagicTmpFs: "tmpfs", - FsMagicSquashFs: "squashfs", + FsMagicExtfs: "extfs", + FsMagicF2fs: "f2fs", + FsMagicJffs2Fs: "jffs2", + FsMagicJfs: "jfs", FsMagicNfsFs: "nfs", + FsMagicRamFs: "ramfs", FsMagicReiserFs: "reiserfs", FsMagicSmbFs: "smb", - FsMagicJffs2Fs: "jffs2", - FsMagicZfs: "zfs", - FsMagicXfs: "xfs", + FsMagicSquashFs: "squashfs", + FsMagicTmpFs: "tmpfs", FsMagicUnsupported: "unsupported", + FsMagicXfs: "xfs", + FsMagicZfs: "zfs", } ) From a55f8e1ce734035a77cc13d1e2f42dbef41d418e Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 30 Mar 2015 16:32:04 +1000 Subject: [PATCH 419/999] Researching Docker Hub account linking and automated builds details Signed-off-by: Sven Dowideit --- docs/sources/docker-hub/builds.md | 149 ++++++++++++++---- .../gh-check-admin-org-dh-app-access.png | Bin 0 -> 31529 bytes .../gh-check-user-org-dh-app-access.png | Bin 0 -> 38325 bytes 3 files changed, 119 insertions(+), 30 deletions(-) create mode 100644 docs/sources/docker-hub/hub-images/gh-check-admin-org-dh-app-access.png create mode 100644 docs/sources/docker-hub/hub-images/gh-check-user-org-dh-app-access.png diff --git a/docs/sources/docker-hub/builds.md b/docs/sources/docker-hub/builds.md index 1613ad1d4..bd3e3d2cb 100644 --- a/docs/sources/docker-hub/builds.md +++ b/docs/sources/docker-hub/builds.md @@ -8,20 +8,18 @@ page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub *Automated Builds* are a special feature of Docker Hub which allow you to use [Docker Hub's](https://hub.docker.com) build clusters to automatically -create images from a specified `Dockerfile` and a GitHub or Bitbucket repository -(or "context"). The system will clone your repository and build the image -described by the `Dockerfile` using the repository as the context. The -resulting automated image will then be uploaded to the Docker Hub registry -and marked as an *Automated Build*. +create images from a GitHub or Bitbucket repository containing a `Dockerfile` +The system will clone your repository and build the image described by the +`Dockerfile` using the directory the `Dockerfile` is in (and subdirectories) +as the build context. The resulting automated image will then be uploaded +to the Docker Hub registry and marked as an *Automated Build*. Automated Builds have several advantages: * Users of *your* Automated Build can trust that the resulting image was built exactly as specified. - * The `Dockerfile` will be available to anyone with access to -your repository on the Docker Hub registry. - +your repository on the Docker Hub registry. * Because the process is automated, Automated Builds help to make sure that your repository is always up to date. @@ -33,16 +31,26 @@ http://docs.docker.com/userguide/dockerhub/#creating-a-docker-hub-account) and on GitHub and/or Bitbucket. In either case, the account needs to be properly validated and activated before you can link to it. -## Setting up Automated Builds with GitHub - -In order to set up an Automated Build, you need to first link your -[Docker Hub](https://hub.docker.com) account with a GitHub account. +The first time you to set up an Automated Build, your +[Docker Hub](https://hub.docker.com) account will need to be linked to +a GitHub or Bitbucket account. This will allow the registry to see your repositories. -> *Note:* +If you have previously linked your Docker Hub account, and want to view or modify +that link, click on the "Manage - Settings" link in the sidebar, and then +"Linked Accounts" in your Settings sidebar. + +## Automated Builds from GitHub + +If you've previously linked your Docker Hub account to your GitHub account, +you'll be able to skip to the [Creating an Automated Build](#creating-an-automated-build). + +### Linking your Docker Hub account to a GitHub account + +> *Note:* > Automated Builds currently require *read* and *write* access since > [Docker Hub](https://hub.docker.com) needs to setup a GitHub service -> hook. We have no choice here, this is how GitHub manages permissions, sorry! +> hook. We have no choice here, this is how GitHub manages permissions, sorry! > We do guarantee nothing else will be touched in your account. To get started, log into your Docker Hub account and click the @@ -51,17 +59,99 @@ To get started, log into your Docker Hub account and click the Select the [GitHub service](https://registry.hub.docker.com/associate/github/). -Then follow the onscreen instructions to authorize and link your +When linking to GitHub, you'll need to select either "Public and Private", +or "Limited" linking. + +The "Public and Private" option is the easiest to use, +as it grants the Docker Hub full access to all of your repositories. GitHub +also allows you to grant access to repositories belonging to your GitHub +organizations. + +By choosing the "Limited" linking, your Docker Hub account only gets permission +to access your public data and public repositories. + +Follow the onscreen instructions to authorize and link your GitHub account to Docker Hub. Once it is linked, you'll be able to -choose a repo from which to create the Automatic Build. +choose a source repository from which to create the Automatic Build. + +You will be able to review and revoke Docker Hub's access by visiting the +[GitHub User's Applications settings](https://github.com/settings/applications). + +> **Note**: If you delete the GitHub account linkage that is used for one of your +> automated build repositories, the previously built images will still be available. +> If you re-link to that GitHub account later, the automated build can be started +> using the "Start Build" button on the Hub, or if the webhook on the GitHub repository +> still exists, will be triggered by any subsequent commits. + +### Auto builds and Limited linked GitHub accounts. + +If you selected to link your GitHub account with only a "Limited" link, then +after creating your automated build, you will need to either manually trigger a +Docker Hub build using the "Start a Build" button, or add the GitHub webhook +manually, as described in [GitHub Service Hooks](#github-service-hooks). + +### Changing the GitHub user link + +If you want to remove, or change the level of linking between your GitHub account +and the Docker Hub, you need to do this in two places. + +First, remove the "Linked Account" from your Docker Hub "Settings". +Then go to your GitHub account's Personal settings, and in the "Applications" +section, "Revoke access". + +You can now re-link your account at any time. + +### GitHub Organizations + +GitHub organizations and private repositories forked from organizations will be +made available to auto build using the "Docker Hub Registry" application, which +needs to be added to the organization - and then will apply to all users. + +To check, or request access, go to your GitHub user's "Setting" page, select the +"Applications" section from the left side bar, then click the "View" button for +"Docker Hub Registry". + +![Check User access to GitHub](/docker-hub/hub-images/gh-check-user-org-dh-app-access.png) + +The organization's administrators may need to go to the Organization's "Third +party access" screen in "Settings" to Grant or Deny access to the Docker Hub +Registry application. This change will apply to all organization members. + +![Check Docker Hub application access to Organization](/docker-hub/hub-images/gh-check-admin-org-dh-app-access.png) + +More detailed access controls to specific users and GitHub repositories would be +managed using the GitHub People and Teams interfaces. ### Creating an Automated Build You can [create an Automated Build]( https://registry.hub.docker.com/builds/github/select/) from any of your -public or private GitHub repositories with a `Dockerfile`. +public or private GitHub repositories that have a `Dockerfile`. -### GitHub Submodules +Once you've selected the source repository, you can then configure: + +- The Hub user/org the repository is built to - either your Hub account name, +or the name of any Hub organizations your account is in +- The Docker repository name the image is built to +- If the Docker repository should be "Public" or "Private" + You can change the accessibility options after the repository has been created. + If you add a Private repository to a Hub user, then you can only add other users + as collaborators, and those users will be able to view and pull all images in that + repository. To configure more granular access permissions, such as using groups of + users or allow different users access to different image tags, then you need + to add the Private repository to a Hub organization that your user has Administrator + privilege on. +- If you want the GitHub to notify the Docker Hub when a commit is made, and thus trigger + a rebuild of all the images in this automated build. + +You can also select one or more +- The git branch/tag, which repository sub-directory to use as the context +- The Docker image tag name + +You can set a description for the repository by clicking "Description" link in the righthand side bar after the automated build - note that the "Full Description" will be over-written next build from the README.md file. +has been created. + +### GitHub private submodules If your GitHub repository contains links to private submodules, you'll get an error message in your build. @@ -114,17 +204,14 @@ can be limited to read-only access to just the repositories required to build. - -### GitHub Organizations -GitHub organizations will appear once your membership to that organization is -made public on GitHub. To verify, you can look at the members tab for your -organization on GitHub. +### GitHub Service hooks -### GitHub Service Hooks +The GitHub Service hook allows GitHub to notify the Docker Hub when something has +been committed to that git repository. You will need to add the Service Hook manually +if your GitHub account is "Limited" linked to the Docker Hub. -Follow the steps below to configure the GitHub service -hooks for your Automated Build: +Follow the steps below to configure the GitHub Service hooks for your Automated Build: @@ -146,14 +233,16 @@ hooks for your Automated Build: - - + + + +
Webhooks & Services Click on "Webhooks & Services" on the left side of the page.
3.Find the service labeled DockerFind the service labeled "Docker" and click on it.
4.Activate Service HooksFind the service labeled DockerFind the service labeled "Docker" (or click on "Add service") and click on it.
4.Activate Service Hooks Make sure the "Active" checkbox is selected and click the "Update service" button to save your changes.
-## Setting up Automated Builds with Bitbucket +## Automated Builds with Bitbucket In order to setup an Automated Build, you need to first link your [Docker Hub](https://hub.docker.com) account with a Bitbucket account. @@ -249,7 +338,7 @@ $ curl --data "build=true" -X POST https://registry.hub.docker.com/u/svendowidei OK ``` -> **Note:** +> **Note:** > You can only trigger one build at a time and no more than one > every five minutes. If you already have a build pending, or if you > recently submitted a build request, those requests *will be ignored*. diff --git a/docs/sources/docker-hub/hub-images/gh-check-admin-org-dh-app-access.png b/docs/sources/docker-hub/hub-images/gh-check-admin-org-dh-app-access.png new file mode 100644 index 0000000000000000000000000000000000000000..0df38c69465ddf106921a8531faff3d596b0a2e8 GIT binary patch literal 31529 zcmb5VWmH?;7A{P^r3KnzEn2}LP=Xb2aS2e|odlPn#VbhhVu1uN5L|<6i$j9D7Yk1C z;N0|m&$)Mu^XvOIBO`mRy;(BXT5~<~nKL0Dlx2wssR#)O2#Dn6q|^uqZg~(8+>p6{ z6JH{;wr7I>df+6d>q0>Ako5281_3ocbK11)>K{Li)gJibX+AJ z?d`!1u6U7vK++j(>I$}a;bHA+^+Hx&>4OC)_#VFeu9>2&6v6f1zx2kOSONmC2zjaZ z8lR@NQXU$RX`Xkk?_9#+hJ_8r$e-0X6|6o4Hd+8yLs%H~NJx`$y?FJi{Cj%Q?b~e6 zUa)});c;KH%?MxJfB(HIJHX_7(5qt8`;FMEG#7}wySs;b?f1`T=YAK3^_G3fg7`8F z%B2rhIe6or9u5plq(AuQj(`I{JpA zo-3sZ+4pXY(!Xsu?_hro@xAyskgvHx&Mq;s@pCw5aP6PAN`l&;%TxNM*xUc{jf(+Cn(?e!{c-He0Xi|SpiT>No;v&OVyuYPq} zW=G+~(`bY~&b%#0$2T<{M$M)FJ9ZOTou@;X!7K$}PY<)PaeSz*;_1b)ue-6*Y`4DO zo|?viqdr|!1Ym1RG#@_gJqGCiuyx-UX|fIGPdPiqz3Y* zPa{V`GyZSwwGS!!QdTkR>)VUK=G7%p%YOJuj@qd=*U{dtPNmOj@^j^sDL1Rxa;18H z+Np}2iE{9HRgti})p(x%ssr)ToFVPwKVtq@Vf~`Aq4Iw6Q2FbD|0-rS*>mTBl4E~Y zr_SW6cTItUE_kNaBX@%X^ z?9Q`_aEpv|#SSV&G;i&P7@qbL#D^OFKeY$v^o)+W48AIX@jyIw2C_Z+#S|&MPrbbh zL_}Cb@{FFmnYPwCucF;)td~Y<*-vn!kJ(RGZVuEAUq*>sOoT_JK)pC%XLH`M%%I() zedjcR{@(UG5ybyYD{7=s{B$YsVM1Lk0LJZmd~24@FZaClp#kQ6xbPtGVdD&?FE4n* z_O%RN7110>>4Gs_rGs=yk$&~|K!*A_CrQ)({${2THQ!<&Wzgj0B=Ph89eu|LpS^aW z5l(G;Tib%ZcGG{BYt0IW%gOg~nz^LKou)RIz4Fy9qfhJYPv{&{dBmJ6=nuxG9Vf5n z#jm0eSFU#RAx8RDR+?xV z=B9Jv|LhTHo_@{5W`saz(%yc=}W0odx2EiB>rkOl*Ebiz>a(TN)(t&-rI9s zh4FMoQmtTs(^DO651(Ej!Yh!Kl`gx=Z2U-2k&J`z58yDbZ;^$}T z5_pj@VAj;|%tJ8sMs#{@YBW`F^p0hBn?H zRoaeM=xt{!CiEN*cq?QaB5W0>h^8KLioBZBKgjF0nc!REx3`0X6~`ojqtVfaIMqEj z9f;}IlA(_^Fb;LnUIPn1H6nPP(s3ygrwG8%sp}?h2~3GGR1ZmDzUtf)ZZzwj6DSAQ zaqcGz_`mEW#_25cxpoaliI1I&mxOz{wb@?dRO+K&8_^1P%GsF88{R`Ek(ZSMTT~ro>J=!?NY5<= z5xhJ2<~ewL(Snw9G?pabVeT-LJ0wyW9~hAx9N+@07r>-f~91Y2TtJsalANAuUHC-~DPYo`Mpbpyk3_UT@S0 z@A;#x*@F4ztJ8~z*Hp)cu~l3o^m~{!Zk`Ge4Zch_i#a_1@$D?cu72~pIL%-MB9I}2l zs@P;===j=a#Q-BFf|}X|kGjVgrGGn+Q7`!DS$xi^Z{Q2;5Mfw8dZfh3sT^iOw5iN5 zqu;<5-$GTlfv}Qa`FQ(jC_p~ux$F0hZa#JM;-K=oVqAO!V~GTw9I=-*j%ZL2rZ`=GN10JzW8zFdf2NmlZ6y40PO~ zO52+SEutuRLK`ZP#bG@`)@)Uo`J+bbx*3XlmX%yY09+cxIQW8;uJnUmipc@~G5}8~L zsh(B}a?kB#Z~)n6dcV0-YImPa6A3uCJ3sm$P+WE=Wu{uWa$?rEY~z5QFr0$dJ=k&7 zlkmQ}x`w79BhON3p1-W*Bkyj!?8f4};??m(R2let&taw!GVY>xYi zZJ9U~%df%^H{5KhaW*Lhd?LzPF}3%?dBqH)5~l43^Q>akFYg1Sk*@bKs@r8IvxPye zoh9IhiQ~^u8bWxeV`m2(tvY)Kk+6ZN6$TG<+uG9Wo0t-*-mpf5$jV6+i`8^&#qz4M zD@4C>8j#=^?gAcN*0z7_GfV9K5XY|$keSLIpLl~dqu(} zP~FZ0K|8y+dBAwEfvc&TlS>QlFV7lIH@a5SE<1AmKG{VqcrJfsAkW_^QC=jhu3$DvzE9+wIdGPU%95$x+M9 ze^Kwi%mC)Y!yLm+Q*|of&+5RFBMj!J> z73HaB?D_mC&RcER%VBiax5|zl;clM&M9u(3$XCA}MPNk3uG9QsM^hq%Y|4e?yJ(!T zPi20`kKOKB_E}A1%!R5}MSle=bv0uxT))FdJJCX+h|wDH6HJxz1Ju3=f%UBNTyim% z(?r<8;@ja_k25k2C~69qA+odueS`7{+KO^F#fLyriA3ClVChE~$o@i&oe0x1o5K%f zm5&j-wfPMvH%1i~0%jC1PkQ=OBLfa_K?`FW&(g{qqr9=L);wbF$=n`u7iErq>% zjr8kRa;wT!muJTf%`P|!0KMaMlL{_)s@i6}N_uk)FjbLme8Xn83XB>9=vCN_7u!eC ziQ3txtxr|jc1F?yDjz(2xX;Qj6FtAV!!0ls1f>##2lCfK#_~I7Okiu=_XuVf2DW48 zQ`fH(Y0U2kGPpvA0VB&*whPL3bN%wL@O?Jr^_4W1u(({E$VaCPE$jUBu#PVdBK6<$NM!OvKHE@;iJ{ zSy{gr4=7HBg7|FBVKED9JkN;!@Gk#I-Io;Y_z3S2(!qR{R!lLRnM*oc@Rw;k>h~8D zF!`09ljesxN-(CI+1wK&5u3;cql{97NVdj&cW>dvV)a$`6usHu{T*R!h9z~hSrxbp zT}3v;P`foIJIGD7NFG|G>gggro+v?c zpP_iejVkC+yt1m{kv__o4ZppWuS{e7ka`HS*QEnV*kg%20DiD)H6X7hWVrhlwb`{<{uB)oiH~xLzA#v-dJwX5r1t zs}Aorb9E*2;4_x&=vcL7^jjz^Zd6F0iM@vd%KGppFgm7v$%f~=)4Z=i`hbE1E-*hk z=VwsuFzYR{zo!1}t~hR4-IvbmWPv!PuG;SWFtah$w@@W2(vWUzsBvW$V8t}B6m|5| zIC;4Yt;GPhpRU@RsYUVhUu0VL&uOLTi}j6)F|?^%F=Sd`O_?~w(E80e3X0pGgf%ev zy{TdhN?X-Nh*QahJ%-l=zm$H$-Y>QVC(^uDdTIu99Y5kt0XtZWRhYWM?a=VVuqjp4ZXcM|+ zp0Qknn13`tn3<`Eqm~;R(3Xe*3kusjVSag^v&fIl`7gyq>j(P;^~7mnJNveDwI*vo zR7%!dCr`pE)@Rj^=jy;zQK*VlVku7P8^YXDiTQN)pNuR394>P$j1NT{W+^-eqd>}O zJ5u5w<_`9ZeCe=)r5@Nh67h=egMhEm_^EOZ`eEruV(>zIC`6LEN4tlB}V5XvCDuNjLMh6gg>wI%LCM*Z{FHl*w z*1HrHegP@)({h;dRF_oakgcnMM=}`z!kIWG8tdd`Nfn@zkC%Y))bH20DIqDAp<)`6 z9I=r6d!F8*OP0fZ`nK|OaXX6RFm#5^1+;KDHvH3wKxf-S7-Yyq^U}__e;@$8ZR2}u zlNmcqvsbjh1l7%+>`8qG-Vcsg?6!WJ=2k9<;eMc>w45Pp_GhSycn<(!jV!1CIfxB6 zDw#k|wNEa_*AAj}+rK9tHr++V^>S->N{0zr-K~H82|NvgUJj}w!I4x4JHkuMO7Q(B z5hx$ancJZX$J+vb)^k$*PBMbek2fyg<($`7Zca2B<^)f9_@x`JRsp6O&W5^h^GUp) z{+JM2%y}52&UZ3TG4^_R+nZ-TF4lPO(esOuBG>1ZnI=RT<^%**5GZS8P;85+8p$G2 zjk(eM03Q-I*jaT)APhF>h2lkXBs3QM1}5E(g`&fuQFa#>a

bzNr=?cDdHSIwRhH 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 kkGH7nzi<{9 literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub/hub-images/gh-check-user-org-dh-app-access.png b/docs/sources/docker-hub/hub-images/gh-check-user-org-dh-app-access.png new file mode 100644 index 0000000000000000000000000000000000000000..13ad6468f6604d50115c88ee3bed0908fbfc679c GIT binary patch literal 38325 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 z%0D@`o6+FJ; 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 literal 0 HcmV?d00001 From b85ade79d5a2cceb926fcae13997a6dfa28be4bc Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Tue, 14 Apr 2015 08:32:25 +0800 Subject: [PATCH 420/999] duplicate logDone in TestRmRunningContainerCheckError409 and TestRmRunningContainer Signed-off-by: Yuan Sun --- integration-cli/docker_cli_rm_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index 8f8ea7b66..56eb2e525 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -73,7 +73,7 @@ func TestRmRunningContainerCheckError409(t *testing.T) { t.Fatalf("Expected error to contain '409 Conflict' but found %s", err) } - logDone("rm - running container") + logDone("rm - running container with Error 409") } func TestRmForceRemoveRunningContainer(t *testing.T) { From f7538c77efaf47fa5b82ae844b5c01887239ce65 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 23:16:42 -0400 Subject: [PATCH 421/999] Move TestRunDetach to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_cli_run_unix_test.go | 71 +++++++++++++++++++++ integration-cli/utils.go | 11 +++- integration/commands_test.go | 50 --------------- 3 files changed, 81 insertions(+), 51 deletions(-) diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 026f8279e..211e6c1f5 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -3,6 +3,7 @@ package main import ( + "bufio" "fmt" "io/ioutil" "os" @@ -201,3 +202,73 @@ func TestRunDeviceDirectory(t *testing.T) { logDone("run - test --device directory mounts all internal devices") } + +// TestRunDetach checks attaching and detaching with the escape sequence. +func TestRunAttachDetach(t *testing.T) { + defer deleteAllContainers() + name := "attach-detach" + cmd := exec.Command(dockerBinary, "run", "--name", name, "-it", "busybox", "cat") + stdout, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + cpty, tty, err := pty.Open() + if err != nil { + t.Fatal(err) + } + defer cpty.Close() + cmd.Stdin = tty + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + if err := waitRun(name); err != nil { + t.Fatal(err) + } + + if _, err := cpty.Write([]byte("hello\n")); err != nil { + t.Fatal(err) + } + + out, err := bufio.NewReader(stdout).ReadString('\n') + if err != nil { + t.Fatal(err) + } + if strings.TrimSpace(out) != "hello" { + t.Fatalf("exepected 'hello', got %q", out) + } + + // escape sequence + if _, err := cpty.Write([]byte{16}); err != nil { + t.Fatal(err) + } + time.Sleep(100 * time.Millisecond) + if _, err := cpty.Write([]byte{17}); err != nil { + t.Fatal(err) + } + + ch := make(chan struct{}) + go func() { + cmd.Wait() + ch <- struct{}{} + }() + + running, err := inspectField(name, "State.Running") + if err != nil { + t.Fatal(err) + } + if running != "true" { + t.Fatal("exepected container to still be running") + } + + go func() { + dockerCmd(t, "kill", name) + }() + + select { + case <-ch: + case <-time.After(10 * time.Millisecond): + t.Fatal("timed out waiting for container to exit") + } + + logDone("run - attach detach") +} diff --git a/integration-cli/utils.go b/integration-cli/utils.go index 536f6984e..1fcf44535 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -213,7 +213,16 @@ func waitInspect(name, expr, expected string, timeout int) error { cmd := exec.Command(dockerBinary, "inspect", "-f", expr, name) out, _, err := runCommandWithOutput(cmd) if err != nil { - return fmt.Errorf("error executing docker inspect: %v", err) + if !strings.Contains(out, "No such") { + return fmt.Errorf("error executing docker inspect: %v\n%s", err, out) + } + select { + case <-after: + return err + default: + time.Sleep(10 * time.Millisecond) + continue + } } out = strings.TrimSpace(out) diff --git a/integration/commands_test.go b/integration/commands_test.go index 97a927b8b..7847605cb 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -113,56 +113,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } -// TestRunDetach checks attaching and detaching with the escape sequence. -func TestRunDetach(t *testing.T) { - stdout, stdoutPipe := io.Pipe() - cpty, tty, err := pty.Open() - if err != nil { - t.Fatal(err) - } - - cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - defer cleanup(globalEngine, t) - - ch := make(chan struct{}) - go func() { - defer close(ch) - cli.CmdRun("-i", "-t", unitTestImageID, "cat") - }() - - container := waitContainerStart(t, 10*time.Second) - - state := setRaw(t, container) - defer unsetRaw(t, container, state) - - setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil { - t.Fatal(err) - } - }) - - setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { - cpty.Write([]byte{16}) - time.Sleep(100 * time.Millisecond) - cpty.Write([]byte{17}) - }) - - // wait for CmdRun to return - setTimeout(t, "Waiting for CmdRun timed out", 15*time.Second, func() { - <-ch - }) - closeWrap(cpty, stdout, stdoutPipe) - - time.Sleep(500 * time.Millisecond) - if !container.IsRunning() { - t.Fatal("The detached container should be still running") - } - - setTimeout(t, "Waiting for container to die timed out", 20*time.Second, func() { - container.Kill() - }) -} - // TestAttachDetach checks that attach in tty mode can be detached using the long container ID func TestAttachDetach(t *testing.T) { stdout, stdoutPipe := io.Pipe() From ae0883ce009daa2b94e67264cfa5a13b4cae76bf Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 23:17:55 -0400 Subject: [PATCH 422/999] Move TestAttachDetach to integration-cli Signed-off-by: Brian Goff --- .../docker_cli_attach_unix_test.go | 76 ++++++++++++++++ integration/commands_test.go | 88 ------------------- 2 files changed, 76 insertions(+), 88 deletions(-) diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index 8d1573512..bea73d770 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -3,6 +3,7 @@ package main import ( + "bufio" "os/exec" "strings" "testing" @@ -137,3 +138,78 @@ func TestAttachAfterDetach(t *testing.T) { logDone("attach - reconnect after detaching") } + +// TestAttachDetach checks that attach in tty mode can be detached using the long container ID +func TestAttachDetach(t *testing.T) { + out, _, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") + id := strings.TrimSpace(out) + if err := waitRun(id); err != nil { + t.Fatal(err) + } + + cpty, tty, err := pty.Open() + if err != nil { + t.Fatal(err) + } + defer cpty.Close() + + cmd := exec.Command(dockerBinary, "attach", id) + cmd.Stdin = tty + stdout, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + defer stdout.Close() + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + if err := waitRun(id); err != nil { + t.Fatalf("error waiting for container to start: %v", err) + } + + if _, err := cpty.Write([]byte("hello\n")); err != nil { + t.Fatal(err) + } + out, err = bufio.NewReader(stdout).ReadString('\n') + if err != nil { + t.Fatal(err) + } + if strings.TrimSpace(out) != "hello" { + t.Fatalf("exepected 'hello', got %q", out) + } + + // escape sequence + if _, err := cpty.Write([]byte{16}); err != nil { + t.Fatal(err) + } + time.Sleep(100 * time.Millisecond) + if _, err := cpty.Write([]byte{17}); err != nil { + t.Fatal(err) + } + + ch := make(chan struct{}) + go func() { + cmd.Wait() + ch <- struct{}{} + }() + + running, err := inspectField(id, "State.Running") + if err != nil { + t.Fatal(err) + } + if running != "true" { + t.Fatal("exepected container to still be running") + } + + go func() { + dockerCmd(t, "kill", id) + }() + + select { + case <-ch: + case <-time.After(10 * time.Millisecond): + t.Fatal("timed out waiting for container to exit") + } + + logDone("attach - detach") +} diff --git a/integration/commands_test.go b/integration/commands_test.go index 7847605cb..88a7bb87b 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -113,94 +113,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } -// TestAttachDetach checks that attach in tty mode can be detached using the long container ID -func TestAttachDetach(t *testing.T) { - stdout, stdoutPipe := io.Pipe() - cpty, tty, err := pty.Open() - if err != nil { - t.Fatal(err) - } - - cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - defer cleanup(globalEngine, t) - - ch := make(chan struct{}) - go func() { - defer close(ch) - if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil { - t.Fatal(err) - } - }() - - container := waitContainerStart(t, 10*time.Second) - - setTimeout(t, "Reading container's id timed out", 10*time.Second, func() { - buf := make([]byte, 1024) - n, err := stdout.Read(buf) - if err != nil { - t.Fatal(err) - } - - if strings.Trim(string(buf[:n]), " \r\n") != container.ID { - t.Fatalf("Wrong ID received. Expect %s, received %s", container.ID, buf[:n]) - } - }) - setTimeout(t, "Starting container timed out", 10*time.Second, func() { - <-ch - }) - - state := setRaw(t, container) - defer unsetRaw(t, container, state) - - stdout, stdoutPipe = io.Pipe() - cpty, tty, err = pty.Open() - if err != nil { - t.Fatal(err) - } - - cli = client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - - ch = make(chan struct{}) - go func() { - defer close(ch) - if err := cli.CmdAttach(container.ID); err != nil { - if err != io.ErrClosedPipe { - t.Fatal(err) - } - } - }() - - setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil { - if err != io.ErrClosedPipe { - t.Fatal(err) - } - } - }) - - setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { - cpty.Write([]byte{16}) - time.Sleep(100 * time.Millisecond) - cpty.Write([]byte{17}) - }) - - // wait for CmdRun to return - setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() { - <-ch - }) - - closeWrap(cpty, stdout, stdoutPipe) - - time.Sleep(500 * time.Millisecond) - if !container.IsRunning() { - t.Fatal("The detached container should be still running") - } - - setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() { - container.Kill() - }) -} - // TestAttachDetachTruncatedID checks that attach in tty mode can be detached func TestAttachDetachTruncatedID(t *testing.T) { stdout, stdoutPipe := io.Pipe() From 28cda048384ac8913f4912758fe35a673f4f8465 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 23:21:45 -0400 Subject: [PATCH 423/999] Move TestAttachDetachTruncatedID to integration-cli Signed-off-by: Brian Goff --- .../docker_cli_attach_unix_test.go | 73 +++++++++++++++++++ integration/commands_test.go | 73 ------------------- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index bea73d770..ebc3804e3 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -9,6 +9,7 @@ import ( "testing" "time" + "github.com/docker/docker/pkg/stringid" "github.com/kr/pty" ) @@ -213,3 +214,75 @@ func TestAttachDetach(t *testing.T) { logDone("attach - detach") } + +// TestAttachDetachTruncatedID checks that attach in tty mode can be detached +func TestAttachDetachTruncatedID(t *testing.T) { + out, _, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") + id := stringid.TruncateID(strings.TrimSpace(out)) + if err := waitRun(id); err != nil { + t.Fatal(err) + } + + cpty, tty, err := pty.Open() + if err != nil { + t.Fatal(err) + } + defer cpty.Close() + + cmd := exec.Command(dockerBinary, "attach", id) + cmd.Stdin = tty + stdout, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + defer stdout.Close() + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + + if _, err := cpty.Write([]byte("hello\n")); err != nil { + t.Fatal(err) + } + out, err = bufio.NewReader(stdout).ReadString('\n') + if err != nil { + t.Fatal(err) + } + if strings.TrimSpace(out) != "hello" { + t.Fatalf("exepected 'hello', got %q", out) + } + + // escape sequence + if _, err := cpty.Write([]byte{16}); err != nil { + t.Fatal(err) + } + time.Sleep(100 * time.Millisecond) + if _, err := cpty.Write([]byte{17}); err != nil { + t.Fatal(err) + } + + ch := make(chan struct{}) + go func() { + cmd.Wait() + ch <- struct{}{} + }() + + running, err := inspectField(id, "State.Running") + if err != nil { + t.Fatal(err) + } + if running != "true" { + t.Fatal("exepected container to still be running") + } + + go func() { + dockerCmd(t, "kill", id) + }() + + select { + case <-ch: + case <-time.After(10 * time.Millisecond): + t.Fatal("timed out waiting for container to exit") + } + + logDone("attach - detach truncated ID") +} diff --git a/integration/commands_test.go b/integration/commands_test.go index 88a7bb87b..900bdace2 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -12,7 +12,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" "github.com/docker/docker/daemon" - "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/term" "github.com/kr/pty" ) @@ -113,78 +112,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } -// TestAttachDetachTruncatedID checks that attach in tty mode can be detached -func TestAttachDetachTruncatedID(t *testing.T) { - stdout, stdoutPipe := io.Pipe() - cpty, tty, err := pty.Open() - if err != nil { - t.Fatal(err) - } - - cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - defer cleanup(globalEngine, t) - - // Discard the CmdRun output - go stdout.Read(make([]byte, 1024)) - setTimeout(t, "Starting container timed out", 2*time.Second, func() { - if err := cli.CmdRun("-i", "-t", "-d", unitTestImageID, "cat"); err != nil { - t.Fatal(err) - } - }) - - container := waitContainerStart(t, 10*time.Second) - - state := setRaw(t, container) - defer unsetRaw(t, container, state) - - stdout, stdoutPipe = io.Pipe() - cpty, tty, err = pty.Open() - if err != nil { - t.Fatal(err) - } - - cli = client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - - ch := make(chan struct{}) - go func() { - defer close(ch) - if err := cli.CmdAttach(stringid.TruncateID(container.ID)); err != nil { - if err != io.ErrClosedPipe { - t.Fatal(err) - } - } - }() - - setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil { - if err != io.ErrClosedPipe { - t.Fatal(err) - } - } - }) - - setTimeout(t, "Escape sequence timeout", 5*time.Second, func() { - cpty.Write([]byte{16}) - time.Sleep(100 * time.Millisecond) - cpty.Write([]byte{17}) - }) - - // wait for CmdRun to return - setTimeout(t, "Waiting for CmdAttach timed out", 15*time.Second, func() { - <-ch - }) - closeWrap(cpty, stdout, stdoutPipe) - - time.Sleep(500 * time.Millisecond) - if !container.IsRunning() { - t.Fatal("The detached container should be still running") - } - - setTimeout(t, "Waiting for container to die timedout", 5*time.Second, func() { - container.Kill() - }) -} - // Expected behaviour, the process stays alive when the client disconnects func TestAttachDisconnect(t *testing.T) { stdout, stdoutPipe := io.Pipe() From e4cfd9b3924fae0369956b4f0e7f73a7e3b0cbf7 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 23:35:54 -0400 Subject: [PATCH 424/999] MovetAttachDisconnect to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_cli_attach_test.go | 49 +++++++++++++++ integration/commands_test.go | 75 ----------------------- 2 files changed, 49 insertions(+), 75 deletions(-) diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go index cf21cda58..04cb59398 100644 --- a/integration-cli/docker_cli_attach_test.go +++ b/integration-cli/docker_cli_attach_test.go @@ -1,6 +1,7 @@ package main import ( + "bufio" "io" "os/exec" "strings" @@ -134,3 +135,51 @@ func TestAttachTtyWithoutStdin(t *testing.T) { logDone("attach - forbid piped stdin to tty enabled container") } + +func TestAttachDisconnect(t *testing.T) { + defer deleteAllContainers() + out, _, _ := dockerCmd(t, "run", "-di", "busybox", "/bin/cat") + id := strings.TrimSpace(out) + + cmd := exec.Command(dockerBinary, "attach", id) + stdin, err := cmd.StdinPipe() + if err != nil { + t.Fatal(err) + } + defer stdin.Close() + stdout, err := cmd.StdoutPipe() + if err != nil { + t.Fatal(err) + } + defer stdout.Close() + if err := cmd.Start(); err != nil { + t.Fatal(err) + } + defer cmd.Process.Kill() + + if _, err := stdin.Write([]byte("hello\n")); err != nil { + t.Fatal(err) + } + out, err = bufio.NewReader(stdout).ReadString('\n') + if err != nil { + t.Fatal(err) + } + if strings.TrimSpace(out) != "hello" { + t.Fatalf("exepected 'hello', got %q", out) + } + + if err := stdin.Close(); err != nil { + t.Fatal(err) + } + + // Expect container to still be running after stdin is closed + running, err := inspectField(id, "State.Running") + if err != nil { + t.Fatal(err) + } + if running != "true" { + t.Fatal("exepected container to still be running") + } + + logDone("attach - disconnect") +} diff --git a/integration/commands_test.go b/integration/commands_test.go index 900bdace2..02efdbe9a 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -9,11 +9,9 @@ import ( "testing" "time" - "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" "github.com/docker/docker/daemon" "github.com/docker/docker/pkg/term" - "github.com/kr/pty" ) func closeWrap(args ...io.Closer) error { @@ -112,79 +110,6 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error return nil } -// Expected behaviour, the process stays alive when the client disconnects -func TestAttachDisconnect(t *testing.T) { - stdout, stdoutPipe := io.Pipe() - cpty, tty, err := pty.Open() - if err != nil { - t.Fatal(err) - } - - cli := client.NewDockerCli(tty, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - defer cleanup(globalEngine, t) - - go func() { - // Start a process in daemon mode - if err := cli.CmdRun("-d", "-i", unitTestImageID, "/bin/cat"); err != nil { - logrus.Debugf("Error CmdRun: %s", err) - } - }() - - setTimeout(t, "Waiting for CmdRun timed out", 10*time.Second, func() { - if _, err := bufio.NewReader(stdout).ReadString('\n'); err != nil { - t.Fatal(err) - } - }) - - setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() { - for { - l := globalDaemon.List() - if len(l) == 1 && l[0].IsRunning() { - break - } - time.Sleep(10 * time.Millisecond) - } - }) - - container := globalDaemon.List()[0] - - // Attach to it - c1 := make(chan struct{}) - go func() { - // We're simulating a disconnect so the return value doesn't matter. What matters is the - // fact that CmdAttach returns. - cli.CmdAttach(container.ID) - close(c1) - }() - - setTimeout(t, "First read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", "hello", stdout, cpty, 150); err != nil { - t.Fatal(err) - } - }) - // Close pipes (client disconnects) - if err := closeWrap(cpty, stdout, stdoutPipe); err != nil { - t.Fatal(err) - } - - // Wait for attach to finish, the client disconnected, therefore, Attach finished his job - setTimeout(t, "Waiting for CmdAttach timed out", 2*time.Second, func() { - <-c1 - }) - - // We closed stdin, expect /bin/cat to still be running - // Wait a little bit to make sure container.monitor() did his thing - _, err = container.WaitStop(500 * time.Millisecond) - if err == nil || !container.IsRunning() { - t.Fatalf("/bin/cat is not running after closing stdin") - } - - // Try to avoid the timeout in destroy. Best effort, don't check error - cStdin := container.StdinPipe() - cStdin.Close() - container.WaitStop(-1 * time.Second) -} - // Expected behaviour: container gets deleted automatically after exit func TestRunAutoRemove(t *testing.T) { t.Skip("Fixme. Skipping test for now, race condition") From 125747e967e82788df8799622f28a7bcd97a03ad Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 23:37:46 -0400 Subject: [PATCH 425/999] Remove TestRunAutoremove This test is already being skipped, and is also fully tested by `TestRunContainerWithRmFlagExitCodeNotEqualToZero` Signed-off-by: Brian Goff --- integration/commands_test.go | 41 ------------------------------------ 1 file changed, 41 deletions(-) diff --git a/integration/commands_test.go b/integration/commands_test.go index 02efdbe9a..20b88611c 100644 --- a/integration/commands_test.go +++ b/integration/commands_test.go @@ -4,12 +4,10 @@ import ( "bufio" "fmt" "io" - "io/ioutil" "strings" "testing" "time" - "github.com/docker/docker/api/client" "github.com/docker/docker/daemon" "github.com/docker/docker/pkg/term" ) @@ -109,42 +107,3 @@ func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error } return nil } - -// Expected behaviour: container gets deleted automatically after exit -func TestRunAutoRemove(t *testing.T) { - t.Skip("Fixme. Skipping test for now, race condition") - stdout, stdoutPipe := io.Pipe() - - cli := client.NewDockerCli(nil, stdoutPipe, ioutil.Discard, "", testDaemonProto, testDaemonAddr, nil) - defer cleanup(globalEngine, t) - - c := make(chan struct{}) - go func() { - defer close(c) - if err := cli.CmdRun("--rm", unitTestImageID, "hostname"); err != nil { - t.Fatal(err) - } - }() - - var temporaryContainerID string - setTimeout(t, "Reading command output time out", 2*time.Second, func() { - cmdOutput, err := bufio.NewReader(stdout).ReadString('\n') - if err != nil { - t.Fatal(err) - } - temporaryContainerID = cmdOutput - if err := closeWrap(stdout, stdoutPipe); err != nil { - t.Fatal(err) - } - }) - - setTimeout(t, "CmdRun timed out", 10*time.Second, func() { - <-c - }) - - time.Sleep(500 * time.Millisecond) - - if len(globalDaemon.List()) > 0 { - t.Fatalf("failed to remove container automatically: container %s still exists", temporaryContainerID) - } -} From 7d738e0b8c650817b8596e8c12fbe0baa3f045ed Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 10 Apr 2015 23:42:02 -0400 Subject: [PATCH 426/999] Move utils from commands_test.go into utils.go Everything else was gone from this file except these utils which are being used in other files and can't yet be removed. Signed-off-by: Brian Goff --- integration/{commands_test.go => utils.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename integration/{commands_test.go => utils.go} (100%) diff --git a/integration/commands_test.go b/integration/utils.go similarity index 100% rename from integration/commands_test.go rename to integration/utils.go From 77f2a4a0e373225c145966c96d076d6d9f25b3b3 Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Tue, 14 Apr 2015 09:23:26 +0800 Subject: [PATCH 427/999] add TestSearchCmdOptions case Signed-off-by: Yuan Sun --- integration-cli/docker_cli_search_test.go | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/integration-cli/docker_cli_search_test.go b/integration-cli/docker_cli_search_test.go index a3546103f..fcfd9eceb 100644 --- a/integration-cli/docker_cli_search_test.go +++ b/integration-cli/docker_cli_search_test.go @@ -45,3 +45,53 @@ func TestSearchStarsOptionWithWrongParameter(t *testing.T) { logDone("search - Verify search with wrong parameter.") } + +func TestSearchCmdOptions(t *testing.T) { + testRequires(t, Network) + searchCmdhelp := exec.Command(dockerBinary, "search", "--help") + out, exitCode, err := runCommandWithOutput(searchCmdhelp) + if err != nil || exitCode != 0 { + t.Fatalf("failed to get search help information: %s, %v", out, err) + } + + if !strings.Contains(out, "Usage: docker search [OPTIONS] TERM") { + t.Fatalf("failed to show docker search usage: %s, %v", out, err) + } + + searchCmd := exec.Command(dockerBinary, "search", "busybox") + outSearchCmd, exitCode, err := runCommandWithOutput(searchCmd) + if err != nil || exitCode != 0 { + t.Fatalf("failed to search on the central registry: %s, %v", outSearchCmd, err) + } + + searchCmdautomated := exec.Command(dockerBinary, "search", "--automated=true", "busybox") + outSearchCmdautomated, exitCode, err := runCommandWithOutput(searchCmdautomated) //The busybox is a busybox base image, not an AUTOMATED image. + if err != nil || exitCode != 0 { + t.Fatalf("failed to search with automated=true on the central registry: %s, %v", outSearchCmdautomated, err) + } + + outSearchCmdautomatedSlice := strings.Split(outSearchCmdautomated, "\n") + for i := range outSearchCmdautomatedSlice { + if strings.HasPrefix(outSearchCmdautomatedSlice[i], "busybox ") { + t.Fatalf("The busybox is not an AUTOMATED image: %s, %v", out, err) + } + } + + searchCmdStars := exec.Command(dockerBinary, "search", "-s=2", "busybox") + outSearchCmdStars, exitCode, err := runCommandWithOutput(searchCmdStars) + if err != nil || exitCode != 0 { + t.Fatalf("failed to search with stars=2 on the central registry: %s, %v", outSearchCmdStars, err) + } + + if strings.Count(outSearchCmdStars, "[OK]") > strings.Count(outSearchCmd, "[OK]") { + t.Fatalf("The quantity of images with stars should be less than that of all images: %s, %v", outSearchCmdStars, err) + } + + searchCmdOptions := exec.Command(dockerBinary, "search", "--stars=2", "--automated=true", "--no-trunc=true", "busybox") + out, exitCode, err = runCommandWithOutput(searchCmdOptions) + if err != nil || exitCode != 0 { + t.Fatalf("failed to search with stars&automated&no-trunc options on the central registry: %s, %v", out, err) + } + + logDone("search - have a try for search options.") +} From bcd5e20a094e63093a95840f4f3342d981752708 Mon Sep 17 00:00:00 2001 From: Tatsushi Inagaki Date: Wed, 8 Apr 2015 04:41:03 -0400 Subject: [PATCH 428/999] Enable "netgo" library when we build a static binary with gccgo Signed-off-by: Tatsushi Inagaki --- hack/make/.dockerinit-gccgo | 1 + hack/make/gccgo | 3 +++ 2 files changed, 4 insertions(+) diff --git a/hack/make/.dockerinit-gccgo b/hack/make/.dockerinit-gccgo index 592a4152c..50854b401 100644 --- a/hack/make/.dockerinit-gccgo +++ b/hack/make/.dockerinit-gccgo @@ -12,6 +12,7 @@ go build --compiler=gccgo \ -g -Wl,--no-export-dynamic $EXTLDFLAGS_STATIC_DOCKER + -lnetgo " \ ./dockerinit diff --git a/hack/make/gccgo b/hack/make/gccgo index c85d2fbda..c3e9a228b 100644 --- a/hack/make/gccgo +++ b/hack/make/gccgo @@ -8,6 +8,9 @@ BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION" source "$(dirname "$BASH_SOURCE")/.go-autogen" +if [[ "${BUILDFLAGS[@]}" =~ 'netgo ' ]]; then + EXTLDFLAGS_STATIC_DOCKER+=' -lnetgo' +fi go build -compiler=gccgo \ -o "$DEST/$BINARY_FULLNAME" \ "${BUILDFLAGS[@]}" \ From b68e161e5b76b5f622cf4fc226df46cbe314ea1e Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Wed, 1 Apr 2015 14:12:15 -0400 Subject: [PATCH 429/999] graphdriver: prefer prior driver state Before this, a storage driver would be defaulted to based on the priority list, and only print a warning if there is state from other drivers. This meant a reordering of priority list would "break" users in an upgrade of docker, such that there images in the prior driver's state were now invisible. With this change, prior state is scanned, and if present that driver is preferred. As such, we can reorder the priority list, and after an upgrade, existing installs with prior drivers can have a contiguous experience, while fresh installs may default to a driver in the new priority list. Ref: https://github.com/docker/docker/pull/11962#issuecomment-88274858 Signed-off-by: Vincent Batts --- daemon/graphdriver/driver.go | 55 ++++++++++++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 6 deletions(-) diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 79e6b72de..0260e532d 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -146,10 +146,40 @@ func GetDriver(name, home string, options []string) (Driver, error) { func New(root string, options []string) (driver Driver, err error) { for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} { if name != "" { + logrus.Infof("[graphdriver] trying provided driver %q", name) // so the logs show specified driver return GetDriver(name, root, options) } } + // Guess for prior driver + priorDrivers := scanPriorDrivers(root) + for _, name := range priority { + if name == "vfs" { + // don't use vfs even if there is state present. + continue + } + for _, prior := range priorDrivers { + // of the state found from prior drivers, check in order of our priority + // which we would prefer + if prior == name { + driver, err = GetDriver(name, root, options) + if err != nil { + // unlike below, we will return error here, because there is prior + // state, and now it is no longer supported/prereq/compatible, so + // something changed and needs attention. Otherwise the daemon's + // images would just "disappear". + logrus.Errorf("[graphdriver] prior storage driver %q failed: %s", name, err) + return nil, err + } + if err := checkPriorDriver(name, root); err != nil { + return nil, err + } + logrus.Infof("[graphdriver] using prior storage driver %q", name) + return driver, nil + } + } + } + // Check for priority drivers first for _, name := range priority { driver, err = GetDriver(name, root, options) @@ -159,34 +189,47 @@ func New(root string, options []string) (driver Driver, err error) { } return nil, err } - checkPriorDriver(name, root) return driver, nil } // Check all registered drivers if no priority driver is found - for name, initFunc := range drivers { + for _, initFunc := range drivers { if driver, err = initFunc(root, options); err != nil { if err == ErrNotSupported || err == ErrPrerequisites || err == ErrIncompatibleFS { continue } return nil, err } - checkPriorDriver(name, root) return driver, nil } return nil, fmt.Errorf("No supported storage backend found") } -func checkPriorDriver(name, root string) { +// scanPriorDrivers returns an un-ordered scan of directories of prior storage drivers +func scanPriorDrivers(root string) []string { priorDrivers := []string{} - for prior := range drivers { + for driver := range drivers { + p := path.Join(root, driver) + if _, err := os.Stat(p); err == nil { + priorDrivers = append(priorDrivers, driver) + } + } + return priorDrivers +} + +func checkPriorDriver(name, root string) error { + priorDrivers := []string{} + for _, prior := range scanPriorDrivers(root) { if prior != name && prior != "vfs" { if _, err := os.Stat(path.Join(root, prior)); err == nil { priorDrivers = append(priorDrivers, prior) } } } + if len(priorDrivers) > 0 { - logrus.Warnf("Graphdriver %s selected. Your graphdriver directory %s already contains data managed by other graphdrivers: %s", name, root, strings.Join(priorDrivers, ",")) + + return errors.New(fmt.Sprintf("%q contains other graphdrivers: %s; Please cleanup or explicitly choose storage driver (-s )", root, strings.Join(priorDrivers, ","))) } + return nil } From 25fab69f7dcf72dabfeb60f61f50f175692f0b03 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Wed, 8 Apr 2015 18:09:55 -0700 Subject: [PATCH 430/999] names-generator: use local random instance Instead of seeding/polluting the global random instance, creating a local `rand.Random` instance which provides the same level of randomness. Signed-off-by: Ahmet Alp Balkan --- pkg/namesgenerator/names-generator.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/namesgenerator/names-generator.go b/pkg/namesgenerator/names-generator.go index 38dced39f..b33e8c21b 100644 --- a/pkg/namesgenerator/names-generator.go +++ b/pkg/namesgenerator/names-generator.go @@ -302,19 +302,19 @@ var ( // Ada Yonath - an Israeli crystallographer, the first woman from the Middle East to win a Nobel prize in the sciences. https://en.wikipedia.org/wiki/Ada_Yonath "yonath", } + + rnd = rand.New(rand.NewSource(time.Now().UnixNano())) ) func GetRandomName(retry int) string { - rand.Seed(time.Now().UnixNano()) - begin: - name := fmt.Sprintf("%s_%s", left[rand.Intn(len(left))], right[rand.Intn(len(right))]) + name := fmt.Sprintf("%s_%s", left[rnd.Intn(len(left))], right[rnd.Intn(len(right))]) if name == "boring_wozniak" /* Steve Wozniak is not boring */ { goto begin } if retry > 0 { - name = fmt.Sprintf("%s%d", name, rand.Intn(10)) + name = fmt.Sprintf("%s%d", name, rnd.Intn(10)) } return name } From 67df8e4257c165bc6abe77bb8f4ab55c10b2fbff Mon Sep 17 00:00:00 2001 From: Yestin Sun Date: Sat, 11 Apr 2015 22:12:40 -0700 Subject: [PATCH 431/999] Improve test accuracy for pkg/chrootarchive (part 2) Check test correctness of untar by comparing destination with source. For part 2, it checkes hashes of source and destination files or the target files of symbolic links. This is a supplement to the #11601 fix. Signed-off-by: Yestin Sun --- pkg/chrootarchive/archive_test.go | 38 +++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/pkg/chrootarchive/archive_test.go b/pkg/chrootarchive/archive_test.go index b381d4301..f9b5b0970 100644 --- a/pkg/chrootarchive/archive_test.go +++ b/pkg/chrootarchive/archive_test.go @@ -3,6 +3,7 @@ package chrootarchive import ( "bytes" "fmt" + "hash/crc32" "io" "io/ioutil" "os" @@ -113,6 +114,16 @@ func prepareSourceDirectory(numberOfFiles int, targetPath string, makeSymLinks b return totalSize, nil } +func getHash(filename string) (uint32, error) { + stream, err := ioutil.ReadFile(filename) + if err != nil { + return 0, err + } + hash := crc32.NewIEEE() + hash.Write(stream) + return hash.Sum32(), nil +} + func compareDirectories(src string, dest string) error { changes, err := archive.ChangesDirs(dest, src) if err != nil { @@ -124,6 +135,21 @@ func compareDirectories(src string, dest string) error { return nil } +func compareFiles(src string, dest string) error { + srcHash, err := getHash(src) + if err != nil { + return err + } + destHash, err := getHash(dest) + if err != nil { + return err + } + if srcHash != destHash { + return fmt.Errorf("%s is different from %s", src, dest) + } + return nil +} + func TestChrootTarUntarWithSymlink(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-TestChrootTarUntarWithSymlink") if err != nil { @@ -176,6 +202,9 @@ func TestChrootCopyWithTar(t *testing.T) { if err := CopyWithTar(srcfile, destfile); err != nil { t.Fatal(err) } + if err := compareFiles(srcfile, destfile); err != nil { + t.Fatal(err) + } // Copy symbolic link srcLinkfile := filepath.Join(src, "file-1-link") @@ -184,6 +213,9 @@ func TestChrootCopyWithTar(t *testing.T) { if err := CopyWithTar(srcLinkfile, destLinkfile); err != nil { t.Fatal(err) } + if err := compareFiles(srcLinkfile, destLinkfile); err != nil { + t.Fatal(err) + } } func TestChrootCopyFileWithTar(t *testing.T) { @@ -213,6 +245,9 @@ func TestChrootCopyFileWithTar(t *testing.T) { if err := CopyFileWithTar(srcfile, destfile); err != nil { t.Fatal(err) } + if err := compareFiles(srcfile, destfile); err != nil { + t.Fatal(err) + } // Copy symbolic link srcLinkfile := filepath.Join(src, "file-1-link") @@ -221,6 +256,9 @@ func TestChrootCopyFileWithTar(t *testing.T) { if err := CopyFileWithTar(srcLinkfile, destLinkfile); err != nil { t.Fatal(err) } + if err := compareFiles(srcLinkfile, destLinkfile); err != nil { + t.Fatal(err) + } } func TestChrootUntarPath(t *testing.T) { From 531433e7650c5d33ff6580d7de28a093a504ac6c Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Tue, 14 Apr 2015 08:00:46 +0000 Subject: [PATCH 432/999] integ-cli: Use status code from sockRequest (fix #12335) sockRequest now makes the status code available in the returned values. This helps avoid string checking for non-HttpStatusOK(=200) yet successful error messages. Signed-off-by: Ahmet Alp Balkan --- integration-cli/docker_api_containers_test.go | 22 +++++++++---------- integration-cli/docker_cli_rm_test.go | 6 ++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 9ca2ebc86..d48b94572 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "io" + "net/http" "os/exec" "strings" "testing" @@ -134,7 +135,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) { "Volumes": map[string]struct{}{"/tmp": {}}, } - if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { t.Fatal(err) } @@ -142,7 +143,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) { config = map[string]interface{}{ "Binds": []string{bindPath + ":/tmp"}, } - if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { t.Fatal(err) } @@ -167,7 +168,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) { "Volumes": map[string]struct{}{"/tmp": {}}, } - if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { t.Fatal(err) } @@ -202,14 +203,14 @@ func TestContainerApiStartVolumesFrom(t *testing.T) { "Volumes": map[string]struct{}{volPath: {}}, } - if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { t.Fatal(err) } config = map[string]interface{}{ "VolumesFrom": []string{volName}, } - if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { t.Fatal(err) } @@ -246,7 +247,7 @@ func TestVolumesFromHasPriority(t *testing.T) { "Volumes": map[string]struct{}{volPath: {}}, } - if _, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") { + if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { t.Fatal(err) } @@ -255,7 +256,7 @@ func TestVolumesFromHasPriority(t *testing.T) { "VolumesFrom": []string{volName}, "Binds": []string{bindPath + ":/tmp"}, } - if _, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { t.Fatal(err) } @@ -538,8 +539,7 @@ func TestPostContainerBindNormalVolume(t *testing.T) { } bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}} - _, _, err = sockRequest("POST", "/containers/two/start", bindSpec) - if err != nil && !strings.Contains(err.Error(), "204 No Content") { + if status, _, err := sockRequest("POST", "/containers/two/start", bindSpec); err != nil && status != http.StatusNoContent { t.Fatal(err) } @@ -566,7 +566,7 @@ func TestContainerApiPause(t *testing.T) { } ContainerID := strings.TrimSpace(out) - if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && status != http.StatusNoContent { t.Fatalf("POST a container pause: sockRequest failed: %v", err) } @@ -580,7 +580,7 @@ func TestContainerApiPause(t *testing.T) { t.Fatalf("there should be one paused container and not %d", len(pausedContainers)) } - if _, _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && !strings.Contains(err.Error(), "204 No Content") { + if status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && status != http.StatusNoContent { t.Fatalf("POST a container pause: sockRequest failed: %v", err) } diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index 56eb2e525..5f9a5dda5 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -1,6 +1,7 @@ package main import ( + "net/http" "os" "os/exec" "strings" @@ -64,12 +65,11 @@ func TestRmRunningContainerCheckError409(t *testing.T) { createRunningContainer(t, "foo") endpoint := "/containers/foo" - _, _, err := sockRequest("DELETE", endpoint, nil) + status, _, err := sockRequest("DELETE", endpoint, nil) if err == nil { t.Fatalf("Expected error, can't rm a running container") - } - if !strings.Contains(err.Error(), "409 Conflict") { + } else if status != http.StatusConflict { t.Fatalf("Expected error to contain '409 Conflict' but found %s", err) } From c89ddb6d015692e38a9b1a3223c406ddb5409758 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Tue, 14 Apr 2015 10:53:37 +0100 Subject: [PATCH 433/999] Add -f option and explanation to push after rebase Signed-off-by: Bryan Boreham --- docs/sources/project/work-issue.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sources/project/work-issue.md b/docs/sources/project/work-issue.md index 1719195cd..32e21d5e8 100644 --- a/docs/sources/project/work-issue.md +++ b/docs/sources/project/work-issue.md @@ -191,9 +191,10 @@ You should pull and rebase frequently as you work. Make sure you include your signature. -8. Push any changes to your fork on GitHub. +8. Push any changes to your fork on GitHub, using the `-f` option to +force the previous change to be overwritten. - $ git push origin 11038-fix-rhel-link + $ git push -f origin 11038-fix-rhel-link ## Where to go next From d791f7e9f81010299130ae8816ebe58667ea6b09 Mon Sep 17 00:00:00 2001 From: wonderflow Date: Tue, 14 Apr 2015 18:34:20 +0800 Subject: [PATCH 434/999] fix memory stats display document Signed-off-by: Sun Jianbo --- docs/man/docker-stats.1.md | 2 +- docs/sources/reference/commandline/cli.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/man/docker-stats.1.md b/docs/man/docker-stats.1.md index 4cf7a66df..f6fc3f7f2 100644 --- a/docs/man/docker-stats.1.md +++ b/docs/man/docker-stats.1.md @@ -23,6 +23,6 @@ Run **docker stats** with multiple containers. $ docker stats redis1 redis2 CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O - redis1 0.07% 796 KiB/64 MiB 1.21% 788 B/648 B + redis1 0.07% 796 KB/64 MB 1.21% 788 B/648 B redis2 0.07% 2.746 MB/64 MB 4.29% 1.266 KB/648 B diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index e607120c8..737526123 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -2343,7 +2343,7 @@ Running `docker stats` on multiple containers $ docker stats redis1 redis2 CONTAINER CPU % MEM USAGE/LIMIT MEM % NET I/O - redis1 0.07% 796 KiB/64 MiB 1.21% 788 B/648 B + redis1 0.07% 796 KB/64 MB 1.21% 788 B/648 B redis2 0.07% 2.746 MB/64 MB 4.29% 1.266 KB/648 B From fc20658a01e362a5bb484b439a0a1004c51f9ff5 Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Tue, 14 Apr 2015 09:13:50 -0700 Subject: [PATCH 435/999] Fix vet warning in archive.go Signed-off-by: Megan Kostick --- pkg/chrootarchive/archive.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/chrootarchive/archive.go b/pkg/chrootarchive/archive.go index a1454f7b9..49d19175d 100644 --- a/pkg/chrootarchive/archive.go +++ b/pkg/chrootarchive/archive.go @@ -82,9 +82,9 @@ func Untar(tarArchive io.Reader, dest string, options *archive.TarOptions) error cmd := reexec.Command("docker-untar", dest) cmd.Stdin = decompressedArchive cmd.ExtraFiles = append(cmd.ExtraFiles, r) - var output bytes.Buffer - cmd.Stdout = &output - cmd.Stderr = &output + output := bytes.NewBuffer(nil) + cmd.Stdout = output + cmd.Stderr = output if err := cmd.Start(); err != nil { return fmt.Errorf("Untar error on re-exec cmd: %v", err) From a0804e8e118510dc49e7e74273a323e99c097a3d Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Wed, 15 Apr 2015 00:29:53 +0800 Subject: [PATCH 436/999] Use local variable err instead of a outer one Signed-off-by: Hu Keping --- api/server/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 7ed3abc88..b8ac0e937 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -506,8 +506,7 @@ func getContainersTop(eng *engine.Engine, version version.Version, w http.Respon } func getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - var err error - if err = parseForm(r); err != nil { + if err := parseForm(r); err != nil { return err } @@ -520,10 +519,11 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo } if tmpLimit := r.Form.Get("limit"); tmpLimit != "" { - config.Limit, err = strconv.Atoi(tmpLimit) + limit, err := strconv.Atoi(tmpLimit) if err != nil { return err } + config.Limit = limit } containers, err := getDaemon(eng).Containers(config) From 736824ccc134a90740c2ae95034b4c5c506594c7 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 13 Apr 2015 19:26:04 -0700 Subject: [PATCH 437/999] change go tools to use certain commit Signed-off-by: Jessica Frazelle --- Dockerfile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3cf5eb5ce..b1c7c4a6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -102,12 +102,15 @@ RUN cd /usr/local/go/src \ ENV GOFMT_VERSION 1.3.3 RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt +# Update this sha when we upgrade to go 1.5.0 +ENV GO_TOOLS_COMMIT 069d2f3bcb68257b627205f0486d6cc69a231ff9 # Grab Go's cover tool for dead-simple code coverage testing -RUN go get golang.org/x/tools/cmd/cover - # Grab Go's vet tool for examining go code to find suspicious constructs # and help prevent errors that the compiler might not catch -RUN go get golang.org/x/tools/cmd/vet +RUN git clone https://github.com/golang/tools.git /go/src/golang.org/x/tools \ + && (cd /go/src/golang.org/x/tools && git checkout -q $GO_TOOLS_COMMIT) \ + && go install -v golang.org/x/tools/cmd/cover \ + && go install -v golang.org/x/tools/cmd/vet # TODO replace FPM with some very minimal debhelper stuff RUN gem install --no-rdoc --no-ri fpm --version 1.3.2 From 4dd4bd00aa8285b3fcfca78322d4d867c65f7a50 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Tue, 14 Apr 2015 10:09:00 -0700 Subject: [PATCH 438/999] Fixing changed pushed without edit Signed-off-by: Mary Anthony --- docs/sources/project/work-issue.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/sources/project/work-issue.md b/docs/sources/project/work-issue.md index 32e21d5e8..6433c42a6 100644 --- a/docs/sources/project/work-issue.md +++ b/docs/sources/project/work-issue.md @@ -191,8 +191,10 @@ You should pull and rebase frequently as you work. Make sure you include your signature. -8. Push any changes to your fork on GitHub, using the `-f` option to -force the previous change to be overwritten. +8. Push any changes to your fork on GitHub. + + The rebase rewrote history, so you'll need to use the `-f` or `--force` flag + to push your change. $ git push -f origin 11038-fix-rhel-link From 69747b3c1e254d78619b2d66340f5d5172280d90 Mon Sep 17 00:00:00 2001 From: buddhamagnet Date: Tue, 14 Apr 2015 14:14:33 +0100 Subject: [PATCH 439/999] add docs for DockerCli and NewDockerCli Signed-off-by: buddhamagnet --- api/client/cli.go | 49 +++++++++++++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/api/client/cli.go b/api/client/cli.go index 01b5f2e63..dfa5fe520 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -20,25 +20,38 @@ import ( "github.com/docker/docker/registry" ) +// DockerCli represents the docker command line client. +// Instances of the client can be returned from NewDockerCli. type DockerCli struct { - proto string - addr string + // proto holds the client protocol i.e. unix. + proto string + // addr holds the client address. + addr string + // configFile holds the configuration file (instance of registry.ConfigFile). configFile *registry.ConfigFile - in io.ReadCloser - out io.Writer - err io.Writer - keyFile string - tlsConfig *tls.Config - scheme string - // inFd holds file descriptor of the client's STDIN, if it's a valid file + // in holds the input stream and closer (io.ReadCloser) for the client. + in io.ReadCloser + // out holds the output stream (io.Writer) for the client. + out io.Writer + // err holds the error stream (io.Writer) for the client. + err io.Writer + // keyFile holds the key file as a string. + keyFile string + // tlsConfig holds the TLS configuration for the client, and will + // set the scheme to https in NewDockerCli if present. + tlsConfig *tls.Config + // scheme holds the scheme of the client i.e. https. + scheme string + // inFd holds the file descriptor of the client's STDIN (if valid). inFd uintptr - // outFd holds file descriptor of the client's STDOUT, if it's a valid file + // outFd holds file descriptor of the client's STDOUT (if valid). outFd uintptr - // isTerminalIn describes if client's STDIN is a TTY + // isTerminalIn indicates whether the client's STDIN is a TTY isTerminalIn bool - // isTerminalOut describes if client's STDOUT is a TTY + // isTerminalOut dindicates whether the client's STDOUT is a TTY isTerminalOut bool - transport *http.Transport + // transport holds the client transport instance. + transport *http.Transport } var funcMap = template.FuncMap{ @@ -125,6 +138,10 @@ func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { return nil } +// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. +// The key file, protocol (i.e. unix) and address are passed in as strings, along with the tls.Config. If the tls.Config +// is set the client scheme will be set to https. +// The client will be given a 32-second timeout (see https://github.com/docker/docker/pull/8035). func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, addr string, tlsConfig *tls.Config) *DockerCli { var ( inFd uintptr @@ -149,15 +166,15 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a err = out } - // The transport is created here for reuse during the client session + // The transport is created here for reuse during the client session. tr := &http.Transport{ TLSClientConfig: tlsConfig, } - // Why 32? See issue 8035 + // Why 32? See https://github.com/docker/docker/pull/8035. timeout := 32 * time.Second if proto == "unix" { - // no need in compressing for local communications + // No need for compression in local communications. tr.DisableCompression = true tr.Dial = func(_, _ string) (net.Conn, error) { return net.DialTimeout(proto, addr, timeout) From 98f857772f3be0985ab87c4bc6bd91e80e122c53 Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Fri, 10 Apr 2015 09:53:40 -0700 Subject: [PATCH 440/999] Remove container name not empty check Signed-off-by: Megan Kostick --- api/client/diff.go | 4 ++++ api/client/rm.go | 4 ++++ api/server/server.go | 12 ++++-------- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/api/client/diff.go b/api/client/diff.go index 3f6c28384..a22734d04 100644 --- a/api/client/diff.go +++ b/api/client/diff.go @@ -21,6 +21,10 @@ func (cli *DockerCli) CmdDiff(args ...string) error { cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) + if cmd.Arg(0) == "" { + return fmt.Errorf("Container name cannot be empty") + } + rdr, _, err := cli.call("GET", "/containers/"+cmd.Arg(0)+"/changes", nil, nil) if err != nil { return err diff --git a/api/client/rm.go b/api/client/rm.go index 89b118254..1d49e98a8 100644 --- a/api/client/rm.go +++ b/api/client/rm.go @@ -30,6 +30,10 @@ func (cli *DockerCli) CmdRm(args ...string) error { var encounteredError error for _, name := range cmd.Args() { + if name == "" { + return fmt.Errorf("Container name cannot be empty") + } + _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) diff --git a/api/server/server.go b/api/server/server.go index 7ed3abc88..61eee110e 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -466,10 +466,6 @@ func getContainersChanges(eng *engine.Engine, version version.Version, w http.Re } name := vars["name"] - if name == "" { - return fmt.Errorf("Container name cannot be empty") - } - d := getDaemon(eng) cont, err := d.Get(name) if err != nil { @@ -883,10 +879,6 @@ func deleteContainers(eng *engine.Engine, version version.Version, w http.Respon } name := vars["name"] - if name == "" { - return fmt.Errorf("Container name cannot be empty") - } - d := getDaemon(eng) config := &daemon.ContainerRmConfig{ ForceRemove: toBool(r.Form.Get("force")), @@ -895,6 +887,10 @@ func deleteContainers(eng *engine.Engine, version version.Version, w http.Respon } if err := d.ContainerRm(name, config); err != nil { + // Force a 404 for the empty string + if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") { + return fmt.Errorf("no such id: \"\"") + } return err } From 18a8bcf0720b394e22e28b1389589f1ebbdc5cfc Mon Sep 17 00:00:00 2001 From: Raghuram Devarakonda Date: Tue, 14 Apr 2015 15:11:35 -0400 Subject: [PATCH 441/999] Improve the git instructions to update a PR. Signed-off-by: Raghuram Devarakonda --- docs/sources/project/review-pr.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/docs/sources/project/review-pr.md b/docs/sources/project/review-pr.md index e8cb6c7c0..3d77ea406 100644 --- a/docs/sources/project/review-pr.md +++ b/docs/sources/project/review-pr.md @@ -49,15 +49,23 @@ need to update your pull request with additional changes. To update your existing pull request: -1. Change one or more files in your local `docker-fork` repository. +1. Checkout the PR branch in your local `docker-fork` repository. -2. Commit the change with the `git commit --amend` command. + This is the branch associated with your request. + +2. Change one or more files and then stage your changes. + + The command syntax is: + + git add + +3. Commit the change. $ git commit --amend Git opens an editor containing your last commit message. -3. Adjust your last comment to reflect this new change. +4. Adjust your last comment to reflect this new change. Added a new sentence per Anaud's suggestion @@ -72,15 +80,17 @@ To update your existing pull request: # modified: docs/sources/installation/mac.md # modified: docs/sources/installation/rhel.md -4. Push to your origin. +5. Force push the change to your origin. - $ git push origin + The command syntax is: -5. Open your browser to your pull request on GitHub. + git push -f origin + +6. Open your browser to your pull request on GitHub. You should see your pull request now contains your newly pushed code. -6. Add a comment to your pull request. +7. Add a comment to your pull request. GitHub only notifies PR participants when you comment. For example, you can mention that you updated your PR. Your comment alerts the maintainers that From 63331abbcadee3528f3e03f96cff1ca6a506cc9e Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 14 Apr 2015 15:17:17 -0400 Subject: [PATCH 442/999] remove integration/utils setRaw funcs Signed-off-by: Brian Goff --- daemon/container.go | 9 --------- integration/utils.go | 21 --------------------- 2 files changed, 30 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index e831f07a5..7cc874f67 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -14,7 +14,6 @@ import ( "syscall" "time" - "github.com/docker/libcontainer" "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/devices" "github.com/docker/libcontainer/label" @@ -1020,14 +1019,6 @@ func (container *Container) Exposes(p nat.Port) bool { return exists } -func (container *Container) GetPtyMaster() (libcontainer.Console, error) { - ttyConsole, ok := container.command.ProcessConfig.Terminal.(execdriver.TtyTerminal) - if !ok { - return nil, ErrNoTTY - } - return ttyConsole.Master(), nil -} - func (container *Container) HostConfig() *runconfig.HostConfig { container.Lock() res := container.hostConfig diff --git a/integration/utils.go b/integration/utils.go index 20b88611c..1d27cd6e4 100644 --- a/integration/utils.go +++ b/integration/utils.go @@ -9,7 +9,6 @@ import ( "time" "github.com/docker/docker/daemon" - "github.com/docker/docker/pkg/term" ) func closeWrap(args ...io.Closer) error { @@ -27,26 +26,6 @@ func closeWrap(args ...io.Closer) error { return nil } -func setRaw(t *testing.T, c *daemon.Container) *term.State { - pty, err := c.GetPtyMaster() - if err != nil { - t.Fatal(err) - } - state, err := term.MakeRaw(pty.Fd()) - if err != nil { - t.Fatal(err) - } - return state -} - -func unsetRaw(t *testing.T, c *daemon.Container, state *term.State) { - pty, err := c.GetPtyMaster() - if err != nil { - t.Fatal(err) - } - term.RestoreTerminal(pty.Fd(), state) -} - func waitContainerStart(t *testing.T, timeout time.Duration) *daemon.Container { var container *daemon.Container From 8ce42baaef314a75bb6726891774393d540e9d06 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 24 Feb 2015 17:17:13 -0500 Subject: [PATCH 443/999] Make `docker cp` bind-mount volumes Allows `docker cp` to work seamlessly, and a lot more cleanly. Signed-off-by: Brian Goff --- daemon/container.go | 41 ++++++++++---------- daemon/volumes.go | 94 +++++++++++++++++++++++++++++++++------------ volumes/volume.go | 32 --------------- 3 files changed, 90 insertions(+), 77 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 228944d2d..f144934c3 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -970,37 +970,35 @@ func (container *Container) GetSize() (int64, int64) { } func (container *Container) Copy(resource string) (io.ReadCloser, error) { + container.Lock() + defer container.Unlock() + var err error if err := container.Mount(); err != nil { return nil, err } + defer func() { + if err != nil { + container.Unmount() + } + }() + + if err = container.mountVolumes(); err != nil { + container.unmountVolumes() + return nil, err + } + defer func() { + if err != nil { + container.unmountVolumes() + } + }() basePath, err := container.getResourcePath(resource) if err != nil { - container.Unmount() return nil, err } - // Check if this is actually in a volume - for _, mnt := range container.VolumeMounts() { - if len(mnt.MountToPath) > 0 && strings.HasPrefix(resource, mnt.MountToPath[1:]) { - return mnt.Export(resource) - } - } - - // Check if this is a special one (resolv.conf, hostname, ..) - if resource == "etc/resolv.conf" { - basePath = container.ResolvConfPath - } - if resource == "etc/hostname" { - basePath = container.HostnamePath - } - if resource == "etc/hosts" { - basePath = container.HostsPath - } - stat, err := os.Stat(basePath) if err != nil { - container.Unmount() return nil, err } var filter []string @@ -1018,11 +1016,12 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { IncludeFiles: filter, }) if err != nil { - container.Unmount() return nil, err } + return ioutils.NewReadCloserWrapper(archive, func() error { err := archive.Close() + container.unmountVolumes() container.Unmount() return err }), diff --git a/daemon/volumes.go b/daemon/volumes.go index f40fdd3e4..7c6696d65 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -2,7 +2,6 @@ package daemon import ( "fmt" - "io" "io/ioutil" "os" "path/filepath" @@ -12,6 +11,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/pkg/chrootarchive" + "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" "github.com/docker/docker/volumes" @@ -27,18 +27,6 @@ type Mount struct { isBind bool } -func (mnt *Mount) Export(resource string) (io.ReadCloser, error) { - var name string - if resource == mnt.MountToPath[1:] { - name = filepath.Base(resource) - } - path, err := filepath.Rel(mnt.MountToPath[1:], resource) - if err != nil { - return nil, err - } - return mnt.volume.Export(path, name) -} - func (container *Container) prepareVolumes() error { if container.Volumes == nil || len(container.Volumes) == 0 { container.Volumes = make(map[string]string) @@ -320,6 +308,20 @@ func validMountMode(mode string) bool { return validModes[mode] } +func (container *Container) specialMounts() []execdriver.Mount { + var mounts []execdriver.Mount + if container.ResolvConfPath != "" { + mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true}) + } + if container.HostnamePath != "" { + mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true}) + } + if container.HostsPath != "" { + mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true}) + } + return mounts +} + func (container *Container) setupMounts() error { mounts := []execdriver.Mount{} @@ -336,17 +338,7 @@ func (container *Container) setupMounts() error { }) } - if container.ResolvConfPath != "" { - mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true}) - } - - if container.HostnamePath != "" { - mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true}) - } - - if container.HostsPath != "" { - mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true}) - } + mounts = append(mounts, container.specialMounts()...) container.command.Mounts = mounts return nil @@ -401,3 +393,57 @@ func copyOwnership(source, destination string) error { return os.Chmod(destination, os.FileMode(stat.Mode())) } + +func (container *Container) mountVolumes() error { + for dest, source := range container.Volumes { + v := container.daemon.volumes.Get(source) + if v == nil { + return fmt.Errorf("could not find volume for %s:%s, impossible to mount", source, dest) + } + + destPath, err := container.getResourcePath(dest) + if err != nil { + return err + } + + if err := mount.Mount(source, destPath, "bind", "rbind,rw"); err != nil { + return fmt.Errorf("error while mounting volume %s: %v", source, err) + } + } + + for _, mnt := range container.specialMounts() { + destPath, err := container.getResourcePath(mnt.Destination) + if err != nil { + return err + } + if err := mount.Mount(mnt.Source, destPath, "bind", "bind,rw"); err != nil { + return fmt.Errorf("error while mounting volume %s: %v", mnt.Source, err) + } + } + return nil +} + +func (container *Container) unmountVolumes() { + for dest := range container.Volumes { + destPath, err := container.getResourcePath(dest) + if err != nil { + logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) + continue + } + if err := mount.ForceUnmount(destPath); err != nil { + logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) + continue + } + } + + for _, mnt := range container.specialMounts() { + destPath, err := container.getResourcePath(mnt.Destination) + if err != nil { + logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) + continue + } + if err := mount.ForceUnmount(destPath); err != nil { + logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) + } + } +} diff --git a/volumes/volume.go b/volumes/volume.go index c5191c48c..0acc3068f 100644 --- a/volumes/volume.go +++ b/volumes/volume.go @@ -2,14 +2,11 @@ package volumes import ( "encoding/json" - "io" "io/ioutil" "os" - "path" "path/filepath" "sync" - "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/symlink" ) @@ -24,35 +21,6 @@ type Volume struct { lock sync.Mutex } -func (v *Volume) Export(resource, name string) (io.ReadCloser, error) { - if v.IsBindMount && filepath.Base(resource) == name { - name = "" - } - - basePath, err := v.getResourcePath(resource) - if err != nil { - return nil, err - } - stat, err := os.Stat(basePath) - if err != nil { - return nil, err - } - var filter []string - if !stat.IsDir() { - d, f := path.Split(basePath) - basePath = d - filter = []string{f} - } else { - filter = []string{path.Base(basePath)} - basePath = path.Dir(basePath) - } - return archive.TarWithOptions(basePath, &archive.TarOptions{ - Compression: archive.Uncompressed, - Name: name, - IncludeFiles: filter, - }) -} - func (v *Volume) IsDir() (bool, error) { stat, err := os.Stat(v.Path) if err != nil { From 2ea1febd9aafd94985a2430c35243e539fa29f53 Mon Sep 17 00:00:00 2001 From: Thomas Texier Date: Tue, 14 Apr 2015 16:49:22 -0400 Subject: [PATCH 444/999] Remove unnecessary fmt.Printf Signed-off-by: Thomas Texier --- integration-cli/docker_cli_exec_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 8906da252..d4e483971 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -705,7 +705,6 @@ func TestExecWithPrivileged(t *testing.T) { cmd := exec.Command(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sda b 8 0") out, _, err := runCommandWithOutput(cmd) - fmt.Printf("%s", out) if err == nil || !strings.Contains(out, "Operation not permitted") { t.Fatalf("exec mknod in --cap-drop=ALL container without --privileged should failed") } From 088e69da35525fdf380ae73046d999332ea977fa Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 14 Apr 2015 23:10:17 +0200 Subject: [PATCH 445/999] Fix wrong graphdb refs paths purging Signed-off-by: Antonio Murdaca --- pkg/graphdb/graphdb.go | 14 ++++++++++-- pkg/graphdb/graphdb_test.go | 44 ++++++++++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/pkg/graphdb/graphdb.go b/pkg/graphdb/graphdb.go index c6f13eda2..b9433dbdd 100644 --- a/pkg/graphdb/graphdb.go +++ b/pkg/graphdb/graphdb.go @@ -378,12 +378,22 @@ func (db *Database) Purge(id string) (int, error) { tx.Rollback() return -1, err } - changes, err := rows.RowsAffected() if err != nil { return -1, err } + // Clear who's using this id as parent + refs, err := tx.Exec("DELETE FROM edge WHERE parent_id = ?;", id) + if err != nil { + tx.Rollback() + return -1, err + } + refsCount, err := refs.RowsAffected() + if err != nil { + return -1, err + } + // Delete entity if _, err := tx.Exec("DELETE FROM entity where id = ?;", id); err != nil { tx.Rollback() @@ -394,7 +404,7 @@ func (db *Database) Purge(id string) (int, error) { return -1, err } - return int(changes), nil + return int(changes + refsCount), nil } // Rename an edge for a given path diff --git a/pkg/graphdb/graphdb_test.go b/pkg/graphdb/graphdb_test.go index f22828560..12dd524ed 100644 --- a/pkg/graphdb/graphdb_test.go +++ b/pkg/graphdb/graphdb_test.go @@ -472,8 +472,8 @@ func TestPurgeId(t *testing.T) { db.Set("/webapp", "1") - if db.Refs("1") != 1 { - t.Fatal("Expect reference count to be 1") + if c := db.Refs("1"); c != 1 { + t.Fatalf("Expect reference count to be 1, got %d", c) } db.Set("/db", "2") @@ -484,7 +484,45 @@ func TestPurgeId(t *testing.T) { t.Fatal(err) } if count != 2 { - t.Fatal("Expected 2 references to be removed") + t.Fatalf("Expected 2 references to be removed, got %d", count) + } +} + +// Regression test https://github.com/docker/docker/issues/12334 +func TestPurgeIdRefPaths(t *testing.T) { + db, dbpath := newTestDb(t) + defer destroyTestDb(dbpath) + + db.Set("/webapp", "1") + db.Set("/db", "2") + + db.Set("/db/webapp", "1") + + if c := db.Refs("1"); c != 2 { + t.Fatalf("Expected 2 reference for webapp, got %d", c) + } + if c := db.Refs("2"); c != 1 { + t.Fatalf("Expected 1 reference for db, got %d", c) + } + + if rp := db.RefPaths("2"); len(rp) != 1 { + t.Fatalf("Expected 1 reference path for db, got %d", len(rp)) + } + + count, err := db.Purge("2") + if err != nil { + t.Fatal(err) + } + + if count != 2 { + t.Fatalf("Expected 2 rows to be removed, got %d", count) + } + + if c := db.Refs("2"); c != 0 { + t.Fatalf("Expected 0 reference for db, got %d", c) + } + if c := db.Refs("1"); c != 1 { + t.Fatalf("Expected 1 reference for webapp, got %d", c) } } From 610c436e07388f4898020432b25939cc7104b894 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 9 Apr 2015 14:27:12 -0700 Subject: [PATCH 446/999] Remove engine.Job from Start action. Signed-off-by: David Calavera --- api/server/server.go | 6 ++-- daemon/create.go | 2 +- daemon/daemon.go | 3 +- daemon/start.go | 13 ++----- runconfig/hostconfig.go | 80 ++++++++++++++++++++--------------------- 5 files changed, 49 insertions(+), 55 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 7ebeb4b6a..91307a199 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -926,7 +926,7 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res } var ( name = vars["name"] - job = eng.Job("start", name) + env = new(engine.Env) ) // If contentLength is -1, we can assumed chunked encoding @@ -940,12 +940,12 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res return err } - if err := job.DecodeEnv(r.Body); err != nil { + if err := env.Decode(r.Body); err != nil { return err } } - if err := job.Run(); err != nil { + if err := getDaemon(eng).ContainerStart(name, env); err != nil { if err.Error() == "Container already started" { w.WriteHeader(http.StatusNotModified) return nil diff --git a/daemon/create.go b/daemon/create.go index c820201e4..02ef7e0a1 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -21,7 +21,7 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) error { } config := runconfig.ContainerConfigFromJob(job) - hostConfig := runconfig.ContainerHostConfigFromJob(job) + hostConfig := runconfig.ContainerHostConfigFromJob(job.env) if len(hostConfig.LxcConf) > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { return fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) diff --git a/daemon/daemon.go b/daemon/daemon.go index e2ca56805..d5d420f10 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -122,7 +122,8 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { "create": daemon.ContainerCreate, "info": daemon.CmdInfo, "restart": daemon.ContainerRestart, - "start": daemon.ContainerStart, + "stop": daemon.ContainerStop, + "wait": daemon.ContainerWait, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, } { diff --git a/daemon/start.go b/daemon/start.go index 8de67b996..b0b6dc75c 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -7,14 +7,7 @@ import ( "github.com/docker/docker/runconfig" ) -func (daemon *Daemon) ContainerStart(job *engine.Job) error { - if len(job.Args) < 1 { - return fmt.Errorf("Usage: %s container_id", job.Name) - } - var ( - name = job.Args[0] - ) - +func (daemon *Daemon) ContainerStart(name string, env *engine.Env) error { container, err := daemon.Get(name) if err != nil { return err @@ -31,8 +24,8 @@ func (daemon *Daemon) ContainerStart(job *engine.Job) error { // If no environment was set, then no hostconfig was passed. // This is kept for backward compatibility - hostconfig should be passed when // creating a container, not during start. - if len(job.Environ()) > 0 { - hostConfig := runconfig.ContainerHostConfigFromJob(job) + if len(env.Map()) > 0 { + hostConfig := runconfig.ContainerHostConfigFromJob(env) if err := daemon.setHostConfig(container, hostConfig); err != nil { return err } diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 9d4eb2641..470588a09 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -152,80 +152,80 @@ func MergeConfigs(config *Config, hostConfig *HostConfig) *ConfigAndHostConfig { } } -func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { - if job.EnvExists("HostConfig") { +func ContainerHostConfigFromJob(env *engine.Env) *HostConfig { + if env.Exists("HostConfig") { hostConfig := HostConfig{} - job.GetenvJson("HostConfig", &hostConfig) + env.GetJson("HostConfig", &hostConfig) // FIXME: These are for backward compatibility, if people use these // options with `HostConfig`, we should still make them workable. - if job.EnvExists("Memory") && hostConfig.Memory == 0 { - hostConfig.Memory = job.GetenvInt64("Memory") + if env.Exists("Memory") && hostConfig.Memory == 0 { + hostConfig.Memory = env.GetInt64("Memory") } - if job.EnvExists("MemorySwap") && hostConfig.MemorySwap == 0 { - hostConfig.MemorySwap = job.GetenvInt64("MemorySwap") + if env.Exists("MemorySwap") && hostConfig.MemorySwap == 0 { + hostConfig.MemorySwap = env.GetInt64("MemorySwap") } - if job.EnvExists("CpuShares") && hostConfig.CpuShares == 0 { - hostConfig.CpuShares = job.GetenvInt64("CpuShares") + if env.Exists("CpuShares") && hostConfig.CpuShares == 0 { + hostConfig.CpuShares = env.GetInt64("CpuShares") } - if job.EnvExists("Cpuset") && hostConfig.CpusetCpus == "" { - hostConfig.CpusetCpus = job.Getenv("Cpuset") + if env.Exists("Cpuset") && hostConfig.CpusetCpus == "" { + hostConfig.CpusetCpus = env.Get("Cpuset") } return &hostConfig } hostConfig := &HostConfig{ - ContainerIDFile: job.Getenv("ContainerIDFile"), - Memory: job.GetenvInt64("Memory"), - MemorySwap: job.GetenvInt64("MemorySwap"), - CpuShares: job.GetenvInt64("CpuShares"), - CpusetCpus: job.Getenv("CpusetCpus"), - Privileged: job.GetenvBool("Privileged"), - PublishAllPorts: job.GetenvBool("PublishAllPorts"), - NetworkMode: NetworkMode(job.Getenv("NetworkMode")), - IpcMode: IpcMode(job.Getenv("IpcMode")), - PidMode: PidMode(job.Getenv("PidMode")), - ReadonlyRootfs: job.GetenvBool("ReadonlyRootfs"), - CgroupParent: job.Getenv("CgroupParent"), + ContainerIDFile: env.Get("ContainerIDFile"), + Memory: env.GetInt64("Memory"), + MemorySwap: env.GetInt64("MemorySwap"), + CpuShares: env.GetInt64("CpuShares"), + CpusetCpus: env.Get("CpusetCpus"), + Privileged: env.GetBool("Privileged"), + PublishAllPorts: env.GetBool("PublishAllPorts"), + NetworkMode: NetworkMode(env.Get("NetworkMode")), + IpcMode: IpcMode(env.Get("IpcMode")), + PidMode: PidMode(env.Get("PidMode")), + ReadonlyRootfs: env.GetBool("ReadonlyRootfs"), + CgroupParent: env.Get("CgroupParent"), } // FIXME: This is for backward compatibility, if people use `Cpuset` // in json, make it workable, we will only pass hostConfig.CpusetCpus // to execDriver. - if job.EnvExists("Cpuset") && hostConfig.CpusetCpus == "" { - hostConfig.CpusetCpus = job.Getenv("Cpuset") + if env.Exists("Cpuset") && hostConfig.CpusetCpus == "" { + hostConfig.CpusetCpus = env.Get("Cpuset") } - job.GetenvJson("LxcConf", &hostConfig.LxcConf) - job.GetenvJson("PortBindings", &hostConfig.PortBindings) - job.GetenvJson("Devices", &hostConfig.Devices) - job.GetenvJson("RestartPolicy", &hostConfig.RestartPolicy) - job.GetenvJson("Ulimits", &hostConfig.Ulimits) - job.GetenvJson("LogConfig", &hostConfig.LogConfig) - hostConfig.SecurityOpt = job.GetenvList("SecurityOpt") - if Binds := job.GetenvList("Binds"); Binds != nil { + env.GetJson("LxcConf", &hostConfig.LxcConf) + env.GetJson("PortBindings", &hostConfig.PortBindings) + env.GetJson("Devices", &hostConfig.Devices) + env.GetJson("RestartPolicy", &hostConfig.RestartPolicy) + env.GetJson("Ulimits", &hostConfig.Ulimits) + env.GetJson("LogConfig", &hostConfig.LogConfig) + hostConfig.SecurityOpt = env.GetList("SecurityOpt") + if Binds := env.GetList("Binds"); Binds != nil { hostConfig.Binds = Binds } - if Links := job.GetenvList("Links"); Links != nil { + if Links := env.GetList("Links"); Links != nil { hostConfig.Links = Links } - if Dns := job.GetenvList("Dns"); Dns != nil { + if Dns := env.GetList("Dns"); Dns != nil { hostConfig.Dns = Dns } - if DnsSearch := job.GetenvList("DnsSearch"); DnsSearch != nil { + if DnsSearch := env.GetList("DnsSearch"); DnsSearch != nil { hostConfig.DnsSearch = DnsSearch } - if ExtraHosts := job.GetenvList("ExtraHosts"); ExtraHosts != nil { + if ExtraHosts := env.GetList("ExtraHosts"); ExtraHosts != nil { hostConfig.ExtraHosts = ExtraHosts } - if VolumesFrom := job.GetenvList("VolumesFrom"); VolumesFrom != nil { + if VolumesFrom := env.GetList("VolumesFrom"); VolumesFrom != nil { hostConfig.VolumesFrom = VolumesFrom } - if CapAdd := job.GetenvList("CapAdd"); CapAdd != nil { + if CapAdd := env.GetList("CapAdd"); CapAdd != nil { hostConfig.CapAdd = CapAdd } - if CapDrop := job.GetenvList("CapDrop"); CapDrop != nil { + if CapDrop := env.GetList("CapDrop"); CapDrop != nil { hostConfig.CapDrop = CapDrop } From 98996a432e079d1434182ea1cf84e70c927da4c2 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 9 Apr 2015 14:49:22 -0700 Subject: [PATCH 447/999] Remove engine.Job from Create action. Signed-off-by: David Calavera --- api/server/server.go | 27 +++++++++--------------- daemon/create.go | 41 ++++++++++++++---------------------- daemon/daemon.go | 3 ++- runconfig/config.go | 50 ++++++++++++++++++++++---------------------- 4 files changed, 53 insertions(+), 68 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 91307a199..2cc966249 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -809,30 +809,23 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re return err } var ( - job = eng.Job("create", r.Form.Get("name")) - outWarnings []string - stdoutBuffer = bytes.NewBuffer(nil) - warnings = bytes.NewBuffer(nil) + warnings []string + name = r.Form.Get("name") + env = new(engine.Env) ) - if err := job.DecodeEnv(r.Body); err != nil { + if err := env.Decode(r.Body); err != nil { return err } - // Read container ID from the first line of stdout - job.Stdout.Add(stdoutBuffer) - // Read warnings from stderr - job.Stderr.Add(warnings) - if err := job.Run(); err != nil { + + containerId, warnings, err := getDaemon(eng).ContainerCreate(name, env) + if err != nil { return err } - // Parse warnings from stderr - scanner := bufio.NewScanner(warnings) - for scanner.Scan() { - outWarnings = append(outWarnings, scanner.Text()) - } + return writeJSON(w, http.StatusCreated, &types.ContainerCreateResponse{ - ID: engine.Tail(stdoutBuffer, 1), - Warnings: outWarnings, + ID: containerId, + Warnings: warnings, }) } diff --git a/daemon/create.go b/daemon/create.go index 02ef7e0a1..da271043f 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -12,36 +12,31 @@ import ( "github.com/docker/libcontainer/label" ) -func (daemon *Daemon) ContainerCreate(job *engine.Job) error { - var name string - if len(job.Args) == 1 { - name = job.Args[0] - } else if len(job.Args) > 1 { - return fmt.Errorf("Usage: %s", job.Name) - } +func (daemon *Daemon) ContainerCreate(name string, env *engine.Env) (string, []string, error) { + var warnings []string - config := runconfig.ContainerConfigFromJob(job) - hostConfig := runconfig.ContainerHostConfigFromJob(job.env) + config := runconfig.ContainerConfigFromJob(env) + hostConfig := runconfig.ContainerHostConfigFromJob(env) if len(hostConfig.LxcConf) > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { - return fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) + return "", warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) } if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 { - return fmt.Errorf("Minimum memory limit allowed is 4MB") + return "", warnings, fmt.Errorf("Minimum memory limit allowed is 4MB") } if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit { - job.Errorf("Your kernel does not support memory limit capabilities. Limitation discarded.\n") + warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.\n") hostConfig.Memory = 0 } if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit { - job.Errorf("Your kernel does not support swap limit capabilities. Limitation discarded.\n") + warnings = append(warnings, "Your kernel does not support swap limit capabilities. Limitation discarded.\n") hostConfig.MemorySwap = -1 } if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory { - return fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") + return "", warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") } if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { - return fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") + return "", warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") } container, buildWarnings, err := daemon.Create(config, hostConfig, name) @@ -51,22 +46,18 @@ func (daemon *Daemon) ContainerCreate(job *engine.Job) error { if tag == "" { tag = graph.DEFAULTTAG } - return fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag) + return "", warnings, fmt.Errorf("No such image: %s (tag: %s)", config.Image, tag) } - return err + return "", warnings, err } if !container.Config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled { - job.Errorf("IPv4 forwarding is disabled.\n") + warnings = append(warnings, "IPv4 forwarding is disabled.\n") } + container.LogEvent("create") + warnings = append(warnings, buildWarnings...) - job.Printf("%s\n", container.ID) - - for _, warning := range buildWarnings { - job.Errorf("%s\n", warning) - } - - return nil + return container.ID, warnings, nil } // Create creates a new container from the given configuration with a given name. diff --git a/daemon/daemon.go b/daemon/daemon.go index d5d420f10..76ed5a5dd 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -119,7 +119,8 @@ type Daemon struct { func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "container_inspect": daemon.ContainerInspect, - "create": daemon.ContainerCreate, + "container_stats": daemon.ContainerStats, + "export": daemon.ContainerExport, "info": daemon.CmdInfo, "restart": daemon.ContainerRestart, "stop": daemon.ContainerStop, diff --git a/runconfig/config.go b/runconfig/config.go index 45255e9b0..30dd1eb4c 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -36,41 +36,41 @@ type Config struct { Labels map[string]string } -func ContainerConfigFromJob(job *engine.Job) *Config { +func ContainerConfigFromJob(env *engine.Env) *Config { config := &Config{ - Hostname: job.Getenv("Hostname"), - Domainname: job.Getenv("Domainname"), - User: job.Getenv("User"), - Memory: job.GetenvInt64("Memory"), - MemorySwap: job.GetenvInt64("MemorySwap"), - CpuShares: job.GetenvInt64("CpuShares"), - Cpuset: job.Getenv("Cpuset"), - AttachStdin: job.GetenvBool("AttachStdin"), - AttachStdout: job.GetenvBool("AttachStdout"), - AttachStderr: job.GetenvBool("AttachStderr"), - Tty: job.GetenvBool("Tty"), - OpenStdin: job.GetenvBool("OpenStdin"), - StdinOnce: job.GetenvBool("StdinOnce"), - Image: job.Getenv("Image"), - WorkingDir: job.Getenv("WorkingDir"), - NetworkDisabled: job.GetenvBool("NetworkDisabled"), - MacAddress: job.Getenv("MacAddress"), + Hostname: env.Get("Hostname"), + Domainname: env.Get("Domainname"), + User: env.Get("User"), + Memory: env.GetInt64("Memory"), + MemorySwap: env.GetInt64("MemorySwap"), + CpuShares: env.GetInt64("CpuShares"), + Cpuset: env.Get("Cpuset"), + AttachStdin: env.GetBool("AttachStdin"), + AttachStdout: env.GetBool("AttachStdout"), + AttachStderr: env.GetBool("AttachStderr"), + Tty: env.GetBool("Tty"), + OpenStdin: env.GetBool("OpenStdin"), + StdinOnce: env.GetBool("StdinOnce"), + Image: env.Get("Image"), + WorkingDir: env.Get("WorkingDir"), + NetworkDisabled: env.GetBool("NetworkDisabled"), + MacAddress: env.Get("MacAddress"), } - job.GetenvJson("ExposedPorts", &config.ExposedPorts) - job.GetenvJson("Volumes", &config.Volumes) - if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil { + env.GetJson("ExposedPorts", &config.ExposedPorts) + env.GetJson("Volumes", &config.Volumes) + if PortSpecs := env.GetList("PortSpecs"); PortSpecs != nil { config.PortSpecs = PortSpecs } - if Env := job.GetenvList("Env"); Env != nil { + if Env := env.GetList("Env"); Env != nil { config.Env = Env } - if Cmd := job.GetenvList("Cmd"); Cmd != nil { + if Cmd := env.GetList("Cmd"); Cmd != nil { config.Cmd = Cmd } - job.GetenvJson("Labels", &config.Labels) + env.GetJson("Labels", &config.Labels) - if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil { + if Entrypoint := env.GetList("Entrypoint"); Entrypoint != nil { config.Entrypoint = Entrypoint } return config From 002afbbe77abe722b8d0c6a2d3f11a258509f430 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 9 Apr 2015 16:29:31 -0700 Subject: [PATCH 448/999] Make integration tests to call the new start and create endpoints. Signed-off-by: David Calavera --- integration/runtime_test.go | 52 +++++++++++++++++-------------------- integration/utils_test.go | 15 +++++------ 2 files changed, 31 insertions(+), 36 deletions(-) diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 6881fbea6..f3485b2b3 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -422,14 +422,13 @@ func TestGet(t *testing.T) { func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daemon.Container, string) { var ( - err error - id string - outputBuffer = bytes.NewBuffer(nil) - strPort string - eng = NewTestEngine(t) - daemon = mkDaemonFromEngine(eng, t) - port = 5554 - p nat.Port + err error + id string + strPort string + eng = NewTestEngine(t) + daemon = mkDaemonFromEngine(eng, t) + port = 5554 + p nat.Port ) defer func() { if err != nil { @@ -452,16 +451,13 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto)) ep[p] = struct{}{} - jobCreate := eng.Job("create") - jobCreate.Setenv("Image", unitTestImageID) - jobCreate.SetenvList("Cmd", []string{"sh", "-c", cmd}) - jobCreate.SetenvList("PortSpecs", []string{fmt.Sprintf("%s/%s", strPort, proto)}) - jobCreate.SetenvJson("ExposedPorts", ep) - jobCreate.Stdout.Add(outputBuffer) - if err := jobCreate.Run(); err != nil { - t.Fatal(err) - } - id = engine.Tail(outputBuffer, 1) + env := new(engine.Env) + env.Set("Image", unitTestImageID) + env.SetList("Cmd", []string{"sh", "-c", cmd}) + env.SetList("PortSpecs", []string{fmt.Sprintf("%s/%s", strPort, proto)}) + env.SetJson("ExposedPorts", ep) + + id, _, err = daemon.ContainerCreate(unitTestImageID, env) // FIXME: this relies on the undocumented behavior of daemon.Create // which will return a nil error AND container if the exposed ports // are invalid. That behavior should be fixed! @@ -472,15 +468,16 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem } - jobStart := eng.Job("start", id) portBindings := make(map[nat.Port][]nat.PortBinding) portBindings[p] = []nat.PortBinding{ {}, } - if err := jobStart.SetenvJson("PortsBindings", portBindings); err != nil { + + env := new(engine.Env) + if err := env.SetJson("PortsBindings", portBindings); err != nil { t.Fatal(err) } - if err := jobStart.Run(); err != nil { + if err := daemon.ContainerStart(id, env); err != nil { t.Fatal(err) } @@ -731,20 +728,20 @@ func TestContainerNameValidation(t *testing.T) { t.Fatal(err) } - var outputBuffer = bytes.NewBuffer(nil) - job := eng.Job("create", test.Name) - if err := job.ImportEnv(config); err != nil { + env := new(engine.Env) + if err := env.Import(config); err != nil { t.Fatal(err) } - job.Stdout.Add(outputBuffer) - if err := job.Run(); err != nil { + + containerId, _, err := daemon.ContainerCreate(test.Name, env) + if err != nil { if !test.Valid { continue } t.Fatal(err) } - container, err := daemon.Get(engine.Tail(outputBuffer, 1)) + container, err := daemon.Get(containerId) if err != nil { t.Fatal(err) } @@ -759,7 +756,6 @@ func TestContainerNameValidation(t *testing.T) { t.Fatalf("Container /%s has ID %s instead of %s", test.Name, c.ID, container.ID) } } - } func TestLinkChildContainer(t *testing.T) { diff --git a/integration/utils_test.go b/integration/utils_test.go index cf9104efe..86b27fb73 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -44,16 +44,15 @@ func mkDaemon(f Fataler) *daemon.Daemon { } func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) { - job := eng.Job("create", name) - if err := job.ImportEnv(config); err != nil { + env := new(engine.Env) + if err := env.Import(config); err != nil { f.Fatal(err) } - var outputBuffer = bytes.NewBuffer(nil) - job.Stdout.Add(outputBuffer) - if err := job.Run(); err != nil { + containerId, _, err := getDaemon(eng).ContainerCreate(name, env) + if err != nil { f.Fatal(err) } - return engine.Tail(outputBuffer, 1) + return containerId } func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler) (shortId string) { @@ -61,8 +60,8 @@ func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler } func startContainer(eng *engine.Engine, id string, t Fataler) { - job := eng.Job("start", id) - if err := job.Run(); err != nil { + env := new(engine.Env) + if err := getDaemon(eng).ContainerStart(id, env); err != nil { t.Fatal(err) } } From d10c5d95a896197b504bae039dc6d9371917cdf1 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 14 Apr 2015 16:53:28 -0700 Subject: [PATCH 449/999] Fix @moxiegirl's handler to be consistent with other maintainers. Signed-off-by: David Calavera --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 6c79e8b4b..b708af774 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -408,7 +408,7 @@ made through a pull request. people = [ "fredlf", "james", - "mary", + "moxiegirl", "spf13", "sven" ] @@ -585,7 +585,7 @@ made through a pull request. Email = "lk4d4@docker.com" GitHub = "lk4d4" - [people.mary] + [people.moxiegirl] Name = "Mary Anthony" Email = "mary.anthony@docker.com" GitHub = "moxiegirl" From 25d07511ed63959777ab1d0bee9f8e5f79382318 Mon Sep 17 00:00:00 2001 From: Jason Divock Date: Tue, 14 Apr 2015 16:19:23 -0700 Subject: [PATCH 450/999] Update basic setup instructions Without adding the user to the group you're going to hit nasty TLS errors. Figured I'd save the next guy the hassle. Problem more accurately described here: https://github.com/docker/docker/issues/5314 Signed-off-by: Jason Divock --- docs/sources/installation/amazon.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/amazon.md b/docs/sources/installation/amazon.md index 6a28685dc..60a6653b7 100644 --- a/docs/sources/installation/amazon.md +++ b/docs/sources/installation/amazon.md @@ -28,8 +28,12 @@ Repository. 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` +4. Add the ec2-user to the docker group : + `sudo usermod -a -G docker ec2-user` +5. Restart the machine and log back in + `sudo shutdown -r now` +6. Once connected to the instance, type + `sudo yum install -y docker ; sudo service docker start` to install and start Docker **If this is your first AWS instance, you may need to set up your Security Group to allow SSH.** By default all incoming ports to your new instance will be blocked by the AWS Security Group, so you might just get timeouts when you try to connect. From 8077b2fb805c78cee642d8350df88227c6414960 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Wed, 15 Apr 2015 09:33:46 +0800 Subject: [PATCH 451/999] add support for cpuset.mems Signed-off-by: Qiang Huang --- daemon/container.go | 1 + daemon/execdriver/driver.go | 2 ++ daemon/execdriver/lxc/lxc_template.go | 3 +++ docs/man/docker-create.1.md | 8 ++++++++ docs/man/docker-run.1.md | 8 ++++++++ .../reference/api/docker_remote_api_v1.19.md | 3 +++ docs/sources/reference/commandline/cli.md | 2 ++ docs/sources/reference/run.md | 16 ++++++++++++++++ integration-cli/docker_cli_run_test.go | 15 +++++++++++++-- runconfig/hostconfig.go | 1 + runconfig/parse.go | 2 ++ 11 files changed, 59 insertions(+), 2 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index f89db5cfc..97d1afbb0 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -355,6 +355,7 @@ func populateCommand(c *Container, env []string) error { MemorySwap: c.hostConfig.MemorySwap, CpuShares: c.hostConfig.CpuShares, CpusetCpus: c.hostConfig.CpusetCpus, + CpusetMems: c.hostConfig.CpusetMems, Rlimits: rlimits, } diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index 637f7d779..fc3b5caba 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -110,6 +110,7 @@ type Resources struct { MemorySwap int64 `json:"memory_swap"` CpuShares int64 `json:"cpu_shares"` CpusetCpus string `json:"cpuset_cpus"` + CpusetMems string `json:"cpuset_mems"` Rlimits []*ulimit.Rlimit `json:"rlimits"` } @@ -204,6 +205,7 @@ func SetupCgroups(container *configs.Config, c *Command) error { container.Cgroups.MemoryReservation = c.Resources.Memory container.Cgroups.MemorySwap = c.Resources.MemorySwap container.Cgroups.CpusetCpus = c.Resources.CpusetCpus + container.Cgroups.CpusetMems = c.Resources.CpusetMems } return nil diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 6c182ab39..ece924d38 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -110,6 +110,9 @@ lxc.cgroup.cpu.shares = {{.Resources.CpuShares}} {{if .Resources.CpusetCpus}} lxc.cgroup.cpuset.cpus = {{.Resources.CpusetCpus}} {{end}} +{{if .Resources.CpusetMems}} +lxc.cgroup.cpuset.mems = {{.Resources.CpusetMems}} +{{end}} {{end}} {{if .LxcConfig}} diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index 1a0da1b8f..6ce7fe6bd 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -13,6 +13,7 @@ docker-create - Create a new container [**--cap-drop**[=*[]*]] [**--cidfile**[=*CIDFILE*]] [**--cpuset-cpus**[=*CPUSET-CPUS*]] +[**--cpuset-mems**[=*CPUSET-MEMS*]] [**--device**[=*[]*]] [**--dns-search**[=*[]*]] [**--dns**[=*[]*]] @@ -74,6 +75,13 @@ IMAGE [COMMAND] [ARG...] **--cpuset-cpus**="" CPUs in which to allow execution (0-3, 0,1) +**--cpuset-mems**="" + Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. + + If you have four memory nodes on your system (0-3), use `--cpuset-mems=0,1` +then processes in your Docker container will only use memory from the first +two memory nodes. + **--device**=[] Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm) diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 8a856ca60..42eeeb349 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -13,6 +13,7 @@ docker-run - Run a command in a new container [**--cap-drop**[=*[]*]] [**--cidfile**[=*CIDFILE*]] [**--cpuset-cpus**[=*CPUSET-CPUS*]] +[**--cpuset-mems**[=*CPUSET-MEMS*]] [**-d**|**--detach**[=*false*]] [**--device**[=*[]*]] [**--dns-search**[=*[]*]] @@ -134,6 +135,13 @@ division of CPU shares: **--cpuset-cpus**="" CPUs in which to allow execution (0-3, 0,1) +**--cpuset-mems**="" + Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. + + If you have four memory nodes on your system (0-3), use `--cpuset-mems=0,1` +then processes in your Docker container will only use memory from the first +two memory nodes. + **-d**, **--detach**=*true*|*false* Detached mode: run the container in the background and print the new container ID. The default is *false*. diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 524f77037..18c52ef42 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -147,6 +147,7 @@ Create a container "MemorySwap": 0, "CpuShares": 512, "CpusetCpus": "0,1", + "CpusetMems": "0,1", "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, "PublishAllPorts": false, "Privileged": false, @@ -191,6 +192,7 @@ Json Parameters: (ie. the relative weight vs other containers). - **Cpuset** - The same as CpusetCpus, but deprecated, please don't use. - **CpusetCpus** - String value containing the cgroups CpusetCpus to use. +- **CpusetMems** - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -340,6 +342,7 @@ Return low-level information on the container `id` "CapDrop": null, "ContainerIDFile": "", "CpusetCpus": "", + "CpusetMems": "", "CpuShares": 0, "Devices": [], "Dns": null, diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 737526123..60a604b8b 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -892,6 +892,7 @@ Creates a new container. --cgroup-parent="" Optional parent cgroup for the container --cidfile="" Write the container ID to the file --cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) + --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) --device=[] Add a host device to the container --dns=[] Set custom DNS servers --dns-search=[] Set custom DNS search domains @@ -1844,6 +1845,7 @@ To remove an image using its digest: --cap-drop=[] Drop Linux capabilities --cidfile="" Write the container ID to the file --cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) + --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) -d, --detach=false Run container in background and print container ID --device=[] Add a host device to the container --dns=[] Set custom DNS servers diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index d41b686c4..b5784cba7 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -474,6 +474,7 @@ container: -memory-swap="": Total memory limit (memory + swap, format: , where unit = b, k, m or g) -c, --cpu-shares=0: CPU shares (relative weight) --cpuset-cpus="": CPUs in which to allow execution (0-3, 0,1) + --cpuset-mems="": Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. ### Memory constraints @@ -599,6 +600,21 @@ This means processes in container can be executed on cpu 1 and cpu 3. This means processes in container can be executed on cpu 0, cpu 1 and cpu 2. +We can set mems in which to allow execution for containers. Only effective +on NUMA systems. + +Examples: + + $ docker run -ti --cpuset-mems="1,3" ubuntu:14.04 /bin/bash + +This example restricts the processes in the container to only use memory from +memory nodes 1 and 3. + + $ docker run -ti --cpuset-mems="0-2" ubuntu:14.04 /bin/bash + +This example restricts the processes in the container to only use memory from +memory nodes 0, 1 and 2. + ## Runtime privilege, Linux capabilities, and LXC configuration --cap-add: Add Linux capabilities diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 7c931f8fa..e990f086a 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -1367,7 +1367,7 @@ func TestRunWithCpuset(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "--cpuset", "0", "busybox", "true") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("container should run successfuly with cpuset of 0: %s", err) + t.Fatalf("container should run successfully with cpuset of 0: %s", err) } logDone("run - cpuset 0") @@ -1378,12 +1378,23 @@ func TestRunWithCpusetCpus(t *testing.T) { cmd := exec.Command(dockerBinary, "run", "--cpuset-cpus", "0", "busybox", "true") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("container should run successfuly with cpuset-cpus of 0: %s", err) + t.Fatalf("container should run successfully with cpuset-cpus of 0: %s", err) } logDone("run - cpuset-cpus 0") } +func TestRunWithCpusetMems(t *testing.T) { + defer deleteAllContainers() + + cmd := exec.Command(dockerBinary, "run", "--cpuset-mems", "0", "busybox", "true") + if code, err := runCommand(cmd); err != nil || code != 0 { + t.Fatalf("container should run successfully with cpuset-mems of 0: %s", err) + } + + logDone("run - cpuset-mems 0") +} + func TestRunDeviceNumbers(t *testing.T) { defer deleteAllContainers() diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 9d4eb2641..273634e63 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -116,6 +116,7 @@ type HostConfig struct { MemorySwap int64 // Total memory usage (memory + swap); set `-1` to disable swap CpuShares int64 // CPU shares (relative weight vs. other containers) CpusetCpus string // CpusetCpus 0-2, 0,1 + CpusetMems string // CpusetMems 0-2, 0,1 Privileged bool PortBindings nat.PortMap Links []string diff --git a/runconfig/parse.go b/runconfig/parse.go index d302330c8..d9b21ca27 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -64,6 +64,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container") flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") flCpusetCpus = cmd.String([]string{"#-cpuset", "-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") + flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") flNetMode = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container") flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use") @@ -313,6 +314,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe MemorySwap: MemorySwap, CpuShares: *flCpuShares, CpusetCpus: *flCpusetCpus, + CpusetMems: *flCpusetMems, Privileged: *flPrivileged, PortBindings: portBindings, Links: flLinks.GetAll(), From f8dc7e8754355c504562021da244c52055ab9204 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Tue, 14 Apr 2015 10:00:48 +0800 Subject: [PATCH 452/999] Add cpuset-mems support for docker build Signed-off-by: Qiang Huang --- api/client/build.go | 2 ++ api/server/server.go | 1 + builder/evaluator.go | 1 + builder/internals.go | 1 + builder/job.go | 2 ++ docs/sources/reference/commandline/cli.md | 1 + integration-cli/docker_cli_build_test.go | 15 ++++++++------- 7 files changed, 16 insertions(+), 7 deletions(-) diff --git a/api/client/build.go b/api/client/build.go index dc54c22ff..b4ee93b2c 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -56,6 +56,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap") flCPUShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") + flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) @@ -278,6 +279,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } v.Set("cpusetcpus", *flCPUSetCpus) + v.Set("cpusetmems", *flCPUSetMems) v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10)) v.Set("memory", strconv.FormatInt(memory, 10)) v.Set("memswap", strconv.FormatInt(memorySwap, 10)) diff --git a/api/server/server.go b/api/server/server.go index 7ebeb4b6a..3e8d9bae0 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1213,6 +1213,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite job.Setenv("memswap", r.FormValue("memswap")) job.Setenv("memory", r.FormValue("memory")) job.Setenv("cpusetcpus", r.FormValue("cpusetcpus")) + job.Setenv("cpusetmems", r.FormValue("cpusetmems")) job.Setenv("cpushares", r.FormValue("cpushares")) // Job cancellation. Note: not all job types support this. diff --git a/builder/evaluator.go b/builder/evaluator.go index 6237f2663..c159e51bf 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -124,6 +124,7 @@ type Builder struct { // Set resource restrictions for build containers cpuSetCpus string + cpuSetMems string cpuShares int64 memory int64 memorySwap int64 diff --git a/builder/internals.go b/builder/internals.go index 728ccde8a..caa94ef2b 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -541,6 +541,7 @@ func (b *Builder) create() (*daemon.Container, error) { hostConfig := &runconfig.HostConfig{ CpuShares: b.cpuShares, CpusetCpus: b.cpuSetCpus, + CpusetMems: b.cpuSetMems, Memory: b.memory, MemorySwap: b.memorySwap, } diff --git a/builder/job.go b/builder/job.go index b0ce8ddc0..f9ff61039 100644 --- a/builder/job.go +++ b/builder/job.go @@ -63,6 +63,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { memorySwap = job.GetenvInt64("memswap") cpuShares = job.GetenvInt64("cpushares") cpuSetCpus = job.Getenv("cpusetcpus") + cpuSetMems = job.Getenv("cpusetmems") authConfig = ®istry.AuthConfig{} configFile = ®istry.ConfigFile{} tag string @@ -153,6 +154,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { dockerfileName: dockerfileName, cpuShares: cpuShares, cpuSetCpus: cpuSetCpus, + cpuSetMems: cpuSetMems, memory: memory, memorySwap: memorySwap, cancelled: job.WaitCancelled(), diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 60a604b8b..607f67076 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -597,6 +597,7 @@ is returned by the `docker attach` command to its caller too: --memory-swap="" Total memory (memory + swap), `-1` to disable swap -c, --cpu-shares CPU Shares (relative weight) --cpuset-cpus="" CPUs in which to allow execution, e.g. `0-3`, `0,1` + --cpuset-mems="" MEMs in which to allow execution, e.g. `0-3`, `0,1` Builds Docker images from a Dockerfile and a "context". A build's context is the files located in the specified `PATH` or `URL`. The build process can diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 40e038fc4..1ef5088e2 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5555,7 +5555,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { t.Fatal(err) } - cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpu-shares=100", "-t", name, ".") + cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpuset-mems=0", "--cpu-shares=100", "-t", name, ".") cmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(cmd) @@ -5573,6 +5573,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { Memory float64 // Use float64 here since the json decoder sees it that way MemorySwap int CpusetCpus string + CpusetMems string CpuShares int } @@ -5586,9 +5587,9 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { t.Fatal(err, cfg) } mem := int64(c1.Memory) - if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpuShares != 100 { - t.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpuShares: %d", - mem, c1.MemorySwap, c1.CpusetCpus, c1.CpuShares) + if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 { + t.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", + mem, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares) } // Make sure constraints aren't saved to image @@ -5605,9 +5606,9 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { t.Fatal(err, cfg) } mem = int64(c2.Memory) - if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpuShares == 100 { - t.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpuShares: %d", - mem, c2.MemorySwap, c2.CpusetCpus, c2.CpuShares) + if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 { + t.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", + mem, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares) } logDone("build - resource constraints applied") From acf025ad1b806fd9b5eb3358a8e1d75c6aae890d Mon Sep 17 00:00:00 2001 From: Deng Guangxing Date: Wed, 15 Apr 2015 11:15:50 +0800 Subject: [PATCH 453/999] Inspect show right LogPath in json-file driver Signed-off-by: Deng Guangxing --- daemon/container.go | 1 + docs/sources/reference/api/docker_remote_api_v1.18.md | 5 ++++- docs/sources/reference/api/docker_remote_api_v1.19.md | 5 ++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index f89db5cfc..1400e967f 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1405,6 +1405,7 @@ func (container *Container) startLogging() error { if err != nil { return err } + container.LogPath = pth dl, err := jsonfilelog.New(pth) if err != nil { diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index c69ade36a..a7e5e5591 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -359,7 +359,10 @@ Return low-level information on the container `id` "MaximumRetryCount": 2, "Name": "on-failure" }, - "LogConfig": { "Type": "json-file", Config: {} }, + "LogConfig": { + "Config": null, + "Type": "json-file" + }, "SecurityOpt": null, "VolumesFrom": null, "Ulimits": [{}] diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 524f77037..932ee5471 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -359,7 +359,10 @@ Return low-level information on the container `id` "MaximumRetryCount": 2, "Name": "on-failure" }, - "LogConfig": { "Type": "json-file", "Config": {} }, + "LogConfig": { + "Config": null, + "Type": "json-file" + }, "SecurityOpt": null, "VolumesFrom": null, "Ulimits": [{}] From 6f812a4ec18453dd0f46e138f7c05b1ade13f007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 14 Apr 2015 17:21:12 +0200 Subject: [PATCH 454/999] hack/make.sh: use bash internal $PWD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörg Thalheim --- hack/make.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index 3bcb265b3..b4312b5d5 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -27,7 +27,7 @@ export DOCKER_PKG='github.com/docker/docker' # We're a nice, sexy, little shell script, and people might try to run us; # but really, they shouldn't. We want to be in a container! -if [ "$(pwd)" != "/go/src/$DOCKER_PKG" ] || [ -z "$DOCKER_CROSSPLATFORMS" ]; then +if [ "$PWD" != "/go/src/$DOCKER_PKG" ] || [ -z "$DOCKER_CROSSPLATFORMS" ]; then { echo "# WARNING! I don't seem to be running in the Docker container." echo "# The result of this command might be an incorrect build, and will not be" @@ -82,7 +82,7 @@ if [ "$AUTO_GOPATH" ]; then rm -rf .gopath mkdir -p .gopath/src/"$(dirname "${DOCKER_PKG}")" ln -sf ../../../.. .gopath/src/"${DOCKER_PKG}" - export GOPATH="$(pwd)/.gopath:$(pwd)/vendor" + export GOPATH="${PWD}/.gopath:${PWD}/vendor" fi if [ ! "$GOPATH" ]; then From 23afce5f7fb575ea5fad1afc5f60b38e3e17d8b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 14 Apr 2015 17:38:14 +0200 Subject: [PATCH 455/999] hack/make.sh: use SCRIPTDIR wherever possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörg Thalheim --- hack/make.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index b4312b5d5..c8b2da7b8 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -24,6 +24,7 @@ set -e set -o pipefail export DOCKER_PKG='github.com/docker/docker' +export SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # We're a nice, sexy, little shell script, and people might try to run us; # but really, they shouldn't. We want to be in a container! @@ -110,7 +111,7 @@ fi # Use these flags when compiling the tests and final binary IAMSTATIC='true' -source "$(dirname "$BASH_SOURCE")/make/.go-autogen" +source "$SCRIPTDIR/make/.go-autogen" LDFLAGS='-w' LDFLAGS_STATIC='-linkmode external' @@ -270,7 +271,6 @@ main() { ln -sfT $VERSION bundles/latest fi - SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" if [ $# -lt 1 ]; then bundles=(${DEFAULT_BUNDLES[@]}) else From ac20568b0a62c794c0f1190703f051bd1cfac341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 14 Apr 2015 18:08:08 +0200 Subject: [PATCH 456/999] hack: quote all parameters with variable interpolation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit better safe then sorry. especially for rm Signed-off-by: Jörg Thalheim --- hack/dind | 2 +- hack/make.sh | 8 ++-- hack/make/.dockerinit | 2 +- hack/make/.dockerinit-gccgo | 2 +- hack/make/.integration-daemon-stop | 4 +- hack/make/test-integration | 2 +- hack/make/test-unit | 2 +- hack/make/ubuntu | 60 +++++++++++++++--------------- hack/release.sh | 52 +++++++++++++------------- 9 files changed, 67 insertions(+), 67 deletions(-) diff --git a/hack/dind b/hack/dind index dfd463731..9289ba655 100755 --- a/hack/dind +++ b/hack/dind @@ -60,7 +60,7 @@ for HIER in $(cut -d: -f2 /proc/1/cgroup); do mkdir -p "$CGROUP/$HIER" - if ! mountpoint -q $CGROUP/$HIER; then + if ! mountpoint -q "$CGROUP/$HIER"; then mount -n -t cgroup -o "$OHIER" cgroup "$CGROUP/$HIER" fi diff --git a/hack/make.sh b/hack/make.sh index c8b2da7b8..0204756d8 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -252,7 +252,7 @@ bundle() { bundlescript=$1 bundle=$(basename $bundlescript) echo "---> Making bundle: $bundle (in bundles/$VERSION/$bundle)" - mkdir -p bundles/$VERSION/$bundle + mkdir -p "bundles/$VERSION/$bundle" source "$bundlescript" "$(pwd)/bundles/$VERSION/$bundle" } @@ -262,13 +262,13 @@ main() { mkdir -p bundles if [ -e "bundles/$VERSION" ]; then echo "bundles/$VERSION already exists. Removing." - rm -fr bundles/$VERSION && mkdir bundles/$VERSION || exit 1 + rm -fr "bundles/$VERSION" && mkdir "bundles/$VERSION" || exit 1 echo fi if [ "$(go env GOHOSTOS)" != 'windows' ]; then # Windows and symlinks don't get along well - ln -sfT $VERSION bundles/latest + ln -sfT "$VERSION" bundles/latest fi if [ $# -lt 1 ]; then @@ -277,7 +277,7 @@ main() { bundles=($@) fi for bundle in ${bundles[@]}; do - bundle $SCRIPTDIR/make/$bundle + bundle "$SCRIPTDIR/make/$bundle" echo done } diff --git a/hack/make/.dockerinit b/hack/make/.dockerinit index fceba7db9..36be4f822 100644 --- a/hack/make/.dockerinit +++ b/hack/make/.dockerinit @@ -30,4 +30,4 @@ else fi # sha1 our new dockerinit to ensure separate docker and dockerinit always run in a perfect pair compiled for one another -export DOCKER_INITSHA1="$($sha1sum $DEST/dockerinit-$VERSION | cut -d' ' -f1)" +export DOCKER_INITSHA1=$($sha1sum "$DEST/dockerinit-$VERSION" | cut -d' ' -f1) diff --git a/hack/make/.dockerinit-gccgo b/hack/make/.dockerinit-gccgo index 592a4152c..47611d66a 100644 --- a/hack/make/.dockerinit-gccgo +++ b/hack/make/.dockerinit-gccgo @@ -27,4 +27,4 @@ else fi # sha1 our new dockerinit to ensure separate docker and dockerinit always run in a perfect pair compiled for one another -export DOCKER_INITSHA1="$($sha1sum $DEST/dockerinit-$VERSION | cut -d' ' -f1)" +export DOCKER_INITSHA1=$($sha1sum "$DEST/dockerinit-$VERSION" | cut -d' ' -f1) diff --git a/hack/make/.integration-daemon-stop b/hack/make/.integration-daemon-stop index 319aaa4a1..7e4dc2353 100644 --- a/hack/make/.integration-daemon-stop +++ b/hack/make/.integration-daemon-stop @@ -2,8 +2,8 @@ for pidFile in $(find "$DEST" -name docker.pid); do pid=$(set -x; cat "$pidFile") - ( set -x; kill $pid ) - if ! wait $pid; then + ( set -x; kill "$pid" ) + if ! wait "$pid"; then echo >&2 "warning: PID $pid from $pidFile had a nonzero exit code" fi done diff --git a/hack/make/test-integration b/hack/make/test-integration index 5cb7102bc..b4cb6debd 100644 --- a/hack/make/test-integration +++ b/hack/make/test-integration @@ -22,4 +22,4 @@ bundle_test_integration() { # spews when it is given packages that aren't used bundle_test_integration 2>&1 \ | grep --line-buffered -v '^warning: no packages being tested depend on ' \ - | tee -a $DEST/test.log + | tee -a "$DEST/test.log" diff --git a/hack/make/test-unit b/hack/make/test-unit index 7a2642820..15595a89c 100644 --- a/hack/make/test-unit +++ b/hack/make/test-unit @@ -85,4 +85,4 @@ go_run_test_dir() { fi } -bundle_test_unit 2>&1 | tee -a $DEST/test.log +bundle_test_unit 2>&1 | tee -a "$DEST/test.log" diff --git a/hack/make/ubuntu b/hack/make/ubuntu index 5bbca8a89..7543789a1 100644 --- a/hack/make/ubuntu +++ b/hack/make/ubuntu @@ -40,26 +40,26 @@ bundle_ubuntu() { DIR=$DEST/build # Include our udev rules - mkdir -p $DIR/etc/udev/rules.d - cp contrib/udev/80-docker.rules $DIR/etc/udev/rules.d/ + mkdir -p "$DIR/etc/udev/rules.d" + cp contrib/udev/80-docker.rules "$DIR/etc/udev/rules.d/" # Include our init scripts - 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 - cp contrib/init/sysvinit-debian/docker.default $DIR/etc/default/docker - mkdir -p $DIR/lib/systemd/system - cp contrib/init/systemd/docker.{service,socket} $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" + cp contrib/init/sysvinit-debian/docker.default "$DIR/etc/default/docker" + mkdir -p "$DIR/lib/systemd/system" + cp contrib/init/systemd/docker.{service,socket} "$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/ + 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/" # Include contributed man pages docs/man/md2man-all.sh -q @@ -76,11 +76,11 @@ bundle_ubuntu() { # Copy the binary # This will fail if the binary bundle hasn't been built - mkdir -p $DIR/usr/bin - cp $DEST/../binary/docker-$VERSION $DIR/usr/bin/docker + mkdir -p "$DIR/usr/bin" + cp "$DEST/../binary/docker-$VERSION" "$DIR/usr/bin/docker" # Generate postinst/prerm/postrm scripts - cat > $DEST/postinst <<'EOF' + cat > "$DEST/postinst" <<'EOF' #!/bin/sh set -e set -u @@ -104,7 +104,7 @@ service docker $_dh_action 2>/dev/null || true #DEBHELPER# EOF - cat > $DEST/prerm <<'EOF' + cat > "$DEST/prerm" <<'EOF' #!/bin/sh set -e set -u @@ -113,7 +113,7 @@ service docker stop 2>/dev/null || true #DEBHELPER# EOF - cat > $DEST/postrm <<'EOF' + cat > "$DEST/postrm" <<'EOF' #!/bin/sh set -e set -u @@ -131,18 +131,18 @@ 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 $DEST/postinst $DEST/prerm $DEST/postrm + chmod +x "$DEST/postinst" "$DEST/prerm" "$DEST/postrm" ( # switch directories so we create *.deb in the right folder - cd $DEST + cd "$DEST" # create lxc-docker-VERSION package - fpm -s dir -C $DIR \ - --name lxc-docker-$VERSION --version "$PKGVERSION" \ - --after-install $DEST/postinst \ - --before-remove $DEST/prerm \ - --after-remove $DEST/postrm \ + fpm -s dir -C "$DIR" \ + --name "lxc-docker-$VERSION" --version "$PKGVERSION" \ + --after-install "$DEST/postinst" \ + --before-remove "$DEST/prerm" \ + --after-remove "$DEST/postrm" \ --architecture "$PACKAGE_ARCHITECTURE" \ --prefix / \ --depends iptables \ @@ -184,8 +184,8 @@ EOF ) # clean up after ourselves so we have a clean output directory - rm $DEST/postinst $DEST/prerm $DEST/postrm - rm -r $DIR + rm "$DEST/postinst" "$DEST/prerm" "$DEST/postrm" + rm -r "$DIR" } bundle_ubuntu diff --git a/hack/release.sh b/hack/release.sh index da95808c5..4b0d32c9d 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -71,23 +71,23 @@ BUCKET=$AWS_S3_BUCKET setup_s3() { # Try creating the bucket. Ignore errors (it might already exist). - s3cmd mb s3://$BUCKET 2>/dev/null || true + s3cmd mb "s3://$BUCKET" 2>/dev/null || true # Check access to the bucket. # s3cmd has no useful exit status, so we cannot check that. # Instead, we check if it outputs anything on standard output. # (When there are problems, it uses standard error instead.) - s3cmd info s3://$BUCKET | grep -q . + s3cmd info "s3://$BUCKET" | grep -q . # Make the bucket accessible through website endpoints. - s3cmd ws-create --ws-index index --ws-error error s3://$BUCKET + s3cmd ws-create --ws-index index --ws-error error "s3://$BUCKET" } # write_to_s3 uploads the contents of standard input to the specified S3 url. write_to_s3() { DEST=$1 F=`mktemp` - cat > $F - s3cmd --acl-public --mime-type='text/plain' put $F $DEST - rm -f $F + cat > "$F" + s3cmd --acl-public --mime-type='text/plain' put "$F" "$DEST" + rm -f "$F" } s3_url() { @@ -246,20 +246,20 @@ release_build() { # 1. A full APT repository is published at $BUCKET/ubuntu/ # 2. Instructions for using the APT repository are uploaded at $BUCKET/ubuntu/index release_ubuntu() { - [ -e bundles/$VERSION/ubuntu ] || { + [ -e "bundles/$VERSION/ubuntu" ] || { echo >&2 './hack/make.sh must be run before release_ubuntu' exit 1 } # Sign our packages dpkg-sig -g "--passphrase $GPG_PASSPHRASE" -k releasedocker \ - --sign builder bundles/$VERSION/ubuntu/*.deb + --sign builder "bundles/$VERSION/ubuntu/"*.deb # Setup the APT repo APTDIR=bundles/$VERSION/ubuntu/apt - mkdir -p $APTDIR/conf $APTDIR/db - s3cmd sync s3://$BUCKET/ubuntu/db/ $APTDIR/db/ || true - cat > $APTDIR/conf/distributions < "$APTDIR/conf/distributions" < bundles/$VERSION/ubuntu/gpg - s3cmd --acl-public put bundles/$VERSION/ubuntu/gpg s3://$BUCKET/gpg + s3cmd sync "$HOME/.gnupg/" "s3://$BUCKET/ubuntu/.gnupg/" + gpg --armor --export releasedocker > "bundles/$VERSION/ubuntu/gpg" + s3cmd --acl-public put "bundles/$VERSION/ubuntu/gpg" "s3://$BUCKET/gpg" local gpgFingerprint=36A1D7869245C8950F966E92D8576A8BA88D21E9 if [[ $BUCKET == test* ]]; then @@ -287,7 +287,7 @@ EOF fi # Upload repo - s3cmd --acl-public sync $APTDIR/ s3://$BUCKET/ubuntu/ + s3cmd --acl-public sync "$APTDIR/" "s3://$BUCKET/ubuntu/" cat <&2 './hack/make.sh must be run before release_binaries' exit 1 } @@ -341,29 +341,29 @@ EOF # Add redirect at /builds/info for URL-backwards-compatibility rm -rf /tmp/emptyfile && touch /tmp/emptyfile - s3cmd --acl-public --add-header='x-amz-website-redirect-location:/builds/' --mime-type='text/plain' put /tmp/emptyfile s3://$BUCKET/builds/info + s3cmd --acl-public --add-header='x-amz-website-redirect-location:/builds/' --mime-type='text/plain' put /tmp/emptyfile "s3://$BUCKET/builds/info" if [ -z "$NOLATEST" ]; then echo "Advertising $VERSION on $BUCKET as most recent version" - echo $VERSION | write_to_s3 s3://$BUCKET/latest + echo "$VERSION" | write_to_s3 "s3://$BUCKET/latest" fi } # Upload the index script release_index() { - sed "s,url='https://get.docker.com/',url='$(s3_url)/'," hack/install.sh | write_to_s3 s3://$BUCKET/index + sed "s,url='https://get.docker.com/',url='$(s3_url)/'," hack/install.sh | write_to_s3 "s3://$BUCKET/index" } release_test() { if [ -e "bundles/$VERSION/test" ]; then - s3cmd --acl-public sync bundles/$VERSION/test/ s3://$BUCKET/test/ + s3cmd --acl-public sync "bundles/$VERSION/test/" "s3://$BUCKET/test/" fi } setup_gpg() { # Make sure that we have our keys - mkdir -p $HOME/.gnupg/ - s3cmd sync s3://$BUCKET/ubuntu/.gnupg/ $HOME/.gnupg/ || true + mkdir -p "$HOME/.gnupg/" + s3cmd sync "s3://$BUCKET/ubuntu/.gnupg/" "$HOME/.gnupg/" || true gpg --list-keys releasedocker >/dev/null || { gpg --gen-key --batch < Date: Tue, 14 Apr 2015 18:31:52 +0200 Subject: [PATCH 457/999] hack: useless use of cat MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Jörg Thalheim --- hack/make.sh | 2 +- hack/release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index 0204756d8..d2547af12 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -63,7 +63,7 @@ DEFAULT_BUNDLES=( ubuntu ) -VERSION=$(cat ./VERSION) +VERSION=$(< ./VERSION) if command -v git &> /dev/null && git rev-parse &> /dev/null; then GITCOMMIT=$(git rev-parse --short HEAD) if [ -n "$(git status --porcelain --untracked-files=no)" ]; then diff --git a/hack/release.sh b/hack/release.sh index 4b0d32c9d..04772546f 100755 --- a/hack/release.sh +++ b/hack/release.sh @@ -60,7 +60,7 @@ if [ "$1" != '--release-regardless-of-test-failure' ]; then ) fi -VERSION=$(cat VERSION) +VERSION=$(< VERSION) BUCKET=$AWS_S3_BUCKET # These are the 2 keys we've used to sign the deb's From 6533cb973f6bab672018148fd6a67644580cc61f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 14 Apr 2015 18:43:33 +0200 Subject: [PATCH 458/999] hack/make/test-integration-cli: introduce MAKEDIR variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - every execution of dirname costs time - less repeating Signed-off-by: Jörg Thalheim --- hack/make.sh | 1 + hack/make/.dockerinit | 2 +- hack/make/.dockerinit-gccgo | 2 +- hack/make/binary | 2 +- hack/make/build-deb | 4 ++-- hack/make/cross | 2 +- hack/make/dynbinary | 4 ++-- hack/make/dyngccgo | 4 ++-- hack/make/gccgo | 2 +- hack/make/test-docker-py | 4 ++-- hack/make/test-integration | 2 +- hack/make/test-integration-cli | 12 ++++++------ hack/make/test-unit | 4 ++-- hack/make/validate-dco | 2 +- hack/make/validate-gofmt | 2 +- hack/make/validate-toml | 2 +- hack/make/validate-vet | 2 +- 17 files changed, 27 insertions(+), 26 deletions(-) diff --git a/hack/make.sh b/hack/make.sh index d2547af12..eeb26cbce 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -25,6 +25,7 @@ set -o pipefail export DOCKER_PKG='github.com/docker/docker' export SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +export MAKEDIR="$SCRIPTDIR/make" # We're a nice, sexy, little shell script, and people might try to run us; # but really, they shouldn't. We want to be in a container! diff --git a/hack/make/.dockerinit b/hack/make/.dockerinit index 36be4f822..4a62ee1ad 100644 --- a/hack/make/.dockerinit +++ b/hack/make/.dockerinit @@ -2,7 +2,7 @@ set -e IAMSTATIC="true" -source "$(dirname "$BASH_SOURCE")/.go-autogen" +source "${MAKEDIR}/.go-autogen" # dockerinit still needs to be a static binary, even if docker is dynamic go build \ diff --git a/hack/make/.dockerinit-gccgo b/hack/make/.dockerinit-gccgo index 47611d66a..022f6db00 100644 --- a/hack/make/.dockerinit-gccgo +++ b/hack/make/.dockerinit-gccgo @@ -2,7 +2,7 @@ set -e IAMSTATIC="true" -source "$(dirname "$BASH_SOURCE")/.go-autogen" +source "${MAKEDIR}/.go-autogen" # dockerinit still needs to be a static binary, even if docker is dynamic go build --compiler=gccgo \ diff --git a/hack/make/binary b/hack/make/binary index 0f57ea0d6..d3ec2939c 100644 --- a/hack/make/binary +++ b/hack/make/binary @@ -11,7 +11,7 @@ if [[ "$(uname -s)" == CYGWIN* ]]; then DEST=$(cygpath -mw $DEST) fi -source "$(dirname "$BASH_SOURCE")/.go-autogen" +source "${MAKEDIR}/.go-autogen" go build \ -o "$DEST/$BINARY_FULLNAME" \ diff --git a/hack/make/build-deb b/hack/make/build-deb index 657aa04cc..90c4c1693 100644 --- a/hack/make/build-deb +++ b/hack/make/build-deb @@ -5,7 +5,7 @@ DEST=$1 # subshell so that we can export PATH without breaking other things ( - source "$(dirname "$BASH_SOURCE")/.integration-daemon-start" + source "${MAKEDIR}/.integration-daemon-start" # we need to wrap up everything in between integration-daemon-start and # integration-daemon-stop to make sure we kill the daemon and don't hang, @@ -76,7 +76,7 @@ DEST=$1 # clean up after ourselves rm -f Dockerfile.build - source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop" + source "${MAKEDIR}/.integration-daemon-stop" [ -z "$didFail" ] # "set -e" ftw ) 2>&1 | tee -a $DEST/test.log diff --git a/hack/make/cross b/hack/make/cross index 3c5cb0401..368ebc5ab 100644 --- a/hack/make/cross +++ b/hack/make/cross @@ -28,6 +28,6 @@ for platform in $DOCKER_CROSSPLATFORMS; do export LDFLAGS_STATIC_DOCKER="" # we just need a simple client for these platforms export BUILDFLAGS=( "${ORIG_BUILDFLAGS[@]/ daemon/}" ) # remove the "daemon" build tag from platforms that aren't supported fi - source "$(dirname "$BASH_SOURCE")/binary" "$DEST/$platform" + source "${MAKEDIR}/binary" "$DEST/$platform" ) done diff --git a/hack/make/dynbinary b/hack/make/dynbinary index f9b43b0e7..e1b65b48e 100644 --- a/hack/make/dynbinary +++ b/hack/make/dynbinary @@ -4,7 +4,7 @@ set -e DEST=$1 if [ -z "$DOCKER_CLIENTONLY" ]; then - source "$(dirname "$BASH_SOURCE")/.dockerinit" + source "${MAKEDIR}/.dockerinit" hash_files "$DEST/dockerinit-$VERSION" else @@ -18,5 +18,5 @@ fi export LDFLAGS_STATIC_DOCKER='' export BUILDFLAGS=( "${BUILDFLAGS[@]/netgo /}" ) # disable netgo, since we don't need it for a dynamic binary export BUILDFLAGS=( "${BUILDFLAGS[@]/static_build /}" ) # we're not building a "static" binary here - source "$(dirname "$BASH_SOURCE")/binary" + source "${MAKEDIR}/binary" ) diff --git a/hack/make/dyngccgo b/hack/make/dyngccgo index 738e1450a..7bdd404f1 100644 --- a/hack/make/dyngccgo +++ b/hack/make/dyngccgo @@ -4,7 +4,7 @@ set -e DEST=$1 if [ -z "$DOCKER_CLIENTONLY" ]; then - source "$(dirname "$BASH_SOURCE")/.dockerinit-gccgo" + source "${MAKEDIR}/.dockerinit-gccgo" hash_files "$DEST/dockerinit-$VERSION" else @@ -19,5 +19,5 @@ fi export LDFLAGS_STATIC_DOCKER='' export BUILDFLAGS=( "${BUILDFLAGS[@]/netgo /}" ) # disable netgo, since we don't need it for a dynamic binary export BUILDFLAGS=( "${BUILDFLAGS[@]/static_build /}" ) # we're not building a "static" binary here - source "$(dirname "$BASH_SOURCE")/gccgo" + source "${MAKEDIR}/gccgo" ) diff --git a/hack/make/gccgo b/hack/make/gccgo index c85d2fbda..c61c190f3 100644 --- a/hack/make/gccgo +++ b/hack/make/gccgo @@ -6,7 +6,7 @@ BINARY_NAME="docker-$VERSION" BINARY_EXTENSION="$(binary_extension)" BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION" -source "$(dirname "$BASH_SOURCE")/.go-autogen" +source "${MAKEDIR}/.go-autogen" go build -compiler=gccgo \ -o "$DEST/$BINARY_FULLNAME" \ diff --git a/hack/make/test-docker-py b/hack/make/test-docker-py index b95cf40af..409cee0e4 100644 --- a/hack/make/test-docker-py +++ b/hack/make/test-docker-py @@ -5,7 +5,7 @@ DEST=$1 # subshell so that we can export PATH without breaking other things ( - source "$(dirname "$BASH_SOURCE")/.integration-daemon-start" + source "${MAKEDIR}/.integration-daemon-start" # we need to wrap up everything in between integration-daemon-start and # integration-daemon-stop to make sure we kill the daemon and don't hang, @@ -24,7 +24,7 @@ DEST=$1 didFail=1 fi - source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop" + source "${MAKEDIR}/.integration-daemon-stop" [ -z "$didFail" ] # "set -e" ftw ) 2>&1 | tee -a $DEST/test.log diff --git a/hack/make/test-integration b/hack/make/test-integration index b4cb6debd..206e37abf 100644 --- a/hack/make/test-integration +++ b/hack/make/test-integration @@ -5,7 +5,7 @@ DEST=$1 INIT=$DEST/../dynbinary/dockerinit-$VERSION [ -x "$INIT" ] || { - source "$(dirname "$BASH_SOURCE")/.dockerinit" + source "${MAKEDIR}/.dockerinit" INIT="$DEST/dockerinit" } export TEST_DOCKERINIT_PATH="$INIT" diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli index 3ef41d919..8e9b97570 100644 --- a/hack/make/test-integration-cli +++ b/hack/make/test-integration-cli @@ -9,23 +9,23 @@ bundle_test_integration_cli() { # subshell so that we can export PATH without breaking other things ( - source "$(dirname "$BASH_SOURCE")/.integration-daemon-start" + source "${MAKEDIR}/.integration-daemon-start" # we need to wrap up everything in between integration-daemon-start and # integration-daemon-stop to make sure we kill the daemon and don't hang, # even and especially on test failures didFail= if ! { - source "$(dirname "$BASH_SOURCE")/.ensure-frozen-images" - source "$(dirname "$BASH_SOURCE")/.ensure-httpserver" - source "$(dirname "$BASH_SOURCE")/.ensure-emptyfs" + source "${MAKEDIR}/.ensure-frozen-images" + source "${MAKEDIR}/.ensure-httpserver" + source "${MAKEDIR}/.ensure-emptyfs" bundle_test_integration_cli }; then didFail=1 fi - source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop" + source "${MAKEDIR}/.integration-daemon-stop" [ -z "$didFail" ] # "set -e" ftw -) 2>&1 | tee -a $DEST/test.log +) 2>&1 | tee -a "$DEST/test.log" diff --git a/hack/make/test-unit b/hack/make/test-unit index 15595a89c..7b6ce089e 100644 --- a/hack/make/test-unit +++ b/hack/make/test-unit @@ -39,12 +39,12 @@ bundle_test_unit() { mkdir -p "$HOME/.parallel" touch "$HOME/.parallel/ignored_vars" - echo "$TESTDIRS" | parallel --jobs "$PARALLEL_JOBS" --env _ "$(dirname "$BASH_SOURCE")/.go-compile-test-dir" + echo "$TESTDIRS" | parallel --jobs "$PARALLEL_JOBS" --env _ "${MAKEDIR}/.go-compile-test-dir" rm -rf "$HOME" else # aww, no "parallel" available - fall back to boring for test_dir in $TESTDIRS; do - "$(dirname "$BASH_SOURCE")/.go-compile-test-dir" "$test_dir" || true + "${MAKEDIR}/.go-compile-test-dir" "$test_dir" || true # don't let one directory that fails to build tank _all_ our tests! done fi diff --git a/hack/make/validate-dco b/hack/make/validate-dco index 84c47f526..5ac98728f 100644 --- a/hack/make/validate-dco +++ b/hack/make/validate-dco @@ -1,6 +1,6 @@ #!/bin/bash -source "$(dirname "$BASH_SOURCE")/.validate" +source "${MAKEDIR}/.validate" adds=$(validate_diff --numstat | awk '{ s += $1 } END { print s }') dels=$(validate_diff --numstat | awk '{ s += $2 } END { print s }') diff --git a/hack/make/validate-gofmt b/hack/make/validate-gofmt index 8fc88cc55..7ad9e8557 100644 --- a/hack/make/validate-gofmt +++ b/hack/make/validate-gofmt @@ -1,6 +1,6 @@ #!/bin/bash -source "$(dirname "$BASH_SOURCE")/.validate" +source "${MAKEDIR}/.validate" IFS=$'\n' files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) ) diff --git a/hack/make/validate-toml b/hack/make/validate-toml index 16c228d14..18f26ee75 100644 --- a/hack/make/validate-toml +++ b/hack/make/validate-toml @@ -1,6 +1,6 @@ #!/bin/bash -source "$(dirname "$BASH_SOURCE")/.validate" +source "${MAKEDIR}/.validate" IFS=$'\n' files=( $(validate_diff --diff-filter=ACMR --name-only -- 'MAINTAINERS' || true) ) diff --git a/hack/make/validate-vet b/hack/make/validate-vet index e88f7549c..febe93e5c 100644 --- a/hack/make/validate-vet +++ b/hack/make/validate-vet @@ -1,6 +1,6 @@ #!/bin/bash -source "$(dirname "$BASH_SOURCE")/.validate" +source "${MAKEDIR}/.validate" IFS=$'\n' files=( $(validate_diff --diff-filter=ACMR --name-only -- '*.go' | grep -v '^vendor/' || true) ) From 960de9c8dd3d64dd4be0e851dd835e6d9427c70c Mon Sep 17 00:00:00 2001 From: Emir Ozer Date: Wed, 15 Apr 2015 15:24:43 +0200 Subject: [PATCH 459/999] closes #8945 Signed-off-by: Emir Ozer --- .../api/remote_api_client_libraries.md | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/docs/sources/reference/api/remote_api_client_libraries.md b/docs/sources/reference/api/remote_api_client_libraries.md index d79bbd89a..3226d0eee 100644 --- a/docs/sources/reference/api/remote_api_client_libraries.md +++ b/docs/sources/reference/api/remote_api_client_libraries.md @@ -61,110 +61,116 @@ will add the libraries here. Active + Haskell + docker-hs + https://github.com/denibertovic/docker-hs + Active + + Java docker-java https://github.com/docker-java/docker-java Active - + Java docker-client https://github.com/spotify/docker-client Active - + Java jclouds-docker https://github.com/jclouds/jclouds-labs/tree/master/docker Active - + JavaScript (NodeJS) dockerode https://github.com/apocas/dockerode Install via NPM: npm install dockerode Active - + JavaScript (NodeJS) docker.io https://github.com/appersonlabs/docker.io Install via NPM: npm install docker.io Active - + JavaScript docker-js https://github.com/dgoujard/docker-js Outdated - + JavaScript (Angular) WebUI docker-cp https://github.com/13W/docker-cp Active - + JavaScript (Angular) WebUI dockerui https://github.com/crosbymichael/dockerui Active - + Perl Net::Docker https://metacpan.org/pod/Net::Docker Active - + Perl Eixo::Docker https://github.com/alambike/eixo-docker Active - + PHP Alvine http://pear.alvine.io/ (alpha) Active - + PHP Docker-PHP http://stage1.github.io/docker-php/ Active - + Python docker-py https://github.com/docker/docker-py Active - + Ruby docker-api https://github.com/swipely/docker-api Active - + Ruby docker-client https://github.com/geku/docker-client Outdated - + Rust docker-rust https://github.com/abh1nav/docker-rust Active - + Scala tugboat https://github.com/softprops/tugboat Active - + Scala reactive-docker https://github.com/almoehi/reactive-docker From b3867b889960604904a4afbab6450bb9528afe06 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Tue, 14 Apr 2015 15:02:02 -0700 Subject: [PATCH 460/999] try to modprobe bridge Signed-off-by: Jessica Frazelle --- daemon/networkdriver/bridge/driver.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index 80c32c281..eda471387 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net" "os" + "os/exec" "strconv" "strings" "sync" @@ -113,6 +114,13 @@ func InitDriver(config *Config) error { addrsv6 []net.Addr bridgeIPv6 = "fe80::1/64" ) + + // try to modprobe bridge first + // see gh#12177 + if out, err := exec.Command("modprobe", "-va", "bridge", "nf_nat").Output(); err != nil { + logrus.Warnf("Running modprobe bridge nf_nat failed with message: %s, error: %v", out, err) + } + initPortMapper() if config.DefaultIp != nil { From 767df67e3149b83255db0809f6543b449a4f652e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 10 Apr 2015 17:05:21 -0700 Subject: [PATCH 461/999] Decode container configurations into typed structures. Signed-off-by: David Calavera --- api/server/server.go | 19 +- builder/dispatchers.go | 14 +- builder/internals.go | 19 +- daemon/create.go | 8 +- daemon/daemon.go | 21 ++- daemon/exec.go | 3 +- daemon/start.go | 8 +- daemon/utils.go | 3 +- daemon/utils_test.go | 7 +- graph/history.go | 2 +- integration/api_test.go | 34 ++-- integration/container_test.go | 10 +- integration/runtime_test.go | 48 ++--- integration/utils_test.go | 9 +- runconfig/compare.go | 21 ++- runconfig/config.go | 155 +++++++++++----- runconfig/config_test.go | 36 ++++ runconfig/fixtures/container_config_1_14.json | 30 ++++ runconfig/fixtures/container_config_1_17.json | 49 +++++ runconfig/fixtures/container_config_1_19.json | 57 ++++++ runconfig/hostconfig.go | 167 +++++++++--------- runconfig/merge.go | 13 +- runconfig/parse.go | 15 +- 23 files changed, 487 insertions(+), 261 deletions(-) create mode 100644 runconfig/fixtures/container_config_1_14.json create mode 100644 runconfig/fixtures/container_config_1_17.json create mode 100644 runconfig/fixtures/container_config_1_19.json diff --git a/api/server/server.go b/api/server/server.go index 2cc966249..2abcbf91f 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -33,6 +33,7 @@ import ( "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/version" "github.com/docker/docker/registry" + "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -811,14 +812,14 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re var ( warnings []string name = r.Form.Get("name") - env = new(engine.Env) ) - if err := env.Decode(r.Body); err != nil { + config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body) + if err != nil { return err } - containerId, warnings, err := getDaemon(eng).ContainerCreate(name, env) + containerId, warnings, err := getDaemon(eng).ContainerCreate(name, config, hostConfig) if err != nil { return err } @@ -917,10 +918,6 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res if vars == nil { return fmt.Errorf("Missing parameter") } - var ( - name = vars["name"] - env = new(engine.Env) - ) // If contentLength is -1, we can assumed chunked encoding // or more technically that the length is unknown @@ -928,17 +925,21 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res // net/http otherwise seems to swallow any headers related to chunked encoding // including r.TransferEncoding // allow a nil body for backwards compatibility + var hostConfig *runconfig.HostConfig if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) { if err := checkForJson(r); err != nil { return err } - if err := env.Decode(r.Body); err != nil { + c, err := runconfig.DecodeHostConfig(r.Body) + if err != nil { return err } + + hostConfig = c } - if err := getDaemon(eng).ContainerStart(name, env); err != nil { + if err := getDaemon(eng).ContainerStart(vars["name"], hostConfig); err != nil { if err.Error() == "Container already started" { w.WriteHeader(http.StatusNotModified) return nil diff --git a/builder/dispatchers.go b/builder/dispatchers.go index 820ac17e3..e807f1aee 100644 --- a/builder/dispatchers.go +++ b/builder/dispatchers.go @@ -262,7 +262,7 @@ func run(b *Builder, args []string, attributes map[string]bool, original string) b.Config.Cmd = config.Cmd runconfig.Merge(b.Config, config) - defer func(cmd []string) { b.Config.Cmd = cmd }(cmd) + defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd) logrus.Debugf("[BUILDER] Command to be executed: %v", b.Config.Cmd) @@ -301,13 +301,15 @@ func run(b *Builder, args []string, attributes map[string]bool, original string) // Argument handling is the same as RUN. // func cmd(b *Builder, args []string, attributes map[string]bool, original string) error { - b.Config.Cmd = handleJsonArgs(args, attributes) + cmdSlice := handleJsonArgs(args, attributes) if !attributes["json"] { - b.Config.Cmd = append([]string{"/bin/sh", "-c"}, b.Config.Cmd...) + cmdSlice = append([]string{"/bin/sh", "-c"}, cmdSlice...) } - if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", b.Config.Cmd)); err != nil { + b.Config.Cmd = runconfig.NewCommand(cmdSlice...) + + if err := b.commit("", b.Config.Cmd, fmt.Sprintf("CMD %q", cmdSlice)); err != nil { return err } @@ -332,13 +334,13 @@ func entrypoint(b *Builder, args []string, attributes map[string]bool, original switch { case attributes["json"]: // ENTRYPOINT ["echo", "hi"] - b.Config.Entrypoint = parsed + b.Config.Entrypoint = runconfig.NewEntrypoint(parsed...) case len(parsed) == 0: // ENTRYPOINT [] b.Config.Entrypoint = nil default: // ENTRYPOINT echo hi - b.Config.Entrypoint = []string{"/bin/sh", "-c", parsed[0]} + b.Config.Entrypoint = runconfig.NewEntrypoint("/bin/sh", "-c", parsed[0]) } // when setting the entrypoint if a CMD was not explicitly set then diff --git a/builder/internals.go b/builder/internals.go index 728ccde8a..be980a265 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -61,7 +61,7 @@ func (b *Builder) readContext(context io.Reader) error { return nil } -func (b *Builder) commit(id string, autoCmd []string, comment string) error { +func (b *Builder) commit(id string, autoCmd *runconfig.Command, comment string) error { if b.disableCommit { return nil } @@ -71,8 +71,8 @@ func (b *Builder) commit(id string, autoCmd []string, comment string) error { b.Config.Image = b.image if id == "" { cmd := b.Config.Cmd - b.Config.Cmd = []string{"/bin/sh", "-c", "#(nop) " + comment} - defer func(cmd []string) { b.Config.Cmd = cmd }(cmd) + b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", "#(nop) "+comment) + defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd) hit, err := b.probeCache() if err != nil { @@ -182,8 +182,8 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp } cmd := b.Config.Cmd - b.Config.Cmd = []string{"/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest)} - defer func(cmd []string) { b.Config.Cmd = cmd }(cmd) + b.Config.Cmd = runconfig.NewCommand("/bin/sh", "-c", fmt.Sprintf("#(nop) %s %s in %s", cmdName, srcHash, dest)) + defer func(cmd *runconfig.Command) { b.Config.Cmd = cmd }(cmd) hit, err := b.probeCache() if err != nil { @@ -559,12 +559,13 @@ func (b *Builder) create() (*daemon.Container, error) { b.TmpContainers[c.ID] = struct{}{} fmt.Fprintf(b.OutStream, " ---> Running in %s\n", stringid.TruncateID(c.ID)) - if len(config.Cmd) > 0 { + if config.Cmd.Len() > 0 { // override the entry point that may have been picked up from the base image - c.Path = config.Cmd[0] - c.Args = config.Cmd[1:] + s := config.Cmd.Slice() + c.Path = s[0] + c.Args = s[1:] } else { - config.Cmd = []string{} + config.Cmd = runconfig.NewCommand() } return c, nil diff --git a/daemon/create.go b/daemon/create.go index da271043f..eb8a25275 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -4,7 +4,6 @@ import ( "fmt" "strings" - "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/image" "github.com/docker/docker/pkg/parsers" @@ -12,13 +11,10 @@ import ( "github.com/docker/libcontainer/label" ) -func (daemon *Daemon) ContainerCreate(name string, env *engine.Env) (string, []string, error) { +func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hostConfig *runconfig.HostConfig) (string, []string, error) { var warnings []string - config := runconfig.ContainerConfigFromJob(env) - hostConfig := runconfig.ContainerHostConfigFromJob(env) - - if len(hostConfig.LxcConf) > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { + if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { return "", warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) } if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 { diff --git a/daemon/daemon.go b/daemon/daemon.go index 76ed5a5dd..23647ecf9 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -119,12 +119,8 @@ type Daemon struct { func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "container_inspect": daemon.ContainerInspect, - "container_stats": daemon.ContainerStats, - "export": daemon.ContainerExport, "info": daemon.CmdInfo, "restart": daemon.ContainerRestart, - "stop": daemon.ContainerStop, - "wait": daemon.ContainerWait, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, } { @@ -485,7 +481,7 @@ func (daemon *Daemon) mergeAndVerifyConfig(config *runconfig.Config, img *image. return nil, err } } - if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 { + if config.Entrypoint.Len() == 0 && config.Cmd.Len() == 0 { return nil, fmt.Errorf("No command specified") } return warnings, nil @@ -577,17 +573,20 @@ func (daemon *Daemon) generateHostname(id string, config *runconfig.Config) { } } -func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint, configCmd []string) (string, []string) { +func (daemon *Daemon) getEntrypointAndArgs(configEntrypoint *runconfig.Entrypoint, configCmd *runconfig.Command) (string, []string) { var ( entrypoint string args []string ) - if len(configEntrypoint) != 0 { - entrypoint = configEntrypoint[0] - args = append(configEntrypoint[1:], configCmd...) + + cmdSlice := configCmd.Slice() + if configEntrypoint.Len() != 0 { + eSlice := configEntrypoint.Slice() + entrypoint = eSlice[0] + args = append(eSlice[1:], cmdSlice...) } else { - entrypoint = configCmd[0] - args = configCmd[1:] + entrypoint = cmdSlice[0] + args = cmdSlice[1:] } return entrypoint, args } diff --git a/daemon/exec.go b/daemon/exec.go index 46c255a7c..4787189a7 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -132,7 +132,8 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) error { return err } - entrypoint, args := d.getEntrypointAndArgs(nil, config.Cmd) + cmd := runconfig.NewCommand(config.Cmd...) + entrypoint, args := d.getEntrypointAndArgs(runconfig.NewEntrypoint(), cmd) processConfig := execdriver.ProcessConfig{ Tty: config.Tty, diff --git a/daemon/start.go b/daemon/start.go index b0b6dc75c..dbb3dd181 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -3,11 +3,10 @@ package daemon import ( "fmt" - "github.com/docker/docker/engine" "github.com/docker/docker/runconfig" ) -func (daemon *Daemon) ContainerStart(name string, env *engine.Env) error { +func (daemon *Daemon) ContainerStart(name string, hostConfig *runconfig.HostConfig) error { container, err := daemon.Get(name) if err != nil { return err @@ -21,15 +20,14 @@ func (daemon *Daemon) ContainerStart(name string, env *engine.Env) error { return fmt.Errorf("Container already started") } - // If no environment was set, then no hostconfig was passed. // This is kept for backward compatibility - hostconfig should be passed when // creating a container, not during start. - if len(env.Map()) > 0 { - hostConfig := runconfig.ContainerHostConfigFromJob(env) + if hostConfig != nil { if err := daemon.setHostConfig(container, hostConfig); err != nil { return err } } + if err := container.Start(); err != nil { container.LogEvent("die") return fmt.Errorf("Cannot start container %s: %s", name, err) diff --git a/daemon/utils.go b/daemon/utils.go index 6202e6d96..ec001ca07 100644 --- a/daemon/utils.go +++ b/daemon/utils.go @@ -42,7 +42,8 @@ func mergeLxcConfIntoOptions(hostConfig *runconfig.HostConfig) ([]string, error) // merge in the lxc conf options into the generic config map if lxcConf := hostConfig.LxcConf; lxcConf != nil { - for _, pair := range lxcConf { + lxSlice := lxcConf.Slice() + for _, pair := range lxSlice { // 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 if !strings.Contains(pair.Key, ".") { diff --git a/daemon/utils_test.go b/daemon/utils_test.go index aabbeaf6f..f81843847 100644 --- a/daemon/utils_test.go +++ b/daemon/utils_test.go @@ -7,10 +7,11 @@ import ( ) func TestMergeLxcConfig(t *testing.T) { + kv := []runconfig.KeyValuePair{ + {"lxc.cgroups.cpuset", "1,2"}, + } hostConfig := &runconfig.HostConfig{ - LxcConf: []runconfig.KeyValuePair{ - {Key: "lxc.cgroups.cpuset", Value: "1,2"}, - }, + LxcConf: runconfig.NewLxcConfig(kv), } out, err := mergeLxcConfIntoOptions(hostConfig) diff --git a/graph/history.go b/graph/history.go index 6f8581b9f..56e759a8e 100644 --- a/graph/history.go +++ b/graph/history.go @@ -31,7 +31,7 @@ func (s *TagStore) History(name string) ([]*types.ImageHistory, error) { history = append(history, &types.ImageHistory{ ID: img.ID, Created: img.Created.Unix(), - CreatedBy: strings.Join(img.ContainerConfig.Cmd, " "), + CreatedBy: strings.Join(img.ContainerConfig.Cmd.Slice(), " "), Tags: lookupMap[img.ID], Size: img.Size, Comment: img.Comment, diff --git a/integration/api_test.go b/integration/api_test.go index c527bcb92..3a795f94f 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -91,7 +91,7 @@ func TestGetContainersTop(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/sh", "-c", "cat"}, + Cmd: runconfig.NewCommand("/bin/sh", "-c", "cat"), OpenStdin: true, }, t, @@ -168,7 +168,7 @@ func TestPostCommit(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"touch", "/test"}, + Cmd: runconfig.NewCommand("touch", "/test"), }, t, ) @@ -201,9 +201,8 @@ func TestPostContainersCreate(t *testing.T) { defer mkDaemonFromEngine(eng, t).Nuke() configJSON, err := json.Marshal(&runconfig.Config{ - Image: unitTestImageID, - Memory: 33554432, - Cmd: []string{"touch", "/test"}, + Image: unitTestImageID, + Cmd: runconfig.NewCommand("touch", "/test"), }) if err != nil { t.Fatal(err) @@ -242,9 +241,8 @@ func TestPostJsonVerify(t *testing.T) { defer mkDaemonFromEngine(eng, t).Nuke() configJSON, err := json.Marshal(&runconfig.Config{ - Image: unitTestImageID, - Memory: 33554432, - Cmd: []string{"touch", "/test"}, + Image: unitTestImageID, + Cmd: runconfig.NewCommand("touch", "/test"), }) if err != nil { t.Fatal(err) @@ -330,8 +328,8 @@ func TestPostCreateNull(t *testing.T) { containerAssertExists(eng, containerID, t) c, _ := daemon.Get(containerID) - if c.Config.Cpuset != "" { - t.Fatalf("Cpuset should have been empty - instead its:" + c.Config.Cpuset) + if c.HostConfig().CpusetCpus != "" { + t.Fatalf("Cpuset should have been empty - instead its:" + c.HostConfig().CpusetCpus) } } @@ -342,7 +340,7 @@ func TestPostContainersKill(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/cat"}, + Cmd: runconfig.NewCommand("/bin/cat"), OpenStdin: true, }, t, @@ -379,7 +377,7 @@ func TestPostContainersRestart(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/top"}, + Cmd: runconfig.NewCommand("/bin/top"), OpenStdin: true, }, t, @@ -423,7 +421,7 @@ func TestPostContainersStart(t *testing.T) { eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/cat"}, + Cmd: runconfig.NewCommand("/bin/cat"), OpenStdin: true, }, t, @@ -473,7 +471,7 @@ func TestPostContainersStop(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/top"}, + Cmd: runconfig.NewCommand("/bin/top"), OpenStdin: true, }, t, @@ -525,7 +523,7 @@ func TestPostContainersWait(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/sleep", "1"}, + Cmd: runconfig.NewCommand("/bin/sleep", "1"), OpenStdin: true, }, t, @@ -561,7 +559,7 @@ func TestPostContainersAttach(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/cat"}, + Cmd: runconfig.NewCommand("/bin/cat"), OpenStdin: true, }, t, @@ -637,7 +635,7 @@ func TestPostContainersAttachStderr(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"}, + Cmd: runconfig.NewCommand("/bin/sh", "-c", "/bin/cat >&2"), OpenStdin: true, }, t, @@ -818,7 +816,7 @@ func TestPostContainersCopy(t *testing.T) { containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, - Cmd: []string{"touch", "/test.txt"}, + Cmd: runconfig.NewCommand("touch", "/test.txt"), }, t, ) diff --git a/integration/container_test.go b/integration/container_test.go index b6cbfd096..01078734c 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -14,7 +14,7 @@ func TestRestartStdin(t *testing.T) { defer nuke(daemon) container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"cat"}, + Cmd: runconfig.NewCommand("cat"), OpenStdin: true, }, @@ -79,7 +79,7 @@ func TestStdin(t *testing.T) { defer nuke(daemon) container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"cat"}, + Cmd: runconfig.NewCommand("cat"), OpenStdin: true, }, @@ -119,7 +119,7 @@ func TestTty(t *testing.T) { defer nuke(daemon) container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"cat"}, + Cmd: runconfig.NewCommand("cat"), OpenStdin: true, }, @@ -160,7 +160,7 @@ func BenchmarkRunSequential(b *testing.B) { for i := 0; i < b.N; i++ { container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"echo", "-n", "foo"}, + Cmd: runconfig.NewCommand("echo", "-n", "foo"), }, &runconfig.HostConfig{}, "", @@ -194,7 +194,7 @@ func BenchmarkRunParallel(b *testing.B) { go func(i int, complete chan error) { container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"echo", "-n", "foo"}, + Cmd: runconfig.NewCommand("echo", "-n", "foo"), }, &runconfig.HostConfig{}, "", diff --git a/integration/runtime_test.go b/integration/runtime_test.go index f3485b2b3..2e456eabf 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -255,7 +255,7 @@ func TestDaemonCreate(t *testing.T) { container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"ls", "-al"}, + Cmd: runconfig.NewCommand("ls", "-al"), }, &runconfig.HostConfig{}, "", @@ -296,15 +296,16 @@ func TestDaemonCreate(t *testing.T) { } // Test that conflict error displays correct details + cmd := runconfig.NewCommand("ls", "-al") testContainer, _, _ := daemon.Create( &runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"ls", "-al"}, + Cmd: cmd, }, &runconfig.HostConfig{}, "conflictname", ) - if _, _, err := daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID, Cmd: []string{"ls", "-al"}}, &runconfig.HostConfig{}, testContainer.Name); err == nil || !strings.Contains(err.Error(), stringid.TruncateID(testContainer.ID)) { + if _, _, err := daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID, Cmd: cmd}, &runconfig.HostConfig{}, testContainer.Name); err == nil || !strings.Contains(err.Error(), stringid.TruncateID(testContainer.ID)) { t.Fatalf("Name conflict error doesn't include the correct short id. Message was: %v", err) } @@ -316,7 +317,7 @@ func TestDaemonCreate(t *testing.T) { if _, _, err := daemon.Create( &runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{}, + Cmd: runconfig.NewCommand(), }, &runconfig.HostConfig{}, "", @@ -326,7 +327,7 @@ func TestDaemonCreate(t *testing.T) { config := &runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"/bin/ls"}, + Cmd: runconfig.NewCommand("/bin/ls"), PortSpecs: []string{"80"}, } container, _, err = daemon.Create(config, &runconfig.HostConfig{}, "") @@ -339,7 +340,7 @@ func TestDaemonCreate(t *testing.T) { // test expose 80:8000 container, warnings, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"ls", "-al"}, + Cmd: runconfig.NewCommand("ls", "-al"), PortSpecs: []string{"80:8000"}, }, &runconfig.HostConfig{}, @@ -359,7 +360,7 @@ func TestDestroy(t *testing.T) { container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"ls", "-al"}, + Cmd: runconfig.NewCommand("ls", "-al"), }, &runconfig.HostConfig{}, "") @@ -451,13 +452,14 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto)) ep[p] = struct{}{} - env := new(engine.Env) - env.Set("Image", unitTestImageID) - env.SetList("Cmd", []string{"sh", "-c", cmd}) - env.SetList("PortSpecs", []string{fmt.Sprintf("%s/%s", strPort, proto)}) - env.SetJson("ExposedPorts", ep) + c := &runconfig.Config{ + Image: unitTestImageID, + Cmd: runconfig.NewCommand("sh", "-c", cmd), + PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, + ExposedPorts: ep, + } - id, _, err = daemon.ContainerCreate(unitTestImageID, env) + id, _, err = daemon.ContainerCreate(unitTestImageID, c, &runconfig.HostConfig{}) // FIXME: this relies on the undocumented behavior of daemon.Create // which will return a nil error AND container if the exposed ports // are invalid. That behavior should be fixed! @@ -468,16 +470,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daem } - portBindings := make(map[nat.Port][]nat.PortBinding) - portBindings[p] = []nat.PortBinding{ - {}, - } - - env := new(engine.Env) - if err := env.SetJson("PortsBindings", portBindings); err != nil { - t.Fatal(err) - } - if err := daemon.ContainerStart(id, env); err != nil { + if err := daemon.ContainerStart(id, &runconfig.HostConfig{}); err != nil { t.Fatal(err) } @@ -728,12 +721,7 @@ func TestContainerNameValidation(t *testing.T) { t.Fatal(err) } - env := new(engine.Env) - if err := env.Import(config); err != nil { - t.Fatal(err) - } - - containerId, _, err := daemon.ContainerCreate(test.Name, env) + containerId, _, err := daemon.ContainerCreate(test.Name, config, &runconfig.HostConfig{}) if err != nil { if !test.Valid { continue @@ -872,7 +860,7 @@ func TestDestroyWithInitLayer(t *testing.T) { container, _, err := daemon.Create(&runconfig.Config{ Image: GetTestImage(daemon).ID, - Cmd: []string{"ls", "-al"}, + Cmd: runconfig.NewCommand("ls", "-al"), }, &runconfig.HostConfig{}, "") diff --git a/integration/utils_test.go b/integration/utils_test.go index 86b27fb73..befd924ea 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -44,11 +44,7 @@ func mkDaemon(f Fataler) *daemon.Daemon { } func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) { - env := new(engine.Env) - if err := env.Import(config); err != nil { - f.Fatal(err) - } - containerId, _, err := getDaemon(eng).ContainerCreate(name, env) + containerId, _, err := getDaemon(eng).ContainerCreate(name, config, &runconfig.HostConfig{}) if err != nil { f.Fatal(err) } @@ -60,8 +56,7 @@ func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler } func startContainer(eng *engine.Engine, id string, t Fataler) { - env := new(engine.Env) - if err := getDaemon(eng).ContainerStart(id, env); err != nil { + if err := getDaemon(eng).ContainerStart(id, &runconfig.HostConfig{}); err != nil { t.Fatal(err) } } diff --git a/runconfig/compare.go b/runconfig/compare.go index 60a21a79c..1d969e9be 100644 --- a/runconfig/compare.go +++ b/runconfig/compare.go @@ -10,25 +10,25 @@ func Compare(a, b *Config) bool { if a.AttachStdout != b.AttachStdout || a.AttachStderr != b.AttachStderr || a.User != b.User || - a.Memory != b.Memory || - a.MemorySwap != b.MemorySwap || - a.CpuShares != b.CpuShares || a.OpenStdin != b.OpenStdin || a.Tty != b.Tty { return false } - if len(a.Cmd) != len(b.Cmd) || + + if a.Cmd.Len() != b.Cmd.Len() || len(a.Env) != len(b.Env) || len(a.Labels) != len(b.Labels) || len(a.PortSpecs) != len(b.PortSpecs) || len(a.ExposedPorts) != len(b.ExposedPorts) || - len(a.Entrypoint) != len(b.Entrypoint) || + a.Entrypoint.Len() != b.Entrypoint.Len() || len(a.Volumes) != len(b.Volumes) { return false } - for i := 0; i < len(a.Cmd); i++ { - if a.Cmd[i] != b.Cmd[i] { + aCmd := a.Cmd.Slice() + bCmd := b.Cmd.Slice() + for i := 0; i < len(aCmd); i++ { + if aCmd[i] != bCmd[i] { return false } } @@ -52,8 +52,11 @@ func Compare(a, b *Config) bool { return false } } - for i := 0; i < len(a.Entrypoint); i++ { - if a.Entrypoint[i] != b.Entrypoint[i] { + + aEntrypoint := a.Entrypoint.Slice() + bEntrypoint := b.Entrypoint.Slice() + for i := 0; i < len(aEntrypoint); i++ { + if aEntrypoint[i] != bEntrypoint[i] { return false } } diff --git a/runconfig/config.go b/runconfig/config.go index 30dd1eb4c..844958be2 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -1,10 +1,103 @@ package runconfig import ( - "github.com/docker/docker/engine" + "encoding/json" + "io" + "github.com/docker/docker/nat" ) +// Entrypoint encapsulates the container entrypoint. +// It might be represented as a string or an array of strings. +// We need to override the json decoder to accept both options. +// The JSON decoder will fail if the api sends an string and +// we try to decode it into an array of string. +type Entrypoint struct { + parts []string +} + +func (e *Entrypoint) MarshalJSON() ([]byte, error) { + if e == nil { + return []byte{}, nil + } + return json.Marshal(e.Slice()) +} + +// UnmarshalJSON decoded the entrypoint whether it's a string or an array of strings. +func (e *Entrypoint) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return nil + } + + p := make([]string, 0, 1) + if err := json.Unmarshal(b, &p); err != nil { + p = append(p, string(b)) + } + e.parts = p + return nil +} + +func (e *Entrypoint) Len() int { + if e == nil { + return 0 + } + return len(e.parts) +} + +func (e *Entrypoint) Slice() []string { + if e == nil { + return nil + } + return e.parts +} + +func NewEntrypoint(parts ...string) *Entrypoint { + return &Entrypoint{parts} +} + +type Command struct { + parts []string +} + +func (e *Command) MarshalJSON() ([]byte, error) { + if e == nil { + return []byte{}, nil + } + return json.Marshal(e.Slice()) +} + +// UnmarshalJSON decoded the entrypoint whether it's a string or an array of strings. +func (e *Command) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return nil + } + + p := make([]string, 0, 1) + if err := json.Unmarshal(b, &p); err != nil { + p = append(p, string(b)) + } + e.parts = p + return nil +} + +func (e *Command) Len() int { + if e == nil { + return 0 + } + return len(e.parts) +} + +func (e *Command) Slice() []string { + if e == nil { + return nil + } + return e.parts +} + +func NewCommand(parts ...string) *Command { + return &Command{parts} +} + // Note: the Config structure should hold only portable information about the container. // Here, "portable" means "independent from the host we are running on". // Non-portable information *should* appear in HostConfig. @@ -12,10 +105,6 @@ type Config struct { Hostname string Domainname string User string - Memory int64 // FIXME: we keep it for backward compatibility, it has been moved to hostConfig. - MemorySwap int64 // FIXME: it has been moved to hostConfig. - CpuShares int64 // FIXME: it has been moved to hostConfig. - Cpuset string // FIXME: it has been moved to hostConfig and renamed to CpusetCpus. AttachStdin bool AttachStdout bool AttachStderr bool @@ -25,53 +114,37 @@ type Config struct { OpenStdin bool // Open stdin StdinOnce bool // If true, close stdin after the 1 attached client disconnects. Env []string - Cmd []string + Cmd *Command Image string // Name of the image as it was passed by the operator (eg. could be symbolic) Volumes map[string]struct{} WorkingDir string - Entrypoint []string + Entrypoint *Entrypoint NetworkDisabled bool MacAddress string OnBuild []string Labels map[string]string } -func ContainerConfigFromJob(env *engine.Env) *Config { - config := &Config{ - Hostname: env.Get("Hostname"), - Domainname: env.Get("Domainname"), - User: env.Get("User"), - Memory: env.GetInt64("Memory"), - MemorySwap: env.GetInt64("MemorySwap"), - CpuShares: env.GetInt64("CpuShares"), - Cpuset: env.Get("Cpuset"), - AttachStdin: env.GetBool("AttachStdin"), - AttachStdout: env.GetBool("AttachStdout"), - AttachStderr: env.GetBool("AttachStderr"), - Tty: env.GetBool("Tty"), - OpenStdin: env.GetBool("OpenStdin"), - StdinOnce: env.GetBool("StdinOnce"), - Image: env.Get("Image"), - WorkingDir: env.Get("WorkingDir"), - NetworkDisabled: env.GetBool("NetworkDisabled"), - MacAddress: env.Get("MacAddress"), +type ContainerConfigWrapper struct { + *Config + *hostConfigWrapper +} + +func (c ContainerConfigWrapper) HostConfig() *HostConfig { + if c.hostConfigWrapper == nil { + return new(HostConfig) } - env.GetJson("ExposedPorts", &config.ExposedPorts) - env.GetJson("Volumes", &config.Volumes) - if PortSpecs := env.GetList("PortSpecs"); PortSpecs != nil { - config.PortSpecs = PortSpecs - } - if Env := env.GetList("Env"); Env != nil { - config.Env = Env - } - if Cmd := env.GetList("Cmd"); Cmd != nil { - config.Cmd = Cmd - } + + return c.hostConfigWrapper.GetHostConfig() +} - env.GetJson("Labels", &config.Labels) +func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) { + decoder := json.NewDecoder(src) - if Entrypoint := env.GetList("Entrypoint"); Entrypoint != nil { - config.Entrypoint = Entrypoint + var w ContainerConfigWrapper + if err := decoder.Decode(&w); err != nil { + return nil, nil, err } - return config + + return w.Config, w.HostConfig(), nil } diff --git a/runconfig/config_test.go b/runconfig/config_test.go index accbd9107..e36dacbf4 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -1,7 +1,9 @@ package runconfig import ( + "bytes" "fmt" + "io/ioutil" "strings" "testing" @@ -260,5 +262,39 @@ func TestMerge(t *testing.T) { t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs) } } +} +func TestDecodeContainerConfig(t *testing.T) { + fixtures := []struct { + file string + entrypoint *Entrypoint + }{ + {"fixtures/container_config_1_14.json", NewEntrypoint()}, + {"fixtures/container_config_1_17.json", NewEntrypoint("bash")}, + {"fixtures/container_config_1_19.json", NewEntrypoint("bash")}, + } + + for _, f := range fixtures { + b, err := ioutil.ReadFile(f.file) + if err != nil { + t.Fatal(err) + } + + c, h, err := DecodeContainerConfig(bytes.NewReader(b)) + if err != nil { + t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err)) + } + + if c.Image != "ubuntu" { + t.Fatalf("Expected ubuntu image, found %s\n", c.Image) + } + + if c.Entrypoint.Len() != f.entrypoint.Len() { + t.Fatalf("Expected %v, found %v\n", f.entrypoint, c.Entrypoint) + } + + if h.Memory != 1000 { + t.Fatalf("Expected memory to be 1000, found %d\n", h.Memory) + } + } } diff --git a/runconfig/fixtures/container_config_1_14.json b/runconfig/fixtures/container_config_1_14.json new file mode 100644 index 000000000..b08334c09 --- /dev/null +++ b/runconfig/fixtures/container_config_1_14.json @@ -0,0 +1,30 @@ +{ + "Hostname":"", + "Domainname": "", + "User":"", + "Memory": 1000, + "MemorySwap":0, + "CpuShares": 512, + "Cpuset": "0,1", + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "bash" + ], + "Image":"ubuntu", + "Volumes":{ + "/tmp": {} + }, + "WorkingDir":"", + "NetworkDisabled": false, + "ExposedPorts":{ + "22/tcp": {} + }, + "RestartPolicy": { "Name": "always" } +} diff --git a/runconfig/fixtures/container_config_1_17.json b/runconfig/fixtures/container_config_1_17.json new file mode 100644 index 000000000..60fc6e25e --- /dev/null +++ b/runconfig/fixtures/container_config_1_17.json @@ -0,0 +1,49 @@ +{ + "Hostname": "", + "Domainname": "", + "User": "", + "Memory": 1000, + "MemorySwap": 0, + "CpuShares": 512, + "Cpuset": "0,1", + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Entrypoint": "bash", + "Image": "ubuntu", + "Volumes": { + "/tmp": {} + }, + "WorkingDir": "", + "NetworkDisabled": false, + "MacAddress": "12:34:56:78:9a:bc", + "ExposedPorts": { + "22/tcp": {} + }, + "SecurityOpt": [""], + "HostConfig": { + "Binds": ["/tmp:/tmp"], + "Links": ["redis3:redis"], + "LxcConf": {"lxc.utsname":"docker"}, + "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts": false, + "Privileged": false, + "ReadonlyRootfs": false, + "Dns": ["8.8.8.8"], + "DnsSearch": [""], + "ExtraHosts": null, + "VolumesFrom": ["parent", "other:ro"], + "CapAdd": ["NET_ADMIN"], + "CapDrop": ["MKNOD"], + "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, + "NetworkMode": "bridge", + "Devices": [] + } +} diff --git a/runconfig/fixtures/container_config_1_19.json b/runconfig/fixtures/container_config_1_19.json new file mode 100644 index 000000000..9a3ce205b --- /dev/null +++ b/runconfig/fixtures/container_config_1_19.json @@ -0,0 +1,57 @@ +{ + "Hostname": "", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Entrypoint": "bash", + "Image": "ubuntu", + "Labels": { + "com.example.vendor": "Acme", + "com.example.license": "GPL", + "com.example.version": "1.0" + }, + "Volumes": { + "/tmp": {} + }, + "WorkingDir": "", + "NetworkDisabled": false, + "MacAddress": "12:34:56:78:9a:bc", + "ExposedPorts": { + "22/tcp": {} + }, + "HostConfig": { + "Binds": ["/tmp:/tmp"], + "Links": ["redis3:redis"], + "LxcConf": {"lxc.utsname":"docker"}, + "Memory": 1000, + "MemorySwap": 0, + "CpuShares": 512, + "CpusetCpus": "0,1", + "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, + "PublishAllPorts": false, + "Privileged": false, + "ReadonlyRootfs": false, + "Dns": ["8.8.8.8"], + "DnsSearch": [""], + "ExtraHosts": null, + "VolumesFrom": ["parent", "other:ro"], + "CapAdd": ["NET_ADMIN"], + "CapDrop": ["MKNOD"], + "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, + "NetworkMode": "bridge", + "Devices": [], + "Ulimits": [{}], + "LogConfig": { "Type": "json-file", "Config": {} }, + "SecurityOpt": [""], + "CgroupParent": "" + } +} diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 470588a09..a25ae1835 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -1,9 +1,10 @@ package runconfig import ( + "encoding/json" + "io" "strings" - "github.com/docker/docker/engine" "github.com/docker/docker/nat" "github.com/docker/docker/pkg/ulimit" ) @@ -108,10 +109,59 @@ type LogConfig struct { Config map[string]string } +type LxcConfig struct { + values []KeyValuePair +} + +func (c *LxcConfig) MarshalJSON() ([]byte, error) { + if c == nil { + return []byte{}, nil + } + return json.Marshal(c.Slice()) +} + +func (c *LxcConfig) UnmarshalJSON(b []byte) error { + if len(b) == 0 { + return nil + } + + var kv []KeyValuePair + if err := json.Unmarshal(b, &kv); err != nil { + var h map[string]string + if err := json.Unmarshal(b, &h); err != nil { + return err + } + for k, v := range h { + kv = append(kv, KeyValuePair{k, v}) + } + } + c.values = kv + + return nil +} + +func (c *LxcConfig) Len() int { + if c == nil { + return 0 + } + return len(c.values) +} + +func (c *LxcConfig) Slice() []KeyValuePair { + if c == nil { + return nil + } + return c.values +} + +func NewLxcConfig(values []KeyValuePair) *LxcConfig { + return &LxcConfig{values} +} + type HostConfig struct { Binds []string ContainerIDFile string - LxcConf []KeyValuePair + LxcConf *LxcConfig Memory int64 // Memory limit (in bytes) MemorySwap int64 // Total memory usage (memory + swap); set `-1` to disable swap CpuShares int64 // CPU shares (relative weight vs. other containers) @@ -138,96 +188,55 @@ type HostConfig struct { CgroupParent string // Parent cgroup. } -// This is used by the create command when you want to set both the -// Config and the HostConfig in the same call -type ConfigAndHostConfig struct { - Config - HostConfig HostConfig -} - -func MergeConfigs(config *Config, hostConfig *HostConfig) *ConfigAndHostConfig { - return &ConfigAndHostConfig{ - *config, - *hostConfig, +func MergeConfigs(config *Config, hostConfig *HostConfig) *ContainerConfigWrapper { + return &ContainerConfigWrapper{ + config, + &hostConfigWrapper{InnerHostConfig: hostConfig}, } } -func ContainerHostConfigFromJob(env *engine.Env) *HostConfig { - if env.Exists("HostConfig") { - hostConfig := HostConfig{} - env.GetJson("HostConfig", &hostConfig) +type hostConfigWrapper struct { + InnerHostConfig *HostConfig `json:"HostConfig,omitempty"` + Cpuset string `json:",omitempty"` // Deprecated. Exported for backwards compatibility. - // FIXME: These are for backward compatibility, if people use these - // options with `HostConfig`, we should still make them workable. - if env.Exists("Memory") && hostConfig.Memory == 0 { - hostConfig.Memory = env.GetInt64("Memory") + *HostConfig // Deprecated. Exported to read attrubutes from json that are not in the inner host config structure. +} + +func (w hostConfigWrapper) GetHostConfig() *HostConfig { + hc := w.HostConfig + + if hc == nil && w.InnerHostConfig != nil { + hc = w.InnerHostConfig + } else if w.InnerHostConfig != nil { + if hc.Memory != 0 && w.InnerHostConfig.Memory == 0 { + w.InnerHostConfig.Memory = hc.Memory } - if env.Exists("MemorySwap") && hostConfig.MemorySwap == 0 { - hostConfig.MemorySwap = env.GetInt64("MemorySwap") + if hc.MemorySwap != 0 && w.InnerHostConfig.MemorySwap == 0 { + w.InnerHostConfig.MemorySwap = hc.MemorySwap } - if env.Exists("CpuShares") && hostConfig.CpuShares == 0 { - hostConfig.CpuShares = env.GetInt64("CpuShares") + if hc.CpuShares != 0 && w.InnerHostConfig.CpuShares == 0 { + w.InnerHostConfig.CpuShares = hc.CpuShares } - if env.Exists("Cpuset") && hostConfig.CpusetCpus == "" { - hostConfig.CpusetCpus = env.Get("Cpuset") - } - return &hostConfig + hc = w.InnerHostConfig } - hostConfig := &HostConfig{ - ContainerIDFile: env.Get("ContainerIDFile"), - Memory: env.GetInt64("Memory"), - MemorySwap: env.GetInt64("MemorySwap"), - CpuShares: env.GetInt64("CpuShares"), - CpusetCpus: env.Get("CpusetCpus"), - Privileged: env.GetBool("Privileged"), - PublishAllPorts: env.GetBool("PublishAllPorts"), - NetworkMode: NetworkMode(env.Get("NetworkMode")), - IpcMode: IpcMode(env.Get("IpcMode")), - PidMode: PidMode(env.Get("PidMode")), - ReadonlyRootfs: env.GetBool("ReadonlyRootfs"), - CgroupParent: env.Get("CgroupParent"), + if hc != nil && w.Cpuset != "" && hc.CpusetCpus == "" { + hc.CpusetCpus = w.Cpuset } + + return hc +} - // FIXME: This is for backward compatibility, if people use `Cpuset` - // in json, make it workable, we will only pass hostConfig.CpusetCpus - // to execDriver. - if env.Exists("Cpuset") && hostConfig.CpusetCpus == "" { - hostConfig.CpusetCpus = env.Get("Cpuset") +func DecodeHostConfig(src io.Reader) (*HostConfig, error) { + decoder := json.NewDecoder(src) + + var w hostConfigWrapper + if err := decoder.Decode(&w); err != nil { + return nil, err } - env.GetJson("LxcConf", &hostConfig.LxcConf) - env.GetJson("PortBindings", &hostConfig.PortBindings) - env.GetJson("Devices", &hostConfig.Devices) - env.GetJson("RestartPolicy", &hostConfig.RestartPolicy) - env.GetJson("Ulimits", &hostConfig.Ulimits) - env.GetJson("LogConfig", &hostConfig.LogConfig) - hostConfig.SecurityOpt = env.GetList("SecurityOpt") - if Binds := env.GetList("Binds"); Binds != nil { - hostConfig.Binds = Binds - } - if Links := env.GetList("Links"); Links != nil { - hostConfig.Links = Links - } - if Dns := env.GetList("Dns"); Dns != nil { - hostConfig.Dns = Dns - } - if DnsSearch := env.GetList("DnsSearch"); DnsSearch != nil { - hostConfig.DnsSearch = DnsSearch - } - if ExtraHosts := env.GetList("ExtraHosts"); ExtraHosts != nil { - hostConfig.ExtraHosts = ExtraHosts - } - if VolumesFrom := env.GetList("VolumesFrom"); VolumesFrom != nil { - hostConfig.VolumesFrom = VolumesFrom - } - if CapAdd := env.GetList("CapAdd"); CapAdd != nil { - hostConfig.CapAdd = CapAdd - } - if CapDrop := env.GetList("CapDrop"); CapDrop != nil { - hostConfig.CapDrop = CapDrop - } + hc := w.GetHostConfig() - return hostConfig + return hc, nil } diff --git a/runconfig/merge.go b/runconfig/merge.go index 68d3d6ee1..ce6697dbf 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -11,15 +11,6 @@ func Merge(userConf, imageConf *Config) error { if userConf.User == "" { userConf.User = imageConf.User } - if userConf.Memory == 0 { - userConf.Memory = imageConf.Memory - } - if userConf.MemorySwap == 0 { - userConf.MemorySwap = imageConf.MemorySwap - } - if userConf.CpuShares == 0 { - userConf.CpuShares = imageConf.CpuShares - } if len(userConf.ExposedPorts) == 0 { userConf.ExposedPorts = imageConf.ExposedPorts } else if imageConf.ExposedPorts != nil { @@ -94,8 +85,8 @@ func Merge(userConf, imageConf *Config) error { userConf.Labels = imageConf.Labels } - if len(userConf.Entrypoint) == 0 { - if len(userConf.Cmd) == 0 { + if userConf.Entrypoint.Len() == 0 { + if userConf.Cmd.Len() == 0 { userConf.Cmd = imageConf.Cmd } diff --git a/runconfig/parse.go b/runconfig/parse.go index d302330c8..973fbbfc3 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -185,21 +185,22 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe var ( parsedArgs = cmd.Args() - runCmd []string - entrypoint []string + runCmd *Command + entrypoint *Entrypoint image = cmd.Arg(0) ) if len(parsedArgs) > 1 { - runCmd = parsedArgs[1:] + runCmd = NewCommand(parsedArgs[1:]...) } if *flEntrypoint != "" { - entrypoint = []string{*flEntrypoint} + entrypoint = NewEntrypoint(*flEntrypoint) } - lxcConf, err := parseKeyValueOpts(flLxcOpts) + lc, err := parseKeyValueOpts(flLxcOpts) if err != nil { return nil, nil, cmd, err } + lxcConf := NewLxcConfig(lc) var ( domainname string @@ -288,10 +289,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe Tty: *flTty, NetworkDisabled: !*flNetwork, OpenStdin: *flStdin, - Memory: flMemory, // FIXME: for backward compatibility - MemorySwap: MemorySwap, // FIXME: for backward compatibility - CpuShares: *flCpuShares, // FIXME: for backward compatibility - Cpuset: *flCpusetCpus, // FIXME: for backward compatibility AttachStdin: attachStdin, AttachStdout: attachStdout, AttachStderr: attachStderr, From bae3023eef0ed8e0eb517ca53f6f97f1bf6786d3 Mon Sep 17 00:00:00 2001 From: Deshi Xiao Date: Wed, 15 Apr 2015 16:57:52 +0800 Subject: [PATCH 462/999] client.StatusError don't be returned as a pointer closes #12373 1. remove & from client.StatusError 2. remove * from Error method Signed-off-by: Deshi Xiao --- api/client/attach.go | 2 +- api/client/build.go | 2 +- api/client/client.go | 2 +- api/client/exec.go | 4 ++-- api/client/inspect.go | 4 ++-- api/client/run.go | 2 +- api/client/start.go | 2 +- docker/docker.go | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/api/client/attach.go b/api/client/attach.go index 77947a294..ef2b4ad12 100644 --- a/api/client/attach.go +++ b/api/client/attach.go @@ -80,7 +80,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return err } if status != 0 { - return &StatusError{StatusCode: status} + return StatusError{StatusCode: status} } return nil diff --git a/api/client/build.go b/api/client/build.go index dc54c22ff..98f7864b0 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -302,7 +302,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if jerr.Code == 0 { jerr.Code = 1 } - return &StatusError{Status: jerr.Message, StatusCode: jerr.Code} + return StatusError{Status: jerr.Message, StatusCode: jerr.Code} } return err } diff --git a/api/client/client.go b/api/client/client.go index c849fa40f..317088174 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -12,6 +12,6 @@ type StatusError struct { StatusCode int } -func (e *StatusError) Error() string { +func (e StatusError) Error() string { return fmt.Sprintf("Status: %s, Code: %d", e.Status, e.StatusCode) } diff --git a/api/client/exec.go b/api/client/exec.go index 25b7a85fd..23545ae9b 100644 --- a/api/client/exec.go +++ b/api/client/exec.go @@ -20,7 +20,7 @@ func (cli *DockerCli) CmdExec(args ...string) error { execConfig, err := runconfig.ParseExec(cmd, args) // just in case the ParseExec does not exit if execConfig.Container == "" || err != nil { - return &StatusError{StatusCode: 1} + return StatusError{StatusCode: 1} } stream, _, err := cli.call("POST", "/containers/"+execConfig.Container+"/exec", execConfig, nil) @@ -121,7 +121,7 @@ func (cli *DockerCli) CmdExec(args ...string) error { } if status != 0 { - return &StatusError{StatusCode: status} + return StatusError{StatusCode: status} } return nil diff --git a/api/client/inspect.go b/api/client/inspect.go index 8514b1ecb..f993030f9 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -26,7 +26,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { var err error if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil { fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) - return &StatusError{StatusCode: 64, + return StatusError{StatusCode: 64, Status: "Template parsing error: " + err.Error()} } } @@ -85,7 +85,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { } if status != 0 { - return &StatusError{StatusCode: status} + return StatusError{StatusCode: status} } return nil } diff --git a/api/client/run.go b/api/client/run.go index b37b6bab2..74c656af3 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -241,7 +241,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } } if status != 0 { - return &StatusError{StatusCode: status} + return StatusError{StatusCode: status} } return nil } diff --git a/api/client/start.go b/api/client/start.go index a03b8c1d2..d3dec9489 100644 --- a/api/client/start.go +++ b/api/client/start.go @@ -155,7 +155,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { return err } if status != 0 { - return &StatusError{StatusCode: status} + return StatusError{StatusCode: status} } } return nil diff --git a/docker/docker.go b/docker/docker.go index cf5b71559..d2d4986ac 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -135,7 +135,7 @@ func main() { } if err := cli.Cmd(flag.Args()...); err != nil { - if sterr, ok := err.(*client.StatusError); ok { + if sterr, ok := err.(client.StatusError); ok { if sterr.Status != "" { logrus.Println(sterr.Status) } From 7afb2347415a8e92020c037bacf5d11467c78e9b Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 15 Apr 2015 21:14:54 +0200 Subject: [PATCH 463/999] Fix TestInitializeCannotStatPathFileNameTooLong Signed-off-by: Antonio Murdaca --- volumes/volume_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/volumes/volume_test.go b/volumes/volume_test.go index caf38c8bb..b30549d37 100644 --- a/volumes/volume_test.go +++ b/volumes/volume_test.go @@ -1,7 +1,7 @@ package volumes import ( - "strings" + "os" "testing" "github.com/docker/docker/pkg/stringutils" @@ -33,8 +33,8 @@ func TestInitializeCannotMkdirOnNonExistentPath(t *testing.T) { t.Fatal("Expected not to initialize volume with a non existent path") } - if !strings.Contains(err.Error(), "mkdir : no such file or directory") { - t.Fatalf("Expected to get mkdir no such file or directory, got %s", err) + if !os.IsNotExist(err) { + t.Fatalf("Expected to get ErrNotExist error, got %s", err) } } @@ -49,7 +49,7 @@ func TestInitializeCannotStatPathFileNameTooLong(t *testing.T) { t.Fatal("Expected not to initialize volume with a non existent path") } - if !strings.Contains(err.Error(), "file name too long") { - t.Fatalf("Expected to get ENAMETOOLONG error, got %s", err) + if os.IsNotExist(err) { + t.Fatal("Expected to not get ErrNotExist") } } From 05641ccffc5088a382fa3bfb21f1276ccb6c1fc0 Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Wed, 15 Apr 2015 00:16:43 -0700 Subject: [PATCH 464/999] Change syslog format and facility This patch changes two things 1. Set facility to LOG_DAEMON 2. Remove ": " from tag so that the tag + pid become a single column in the log Signed-off-by: Darren Shepherd --- daemon/logger/syslog/syslog.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/logger/syslog/syslog.go b/daemon/logger/syslog/syslog.go index 4de14aacf..a250d6e93 100644 --- a/daemon/logger/syslog/syslog.go +++ b/daemon/logger/syslog/syslog.go @@ -14,7 +14,7 @@ type Syslog struct { } func New(tag string) (logger.Logger, error) { - log, err := syslog.New(syslog.LOG_USER, fmt.Sprintf("%s: <%s> ", path.Base(os.Args[0]), tag)) + log, err := syslog.New(syslog.LOG_DAEMON, fmt.Sprintf("%s/%s", path.Base(os.Args[0]), tag)) if err != nil { return nil, err } From 6860c75b7b4011fd4cc48f5d9a1458e49fb86b60 Mon Sep 17 00:00:00 2001 From: Sabin Basyal Date: Wed, 15 Apr 2015 11:23:28 -0700 Subject: [PATCH 465/999] The link to issue 407 was broken The link to issue 407 was broken. The old link was: https://github.com/docker/docker/issues/407%20kernel%20versions The link must be: https://github.com/docker/docker/issues/407 Signed-off-by: Sabin Basyal --- docs/sources/installation/debian.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index 709a44d41..e3fb6e292 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -39,7 +39,7 @@ Which should download the `ubuntu` image, and then start `bash` in a container. Docker requires Kernel 3.8+, while Wheezy ships with Kernel 3.2 (for more details on why 3.8 is required, see discussion on -[bug #407](https://github.com/docker/docker/issues/407%20kernel%20versions)). +[bug #407](https://github.com/docker/docker/issues/407)). Fortunately, wheezy-backports currently has [Kernel 3.16 ](https://packages.debian.org/search?suite=wheezy-backports§ion=all&arch=any&searchon=names&keywords=linux-image-amd64), From d1855c6cc0cb28fed7426ee3024f147e74ac828e Mon Sep 17 00:00:00 2001 From: Steven Taylor Date: Wed, 15 Apr 2015 15:30:09 -0700 Subject: [PATCH 466/999] What if authConfig or factory is Null? Signed-off-by: Steven Taylor --- registry/session.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/session.go b/registry/session.go index c62745b5b..dce4accd0 100644 --- a/registry/session.go +++ b/registry/session.go @@ -53,7 +53,7 @@ func NewSession(authConfig *AuthConfig, factory *requestdecorator.RequestFactory if err != nil { return nil, err } - if info.Standalone { + if info.Standalone && authConfig != nil && factory != nil { logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", r.indexEndpoint.String()) dec := requestdecorator.NewAuthDecorator(authConfig.Username, authConfig.Password) factory.AddDecorator(dec) From a5f7c4aa31fa1ee2a3bebf4d38f5fda7a4a28a0d Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 15 Apr 2015 17:39:34 -0700 Subject: [PATCH 467/999] Ensure state is destroyed on daemont restart Signed-off-by: Michael Crosby --- daemon/daemon.go | 13 +------------ daemon/execdriver/native/driver.go | 14 +++++--------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index e2ca56805..bf8eef6dc 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -273,19 +273,8 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err if err := container.ToDisk(); err != nil { logrus.Debugf("saving stopped state to disk %s", err) } - - info := daemon.execDriver.Info(container.ID) - if !info.IsRunning() { - logrus.Debugf("Container %s was supposed to be running but is not.", container.ID) - - logrus.Debug("Marking as stopped") - - container.SetStopped(&execdriver.ExitStatus{ExitCode: -127}) - if err := container.ToDisk(); err != nil { - return err - } - } } + return nil } diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 6caa78390..fba22c1c2 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -268,29 +268,25 @@ func (d *driver) Unpause(c *execdriver.Command) error { func (d *driver) Terminate(c *execdriver.Command) error { defer d.cleanContainer(c.ID) - // lets check the start time for the process - active := d.activeContainers[c.ID] - if active == nil { - return fmt.Errorf("active container for %s does not exist", c.ID) + container, err := d.factory.Load(c.ID) + if err != nil { + return err } - state, err := active.State() + defer container.Destroy() + state, err := container.State() if err != nil { return err } pid := state.InitProcessPid - currentStartTime, err := system.GetProcessStartTime(pid) if err != nil { return err } - if state.InitProcessStartTime == currentStartTime { err = syscall.Kill(pid, 9) syscall.Wait4(pid, nil, 0, nil) } - return err - } func (d *driver) Info(id string) execdriver.Info { From fe8fb24b530016e56ab584526c093daccacf6040 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Wed, 15 Apr 2015 19:02:01 -0700 Subject: [PATCH 468/999] In with the old menu layout Signed-off-by: Mary Anthony --- docs/Dockerfile | 31 ++++++++++++++----- docs/mkdocs.yml | 15 +++++++-- .../reference/api/hub_registry_spec.md | 6 ++-- docs/sources/reference/api/registry_api.md | 4 +-- .../api/registry_api_client_libraries.md | 2 +- 5 files changed, 42 insertions(+), 16 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index 7914abf38..aa97313ae 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -37,15 +37,32 @@ COPY ./release.sh release.sh # #ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/mkdocs.yml /docs/mkdocs-distribution.yml -#ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md /docs/sources/distribution/overview.md -#RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/overview.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/notifications.png /docs/sources/registry/images/notifications.png +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/registry.png /docs/sources/registry/images/registry.png -#ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/install.md /docs/sources/distribution/install.md -#RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/install.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md /docs/sources/registry/overview.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/overview.md -#ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/architecture.md /docs/sources/distribution/architecture.md -#RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/distribution/architecture.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/deploying.md /docs/sources/registry/deploying.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/deploying.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/configuration.md /docs/sources/registry/configuration.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/configuration.md + +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storagedrivers.md /docs/sources/registry/storagedrivers.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/storagedrivers.md + +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/notifications.md /docs/sources/registry/notifications.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/notifications.md + +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/api.md /docs/sources/registry/spec/api.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/api.md + +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/json.md /docs/sources/registry/spec/json.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/json.md + +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/auth/token.md /docs/sources/registry/spec/auth/token.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/auth/token.md # Docker Swarm #ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/mkdocs.yml /docs/mkdocs-swarm.yml @@ -88,4 +105,4 @@ ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/word RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/wordpress.md # Then build everything together, ready for mkdocs -RUN /docs/build.sh +RUN /docs/build.sh \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index fd56ad15c..f159ff28b 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -133,10 +133,19 @@ pages: - ['swarm/scheduler/filter.md', 'Reference', 'Swarm filters'] - ['swarm/API.md', 'Reference', 'Swarm API'] - ['reference/api/index.md', '**HIDDEN**'] +- ['registry/overview.md', 'Reference', 'Docker Registry 2.0'] +- ['registry/deploying.md', 'Reference', '    ▪  Deploy a registry' ] +- ['registry/configuration.md', 'Reference', '    ▪  Configure a registry' ] +- ['registry/storagedrivers.md', 'Reference', '    ▪  Storage driver model' ] +- ['registry/notifications.md', 'Reference', '    ▪  Work with notifications' ] +- ['registry/spec/api.md', 'Reference', '    ▪  Registry Service API v2' ] +- ['registry/spec/json.md', 'Reference', '    ▪  JSON format' ] +- ['registry/spec/auth/token.md', 'Reference', '    ▪  Authenticate via central service' ] +- ['reference/api/hub_registry_spec.md', 'Reference', 'Docker Hub and Registry 1.0'] +- ['reference/api/registry_api.md', 'Reference', '    ▪ Docker Registry API v1'] +- ['reference/api/registry_api_client_libraries.md', 'Reference', '    ▪ Docker Registry 1.0 API Client Libraries'] +#- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0'] - ['reference/api/docker-io_api.md', 'Reference', 'Docker Hub API'] -- ['reference/api/registry_api.md', 'Reference', 'Docker Registry API'] -- ['reference/api/registry_api_client_libraries.md', 'Reference', 'Docker Registry API Client Libraries'] -- ['reference/api/hub_registry_spec.md', 'Reference', 'Docker Hub and Registry Spec'] #- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0'] - ['reference/api/docker_remote_api.md', 'Reference', 'Docker Remote API'] - ['reference/api/docker_remote_api_v1.19.md', 'Reference', 'Docker Remote API v1.19'] diff --git a/docs/sources/reference/api/hub_registry_spec.md b/docs/sources/reference/api/hub_registry_spec.md index f01007587..2999f453f 100644 --- a/docs/sources/reference/api/hub_registry_spec.md +++ b/docs/sources/reference/api/hub_registry_spec.md @@ -2,7 +2,7 @@ page_title: Registry Documentation page_description: Documentation for docker Registry and Registry API page_keywords: docker, registry, api, hub -# The Docker Hub and the Registry spec +# The Docker Hub and the Registry 1.0 spec ## The three roles @@ -28,9 +28,9 @@ The Docker Hub is authoritative for that information. There is only one instance of the Docker Hub, run and managed by Docker Inc. -### Registry +### Docker Registry 1.0 -The registry has the following characteristics: +The 1.0 registry has the following characteristics: - It stores the images and the graph for a set of repositories - It does not have user accounts data diff --git a/docs/sources/reference/api/registry_api.md b/docs/sources/reference/api/registry_api.md index 54a158934..13a51356f 100644 --- a/docs/sources/reference/api/registry_api.md +++ b/docs/sources/reference/api/registry_api.md @@ -2,11 +2,11 @@ page_title: Registry API page_description: API Documentation for Docker Registry page_keywords: API, Docker, index, registry, REST, documentation -# Docker Registry API +# Docker Registry API v1 ## Introduction - - This is the REST API for the Docker Registry + - This is the REST API for the Docker Registry 1.0 - It stores the images and the graph for a set of repositories - It does not have user accounts data - It has no notion of user accounts or authorization diff --git a/docs/sources/reference/api/registry_api_client_libraries.md b/docs/sources/reference/api/registry_api_client_libraries.md index 6977af3cc..811ac859e 100644 --- a/docs/sources/reference/api/registry_api_client_libraries.md +++ b/docs/sources/reference/api/registry_api_client_libraries.md @@ -2,7 +2,7 @@ page_title: Registry API Client Libraries page_description: Various client libraries available to use with the Docker registry API page_keywords: API, Docker, index, registry, REST, documentation, clients, C#, Erlang, Go, Groovy, Java, JavaScript, Perl, PHP, Python, Ruby, Rust, Scala -# Docker Registry API Client Libraries +# Docker Registry 1.0 API Client Libraries These libraries have not been tested by the Docker maintainers for compatibility. Please file issues with the library owners. If you find From dc104ccb40b66a89611f11737d78a2ad31102427 Mon Sep 17 00:00:00 2001 From: Jason Smith Date: Mon, 23 Mar 2015 19:46:22 -0400 Subject: [PATCH 469/999] added documentation for functions Signed-off-by: Jason Smith --- pkg/etchosts/etchosts.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pkg/etchosts/etchosts.go b/pkg/etchosts/etchosts.go index d7edef27f..bef4a480c 100644 --- a/pkg/etchosts/etchosts.go +++ b/pkg/etchosts/etchosts.go @@ -8,16 +8,19 @@ import ( "regexp" ) +// Structure for a single host record type Record struct { Hosts string IP string } +// Writes record to file and returns bytes written or error func (r Record) WriteTo(w io.Writer) (int64, error) { n, err := fmt.Fprintf(w, "%s\t%s\n", r.IP, r.Hosts) return int64(n), err } +// Default hosts config records slice var defaultContent = []Record{ {Hosts: "localhost", IP: "127.0.0.1"}, {Hosts: "localhost ip6-localhost ip6-loopback", IP: "::1"}, @@ -27,9 +30,14 @@ var defaultContent = []Record{ {Hosts: "ip6-allrouters", IP: "ff02::2"}, } +// Build function +// path is path to host file string required +// IP, hostname, and domainname set main record leave empty for no master record +// extraContent is an array of extra host records. func Build(path, IP, hostname, domainname string, extraContent []Record) error { content := bytes.NewBuffer(nil) if IP != "" { + //set main record var mainRec Record mainRec.IP = IP if domainname != "" { @@ -41,13 +49,13 @@ func Build(path, IP, hostname, domainname string, extraContent []Record) error { return err } } - + // Write defaultContent slice to buffer for _, r := range defaultContent { if _, err := r.WriteTo(content); err != nil { return err } } - + // Write extra content from function arguments for _, r := range extraContent { if _, err := r.WriteTo(content); err != nil { return err @@ -57,6 +65,10 @@ func Build(path, IP, hostname, domainname string, extraContent []Record) error { return ioutil.WriteFile(path, content.Bytes(), 0644) } +// Update all IP addresses where hostname matches. +// path is path to host file +// IP is new IP address +// hostname is hostname to search for to replace IP func Update(path, IP, hostname string) error { old, err := ioutil.ReadFile(path) if err != nil { From 73bf9b5c195170b3d71f86b285ac12e50d26ef51 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Thu, 16 Apr 2015 17:36:45 +0800 Subject: [PATCH 470/999] add err check before getting term Signed-off-by: Ma Shimiao --- daemon/execdriver/lxc/driver.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 1637bc2c6..15f57bfe0 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -85,16 +85,21 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba dataPath = d.containerDir(c.ID) ) + container, err := d.createContainer(c) + if err != nil { + return execdriver.ExitStatus{ExitCode: -1}, err + } + if c.ProcessConfig.Tty { term, err = NewTtyConsole(&c.ProcessConfig, pipes) } else { term, err = execdriver.NewStdConsole(&c.ProcessConfig, pipes) } - c.ProcessConfig.Terminal = term - container, err := d.createContainer(c) if err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } + c.ProcessConfig.Terminal = term + d.Lock() d.activeContainers[c.ID] = &activeContainer{ container: container, From 93cdb0071be29cde5e9f5574926ae628ef4cfc41 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Thu, 16 Apr 2015 14:31:52 +0800 Subject: [PATCH 471/999] optimize code to clarify logic Signed-off-by: Ma Shimiao --- daemon/create.go | 31 ++++++------------------------- daemon/daemon.go | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/daemon/create.go b/daemon/create.go index eb8a25275..db6035507 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -2,7 +2,6 @@ package daemon import ( "fmt" - "strings" "github.com/docker/docker/graph" "github.com/docker/docker/image" @@ -12,27 +11,9 @@ import ( ) func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hostConfig *runconfig.HostConfig) (string, []string, error) { - var warnings []string - - if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { - return "", warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) - } - if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 { - return "", warnings, fmt.Errorf("Minimum memory limit allowed is 4MB") - } - if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit { - warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.\n") - hostConfig.Memory = 0 - } - if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit { - warnings = append(warnings, "Your kernel does not support swap limit capabilities. Limitation discarded.\n") - hostConfig.MemorySwap = -1 - } - if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory { - return "", warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") - } - if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { - return "", warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") + warnings, err := daemon.verifyHostConfig(hostConfig) + if err != nil { + return "", warnings, err } container, buildWarnings, err := daemon.Create(config, hostConfig, name) @@ -46,9 +27,6 @@ func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hos } return "", warnings, err } - if !container.Config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled { - warnings = append(warnings, "IPv4 forwarding is disabled.\n") - } container.LogEvent("create") warnings = append(warnings, buildWarnings...) @@ -80,6 +58,9 @@ func (daemon *Daemon) Create(config *runconfig.Config, hostConfig *runconfig.Hos if warnings, err = daemon.mergeAndVerifyConfig(config, img); err != nil { return nil, nil, err } + if !config.NetworkDisabled && daemon.SystemConfig().IPv4ForwardingDisabled { + warnings = append(warnings, "IPv4 forwarding is disabled.\n") + } if hostConfig == nil { hostConfig = &runconfig.HostConfig{} } diff --git a/daemon/daemon.go b/daemon/daemon.go index 6f50dc9f3..30a1f4be3 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1224,3 +1224,30 @@ func checkKernel() error { } return nil } + +func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]string, error) { + var warnings []string + + if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { + return warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) + } + if hostConfig.Memory != 0 && hostConfig.Memory < 4194304 { + return warnings, fmt.Errorf("Minimum memory limit allowed is 4MB") + } + if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit { + warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.\n") + hostConfig.Memory = 0 + } + if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit { + warnings = append(warnings, "Your kernel does not support swap limit capabilities. Limitation discarded.\n") + hostConfig.MemorySwap = -1 + } + if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory { + return warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") + } + if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { + return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") + } + + return warnings, nil +} From 7e01ecc119ea3871058309a47a3f9cbf2a9483dd Mon Sep 17 00:00:00 2001 From: Yan Feng Date: Thu, 16 Apr 2015 10:56:15 -0400 Subject: [PATCH 472/999] Fix a typo in docker/daemon/state.go Signed-off-by: Yan Feng --- daemon/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/state.go b/daemon/state.go index 6387e6fc5..4119d0e6c 100644 --- a/daemon/state.go +++ b/daemon/state.go @@ -183,7 +183,7 @@ func (s *State) setStopped(exitStatus *execdriver.ExitStatus) { s.waitChan = make(chan struct{}) } -// SetRestarting is when docker hanldes the auto restart of containers when they are +// SetRestarting is when docker handles the auto restart of containers when they are // in the middle of a stop and being restarted again func (s *State) SetRestarting(exitStatus *execdriver.ExitStatus) { s.Lock() From dcff07d03d9accbedd2467fe9c7c10fba7c2b35c Mon Sep 17 00:00:00 2001 From: Dave Henderson Date: Tue, 7 Apr 2015 20:37:36 -0400 Subject: [PATCH 473/999] Adding a verbose time option to output formatted timestamps Fixes #11413 Signed-off-by: Dave Henderson --- api/client/history.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/client/history.go b/api/client/history.go index 4ac46d92c..82a012265 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -46,11 +46,11 @@ func (cli *DockerCli) CmdHistory(args ...string) error { fmt.Fprintf(w, stringid.TruncateID(entry.ID)) } if !*quiet { - fmt.Fprintf(w, "\t%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0)))) - if *noTrunc { + fmt.Fprintf(w, "\t%s\t", time.Unix(entry.Created, 0).Format(time.RFC3339)) fmt.Fprintf(w, "%s\t", entry.CreatedBy) } else { + fmt.Fprintf(w, "\t%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0)))) fmt.Fprintf(w, "%s\t", stringutils.Truncate(entry.CreatedBy, 45)) } fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size))) From ae5cf30c7c6630d201ef14e2e460f4164f58a261 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Thu, 16 Apr 2015 08:29:04 -0700 Subject: [PATCH 474/999] Add -H|--human flag to `docker history` Add a flag to print sizes and dates in human readable format. Signed-off-by: Arnaud Porterie --- api/client/history.go | 17 ++++++++++++++--- docs/man/docker-history.1.md | 3 +++ docs/sources/reference/commandline/cli.md | 1 + 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/api/client/history.go b/api/client/history.go index 82a012265..79c6f3f7a 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -18,6 +18,7 @@ import ( // Usage: docker history [OPTIONS] IMAGE func (cli *DockerCli) CmdHistory(args ...string) error { cmd := cli.Subcmd("history", "IMAGE", "Show the history of an image", true) + human := cmd.Bool([]string{"H", "-human"}, true, "Print sizes and dates in human readable format") quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") cmd.Require(flag.Exact, 1) @@ -46,14 +47,24 @@ func (cli *DockerCli) CmdHistory(args ...string) error { fmt.Fprintf(w, stringid.TruncateID(entry.ID)) } if !*quiet { - if *noTrunc { + if *human { + fmt.Fprintf(w, "\t%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0)))) + } else { fmt.Fprintf(w, "\t%s\t", time.Unix(entry.Created, 0).Format(time.RFC3339)) + } + + if *noTrunc { fmt.Fprintf(w, "%s\t", entry.CreatedBy) } else { - fmt.Fprintf(w, "\t%s ago\t", units.HumanDuration(time.Now().UTC().Sub(time.Unix(entry.Created, 0)))) fmt.Fprintf(w, "%s\t", stringutils.Truncate(entry.CreatedBy, 45)) } - fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size))) + + if *human { + fmt.Fprintf(w, "%s\t", units.HumanSize(float64(entry.Size))) + } else { + fmt.Fprintf(w, "%d\t", entry.Size) + } + fmt.Fprintf(w, "%s", entry.Comment) } fmt.Fprintf(w, "\n") diff --git a/docs/man/docker-history.1.md b/docs/man/docker-history.1.md index 2b38d83e6..268e378d0 100644 --- a/docs/man/docker-history.1.md +++ b/docs/man/docker-history.1.md @@ -19,6 +19,9 @@ Show the history of when and how an image was created. **--help** Print usage statement +**-H**. **--human**=*true*|*false* + Print sizes and dates in human readable format. The default is *true*. + **--no-trunc**=*true*|*false* Don't truncate output. The default is *false*. diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 607f67076..70f67d13a 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -1191,6 +1191,7 @@ This will create a new Bash session in the container `ubuntu_bash`. Show the history of an image + -H, --human=true Print sizes and dates in human readable format --no-trunc=false Don't truncate output -q, --quiet=false Only show numeric IDs From e41192a3f8cbfbbfecde03f58a3b2be2b1afd836 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 14 Apr 2015 01:34:34 +0200 Subject: [PATCH 475/999] Remove job from restart Signed-off-by: Antonio Murdaca --- api/server/server.go | 13 ++++++++++--- daemon/daemon.go | 1 - daemon/restart.go | 20 +++----------------- 3 files changed, 13 insertions(+), 21 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index d4f445b93..d40413a4e 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -837,12 +837,19 @@ func postContainersRestart(eng *engine.Engine, version version.Version, w http.R if vars == nil { return fmt.Errorf("Missing parameter") } - job := eng.Job("restart", vars["name"]) - job.Setenv("t", r.Form.Get("t")) - if err := job.Run(); err != nil { + + s, err := strconv.Atoi(r.Form.Get("t")) + if err != nil { return err } + + d := getDaemon(eng) + if err := d.ContainerRestart(vars["name"], s); err != nil { + return err + } + w.WriteHeader(http.StatusNoContent) + return nil } diff --git a/daemon/daemon.go b/daemon/daemon.go index 6f50dc9f3..959907d59 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -120,7 +120,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "container_inspect": daemon.ContainerInspect, "info": daemon.CmdInfo, - "restart": daemon.ContainerRestart, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, } { diff --git a/daemon/restart.go b/daemon/restart.go index 1bd2f8ca1..86cc97d7e 100644 --- a/daemon/restart.go +++ b/daemon/restart.go @@ -1,27 +1,13 @@ package daemon -import ( - "fmt" +import "fmt" - "github.com/docker/docker/engine" -) - -func (daemon *Daemon) ContainerRestart(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s CONTAINER\n", job.Name) - } - var ( - name = job.Args[0] - t = 10 - ) - if job.EnvExists("t") { - t = job.GetenvInt("t") - } +func (daemon *Daemon) ContainerRestart(name string, seconds int) error { container, err := daemon.Get(name) if err != nil { return err } - if err := container.Restart(int(t)); err != nil { + if err := container.Restart(seconds); err != nil { return fmt.Errorf("Cannot restart container %s: %s\n", name, err) } container.LogEvent("restart") From 8232cc777e329a47e123dbdc42411dae65288a80 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 13 Apr 2015 22:53:54 -0400 Subject: [PATCH 476/999] Make sockRequestRaw return reader, not []byte Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 40 ++++++++++++++++--- integration-cli/docker_utils.go | 28 +++++++++---- 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index d48b94572..68561a587 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -369,10 +369,15 @@ func TestBuildApiDockerfilePath(t *testing.T) { t.Fatalf("failed to close tar archive: %v", err) } - _, out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") + _, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") if err == nil { + out, _ := readBody(body) t.Fatalf("Build was supposed to fail: %s", out) } + out, err := readBody(body) + if err != nil { + t.Fatal(err) + } if !strings.Contains(string(out), "must be within the build context") { t.Fatalf("Didn't complain about leaving build context: %s", out) @@ -393,10 +398,14 @@ RUN find /tmp/`, } defer server.Close() - _, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") + _, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") if err != nil { t.Fatalf("Build failed: %s", err) } + buf, err := readBody(body) + if err != nil { + t.Fatal(err) + } // Make sure Dockerfile exists. // Make sure 'baz' doesn't exist ANYWHERE despite being mentioned in the URL @@ -419,10 +428,15 @@ RUN echo from dockerfile`, } defer git.Close() - _, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + _, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") if err != nil { + buf, _ := readBody(body) t.Fatalf("Build failed: %s\n%q", err, buf) } + buf, err := readBody(body) + if err != nil { + t.Fatal(err) + } out := string(buf) if !strings.Contains(out, "from dockerfile") { @@ -445,10 +459,15 @@ RUN echo from Dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - _, buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") + _, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") if err != nil { + buf, _ := readBody(body) t.Fatalf("Build failed: %s\n%q", err, buf) } + buf, err := readBody(body) + if err != nil { + t.Fatal(err) + } out := string(buf) if !strings.Contains(out, "from baz") { @@ -472,10 +491,14 @@ RUN echo from dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - _, buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + _, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") if err != nil { t.Fatalf("Build failed: %s", err) } + buf, err := readBody(body) + if err != nil { + t.Fatal(err) + } out := string(buf) if !strings.Contains(out, "from Dockerfile") { @@ -503,10 +526,15 @@ func TestBuildApiDockerfileSymlink(t *testing.T) { t.Fatalf("failed to close tar archive: %v", err) } - _, out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") + _, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") if err == nil { + out, _ := readBody(body) t.Fatalf("Build was supposed to fail: %s", out) } + out, err := readBody(body) + if err != nil { + t.Fatal(err) + } // The reason the error is "Cannot locate specified Dockerfile" is because // in the builder, the symlink is resolved within the context, therefore diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 4984b578b..ab200e124 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -22,6 +22,7 @@ import ( "time" "github.com/docker/docker/api" + "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stringutils" ) @@ -304,20 +305,27 @@ func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) return -1, nil, err } - return sockRequestRaw(method, endpoint, jsonData, "application/json") + status, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json") + if err != nil { + b, _ := ioutil.ReadAll(body) + return status, b, err + } + var b []byte + b, err = readBody(body) + return status, b, err } -func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, []byte, error) { +func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io.ReadCloser, error) { c, err := sockConn(time.Duration(10 * time.Second)) if err != nil { return -1, nil, fmt.Errorf("could not dial docker daemon: %v", err) } client := httputil.NewClientConn(c, nil) - defer client.Close() req, err := http.NewRequest(method, endpoint, data) if err != nil { + client.Close() return -1, nil, fmt.Errorf("could not create new request: %v", err) } @@ -328,17 +336,23 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, [] resp, err := client.Do(req) if err != nil { + client.Close() return -1, nil, fmt.Errorf("could not perform request: %v", err) } - defer resp.Body.Close() + body := ioutils.NewReadCloserWrapper(resp.Body, func() error { + defer client.Close() + return resp.Body.Close() + }) if resp.StatusCode != http.StatusOK { - body, _ := ioutil.ReadAll(resp.Body) return resp.StatusCode, body, fmt.Errorf("received status != 200 OK: %s", resp.Status) } - b, err := ioutil.ReadAll(resp.Body) + return resp.StatusCode, body, err +} - return resp.StatusCode, b, err +func readBody(b io.ReadCloser) ([]byte, error) { + defer b.Close() + return ioutil.ReadAll(b) } func deleteContainer(container string) error { From 6f5b895bc767585be9a8e1109672fb4946e56f15 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 13 Apr 2015 23:02:29 -0400 Subject: [PATCH 477/999] Move SaveAndThenload to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_images_test.go | 31 ++++++++++++ integration/api_test.go | 61 ----------------------- 2 files changed, 31 insertions(+), 61 deletions(-) diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index ee403a188..083d63204 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -3,6 +3,7 @@ package main import ( "encoding/json" "net/url" + "strings" "testing" "github.com/docker/docker/api/types" @@ -67,3 +68,33 @@ func TestApiImagesFilter(t *testing.T) { logDone("images - filter param is applied") } + +func TestApiImagesSaveAndLoad(t *testing.T) { + out, err := buildImage("saveandload", "FROM hello-world\nENV FOO bar", false) + if err != nil { + t.Fatal(err) + } + id := strings.TrimSpace(out) + defer deleteImages("saveandload") + + _, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") + if err != nil { + t.Fatal(err) + } + defer body.Close() + + dockerCmd(t, "rmi", id) + + _, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar") + if err != nil { + t.Fatal(err) + } + defer loadBody.Close() + + out, _, _ = dockerCmd(t, "inspect", "--format='{{ .Id }}'", id) + if strings.TrimSpace(out) != id { + t.Fatal("load did not work properly") + } + + logDone("images API - save and load") +} diff --git a/integration/api_test.go b/integration/api_test.go index 3a795f94f..4808aa02d 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -23,67 +23,6 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -func TestSaveImageAndThenLoad(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - // save image - r := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/images/"+unitTestImageID+"/get", nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } - tarball := r.Body - - // delete the image - r = httptest.NewRecorder() - req, err = http.NewRequest("DELETE", "/images/"+unitTestImageID, nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } - - // make sure there is no image - r = httptest.NewRecorder() - req, err = http.NewRequest("GET", "/images/"+unitTestImageID+"/get", nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusNotFound { - t.Fatalf("%d NotFound expected, received %d\n", http.StatusNotFound, r.Code) - } - - // load the image - r = httptest.NewRecorder() - req, err = http.NewRequest("POST", "/images/load", tarball) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } - - // finally make sure the image is there - r = httptest.NewRecorder() - req, err = http.NewRequest("GET", "/images/"+unitTestImageID+"/get", nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } -} - func TestGetContainersTop(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From d9e4b14346c6a93bee8c24803a8538dc2d74911d Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 14 Apr 2015 17:14:29 -0400 Subject: [PATCH 478/999] Move TestGetContainersTop to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 41 ++++++++++ integration/api_test.go | 74 ------------------- 2 files changed, 41 insertions(+), 74 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 68561a587..26a8f115d 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -624,3 +624,44 @@ func TestContainerApiPause(t *testing.T) { logDone("container REST API - check POST containers/pause and unpause") } + +func TestContainerApiTop(t *testing.T) { + defer deleteAllContainers() + out, _, _ := dockerCmd(t, "run", "-d", "-i", "busybox", "/bin/sh", "-c", "cat") + id := strings.TrimSpace(out) + if err := waitRun(id); err != nil { + t.Fatal(err) + } + + type topResp struct { + Titles []string + Processes [][]string + } + var top topResp + _, b, err := sockRequest("GET", "/containers/"+id+"/top?ps_args=aux", nil) + if err != nil { + t.Fatal(err) + } + if err := json.Unmarshal(b, &top); err != nil { + t.Fatal(err) + } + + if len(top.Titles) != 11 { + t.Fatalf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles) + } + + if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" { + t.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles) + } + if len(top.Processes) != 2 { + t.Fatalf("expeted 2 processes, found %d: %v", len(top.Processes), top.Processes) + } + if top.Processes[0][10] != "/bin/sh -c cat" { + t.Fatalf("expected `/bin/sh -c cat`, found: %s", top.Processes[0][10]) + } + if top.Processes[1][10] != "cat" { + t.Fatalf("expected `cat`, found: %s", top.Processes[1][10]) + } + + logDone("containers REST API - GET /containers//top") +} diff --git a/integration/api_test.go b/integration/api_test.go index 4808aa02d..476fa35b3 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -23,80 +23,6 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -func TestGetContainersTop(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/sh", "-c", "cat"), - OpenStdin: true, - }, - t, - ) - defer func() { - // Make sure the process dies before destroying daemon - containerKill(eng, containerID, t) - containerWait(eng, containerID, t) - }() - - startContainer(eng, containerID, t) - - setTimeout(t, "Waiting for the container to be started timed out", 10*time.Second, func() { - for { - if containerRunning(eng, containerID, t) { - break - } - time.Sleep(10 * time.Millisecond) - } - }) - - if !containerRunning(eng, containerID, t) { - t.Fatalf("Container should be running") - } - - // Make sure sh spawn up cat - setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { - in, out := containerAttach(eng, containerID, t) - if err := assertPipe("hello\n", "hello", out, in, 150); err != nil { - t.Fatal(err) - } - }) - - r := httptest.NewRecorder() - req, err := http.NewRequest("GET", "/containers/"+containerID+"/top?ps_args=aux", nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - var procs engine.Env - if err := procs.Decode(r.Body); err != nil { - t.Fatal(err) - } - - if len(procs.GetList("Titles")) != 11 { - t.Fatalf("Expected 11 titles, found %d.", len(procs.GetList("Titles"))) - } - if procs.GetList("Titles")[0] != "USER" || procs.GetList("Titles")[10] != "COMMAND" { - t.Fatalf("Expected Titles[0] to be USER and Titles[10] to be COMMAND, found %s and %s.", procs.GetList("Titles")[0], procs.GetList("Titles")[10]) - } - processes := [][]string{} - if err := procs.GetJson("Processes", &processes); err != nil { - t.Fatal(err) - } - if len(processes) != 2 { - t.Fatalf("Expected 2 processes, found %d.", len(processes)) - } - if processes[0][10] != "/bin/sh -c cat" { - t.Fatalf("Expected `/bin/sh -c cat`, found %s.", processes[0][10]) - } - if processes[1][10] != "/bin/sh -c cat" { - t.Fatalf("Expected `/bin/sh -c cat`, found %s.", processes[1][10]) - } -} - func TestPostCommit(t *testing.T) { eng := NewTestEngine(t) b := &builder.BuilderJob{Engine: eng} From f19061ccfd526487391f7677bef7e1b38694d06a Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 14 Apr 2015 20:48:03 -0400 Subject: [PATCH 479/999] Move TestPostCommit to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 29 ++++++++++++++ integration/api_test.go | 39 ------------------- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 26a8f115d..5edd917f0 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -665,3 +665,32 @@ func TestContainerApiTop(t *testing.T) { logDone("containers REST API - GET /containers//top") } + +func TestContainerApiCommit(t *testing.T) { + out, _, _ := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /test") + id := strings.TrimSpace(out) + + name := "testcommit" + _, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+id, nil) + if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { + t.Fatal(err) + } + + type resp struct { + Id string + } + var img resp + if err := json.Unmarshal(b, &img); err != nil { + t.Fatal(err) + } + defer deleteImages(img.Id) + + out, err = inspectField(img.Id, "Config.Cmd") + if out != "[/bin/sh -c touch /test]" { + t.Fatalf("got wrong Cmd from commit: %q", out) + } + // sanity check, make sure the image is what we think it is + dockerCmd(t, "run", img.Id, "ls", "/test") + + logDone("containers REST API - POST /commit") +} diff --git a/integration/api_test.go b/integration/api_test.go index 476fa35b3..9ccab1c3b 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -17,50 +17,11 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/api/server" "github.com/docker/docker/api/types" - "github.com/docker/docker/builder" "github.com/docker/docker/engine" "github.com/docker/docker/runconfig" "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -func TestPostCommit(t *testing.T) { - eng := NewTestEngine(t) - b := &builder.BuilderJob{Engine: eng} - b.Install() - defer mkDaemonFromEngine(eng, t).Nuke() - - // Create a container and remove a file - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("touch", "/test"), - }, - t, - ) - - containerRun(eng, containerID, t) - - req, err := http.NewRequest("POST", "/commit?repo=testrepo&testtag=tag&container="+containerID, bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusCreated { - t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) - } - - var env engine.Env - if err := env.Decode(r.Body); err != nil { - t.Fatal(err) - } - if err := eng.Job("image_inspect", env.Get("Id")).Run(); err != nil { - t.Fatalf("The image has not been committed") - } -} - func TestPostContainersCreate(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From 23fa7d41d5510131cdf7883a930087ac4fb34187 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 14 Apr 2015 21:04:43 -0400 Subject: [PATCH 480/999] Move TestContainerApiCreate to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 27 +++++++++++++ integration/api_test.go | 40 ------------------- 2 files changed, 27 insertions(+), 40 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 5edd917f0..cbfd99eee 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -694,3 +694,30 @@ func TestContainerApiCommit(t *testing.T) { logDone("containers REST API - POST /commit") } + +func TestContainerApiCreate(t *testing.T) { + defer deleteAllContainers() + config := map[string]interface{}{ + "Image": "busybox", + "Cmd": []string{"/bin/sh", "-c", "touch /test && ls /test"}, + } + + _, b, err := sockRequest("POST", "/containers/create", config) + if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { + t.Fatal(err) + } + type createResp struct { + Id string + } + var container createResp + if err := json.Unmarshal(b, &container); err != nil { + t.Fatal(err) + } + + out, _, _ := dockerCmd(t, "start", "-a", container.Id) + if strings.TrimSpace(out) != "/test" { + t.Fatalf("expected output `/test`, got %q", out) + } + + logDone("containers REST API - POST /containers/create") +} diff --git a/integration/api_test.go b/integration/api_test.go index 9ccab1c3b..318978e5a 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -22,46 +22,6 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -func TestPostContainersCreate(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - configJSON, err := json.Marshal(&runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("touch", "/test"), - }) - if err != nil { - t.Fatal(err) - } - - req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJSON)) - if err != nil { - t.Fatal(err) - } - - req.Header.Set("Content-Type", "application/json") - - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusCreated { - t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) - } - - var apiRun engine.Env - if err := apiRun.Decode(r.Body); err != nil { - t.Fatal(err) - } - containerID := apiRun.Get("Id") - - containerAssertExists(eng, containerID, t) - containerRun(eng, containerID, t) - - if !containerFileExists(eng, containerID, "test", t) { - t.Fatal("Test file was not created") - } -} - func TestPostJsonVerify(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From 5dc02a2fa8819b3a61bcd5a6fedcfb1a5e64cba5 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 14 Apr 2015 21:55:04 -0400 Subject: [PATCH 481/999] Move TestPostJsonVerify to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 40 +++++++++++++++++++ integration-cli/docker_utils.go | 5 +-- integration/api_test.go | 38 ------------------ 3 files changed, 42 insertions(+), 41 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index cbfd99eee..cf18dcde2 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -721,3 +721,43 @@ func TestContainerApiCreate(t *testing.T) { logDone("containers REST API - POST /containers/create") } + +func TestContainerApiVerifyHeader(t *testing.T) { + defer deleteAllContainers() + config := map[string]interface{}{ + "Image": "busybox", + } + + create := func(ct string) (int, io.ReadCloser, error) { + jsonData := bytes.NewBuffer(nil) + if err := json.NewEncoder(jsonData).Encode(config); err != nil { + t.Fatal(err) + } + return sockRequestRaw("POST", "/containers/create", jsonData, ct) + } + + // Try with no content-type + _, body, err := create("") + if err == nil { + b, _ := readBody(body) + t.Fatalf("expected error when content-type is not set: %q", string(b)) + } + body.Close() + // Try with wrong content-type + _, body, err = create("application/xml") + if err == nil { + b, _ := readBody(body) + t.Fatalf("expected error when content-type is not set: %q", string(b)) + } + body.Close() + + // now application/json + _, body, err = create("application/json") + if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { + b, _ := readBody(body) + t.Fatalf("%v - %q", err, string(b)) + } + body.Close() + + logDone("containers REST API - verify create header") +} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index ab200e124..367e51809 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -329,10 +329,9 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io return -1, nil, fmt.Errorf("could not create new request: %v", err) } - if ct == "" { - ct = "application/json" + if ct != "" { + req.Header.Set("Content-Type", ct) } - req.Header.Set("Content-Type", ct) resp, err := client.Do(req) if err != nil { diff --git a/integration/api_test.go b/integration/api_test.go index 318978e5a..1663f4169 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -22,44 +22,6 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -func TestPostJsonVerify(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - configJSON, err := json.Marshal(&runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("touch", "/test"), - }) - if err != nil { - t.Fatal(err) - } - - req, err := http.NewRequest("POST", "/containers/create", bytes.NewReader(configJSON)) - if err != nil { - t.Fatal(err) - } - - r := httptest.NewRecorder() - - server.ServeRequest(eng, api.APIVERSION, r, req) - - // Don't add Content-Type header - // req.Header.Set("Content-Type", "application/json") - - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusInternalServerError || !strings.Contains(((*r.Body).String()), "application/json") { - t.Fatal("Create should have failed due to no Content-Type header - got:", r) - } - - // Now add header but with wrong type and retest - req.Header.Set("Content-Type", "application/xml") - - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusInternalServerError || !strings.Contains(((*r.Body).String()), "application/json") { - t.Fatal("Create should have failed due to wrong Content-Type header - got:", r) - } -} - // Issue 7941 - test to make sure a "null" in JSON is just ignored. // W/o this fix a null in JSON would be parsed into a string var as "null" func TestPostCreateNull(t *testing.T) { From 308a23021d65282cdf471ef0708ba5998b48c247 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 14 Apr 2015 22:07:04 -0400 Subject: [PATCH 482/999] Move TestPostCreateNull to integration-cli Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 56 +++++++++++++++++ integration/api_test.go | 61 ------------------- 2 files changed, 56 insertions(+), 61 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index cf18dcde2..cabbfc204 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -761,3 +761,59 @@ func TestContainerApiVerifyHeader(t *testing.T) { logDone("containers REST API - verify create header") } + +// Issue 7941 - test to make sure a "null" in JSON is just ignored. +// W/o this fix a null in JSON would be parsed into a string var as "null" +func TestContainerApiPostCreateNull(t *testing.T) { + config := `{ + "Hostname":"", + "Domainname":"", + "Memory":0, + "MemorySwap":0, + "CpuShares":0, + "Cpuset":null, + "AttachStdin":true, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "ExposedPorts":{}, + "Tty":true, + "OpenStdin":true, + "StdinOnce":true, + "Env":[], + "Cmd":"ls", + "Image":"busybox", + "Volumes":{}, + "WorkingDir":"", + "Entrypoint":null, + "NetworkDisabled":false, + "OnBuild":null}` + + _, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") + if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { + b, _ := readBody(body) + t.Fatal(err, string(b)) + } + + b, err := readBody(body) + if err != nil { + t.Fatal(err) + } + type createResp struct { + Id string + } + var container createResp + if err := json.Unmarshal(b, &container); err != nil { + t.Fatal(err) + } + + out, err := inspectField(container.Id, "HostConfig.CpusetCpus") + if err != nil { + t.Fatal(err, out) + } + if out != "" { + t.Fatalf("expected empty string, got %q", out) + } + + logDone("containers REST API - Create Null") +} diff --git a/integration/api_test.go b/integration/api_test.go index 1663f4169..4b09e4917 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -4,13 +4,11 @@ import ( "bufio" "bytes" "encoding/json" - "fmt" "io" "io/ioutil" "net" "net/http" "net/http/httptest" - "strings" "testing" "time" @@ -22,65 +20,6 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) -// Issue 7941 - test to make sure a "null" in JSON is just ignored. -// W/o this fix a null in JSON would be parsed into a string var as "null" -func TestPostCreateNull(t *testing.T) { - eng := NewTestEngine(t) - daemon := mkDaemonFromEngine(eng, t) - defer daemon.Nuke() - - configStr := fmt.Sprintf(`{ - "Hostname":"", - "Domainname":"", - "Memory":0, - "MemorySwap":0, - "CpuShares":0, - "Cpuset":null, - "AttachStdin":true, - "AttachStdout":true, - "AttachStderr":true, - "PortSpecs":null, - "ExposedPorts":{}, - "Tty":true, - "OpenStdin":true, - "StdinOnce":true, - "Env":[], - "Cmd":"ls", - "Image":"%s", - "Volumes":{}, - "WorkingDir":"", - "Entrypoint":null, - "NetworkDisabled":false, - "OnBuild":null}`, unitTestImageID) - - req, err := http.NewRequest("POST", "/containers/create", strings.NewReader(configStr)) - if err != nil { - t.Fatal(err) - } - - req.Header.Set("Content-Type", "application/json") - - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusCreated { - t.Fatalf("%d Created expected, received %d\n", http.StatusCreated, r.Code) - } - - var apiRun engine.Env - if err := apiRun.Decode(r.Body); err != nil { - t.Fatal(err) - } - containerID := apiRun.Get("Id") - - containerAssertExists(eng, containerID, t) - - c, _ := daemon.Get(containerID) - if c.HostConfig().CpusetCpus != "" { - t.Fatalf("Cpuset should have been empty - instead its:" + c.HostConfig().CpusetCpus) - } -} - func TestPostContainersKill(t *testing.T) { eng := NewTestEngine(t) defer mkDaemonFromEngine(eng, t).Nuke() From f44aa3b1fbe5e042fee0fb78507ebac35eca3d04 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Thu, 16 Apr 2015 11:33:49 -0700 Subject: [PATCH 483/999] for 1.6 Signed-off-by: Mary Anthony --- docs/Dockerfile | 6 +++--- docs/s3_website.json | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index aa97313ae..d5ffae460 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -6,9 +6,9 @@ MAINTAINER Sven Dowideit (@SvenDowideit) # This section ensures we pull the correct version of each # sub project -ENV COMPOSE_BRANCH release -ENV SWARM_BRANCH v0.1.0 -ENV MACHINE_BRANCH v0.1.0 +ENV COMPOSE_BRANCH 1.2.0 +ENV SWARM_BRANCH v0.2.0 +ENV MACHINE_BRANCH master ENV DISTRIB_BRANCH master diff --git a/docs/s3_website.json b/docs/s3_website.json index 1142fc0d8..95e7109c6 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -42,7 +42,8 @@ { "Condition": { "KeyPrefixEquals": "installation/openSUSE/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "installation/SUSE/" } }, { "Condition": { "KeyPrefixEquals": "contributing/contributing/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/who-written-for/" } }, { "Condition": { "KeyPrefixEquals": "contributing/devenvironment/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/set-up-prereqs/" } }, - { "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } } + { "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } }, + { "Condition": { "KeyPrefixEquals": "registry/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "registry/overview/" } } ] } From b4b21ff0a6c45c1565c74e6f923b2c7e8ca565d6 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Thu, 16 Apr 2015 11:39:54 -0700 Subject: [PATCH 484/999] Updating with final version from Stephen Signed-off-by: Mary Anthony --- docs/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index d5ffae460..d82c64df8 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -9,7 +9,7 @@ MAINTAINER Sven Dowideit (@SvenDowideit) ENV COMPOSE_BRANCH 1.2.0 ENV SWARM_BRANCH v0.2.0 ENV MACHINE_BRANCH master -ENV DISTRIB_BRANCH master +ENV DISTRIB_BRANCH v2.0.0 From 1c89c6ea2f34f51a05215279c9cdefca30bb13b1 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 16 Apr 2015 21:22:32 +0200 Subject: [PATCH 485/999] Add minor stylistic fixes Signed-off-by: Antonio Murdaca --- builder/internals.go | 3 +-- daemon/networkdriver/bridge/driver.go | 6 +++--- pkg/iptables/iptables.go | 12 ++++++------ pkg/streamformatter/streamformatter.go | 3 ++- pkg/streamformatter/streamformatter_test.go | 3 ++- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/builder/internals.go b/builder/internals.go index 669d3d888..49cf9422f 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -603,11 +603,10 @@ func (b *Builder) run(c *daemon.Container) error { // Wait for it to finish if ret, _ := c.WaitStop(-1 * time.Second); ret != 0 { - err := &jsonmessage.JSONError{ + return &jsonmessage.JSONError{ Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.Config.Cmd, ret), Code: ret, } - return err } return nil diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index eda471387..e8627363a 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -308,7 +308,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { "-t", string(iptables.Nat), "-I", "POSTROUTING"}, natArgs...)...); err != nil { return fmt.Errorf("Unable to enable network bridge NAT: %s", err) } else if len(output) != 0 { - return &iptables.ChainError{Chain: "POSTROUTING", Output: output} + return iptables.ChainError{Chain: "POSTROUTING", Output: output} } } } @@ -349,7 +349,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { if output, err := iptables.Raw(append([]string{"-I", "FORWARD"}, outgoingArgs...)...); err != nil { return fmt.Errorf("Unable to allow outgoing packets: %s", err) } else if len(output) != 0 { - return &iptables.ChainError{Chain: "FORWARD outgoing", Output: output} + return iptables.ChainError{Chain: "FORWARD outgoing", Output: output} } } @@ -360,7 +360,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { if output, err := iptables.Raw(append([]string{"-I", "FORWARD"}, existingArgs...)...); err != nil { return fmt.Errorf("Unable to allow incoming packets: %s", err) } else if len(output) != 0 { - return &iptables.ChainError{Chain: "FORWARD incoming", Output: output} + return iptables.ChainError{Chain: "FORWARD incoming", Output: output} } } return nil diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index f8b3aa769..204d703a6 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -41,7 +41,7 @@ type ChainError struct { Output []byte } -func (e *ChainError) Error() string { +func (e ChainError) Error() string { return fmt.Sprintf("Error iptables %s: %s", e.Chain, string(e.Output)) } @@ -142,7 +142,7 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr stri "--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))); err != nil { return err } else if len(output) != 0 { - return &ChainError{Chain: "FORWARD", Output: output} + return ChainError{Chain: "FORWARD", Output: output} } if output, err := Raw("-t", string(Filter), string(action), c.Name, @@ -154,7 +154,7 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr stri "-j", "ACCEPT"); err != nil { return err } else if len(output) != 0 { - return &ChainError{Chain: "FORWARD", Output: output} + return ChainError{Chain: "FORWARD", Output: output} } if output, err := Raw("-t", string(Nat), string(action), "POSTROUTING", @@ -165,7 +165,7 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr stri "-j", "MASQUERADE"); err != nil { return err } else if len(output) != 0 { - return &ChainError{Chain: "FORWARD", Output: output} + return ChainError{Chain: "FORWARD", Output: output} } return nil @@ -208,7 +208,7 @@ func (c *Chain) Prerouting(action Action, args ...string) error { if output, err := Raw(append(a, "-j", c.Name)...); err != nil { return err } else if len(output) != 0 { - return &ChainError{Chain: "PREROUTING", Output: output} + return ChainError{Chain: "PREROUTING", Output: output} } return nil } @@ -222,7 +222,7 @@ func (c *Chain) Output(action Action, args ...string) error { if output, err := Raw(append(a, "-j", c.Name)...); err != nil { return err } else if len(output) != 0 { - return &ChainError{Chain: "OUTPUT", Output: output} + return ChainError{Chain: "OUTPUT", Output: output} } return nil } diff --git a/pkg/streamformatter/streamformatter.go b/pkg/streamformatter/streamformatter.go index 383e7adf9..90f2b695d 100644 --- a/pkg/streamformatter/streamformatter.go +++ b/pkg/streamformatter/streamformatter.go @@ -3,8 +3,9 @@ package streamformatter import ( "encoding/json" "fmt" - "github.com/docker/docker/pkg/jsonmessage" "io" + + "github.com/docker/docker/pkg/jsonmessage" ) type StreamFormatter struct { diff --git a/pkg/streamformatter/streamformatter_test.go b/pkg/streamformatter/streamformatter_test.go index edc432e90..1dee05aa6 100644 --- a/pkg/streamformatter/streamformatter_test.go +++ b/pkg/streamformatter/streamformatter_test.go @@ -3,9 +3,10 @@ package streamformatter import ( "encoding/json" "errors" - "github.com/docker/docker/pkg/jsonmessage" "reflect" "testing" + + "github.com/docker/docker/pkg/jsonmessage" ) func TestFormatStream(t *testing.T) { From 99d0413033e6ecf05a30eac18d51349ef859a9c7 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Thu, 16 Apr 2015 11:38:05 -0700 Subject: [PATCH 486/999] Bump version to v1.6.0 Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) (cherry picked from commit 47496519da9664202d900d3635bb840509fa9647) --- CHANGELOG.md | 20 ++++++++++++++++++++ VERSION | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e845e459..9c541388e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 1.6.0 (2015-04-07) + +#### Builder ++ Building images from an image ID ++ build containers with resource constraints, ie `docker build --cpu-shares=100 --memory=1024m...` ++ `commit --change` to apply specified Dockerfile instructions while committing the image ++ `import --change` to apply specified Dockerfile instructions while importing the image ++ basic build cancellation + +#### Client ++ Windows Support + +#### Runtime ++ Container and image Labels ++ `--cgroup-parent` for specifying a parent cgroup to place container cgroup within ++ Logging drivers, `json-file`, `syslog`, or `none` ++ Pulling images by ID ++ `--ulimit` to set the ulimit on a container ++ `--default-ulimit` option on the daemon which applies to all created containers (and overwritten by `--ulimit` on run) + ## 1.5.0 (2015-02-10) #### Builder diff --git a/VERSION b/VERSION index 59b9db0c7..dc1e644a1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.5.0-dev +1.6.0 From 4c6d5e3a25557b2a053e8f8e5aafc84f5f6aada8 Mon Sep 17 00:00:00 2001 From: Ben Firshman Date: Thu, 16 Apr 2015 12:54:56 -0700 Subject: [PATCH 487/999] Remove registry redirect ... it was causing an infinite redirect. Signed-off-by: Ben Firshman --- docs/s3_website.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/s3_website.json b/docs/s3_website.json index 95e7109c6..1142fc0d8 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -42,8 +42,7 @@ { "Condition": { "KeyPrefixEquals": "installation/openSUSE/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "installation/SUSE/" } }, { "Condition": { "KeyPrefixEquals": "contributing/contributing/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/who-written-for/" } }, { "Condition": { "KeyPrefixEquals": "contributing/devenvironment/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/set-up-prereqs/" } }, - { "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } }, - { "Condition": { "KeyPrefixEquals": "registry/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "registry/overview/" } } + { "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } } ] } From e79c77fb8d16ba38387e43fbf9224e341f252b95 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Thu, 16 Apr 2015 12:45:30 -0700 Subject: [PATCH 488/999] bump version to 1.6.0-dev Signed-off-by: Jessica Frazelle --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index dc1e644a1..de023c91b 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.6.0 +1.7.0-dev From b57830aee4abc86155d401b6d8a0215c3a4b5a1b Mon Sep 17 00:00:00 2001 From: Yahya Date: Thu, 16 Apr 2015 00:17:18 +0200 Subject: [PATCH 489/999] adding elementary OS to install script It's basically just an Ubuntu, I'm a total noob but I've tried it on elementary OS "Freya" and it worked Signed-off-by: M Yahya --- hack/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/install.sh b/hack/install.sh index b0177e670..32e506cdd 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -126,7 +126,7 @@ do_install() { exit 0 ;; - ubuntu|debian|linuxmint) + ubuntu|debian|linuxmint|'elementary os') export DEBIAN_FRONTEND=noninteractive did_apt_get_update= From 01548ed1dc25e94bf6cc7decca1d2045069dc5b1 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Fri, 17 Apr 2015 09:25:06 +0800 Subject: [PATCH 490/999] update docker-inspect man page - sort inspect out - update output fields - format output - add doc about go template - other minor fix Signed-off-by: Qiang Huang --- docs/man/docker-inspect.1.md | 327 +++++++++++++++++++---------------- 1 file changed, 179 insertions(+), 148 deletions(-) diff --git a/docs/man/docker-inspect.1.md b/docs/man/docker-inspect.1.md index 85f673000..6f3cf5122 100644 --- a/docs/man/docker-inspect.1.md +++ b/docs/man/docker-inspect.1.md @@ -19,80 +19,120 @@ each result. # OPTIONS **--help** - Print usage statement + Print usage statement **-f**, **--format**="" - Format the output using the given go template. + Format the output using the given go template. # EXAMPLES ## Getting information on a container -To get information on a container use it's ID or instance name: +To get information on a container use its ID or instance name: - #docker inspect 1eb5fabf5a03 + $ 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, + "AppArmorProfile": "", + "Args": [], + "Config": { + "AttachStderr": false, "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": "", + "Domainname": "", "Entrypoint": null, + "Env": [ + "HOME=/", + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + ], + "ExposedPorts": { + "80/tcp": {} + }, + "Hostname": "1eb5fabf5a03", + "Image": "summit/nginx", + "Labels": { + "com.example.vendor": "Acme", + "com.example.license": "GPL", + "com.example.version": "1.0" + }, + "MacAddress": "", "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 + "OpenStdin": false, + "PortSpecs": null, + "StdinOnce": false, + "Tty": true, + "User": "", + "Volumes": null, + "WorkingDir": "", }, + "Created": "2014-04-04T21:33:52.02361335Z", + "Driver": "devicemapper", + "ExecDriver": "native-0.1", + "ExecIDs": null, + "HostConfig": { + "Binds": null, + "CapAdd": null, + "CapDrop": null, + "CgroupParent": "", + "ContainerIDFile": "", + "CpuShares": 512, + "CpusetCpus": "0,1", + "CpusetMems": "", + "Devices": [], + "Dns": null, + "DnsSearch": null, + "ExtraHosts": null, + "IpcMode": "", + "Links": null, + "LogConfig": { + "Config": null, + "Type": "json-file" + }, + "LxcConf": null, + "Memory": 16777216, + "MemorySwap": -1, + "NetworkMode": "", + "PidMode": "", + "PortBindings": { + "80/tcp": [ + { + "HostIp": "0.0.0.0", + "HostPort": "80" + } + ] + }, + "Privileged": false, + "PublishAllPorts": false, + "ReadonlyRootfs": false, + "RestartPolicy": { + "MaximumRetryCount": 0, + "Name": "" + }, + "SecurityOpt": null, + "Ulimits": null, + "VolumesFrom": null + } + "HostnamePath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/hostname", + "HostsPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/hosts", + "ID": "1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b", "Image": "df53773a4390e25936f9fd3739e0c0e60a62d024ea7b669282b27e65ae8458e6", - "Labels": { - "com.example.vendor": "Acme", - "com.example.license": "GPL", - "com.example.version": "1.0" - }, + "LogPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b-json.log", + "MountLabel": "", + "Name": "/ecstatic_ptolemy", "NetworkSettings": { + "Bridge": "docker0", + "Gateway": "172.17.42.1", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, "IPAddress": "172.17.0.2", "IPPrefixLen": 16, - "Gateway": "172.17.42.1", - "Bridge": "docker0", + "IPv6Gateway": "", + "LinkLocalIPv6Address": "", + "LinkLocalIPv6PrefixLen": 0, + "MacAddress": "", "PortMapping": null, "Ports": { "80/tcp": [ @@ -103,41 +143,31 @@ To get information on a container use it's ID or instance name: ] } }, + "Path": "/usr/sbin/nginx", + "ProcessLabel": "", "ResolvConfPath": "/etc/resolv.conf", - "HostnamePath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/hostname", - "HostsPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/hosts", - "LogPath": "/var/lib/docker/containers/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b/1eb5fabf5a03807136561b3c00adcd2992b535d624d5e18b6cdc6a6844d9767b-json.log", - "Name": "/ecstatic_ptolemy", - "Driver": "devicemapper", - "ExecDriver": "native-0.1", + "RestartCount": 0, + "State": { + "Dead": false, + "Error": "", + "ExitCode": 0, + "FinishedAt": "0001-01-01T00:00:00Z", + "OOMKilled": false, + "Paused": false, + "Pid": 858, + "Restarting": false, + "Running": true, + "StartedAt": "2014-04-04T21:33:54.16259207Z", + }, "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": "" - } + } ## Getting the IP address of a container instance To get the IP address of a container use: - # docker inspect --format='{{.NetworkSettings.IPAddress}}' 1eb5fabf5a03 + $ docker inspect --format='{{.NetworkSettings.IPAddress}}' 1eb5fabf5a03 172.17.0.2 ## Listing all port bindings @@ -145,95 +175,96 @@ To get the IP address of a container use: One can loop over arrays and maps in the results to produce simple text output: - # docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} \ - {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' 1eb5fabf5a03 + $ docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} \ + {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' 1eb5fabf5a03 + 80/tcp -> 80 - 80/tcp -> 80 +You can get more information about how to write a go template from: +http://golang.org/pkg/text/template/. ## Getting information on an image Use an image's ID or name (e.g., repository/name[:tag]) to get information - on it. +on it. - # docker inspect 58394af37342 + $ docker inspect fc1203419df2 [{ - "id": "58394af373423902a1b97f209a31e3777932d9321ef10e64feaaa7b4df609cf9", - "parent": "8abc22bad04266308ff408ca61cb8f6f4244a59308f7efc64e54b08b496c58db", - "created": "2014-02-03T16:10:40.500814677Z", - "container": "f718f19a28a5147da49313c54620306243734bafa63c76942ef6f8c4b4113bc5", - "container_config": { - "Hostname": "88807319f25e", - "Domainname": "", - "User": "", - "Memory": 0, - "MemorySwap": 0, - "CpuShares": 0, + "Architecture": "amd64", + "Author": "", + "Comment": "", + "Config": { + "AttachStderr": false, "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": [ + "make", + "direct-test" ], + "Domainname": "", + "Entrypoint": [ + "/dind" + ], + "Env": [ + "PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + ], + "ExposedPorts": null, + "Hostname": "242978536a06", + "Image": "c2b774c744afc5bea603b5e6c5218539e506649326de3ea0135182f299d0519a", + "Labels": {}, + "MacAddress": "", + "NetworkDisabled": false, + "OnBuild": [], + "OpenStdin": false, + "PortSpecs": null, + "StdinOnce": false, + "Tty": false, + "User": "", + "Volumes": null, + "WorkingDir": "/go/src/github.com/docker/libcontainer" + }, + "Container": "1c00417f3812a96d3ebc29e7fdee69f3d586d703ab89c8233fd4678d50707b39", + "ContainerConfig": { + "AttachStderr": false, + "AttachStdin": false, + "AttachStdout": false, "Cmd": [ "/bin/sh", "-c", - "#(nop) ADD fedora-20-dummy.tar.xz in /" + "#(nop) CMD [\"make\" \"direct-test\"]" ], - "Dns": null, - "DnsSearch": null, - "Image": "8abc22bad04266308ff408ca61cb8f6f4244a59308f7efc64e54b08b496c58db", - "Volumes": null, - "VolumesFrom": "", - "WorkingDir": "", - "Entrypoint": null, - "NetworkDisabled": false, - "OnBuild": null, - "Context": null - }, - "docker_version": "0.6.3", - "author": "I P Babble \u003clsm5@ipbabble.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" + "Entrypoint": [ + "/dind" ], - "Cmd": null, - "Dns": null, - "DnsSearch": null, - "Image": "8abc22bad04266308ff408ca61cb8f6f4244a59308f7efc64e54b08b496c58db", - "Volumes": null, - "VolumesFrom": "", - "WorkingDir": "", - "Entrypoint": null, + "Env": [ + "PATH=/go/bin:/usr/src/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + ], + "ExposedPorts": null, + "Hostname": "242978536a06", + "Image": "c2b774c744afc5bea603b5e6c5218539e506649326de3ea0135182f299d0519a", + "Labels": {}, + "MacAddress": "", "NetworkDisabled": false, - "OnBuild": null, - "Context": null + "OnBuild": [], + "OpenStdin": false, + "PortSpecs": null, + "StdinOnce": false, + "Tty": false, + "User": "", + "Volumes": null, + "WorkingDir": "/go/src/github.com/docker/libcontainer" }, - "architecture": "x86_64", - "Size": 385520098 + "Created": "2015-04-07T05:34:39.079489206Z", + "DockerVersion": "1.5.0-dev", + "Id": "fc1203419df26ca82cad1dd04c709cb1b8a8a947bd5bcbdfbef8241a76f031db", + "Os": "linux", + "Parent": "c2b774c744afc5bea603b5e6c5218539e506649326de3ea0135182f299d0519a", + "Size": 0, + "VirtualSize": 613136466 }] # HISTORY -April 2014, Originally compiled by William Henry (whenry at redhat dot com) +April 2014, originally compiled by William Henry (whenry at redhat dot com) based on docker.com source material and internal work. June 2014, updated by Sven Dowideit +April 2015, updated by Qiang Huang From 4e356ee410e1abb86665ab15e4c4155d7c807866 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Thu, 16 Apr 2015 08:50:20 -0700 Subject: [PATCH 491/999] Improve export/import tests cleanup Signed-off-by: Arnaud Porterie --- .../docker_cli_export_import_test.go | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go index 2d03179ac..3df8d60f8 100644 --- a/integration-cli/docker_cli_export_import_test.go +++ b/integration-cli/docker_cli_export_import_test.go @@ -9,21 +9,24 @@ import ( // 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") + containerID := "testexportcontainerandimportimage" + + defer deleteImages("repo/testexp:v1") + defer deleteContainer(containerID) + + runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := strings.TrimSpace(out) - - inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) + inspectCmd := exec.Command(dockerBinary, "inspect", containerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("output should've been a container id: %s %s ", cleanedContainerID, err) + t.Fatalf("output should've been a container id: %s %s ", containerID, err) } - exportCmd := exec.Command(dockerBinary, "export", cleanedContainerID) + exportCmd := exec.Command(dockerBinary, "export", containerID) if out, _, err = runCommandWithOutput(exportCmd); err != nil { t.Fatalf("failed to export container: %s, %v", out, err) } @@ -42,29 +45,31 @@ func TestExportContainerAndImportImage(t *testing.T) { t.Fatalf("output should've been an image id: %s, %v", out, err) } - deleteContainer(cleanedContainerID) - deleteImages("repo/testexp:v1") - logDone("export - export/import a container/image") } // Used to test output flag in the export command func TestExportContainerWithOutputAndImportImage(t *testing.T) { - runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + containerID := "testexportcontainerwithoutputandimportimage" + + defer deleteImages("repo/testexp:v1") + defer deleteContainer(containerID) + + runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { t.Fatal("failed to create a container", out, err) } - cleanedContainerID := strings.TrimSpace(out) - - inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) + inspectCmd := exec.Command(dockerBinary, "inspect", containerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("output should've been a container id: %s %s ", cleanedContainerID, err) + t.Fatalf("output should've been a container id: %s %s ", containerID, err) } - exportCmd := exec.Command(dockerBinary, "export", "--output=testexp.tar", cleanedContainerID) + defer os.Remove("testexp.tar") + + exportCmd := exec.Command(dockerBinary, "export", "--output=testexp.tar", containerID) if out, _, err = runCommandWithOutput(exportCmd); err != nil { t.Fatalf("failed to export container: %s, %v", out, err) } @@ -88,10 +93,5 @@ func TestExportContainerWithOutputAndImportImage(t *testing.T) { t.Fatalf("output should've been an image id: %s, %v", out, err) } - deleteContainer(cleanedContainerID) - deleteImages("repo/testexp:v1") - - os.Remove("/tmp/testexp.tar") - logDone("export - export/import a container/image with output flag") } From 9a4fa9c19167756cf39a4d002efe81d4bcd3bb75 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Thu, 16 Apr 2015 23:05:47 -0700 Subject: [PATCH 492/999] Skip TestPullVerified Signed-off-by: Arnaud Porterie --- integration-cli/docker_cli_pull_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index 6e5ddb840..f9fd17852 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -55,6 +55,8 @@ func TestPullImageWithAliases(t *testing.T) { // pulling library/hello-world should show verified message func TestPullVerified(t *testing.T) { + t.Skip("Skipping hub dependent test") + // Image must be pulled from central repository to get verified message // unless keychain is manually updated to contain the daemon's sign key. From b052f7a87cb73aa2b0d4827f6cf23b9eb0fdfa2e Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Fri, 17 Apr 2015 18:05:30 +0800 Subject: [PATCH 493/999] Change log severity for non-tlsverify bind closes #12459 Signed-off-by: Hu Keping --- api/server/server_linux.go | 2 +- api/server/server_windows.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/api/server/server_linux.go b/api/server/server_linux.go index 37ba7ed80..06e1a3717 100644 --- a/api/server/server_linux.go +++ b/api/server/server_linux.go @@ -53,7 +53,7 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { return nil, nil case "tcp": if !job.GetenvBool("TlsVerify") { - logrus.Infof("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") + logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } if l, err = NewTcpSocket(addr, tlsConfigFromJob(job)); err != nil { return nil, err diff --git a/api/server/server_windows.go b/api/server/server_windows.go index ad7b3c48a..c81313e25 100644 --- a/api/server/server_windows.go +++ b/api/server/server_windows.go @@ -1,4 +1,5 @@ // +build windows + package server import ( @@ -25,7 +26,7 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { switch proto { case "tcp": if !job.GetenvBool("TlsVerify") { - logrus.Infof("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") + logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } if l, err = NewTcpSocket(addr, tlsConfigFromJob(job)); err != nil { return nil, err From 3cdf94ed1a8a63c068e17310ffa764230b280cbe Mon Sep 17 00:00:00 2001 From: Masahito Zembutsu Date: Fri, 17 Apr 2015 16:01:18 +0900 Subject: [PATCH 494/999] fix typo Is this typo? Signed-off-by: Masahito Zembutsu --- docs/sources/installation/windows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index a1bd1de1d..f93f13e60 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -30,7 +30,7 @@ is developed, you can launch only Linux containers from your Windows machine. 1. Download the latest release of the [Docker for Windows Installer](https://github.com/boot2docker/windows-installer/releases/latest). -2. Run the installer, which will install Docker Client or Windows, VirtualBox, +2. Run the installer, which will install Docker Client for Windows, VirtualBox, Git for Windows (MSYS-git), the boot2docker Linux ISO, and the Boot2Docker management tool. ![](/installation/images/windows-installer.png) From 05a8de46853f8b3534ca6d0cb03121ae214e8a34 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Fri, 17 Apr 2015 15:28:12 +0800 Subject: [PATCH 495/999] Fix weird terminal output format Signed-off-by: Lei Jitang --- pkg/term/tc_linux_cgo.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/term/tc_linux_cgo.go b/pkg/term/tc_linux_cgo.go index ae9516c99..d47cf59b8 100644 --- a/pkg/term/tc_linux_cgo.go +++ b/pkg/term/tc_linux_cgo.go @@ -24,6 +24,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState := oldState.termios C.cfmakeraw((*C.struct_termios)(unsafe.Pointer(&newState))) + newState.Oflag = newState.Oflag | C.OPOST if err := tcset(fd, &newState); err != 0 { return nil, err } From 70f1910a8bbeb3727829070a2454a636a91e2d48 Mon Sep 17 00:00:00 2001 From: bin liu Date: Thu, 16 Apr 2015 09:10:05 +0000 Subject: [PATCH 496/999] fix some typos Signed-off-by: bin liu --- pkg/term/winconsole/term_emulator_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/term/winconsole/term_emulator_test.go b/pkg/term/winconsole/term_emulator_test.go index 65de5a793..94104ff51 100644 --- a/pkg/term/winconsole/term_emulator_test.go +++ b/pkg/term/winconsole/term_emulator_test.go @@ -138,7 +138,7 @@ func TestAssertEqualBytesNegative(t *testing.T) { AssertBytesEqual(t, []byte{1, 2, 3}, []byte{1, 1, 1}, "content mismatch") }*/ -// Checks that the calls recieved +// Checks that the calls received func assertHandlerOutput(t *testing.T, mock *mockTerminal, plainText string, commands ...string) { text := make([]byte, 0, 3*len(plainText)) cmdIndex := 0 From 609fa93aa2fd98f2eac30933623f15ece59e4527 Mon Sep 17 00:00:00 2001 From: Peter Waller Date: Fri, 17 Apr 2015 09:44:12 +0100 Subject: [PATCH 497/999] Improve build cancelation description in CHANGELOG The existing text didn't explain what had changed. (See #9774) Signed-off-by: Peter Waller --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c541388e..d168ad280 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,10 @@ #### Builder + Building images from an image ID -+ build containers with resource constraints, ie `docker build --cpu-shares=100 --memory=1024m...` ++ Build containers with resource constraints, ie `docker build --cpu-shares=100 --memory=1024m...` + `commit --change` to apply specified Dockerfile instructions while committing the image + `import --change` to apply specified Dockerfile instructions while importing the image -+ basic build cancellation ++ Builds no longer continue in the background when canceled with CTRL-C #### Client + Windows Support From a0bf80fe0372196812a9cb295f209c08f8037601 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 16 Apr 2015 21:48:04 +0200 Subject: [PATCH 498/999] Remove builtins Signed-off-by: Antonio Murdaca --- api/server/server.go | 48 ++++++++++++++++------ api/server/server_linux.go | 22 +++++----- api/server/server_unit_test.go | 29 ------------- api/server/server_windows.go | 4 +- api/server/tcp_socket.go | 13 +++--- api/types/types.go | 12 ++++++ builtins/builtins.go | 48 ---------------------- docker/daemon.go | 42 ++++++++----------- integration-cli/docker_api_version_test.go | 24 +++++++++++ integration-cli/docker_cli_run_test.go | 2 +- integration/runtime_test.go | 33 ++++++++------- integration/utils_test.go | 5 --- 12 files changed, 122 insertions(+), 160 deletions(-) delete mode 100644 builtins/builtins.go create mode 100644 integration-cli/docker_api_version_test.go diff --git a/api/server/server.go b/api/server/server.go index d40413a4e..f36f082f6 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -3,6 +3,7 @@ package server import ( "bufio" "bytes" + "runtime" "time" "encoding/base64" @@ -21,6 +22,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api" "github.com/docker/docker/api/types" + "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" @@ -28,6 +30,7 @@ import ( "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/filters" + "github.com/docker/docker/pkg/parsers/kernel" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/streamformatter" @@ -41,6 +44,19 @@ var ( activationLock = make(chan struct{}) ) +type ServerConfig struct { + Logging bool + EnableCors bool + CorsHeaders string + Version string + SocketGroup string + Tls bool + TlsVerify bool + TlsCa string + TlsCert string + TlsKey string +} + type HttpServer struct { srv *http.Server l net.Listener @@ -187,8 +203,20 @@ func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter func getVersion(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") - eng.ServeHTTP(w, r) - return nil + + v := &types.Version{ + Version: dockerversion.VERSION, + ApiVersion: api.APIVERSION, + GitCommit: dockerversion.GITCOMMIT, + GoVersion: runtime.Version(), + Os: runtime.GOOS, + Arch: runtime.GOARCH, + } + if kernelVersion, err := kernel.GetKernelVersion(); err == nil { + v.KernelVersion = kernelVersion.String() + } + + return writeJSON(w, http.StatusOK, v) } func postContainersKill(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -1588,28 +1616,22 @@ type Server interface { // ServeApi loops through all of the protocols sent in to docker and spawns // off a go routine to setup a serving http.Server for each. -func ServeApi(job *engine.Job) error { - if len(job.Args) == 0 { - return fmt.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) - } - var ( - protoAddrs = job.Args - chErrors = make(chan error, len(protoAddrs)) - ) +func ServeApi(protoAddrs []string, conf *ServerConfig, eng *engine.Engine) error { + var chErrors = make(chan error, len(protoAddrs)) for _, protoAddr := range protoAddrs { protoAddrParts := strings.SplitN(protoAddr, "://", 2) if len(protoAddrParts) != 2 { - return fmt.Errorf("usage: %s PROTO://ADDR [PROTO://ADDR ...]", job.Name) + return fmt.Errorf("bad format, expected PROTO://ADDR") } go func() { logrus.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1]) - srv, err := NewServer(protoAddrParts[0], protoAddrParts[1], job) + srv, err := NewServer(protoAddrParts[0], protoAddrParts[1], conf, eng) if err != nil { chErrors <- err return } - job.Eng.OnShutdown(func() { + eng.OnShutdown(func() { if err := srv.Close(); err != nil { logrus.Error(err) } diff --git a/api/server/server_linux.go b/api/server/server_linux.go index 06e1a3717..4d53a888e 100644 --- a/api/server/server_linux.go +++ b/api/server/server_linux.go @@ -13,16 +13,16 @@ import ( ) // NewServer sets up the required Server and does protocol specific checking. -func NewServer(proto, addr string, job *engine.Job) (Server, error) { +func NewServer(proto, addr string, conf *ServerConfig, eng *engine.Engine) (Server, error) { var ( err error l net.Listener r = createRouter( - job.Eng, - job.GetenvBool("Logging"), - job.GetenvBool("EnableCors"), - job.Getenv("CorsHeaders"), - job.Getenv("Version"), + eng, + conf.Logging, + conf.EnableCors, + conf.CorsHeaders, + conf.Version, ) ) switch proto { @@ -52,17 +52,17 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { } return nil, nil case "tcp": - if !job.GetenvBool("TlsVerify") { + if !conf.TlsVerify { logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } - if l, err = NewTcpSocket(addr, tlsConfigFromJob(job)); err != nil { + if l, err = NewTcpSocket(addr, tlsConfigFromServerConfig(conf)); err != nil { return nil, err } if err := allocateDaemonPort(addr); err != nil { return nil, err } case "unix": - if l, err = NewUnixSocket(addr, job.Getenv("SocketGroup")); err != nil { + if l, err = NewUnixSocket(addr, conf.SocketGroup); err != nil { return nil, err } default: @@ -77,8 +77,7 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { }, nil } -// Called through eng.Job("acceptconnections") -func AcceptConnections(job *engine.Job) error { +func AcceptConnections() { // Tell the init daemon we are accepting requests go systemd.SdNotify("READY=1") // close the lock so the listeners start accepting connections @@ -87,5 +86,4 @@ func AcceptConnections(job *engine.Job) error { default: close(activationLock) } - return nil } diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index 78b95b26b..aca3af9ff 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -34,35 +34,6 @@ func TesthttpError(t *testing.T) { } } -func TestGetVersion(t *testing.T) { - eng := engine.New() - var called bool - eng.Register("version", func(job *engine.Job) error { - called = true - v := &engine.Env{} - v.SetJson("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 err - } - return nil - }) - r := serveRequest("GET", "/version", nil, eng, t) - if !called { - t.Fatalf("handler was not called") - } - v := readEnv(r.Body, t) - if v.Get("Version") != "42.1" { - t.Fatalf("%#v\n", v) - } - if r.HeaderMap.Get("Content-Type") != "application/json" { - t.Fatalf("%#v\n", r) - } -} - func TestGetInfo(t *testing.T) { eng := engine.New() var called bool diff --git a/api/server/server_windows.go b/api/server/server_windows.go index c81313e25..e6b23b97e 100644 --- a/api/server/server_windows.go +++ b/api/server/server_windows.go @@ -39,13 +39,11 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { } } -// Called through eng.Job("acceptconnections") -func AcceptConnections(job *engine.Job) error { +func AcceptConnections() { // close the lock so the listeners start accepting connections select { case <-activationLock: default: close(activationLock) } - return nil } diff --git a/api/server/tcp_socket.go b/api/server/tcp_socket.go index 415542c14..8454e0c58 100644 --- a/api/server/tcp_socket.go +++ b/api/server/tcp_socket.go @@ -8,7 +8,6 @@ import ( "net" "os" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/listenbuffer" ) @@ -19,16 +18,16 @@ type tlsConfig struct { Verify bool } -func tlsConfigFromJob(job *engine.Job) *tlsConfig { - verify := job.GetenvBool("TlsVerify") - if !job.GetenvBool("Tls") && !verify { +func tlsConfigFromServerConfig(conf *ServerConfig) *tlsConfig { + verify := conf.TlsVerify + if !conf.Tls && !conf.TlsVerify { return nil } return &tlsConfig{ Verify: verify, - Certificate: job.Getenv("TlsCert"), - Key: job.Getenv("TlsKey"), - CA: job.Getenv("TlsCa"), + Certificate: conf.TlsCert, + Key: conf.TlsKey, + CA: conf.TlsCa, } } diff --git a/api/types/types.go b/api/types/types.go index d7defaf85..cdafe7e19 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -1,5 +1,7 @@ package types +import "github.com/docker/docker/pkg/version" + // ContainerCreateResponse contains the information returned to a client on the // creation of a new container. type ContainerCreateResponse struct { @@ -110,3 +112,13 @@ type ContainerProcessList struct { Processes [][]string Titles []string } + +type Version struct { + Version string + ApiVersion version.Version + GitCommit string + GoVersion string + Os string + Arch string + KernelVersion string `json:",omitempty"` +} diff --git a/builtins/builtins.go b/builtins/builtins.go deleted file mode 100644 index 8957b5833..000000000 --- a/builtins/builtins.go +++ /dev/null @@ -1,48 +0,0 @@ -package builtins - -import ( - "runtime" - - "github.com/docker/docker/api" - apiserver "github.com/docker/docker/api/server" - "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/engine" - "github.com/docker/docker/pkg/parsers/kernel" -) - -func Register(eng *engine.Engine) error { - if err := remote(eng); err != nil { - return err - } - if err := eng.Register("version", dockerVersion); err != nil { - return err - } - - return nil -} - -// remote: a RESTful api for cross-docker communication -func remote(eng *engine.Engine) error { - if err := eng.Register("serveapi", apiserver.ServeApi); err != nil { - return err - } - return eng.Register("acceptconnections", apiserver.AcceptConnections) -} - -// builtins jobs independent of any subsystem -func dockerVersion(job *engine.Job) error { - v := &engine.Env{} - v.SetJson("Version", dockerversion.VERSION) - v.SetJson("ApiVersion", api.APIVERSION) - v.SetJson("GitCommit", dockerversion.GITCOMMIT) - v.Set("GoVersion", runtime.Version()) - v.Set("Os", runtime.GOOS) - v.Set("Arch", runtime.GOARCH) - if kernelVersion, err := kernel.GetKernelVersion(); err == nil { - v.Set("KernelVersion", kernelVersion.String()) - } - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil -} diff --git a/docker/daemon.go b/docker/daemon.go index b1a92c52e..769b4f5bf 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -10,9 +10,9 @@ import ( "strings" "github.com/Sirupsen/logrus" + apiserver "github.com/docker/docker/api/server" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/builder" - "github.com/docker/docker/builtins" "github.com/docker/docker/daemon" _ "github.com/docker/docker/daemon/execdriver/lxc" _ "github.com/docker/docker/daemon/execdriver/native" @@ -93,11 +93,6 @@ func mainDaemon() { } daemonCfg.TrustKeyPath = *flTrustKey - // Load builtins - if err := builtins.Register(eng); err != nil { - logrus.Fatal(err) - } - registryService := registry.NewService(registryCfg) // load the daemon in the background so we can immediately start // the http api so that connections don't fail while the daemon @@ -127,33 +122,30 @@ func mainDaemon() { // after the daemon is done setting up we can tell the api to start // accepting connections - if err := eng.Job("acceptconnections").Run(); err != nil { - daemonInitWait <- err - return - } + apiserver.AcceptConnections() + daemonInitWait <- nil }() - // Serve api - job := eng.Job("serveapi", flHosts...) - job.SetenvBool("Logging", true) - job.SetenvBool("EnableCors", daemonCfg.EnableCors) - job.Setenv("CorsHeaders", daemonCfg.CorsHeaders) - job.Setenv("Version", dockerversion.VERSION) - job.Setenv("SocketGroup", daemonCfg.SocketGroup) + serverConfig := &apiserver.ServerConfig{ + Logging: true, + EnableCors: daemonCfg.EnableCors, + CorsHeaders: daemonCfg.CorsHeaders, + Version: dockerversion.VERSION, + SocketGroup: daemonCfg.SocketGroup, + Tls: *flTls, + TlsVerify: *flTlsVerify, + TlsCa: *flCa, + TlsCert: *flCert, + TlsKey: *flKey, + } - job.SetenvBool("Tls", *flTls) - job.SetenvBool("TlsVerify", *flTlsVerify) - job.Setenv("TlsCa", *flCa) - job.Setenv("TlsCert", *flCert) - job.Setenv("TlsKey", *flKey) - - // The serve API job never exits unless an error occurs + // The serve API routine never exits unless an error occurs // We need to start it as a goroutine and wait on it so // daemon doesn't exit serveAPIWait := make(chan error) go func() { - if err := job.Run(); err != nil { + if err := apiserver.ServeApi(flHosts, serverConfig, eng); err != nil { logrus.Errorf("ServeAPI error: %v", err) serveAPIWait <- err return diff --git a/integration-cli/docker_api_version_test.go b/integration-cli/docker_api_version_test.go new file mode 100644 index 000000000..2846fb1d3 --- /dev/null +++ b/integration-cli/docker_api_version_test.go @@ -0,0 +1,24 @@ +package main + +import ( + "encoding/json" + "testing" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/autogen/dockerversion" +) + +func TestGetVersion(t *testing.T) { + _, body, err := sockRequest("GET", "/version", nil) + if err != nil { + t.Fatal(err) + } + var v types.Version + if err := json.Unmarshal(body, &v); err != nil { + t.Fatal(err) + } + + if v.Version != dockerversion.VERSION { + t.Fatal("Version mismatch") + } +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index e990f086a..b3cd1c73a 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3363,7 +3363,7 @@ func TestRunRestartMaxRetries(t *testing.T) { t.Fatal(string(out), err) } id := strings.TrimSpace(string(out)) - if err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false false", 5); err != nil { + if err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false false", 10); err != nil { t.Fatal(err) } count, err := inspectField(id, "RestartCount") diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 2e456eabf..b399c3745 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -17,6 +17,7 @@ import ( "time" "github.com/Sirupsen/logrus" + apiserver "github.com/docker/docker/api/server" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/engine" @@ -157,9 +158,9 @@ func spawnGlobalDaemon() { Scheme: testDaemonProto, Host: testDaemonAddr, } - job := eng.Job("serveapi", listenURL.String()) - job.SetenvBool("Logging", true) - if err := job.Run(); err != nil { + + serverConfig := &apiserver.ServerConfig{Logging: true} + if err := apiserver.ServeApi([]string{listenURL.String()}, serverConfig, eng); err != nil { logrus.Fatalf("Unable to spawn the test daemon: %s", err) } }() @@ -168,9 +169,7 @@ func spawnGlobalDaemon() { // FIXME: use inmem transports instead of tcp time.Sleep(time.Second) - if err := eng.Job("acceptconnections").Run(); err != nil { - logrus.Fatalf("Unable to accept connections for test api: %s", err) - } + apiserver.AcceptConnections() } func spawnLegitHttpsDaemon() { @@ -207,14 +206,15 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { 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 { + serverConfig := &apiserver.ServerConfig{ + Logging: true, + Tls: true, + TlsVerify: true, + TlsCa: cacert, + TlsCert: cert, + TlsKey: key, + } + if err := apiserver.ServeApi([]string{listenURL.String()}, serverConfig, eng); err != nil { logrus.Fatalf("Unable to spawn the test daemon: %s", err) } }() @@ -222,9 +222,8 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { // Give some time to ListenAndServer to actually start time.Sleep(time.Second) - if err := eng.Job("acceptconnections").Run(); err != nil { - logrus.Fatalf("Unable to accept connections for test api: %s", err) - } + apiserver.AcceptConnections() + return eng } diff --git a/integration/utils_test.go b/integration/utils_test.go index befd924ea..9479d4296 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -17,7 +17,6 @@ import ( "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "github.com/docker/docker/api/types" - "github.com/docker/docker/builtins" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" @@ -170,10 +169,6 @@ func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine { eng := engine.New() eng.Logging = false - // Load default plugins - if err := builtins.Register(eng); err != nil { - t.Fatal(err) - } // (This is manually copied and modified from main() until we have a more generic plugin system) cfg := &daemon.Config{ From 37b9ce61ac5aa762cf0ad34d84bc1924807a7617 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Steenis Date: Fri, 17 Apr 2015 14:54:40 +0200 Subject: [PATCH 499/999] Added Debian 8 note for adding backports Signed-off-by: Sebastiaan van Steenis --- docs/sources/installation/debian.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index e3fb6e292..aeee1ecb1 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -11,8 +11,7 @@ Docker is supported on the following versions of Debian: ## Debian Jessie 8.0 (64-bit) -Debian 8 comes with a 3.14.0 Linux kernel, and a `docker.io` package which -installs all its prerequisites from Debian's repository. +Debian 8 comes with a 3.16.0 Linux kernel, the `docker.io` package can be found in the `jessie-backports` repository. Reasoning behind this can be found here. Instructions how to enable the backports repository can be found here. > **Note**: > Debian contains a much older KDE3/GNOME2 package called ``docker``, so the @@ -20,6 +19,8 @@ installs all its prerequisites from Debian's repository. ### Installation +Make sure you enabled the `jessie-backports` repository, as stated above. + To install the latest Debian package (may not be the latest Docker release): $ sudo apt-get update From 621b601b3c602aab5ef0f07903fdf413881bb261 Mon Sep 17 00:00:00 2001 From: bobby abbott Date: Mon, 13 Apr 2015 22:16:19 -0700 Subject: [PATCH 500/999] Removed unnecessary error output from dockerCmd Changed method declaration. Fixed all calls to dockerCmd method to reflect the change. resolves #12355 Signed-off-by: bobby abbott --- integration-cli/docker_api_containers_test.go | 6 +- integration-cli/docker_api_images_test.go | 2 +- integration-cli/docker_cli_attach_test.go | 2 +- .../docker_cli_attach_unix_test.go | 4 +- integration-cli/docker_cli_build_test.go | 13 +- integration-cli/docker_cli_commit_test.go | 4 +- integration-cli/docker_cli_cp_test.go | 206 ++++++++---------- integration-cli/docker_cli_create_test.go | 2 +- integration-cli/docker_cli_events_test.go | 2 +- integration-cli/docker_cli_exec_test.go | 4 +- integration-cli/docker_cli_links_test.go | 4 +- integration-cli/docker_cli_pause_test.go | 4 +- integration-cli/docker_cli_rmi_test.go | 20 +- integration-cli/docker_cli_run_test.go | 18 +- integration-cli/docker_cli_start_test.go | 2 +- integration-cli/docker_utils.go | 10 +- 16 files changed, 127 insertions(+), 176 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index cabbfc204..b76eb6ba2 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -627,7 +627,7 @@ func TestContainerApiPause(t *testing.T) { func TestContainerApiTop(t *testing.T) { defer deleteAllContainers() - out, _, _ := dockerCmd(t, "run", "-d", "-i", "busybox", "/bin/sh", "-c", "cat") + out, _ := dockerCmd(t, "run", "-d", "-i", "busybox", "/bin/sh", "-c", "cat") id := strings.TrimSpace(out) if err := waitRun(id); err != nil { t.Fatal(err) @@ -667,7 +667,7 @@ func TestContainerApiTop(t *testing.T) { } func TestContainerApiCommit(t *testing.T) { - out, _, _ := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /test") + out, _ := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /test") id := strings.TrimSpace(out) name := "testcommit" @@ -714,7 +714,7 @@ func TestContainerApiCreate(t *testing.T) { t.Fatal(err) } - out, _, _ := dockerCmd(t, "start", "-a", container.Id) + out, _ := dockerCmd(t, "start", "-a", container.Id) if strings.TrimSpace(out) != "/test" { t.Fatalf("expected output `/test`, got %q", out) } diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 083d63204..276ee7f3c 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -91,7 +91,7 @@ func TestApiImagesSaveAndLoad(t *testing.T) { } defer loadBody.Close() - out, _, _ = dockerCmd(t, "inspect", "--format='{{ .Id }}'", id) + out, _ = dockerCmd(t, "inspect", "--format='{{ .Id }}'", id) if strings.TrimSpace(out) != id { t.Fatal("load did not work properly") } diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go index 04cb59398..08b109f88 100644 --- a/integration-cli/docker_cli_attach_test.go +++ b/integration-cli/docker_cli_attach_test.go @@ -138,7 +138,7 @@ func TestAttachTtyWithoutStdin(t *testing.T) { func TestAttachDisconnect(t *testing.T) { defer deleteAllContainers() - out, _, _ := dockerCmd(t, "run", "-di", "busybox", "/bin/cat") + out, _ := dockerCmd(t, "run", "-di", "busybox", "/bin/cat") id := strings.TrimSpace(out) cmd := exec.Command(dockerBinary, "attach", id) diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index ebc3804e3..e17256cf7 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -142,7 +142,7 @@ func TestAttachAfterDetach(t *testing.T) { // TestAttachDetach checks that attach in tty mode can be detached using the long container ID func TestAttachDetach(t *testing.T) { - out, _, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") + out, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") id := strings.TrimSpace(out) if err := waitRun(id); err != nil { t.Fatal(err) @@ -217,7 +217,7 @@ func TestAttachDetach(t *testing.T) { // TestAttachDetachTruncatedID checks that attach in tty mode can be detached func TestAttachDetachTruncatedID(t *testing.T) { - out, _, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") + out, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") id := stringid.TruncateID(strings.TrimSpace(out)) if err := waitRun(id); err != nil { t.Fatal(err) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 1ef5088e2..44370d4b9 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -3294,7 +3294,7 @@ func TestBuildNoContext(t *testing.T) { t.Fatalf("build failed to complete: %v %v", out, err) } - if out, _, err := dockerCmd(t, "run", "--rm", "nocontext"); out != "ok\n" || err != nil { + if out, _ := dockerCmd(t, "run", "--rm", "nocontext"); out != "ok\n" { t.Fatalf("run produced invalid output: %q, expected %q", out, "ok") } @@ -5562,10 +5562,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { if err != nil { t.Fatal(err, out) } - out, _, err = dockerCmd(t, "ps", "-lq") - if err != nil { - t.Fatal(err, out) - } + out, _ = dockerCmd(t, "ps", "-lq") cID := strings.TrimSpace(out) @@ -5593,10 +5590,8 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { } // Make sure constraints aren't saved to image - _, _, err = dockerCmd(t, "run", "--name=test", name) - if err != nil { - t.Fatal(err) - } + _, _ = dockerCmd(t, "run", "--name=test", name) + cfg, err = inspectFieldJSON("test", "HostConfig") if err != nil { t.Fatal(err) diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index a51360a9b..5d4288192 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -284,13 +284,13 @@ func TestCommitChange(t *testing.T) { func TestCommitMergeConfigRun(t *testing.T) { defer deleteAllContainers() name := "commit-test" - out, _, _ := dockerCmd(t, "run", "-d", "-e=FOO=bar", "busybox", "/bin/sh", "-c", "echo testing > /tmp/foo") + out, _ := dockerCmd(t, "run", "-d", "-e=FOO=bar", "busybox", "/bin/sh", "-c", "echo testing > /tmp/foo") id := strings.TrimSpace(out) dockerCmd(t, "commit", `--run={"Cmd": ["cat", "/tmp/foo"]}`, id, "commit-test") defer deleteImages("commit-test") - out, _, _ = dockerCmd(t, "run", "--name", name, "commit-test") + out, _ = dockerCmd(t, "run", "--name", name, "commit-test") if strings.TrimSpace(out) != "testing" { t.Fatal("run config in commited container was not merged") } diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index 12da76abc..b577e8298 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -25,17 +25,17 @@ const ( // Test for #5656 // Check that garbage paths don't escape the container's rootfs func TestCpGarbagePath(t *testing.T) { - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { @@ -61,10 +61,7 @@ func TestCpGarbagePath(t *testing.T) { path := path.Join("../../../../../../../../../../../../", cpFullPath) - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) - if err != nil { - t.Fatalf("couldn't copy from garbage path: %s:%s %s", cleanedContainerID, path, err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() @@ -87,17 +84,17 @@ func TestCpGarbagePath(t *testing.T) { // Check that relative paths are relative to the container's rootfs func TestCpRelativePath(t *testing.T) { - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { @@ -131,10 +128,7 @@ func TestCpRelativePath(t *testing.T) { t.Fatalf("path %s was assumed to be an absolute path", cpFullPath) } - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+relPath, tmpdir) - if err != nil { - t.Fatalf("couldn't copy from relative path: %s:%s %s", cleanedContainerID, relPath, err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+relPath, tmpdir) file, _ := os.Open(tmpname) defer file.Close() @@ -157,17 +151,17 @@ func TestCpRelativePath(t *testing.T) { // Check that absolute paths are relative to the container's rootfs func TestCpAbsolutePath(t *testing.T) { - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { @@ -194,10 +188,7 @@ func TestCpAbsolutePath(t *testing.T) { path := cpFullPath - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) - if err != nil { - t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() @@ -221,17 +212,17 @@ func TestCpAbsolutePath(t *testing.T) { // Test for #5619 // Check that absolute symlinks are still relative to the container's rootfs func TestCpAbsoluteSymlink(t *testing.T) { - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path") - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path") + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { @@ -258,10 +249,7 @@ func TestCpAbsoluteSymlink(t *testing.T) { path := path.Join("/", "container_path") - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) - if err != nil { - t.Fatalf("couldn't copy from absolute path: %s:%s %s", cleanedContainerID, path, err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() @@ -285,17 +273,17 @@ func TestCpAbsoluteSymlink(t *testing.T) { // Test for #5619 // Check that symlinks which are part of the resource path are still relative to the container's rootfs func TestCpSymlinkComponent(t *testing.T) { - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path") - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path") + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { @@ -322,10 +310,7 @@ func TestCpSymlinkComponent(t *testing.T) { path := path.Join("/", "container_path", cpTestName) - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) - if err != nil { - t.Fatalf("couldn't copy from symlink path component: %s:%s %s", cleanedContainerID, path, err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() @@ -350,17 +335,17 @@ func TestCpSymlinkComponent(t *testing.T) { func TestCpUnprivilegedUser(t *testing.T) { testRequires(t, UnixCli) // uses chmod/su: not available on windows - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName) - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName) + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } tmpdir, err := ioutil.TempDir("", "docker-integration") @@ -393,24 +378,21 @@ func TestCpSpecialFiles(t *testing.T) { } defer os.RemoveAll(outDir) - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo") - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo") + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } // Copy actual /etc/resolv.conf - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/etc/resolv.conf", outDir) - if err != nil { - t.Fatalf("couldn't copy from container: %s:%s %v", cleanedContainerID, "/etc/resolv.conf", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/etc/resolv.conf", outDir) expected, err := ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/resolv.conf") actual, err := ioutil.ReadFile(outDir + "/resolv.conf") @@ -420,10 +402,7 @@ func TestCpSpecialFiles(t *testing.T) { } // Copy actual /etc/hosts - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/etc/hosts", outDir) - if err != nil { - t.Fatalf("couldn't copy from container: %s:%s %v", cleanedContainerID, "/etc/hosts", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/etc/hosts", outDir) expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hosts") actual, err = ioutil.ReadFile(outDir + "/hosts") @@ -433,10 +412,7 @@ func TestCpSpecialFiles(t *testing.T) { } // Copy actual /etc/resolv.conf - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/etc/hostname", outDir) - if err != nil { - t.Fatalf("couldn't copy from container: %s:%s %v", cleanedContainerID, "/etc/hostname", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/etc/hostname", outDir) expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hostname") actual, err = ioutil.ReadFile(outDir + "/hostname") @@ -466,24 +442,22 @@ func TestCpVolumePath(t *testing.T) { t.Fatal(err) } - out, exitCode, err := dockerCmd(t, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar") - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar") + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer dockerCmd(t, "rm", "-fv", cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } // Copy actual volume path - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/foo", outDir) - if err != nil { - t.Fatalf("couldn't copy from volume path: %s:%s %v", cleanedContainerID, "/foo", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/foo", outDir) + stat, err := os.Stat(outDir + "/foo") if err != nil { t.Fatal(err) @@ -500,10 +474,8 @@ func TestCpVolumePath(t *testing.T) { } // Copy file nested in volume - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/foo/bar", outDir) - if err != nil { - t.Fatalf("couldn't copy from volume path: %s:%s %v", cleanedContainerID, "/foo", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/foo/bar", outDir) + stat, err = os.Stat(outDir + "/bar") if err != nil { t.Fatal(err) @@ -513,10 +485,7 @@ func TestCpVolumePath(t *testing.T) { } // Copy Bind-mounted dir - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/baz", outDir) - if err != nil { - t.Fatalf("couldn't copy from bind-mounted volume path: %s:%s %v", cleanedContainerID, "/baz", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/baz", outDir) stat, err = os.Stat(outDir + "/baz") if err != nil { t.Fatal(err) @@ -526,7 +495,7 @@ func TestCpVolumePath(t *testing.T) { } // Copy file nested in bind-mounted dir - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/baz/test", outDir) + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/baz/test", outDir) fb, err := ioutil.ReadFile(outDir + "/baz/test") if err != nil { t.Fatal(err) @@ -540,7 +509,7 @@ func TestCpVolumePath(t *testing.T) { } // Copy bind-mounted file - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/test", outDir) + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/test", outDir) fb, err = ioutil.ReadFile(outDir + "/test") if err != nil { t.Fatal(err) @@ -557,17 +526,17 @@ func TestCpVolumePath(t *testing.T) { } func TestCpToDot(t *testing.T) { - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } tmpdir, err := ioutil.TempDir("", "docker-integration") @@ -583,10 +552,7 @@ func TestCpToDot(t *testing.T) { if err := os.Chdir(tmpdir); err != nil { t.Fatal(err) } - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/test", ".") - if err != nil { - t.Fatalf("couldn't docker cp to \".\" path: %s", err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/test", ".") content, err := ioutil.ReadFile("./test") if string(content) != "lololol\n" { t.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") @@ -595,22 +561,23 @@ func TestCpToDot(t *testing.T) { } func TestCpToStdout(t *testing.T) { - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") - if err != nil || exitCode != 0 { - t.Fatalf("failed to create a container:%s\n%s", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") + if exitCode != 0 { + t.Fatalf("failed to create a container:%s\n", out) } cID := strings.TrimSpace(out) defer deleteContainer(cID) - out, _, err = dockerCmd(t, "wait", cID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatalf("failed to set up container:%s\n%s", out, err) + out, _ = dockerCmd(t, "wait", cID) + if strings.TrimSpace(out) != "0" { + t.Fatalf("failed to set up container:%s\n", out) } - out, _, err = runCommandPipelineWithOutput( + out, _, err := runCommandPipelineWithOutput( exec.Command(dockerBinary, "cp", cID+":/test", "-"), exec.Command("tar", "-vtf", "-")) + if err != nil { t.Fatalf("Failed to run commands: %s", err) } @@ -624,17 +591,17 @@ func TestCpToStdout(t *testing.T) { func TestCpNameHasColon(t *testing.T) { testRequires(t, SameHostDaemon) - out, exitCode, err := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t") - if err != nil || exitCode != 0 { - t.Fatal("failed to create a container", out, err) + out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t") + if exitCode != 0 { + t.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _, err = dockerCmd(t, "wait", cleanedContainerID) - if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + out, _ = dockerCmd(t, "wait", cleanedContainerID) + if strings.TrimSpace(out) != "0" { + t.Fatal("failed to set up container", out) } tmpdir, err := ioutil.TempDir("", "docker-integration") @@ -642,10 +609,7 @@ func TestCpNameHasColon(t *testing.T) { t.Fatal(err) } defer os.RemoveAll(tmpdir) - _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/te:s:t", tmpdir) - if err != nil { - t.Fatalf("couldn't docker cp to %s: %s", tmpdir, err) - } + _, _ = dockerCmd(t, "cp", cleanedContainerID+":/te:s:t", tmpdir) content, err := ioutil.ReadFile(tmpdir + "/te:s:t") if string(content) != "lololol\n" { t.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index ac10b264e..a8cc09106 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -307,7 +307,7 @@ func TestCreateLabelFromImage(t *testing.T) { } func TestCreateHostnameWithNumber(t *testing.T) { - out, _, _ := dockerCmd(t, "run", "-h", "web.0", "busybox", "hostname") + out, _ := dockerCmd(t, "run", "-h", "web.0", "busybox", "hostname") if strings.TrimSpace(out) != "web.0" { t.Fatalf("hostname not set, expected `web.0`, got: %s", out) } diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 767af5501..3e4c005b2 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -38,7 +38,7 @@ func TestEventsUntag(t *testing.T) { func TestEventsContainerFailStartDie(t *testing.T) { defer deleteAllContainers() - out, _, _ := dockerCmd(t, "images", "-q") + out, _ := dockerCmd(t, "images", "-q") image := strings.Split(out, "\n")[0] eventsCmd := exec.Command(dockerBinary, "run", "--name", "testeventdie", image, "blerg") _, _, err := runCommandWithOutput(eventsCmd) diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index d4e483971..6a0999eef 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -500,12 +500,12 @@ func TestLinksPingLinkedContainersOnRename(t *testing.T) { defer deleteAllContainers() var out string - out, _, _ = dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") + out, _ = dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") idA := strings.TrimSpace(out) if idA == "" { t.Fatal(out, "id should not be nil") } - out, _, _ = dockerCmd(t, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") + out, _ = dockerCmd(t, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") idB := strings.TrimSpace(out) if idB == "" { t.Fatal(out, "id should not be nil") diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index 04718c23f..0f48cbb1e 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -110,9 +110,9 @@ func TestLinksPingLinkedContainers(t *testing.T) { func TestLinksPingLinkedContainersAfterRename(t *testing.T) { defer deleteAllContainers() - out, _, _ := dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") + out, _ := dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") idA := strings.TrimSpace(out) - out, _, _ = dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") + out, _ = dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") idB := strings.TrimSpace(out) dockerCmd(t, "rename", "container1", "container_new") dockerCmd(t, "run", "--rm", "--link", "container_new:alias1", "--link", "container2:alias2", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1") diff --git a/integration-cli/docker_cli_pause_test.go b/integration-cli/docker_cli_pause_test.go index 41147b206..6c620e7cd 100644 --- a/integration-cli/docker_cli_pause_test.go +++ b/integration-cli/docker_cli_pause_test.go @@ -12,7 +12,7 @@ func TestPause(t *testing.T) { defer unpauseAllContainers() name := "testeventpause" - out, _, _ := dockerCmd(t, "images", "-q") + out, _ := dockerCmd(t, "images", "-q") image := strings.Split(out, "\n")[0] dockerCmd(t, "run", "-d", "--name", name, image, "top") @@ -55,7 +55,7 @@ func TestPauseMultipleContainers(t *testing.T) { "testpausewithmorecontainers1", "testpausewithmorecontainers2", } - out, _, _ := dockerCmd(t, "images", "-q") + out, _ := dockerCmd(t, "images", "-q") image := strings.Split(out, "\n")[0] for _, name := range containers { dockerCmd(t, "run", "-d", "--name", name, image, "top") diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index 7161a0922..f6b12aa6c 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -29,7 +29,7 @@ func TestRmiWithContainerFails(t *testing.T) { } // make sure it didn't delete the busybox name - images, _, _ := dockerCmd(t, "images") + images, _ := dockerCmd(t, "images") if !strings.Contains(images, "busybox") { t.Fatalf("The name 'busybox' should not have been removed from images: %q", images) } @@ -40,19 +40,19 @@ func TestRmiWithContainerFails(t *testing.T) { } func TestRmiTag(t *testing.T) { - imagesBefore, _, _ := dockerCmd(t, "images", "-a") + imagesBefore, _ := dockerCmd(t, "images", "-a") dockerCmd(t, "tag", "busybox", "utest:tag1") dockerCmd(t, "tag", "busybox", "utest/docker:tag2") dockerCmd(t, "tag", "busybox", "utest:5000/docker:tag3") { - imagesAfter, _, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(t, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+3 { t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } } dockerCmd(t, "rmi", "utest/docker:tag2") { - imagesAfter, _, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(t, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+2 { t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } @@ -60,7 +60,7 @@ func TestRmiTag(t *testing.T) { } dockerCmd(t, "rmi", "utest:5000/docker:tag3") { - imagesAfter, _, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(t, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+1 { t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } @@ -68,7 +68,7 @@ func TestRmiTag(t *testing.T) { } dockerCmd(t, "rmi", "utest:tag1") { - imagesAfter, _, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(t, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+0 { t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } @@ -90,22 +90,22 @@ func TestRmiImgIDForce(t *testing.T) { t.Fatalf("failed to commit a new busybox-test:%s, %v", out, err) } - imagesBefore, _, _ := dockerCmd(t, "images", "-a") + imagesBefore, _ := dockerCmd(t, "images", "-a") dockerCmd(t, "tag", "busybox-test", "utest:tag1") dockerCmd(t, "tag", "busybox-test", "utest:tag2") dockerCmd(t, "tag", "busybox-test", "utest/docker:tag3") dockerCmd(t, "tag", "busybox-test", "utest:5000/docker:tag4") { - imagesAfter, _, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(t, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+4 { t.Fatalf("tag busybox to create 4 more images with same imageID; docker images shows: %q\n", imagesAfter) } } - out, _, _ = dockerCmd(t, "inspect", "-f", "{{.Id}}", "busybox-test") + out, _ = dockerCmd(t, "inspect", "-f", "{{.Id}}", "busybox-test") imgID := strings.TrimSpace(out) dockerCmd(t, "rmi", "-f", imgID) { - imagesAfter, _, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(t, "images", "-a") if strings.Contains(imagesAfter, imgID[:12]) { t.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter) } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index e990f086a..137f1776e 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -501,9 +501,7 @@ func TestRunCreateVolumesInSymlinkDir(t *testing.T) { } defer deleteImages(name) - if out, _, err := dockerCmd(t, "run", "-v", "/test/test", name); err != nil { - t.Fatal(err, out) - } + dockerCmd(t, "run", "-v", "/test/test", name) logDone("run - create volume in symlink directory") } @@ -1058,9 +1056,9 @@ func TestRunLoopbackWhenNetworkDisabled(t *testing.T) { func TestRunNetHostNotAllowedWithLinks(t *testing.T) { defer deleteAllContainers() - _, _, err := dockerCmd(t, "run", "--name", "linked", "busybox", "true") + _, _ = dockerCmd(t, "run", "--name", "linked", "busybox", "true") cmd := exec.Command(dockerBinary, "run", "--net=host", "--link", "linked:linked", "busybox", "true") - _, _, err = runCommandWithOutput(cmd) + _, _, err := runCommandWithOutput(cmd) if err == nil { t.Fatal("Expected error") } @@ -1493,10 +1491,7 @@ func TestRunModeHostname(t *testing.T) { func TestRunRootWorkdir(t *testing.T) { defer deleteAllContainers() - s, _, err := dockerCmd(t, "run", "--workdir", "/", "busybox", "pwd") - if err != nil { - t.Fatal(s, err) - } + s, _ := dockerCmd(t, "run", "--workdir", "/", "busybox", "pwd") if s != "/\n" { t.Fatalf("pwd returned %q (expected /\\n)", s) } @@ -1507,10 +1502,7 @@ func TestRunRootWorkdir(t *testing.T) { func TestRunAllowBindMountingRoot(t *testing.T) { defer deleteAllContainers() - s, _, err := dockerCmd(t, "run", "-v", "/:/host", "busybox", "ls", "/host") - if err != nil { - t.Fatal(s, err) - } + _, _ = dockerCmd(t, "run", "-v", "/:/host", "busybox", "ls", "/host") logDone("run - bind mount / as volume") } diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index c703e434c..342209dc4 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -157,7 +157,7 @@ func TestStartVolumesFromFailsCleanly(t *testing.T) { dockerCmd(t, "start", "consumer") // Check that we have the volumes we want - out, _, _ := dockerCmd(t, "inspect", "--format='{{ len .Volumes }}'", "consumer") + out, _ := dockerCmd(t, "inspect", "--format='{{ len .Volumes }}'", "consumer") nVolumes := strings.Trim(out, " \r\n'") if nVolumes != "2" { t.Fatalf("Missing volumes: expected 2, got %s", nVolumes) diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 367e51809..4e622b84c 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -478,12 +478,12 @@ func pullImageIfNotExist(image string) (err error) { return } -func dockerCmd(t *testing.T, args ...string) (string, int, error) { +func dockerCmd(t *testing.T, args ...string) (string, int) { out, status, err := runCommandWithOutput(exec.Command(dockerBinary, args...)) if err != nil { t.Fatalf("%q failed with errors: %s, %v", strings.Join(args, " "), out, err) } - return out, status, err + return out, status } // execute a docker command with a timeout @@ -784,9 +784,9 @@ func getContainerState(t *testing.T, id string) (int, bool, error) { exitStatus int running bool ) - out, exitCode, err := dockerCmd(t, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id) - if err != nil || exitCode != 0 { - return 0, false, fmt.Errorf("%q doesn't exist: %s", id, err) + out, exitCode := dockerCmd(t, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id) + if exitCode != 0 { + return 0, false, fmt.Errorf("%q doesn't exist: %s", id, out) } out = strings.Trim(out, "\n") From 6e38a53f96403b4cbd38e49e6b294128ed054a20 Mon Sep 17 00:00:00 2001 From: Simei He Date: Wed, 15 Apr 2015 19:43:15 +0800 Subject: [PATCH 501/999] remove job from pull and import Closes #12396 Signed-off-by: Simei He Signed-off-by: Alexander Morozov --- api/server/server.go | 54 ++++++++++++++++++++++++------------- builder/internals.go | 18 ++++++++----- graph/import.go | 37 +++++++++++++------------ graph/pull.go | 37 +++++++++++-------------- graph/service.go | 2 -- integration-cli/utils.go | 1 + integration/runtime_test.go | 11 +++++--- 7 files changed, 89 insertions(+), 71 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 7ebeb4b6a..429767298 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -639,7 +639,6 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon image = r.Form.Get("fromImage") repo = r.Form.Get("repo") tag = r.Form.Get("tag") - job *engine.Job ) authEncoded := r.Header.Get("X-Registry-Auth") authConfig := ®istry.AuthConfig{} @@ -651,6 +650,9 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon authConfig = ®istry.AuthConfig{} } } + + d := getDaemon(eng) + if image != "" { //pull if tag == "" { image, tag = parsers.ParseRepositoryTag(image) @@ -661,31 +663,45 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon metaHeaders[k] = v } } - job = eng.Job("pull", image, tag) - job.SetenvBool("parallel", version.GreaterThan("1.3")) - job.SetenvJson("metaHeaders", metaHeaders) - job.SetenvJson("authConfig", authConfig) + + imagePullConfig := &graph.ImagePullConfig{ + Parallel: version.GreaterThan("1.3"), + MetaHeaders: metaHeaders, + AuthConfig: authConfig, + OutStream: utils.NewWriteFlusher(w), + } + if version.GreaterThan("1.0") { + imagePullConfig.Json = true + w.Header().Set("Content-Type", "application/json") + } else { + imagePullConfig.Json = false + } + + if err := d.Repositories().Pull(image, tag, imagePullConfig, eng); err != nil { + return err + } } else { //import if tag == "" { repo, tag = parsers.ParseRepositoryTag(repo) } - job = eng.Job("import", r.Form.Get("fromSrc"), repo, tag) - job.Stdin.Add(r.Body) - job.SetenvList("changes", r.Form["changes"]) - } - if version.GreaterThan("1.0") { - job.SetenvBool("json", true) - streamJSON(job, w, true) - } else { - job.Stdout.Add(utils.NewWriteFlusher(w)) - } - if err := job.Run(); err != nil { - if !job.Stdout.Used() { + src := r.Form.Get("fromSrc") + imageImportConfig := &graph.ImageImportConfig{ + Changes: r.Form["changes"], + InConfig: r.Body, + OutStream: utils.NewWriteFlusher(w), + } + if version.GreaterThan("1.0") { + imageImportConfig.Json = true + w.Header().Set("Content-Type", "application/json") + } else { + imageImportConfig.Json = false + } + + if err := d.Repositories().Import(src, repo, tag, imageImportConfig, eng); err != nil { return err } - sf := streamformatter.NewStreamFormatter(version.GreaterThan("1.0")) - w.Write(sf.FormatError(err)) + } return nil diff --git a/builder/internals.go b/builder/internals.go index 728ccde8a..dec84ffe0 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -22,6 +22,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/builder/parser" "github.com/docker/docker/daemon" + "github.com/docker/docker/graph" imagepkg "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" @@ -434,7 +435,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { if tag == "" { tag = "latest" } - job := b.Engine.Job("pull", remote, tag) + pullRegistryAuth := b.AuthConfig if len(b.AuthConfigFile.Configs) > 0 { // The request came with a full auth config file, we prefer to use that @@ -445,13 +446,18 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(repoInfo.Index) pullRegistryAuth = &resolvedAuth } - job.SetenvBool("json", b.StreamFormatter.Json()) - job.SetenvBool("parallel", true) - job.SetenvJson("authConfig", pullRegistryAuth) - job.Stdout.Add(ioutils.NopWriteCloser(b.OutOld)) - if err := job.Run(); err != nil { + + imagePullConfig := &graph.ImagePullConfig{ + Parallel: true, + AuthConfig: pullRegistryAuth, + OutStream: ioutils.NopWriteCloser(b.OutOld), + Json: b.StreamFormatter.Json(), + } + + if err := b.Daemon.Repositories().Pull(remote, tag, imagePullConfig, b.Engine); err != nil { return nil, err } + image, err := b.Daemon.Repositories().LookupImage(name) if err != nil { return nil, err diff --git a/graph/import.go b/graph/import.go index 0ba03d0f5..a970b22d9 100644 --- a/graph/import.go +++ b/graph/import.go @@ -3,7 +3,7 @@ package graph import ( "bytes" "encoding/json" - "fmt" + "io" "net/http" "net/url" @@ -16,26 +16,25 @@ import ( "github.com/docker/docker/utils" ) -func (s *TagStore) CmdImport(job *engine.Job) error { - if n := len(job.Args); n != 2 && n != 3 { - return fmt.Errorf("Usage: %s SRC REPO [TAG]", job.Name) - } +type ImageImportConfig struct { + Changes []string + InConfig io.ReadCloser + Json bool + OutStream io.Writer + //OutStream WriteFlusher +} + +func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig *ImageImportConfig, eng *engine.Engine) error { var ( - src = job.Args[0] - repo = job.Args[1] - tag string - sf = streamformatter.NewStreamFormatter(job.GetenvBool("json")) + sf = streamformatter.NewStreamFormatter(imageImportConfig.Json) archive archive.ArchiveReader resp *http.Response stdoutBuffer = bytes.NewBuffer(nil) newConfig runconfig.Config ) - if len(job.Args) > 2 { - tag = job.Args[2] - } if src == "-" { - archive = job.Stdin + archive = imageImportConfig.InConfig } else { u, err := url.Parse(src) if err != nil { @@ -46,14 +45,14 @@ func (s *TagStore) CmdImport(job *engine.Job) error { u.Host = src u.Path = "" } - job.Stdout.Write(sf.FormatStatus("", "Downloading from %s", u)) + imageImportConfig.OutStream.Write(sf.FormatStatus("", "Downloading from %s", u)) resp, err = httputils.Download(u.String()) if err != nil { return err } progressReader := progressreader.New(progressreader.Config{ In: resp.Body, - Out: job.Stdout, + Out: imageImportConfig.OutStream, Formatter: sf, Size: int(resp.ContentLength), NewLines: true, @@ -64,11 +63,11 @@ func (s *TagStore) CmdImport(job *engine.Job) error { archive = progressReader } - buildConfigJob := job.Eng.Job("build_config") + buildConfigJob := eng.Job("build_config") buildConfigJob.Stdout.Add(stdoutBuffer) - buildConfigJob.Setenv("changes", job.Getenv("changes")) + buildConfigJob.SetenvList("changes", imageImportConfig.Changes) // FIXME this should be remove when we remove deprecated config param - buildConfigJob.Setenv("config", job.Getenv("config")) + //buildConfigJob.Setenv("config", job.Getenv("config")) if err := buildConfigJob.Run(); err != nil { return err @@ -87,7 +86,7 @@ func (s *TagStore) CmdImport(job *engine.Job) error { return err } } - job.Stdout.Write(sf.FormatStatus("", img.ID)) + imageImportConfig.OutStream.Write(sf.FormatStatus("", img.ID)) logID := img.ID if tag != "" { logID = utils.ImageReference(logID, tag) diff --git a/graph/pull.go b/graph/pull.go index a9f91b4a2..4cb5957a5 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -21,37 +21,30 @@ import ( "github.com/docker/docker/utils" ) -func (s *TagStore) CmdPull(job *engine.Job) error { - if n := len(job.Args); n != 1 && n != 2 { - return fmt.Errorf("Usage: %s IMAGE [TAG|DIGEST]", job.Name) - } +type ImagePullConfig struct { + Parallel bool + MetaHeaders map[string][]string + AuthConfig *registry.AuthConfig + Json bool + OutStream io.Writer +} +func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig, eng *engine.Engine) error { var ( - localName = job.Args[0] - tag string - sf = streamformatter.NewStreamFormatter(job.GetenvBool("json")) - authConfig = ®istry.AuthConfig{} - metaHeaders map[string][]string + sf = streamformatter.NewStreamFormatter(imagePullConfig.Json) ) // Resolve the Repository name from fqn to RepositoryInfo - repoInfo, err := s.registryService.ResolveRepository(localName) + repoInfo, err := s.registryService.ResolveRepository(image) if err != nil { return err } - if len(job.Args) > 1 { - tag = job.Args[1] - } - - job.GetenvJson("authConfig", authConfig) - job.GetenvJson("metaHeaders", &metaHeaders) - c, err := s.poolAdd("pull", utils.ImageReference(repoInfo.LocalName, tag)) if err != nil { if c != nil { // Another pull of the same repository is already taking place; just wait for it to finish - job.Stdout.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", repoInfo.LocalName)) + imagePullConfig.OutStream.Write(sf.FormatStatus("", "Repository %s already being pulled by another client. Waiting.", repoInfo.LocalName)) <-c return nil } @@ -65,7 +58,7 @@ func (s *TagStore) CmdPull(job *engine.Job) error { return err } - r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, true) + r, err := registry.NewSession(imagePullConfig.AuthConfig, registry.HTTPRequestFactory(imagePullConfig.MetaHeaders), endpoint, true) if err != nil { return err } @@ -77,14 +70,14 @@ func (s *TagStore) CmdPull(job *engine.Job) error { if len(repoInfo.Index.Mirrors) == 0 && (repoInfo.Index.Official || endpoint.Version == registry.APIVersion2) { if repoInfo.Official { - j := job.Eng.Job("trust_update_base") + j := eng.Job("trust_update_base") if err = j.Run(); err != nil { logrus.Errorf("error updating trust base graph: %s", err) } } logrus.Debugf("pulling v2 repository with local name %q", repoInfo.LocalName) - if err := s.pullV2Repository(job.Eng, r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err == nil { + if err := s.pullV2Repository(eng, r, imagePullConfig.OutStream, repoInfo, tag, sf, imagePullConfig.Parallel); err == nil { s.eventsService.Log("pull", logName, "") return nil } else if err != registry.ErrDoesNotExist && err != ErrV2RegistryUnavailable { @@ -95,7 +88,7 @@ func (s *TagStore) CmdPull(job *engine.Job) error { } logrus.Debugf("pulling v1 repository with local name %q", repoInfo.LocalName) - if err = s.pullRepository(r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err != nil { + if err = s.pullRepository(r, imagePullConfig.OutStream, repoInfo, tag, sf, imagePullConfig.Parallel); err != nil { return err } diff --git a/graph/service.go b/graph/service.go index 46f83103d..adc775235 100644 --- a/graph/service.go +++ b/graph/service.go @@ -19,8 +19,6 @@ func (s *TagStore) Install(eng *engine.Engine) error { "image_export": s.CmdImageExport, "viz": s.CmdViz, "load": s.CmdLoad, - "import": s.CmdImport, - "pull": s.CmdPull, "push": s.CmdPush, } { if err := eng.Register(name, handler); err != nil { diff --git a/integration-cli/utils.go b/integration-cli/utils.go index 1fcf44535..c3a84bbc5 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -143,6 +143,7 @@ func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in if i > 0 { prevCmd := cmds[i-1] cmd.Stdin, err = prevCmd.StdoutPipe() + if err != nil { return "", 0, fmt.Errorf("cannot set stdout pipe for %s: %v", cmd.Path, err) } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 6881fbea6..cd3033939 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -27,6 +27,7 @@ import ( "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -132,9 +133,13 @@ func setupBaseImage() { // If the unit test is not found, try to download it. if err := job.Run(); err != nil || img.Get("Id") != unitTestImageID { // Retrieve the Image - job = eng.Job("pull", unitTestImageName) - job.Stdout.Add(ioutils.NopWriteCloser(os.Stdout)) - if err := job.Run(); err != nil { + imagePullConfig := &graph.ImagePullConfig{ + Parallel: true, + OutStream: ioutils.NopWriteCloser(os.Stdout), + AuthConfig: ®istry.AuthConfig{}, + } + d := getDaemon(eng) + if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig, eng); err != nil { logrus.Fatalf("Unable to pull the test image: %s", err) } } From cdc63ce5d032de593fc2fd13997311b316c0103b Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Fri, 17 Apr 2015 10:56:12 -0700 Subject: [PATCH 502/999] Updated message severity in graphdriver Signed-off-by: Megan Kostick --- daemon/graphdriver/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 0260e532d..c57dd8713 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -146,7 +146,7 @@ func GetDriver(name, home string, options []string) (Driver, error) { func New(root string, options []string) (driver Driver, err error) { for _, name := range []string{os.Getenv("DOCKER_DRIVER"), DefaultDriver} { if name != "" { - logrus.Infof("[graphdriver] trying provided driver %q", name) // so the logs show specified driver + logrus.Debugf("[graphdriver] trying provided driver %q", name) // so the logs show specified driver return GetDriver(name, root, options) } } From 3a883672417fcb2b3ac0d57d992285849840bfb2 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Fri, 17 Apr 2015 12:53:30 -0700 Subject: [PATCH 503/999] Updates to Compose docs and ENV vars - Compose teamhad forgotten some documentation - Updated ENV for Distribution also - Forgot one of the readability sections Signed-off-by: Mary Anthony --- docs/Dockerfile | 36 ++++++++++++++++++++++++++++++++---- docs/mkdocs.yml | 23 +++++++++++------------ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index d82c64df8..d1a0f04c0 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -6,11 +6,10 @@ MAINTAINER Sven Dowideit (@SvenDowideit) # This section ensures we pull the correct version of each # sub project -ENV COMPOSE_BRANCH 1.2.0 +ENV COMPOSE_BRANCH release ENV SWARM_BRANCH v0.2.0 ENV MACHINE_BRANCH master -ENV DISTRIB_BRANCH v2.0.0 - +ENV DISTRIB_BRANCH release/2.0 # TODO: need the full repo source to get the git version info @@ -33,8 +32,10 @@ COPY ./s3_website.json s3_website.json COPY ./release.sh release.sh +####################### # Docker Distribution -# +######################## + #ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/mkdocs.yml /docs/mkdocs-distribution.yml ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/notifications.png /docs/sources/registry/images/notifications.png @@ -64,45 +65,72 @@ RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/jso ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/auth/token.md /docs/sources/registry/spec/auth/token.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/auth/token.md +####################### # Docker Swarm +####################### + #ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/mkdocs.yml /docs/mkdocs-swarm.yml ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/index.md /docs/sources/swarm/index.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/index.md + ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/discovery/README.md /docs/sources/swarm/discovery.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/discovery.md + ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/api/README.md /docs/sources/swarm/API.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/API.md + ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/filter/README.md /docs/sources/swarm/scheduler/filter.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/filter.md + ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/strategy/README.md /docs/sources/swarm/scheduler/strategy.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/strategy.md +####################### # Docker Machine +####################### #ADD https://raw.githubusercontent.com/docker/machine/${MACHINE_BRANCH}/docs/mkdocs.yml /docs/mkdocs-machine.yml + ADD https://raw.githubusercontent.com/docker/machine/${MACHINE_BRANCH}/docs/index.md /docs/sources/machine/index.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/machine/index.md +####################### # Docker Compose +####################### + #ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/mkdocs.yml /docs/mkdocs-compose.yml + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/index.md /docs/sources/compose/index.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/index.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/install.md /docs/sources/compose/install.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/install.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/cli.md /docs/sources/compose/cli.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/cli.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/yml.md /docs/sources/compose/yml.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/yml.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/env.md /docs/sources/compose/env.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/env.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/completion.md /docs/sources/compose/completion.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/completion.md ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/django.md /docs/sources/compose/django.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/django.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/rails.md /docs/sources/compose/rails.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/rails.md + ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/wordpress.md /docs/sources/compose/wordpress.md RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/wordpress.md +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/extends.md /docs/sources/compose/extends.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/extends.md + +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/production.md /docs/sources/compose/production.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/production.md + # Then build everything together, ready for mkdocs RUN /docs/build.sh \ No newline at end of file diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index f159ff28b..5fb99ffc4 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -1,5 +1,5 @@ site_name: Docker Documentation -#site_url: https://docs.docker.com/ +#site_url: http://docs.docker.com/ site_url: / site_description: Documentation for fast and lightweight Docker container based virtualization framework. site_favicon: img/favicon.png @@ -66,6 +66,8 @@ pages: - ['userguide/level1.md', '**HIDDEN**' ] - ['userguide/level2.md', '**HIDDEN**' ] - ['compose/index.md', 'User Guide', 'Docker Compose' ] +- ['compose/production.md', 'User Guide', '    ▪  Use Compose in production' ] +- ['compose/extends.md', 'User Guide', '    ▪  Extend Compose services' ] - ['machine/index.md', 'User Guide', 'Docker Machine' ] - ['swarm/index.md', 'User Guide', 'Docker Swarm' ] @@ -122,7 +124,6 @@ pages: - ['reference/commandline/cli.md', 'Reference', 'Docker command line'] - ['reference/builder.md', 'Reference', 'Dockerfile'] - ['faq.md', 'Reference', 'FAQ'] -- ['reference/glossary.md', 'Reference', 'Glossary'] - ['reference/run.md', 'Reference', 'Run Reference'] - ['compose/cli.md', 'Reference', 'Compose command line'] - ['compose/yml.md', 'Reference', 'Compose yml'] @@ -148,10 +149,9 @@ pages: - ['reference/api/docker-io_api.md', 'Reference', 'Docker Hub API'] #- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0'] - ['reference/api/docker_remote_api.md', 'Reference', 'Docker Remote API'] -- ['reference/api/docker_remote_api_v1.19.md', 'Reference', 'Docker Remote API v1.19'] - ['reference/api/docker_remote_api_v1.18.md', 'Reference', 'Docker Remote API v1.18'] - ['reference/api/docker_remote_api_v1.17.md', 'Reference', 'Docker Remote API v1.17'] -- ['reference/api/docker_remote_api_v1.16.md', '**HIDDEN**'] +- ['reference/api/docker_remote_api_v1.16.md', 'Reference', 'Docker Remote API v1.16'] - ['reference/api/docker_remote_api_v1.15.md', '**HIDDEN**'] - ['reference/api/docker_remote_api_v1.14.md', '**HIDDEN**'] - ['reference/api/docker_remote_api_v1.13.md', '**HIDDEN**'] @@ -187,17 +187,16 @@ pages: # Project: - ['project/index.md', '**HIDDEN**'] - ['project/who-written-for.md', 'Contributor Guide', 'README first'] -- ['project/software-required.md', 'Contributor Guide', 'Get required software'] -- ['project/set-up-git.md', 'Contributor Guide', 'Configure Git for contributing'] -- ['project/set-up-dev-env.md', 'Contributor Guide', 'Work with a development container'] +- ['project/software-required.md', 'Contributor Guide', 'Get required software'] +- ['project/set-up-git.md', 'Contributor Guide', 'Configure Git for contributing'] +- ['project/set-up-dev-env.md', 'Contributor Guide', 'Work with a development container'] - ['project/test-and-docs.md', 'Contributor Guide', 'Run tests and test documentation'] - ['project/make-a-contribution.md', 'Contributor Guide', 'Understand contribution workflow'] -- ['project/find-an-issue.md', 'Contributor Guide', 'Find an issue'] -- ['project/work-issue.md', 'Contributor Guide', 'Work on an issue'] -- ['project/create-pr.md', 'Contributor Guide', 'Create a pull request'] -- ['project/review-pr.md', 'Contributor Guide', 'Participate in the PR review'] +- ['project/find-an-issue.md', 'Contributor Guide', 'Find an issue'] +- ['project/work-issue.md', 'Contributor Guide', 'Work on an issue'] +- ['project/create-pr.md', 'Contributor Guide', 'Create a pull request'] +- ['project/review-pr.md', 'Contributor Guide', 'Participate in the PR review'] - ['project/advanced-contributing.md', 'Contributor Guide', 'Advanced contributing'] - ['project/get-help.md', 'Contributor Guide', 'Where to get help'] - ['project/coding-style.md', 'Contributor Guide', 'Coding style guide'] - ['project/doc-style.md', 'Contributor Guide', 'Documentation style guide'] - From bbe6df128802b22605f9eb079f105460ec78ac6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sat, 18 Apr 2015 12:59:20 +0200 Subject: [PATCH 504/999] docs: speed up build by reducing build steps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - should be also easier to maintain Signed-off-by: Jörg Thalheim --- docs/Dockerfile | 97 +++++++++++++++++-------------------------------- 1 file changed, 34 insertions(+), 63 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index d1a0f04c0..956879fec 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -27,10 +27,7 @@ COPY ./VERSION VERSION #COPY ./image/spec/v1.md /docs/sources/reference/image-spec-v1.md # TODO: don't do this - look at merging the yml file in build.sh -COPY ./mkdocs.yml mkdocs.yml -COPY ./s3_website.json s3_website.json -COPY ./release.sh release.sh - +COPY ./mkdocs.yml ./s3_website.json ./release.sh ./ ####################### # Docker Distribution @@ -38,32 +35,27 @@ COPY ./release.sh release.sh #ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/mkdocs.yml /docs/mkdocs-distribution.yml -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/notifications.png /docs/sources/registry/images/notifications.png -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/registry.png /docs/sources/registry/images/registry.png +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/notifications.png \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/registry.png \ + /docs/sources/registry/images/ -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md /docs/sources/registry/overview.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/overview.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/deploying.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/configuration.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storagedrivers.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/notifications.md \ + /docs/sources/registry/ -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/deploying.md /docs/sources/registry/deploying.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/deploying.md - -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/configuration.md /docs/sources/registry/configuration.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/configuration.md - -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storagedrivers.md /docs/sources/registry/storagedrivers.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/storagedrivers.md - -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/notifications.md /docs/sources/registry/notifications.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/notifications.md - -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/api.md /docs/sources/registry/spec/api.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/api.md - -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/json.md /docs/sources/registry/spec/json.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/json.md +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/api.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/json.md \ + /docs/sources/registry/spec/ ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/auth/token.md /docs/sources/registry/spec/auth/token.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/auth/token.md + +RUN sed -i.old '1s;^;no_version_dropdown: true;' \ + /docs/sources/registry/*.md \ + /docs/sources/registry/spec/*.md \ + /docs/sources/registry/spec/auth/*.md ####################### # Docker Swarm @@ -71,19 +63,16 @@ RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/registry/spec/aut #ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/mkdocs.yml /docs/mkdocs-swarm.yml ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/docs/index.md /docs/sources/swarm/index.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/index.md ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/discovery/README.md /docs/sources/swarm/discovery.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/discovery.md ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/api/README.md /docs/sources/swarm/API.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/API.md ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/filter/README.md /docs/sources/swarm/scheduler/filter.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/filter.md ADD https://raw.githubusercontent.com/docker/swarm/${SWARM_BRANCH}/scheduler/strategy/README.md /docs/sources/swarm/scheduler/strategy.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/scheduler/strategy.md + +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/swarm/*.md /docs/sources/swarm/scheduler/*.md ####################### # Docker Machine @@ -99,38 +88,20 @@ RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/machine/index.md #ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/mkdocs.yml /docs/mkdocs-compose.yml -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/index.md /docs/sources/compose/index.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/index.md +ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/index.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/install.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/cli.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/yml.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/env.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/completion.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/django.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/rails.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/wordpress.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/extends.md \ + https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/production.md \ + /docs/sources/compose/ -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/install.md /docs/sources/compose/install.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/install.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/cli.md /docs/sources/compose/cli.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/cli.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/yml.md /docs/sources/compose/yml.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/yml.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/env.md /docs/sources/compose/env.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/env.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/completion.md /docs/sources/compose/completion.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/completion.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/django.md /docs/sources/compose/django.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/django.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/rails.md /docs/sources/compose/rails.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/rails.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/wordpress.md /docs/sources/compose/wordpress.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/wordpress.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/extends.md /docs/sources/compose/extends.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/extends.md - -ADD https://raw.githubusercontent.com/docker/compose/${COMPOSE_BRANCH}/docs/production.md /docs/sources/compose/production.md -RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/production.md +RUN sed -i.old '1s;^;no_version_dropdown: true;' /docs/sources/compose/*.md # Then build everything together, ready for mkdocs -RUN /docs/build.sh \ No newline at end of file +RUN /docs/build.sh From 89df65be5d3ee72cd6e1b4aa53953a62f4b5d00a Mon Sep 17 00:00:00 2001 From: Jeff Nickoloff Date: Sat, 18 Apr 2015 11:34:28 -0700 Subject: [PATCH 505/999] Update builder.md Single value labels do not work in 1.6 and multi-label instructions only work when separated by non-EOL whitespace. I also added an example snip from the inspect output with the labels that are included in this guide. Signed-off-by: Jeff Nickoloff --- docs/sources/reference/builder.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index d837541aa..a4fcbebc1 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -373,9 +373,8 @@ blackslashes as you would in command-line parsing. LABEL "com.example.vendor"="ACME Incorporated" An image can have more than one label. To specify multiple labels, separate each -key-value pair by an EOL. +key-value pair with whitespace. - LABEL com.example.label-without-value LABEL com.example.label-with-value="foo" LABEL version="1.0" LABEL description="This text illustrates \ @@ -385,6 +384,8 @@ Docker recommends combining labels in a single `LABEL` instruction where possible. Each `LABEL` instruction produces a new layer which can result in an inefficient image if you use many labels. This example results in four image layers. + + LABEL multi.label1="value1" multi.label2="value2" other="value3" Labels are additive including `LABEL`s in `FROM` images. As the system encounters and then applies a new label, new `key`s override any previous labels @@ -392,6 +393,16 @@ with identical keys. To view an image's labels, use the `docker inspect` command. + "Labels": { + "com.example.vendor": "ACME Incorporated" + "com.example.label-with-value": "foo", + "version": "1.0", + "description": "This text illustrates that label-values can span multiple lines.", + "multi.label1": "value1", + "multi.label2": "value2", + "other": "value3" + }, + ## EXPOSE EXPOSE [...] From 7b2b7df3866d0c0101e9367b7f4f63bfed5faac4 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Sat, 18 Apr 2015 17:42:24 -0700 Subject: [PATCH 506/999] Docker Registry Server > Docker Registry Fixing registry index Tested on beta and this redirect works Signed-off-by: Mary Anthony --- docs/Dockerfile | 2 +- docs/man/docker-login.1.md | 2 +- docs/man/docker-logout.1.md | 4 ++-- docs/man/docker.1.md | 8 ++++---- docs/mkdocs.yml | 2 +- docs/s3_website.json | 4 +++- 6 files changed, 12 insertions(+), 10 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index 956879fec..91cf52bc1 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -39,7 +39,7 @@ ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/images/registry.png \ /docs/sources/registry/images/ -ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/overview.md \ +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/index.md \ https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/deploying.md \ https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/configuration.md \ https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storagedrivers.md \ diff --git a/docs/man/docker-login.1.md b/docs/man/docker-login.1.md index f73df77ed..87ad31b70 100644 --- a/docs/man/docker-login.1.md +++ b/docs/man/docker-login.1.md @@ -13,7 +13,7 @@ docker-login - Register or log in to a Docker registry. [SERVER] # DESCRIPTION -Register or log in to a Docker Registry Service located on the specified +Register or log in to a Docker Registry located on the specified `SERVER`. You can specify a URL or a `hostname` for the `SERVER` value. If you do not specify a `SERVER`, the command uses Docker's public registry located at `https://registry-1.docker.io/` by default. To get a username/password for Docker's public registry, create an account on Docker Hub. diff --git a/docs/man/docker-logout.1.md b/docs/man/docker-logout.1.md index d464f00fd..3726fd66c 100644 --- a/docs/man/docker-logout.1.md +++ b/docs/man/docker-logout.1.md @@ -2,14 +2,14 @@ % Docker Community % JUNE 2014 # NAME -docker-logout - Log out from a Docker Registry Service. +docker-logout - Log out from a Docker Registry. # SYNOPSIS **docker logout** [SERVER] # DESCRIPTION -Log out of a Docker Registry Service located on the specified `SERVER`. You can +Log out of a Docker Registry located on the specified `SERVER`. You can specify a URL or a `hostname` for the `SERVER` value. If you do not specify a `SERVER`, the command attempts to log you out of Docker's public registry located at `https://registry-1.docker.io/` by default. diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index ec79850de..afa14b661 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -172,10 +172,10 @@ inside it) Load an image from a tar archive **docker-login(1)** - Register or login to a Docker Registry Service + Register or login to a Docker Registry **docker-logout(1)** - Log the user out of a Docker Registry Service + Log the user out of a Docker Registry **docker-logs(1)** Fetch the logs of a container @@ -190,10 +190,10 @@ inside it) List containers **docker-pull(1)** - Pull an image or a repository from a Docker Registry Service + Pull an image or a repository from a Docker Registry **docker-push(1)** - Push an image or a repository to a Docker Registry Service + Push an image or a repository to a Docker Registry **docker-restart(1)** Restart a running container diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 5fb99ffc4..49c9b80b7 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -134,7 +134,7 @@ pages: - ['swarm/scheduler/filter.md', 'Reference', 'Swarm filters'] - ['swarm/API.md', 'Reference', 'Swarm API'] - ['reference/api/index.md', '**HIDDEN**'] -- ['registry/overview.md', 'Reference', 'Docker Registry 2.0'] +- ['registry/index.md', 'Reference', 'Docker Registry 2.0'] - ['registry/deploying.md', 'Reference', '    ▪  Deploy a registry' ] - ['registry/configuration.md', 'Reference', '    ▪  Configure a registry' ] - ['registry/storagedrivers.md', 'Reference', '    ▪  Storage driver model' ] diff --git a/docs/s3_website.json b/docs/s3_website.json index 1142fc0d8..b2479bc33 100644 --- a/docs/s3_website.json +++ b/docs/s3_website.json @@ -42,7 +42,9 @@ { "Condition": { "KeyPrefixEquals": "installation/openSUSE/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "installation/SUSE/" } }, { "Condition": { "KeyPrefixEquals": "contributing/contributing/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/who-written-for/" } }, { "Condition": { "KeyPrefixEquals": "contributing/devenvironment/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/set-up-prereqs/" } }, - { "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } } + { "Condition": { "KeyPrefixEquals": "contributing/docs_style-guide/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "project/doc-style/" } }, + { "Condition": { "KeyPrefixEquals": "registry/overview/" }, "Redirect": { "HostName": "$BUCKET", "ReplaceKeyPrefixWith": "registry/" } } + ] } From 99251f60c2f554fc03ddc9e7b478de6bd5cf0b49 Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Sat, 18 Apr 2015 13:14:44 -0700 Subject: [PATCH 507/999] Document the download location of binaries Signed-off-by: Ankush Agarwal --- docs/sources/installation/binaries.md | 89 ++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 7 deletions(-) diff --git a/docs/sources/installation/binaries.md b/docs/sources/installation/binaries.md index ef9f5cafa..855d46028 100644 --- a/docs/sources/installation/binaries.md +++ b/docs/sources/installation/binaries.md @@ -78,16 +78,91 @@ exhibit unexpected behaviour. > vendor for the system, and might break regulations and security > policies in heavily regulated environments. -## Get the docker binary: +## Get the docker binary + +You can download either the latest release binary or a specific version. +After downloading a binary file, you must set the file's execute bit to run it. + +To set the file's execute bit on Linux and OS X: - $ wget https://get.docker.com/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.com/builds/Linux/x86_64/docker-latest.tgz]( -> https://get.docker.com/builds/Linux/x86_64/docker-latest.tgz) +To get the list of stable release version numbers from Github, view the +`docker/docker` [releases page](https://github.com/docker/docker/releases). + +> **Note** +> +> 1) You can get the MD5 and SHA256 hashes by appending .md5 and .sha256 to the URLs respectively +> +> 2) You can get the compressed binaries by appending .tgz to the URLs + +### Get the Linux binary + +To download the latest version for Linux, use the +following URLs: + + https://get.docker.com/builds/Linux/i386/docker-latest + + https://get.docker.com/builds/Linux/x86_64/docker-latest + +To download a specific version for Linux, use the +following URL patterns: + + https://get.docker.com/builds/Linux/i386/docker- + + https://get.docker.com/builds/Linux/x86_64/docker- + +For example: + + https://get.docker.com/builds/Linux/i386/docker-1.6.0 + + https://get.docker.com/builds/Linux/x86_64/docker-1.6.0 + + +### Get the Mac OS X binary + +The Mac OS X binary is only a client. You cannot use it to run the `docker` +daemon. To download the latest version for Mac OS X, use the following URLs: + + https://get.docker.com/builds/Darwin/i386/docker-latest + + https://get.docker.com/builds/Darwin/x86_64/docker-latest + +To download a specific version for Mac OS X, use the +following URL patterns: + + https://get.docker.com/builds/Darwin/i386/docker- + + https://get.docker.com/builds/Darwin/x86_64/docker- + +For example: + + https://get.docker.com/builds/Darwin/i386/docker-1.6.0 + + https://get.docker.com/builds/Darwin/x86_64/docker-1.6.0 + +### Get the Windows binary + +You can only download the Windows client binary for version `1.6.0` onwards. +Moreover, the binary is only a client, you cannot use it to run the `docker` daemon. +To download the latest version for Windows, use the following URLs: + + https://get.docker.com/builds/Windows/i386/docker-latest.exe + + https://get.docker.com/builds/Windows/x86_64/docker-latest.exe + +To download a specific version for Windows, use the following URL pattern: + + https://get.docker.com/builds/Windows/i386/docker-.exe + + https://get.docker.com/builds/Windows/x86_64/docker-.exe + +For example: + + https://get.docker.com/builds/Windows/i386/docker-1.6.0.exe + + https://get.docker.com/builds/Windows/x86_64/docker-1.6.0.exe + ## Run the docker daemon From 99f6309b97041bf82cc845340734dc8e47977c8a Mon Sep 17 00:00:00 2001 From: Simei He Date: Tue, 14 Apr 2015 10:46:29 +0800 Subject: [PATCH 508/999] remove job from tag Signed-off-by: Simei He --- api/server/server.go | 8 +++-- builder/job.go | 2 +- daemon/commit.go | 2 +- graph/import.go | 2 +- graph/manifest_test.go | 2 +- graph/pull.go | 4 +-- graph/service.go | 1 - graph/tag.go | 18 ---------- graph/tags.go | 2 +- graph/tags_unit_test.go | 4 +-- integration/api_test.go | 3 +- integration/server_test.go | 71 -------------------------------------- 12 files changed, 16 insertions(+), 103 deletions(-) delete mode 100644 graph/tag.go delete mode 100644 integration/server_test.go diff --git a/api/server/server.go b/api/server/server.go index 7ed3abc88..7d31c8420 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -586,9 +586,11 @@ func postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseW return fmt.Errorf("Missing parameter") } - job := eng.Job("tag", vars["name"], r.Form.Get("repo"), r.Form.Get("tag")) - job.Setenv("force", r.Form.Get("force")) - if err := job.Run(); err != nil { + d := getDaemon(eng) + repo := r.Form.Get("repo") + tag := r.Form.Get("tag") + force := toBool(r.Form.Get("force")) + if err := d.Repositories().Tag(repo, tag, vars["name"], force); err != nil { return err } w.WriteHeader(http.StatusCreated) diff --git a/builder/job.go b/builder/job.go index 89ed52f87..7e1d035d3 100644 --- a/builder/job.go +++ b/builder/job.go @@ -164,7 +164,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { } if repoName != "" { - b.Daemon.Repositories().Set(repoName, tag, id, true) + b.Daemon.Repositories().Tag(repoName, tag, id, true) } return nil } diff --git a/daemon/commit.go b/daemon/commit.go index 1e534cf62..a60ed0819 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -101,7 +101,7 @@ func (daemon *Daemon) Commit(container *Container, repository, tag, comment, aut // Register the image if needed if repository != "" { - if err := daemon.repositories.Set(repository, tag, img.ID, true); err != nil { + if err := daemon.repositories.Tag(repository, tag, img.ID, true); err != nil { return img, err } } diff --git a/graph/import.go b/graph/import.go index eb63af0b6..7c6485d9d 100644 --- a/graph/import.go +++ b/graph/import.go @@ -82,7 +82,7 @@ func (s *TagStore) CmdImport(job *engine.Job) error { } // Optionally register the image at REPO/TAG if repo != "" { - if err := s.Set(repo, tag, img.ID, true); err != nil { + if err := s.Tag(repo, tag, img.ID, true); err != nil { return err } } diff --git a/graph/manifest_test.go b/graph/manifest_test.go index 913704182..2702dcaf5 100644 --- a/graph/manifest_test.go +++ b/graph/manifest_test.go @@ -135,7 +135,7 @@ func TestManifestTarsumCache(t *testing.T) { if err := store.graph.Register(img, archive); err != nil { t.Fatal(err) } - if err := store.Set(testManifestImageName, testManifestTag, testManifestImageID, false); err != nil { + if err := store.Tag(testManifestImageName, testManifestTag, testManifestImageID, false); err != nil { t.Fatal(err) } diff --git a/graph/pull.go b/graph/pull.go index a9f91b4a2..ce66a0333 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -249,7 +249,7 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo * if askedTag != "" && tag != askedTag { continue } - if err := s.Set(repoInfo.LocalName, tag, id, true); err != nil { + if err := s.Tag(repoInfo.LocalName, tag, id, true); err != nil { return err } } @@ -617,7 +617,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri } } else { // only set the repository/tag -> image ID mapping when pulling by tag (i.e. not by digest) - if err = s.Set(repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil { + if err = s.Tag(repoInfo.LocalName, tag, downloads[0].img.ID, true); err != nil { return false, err } } diff --git a/graph/service.go b/graph/service.go index 46f83103d..dfa35565d 100644 --- a/graph/service.go +++ b/graph/service.go @@ -12,7 +12,6 @@ import ( func (s *TagStore) Install(eng *engine.Engine) error { for name, handler := range map[string]engine.Handler{ "image_set": s.CmdSet, - "tag": s.CmdTag, "image_get": s.CmdGet, "image_inspect": s.CmdLookup, "image_tarlayer": s.CmdTarLayer, diff --git a/graph/tag.go b/graph/tag.go deleted file mode 100644 index c0b269946..000000000 --- a/graph/tag.go +++ /dev/null @@ -1,18 +0,0 @@ -package graph - -import ( - "fmt" - - "github.com/docker/docker/engine" -) - -func (s *TagStore) CmdTag(job *engine.Job) error { - if len(job.Args) != 2 && len(job.Args) != 3 { - return fmt.Errorf("Usage: %s IMAGE REPOSITORY [TAG]\n", job.Name) - } - var tag string - if len(job.Args) == 3 { - tag = job.Args[2] - } - return s.Set(job.Args[1], tag, job.Args[0], job.GetenvBool("force")) -} diff --git a/graph/tags.go b/graph/tags.go index 6346ea8b5..444e74f72 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -224,7 +224,7 @@ func (store *TagStore) Delete(repoName, ref string) (bool, error) { return deleted, store.save() } -func (store *TagStore) Set(repoName, tag, imageName string, force bool) error { +func (store *TagStore) Tag(repoName, tag, imageName string, force bool) error { return store.SetLoad(repoName, tag, imageName, force, nil) } diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go index be5624245..4a4ddbe4b 100644 --- a/graph/tags_unit_test.go +++ b/graph/tags_unit_test.go @@ -72,7 +72,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore { if err := graph.Register(img, officialArchive); err != nil { t.Fatal(err) } - if err := store.Set(testOfficialImageName, "", testOfficialImageID, false); err != nil { + if err := store.Tag(testOfficialImageName, "", testOfficialImageID, false); err != nil { t.Fatal(err) } privateArchive, err := fakeTar() @@ -83,7 +83,7 @@ func mkTestTagStore(root string, t *testing.T) *TagStore { if err := graph.Register(img, privateArchive); err != nil { t.Fatal(err) } - if err := store.Set(testPrivateImageName, "", testPrivateImageID, false); err != nil { + if err := store.Tag(testPrivateImageName, "", testPrivateImageID, false); err != nil { t.Fatal(err) } if err := store.SetDigest(testPrivateImageName, testPrivateImageDigest, testPrivateImageID); err != nil { diff --git a/integration/api_test.go b/integration/api_test.go index c527bcb92..5a8c7d459 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -761,7 +761,8 @@ func TestDeleteImages(t *testing.T) { initialImages := getImages(eng, t, true, "") - if err := eng.Job("tag", unitTestImageName, "test", "test").Run(); err != nil { + d := getDaemon(eng) + if err := d.Repositories().Tag("test", "test", unitTestImageName, true); err != nil { t.Fatal(err) } diff --git a/integration/server_test.go b/integration/server_test.go deleted file mode 100644 index 9745d9ce0..000000000 --- a/integration/server_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package docker - -import "testing" - -func TestCreateNumberHostname(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - config, _, _, err := parseRun([]string{"-h", "web.0", unitTestImageID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - createTestContainer(eng, config, t) -} - -func TestRunWithTooLowMemoryLimit(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - // Try to create a container with a memory limit of 1 byte less than the minimum allowed limit. - job := eng.Job("create") - job.Setenv("Image", unitTestImageID) - job.Setenv("Memory", "524287") - job.Setenv("CpuShares", "1000") - job.SetenvList("Cmd", []string{"/bin/cat"}) - if err := job.Run(); err == nil { - t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") - } -} - -func TestImagesFilter(t *testing.T) { - eng := NewTestEngine(t) - defer nuke(mkDaemonFromEngine(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 := getImages(eng, t, false, "utest*/*") - - if len(images[0].RepoTags) != 2 { - t.Fatal("incorrect number of matches returned") - } - - images = getImages(eng, t, false, "utest") - - if len(images[0].RepoTags) != 1 { - t.Fatal("incorrect number of matches returned") - } - - images = getImages(eng, t, false, "utest*") - - if len(images[0].RepoTags) != 1 { - t.Fatal("incorrect number of matches returned") - } - - images = getImages(eng, t, false, "*5000*/*") - - if len(images[0].RepoTags) != 1 { - t.Fatal("incorrect number of matches returned") - } -} From 448a1a7139cf89a0f4f985ec027a93799d0befb7 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Sat, 18 Apr 2015 17:55:40 -0700 Subject: [PATCH 509/999] Enhanced port integration-cli tests THe port tests in integration-cli tests just for the port-mapping as seen by Docker daemon. But it doesn't perform a more indepth testing by checking for the exposed port on the host. This change helps to fill that gap. Signed-off-by: Madhu Venugopal --- integration-cli/docker_cli_port_test.go | 83 +++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/integration-cli/docker_cli_port_test.go b/integration-cli/docker_cli_port_test.go index 91c1ee300..8fe6c2dc6 100644 --- a/integration-cli/docker_cli_port_test.go +++ b/integration-cli/docker_cli_port_test.go @@ -1,6 +1,7 @@ package main import ( + "net" "os/exec" "sort" "strings" @@ -145,3 +146,85 @@ func assertPortList(t *testing.T, out string, expected []string) bool { return true } + +func TestPortHostBinding(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "9876:80", "busybox", + "nc", "-l", "-p", "80") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + firstID := strings.TrimSpace(out) + + runCmd = exec.Command(dockerBinary, "port", firstID, "80") + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + + if !assertPortList(t, out, []string{"0.0.0.0:9876"}) { + t.Error("Port list is not correct") + } + + runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", + "nc", "localhost", "9876") + if out, _, err = runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + runCmd = exec.Command(dockerBinary, "rm", "-f", firstID) + if out, _, err = runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", + "nc", "localhost", "9876") + if out, _, err = runCommandWithOutput(runCmd); err == nil { + t.Error("Port is still bound after the Container is removed") + } + logDone("port - test host binding done") +} + +func TestPortExposeHostBinding(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "-d", "-P", "--expose", "80", "busybox", + "nc", "-l", "-p", "80") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + firstID := strings.TrimSpace(out) + + runCmd = exec.Command(dockerBinary, "port", firstID, "80") + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + t.Fatal(out, err) + } + + _, exposedPort, err := net.SplitHostPort(out) + + if err != nil { + t.Fatal(out, err) + } + + runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", + "nc", "localhost", strings.TrimSpace(exposedPort)) + if out, _, err = runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + runCmd = exec.Command(dockerBinary, "rm", "-f", firstID) + if out, _, err = runCommandWithOutput(runCmd); err != nil { + t.Fatal(out, err) + } + + runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", + "nc", "localhost", strings.TrimSpace(exposedPort)) + if out, _, err = runCommandWithOutput(runCmd); err == nil { + t.Error("Port is still bound after the Container is removed") + } + logDone("port - test port expose done") +} From 8655214b3dc8abb4edbca3db3e04557e09a1149b Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 19 Apr 2015 15:23:48 +0200 Subject: [PATCH 510/999] Refactor else branches Signed-off-by: Antonio Murdaca --- registry/session.go | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/registry/session.go b/registry/session.go index c62745b5b..e9d6a33df 100644 --- a/registry/session.go +++ b/registry/session.go @@ -222,10 +222,10 @@ func (r *Session) GetRemoteTags(registries []string, repository string, token [] logrus.Debugf("Got status code %d from %s", res.StatusCode, endpoint) defer res.Body.Close() - if res.StatusCode != 200 && res.StatusCode != 404 { - continue - } else if res.StatusCode == 404 { + if res.StatusCode == 404 { return nil, fmt.Errorf("Repository not found") + } else if res.StatusCode != 200 { + continue } result := make(map[string]string) @@ -524,21 +524,19 @@ func (r *Session) PushImageJSONIndex(remote string, imgList []*ImgData, validate } return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res) } - if res.Header.Get("X-Docker-Token") != "" { - tokens = res.Header["X-Docker-Token"] - logrus.Debugf("Auth token: %v", tokens) - } else { + if res.Header.Get("X-Docker-Token") == "" { return nil, fmt.Errorf("Index response didn't contain an access token") } + tokens = res.Header["X-Docker-Token"] + logrus.Debugf("Auth token: %v", tokens) - if res.Header.Get("X-Docker-Endpoints") != "" { - endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.VersionString(1)) - if err != nil { - return nil, err - } - } else { + if res.Header.Get("X-Docker-Endpoints") == "" { return nil, fmt.Errorf("Index response didn't contain any endpoints") } + endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.VersionString(1)) + if err != nil { + return nil, err + } } if validate { if res.StatusCode != 204 { From 5f2b051ec5a2f639857a1628f3c994fbfd0b3da0 Mon Sep 17 00:00:00 2001 From: Rick Wieman Date: Sun, 19 Apr 2015 23:36:58 +0200 Subject: [PATCH 511/999] Removes redundant else in registry/session.go Fixes #12523 Signed-off-by: Rick Wieman --- registry/session.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/registry/session.go b/registry/session.go index e9d6a33df..940e407e9 100644 --- a/registry/session.go +++ b/registry/session.go @@ -224,7 +224,8 @@ func (r *Session) GetRemoteTags(registries []string, repository string, token [] if res.StatusCode == 404 { return nil, fmt.Errorf("Repository not found") - } else if res.StatusCode != 200 { + } + if res.StatusCode != 200 { continue } From edf541c22b5253a980ee061b35110d0da8fdb905 Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Mon, 20 Apr 2015 01:08:01 -0700 Subject: [PATCH 512/999] gofmt whole directory Signed-off-by: Ankush Agarwal --- api/client/stats.go | 2 +- api/client/utils.go | 2 +- daemon/networkdriver/portmapper/proxy.go | 2 +- daemon/stats_collector.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/api/client/stats.go b/api/client/stats.go index 317a4fd84..b2dd36d68 100644 --- a/api/client/stats.go +++ b/api/client/stats.go @@ -145,7 +145,7 @@ func (cli *DockerCli) CmdStats(args ...string) error { if len(errs) > 0 { return fmt.Errorf("%s", strings.Join(errs, ", ")) } - for _ = range time.Tick(500 * time.Millisecond) { + for range time.Tick(500 * time.Millisecond) { printHeader() toRemove := []int{} for i, s := range cStats { diff --git a/api/client/utils.go b/api/client/utils.go index cf11fefe5..3d766a153 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -299,7 +299,7 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { sigchan := make(chan os.Signal, 1) gosignal.Notify(sigchan, signal.SIGWINCH) go func() { - for _ = range sigchan { + for range sigchan { cli.resizeTty(id, isExec) } }() diff --git a/daemon/networkdriver/portmapper/proxy.go b/daemon/networkdriver/portmapper/proxy.go index 5d0aa0be0..80b0027c7 100644 --- a/daemon/networkdriver/portmapper/proxy.go +++ b/daemon/networkdriver/portmapper/proxy.go @@ -84,7 +84,7 @@ func handleStopSignals(p proxy.Proxy) { s := make(chan os.Signal, 10) signal.Notify(s, os.Interrupt, syscall.SIGTERM, syscall.SIGSTOP) - for _ = range s { + for range s { p.Close() os.Exit(0) diff --git a/daemon/stats_collector.go b/daemon/stats_collector.go index 926dd256e..5677a8634 100644 --- a/daemon/stats_collector.go +++ b/daemon/stats_collector.go @@ -76,7 +76,7 @@ func (s *statsCollector) unsubscribe(c *Container, ch chan interface{}) { } func (s *statsCollector) run() { - for _ = range time.Tick(s.interval) { + for range time.Tick(s.interval) { for container, publisher := range s.publishers { systemUsage, err := s.getSystemCpuUsage() if err != nil { From 641a7ec9ad61a42b255d203b7791198431761104 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Mon, 20 Apr 2015 16:27:47 +0800 Subject: [PATCH 513/999] remove unused function in server_unit_test.go After engine refactor, some functions are no longer used. Signed-off-by: Qiang Huang --- api/server/server_unit_test.go | 47 ---------------------------------- 1 file changed, 47 deletions(-) diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index aca3af9ff..7ac0c2268 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -1,7 +1,6 @@ package server import ( - "bytes" "encoding/json" "fmt" "io" @@ -10,7 +9,6 @@ import ( "testing" "github.com/docker/docker/api" - "github.com/docker/docker/api/types" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/version" ) @@ -160,53 +158,8 @@ func readEnv(src io.Reader, t *testing.T) *engine.Env { return v } -func toJson(data interface{}, t *testing.T) io.Reader { - var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(data); err != nil { - t.Fatal(err) - } - return &buf -} - func assertContentType(recorder *httptest.ResponseRecorder, contentType string, t *testing.T) { if recorder.HeaderMap.Get("Content-Type") != contentType { t.Fatalf("%#v\n", recorder) } } - -// XXX: Duplicated from integration/utils_test.go, but maybe that's OK as that -// should die as soon as we converted all integration tests? -// assertHttpNotError expect the given response to not have an error. -// Otherwise the it causes the test to fail. -func assertHttpNotError(r *httptest.ResponseRecorder, t *testing.T) { - // Non-error http status are [200, 400) - if r.Code < http.StatusOK || r.Code >= http.StatusBadRequest { - t.Fatal(fmt.Errorf("Unexpected http error: %v", r.Code)) - } -} - -func createEnvFromGetImagesJSONStruct(data getImagesJSONStruct) types.Image { - return types.Image{ - RepoTags: data.RepoTags, - ID: data.Id, - Created: int(data.Created), - Size: int(data.Size), - VirtualSize: int(data.VirtualSize), - } -} - -type getImagesJSONStruct struct { - RepoTags []string - Id string - Created int64 - Size int64 - VirtualSize int64 -} - -var sampleImage getImagesJSONStruct = getImagesJSONStruct{ - RepoTags: []string{"test-name:test-tag"}, - Id: "ID", - Created: 999, - Size: 777, - VirtualSize: 666, -} From e607bb49c48e0478b07fceb640d3e765151050e4 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Mon, 20 Apr 2015 16:08:12 +0800 Subject: [PATCH 514/999] clenaup: delete unused function getEnv Signed-off-by: Ma Shimiao --- daemon/execdriver/lxc/init.go | 10 ---------- daemon/execdriver/native/driver.go | 11 ----------- 2 files changed, 21 deletions(-) diff --git a/daemon/execdriver/lxc/init.go b/daemon/execdriver/lxc/init.go index e99502667..6cdbf775e 100644 --- a/daemon/execdriver/lxc/init.go +++ b/daemon/execdriver/lxc/init.go @@ -141,13 +141,3 @@ func setupWorkingDirectory(args *InitArgs) error { } return nil } - -func getEnv(args *InitArgs, key string) string { - for _, kv := range args.Env { - parts := strings.SplitN(kv, "=", 2) - if parts[0] == key && len(parts) == 2 { - return parts[1] - } - } - return "" -} diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index fba22c1c2..7bc28f10f 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -8,7 +8,6 @@ import ( "os" "os/exec" "path/filepath" - "strings" "sync" "syscall" "time" @@ -349,16 +348,6 @@ func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) { }, nil } -func getEnv(key string, env []string) string { - for _, pair := range env { - parts := strings.Split(pair, "=") - if parts[0] == key { - return parts[1] - } - } - return "" -} - type TtyConsole struct { console libcontainer.Console } From 49be84842e41a612601ff9d0563ec30f138e95bc Mon Sep 17 00:00:00 2001 From: Shijiang Wei Date: Mon, 20 Apr 2015 16:48:17 +0800 Subject: [PATCH 515/999] Remove some unsupported instructions in the docs. Signed-off-by: Shijiang Wei --- docs/man/docker-commit.1.md | 2 +- docs/man/docker-import.1.md | 2 +- docs/sources/reference/commandline/cli.md | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/man/docker-commit.1.md b/docs/man/docker-commit.1.md index 003cb6f69..5a290682d 100644 --- a/docs/man/docker-commit.1.md +++ b/docs/man/docker-commit.1.md @@ -22,7 +22,7 @@ Using an existing container's name or ID you can create a new image. **-c** , **--change**=[] Apply specified Dockerfile instructions while committing the image - Supported Dockerfile instructions: `ADD`|`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`FROM`|`MAINTAINER`|`RUN`|`USER`|`LABEL`|`VOLUME`|`WORKDIR`|`COPY` + Supported Dockerfile instructions: `CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`ONBUILD`|`USER`|`VOLUME`|`WORKDIR` **--help** Print usage statement diff --git a/docs/man/docker-import.1.md b/docs/man/docker-import.1.md index 6b3899b6a..b45bf5d4c 100644 --- a/docs/man/docker-import.1.md +++ b/docs/man/docker-import.1.md @@ -13,7 +13,7 @@ URL|- [REPOSITORY[:TAG]] # OPTIONS **-c**, **--change**=[] Apply specified Dockerfile instructions while importing the image - Supported Dockerfile instructions: `ADD`|`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`FROM`|`MAINTAINER`|`RUN`|`USER`|`LABEL`|`VOLUME`|`WORKDIR`|`COPY` + Supported Dockerfile instructions: `CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`ONBUILD`|`USER`|`VOLUME`|`WORKDIR` # DESCRIPTION Create a new filesystem image from the contents of a tarball (`.tar`, diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 607f67076..4b6c1bb4f 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -839,7 +839,8 @@ If this behavior is undesired, set the 'p' option to false. The `--change` option will apply `Dockerfile` instructions to the image that is created. -Supported `Dockerfile` instructions: `ADD`|`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`FROM`|`MAINTAINER`|`RUN`|`USER`|`LABEL`|`VOLUME`|`WORKDIR`|`COPY` +Supported `Dockerfile` instructions: +`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`ONBUILD`|`USER`|`VOLUME`|`WORKDIR` #### Commit a container @@ -1347,8 +1348,8 @@ the `-` parameter to take the data from `STDIN`. The `--change` option will apply `Dockerfile` instructions to the image that is created. -Supported `Dockerfile` instructions: `CMD`, `ENTRYPOINT`, `ENV`, `EXPOSE`, -`ONBUILD`, `USER`, `VOLUME`, `WORKDIR` +Supported `Dockerfile` instructions: +`CMD`|`ENTRYPOINT`|`ENV`|`EXPOSE`|`ONBUILD`|`USER`|`VOLUME`|`WORKDIR` #### Examples From f4942ed864f00a31591ef0257a971ef41ddd4c70 Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Sat, 11 Apr 2015 01:26:30 +0800 Subject: [PATCH 516/999] Remove Job from Info API Two main things - Create a real struct Info for all of the data with the proper types - Add test for REST API get info Signed-off-by: Hu Keping --- api/client/info.go | 143 ++++++++---------------- api/server/server.go | 9 +- api/server/server_unit_test.go | 27 ----- api/types/types.go | 33 ++++++ daemon/daemon.go | 1 - daemon/info.go | 81 +++++++------- integration-cli/docker_api_info_test.go | 38 +++++++ 7 files changed, 163 insertions(+), 169 deletions(-) create mode 100644 integration-cli/docker_api_info_test.go diff --git a/api/client/info.go b/api/client/info.go index 0f509d83f..655788882 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -1,12 +1,11 @@ package client import ( + "encoding/json" "fmt" "os" - "time" - "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/units" ) @@ -19,127 +18,75 @@ func (cli *DockerCli) CmdInfo(args ...string) error { cmd.Require(flag.Exact, 0) cmd.ParseFlags(args, false) - body, _, err := readBody(cli.call("GET", "/info", nil, nil)) + rdr, _, err := cli.call("GET", "/info", nil, nil) if err != nil { return err } - out := engine.NewOutput() - remoteInfo, err := out.AddEnv() - if err != nil { - return err + info := &types.Info{} + if err := json.NewDecoder(rdr).Decode(info); err != nil { + return fmt.Errorf("Error reading remote info: %v", err) } - if _, err := out.Write(body); err != nil { - logrus.Errorf("Error reading remote info: %s", err) - return err - } - out.Close() - - if remoteInfo.Exists("Containers") { - fmt.Fprintf(cli.out, "Containers: %d\n", remoteInfo.GetInt("Containers")) - } - if remoteInfo.Exists("Images") { - fmt.Fprintf(cli.out, "Images: %d\n", remoteInfo.GetInt("Images")) - } - if remoteInfo.Exists("Driver") { - fmt.Fprintf(cli.out, "Storage Driver: %s\n", remoteInfo.Get("Driver")) - } - if remoteInfo.Exists("DriverStatus") { - var driverStatus [][2]string - if err := remoteInfo.GetJson("DriverStatus", &driverStatus); err != nil { - return err - } - for _, pair := range driverStatus { + fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers) + fmt.Fprintf(cli.out, "Images: %d\n", info.Images) + fmt.Fprintf(cli.out, "Storage Driver: %s\n", info.Driver) + if info.DriverStatus != nil { + for _, pair := range info.DriverStatus { fmt.Fprintf(cli.out, " %s: %s\n", pair[0], pair[1]) } } - if remoteInfo.Exists("ExecutionDriver") { - fmt.Fprintf(cli.out, "Execution Driver: %s\n", remoteInfo.Get("ExecutionDriver")) - } - if remoteInfo.Exists("LoggingDriver") { - fmt.Fprintf(cli.out, "Logging Driver: %s\n", remoteInfo.Get("LoggingDriver")) - } - if remoteInfo.Exists("KernelVersion") { - fmt.Fprintf(cli.out, "Kernel Version: %s\n", remoteInfo.Get("KernelVersion")) - } - if remoteInfo.Exists("OperatingSystem") { - fmt.Fprintf(cli.out, "Operating System: %s\n", remoteInfo.Get("OperatingSystem")) - } - if remoteInfo.Exists("NCPU") { - fmt.Fprintf(cli.out, "CPUs: %d\n", remoteInfo.GetInt("NCPU")) - } - if remoteInfo.Exists("MemTotal") { - fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(remoteInfo.GetInt64("MemTotal")))) - } - if remoteInfo.Exists("Name") { - fmt.Fprintf(cli.out, "Name: %s\n", remoteInfo.Get("Name")) - } - if remoteInfo.Exists("ID") { - fmt.Fprintf(cli.out, "ID: %s\n", remoteInfo.Get("ID")) + fmt.Fprintf(cli.out, "Execution Driver: %s\n", info.ExecutionDriver) + fmt.Fprintf(cli.out, "Logging Driver: %s\n", info.LoggingDriver) + fmt.Fprintf(cli.out, "Kernel Version: %s\n", info.KernelVersion) + fmt.Fprintf(cli.out, "Operating System: %s\n", info.OperatingSystem) + fmt.Fprintf(cli.out, "CPUs: %d\n", info.NCPU) + fmt.Fprintf(cli.out, "Total Memory: %s\n", units.BytesSize(float64(info.MemTotal))) + fmt.Fprintf(cli.out, "Name: %s\n", info.Name) + fmt.Fprintf(cli.out, "ID: %s\n", info.ID) + + if info.Debug || os.Getenv("DEBUG") != "" { + fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug) + fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") + fmt.Fprintf(cli.out, "File Descriptors: %d\n", info.NFd) + fmt.Fprintf(cli.out, "Goroutines: %d\n", info.NGoroutines) + fmt.Fprintf(cli.out, "System Time: %s\n", info.SystemTime) + fmt.Fprintf(cli.out, "EventsListeners: %d\n", info.NEventsListener) + fmt.Fprintf(cli.out, "Init SHA1: %s\n", info.InitSha1) + fmt.Fprintf(cli.out, "Init Path: %s\n", info.InitPath) + fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", info.DockerRootDir) } - if remoteInfo.GetBool("Debug") || os.Getenv("DEBUG") != "" { - if remoteInfo.Exists("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") != "") - if remoteInfo.Exists("NFd") { - fmt.Fprintf(cli.out, "File Descriptors: %d\n", remoteInfo.GetInt("NFd")) - } - if remoteInfo.Exists("NGoroutines") { - fmt.Fprintf(cli.out, "Goroutines: %d\n", remoteInfo.GetInt("NGoroutines")) - } - if remoteInfo.Exists("SystemTime") { - t, err := remoteInfo.GetTime("SystemTime") - if err != nil { - logrus.Errorf("Error reading system time: %v", err) - } else { - fmt.Fprintf(cli.out, "System Time: %s\n", t.Format(time.UnixDate)) - } - } - if remoteInfo.Exists("NEventsListener") { - fmt.Fprintf(cli.out, "EventsListeners: %d\n", remoteInfo.GetInt("NEventsListener")) - } - if initSha1 := remoteInfo.Get("InitSha1"); initSha1 != "" { - fmt.Fprintf(cli.out, "Init SHA1: %s\n", initSha1) - } - if initPath := remoteInfo.Get("InitPath"); initPath != "" { - fmt.Fprintf(cli.out, "Init Path: %s\n", initPath) - } - if root := remoteInfo.Get("DockerRootDir"); root != "" { - fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", root) - } + if info.HttpProxy != "" { + fmt.Fprintf(cli.out, "Http Proxy: %s\n", info.HttpProxy) } - if remoteInfo.Exists("HttpProxy") { - fmt.Fprintf(cli.out, "Http Proxy: %s\n", remoteInfo.Get("HttpProxy")) + if info.HttpsProxy != "" { + fmt.Fprintf(cli.out, "Https Proxy: %s\n", info.HttpsProxy) } - if remoteInfo.Exists("HttpsProxy") { - fmt.Fprintf(cli.out, "Https Proxy: %s\n", remoteInfo.Get("HttpsProxy")) + if info.NoProxy != "" { + fmt.Fprintf(cli.out, "No Proxy: %s\n", info.NoProxy) } - if remoteInfo.Exists("NoProxy") { - fmt.Fprintf(cli.out, "No Proxy: %s\n", remoteInfo.Get("NoProxy")) - } - if len(remoteInfo.GetList("IndexServerAddress")) != 0 { + + if info.IndexServerAddress != "" { cli.LoadConfigFile() - u := cli.configFile.Configs[remoteInfo.Get("IndexServerAddress")].Username + u := cli.configFile.Configs[info.IndexServerAddress].Username if len(u) > 0 { fmt.Fprintf(cli.out, "Username: %v\n", u) - fmt.Fprintf(cli.out, "Registry: %v\n", remoteInfo.GetList("IndexServerAddress")) + fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress) } } - if remoteInfo.Exists("MemoryLimit") && !remoteInfo.GetBool("MemoryLimit") { + if !info.MemoryLimit { fmt.Fprintf(cli.err, "WARNING: No memory limit support\n") } - if remoteInfo.Exists("SwapLimit") && !remoteInfo.GetBool("SwapLimit") { + if !info.SwapLimit { fmt.Fprintf(cli.err, "WARNING: No swap limit support\n") } - if remoteInfo.Exists("IPv4Forwarding") && !remoteInfo.GetBool("IPv4Forwarding") { + if !info.IPv4Forwarding { fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n") } - if remoteInfo.Exists("Labels") { + if info.Labels != nil { fmt.Fprintln(cli.out, "Labels:") - for _, attribute := range remoteInfo.GetList("Labels") { + for _, attribute := range info.Labels { fmt.Fprintf(cli.out, " %s\n", attribute) } } diff --git a/api/server/server.go b/api/server/server.go index 847ec6c21..d12bec957 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -367,8 +367,13 @@ func getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWr func getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") - eng.ServeHTTP(w, r) - return nil + + info, err := getDaemon(eng).SystemInfo() + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, info) } func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index aca3af9ff..a2abee188 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -34,33 +34,6 @@ func TesthttpError(t *testing.T) { } } -func TestGetInfo(t *testing.T) { - eng := engine.New() - var called bool - eng.Register("info", func(job *engine.Job) error { - called = true - v := &engine.Env{} - v.SetInt("Containers", 1) - v.SetInt("Images", 42000) - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil - }) - r := serveRequest("GET", "/info", nil, eng, t) - if !called { - t.Fatalf("handler was not called") - } - v := readEnv(r.Body, t) - if v.GetInt("Images") != 42000 { - t.Fatalf("%#v\n", v) - } - if v.GetInt("Containers") != 1 { - t.Fatalf("%#v\n", v) - } - assertContentType(r, "application/json", t) -} - func TestGetContainersByName(t *testing.T) { eng := engine.New() name := "container_name" diff --git a/api/types/types.go b/api/types/types.go index cdafe7e19..2a641fbaa 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -122,3 +122,36 @@ type Version struct { Arch string KernelVersion string `json:",omitempty"` } + +// GET "/info" +type Info struct { + ID string + Containers int + Images int + Driver string + DriverStatus [][2]string + MemoryLimit bool + SwapLimit bool + IPv4Forwarding bool + Debug bool + NFd int + NGoroutines int + SystemTime string + ExecutionDriver string + LoggingDriver string + NEventsListener int + KernelVersion string + OperatingSystem string + IndexServerAddress string + RegistryConfig interface{} + InitSha1 string + InitPath string + NCPU int + MemTotal int64 + DockerRootDir string + HttpProxy string + HttpsProxy string + NoProxy string + Name string + Labels []string +} diff --git a/daemon/daemon.go b/daemon/daemon.go index 7d8249b07..ccf254b2c 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -119,7 +119,6 @@ type Daemon struct { func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "container_inspect": daemon.ContainerInspect, - "info": daemon.CmdInfo, "execCreate": daemon.ContainerExecCreate, "execStart": daemon.ContainerExecStart, } { diff --git a/daemon/info.go b/daemon/info.go index a77e3efd1..fa019fe08 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -6,8 +6,8 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/parsers/kernel" "github.com/docker/docker/pkg/parsers/operatingsystem" @@ -16,7 +16,7 @@ import ( "github.com/docker/docker/utils" ) -func (daemon *Daemon) CmdInfo(job *engine.Job) error { +func (daemon *Daemon) SystemInfo() (*types.Info, error) { images, _ := daemon.Graph().Map() var imgcount int if images == nil { @@ -52,47 +52,46 @@ func (daemon *Daemon) CmdInfo(job *engine.Job) error { initPath = daemon.SystemInitPath() } - v := &engine.Env{} - v.SetJson("ID", daemon.ID) - v.SetInt("Containers", len(daemon.List())) - v.SetInt("Images", imgcount) - v.Set("Driver", daemon.GraphDriver().String()) - v.SetJson("DriverStatus", daemon.GraphDriver().Status()) - v.SetBool("MemoryLimit", daemon.SystemConfig().MemoryLimit) - v.SetBool("SwapLimit", daemon.SystemConfig().SwapLimit) - v.SetBool("IPv4Forwarding", !daemon.SystemConfig().IPv4ForwardingDisabled) - v.SetBool("Debug", os.Getenv("DEBUG") != "") - v.SetInt("NFd", fileutils.GetTotalUsedFds()) - v.SetInt("NGoroutines", runtime.NumGoroutine()) - v.Set("SystemTime", time.Now().Format(time.RFC3339Nano)) - v.Set("ExecutionDriver", daemon.ExecutionDriver().Name()) - v.Set("LoggingDriver", daemon.defaultLogConfig.Type) - v.SetInt("NEventsListener", daemon.EventsService.SubscribersCount()) - v.Set("KernelVersion", kernelVersion) - v.Set("OperatingSystem", operatingSystem) - v.Set("IndexServerAddress", registry.IndexServerAddress()) - v.SetJson("RegistryConfig", daemon.RegistryService.Config) - v.Set("InitSha1", dockerversion.INITSHA1) - v.Set("InitPath", initPath) - v.SetInt("NCPU", runtime.NumCPU()) - v.SetInt64("MemTotal", meminfo.MemTotal) - v.Set("DockerRootDir", daemon.Config().Root) - if httpProxy := os.Getenv("http_proxy"); httpProxy != "" { - v.Set("HttpProxy", httpProxy) - } - if httpsProxy := os.Getenv("https_proxy"); httpsProxy != "" { - v.Set("HttpsProxy", httpsProxy) - } - if noProxy := os.Getenv("no_proxy"); noProxy != "" { - v.Set("NoProxy", noProxy) + v := &types.Info{ + ID: daemon.ID, + Containers: len(daemon.List()), + Images: imgcount, + Driver: daemon.GraphDriver().String(), + DriverStatus: daemon.GraphDriver().Status(), + MemoryLimit: daemon.SystemConfig().MemoryLimit, + SwapLimit: daemon.SystemConfig().SwapLimit, + IPv4Forwarding: !daemon.SystemConfig().IPv4ForwardingDisabled, + Debug: os.Getenv("DEBUG") != "", + NFd: fileutils.GetTotalUsedFds(), + NGoroutines: runtime.NumGoroutine(), + SystemTime: time.Now().Format(time.RFC3339Nano), + ExecutionDriver: daemon.ExecutionDriver().Name(), + LoggingDriver: daemon.defaultLogConfig.Type, + NEventsListener: daemon.EventsService.SubscribersCount(), + KernelVersion: kernelVersion, + OperatingSystem: operatingSystem, + IndexServerAddress: registry.IndexServerAddress(), + RegistryConfig: daemon.RegistryService.Config, + InitSha1: dockerversion.INITSHA1, + InitPath: initPath, + NCPU: runtime.NumCPU(), + MemTotal: meminfo.MemTotal, + DockerRootDir: daemon.Config().Root, + Labels: daemon.Config().Labels, } + if httpProxy := os.Getenv("http_proxy"); httpProxy != "" { + v.HttpProxy = httpProxy + } + if httpsProxy := os.Getenv("https_proxy"); httpsProxy != "" { + v.HttpsProxy = httpsProxy + } + if noProxy := os.Getenv("no_proxy"); noProxy != "" { + v.NoProxy = noProxy + } if hostname, err := os.Hostname(); err == nil { - v.SetJson("Name", hostname) + v.Name = hostname } - v.SetList("Labels", daemon.Config().Labels) - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil + + return v, nil } diff --git a/integration-cli/docker_api_info_test.go b/integration-cli/docker_api_info_test.go new file mode 100644 index 000000000..a934ed6cc --- /dev/null +++ b/integration-cli/docker_api_info_test.go @@ -0,0 +1,38 @@ +package main + +import ( + "net/http" + "strings" + "testing" +) + +func TestInfoApi(t *testing.T) { + endpoint := "/info" + + statusCode, body, err := sockRequest("GET", endpoint, nil) + if err != nil || statusCode != http.StatusOK { + t.Fatalf("Expected %d from info request, got %d", http.StatusOK, statusCode) + } + + // always shown fields + stringsToCheck := []string{ + "ID", + "Containers", + "Images", + "ExecutionDriver", + "LoggingDriver", + "OperatingSystem", + "NCPU", + "MemTotal", + "KernelVersion", + "Driver"} + + out := string(body) + for _, linePrefix := range stringsToCheck { + if !strings.Contains(out, linePrefix) { + t.Errorf("couldn't find string %v in output", linePrefix) + } + } + + logDone("container REST API - check GET /info") +} From 8301dcc6d702a97feeb968ee79ae381fd8a4997a Mon Sep 17 00:00:00 2001 From: Jiri Popelka Date: Wed, 26 Nov 2014 12:14:50 +0100 Subject: [PATCH 517/999] Support for Firewalld Firewalld [1] is a firewall managing daemon with D-Bus interface. What sort of problem are we trying to solve with this ? Firewalld internally also executes iptables/ip6tables to change firewall settings. It might happen on systems where both docker and firewalld are running concurrently, that both of them try to call iptables at the same time. The result is that the second one fails because the first one is holding a xtables lock. One workaround is to use --wait/-w option in both docker & firewalld when calling iptables. It's already been done in both upstreams: https://github.com/docker/docker/commit/b315c380f4acd65cc0428009702f99a266f96c59 https://github.com/t-woerner/firewalld/commit/b3b451d6f8946986b8f50c8bcddeef50ed7a5f8f But it'd still be better if docker used firewalld when it's running. Other problem the firewalld support would solve is that iptables/firewalld service's restart flushes all firewall rules previously added by docker. See next patch for possible solution. This patch utilizes firewalld's D-Bus interface. If firewalld is running, we call direct.passthrough() [2] method instead of executing iptables directly. direct.passthrough() takes the same arguments as iptables tool itself and passes them through to iptables tool. It might be better to use other methods, like direct.addChain and direct.addRule [3] so it'd be more intergrated with firewalld, but that'd make the patch much bigger. If firewalld is not running, everything works as before. [1] http://www.firewalld.org/ [2] https://jpopelka.fedorapeople.org/firewalld/doc/firewalld.dbus.html#FirewallD1.direct.Methods.passthrough [3] https://jpopelka.fedorapeople.org/firewalld/doc/firewalld.dbus.html#FirewallD1.direct.Methods.addChain https://jpopelka.fedorapeople.org/firewalld/doc/firewalld.dbus.html#FirewallD1.direct.Methods.addRule Signed-off-by: Jiri Popelka --- daemon/networkdriver/bridge/driver.go | 4 ++ pkg/iptables/firewalld.go | 94 +++++++++++++++++++++++++++ pkg/iptables/iptables.go | 7 ++ 3 files changed, 105 insertions(+) create mode 100644 pkg/iptables/firewalld.go diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index eda471387..bea621870 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -222,6 +222,10 @@ func InitDriver(config *Config) error { bridgeIPv6Addr = networkv6.IP } + if config.EnableIptables { + iptables.FirewalldInit() + } + // Configure iptables for link support if config.EnableIptables { if err := setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq); err != nil { diff --git a/pkg/iptables/firewalld.go b/pkg/iptables/firewalld.go new file mode 100644 index 000000000..cb7e0b4b7 --- /dev/null +++ b/pkg/iptables/firewalld.go @@ -0,0 +1,94 @@ +package iptables + +import ( + "github.com/Sirupsen/logrus" + "github.com/godbus/dbus" +) + +type IPV string + +const ( + Iptables IPV = "ipv4" + Ip6tables IPV = "ipv6" + Ebtables IPV = "eb" +) +const ( + dbusInterface = "org.fedoraproject.FirewallD1" + dbusPath = "/org/fedoraproject/FirewallD1" +) + +// Conn is a connection to firewalld dbus endpoint. +type Conn struct { + sysconn *dbus.Conn + sysobj *dbus.Object + signal chan *dbus.Signal +} + +var ( + connection *Conn + firewalldRunning bool // is Firewalld service running +) + +func FirewalldInit() { + var err error + + connection, err = newConnection() + + if err != nil { + logrus.Errorf("Failed to connect to D-Bus system bus: %s", err) + } + + firewalldRunning = checkRunning() +} + +// New() establishes a connection to the system bus. +func newConnection() (*Conn, error) { + c := new(Conn) + if err := c.initConnection(); err != nil { + return nil, err + } + + return c, nil +} + +// Innitialize D-Bus connection. +func (c *Conn) initConnection() error { + var err error + + c.sysconn, err = dbus.SystemBus() + if err != nil { + return err + } + + // This never fails, even if the service is not running atm. + c.sysobj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusPath)) + + return nil +} + +// Call some remote method to see whether the service is actually running. +func checkRunning() bool { + var zone string + var err error + + if connection != nil { + err = connection.sysobj.Call(dbusInterface+".getDefaultZone", 0).Store(&zone) + logrus.Infof("Firewalld running: %t", err == nil) + return err == nil + } + logrus.Info("Firewalld not running") + return false +} + +// Firewalld's passthrough method simply passes args through to iptables/ip6tables +func Passthrough(ipv IPV, args ...string) ([]byte, error) { + var output string + + logrus.Debugf("Firewalld passthrough: %s, %s", ipv, args) + err := connection.sysobj.Call(dbusInterface+".direct.passthrough", 0, ipv, args).Store(&output) + if output != "" { + logrus.Debugf("passthrough output: %s", output) + } + + return []byte(output), err +} diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index f8b3aa769..9019f3463 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -275,6 +275,13 @@ func Exists(table Table, chain string, rule ...string) bool { // Call 'iptables' system command, passing supplied arguments func Raw(args ...string) ([]byte, error) { + if firewalldRunning { + output, err := Passthrough(Iptables, args...) + if err == nil || !strings.Contains(err.Error(), "was not provided by any .service files") { + return output, err + } + + } if err := initCheck(); err != nil { return nil, err From b052827e025267336f0d426df44ec536745821f8 Mon Sep 17 00:00:00 2001 From: Jiri Popelka Date: Wed, 26 Nov 2014 19:10:35 +0100 Subject: [PATCH 518/999] React to firewalld's reload/restart When firewalld (or iptables service) restarts/reloads, all previously added docker firewall rules are flushed. With firewalld we can react to its Reloaded() [1] D-Bus signal and recreate the firewall rules. Also when firewalld gets restarted (stopped & started) we can catch the NameOwnerChanged signal [2]. To specify which signals we want to react to we use AddMatch [3]. Libvirt has been doing this for quite a long time now. Docker changes firewall rules on basically 3 places. 1) daemon/networkdriver/portmapper/mapper.go - port mappings Portmapper fortunatelly keeps list of mapped ports, so we can easily recreate firewall rules on firewalld restart/reload New ReMapAll() function does that 2) daemon/networkdriver/bridge/driver.go When setting a bridge, basic firewall rules are created. This is done at once during start, it's parametrized and nowhere tracked so how can one know what and how to set it again when there's been firewalld restart/reload ? The only solution that came to my mind is using of closures [4], i.e. I keep list of references to closures (anonymous functions together with a referencing environment) and when there's firewalld restart/reload I re-call them in the same order. 3) links/links.go - linking containers Link is added in Enable() and removed in Disable(). In Enable() we add a callback function, which creates the link, that's OK so far. It'd be ideal if we could remove the same function from the list in Disable(). Unfortunatelly that's not possible AFAICT, because we don't know the reference to that function at that moment, so we can only add a reference to function, which removes the link. That means that after creating and removing a link there are 2 functions in the list, one adding and one removing the link and after firewalld restart/reload both are called. It works, but it's far from ideal. [1] https://jpopelka.fedorapeople.org/firewalld/doc/firewalld.dbus.html#FirewallD1.Signals.Reloaded [2] http://dbus.freedesktop.org/doc/dbus-specification.html#bus-messages-name-owner-changed [3] http://dbus.freedesktop.org/doc/dbus-specification.html#message-bus-routing-match-rules [4] https://en.wikipedia.org/wiki/Closure_%28computer_programming%29 Signed-off-by: Jiri Popelka --- daemon/networkdriver/bridge/driver.go | 13 ++++- daemon/networkdriver/portmapper/mapper.go | 12 ++++ links/links.go | 6 +- pkg/iptables/firewalld.go | 71 ++++++++++++++++++++++- 4 files changed, 99 insertions(+), 3 deletions(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index bea621870..64772bfd7 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -232,7 +232,8 @@ func InitDriver(config *Config) error { logrus.Errorf("Error configuing iptables: %s", err) return err } - + // call this on Firewalld reload + iptables.OnReloaded(func() { setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq) }) } if config.EnableIpForward { @@ -262,10 +263,16 @@ func InitDriver(config *Config) error { if err != nil { return err } + // call this on Firewalld reload + iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Nat) }) + chain, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter) if err != nil { return err } + // call this on Firewalld reload + iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Filter) }) + portMapper.SetIptablesChain(chain) } @@ -298,6 +305,10 @@ func InitDriver(config *Config) error { // Block BridgeIP in IP allocator ipAllocator.RequestIP(bridgeIPv4Network, bridgeIPv4Network.IP) + if config.EnableIptables { + iptables.OnReloaded(portMapper.ReMapAll) // call this on Firewalld reload + } + return nil } diff --git a/daemon/networkdriver/portmapper/mapper.go b/daemon/networkdriver/portmapper/mapper.go index 8f79bae3f..09952ba35 100644 --- a/daemon/networkdriver/portmapper/mapper.go +++ b/daemon/networkdriver/portmapper/mapper.go @@ -132,6 +132,18 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host return m.host, nil } +// re-apply all port mappings +func (pm *PortMapper) ReMapAll() { + logrus.Debugln("Re-applying all port mappings.") + for _, data := range pm.currentMappings { + containerIP, containerPort := getIPAndPort(data.container) + hostIP, hostPort := getIPAndPort(data.host) + if err := pm.forward(iptables.Append, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { + logrus.Errorf("Error on iptables add: %s", err) + } + } +} + func (pm *PortMapper) Unmap(host net.Addr) error { pm.lock.Lock() defer pm.lock.Unlock() diff --git a/links/links.go b/links/links.go index 1ae8f23ae..8bbacdd3d 100644 --- a/links/links.go +++ b/links/links.go @@ -7,6 +7,7 @@ import ( "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/nat" + "github.com/docker/docker/pkg/iptables" ) type Link struct { @@ -143,6 +144,8 @@ func (l *Link) Enable() error { if err := l.toggle("-A", false); err != nil { return err } + // call this on Firewalld reload + iptables.OnReloaded(func() { l.toggle("-I", false) }) l.IsEnabled = true return nil } @@ -152,7 +155,8 @@ func (l *Link) Disable() { // exist in iptables // -D == iptables delete flag l.toggle("-D", true) - + // call this on Firewalld reload + iptables.OnReloaded(func() { l.toggle("-D", true) }) l.IsEnabled = false } diff --git a/pkg/iptables/firewalld.go b/pkg/iptables/firewalld.go index cb7e0b4b7..308779413 100644 --- a/pkg/iptables/firewalld.go +++ b/pkg/iptables/firewalld.go @@ -1,8 +1,10 @@ package iptables import ( + "fmt" "github.com/Sirupsen/logrus" "github.com/godbus/dbus" + "strings" ) type IPV string @@ -26,7 +28,8 @@ type Conn struct { var ( connection *Conn - firewalldRunning bool // is Firewalld service running + firewalldRunning bool // is Firewalld service running + onReloaded []*func() // callbacks when Firewalld has been reloaded ) func FirewalldInit() { @@ -63,9 +66,75 @@ func (c *Conn) initConnection() error { // This never fails, even if the service is not running atm. c.sysobj = c.sysconn.Object(dbusInterface, dbus.ObjectPath(dbusPath)) + rule := fmt.Sprintf("type='signal',path='%s',interface='%s',sender='%s',member='Reloaded'", + dbusPath, dbusInterface, dbusInterface) + c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) + + rule = fmt.Sprintf("type='signal',interface='org.freedesktop.DBus',member='NameOwnerChanged',path='/org/freedesktop/DBus',sender='org.freedesktop.DBus',arg0='%s'", + dbusInterface) + c.sysconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, rule) + + c.signal = make(chan *dbus.Signal, 10) + c.sysconn.Signal(c.signal) + go signalHandler() + return nil } +func signalHandler() { + if connection != nil { + for signal := range connection.signal { + if strings.Contains(signal.Name, "NameOwnerChanged") { + firewalldRunning = checkRunning() + dbusConnectionChanged(signal.Body) + } else if strings.Contains(signal.Name, "Reloaded") { + reloaded() + } + } + } +} + +func dbusConnectionChanged(args []interface{}) { + name := args[0].(string) + old_owner := args[1].(string) + new_owner := args[2].(string) + + if name != dbusInterface { + return + } + + if len(new_owner) > 0 { + connectionEstablished() + } else if len(old_owner) > 0 { + connectionLost() + } +} + +func connectionEstablished() { + reloaded() +} + +func connectionLost() { + // Doesn't do anything for now. Libvirt also doesn't react to this. +} + +// call all callbacks +func reloaded() { + for _, pf := range onReloaded { + (*pf)() + } +} + +// add callback +func OnReloaded(callback func()) { + for _, pf := range onReloaded { + if pf == &callback { + return + } + } + onReloaded = append(onReloaded, &callback) +} + // Call some remote method to see whether the service is actually running. func checkRunning() bool { var zone string From 379773905c7ff4db3c16e2235f831a9552b4e158 Mon Sep 17 00:00:00 2001 From: Jiri Popelka Date: Thu, 15 Jan 2015 18:23:20 +0100 Subject: [PATCH 519/999] Firewalld tests Signed-off-by: Jiri Popelka --- pkg/iptables/firewalld_test.go | 78 ++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 pkg/iptables/firewalld_test.go diff --git a/pkg/iptables/firewalld_test.go b/pkg/iptables/firewalld_test.go new file mode 100644 index 000000000..3896007d6 --- /dev/null +++ b/pkg/iptables/firewalld_test.go @@ -0,0 +1,78 @@ +package iptables + +import ( + "net" + "strconv" + "testing" +) + +func TestFirewalldInit(t *testing.T) { + FirewalldInit() +} + +func TestReloaded(t *testing.T) { + var err error + var fwdChain *Chain + + fwdChain, err = NewChain("FWD", "lo", Filter) + if err != nil { + t.Fatal(err) + } + defer fwdChain.Remove() + + // copy-pasted from iptables_test:TestLink + ip1 := net.ParseIP("192.168.1.1") + ip2 := net.ParseIP("192.168.1.2") + port := 1234 + proto := "tcp" + + err = fwdChain.Link(Append, ip1, ip2, port, proto) + if err != nil { + t.Fatal(err) + } else { + // to be re-called again later + OnReloaded(func() { fwdChain.Link(Append, ip1, ip2, port, proto) }) + } + + rule1 := []string{ + "-i", fwdChain.Bridge, + "-o", fwdChain.Bridge, + "-p", proto, + "-s", ip1.String(), + "-d", ip2.String(), + "--dport", strconv.Itoa(port), + "-j", "ACCEPT"} + + if !Exists(fwdChain.Table, fwdChain.Name, rule1...) { + t.Fatalf("rule1 does not exist") + } + + // flush all rules + fwdChain.Remove() + + reloaded() + + // make sure the rules have been recreated + if !Exists(fwdChain.Table, fwdChain.Name, rule1...) { + t.Fatalf("rule1 hasn't been recreated") + } +} + +func TestPassthrough(t *testing.T) { + rule1 := []string{ + "-i", "lo", + "-p", "udp", + "--dport", "123", + "-j", "ACCEPT"} + + if firewalldRunning { + _, err := Passthrough(Iptables, append([]string{"-A"}, rule1...)...) + if err != nil { + t.Fatal(err) + } + if !Exists(Filter, "INPUT", rule1...) { + t.Fatalf("rule1 does not exist") + } + } + +} From acb6127c1a3f7054c25d1468b67f2eb269f4ecbf Mon Sep 17 00:00:00 2001 From: Sylvain Baubeau Date: Thu, 18 Dec 2014 10:09:42 +0100 Subject: [PATCH 520/999] Allow specifying a default gateway for bridge networking Signed-off-by: Sylvain Baubeau --- daemon/config.go | 2 + daemon/networkdriver/bridge/driver.go | 60 ++++++++++++++++++++--- docs/man/docker.1.md | 6 +++ docs/sources/articles/networking.md | 23 ++++++--- docs/sources/reference/commandline/cli.md | 2 + 5 files changed, 80 insertions(+), 13 deletions(-) diff --git a/daemon/config.go b/daemon/config.go index 40019e503..952fb5f74 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -58,6 +58,8 @@ func (config *Config) InstallFlags() { flag.StringVar(&config.Bridge.Iface, []string{"b", "-bridge"}, "", "Attach containers to a network bridge") flag.StringVar(&config.Bridge.FixedCIDR, []string{"-fixed-cidr"}, "", "IPv4 subnet for fixed IPs") flag.StringVar(&config.Bridge.FixedCIDRv6, []string{"-fixed-cidr-v6"}, "", "IPv6 subnet for fixed IPs") + flag.StringVar(&config.Bridge.DefaultGatewayIPv4, []string{"-default-gateway"}, "", "Container default gateway IPv4 address") + flag.StringVar(&config.Bridge.DefaultGatewayIPv6, []string{"-default-gateway-v6"}, "", "Container default gateway IPv6 address") flag.BoolVar(&config.Bridge.InterContainerCommunication, []string{"#icc", "-icc"}, true, "Enable inter-container communication") flag.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", "Storage driver to use") flag.StringVar(&config.ExecDriver, []string{"e", "-exec-driver"}, "native", "Exec driver to use") diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index eda471387..14102632b 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -77,8 +77,10 @@ var ( bridgeIface string bridgeIPv4Network *net.IPNet + gatewayIPv4 net.IP bridgeIPv6Addr net.IP globalIPv6Network *net.IPNet + gatewayIPv6 net.IP portMapper *portmapper.PortMapper once sync.Once @@ -103,6 +105,8 @@ type Config struct { IP string FixedCIDR string FixedCIDRv6 string + DefaultGatewayIPv4 string + DefaultGatewayIPv6 string InterContainerCommunication bool } @@ -278,6 +282,12 @@ func InitDriver(config *Config) error { } } + if gateway, err := requestDefaultGateway(config.DefaultGatewayIPv4, bridgeIPv4Network); err != nil { + return err + } else { + gatewayIPv4 = gateway + } + if config.FixedCIDRv6 != "" { _, subnet, err := net.ParseCIDR(config.FixedCIDRv6) if err != nil { @@ -289,6 +299,12 @@ func InitDriver(config *Config) error { return err } globalIPv6Network = subnet + + if gateway, err := requestDefaultGateway(config.DefaultGatewayIPv6, globalIPv6Network); err != nil { + return err + } else { + gatewayIPv6 = gateway + } } // Block BridgeIP in IP allocator @@ -473,6 +489,24 @@ func setupIPv6Bridge(bridgeIPv6 string) error { return nil } +func requestDefaultGateway(requestedGateway string, network *net.IPNet) (gateway net.IP, err error) { + if requestedGateway != "" { + gateway = net.ParseIP(requestedGateway) + + if gateway == nil { + return nil, fmt.Errorf("Bad parameter: invalid gateway ip %s", requestedGateway) + } + + if !network.Contains(gateway) { + return nil, fmt.Errorf("Gateway ip %s must be part of the network %s", requestedGateway, network.String()) + } + + ipAllocator.RequestIP(network, gateway) + } + + return gateway, nil +} + func createBridgeIface(name string) error { kv, err := kernel.GetKernelVersion() // Only set the bridge's mac address if the kernel version is > 3.3 @@ -522,10 +556,12 @@ func linkLocalIPv6FromMac(mac string) (string, error) { // Allocate a network interface func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Settings, error) { var ( - ip net.IP - mac net.HardwareAddr - err error - globalIPv6 net.IP + ip net.IP + mac net.HardwareAddr + err error + globalIPv6 net.IP + defaultGWIPv4 net.IP + defaultGWIPv6 net.IP ) ip, err = ipAllocator.RequestIP(bridgeIPv4Network, net.ParseIP(requestedIP)) @@ -560,6 +596,18 @@ func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Set maskSize, _ := bridgeIPv4Network.Mask.Size() + if gatewayIPv4 != nil { + defaultGWIPv4 = gatewayIPv4 + } else { + defaultGWIPv4 = bridgeIPv4Network.IP + } + + if gatewayIPv6 != nil { + defaultGWIPv6 = gatewayIPv6 + } else { + defaultGWIPv6 = bridgeIPv6Addr + } + // If linklocal IPv6 localIPv6Net, err := linkLocalIPv6FromMac(mac.String()) if err != nil { @@ -569,7 +617,7 @@ func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Set networkSettings := &network.Settings{ IPAddress: ip.String(), - Gateway: bridgeIPv4Network.IP.String(), + Gateway: defaultGWIPv4.String(), MacAddress: mac.String(), Bridge: bridgeIface, IPPrefixLen: maskSize, @@ -580,7 +628,7 @@ func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Set networkSettings.GlobalIPv6Address = globalIPv6.String() maskV6Size, _ := globalIPv6Network.Mask.Size() networkSettings.GlobalIPv6PrefixLen = maskV6Size - networkSettings.IPv6Gateway = bridgeIPv6Addr.String() + networkSettings.IPv6Gateway = defaultGWIPv6.String() } currentInterfaces.Set(id, &networkInterface{ diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index afa14b661..53c54f903 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -41,6 +41,12 @@ To see the man page for a command run **man docker **. **-d**, **--daemon**=*true*|*false* Enable daemon mode. Default is false. +**--default-gateway**="" + IPv4 address of the container default gateway; this address must be part of the bridge subnet (which is defined by \-b or \--bip) + +**--default-gateway-v6**="" + IPv6 address of the container default gateway + **--dns**="" Force Docker to use specific DNS servers diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index 46a907f7e..2ce52ce0e 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -56,6 +56,12 @@ server when it starts up, and cannot be changed once it is running: * `--bip=CIDR` — see [Customizing docker0](#docker0) + * `--default-gateway=IP_ADDRESS` — see + [How Docker networks a container](#container-networking) + + * `--default-gateway-v6=IP_ADDRESS` — see + [IPv6](#ipv6) + * `--fixed-cidr` — see [Customizing docker0](#docker0) @@ -499,7 +505,9 @@ want to configure `eth0` via Router Advertisements you should set: ![](/article-img/ipv6_basic_host_config.svg) Every new container will get an IPv6 address from the defined subnet. Further -a default route will be added via the gateway `fe80::1` on `eth0`: +a default route will be added on `eth0` in the container via the address +specified by the daemon option `--default-gateway-v6` if present, otherwise +via `fe80::1`: docker run -it ubuntu bash -c "ip -6 addr show dev eth0; ip -6 route show" @@ -865,12 +873,13 @@ The steps with which Docker configures a container are: parameter or generate a random one. 5. Give the container's `eth0` a new IP address from within the - bridge's range of network addresses, and set its default route to - the IP address that the Docker host owns on the bridge. The MAC - address is generated from the IP address unless otherwise specified. - This prevents ARP cache invalidation problems, when a new container - comes up with an IP used in the past by another container with another - MAC. + bridge's range of network addresses. The default route is set to the + IP address passed to the Docker daemon using the `--default-gateway` + option if specified, otherwise to the IP address that the Docker host + owns on the bridge. The MAC address is generated from the IP address + unless otherwise specified. This prevents ARP cache invalidation + problems, when a new container comes up with an IP used in the past by + another container with another MAC. With these steps complete, the container now possesses an `eth0` (virtual) network card and will find itself able to communicate with diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 607f67076..60518c3a9 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -116,6 +116,8 @@ expect an integer, and they can only be specified once. --bip="" Specify network bridge IP -D, --debug=false Enable debug mode -d, --daemon=false Enable daemon mode + --default-gateway="" Container default gateway IPv4 address + --default-gateway-v6="" Container default gateway IPv6 address --dns=[] DNS server to use --dns-search=[] DNS search domains to use -e, --exec-driver="native" Exec driver to use From 2d5ede67c098dd11803d502865ab546388f2a553 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Mon, 20 Apr 2015 22:00:03 +0800 Subject: [PATCH 521/999] Remove redundant '\n' in daemon.go and correct the warning messages for memory swap Signed-off-by: Lei Jitang --- daemon/daemon.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index ccf254b2c..8428cc827 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1233,18 +1233,18 @@ func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]stri return warnings, fmt.Errorf("Minimum memory limit allowed is 4MB") } if hostConfig.Memory > 0 && !daemon.SystemConfig().MemoryLimit { - warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.\n") + warnings = append(warnings, "Your kernel does not support memory limit capabilities. Limitation discarded.") hostConfig.Memory = 0 } if hostConfig.Memory > 0 && hostConfig.MemorySwap != -1 && !daemon.SystemConfig().SwapLimit { - warnings = append(warnings, "Your kernel does not support swap limit capabilities. Limitation discarded.\n") + warnings = append(warnings, "Your kernel does not support swap limit capabilities, memory limited without swap.") hostConfig.MemorySwap = -1 } if hostConfig.Memory > 0 && hostConfig.MemorySwap > 0 && hostConfig.MemorySwap < hostConfig.Memory { - return warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.\n") + return warnings, fmt.Errorf("Minimum memoryswap limit should be larger than memory limit, see usage.") } if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { - return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") + return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.") } return warnings, nil From 181fea24aac7499a3d6dc0c8c9de67e6c0036140 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 17 Apr 2015 12:03:11 -0700 Subject: [PATCH 522/999] Make daemon initialization in main goroutine It is simplifies code and lead to next refactoring step, where daemon will be incorporated to some structure which represents API. Signed-off-by: Alexander Morozov --- docker/daemon.go | 78 ++++++++--------------- integration-cli/docker_cli_daemon_test.go | 4 +- 2 files changed, 27 insertions(+), 55 deletions(-) diff --git a/docker/daemon.go b/docker/daemon.go index 769b4f5bf..0fe10de65 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -7,7 +7,6 @@ import ( "io" "os" "path/filepath" - "strings" "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" @@ -93,40 +92,6 @@ func mainDaemon() { } daemonCfg.TrustKeyPath = *flTrustKey - registryService := registry.NewService(registryCfg) - // load the daemon in the background so we can immediately start - // the http api so that connections don't fail while the daemon - // is booting - daemonInitWait := make(chan error) - go func() { - d, err := daemon.NewDaemon(daemonCfg, eng, registryService) - if err != nil { - daemonInitWait <- err - return - } - - logrus.WithFields(logrus.Fields{ - "version": dockerversion.VERSION, - "commit": dockerversion.GITCOMMIT, - "execdriver": d.ExecutionDriver().Name(), - "graphdriver": d.GraphDriver().String(), - }).Info("Docker daemon") - - if err := d.Install(eng); err != nil { - daemonInitWait <- err - return - } - - b := &builder.BuilderJob{eng, d} - b.Install() - - // after the daemon is done setting up we can tell the api to start - // accepting connections - apiserver.AcceptConnections() - - daemonInitWait <- nil - }() - serverConfig := &apiserver.ServerConfig{ Logging: true, EnableCors: daemonCfg.EnableCors, @@ -153,30 +118,37 @@ func mainDaemon() { serveAPIWait <- nil }() - // Wait for the daemon startup goroutine to finish - // This makes sure we can actually cleanly shutdown the daemon - logrus.Debug("waiting for daemon to initialize") - errDaemon := <-daemonInitWait - if errDaemon != nil { + registryService := registry.NewService(registryCfg) + d, err := daemon.NewDaemon(daemonCfg, eng, registryService) + if err != nil { eng.Shutdown() - outStr := fmt.Sprintf("Shutting down daemon due to errors: %v", errDaemon) - if strings.Contains(errDaemon.Error(), "engine is shutdown") { - // if the error is "engine is shutdown", we've already reported (or - // will report below in API server errors) the error - outStr = "Shutting down daemon due to reported errors" - } - // we must "fatal" exit here as the API server may be happy to - // continue listening forever if the error had no impact to API - logrus.Fatal(outStr) - } else { - logrus.Info("Daemon has completed initialization") + logrus.Fatalf("Error starting daemon: %v", err) } + if err := d.Install(eng); err != nil { + eng.Shutdown() + logrus.Fatalf("Error starting daemon: %v", err) + } + + logrus.Info("Daemon has completed initialization") + + logrus.WithFields(logrus.Fields{ + "version": dockerversion.VERSION, + "commit": dockerversion.GITCOMMIT, + "execdriver": d.ExecutionDriver().Name(), + "graphdriver": d.GraphDriver().String(), + }).Info("Docker daemon") + + b := &builder.BuilderJob{eng, d} + b.Install() + + // after the daemon is done setting up we can tell the api to start + // accepting connections + apiserver.AcceptConnections() + // Daemon is fully initialized and handling API traffic // Wait for serve API job to complete errAPI := <-serveAPIWait - // If we have an error here it is unique to API (as daemonErr would have - // exited the daemon process above) eng.Shutdown() if errAPI != nil { logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 384c96803..d81ad3c80 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -498,9 +498,9 @@ func TestDaemonExitOnFailure(t *testing.T) { t.Fatalf("Expected daemon not to start, got %v", err) } // look in the log and make sure we got the message that daemon is shutting down - runCmd := exec.Command("grep", "Shutting down daemon due to", d.LogfileName()) + runCmd := exec.Command("grep", "Error starting daemon", d.LogfileName()) if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatalf("Expected 'shutting down daemon due to error' message; but doesn't exist in log: %q, err: %v", out, err) + t.Fatalf("Expected 'Error starting daemon' message; but doesn't exist in log: %q, err: %v", out, err) } } else { //if we didn't get an error and the daemon is running, this is a failure From d9ed3165228b60cb89c31d0d66b99e01ab83eb3e Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 17 Apr 2015 14:32:18 -0700 Subject: [PATCH 523/999] Make API server datastructure Added daemon field to it, will use it later for acces to daemon from handlers Signed-off-by: Alexander Morozov --- api/server/server.go | 122 +++++++++++++++++++++-------------- api/server/server_linux.go | 32 ++++----- api/server/server_windows.go | 30 +++++---- api/server/tcp_socket.go | 4 +- api/server/unix_socket.go | 4 +- docker/daemon.go | 8 ++- integration/runtime_test.go | 28 ++++---- pkg/listenbuffer/buffer.go | 8 +-- 8 files changed, 131 insertions(+), 105 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index d12bec957..3146e09ed 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -40,10 +40,6 @@ import ( "github.com/docker/docker/utils" ) -var ( - activationLock = make(chan struct{}) -) - type ServerConfig struct { Logging bool EnableCors bool @@ -57,6 +53,80 @@ type ServerConfig struct { TlsKey string } +type Server struct { + daemon *daemon.Daemon + cfg *ServerConfig + router *mux.Router + start chan struct{} + + // TODO: delete engine + eng *engine.Engine +} + +func New(cfg *ServerConfig, eng *engine.Engine) *Server { + r := createRouter( + eng, + cfg.Logging, + cfg.EnableCors, + cfg.CorsHeaders, + cfg.Version, + ) + return &Server{ + cfg: cfg, + router: r, + start: make(chan struct{}), + eng: eng, + } +} + +func (s *Server) SetDaemon(d *daemon.Daemon) { + s.daemon = d +} + +type serverCloser interface { + Serve() error + Close() error +} + +// ServeApi loops through all of the protocols sent in to docker and spawns +// off a go routine to setup a serving http.Server for each. +func (s *Server) ServeApi(protoAddrs []string) error { + var chErrors = make(chan error, len(protoAddrs)) + + for _, protoAddr := range protoAddrs { + protoAddrParts := strings.SplitN(protoAddr, "://", 2) + if len(protoAddrParts) != 2 { + return fmt.Errorf("bad format, expected PROTO://ADDR") + } + go func(proto, addr string) { + logrus.Infof("Listening for HTTP on %s (%s)", proto, addr) + srv, err := s.newServer(proto, addr) + if err != nil { + chErrors <- err + return + } + s.eng.OnShutdown(func() { + if err := srv.Close(); err != nil { + logrus.Error(err) + } + }) + if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { + err = nil + } + chErrors <- err + }(protoAddrParts[0], protoAddrParts[1]) + } + + for i := 0; i < len(protoAddrs); i++ { + err := <-chErrors + if err != nil { + return err + } + } + + return nil +} + type HttpServer struct { srv *http.Server l net.Listener @@ -1632,50 +1702,6 @@ func allocateDaemonPort(addr string) error { return nil } -type Server interface { - Serve() error - Close() error -} - -// ServeApi loops through all of the protocols sent in to docker and spawns -// off a go routine to setup a serving http.Server for each. -func ServeApi(protoAddrs []string, conf *ServerConfig, eng *engine.Engine) error { - var chErrors = make(chan error, len(protoAddrs)) - - for _, protoAddr := range protoAddrs { - protoAddrParts := strings.SplitN(protoAddr, "://", 2) - if len(protoAddrParts) != 2 { - return fmt.Errorf("bad format, expected PROTO://ADDR") - } - go func() { - logrus.Infof("Listening for HTTP on %s (%s)", protoAddrParts[0], protoAddrParts[1]) - srv, err := NewServer(protoAddrParts[0], protoAddrParts[1], conf, eng) - if err != nil { - chErrors <- err - return - } - eng.OnShutdown(func() { - if err := srv.Close(); err != nil { - logrus.Error(err) - } - }) - if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { - err = nil - } - chErrors <- err - }() - } - - for i := 0; i < len(protoAddrs); i++ { - err := <-chErrors - if err != nil { - return err - } - } - - return nil -} - func toBool(s string) bool { s = strings.ToLower(strings.TrimSpace(s)) return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") diff --git a/api/server/server_linux.go b/api/server/server_linux.go index 4d53a888e..43f0eefe0 100644 --- a/api/server/server_linux.go +++ b/api/server/server_linux.go @@ -8,22 +8,15 @@ import ( "net/http" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" + "github.com/docker/docker/daemon" "github.com/docker/docker/pkg/systemd" ) -// NewServer sets up the required Server and does protocol specific checking. -func NewServer(proto, addr string, conf *ServerConfig, eng *engine.Engine) (Server, error) { +// newServer sets up the required serverCloser and does protocol specific checking. +func (s *Server) newServer(proto, addr string) (serverCloser, error) { var ( err error l net.Listener - r = createRouter( - eng, - conf.Logging, - conf.EnableCors, - conf.CorsHeaders, - conf.Version, - ) ) switch proto { case "fd": @@ -35,13 +28,13 @@ func NewServer(proto, addr string, conf *ServerConfig, eng *engine.Engine) (Serv // We don't want to start serving on these sockets until the // daemon is initialized and installed. Otherwise required handlers // won't be ready. - <-activationLock + <-s.start // Since ListenFD will return one or more sockets we have // to create a go func to spawn off multiple serves for i := range ls { listener := ls[i] go func() { - httpSrv := http.Server{Handler: r} + httpSrv := http.Server{Handler: s.router} chErrors <- httpSrv.Serve(listener) }() } @@ -52,17 +45,17 @@ func NewServer(proto, addr string, conf *ServerConfig, eng *engine.Engine) (Serv } return nil, nil case "tcp": - if !conf.TlsVerify { + if !s.cfg.TlsVerify { logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } - if l, err = NewTcpSocket(addr, tlsConfigFromServerConfig(conf)); err != nil { + if l, err = NewTcpSocket(addr, tlsConfigFromServerConfig(s.cfg), s.start); err != nil { return nil, err } if err := allocateDaemonPort(addr); err != nil { return nil, err } case "unix": - if l, err = NewUnixSocket(addr, conf.SocketGroup); err != nil { + if l, err = NewUnixSocket(addr, s.cfg.SocketGroup, s.start); err != nil { return nil, err } default: @@ -71,19 +64,20 @@ func NewServer(proto, addr string, conf *ServerConfig, eng *engine.Engine) (Serv return &HttpServer{ &http.Server{ Addr: addr, - Handler: r, + Handler: s.router, }, l, }, nil } -func AcceptConnections() { +func (s *Server) AcceptConnections(d *daemon.Daemon) { // Tell the init daemon we are accepting requests + s.daemon = d go systemd.SdNotify("READY=1") // close the lock so the listeners start accepting connections select { - case <-activationLock: + case <-s.start: default: - close(activationLock) + close(s.start) } } diff --git a/api/server/server_windows.go b/api/server/server_windows.go index e6b23b97e..c121bbd3e 100644 --- a/api/server/server_windows.go +++ b/api/server/server_windows.go @@ -5,30 +5,24 @@ package server import ( "errors" "net" + "net/http" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" + "github.com/docker/docker/daemon" ) // NewServer sets up the required Server and does protocol specific checking. -func NewServer(proto, addr string, job *engine.Job) (Server, error) { +func (s *Server) newServer(proto, addr string) (Server, error) { var ( err error l net.Listener - r = createRouter( - job.Eng, - job.GetenvBool("Logging"), - job.GetenvBool("EnableCors"), - job.Getenv("CorsHeaders"), - job.Getenv("Version"), - ) ) switch proto { case "tcp": - if !job.GetenvBool("TlsVerify") { + if !s.cfg.TlsVerify { logrus.Warn("/!\\ DON'T BIND ON ANY IP ADDRESS WITHOUT setting -tlsverify IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\") } - if l, err = NewTcpSocket(addr, tlsConfigFromJob(job)); err != nil { + if l, err = NewTcpSocket(addr, tlsConfigFromServerConfig(s.cfg)); err != nil { return nil, err } if err := allocateDaemonPort(addr); err != nil { @@ -37,13 +31,21 @@ func NewServer(proto, addr string, job *engine.Job) (Server, error) { default: return nil, errors.New("Invalid protocol format. Windows only supports tcp.") } + return &HttpServer{ + &http.Server{ + Addr: addr, + Handler: s.router, + }, + l, + }, nil } -func AcceptConnections() { +func (s *Server) AcceptConnections(d *daemon.Daemon) { + s.daemon = d // close the lock so the listeners start accepting connections select { - case <-activationLock: + case <-s.start: default: - close(activationLock) + close(s.start) } } diff --git a/api/server/tcp_socket.go b/api/server/tcp_socket.go index 8454e0c58..a1f57231a 100644 --- a/api/server/tcp_socket.go +++ b/api/server/tcp_socket.go @@ -31,8 +31,8 @@ func tlsConfigFromServerConfig(conf *ServerConfig) *tlsConfig { } } -func NewTcpSocket(addr string, config *tlsConfig) (net.Listener, error) { - l, err := listenbuffer.NewListenBuffer("tcp", addr, activationLock) +func NewTcpSocket(addr string, config *tlsConfig, activate <-chan struct{}) (net.Listener, error) { + l, err := listenbuffer.NewListenBuffer("tcp", addr, activate) if err != nil { return nil, err } diff --git a/api/server/unix_socket.go b/api/server/unix_socket.go index e472efd0a..157005da6 100644 --- a/api/server/unix_socket.go +++ b/api/server/unix_socket.go @@ -12,13 +12,13 @@ import ( "github.com/docker/libcontainer/user" ) -func NewUnixSocket(path, group string) (net.Listener, error) { +func NewUnixSocket(path, group string, activate <-chan struct{}) (net.Listener, error) { if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) { return nil, err } mask := syscall.Umask(0777) defer syscall.Umask(mask) - l, err := listenbuffer.NewListenBuffer("unix", path, activationLock) + l, err := listenbuffer.NewListenBuffer("unix", path, activate) if err != nil { return nil, err } diff --git a/docker/daemon.go b/docker/daemon.go index 0fe10de65..0602ddf65 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -105,12 +105,14 @@ func mainDaemon() { TlsKey: *flKey, } + api := apiserver.New(serverConfig, eng) + // The serve API routine never exits unless an error occurs // We need to start it as a goroutine and wait on it so // daemon doesn't exit serveAPIWait := make(chan error) go func() { - if err := apiserver.ServeApi(flHosts, serverConfig, eng); err != nil { + if err := api.ServeApi(flHosts); err != nil { logrus.Errorf("ServeAPI error: %v", err) serveAPIWait <- err return @@ -143,8 +145,8 @@ func mainDaemon() { b.Install() // after the daemon is done setting up we can tell the api to start - // accepting connections - apiserver.AcceptConnections() + // accepting connections with specified daemon + api.AcceptConnections(d) // Daemon is fully initialized and handling API traffic // Wait for serve API job to complete diff --git a/integration/runtime_test.go b/integration/runtime_test.go index cd9be89a0..beb15b874 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -156,6 +156,8 @@ func spawnGlobalDaemon() { globalEngine = eng globalDaemon = mkDaemonFromEngine(eng, t) + serverConfig := &apiserver.ServerConfig{Logging: true} + api := apiserver.New(serverConfig, eng) // Spawn a Daemon go func() { logrus.Debugf("Spawning global daemon for integration tests") @@ -164,8 +166,7 @@ func spawnGlobalDaemon() { Host: testDaemonAddr, } - serverConfig := &apiserver.ServerConfig{Logging: true} - if err := apiserver.ServeApi([]string{listenURL.String()}, serverConfig, eng); err != nil { + if err := api.ServeApi([]string{listenURL.String()}); err != nil { logrus.Fatalf("Unable to spawn the test daemon: %s", err) } }() @@ -174,7 +175,7 @@ func spawnGlobalDaemon() { // FIXME: use inmem transports instead of tcp time.Sleep(time.Second) - apiserver.AcceptConnections() + api.AcceptConnections(getDaemon(eng)) } func spawnLegitHttpsDaemon() { @@ -204,6 +205,15 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { eng := newTestEngine(t, true, root) + serverConfig := &apiserver.ServerConfig{ + Logging: true, + Tls: true, + TlsVerify: true, + TlsCa: cacert, + TlsCert: cert, + TlsKey: key, + } + api := apiserver.New(serverConfig, eng) // Spawn a Daemon go func() { logrus.Debugf("Spawning https daemon for integration tests") @@ -211,15 +221,7 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { Scheme: testDaemonHttpsProto, Host: addr, } - serverConfig := &apiserver.ServerConfig{ - Logging: true, - Tls: true, - TlsVerify: true, - TlsCa: cacert, - TlsCert: cert, - TlsKey: key, - } - if err := apiserver.ServeApi([]string{listenURL.String()}, serverConfig, eng); err != nil { + if err := api.ServeApi([]string{listenURL.String()}); err != nil { logrus.Fatalf("Unable to spawn the test daemon: %s", err) } }() @@ -227,7 +229,7 @@ func spawnHttpsDaemon(addr, cacert, cert, key string) *engine.Engine { // Give some time to ListenAndServer to actually start time.Sleep(time.Second) - apiserver.AcceptConnections() + api.AcceptConnections(getDaemon(eng)) return eng } diff --git a/pkg/listenbuffer/buffer.go b/pkg/listenbuffer/buffer.go index 6e3656d2c..97d622c15 100644 --- a/pkg/listenbuffer/buffer.go +++ b/pkg/listenbuffer/buffer.go @@ -32,7 +32,7 @@ import "net" // NewListenBuffer returns a net.Listener listening on addr with the protocol // passed. The channel passed is used to activate the listenbuffer when the // caller is ready to accept connections. -func NewListenBuffer(proto, addr string, activate chan struct{}) (net.Listener, error) { +func NewListenBuffer(proto, addr string, activate <-chan struct{}) (net.Listener, error) { wrapped, err := net.Listen(proto, addr) if err != nil { return nil, err @@ -46,9 +46,9 @@ func NewListenBuffer(proto, addr string, activate chan struct{}) (net.Listener, // defaultListener is the buffered wrapper around the net.Listener type defaultListener struct { - wrapped net.Listener // The net.Listener wrapped by listenbuffer - ready bool // Whether the listenbuffer has been activated - activate chan struct{} // Channel to control activation of the listenbuffer + wrapped net.Listener // The net.Listener wrapped by listenbuffer + ready bool // Whether the listenbuffer has been activated + activate <-chan struct{} // Channel to control activation of the listenbuffer } // Close closes the wrapped socket. From dcc50e1d593fd7995189872791c6d7a013f16970 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Mon, 20 Apr 2015 08:16:47 -0700 Subject: [PATCH 524/999] Add support cpu cfs quota Signed-off-by: Lei Jitang --- api/types/types.go | 1 + contrib/completion/bash/docker | 1 + daemon/container.go | 1 + daemon/daemon.go | 4 +++ daemon/execdriver/driver.go | 2 ++ daemon/execdriver/lxc/lxc_template.go | 3 +++ daemon/info.go | 1 + docs/man/docker-create.1.md | 4 +++ docs/man/docker-run.1.md | 8 ++++++ docs/sources/reference/commandline/cli.md | 2 ++ docs/sources/reference/run.md | 10 ++++++++ integration-cli/docker_cli_run_test.go | 30 +++++++++++++++++++++++ pkg/sysinfo/sysinfo.go | 14 +++++++++++ runconfig/hostconfig.go | 1 + runconfig/parse.go | 2 ++ 15 files changed, 84 insertions(+) diff --git a/api/types/types.go b/api/types/types.go index 2a641fbaa..01b5d38f1 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -132,6 +132,7 @@ type Info struct { DriverStatus [][2]string MemoryLimit bool SwapLimit bool + CpuCfsQuota bool IPv4Forwarding bool Debug bool NFd int diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index f66935211..7f87e50f5 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -770,6 +770,7 @@ _docker_run() { --cidfile --cpuset --cpu-shares -c + --cpu-quota --device --dns --dns-search diff --git a/daemon/container.go b/daemon/container.go index 5c90f1406..9dc0696ea 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -356,6 +356,7 @@ func populateCommand(c *Container, env []string) error { CpuShares: c.hostConfig.CpuShares, CpusetCpus: c.hostConfig.CpusetCpus, CpusetMems: c.hostConfig.CpusetMems, + CpuQuota: c.hostConfig.CpuQuota, Rlimits: rlimits, } diff --git a/daemon/daemon.go b/daemon/daemon.go index ccf254b2c..0de7b1848 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1246,6 +1246,10 @@ func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]stri if hostConfig.Memory == 0 && hostConfig.MemorySwap > 0 { return warnings, fmt.Errorf("You should always set the Memory limit when using Memoryswap limit, see usage.\n") } + if hostConfig.CpuQuota > 0 && !daemon.SystemConfig().CpuCfsQuota { + warnings = append(warnings, "Your kernel does not support CPU cfs quota. Quota discarded.") + hostConfig.CpuQuota = 0 + } return warnings, nil } diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index fc3b5caba..ce196df20 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -111,6 +111,7 @@ type Resources struct { CpuShares int64 `json:"cpu_shares"` CpusetCpus string `json:"cpuset_cpus"` CpusetMems string `json:"cpuset_mems"` + CpuQuota int64 `json:"cpu_quota"` Rlimits []*ulimit.Rlimit `json:"rlimits"` } @@ -206,6 +207,7 @@ func SetupCgroups(container *configs.Config, c *Command) error { container.Cgroups.MemorySwap = c.Resources.MemorySwap container.Cgroups.CpusetCpus = c.Resources.CpusetCpus container.Cgroups.CpusetMems = c.Resources.CpusetMems + container.Cgroups.CpuQuota = c.Resources.CpuQuota } return nil diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index ece924d38..b3be7f8c5 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -113,6 +113,9 @@ lxc.cgroup.cpuset.cpus = {{.Resources.CpusetCpus}} {{if .Resources.CpusetMems}} lxc.cgroup.cpuset.mems = {{.Resources.CpusetMems}} {{end}} +{{if .Resources.CpuQuota}} +lxc.cgroup.cpu.cfs_quota_us = {{.Resources.CpuQuota}} +{{end}} {{end}} {{if .LxcConfig}} diff --git a/daemon/info.go b/daemon/info.go index fa019fe08..270abda59 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -60,6 +60,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { DriverStatus: daemon.GraphDriver().Status(), MemoryLimit: daemon.SystemConfig().MemoryLimit, SwapLimit: daemon.SystemConfig().SwapLimit, + CpuCfsQuota: daemon.SystemConfig().CpuCfsQuota, IPv4Forwarding: !daemon.SystemConfig().IPv4ForwardingDisabled, Debug: os.Getenv("DEBUG") != "", NFd: fileutils.GetTotalUsedFds(), diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index 6ce7fe6bd..bb9cbdc8f 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -14,6 +14,7 @@ docker-create - Create a new container [**--cidfile**[=*CIDFILE*]] [**--cpuset-cpus**[=*CPUSET-CPUS*]] [**--cpuset-mems**[=*CPUSET-MEMS*]] +[**--cpu-quota**[=*0*]] [**--device**[=*[]*]] [**--dns-search**[=*[]*]] [**--dns**[=*[]*]] @@ -82,6 +83,9 @@ IMAGE [COMMAND] [ARG...] then processes in your Docker container will only use memory from the first two memory nodes. +**-cpu-quota**=0 + Limit the CPU CFS (Completely Fair Scheduler) quota + **--device**=[] Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm) diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 42eeeb349..2893437bb 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -15,6 +15,7 @@ docker-run - Run a command in a new container [**--cpuset-cpus**[=*CPUSET-CPUS*]] [**--cpuset-mems**[=*CPUSET-MEMS*]] [**-d**|**--detach**[=*false*]] +[**--cpu-quota**[=*0*]] [**--device**[=*[]*]] [**--dns-search**[=*[]*]] [**--dns**[=*[]*]] @@ -142,6 +143,13 @@ division of CPU shares: then processes in your Docker container will only use memory from the first two memory nodes. +**--cpu-quota**=0 + Limit the CPU CFS (Completely Fair Scheduler) quota + + Limit the container's CPU usage. By default, containers run with the full +CPU resource. This flag tell the kernel to restrict the container's CPU usage +to the quota you specify. + **-d**, **--detach**=*true*|*false* Detached mode: run the container in the background and print the new container ID. The default is *false*. diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 607f67076..eb423ab19 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -894,6 +894,7 @@ Creates a new container. --cidfile="" Write the container ID to the file --cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) + --cpu-quota=0 Limit the CPU CFS (Completely Fair Scheduler) quota --device=[] Add a host device to the container --dns=[] Set custom DNS servers --dns-search=[] Set custom DNS search domains @@ -1847,6 +1848,7 @@ To remove an image using its digest: --cidfile="" Write the container ID to the file --cpuset-cpus="" CPUs in which to allow execution (0-3, 0,1) --cpuset-mems="" Memory nodes (MEMs) in which to allow execution (0-3, 0,1) + --cpu-quota=0 Limit the CPU CFS (Completely Fair Scheduler) quota -d, --detach=false Run container in background and print container ID --device=[] Add a host device to the container --dns=[] Set custom DNS servers diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index b5784cba7..10178b382 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -475,6 +475,7 @@ container: -c, --cpu-shares=0: CPU shares (relative weight) --cpuset-cpus="": CPUs in which to allow execution (0-3, 0,1) --cpuset-mems="": Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. + --cpu-quota=0: Limit the CPU CFS (Completely Fair Scheduler) quota ### Memory constraints @@ -615,6 +616,15 @@ memory nodes 1 and 3. This example restricts the processes in the container to only use memory from memory nodes 0, 1 and 2. +### CPU quota constraint + +The `--cpu-quota` flag limits the container's CPU usage. The default 0 value +allows the container to take 100% of a CPU resource (1 CPU). The CFS (Completely Fair +Scheduler) handles resource allocation for executing processes and is default +Linux Scheduler used by the kernel. Set this value to 50000 to limit the container +to 50% of a CPU resource. For multiple CPUs, adjust the `--cpu-quota` as necessary. +For more information, see the [CFS documentation on bandwidth limiting](https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt). + ## Runtime privilege, Linux capabilities, and LXC configuration --cap-add: Add Linux capabilities diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index de53fd494..e434261d8 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -105,6 +105,36 @@ func TestRunEchoStdoutWithCPUAndMemoryLimit(t *testing.T) { logDone("run - echo with CPU and memory limit") } +// "test" should be printed +func TestRunEchoStdoutWitCPUQuota(t *testing.T) { + defer deleteAllContainers() + + runCmd := exec.Command(dockerBinary, "run", "--cpu-quota", "8000", "--name", "test", "busybox", "echo", "test") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + if err != nil { + t.Fatalf("failed to run container: %v, output: %q", err, out) + } + out = strings.TrimSpace(out) + if strings.Contains(out, "Your kernel does not support CPU cfs quota") { + t.Skip("Your kernel does not support CPU cfs quota, skip this test") + } + if out != "test" { + t.Errorf("container should've printed 'test'") + } + + cmd := exec.Command(dockerBinary, "inspect", "-f", "{{.HostConfig.CpuQuota}}", "test") + out, _, err = runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("failed to inspect container: %s, %v", out, err) + } + out = strings.TrimSpace(out) + if out != "8000" { + t.Errorf("setting the CPU CFS quota failed") + } + + logDone("run - echo with CPU quota") +} + // "test" should be printed func TestRunEchoNamedContainer(t *testing.T) { defer deleteAllContainers() diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 16839bcb4..d1dcea3bf 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -13,6 +13,7 @@ import ( type SysInfo struct { MemoryLimit bool SwapLimit bool + CpuCfsQuota bool IPv4ForwardingDisabled bool AppArmor bool } @@ -39,6 +40,19 @@ func New(quiet bool) *SysInfo { } } + if cgroupCpuMountpoint, err := cgroups.FindCgroupMountpoint("cpu"); err != nil { + if !quiet { + logrus.Warnf("WARING: %s\n", err) + } + } else { + _, err1 := ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_quota_us")) + logrus.Warnf("%s", cgroupCpuMountpoint) + sysInfo.CpuCfsQuota = err1 == nil + if !sysInfo.CpuCfsQuota && !quiet { + logrus.Warnf("WARING: Your kernel does not support cgroup cfs quotas") + } + } + // Check if AppArmor is supported. if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) { sysInfo.AppArmor = false diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 9d338d7fb..171671b6e 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -167,6 +167,7 @@ type HostConfig struct { CpuShares int64 // CPU shares (relative weight vs. other containers) CpusetCpus string // CpusetCpus 0-2, 0,1 CpusetMems string // CpusetMems 0-2, 0,1 + CpuQuota int64 Privileged bool PortBindings nat.PortMap Links []string diff --git a/runconfig/parse.go b/runconfig/parse.go index 81dbf2d49..2cdb2d331 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -65,6 +65,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") flCpusetCpus = cmd.String([]string{"#-cpuset", "-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") + flCpuQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota") flNetMode = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container") flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use") @@ -312,6 +313,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe CpuShares: *flCpuShares, CpusetCpus: *flCpusetCpus, CpusetMems: *flCpusetMems, + CpuQuota: *flCpuQuota, Privileged: *flPrivileged, PortBindings: portBindings, Links: flLinks.GetAll(), From 66239ab5c914ce716c044b676ba1ad4757ce6f2b Mon Sep 17 00:00:00 2001 From: Zhang Wei Date: Mon, 20 Apr 2015 17:49:51 +0800 Subject: [PATCH 525/999] change httpError logic Signed-off-by: Zhang Wei Signed-off-by: Zhang Wei --- api/server/server.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 847ec6c21..c3ec548eb 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -130,6 +130,10 @@ func parseMultipartForm(r *http.Request) error { } func httpError(w http.ResponseWriter, err error) { + if err == nil || w == nil { + logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling") + return + } statusCode := http.StatusInternalServerError // FIXME: this is brittle and should not be necessary. // If we need to differentiate between different possible error types, we should @@ -149,10 +153,8 @@ func httpError(w http.ResponseWriter, err error) { statusCode = http.StatusForbidden } - if err != nil { - logrus.Errorf("HTTP Error: statusCode=%d %v", statusCode, err) - http.Error(w, err.Error(), statusCode) - } + logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": err}).Error("HTTP Error") + http.Error(w, err.Error(), statusCode) } // writeJSONEnv writes the engine.Env values to the http response stream as a From da7bca449652396dd8a4a0ff2e303d3d3c7a58a2 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 17 Apr 2015 15:18:28 -0700 Subject: [PATCH 526/999] Make all http handlers api.server.Server methods Signed-off-by: Alexander Morozov --- api/server/server.go | 316 ++++++++++++++++-------------------- integration/runtime_test.go | 3 - 2 files changed, 143 insertions(+), 176 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 3146e09ed..50dc84ff7 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -64,19 +64,14 @@ type Server struct { } func New(cfg *ServerConfig, eng *engine.Engine) *Server { - r := createRouter( - eng, - cfg.Logging, - cfg.EnableCors, - cfg.CorsHeaders, - cfg.Version, - ) - return &Server{ - cfg: cfg, - router: r, - start: make(chan struct{}), - eng: eng, + srv := &Server{ + cfg: cfg, + start: make(chan struct{}), + eng: eng, } + r := createRouter(srv, eng) + srv.router = r + return srv } func (s *Server) SetDaemon(d *daemon.Daemon) { @@ -250,19 +245,14 @@ func streamJSON(job *engine.Job, w http.ResponseWriter, flush bool) { } } -func getDaemon(eng *engine.Engine) *daemon.Daemon { - return eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon) -} - -func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var config *registry.AuthConfig err := json.NewDecoder(r.Body).Decode(&config) r.Body.Close() if err != nil { return err } - d := getDaemon(eng) - status, err := d.RegistryService.Auth(config) + status, err := s.daemon.RegistryService.Auth(config) if err != nil { return err } @@ -271,7 +261,7 @@ func postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter }) } -func getVersion(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getVersion(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") v := &types.Version{ @@ -289,7 +279,7 @@ func getVersion(eng *engine.Engine, version version.Version, w http.ResponseWrit return writeJSON(w, http.StatusOK, v) } -func postContainersKill(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersKill(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -317,7 +307,7 @@ func postContainersKill(eng *engine.Engine, version version.Version, w http.Resp } } - if err = getDaemon(eng).ContainerKill(name, sig); err != nil { + if err = s.daemon.ContainerKill(name, sig); err != nil { return err } @@ -325,7 +315,7 @@ func postContainersKill(eng *engine.Engine, version version.Version, w http.Resp return nil } -func postContainersPause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersPause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -334,8 +324,7 @@ func postContainersPause(eng *engine.Engine, version version.Version, w http.Res } name := vars["name"] - d := getDaemon(eng) - cont, err := d.Get(name) + cont, err := s.daemon.Get(name) if err != nil { return err } @@ -350,7 +339,7 @@ func postContainersPause(eng *engine.Engine, version version.Version, w http.Res return nil } -func postContainersUnpause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersUnpause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -359,8 +348,7 @@ func postContainersUnpause(eng *engine.Engine, version version.Version, w http.R } name := vars["name"] - d := getDaemon(eng) - cont, err := d.Get(name) + cont, err := s.daemon.Get(name) if err != nil { return err } @@ -375,17 +363,15 @@ func postContainersUnpause(eng *engine.Engine, version version.Version, w http.R return nil } -func getContainersExport(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersExport(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) - - return d.ContainerExport(vars["name"], w) + return s.daemon.ContainerExport(vars["name"], w) } -func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesJSON(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 } @@ -397,7 +383,7 @@ func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseW All: toBool(r.Form.Get("all")), } - images, err := getDaemon(eng).Repositories().Images(&imagesConfig) + images, err := s.daemon.Repositories().Images(&imagesConfig) if err != nil { return err } @@ -426,7 +412,7 @@ func getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseW return writeJSON(w, http.StatusOK, legacyImages) } -func getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version.GreaterThan("1.6") { w.WriteHeader(http.StatusNotFound) return fmt.Errorf("This is now implemented in the client.") @@ -435,10 +421,10 @@ func getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWr return nil } -func getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") - info, err := getDaemon(eng).SystemInfo() + info, err := s.daemon.SystemInfo() if err != nil { return err } @@ -446,7 +432,7 @@ func getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, return writeJSON(w, http.StatusOK, info) } -func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getEvents(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 } @@ -497,7 +483,7 @@ func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWrite return true } - d := getDaemon(eng) + d := s.daemon es := d.EventsService w.Header().Set("Content-Type", "application/json") enc := json.NewEncoder(utils.NewWriteFlusher(w)) @@ -550,13 +536,13 @@ func getEvents(eng *engine.Engine, version version.Version, w http.ResponseWrite } } -func getImagesHistory(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesHistory(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } name := vars["name"] - history, err := getDaemon(eng).Repositories().History(name) + history, err := s.daemon.Repositories().History(name) if err != nil { return err } @@ -564,14 +550,13 @@ func getImagesHistory(eng *engine.Engine, version version.Version, w http.Respon return writeJSON(w, http.StatusOK, history) } -func getContainersChanges(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersChanges(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } name := vars["name"] - d := getDaemon(eng) - cont, err := d.Get(name) + cont, err := s.daemon.Get(name) if err != nil { return err } @@ -584,7 +569,7 @@ func getContainersChanges(eng *engine.Engine, version version.Version, w http.Re return writeJSON(w, http.StatusOK, changes) } -func getContainersTop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersTop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version.LessThan("1.4") { return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.") } @@ -597,7 +582,7 @@ func getContainersTop(eng *engine.Engine, version version.Version, w http.Respon return err } - procList, err := getDaemon(eng).ContainerTop(vars["name"], r.Form.Get("ps_args")) + procList, err := s.daemon.ContainerTop(vars["name"], r.Form.Get("ps_args")) if err != nil { return err } @@ -605,7 +590,7 @@ func getContainersTop(eng *engine.Engine, version version.Version, w http.Respon return writeJSON(w, http.StatusOK, procList) } -func getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersJSON(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 } @@ -626,7 +611,7 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo config.Limit = limit } - containers, err := getDaemon(eng).Containers(config) + containers, err := s.daemon.Containers(config) if err != nil { return err } @@ -634,7 +619,7 @@ func getContainersJSON(eng *engine.Engine, version version.Version, w http.Respo return writeJSON(w, http.StatusOK, containers) } -func getContainersStats(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersStats(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 } @@ -642,12 +627,10 @@ func getContainersStats(eng *engine.Engine, version version.Version, w http.Resp return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) - - return d.ContainerStats(vars["name"], utils.NewWriteFlusher(w)) + return s.daemon.ContainerStats(vars["name"], utils.NewWriteFlusher(w)) } -func getContainersLogs(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersLogs(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 } @@ -670,15 +653,14 @@ func getContainersLogs(eng *engine.Engine, version version.Version, w http.Respo OutStream: utils.NewWriteFlusher(w), } - d := getDaemon(eng) - if err := d.ContainerLogs(vars["name"], logsConfig); err != nil { + if err := s.daemon.ContainerLogs(vars["name"], logsConfig); err != nil { fmt.Fprintf(w, "Error running logs job: %s\n", err) } return nil } -func postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesTag(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 } @@ -686,18 +668,17 @@ func postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseW return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) repo := r.Form.Get("repo") tag := r.Form.Get("tag") force := toBool(r.Form.Get("force")) - if err := d.Repositories().Tag(repo, tag, vars["name"], force); err != nil { + if err := s.daemon.Repositories().Tag(repo, tag, vars["name"], force); err != nil { return err } w.WriteHeader(http.StatusCreated) return nil } -func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postCommit(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 } @@ -723,9 +704,7 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit Config: r.Body, } - d := getDaemon(eng) - - imgID, err := d.ContainerCommit(cont, containerCommitConfig) + imgID, err := s.daemon.ContainerCommit(cont, containerCommitConfig) if err != nil { return err } @@ -736,7 +715,7 @@ func postCommit(eng *engine.Engine, version version.Version, w http.ResponseWrit } // Creates an image from Pull or from Import -func postImagesCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesCreate(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 } @@ -757,8 +736,6 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon } } - d := getDaemon(eng) - if image != "" { //pull if tag == "" { image, tag = parsers.ParseRepositoryTag(image) @@ -783,7 +760,7 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon imagePullConfig.Json = false } - if err := d.Repositories().Pull(image, tag, imagePullConfig, eng); err != nil { + if err := s.daemon.Repositories().Pull(image, tag, imagePullConfig, eng); err != nil { return err } } else { //import @@ -804,7 +781,7 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon imageImportConfig.Json = false } - if err := d.Repositories().Import(src, repo, tag, imageImportConfig, eng); err != nil { + if err := s.daemon.Repositories().Import(src, repo, tag, imageImportConfig, eng); err != nil { return err } @@ -813,7 +790,7 @@ func postImagesCreate(eng *engine.Engine, version version.Version, w http.Respon return nil } -func getImagesSearch(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesSearch(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 } @@ -836,15 +813,14 @@ func getImagesSearch(eng *engine.Engine, version version.Version, w http.Respons headers[k] = v } } - d := getDaemon(eng) - query, err := d.RegistryService.Search(r.Form.Get("term"), config, headers) + query, err := s.daemon.RegistryService.Search(r.Form.Get("term"), config, headers) if err != nil { return err } return json.NewEncoder(w).Encode(query.Results) } -func postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -896,7 +872,7 @@ func postImagesPush(eng *engine.Engine, version version.Version, w http.Response return nil } -func getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -916,14 +892,14 @@ func getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWr return job.Run() } -func postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { job := eng.Job("load") job.Stdin.Add(r.Body) job.Stdout.Add(w) return job.Run() } -func postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -940,7 +916,7 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re return err } - containerId, warnings, err := getDaemon(eng).ContainerCreate(name, config, hostConfig) + containerId, warnings, err := s.daemon.ContainerCreate(name, config, hostConfig) if err != nil { return err } @@ -951,7 +927,7 @@ func postContainersCreate(eng *engine.Engine, version version.Version, w http.Re }) } -func postContainersRestart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersRestart(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 } @@ -959,13 +935,12 @@ func postContainersRestart(eng *engine.Engine, version version.Version, w http.R return fmt.Errorf("Missing parameter") } - s, err := strconv.Atoi(r.Form.Get("t")) + timeout, err := strconv.Atoi(r.Form.Get("t")) if err != nil { return err } - d := getDaemon(eng) - if err := d.ContainerRestart(vars["name"], s); err != nil { + if err := s.daemon.ContainerRestart(vars["name"], timeout); err != nil { return err } @@ -974,7 +949,7 @@ func postContainersRestart(eng *engine.Engine, version version.Version, w http.R return nil } -func postContainerRename(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerRename(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 } @@ -982,17 +957,16 @@ func postContainerRename(eng *engine.Engine, version version.Version, w http.Res return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) name := vars["name"] newName := r.Form.Get("name") - if err := d.ContainerRename(name, newName); err != nil { + if err := s.daemon.ContainerRename(name, newName); err != nil { return err } w.WriteHeader(http.StatusNoContent) return nil } -func deleteContainers(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) deleteContainers(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 } @@ -1001,14 +975,13 @@ func deleteContainers(eng *engine.Engine, version version.Version, w http.Respon } name := vars["name"] - d := getDaemon(eng) config := &daemon.ContainerRmConfig{ ForceRemove: toBool(r.Form.Get("force")), RemoveVolume: toBool(r.Form.Get("v")), RemoveLink: toBool(r.Form.Get("link")), } - if err := d.ContainerRm(name, config); err != nil { + if err := s.daemon.ContainerRm(name, config); err != nil { // Force a 404 for the empty string if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") { return fmt.Errorf("no such id: \"\"") @@ -1021,7 +994,7 @@ func deleteContainers(eng *engine.Engine, version version.Version, w http.Respon return nil } -func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) deleteImages(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 } @@ -1029,12 +1002,11 @@ func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWr return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) name := vars["name"] force := toBool(r.Form.Get("force")) noprune := toBool(r.Form.Get("noprune")) - list, err := d.ImageDelete(name, force, noprune) + list, err := s.daemon.ImageDelete(name, force, noprune) if err != nil { return err } @@ -1042,7 +1014,7 @@ func deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWr return writeJSON(w, http.StatusOK, list) } -func postContainersStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1067,7 +1039,7 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res hostConfig = c } - if err := getDaemon(eng).ContainerStart(vars["name"], hostConfig); err != nil { + if err := s.daemon.ContainerStart(vars["name"], hostConfig); err != nil { if err.Error() == "Container already started" { w.WriteHeader(http.StatusNotModified) return nil @@ -1078,7 +1050,7 @@ func postContainersStart(eng *engine.Engine, version version.Version, w http.Res return nil } -func postContainersStop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersStop(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 } @@ -1086,13 +1058,12 @@ func postContainersStop(eng *engine.Engine, version version.Version, w http.Resp return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) seconds, err := strconv.Atoi(r.Form.Get("t")) if err != nil { return err } - if err := d.ContainerStop(vars["name"], seconds); err != nil { + if err := s.daemon.ContainerStop(vars["name"], seconds); err != nil { if err.Error() == "Container already stopped" { w.WriteHeader(http.StatusNotModified) return nil @@ -1104,14 +1075,13 @@ func postContainersStop(eng *engine.Engine, version version.Version, w http.Resp return nil } -func postContainersWait(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersWait(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } name := vars["name"] - d := getDaemon(eng) - cont, err := d.Get(name) + cont, err := s.daemon.Get(name) if err != nil { return err } @@ -1123,7 +1093,7 @@ func postContainersWait(eng *engine.Engine, version version.Version, w http.Resp }) } -func postContainersResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersResize(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 } @@ -1140,8 +1110,7 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re return err } - d := getDaemon(eng) - cont, err := d.Get(vars["name"]) + cont, err := s.daemon.Get(vars["name"]) if err != nil { return err } @@ -1149,7 +1118,7 @@ func postContainersResize(eng *engine.Engine, version version.Version, w http.Re return cont.Resize(height, width) } -func postContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersAttach(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 } @@ -1157,9 +1126,7 @@ func postContainersAttach(eng *engine.Engine, version version.Version, w http.Re return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) - - cont, err := d.Get(vars["name"]) + cont, err := s.daemon.Get(vars["name"]) if err != nil { return err } @@ -1206,16 +1173,14 @@ func postContainersAttach(eng *engine.Engine, version version.Version, w http.Re return nil } -func wsContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) wsContainersAttach(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 } if vars == nil { return fmt.Errorf("Missing parameter") } - d := getDaemon(eng) - - cont, err := d.Get(vars["name"]) + cont, err := s.daemon.Get(vars["name"]) if err != nil { return err } @@ -1234,7 +1199,7 @@ func wsContainersAttach(eng *engine.Engine, version version.Version, w http.Resp return nil } -func getContainersByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1246,13 +1211,12 @@ func getContainersByName(eng *engine.Engine, version version.Version, w http.Res return job.Run() } -func getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter 'id'") } - d := getDaemon(eng) - eConfig, err := d.ContainerExecInspect(vars["id"]) + eConfig, err := s.daemon.ContainerExecInspect(vars["id"]) if err != nil { return err } @@ -1260,7 +1224,7 @@ func getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWri return writeJSON(w, http.StatusOK, eConfig) } -func getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1272,7 +1236,7 @@ func getImagesByName(eng *engine.Engine, version version.Version, w http.Respons return job.Run() } -func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version.LessThan("1.3") { return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") } @@ -1362,7 +1326,7 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite return nil } -func postContainersCopy(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersCopy(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1386,7 +1350,7 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp res = res[1:] } - cont, err := getDaemon(eng).Get(vars["name"]) + cont, err := s.daemon.Get(vars["name"]) if err != nil { logrus.Errorf("%v", err) if strings.Contains(strings.ToLower(err.Error()), "no such id") { @@ -1412,7 +1376,7 @@ func postContainersCopy(eng *engine.Engine, version version.Version, w http.Resp return nil } -func postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -1449,7 +1413,7 @@ func postContainerExecCreate(eng *engine.Engine, version version.Version, w http } // TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. -func postContainerExecStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerExecStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -1500,7 +1464,7 @@ func postContainerExecStart(eng *engine.Engine, version version.Version, w http. return nil } -func postContainerExecResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerExecResize(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 } @@ -1517,12 +1481,10 @@ func postContainerExecResize(eng *engine.Engine, version version.Version, w http return err } - d := getDaemon(eng) - - return d.ContainerExecResize(vars["name"], height, width) + return s.daemon.ContainerExecResize(vars["name"], height, width) } -func optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.WriteHeader(http.StatusOK) return nil } @@ -1533,7 +1495,7 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") } -func ping(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) ping(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { _, err := w.Write([]byte{'O', 'K'}) return err } @@ -1574,71 +1536,72 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local } // we keep enableCors just for legacy usage, need to be removed in the future -func createRouter(eng *engine.Engine, logging, enableCors bool, corsHeaders string, dockerVersion string) *mux.Router { +func createRouter(s *Server, eng *engine.Engine) *mux.Router { r := mux.NewRouter() if os.Getenv("DEBUG") != "" { ProfilerSetup(r, "/debug/") } m := map[string]map[string]HttpApiFunc{ "GET": { - "/_ping": ping, - "/events": getEvents, - "/info": getInfo, - "/version": getVersion, - "/images/json": getImagesJSON, - "/images/viz": getImagesViz, - "/images/search": getImagesSearch, - "/images/get": getImagesGet, - "/images/{name:.*}/get": getImagesGet, - "/images/{name:.*}/history": getImagesHistory, - "/images/{name:.*}/json": getImagesByName, - "/containers/ps": getContainersJSON, - "/containers/json": getContainersJSON, - "/containers/{name:.*}/export": getContainersExport, - "/containers/{name:.*}/changes": getContainersChanges, - "/containers/{name:.*}/json": getContainersByName, - "/containers/{name:.*}/top": getContainersTop, - "/containers/{name:.*}/logs": getContainersLogs, - "/containers/{name:.*}/stats": getContainersStats, - "/containers/{name:.*}/attach/ws": wsContainersAttach, - "/exec/{id:.*}/json": getExecByID, + "/_ping": s.ping, + "/events": s.getEvents, + "/info": s.getInfo, + "/version": s.getVersion, + "/images/json": s.getImagesJSON, + "/images/viz": s.getImagesViz, + "/images/search": s.getImagesSearch, + "/images/get": s.getImagesGet, + "/images/{name:.*}/get": s.getImagesGet, + "/images/{name:.*}/history": s.getImagesHistory, + "/images/{name:.*}/json": s.getImagesByName, + "/containers/ps": s.getContainersJSON, + "/containers/json": s.getContainersJSON, + "/containers/{name:.*}/export": s.getContainersExport, + "/containers/{name:.*}/changes": s.getContainersChanges, + "/containers/{name:.*}/json": s.getContainersByName, + "/containers/{name:.*}/top": s.getContainersTop, + "/containers/{name:.*}/logs": s.getContainersLogs, + "/containers/{name:.*}/stats": s.getContainersStats, + "/containers/{name:.*}/attach/ws": s.wsContainersAttach, + "/exec/{id:.*}/json": s.getExecByID, }, "POST": { - "/auth": postAuth, - "/commit": postCommit, - "/build": postBuild, - "/images/create": postImagesCreate, - "/images/load": postImagesLoad, - "/images/{name:.*}/push": postImagesPush, - "/images/{name:.*}/tag": postImagesTag, - "/containers/create": postContainersCreate, - "/containers/{name:.*}/kill": postContainersKill, - "/containers/{name:.*}/pause": postContainersPause, - "/containers/{name:.*}/unpause": postContainersUnpause, - "/containers/{name:.*}/restart": postContainersRestart, - "/containers/{name:.*}/start": postContainersStart, - "/containers/{name:.*}/stop": postContainersStop, - "/containers/{name:.*}/wait": postContainersWait, - "/containers/{name:.*}/resize": postContainersResize, - "/containers/{name:.*}/attach": postContainersAttach, - "/containers/{name:.*}/copy": postContainersCopy, - "/containers/{name:.*}/exec": postContainerExecCreate, - "/exec/{name:.*}/start": postContainerExecStart, - "/exec/{name:.*}/resize": postContainerExecResize, - "/containers/{name:.*}/rename": postContainerRename, + "/auth": s.postAuth, + "/commit": s.postCommit, + "/build": s.postBuild, + "/images/create": s.postImagesCreate, + "/images/load": s.postImagesLoad, + "/images/{name:.*}/push": s.postImagesPush, + "/images/{name:.*}/tag": s.postImagesTag, + "/containers/create": s.postContainersCreate, + "/containers/{name:.*}/kill": s.postContainersKill, + "/containers/{name:.*}/pause": s.postContainersPause, + "/containers/{name:.*}/unpause": s.postContainersUnpause, + "/containers/{name:.*}/restart": s.postContainersRestart, + "/containers/{name:.*}/start": s.postContainersStart, + "/containers/{name:.*}/stop": s.postContainersStop, + "/containers/{name:.*}/wait": s.postContainersWait, + "/containers/{name:.*}/resize": s.postContainersResize, + "/containers/{name:.*}/attach": s.postContainersAttach, + "/containers/{name:.*}/copy": s.postContainersCopy, + "/containers/{name:.*}/exec": s.postContainerExecCreate, + "/exec/{name:.*}/start": s.postContainerExecStart, + "/exec/{name:.*}/resize": s.postContainerExecResize, + "/containers/{name:.*}/rename": s.postContainerRename, }, "DELETE": { - "/containers/{name:.*}": deleteContainers, - "/images/{name:.*}": deleteImages, + "/containers/{name:.*}": s.deleteContainers, + "/images/{name:.*}": s.deleteImages, }, "OPTIONS": { - "": optionsHandler, + "": s.optionsHandler, }, } // If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*" // otherwise, all head values will be passed to HTTP handler - if corsHeaders == "" && enableCors { + corsHeaders := s.cfg.CorsHeaders + if corsHeaders == "" && s.cfg.EnableCors { corsHeaders = "*" } @@ -1651,7 +1614,7 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, corsHeaders stri localMethod := method // build the handler function - f := makeHttpHandler(eng, logging, localMethod, localRoute, localFct, corsHeaders, version.Version(dockerVersion)) + f := makeHttpHandler(eng, s.cfg.Logging, localMethod, localRoute, localFct, corsHeaders, version.Version(s.cfg.Version)) // add the new route if localRoute == "" { @@ -1670,7 +1633,14 @@ func createRouter(eng *engine.Engine, logging, enableCors bool, corsHeaders stri // FIXME: refactor this to be part of Server and not require re-creating a new // router each time. This requires first moving ListenAndServe into Server. func ServeRequest(eng *engine.Engine, apiversion version.Version, w http.ResponseWriter, req *http.Request) { - router := createRouter(eng, false, true, "", "") + cfg := &ServerConfig{ + EnableCors: true, + Version: string(apiversion), + } + api := New(cfg, eng) + daemon, _ := eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon) + api.AcceptConnections(daemon) + router := createRouter(api, eng) // Insert APIVERSION into the request as a convenience req.URL.Path = fmt.Sprintf("/v%s%s", apiversion, req.URL.Path) router.ServeHTTP(w, req) diff --git a/integration/runtime_test.go b/integration/runtime_test.go index beb15b874..2e106972e 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -48,9 +48,7 @@ const ( ) var ( - // FIXME: globalDaemon is deprecated by globalEngine. All tests should be converted. globalDaemon *daemon.Daemon - globalEngine *engine.Engine globalHttpsEngine *engine.Engine globalRogueHttpsEngine *engine.Engine startFds int @@ -153,7 +151,6 @@ func spawnGlobalDaemon() { } t := std_log.New(os.Stderr, "", 0) eng := NewTestEngine(t) - globalEngine = eng globalDaemon = mkDaemonFromEngine(eng, t) serverConfig := &apiserver.ServerConfig{Logging: true} From fcdb1fbfa1e691bbdfb73a289ff84fbf6b26fad6 Mon Sep 17 00:00:00 2001 From: Raghuram Devarakonda Date: Wed, 15 Apr 2015 17:11:58 -0400 Subject: [PATCH 527/999] Improve documentation in "project" directory. Signed-off-by: Raghuram Devarakonda --- docs/sources/project/advanced-contributing.md | 2 +- docs/sources/project/coding-style.md | 4 +- docs/sources/project/create-pr.md | 30 +++++---- docs/sources/project/find-an-issue.md | 16 ++--- docs/sources/project/set-up-git.md | 4 +- docs/sources/project/software-required.md | 2 +- docs/sources/project/work-issue.md | 66 ++++++++----------- 7 files changed, 58 insertions(+), 66 deletions(-) diff --git a/docs/sources/project/advanced-contributing.md b/docs/sources/project/advanced-contributing.md index 0c9b5d1ce..ee958f4b4 100644 --- a/docs/sources/project/advanced-contributing.md +++ b/docs/sources/project/advanced-contributing.md @@ -89,7 +89,7 @@ The following provides greater detail on the process: This is a Markdown file that describes your idea. Your proposal should include information like: - * Why is this changed needed or what are the use cases? + * Why is this change needed or what are the use cases? * What are the requirements this change should meet? * What are some ways to design/implement this feature? * Which design/implementation do you think is best and why? diff --git a/docs/sources/project/coding-style.md b/docs/sources/project/coding-style.md index e5b6f5fe9..bf8267e71 100644 --- a/docs/sources/project/coding-style.md +++ b/docs/sources/project/coding-style.md @@ -6,8 +6,8 @@ page_keywords: change, commit, squash, request, pull request, test, unit test, i This checklist summarizes the material you experienced working through [make a code contribution](/project/make-a-contribution) and [advanced -contributing](/project/advanced-contributing). The checklist applies to code -that is program code or code that is documentation code. +contributing](/project/advanced-contributing). The checklist applies to both +program code and documentation code. ## Change and commit code diff --git a/docs/sources/project/create-pr.md b/docs/sources/project/create-pr.md index 197aee849..f39f0aa98 100644 --- a/docs/sources/project/create-pr.md +++ b/docs/sources/project/create-pr.md @@ -22,7 +22,7 @@ Before you create a pull request, check your work. 2. Checkout your feature branch. $ git checkout 11038-fix-rhel-link - Already on '11038-fix-rhel-link' + Switched to branch '11038-fix-rhel-link' 3. Run the full test suite on your branch. @@ -41,7 +41,11 @@ Before you create a pull request, check your work. Always rebase and squash your commits before making a pull request. -1. Fetch any of the last minute changes from `docker/docker`. +1. Checkout your feature branch in your local `docker-fork` repository. + + This is the branch associated with your request. + +2. Fetch any last minute changes from `docker/docker`. $ git fetch upstream master From github.com:docker/docker @@ -56,28 +60,28 @@ Always rebase and squash your commits before making a pull request. pick 1a79f55 Tweak some of the other text for grammar pick 53e4983 Fix a link pick 3ce07bb Add a new line about RHEL - - If you run into trouble, `git --rebase abort` removes any changes and gets - you back to where you started. -4. Squash the `pick` keyword with `squash` on all but the first commit. +5. Replace the `pick` keyword with `squash` on all but the first commit. pick 1a79f55 Tweak some of the other text for grammar squash 53e4983 Fix a link squash 3ce07bb Add a new line about RHEL - After closing the file, `git` opens your editor again to edit the commit - message. + After you save the changes and quit from the editor, git starts + the rebase, reporting the progress along the way. Sometimes + your changes can conflict with the work of others. If git + encounters a conflict, it stops the rebase, and prints guidance + for how to correct the conflict. -5. Edit and save your commit message. +6. Edit and save your commit message. `git commit -s` Make sure your message includes FETCH_HEAD -3. Fetch all the changes from the `upstream master` branch. +3. Start an interactive rebase. - $ git fetch upstream master + $ git rebase -i upstream/master - This command says get all the changes from the `master` branch belonging to - the `upstream` remote. +4. Rebase opens an editor with a list of commits. -4. Rebase your master with the local copy of Docker's `master` branch. + pick 1a79f55 Tweak some of the other text for grammar + pick 53e4983 Fix a link + pick 3ce07bb Add a new line about RHEL - $ git rebase -i upstream/master - - This command starts an interactive rebase to rewrite all the commits from - Docker's `upstream/master` onto your local branch, and then re-apply each of - your commits on top of the upstream changes. If you aren't familiar or - comfortable with rebase, you can learn more about rebasing on the web. - -5. Rebase opens an editor with a list of commits. +5. Replace the `pick` keyword with `squash` on all but the first commit. - pick 1a79f55 Tweak some of the other text for grammar - pick 53e4983 Fix a link - pick 3ce07bb Add a new line about RHEL - - If you run into trouble, `git --rebase abort` removes any changes and gets - you back to where you started. + pick 1a79f55 Tweak some of the other text for grammar + squash 53e4983 Fix a link + squash 3ce07bb Add a new line about RHEL -6. Squash the `pick` keyword with `squash` on all but the first commit. + After you save the changes and quit from the editor, git starts + the rebase, reporting the progress along the way. Sometimes + your changes can conflict with the work of others. If git + encounters a conflict, it stops the rebase, and prints guidance + for how to correct the conflict. - pick 1a79f55 Tweak some of the other text for grammar - squash 53e4983 Fix a link - squash 3ce07bb Add a new line about RHEL +6. Edit and save your commit message. - After closing the file, `git` opens your editor again to edit the commit - message. + `git commit -s` -7. Edit the commit message to reflect the entire change. + Make sure your message includes Date: Wed, 15 Apr 2015 22:43:18 +0000 Subject: [PATCH 529/999] Port test from integration tests Addresses #12255 Signed-off-by: Srini Brahmaroutu --- daemon/daemon.go | 4 ++ daemon/start.go | 4 ++ integration-cli/docker_api_containers_test.go | 51 +++++++++++++++++++ integration-cli/docker_cli_run_test.go | 12 ----- 4 files changed, 59 insertions(+), 12 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index ccf254b2c..e5fd6f939 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1226,6 +1226,10 @@ func checkKernel() error { func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]string, error) { var warnings []string + if hostConfig == nil { + return warnings, nil + } + if hostConfig.LxcConf.Len() > 0 && !strings.Contains(daemon.ExecutionDriver().Name(), "lxc") { return warnings, fmt.Errorf("Cannot use --lxc-conf with execdriver: %s", daemon.ExecutionDriver().Name()) } diff --git a/daemon/start.go b/daemon/start.go index dbb3dd181..d3af073a8 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -20,6 +20,10 @@ func (daemon *Daemon) ContainerStart(name string, hostConfig *runconfig.HostConf return fmt.Errorf("Container already started") } + if _, err = daemon.verifyHostConfig(hostConfig); err != nil { + return err + } + // This is kept for backward compatibility - hostconfig should be passed when // creating a container, not during start. if hostConfig != nil { diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index b76eb6ba2..1dea47845 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -817,3 +817,54 @@ func TestContainerApiPostCreateNull(t *testing.T) { logDone("containers REST API - Create Null") } + +func TestCreateWithTooLowMemoryLimit(t *testing.T) { + defer deleteAllContainers() + config := `{ + "Image": "busybox", + "Cmd": "ls", + "OpenStdin": true, + "CpuShares": 100, + "Memory": 524287 + }` + + _, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") + b, err2 := readBody(body) + if err2 != nil { + t.Fatal(err2) + } + + if err == nil || !strings.Contains(string(b), "Minimum memory limit allowed is 4MB") { + t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") + } + + logDone("container REST API - create can't set too low memory limit") +} + +func TestStartWithTooLowMemoryLimit(t *testing.T) { + defer deleteAllContainers() + + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "busybox")) + if err != nil { + t.Fatal(err, out) + } + + containerID := strings.TrimSpace(out) + + config := `{ + "CpuShares": 100, + "Memory": 524287 + }` + + _, body, err := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json") + b, err2 := readBody(body) + if err2 != nil { + t.Fatal(err2) + } + + if err == nil || !strings.Contains(string(b), "Minimum memory limit allowed is 4MB") { + t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") + } + + logDone("container REST API - start can't set too low memory limit") +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index de53fd494..f2e4eb9b2 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -3496,15 +3496,3 @@ func TestRunPidHostWithChildIsKillable(t *testing.T) { } logDone("run - can kill container with pid-host and some childs of pid 1") } - -func TestRunWithTooSmallMemoryLimit(t *testing.T) { - defer deleteAllContainers() - // this memory limit is 1 byte less than the min, which is 4MB - // https://github.com/docker/docker/blob/v1.5.0/daemon/create.go#L22 - out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-m", "4194303", "busybox")) - if err == nil || !strings.Contains(out, "Minimum memory limit allowed is 4MB") { - t.Fatalf("expected run to fail when using too low a memory limit: %q", out) - } - - logDone("run - can't set too low memory limit") -} From 716e21be2b9780306cc1ffe02b2a784e2dece94e Mon Sep 17 00:00:00 2001 From: Sergey Evstifeev Date: Mon, 20 Apr 2015 21:13:05 +0200 Subject: [PATCH 530/999] Add missing testRequires(t, Network) Fixes #12552 Signed-off-by: Sergey Evstifeev --- integration-cli/docker_api_images_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 276ee7f3c..1774e6b74 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -70,6 +70,7 @@ func TestApiImagesFilter(t *testing.T) { } func TestApiImagesSaveAndLoad(t *testing.T) { + testRequires(t, Network) out, err := buildImage("saveandload", "FROM hello-world\nENV FOO bar", false) if err != nil { t.Fatal(err) From 9e50bf6270f426f6ef6649b1985036988b207407 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Mon, 20 Apr 2015 12:48:33 -0700 Subject: [PATCH 531/999] Remove engine from trust Signed-off-by: Alexander Morozov --- daemon/daemon.go | 28 +++++++++--------- graph/manifest.go | 36 +++++++++++------------ graph/pull.go | 5 +--- graph/tags.go | 21 ++++++++++---- graph/tags_unit_test.go | 6 +++- trust/service.go | 63 ++++++++++++++--------------------------- 6 files changed, 74 insertions(+), 85 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index ccf254b2c..7dc0f2197 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -108,7 +108,6 @@ type Daemon struct { containerGraph *graphdb.Database driver graphdriver.Driver execDriver execdriver.Driver - trustStore *trust.TrustStore statsCollector *statsCollector defaultLogConfig runconfig.LogConfig RegistryService *registry.Service @@ -129,9 +128,6 @@ func (daemon *Daemon) Install(eng *engine.Engine) error { if err := daemon.Repositories().Install(eng); err != nil { return err } - if err := daemon.trustStore.Install(eng); err != nil { - return err - } // FIXME: this hack is necessary for legacy integration tests to access // the daemon object. eng.HackSetGlobalVar("httpapi.daemon", daemon) @@ -903,22 +899,29 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService return nil, err } - eventsService := events.New() - logrus.Debug("Creating repository list") - repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), g, trustKey, registryService, eventsService) - if err != nil { - return nil, fmt.Errorf("Couldn't create Tag store: %s", err) - } - trustDir := path.Join(config.Root, "trust") if err := os.MkdirAll(trustDir, 0700); err != nil && !os.IsExist(err) { return nil, err } - t, err := trust.NewTrustStore(trustDir) + trustService, err := trust.NewTrustStore(trustDir) if err != nil { return nil, fmt.Errorf("could not create trust store: %s", err) } + eventsService := events.New() + logrus.Debug("Creating repository list") + tagCfg := &graph.TagStoreConfig{ + Graph: g, + Key: trustKey, + Registry: registryService, + Events: eventsService, + Trust: trustService, + } + repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), tagCfg) + if err != nil { + return nil, fmt.Errorf("Couldn't create Tag store: %s", err) + } + if !config.DisableNetwork { if err := bridge.InitDriver(&config.Bridge); err != nil { return nil, fmt.Errorf("Error initializing Bridge: %v", err) @@ -980,7 +983,6 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService sysInitPath: sysInitPath, execDriver: ed, eng: eng, - trustStore: t, statsCollector: newStatsCollector(1 * time.Second), defaultLogConfig: config.LogConfig, RegistryService: registryService, diff --git a/graph/manifest.go b/graph/manifest.go index 7e9281537..e6d5ebc39 100644 --- a/graph/manifest.go +++ b/graph/manifest.go @@ -1,7 +1,6 @@ package graph import ( - "bytes" "encoding/json" "fmt" @@ -9,6 +8,7 @@ import ( "github.com/docker/distribution/digest" "github.com/docker/docker/engine" "github.com/docker/docker/registry" + "github.com/docker/docker/trust" "github.com/docker/docker/utils" "github.com/docker/libtrust" ) @@ -69,32 +69,28 @@ func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte, dgst, var verified bool for _, key := range keys { - job := eng.Job("trust_key_check") - b, err := key.MarshalJSON() - if err != nil { - return nil, false, fmt.Errorf("error marshalling public key: %s", err) - } namespace := manifest.Name if namespace[0] != '/' { namespace = "/" + namespace } - stdoutBuffer := bytes.NewBuffer(nil) - - job.Args = append(job.Args, namespace) - job.Setenv("PublicKey", string(b)) - // Check key has read/write permission (0x03) - job.SetenvInt("Permission", 0x03) - job.Stdout.Add(stdoutBuffer) - if err = job.Run(); err != nil { - return nil, false, fmt.Errorf("error running key check: %s", err) + b, err := key.MarshalJSON() + if err != nil { + return nil, false, fmt.Errorf("error marshalling public key: %s", err) } - result := engine.Tail(stdoutBuffer, 1) - logrus.Debugf("Key check result: %q", result) - if result == "verified" { - verified = true + // Check key has read/write permission (0x03) + v, err := s.trustService.CheckKey(namespace, b, 0x03) + if err != nil { + vErr, ok := err.(trust.NotVerifiedError) + if !ok { + return nil, false, fmt.Errorf("error running key check: %s", err) + } + logrus.Debugf("Key check result: %v", vErr) + } + verified = v + if verified { + logrus.Debug("Key check result: verified") } } - return &manifest, verified, nil } diff --git a/graph/pull.go b/graph/pull.go index fa22d335d..edc67df9d 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -70,10 +70,7 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf if len(repoInfo.Index.Mirrors) == 0 && (repoInfo.Index.Official || endpoint.Version == registry.APIVersion2) { if repoInfo.Official { - j := eng.Job("trust_update_base") - if err = j.Run(); err != nil { - logrus.Errorf("error updating trust base graph: %s", err) - } + s.trustService.UpdateBase() } logrus.Debugf("pulling v2 repository with local name %q", repoInfo.LocalName) diff --git a/graph/tags.go b/graph/tags.go index 444e74f72..39f0ffc29 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -18,6 +18,7 @@ import ( "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/registry" + "github.com/docker/docker/trust" "github.com/docker/docker/utils" "github.com/docker/libtrust" ) @@ -42,6 +43,7 @@ type TagStore struct { pushingPool map[string]chan struct{} registryService *registry.Service eventsService *events.Events + trustService *trust.TrustStore } type Repository map[string]string @@ -64,7 +66,15 @@ func (r Repository) Contains(u Repository) bool { return true } -func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey, registryService *registry.Service, eventsService *events.Events) (*TagStore, error) { +type TagStoreConfig struct { + Graph *Graph + Key libtrust.PrivateKey + Registry *registry.Service + Events *events.Events + Trust *trust.TrustStore +} + +func NewTagStore(path string, cfg *TagStoreConfig) (*TagStore, error) { abspath, err := filepath.Abs(path) if err != nil { return nil, err @@ -72,13 +82,14 @@ func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey, registrySer store := &TagStore{ path: abspath, - graph: graph, - trustKey: key, + graph: cfg.Graph, + trustKey: cfg.Key, Repositories: make(map[string]Repository), pullingPool: make(map[string]chan struct{}), pushingPool: make(map[string]chan struct{}), - registryService: registryService, - eventsService: eventsService, + registryService: cfg.Registry, + eventsService: cfg.Events, + trustService: cfg.Trust, } // Load the json file if it exists, otherwise create it. if err := store.reload(); os.IsNotExist(err) { diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go index 4a4ddbe4b..0482fa58e 100644 --- a/graph/tags_unit_test.go +++ b/graph/tags_unit_test.go @@ -60,7 +60,11 @@ func mkTestTagStore(root string, t *testing.T) *TagStore { if err != nil { t.Fatal(err) } - store, err := NewTagStore(path.Join(root, "tags"), graph, nil, nil, events.New()) + tagCfg := &TagStoreConfig{ + Graph: graph, + Events: events.New(), + } + store, err := NewTagStore(path.Join(root, "tags"), tagCfg) if err != nil { t.Fatal(err) } diff --git a/trust/service.go b/trust/service.go index 12b964566..6a804faf5 100644 --- a/trust/service.go +++ b/trust/service.go @@ -5,70 +5,49 @@ import ( "time" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" "github.com/docker/libtrust" ) -func (t *TrustStore) Install(eng *engine.Engine) error { - for name, handler := range map[string]engine.Handler{ - "trust_key_check": t.CmdCheckKey, - "trust_update_base": t.CmdUpdateBase, - } { - if err := eng.Register(name, handler); err != nil { - return fmt.Errorf("Could not register %q: %v", name, err) - } - } - return nil +type NotVerifiedError string + +func (e NotVerifiedError) Error() string { + return string(e) } -func (t *TrustStore) CmdCheckKey(job *engine.Job) error { - if n := len(job.Args); n != 1 { - return fmt.Errorf("Usage: %s NAMESPACE", job.Name) +func (t *TrustStore) CheckKey(ns string, key []byte, perm uint16) (bool, error) { + if len(key) == 0 { + return false, fmt.Errorf("Missing PublicKey") } - var ( - namespace = job.Args[0] - keyBytes = job.Getenv("PublicKey") - ) - - if keyBytes == "" { - return fmt.Errorf("Missing PublicKey") - } - pk, err := libtrust.UnmarshalPublicKeyJWK([]byte(keyBytes)) + pk, err := libtrust.UnmarshalPublicKeyJWK(key) if err != nil { - return fmt.Errorf("Error unmarshalling public key: %s", err) + return false, fmt.Errorf("Error unmarshalling public key: %v", err) } - permission := uint16(job.GetenvInt("Permission")) - if permission == 0 { - permission = 0x03 + if perm == 0 { + perm = 0x03 } t.RLock() defer t.RUnlock() if t.graph == nil { - job.Stdout.Write([]byte("no graph")) - return nil + return false, NotVerifiedError("no graph") } // Check if any expired grants - verified, err := t.graph.Verify(pk, namespace, permission) + verified, err := t.graph.Verify(pk, ns, perm) if err != nil { - return fmt.Errorf("Error verifying key to namespace: %s", namespace) + return false, fmt.Errorf("Error verifying key to namespace: %s", ns) } if !verified { - logrus.Debugf("Verification failed for %s using key %s", namespace, pk.KeyID()) - job.Stdout.Write([]byte("not verified")) - } else if t.expiration.Before(time.Now()) { - job.Stdout.Write([]byte("expired")) - } else { - job.Stdout.Write([]byte("verified")) + logrus.Debugf("Verification failed for %s using key %s", ns, pk.KeyID()) + return false, NotVerifiedError("not verified") } - - return nil + if t.expiration.Before(time.Now()) { + return false, NotVerifiedError("expired") + } + return true, nil } -func (t *TrustStore) CmdUpdateBase(job *engine.Job) error { +func (t *TrustStore) UpdateBase() { t.fetch() - - return nil } From 18c9b6c6455f116ae59cde8544413b3d7d294a5e Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 1 Apr 2015 15:39:37 -0700 Subject: [PATCH 532/999] Add .docker/config.json and support for HTTP Headers This PR does the following: - migrated ~/.dockerfg to ~/.docker/config.json. The data is migrated but the old file remains in case its needed - moves the auth json in that fie into an "auth" property so we can add new top-level properties w/o messing with the auth stuff - adds support for an HttpHeaders property in ~/.docker/config.json which adds these http headers to all msgs from the cli In a follow-on PR I'll move the config file process out from under "registry" since it not specific to that any more. I didn't do it here because I wanted the diff to be smaller so people can make sure I didn't break/miss any auth code during my edits. Signed-off-by: Doug Davis --- api/client/build.go | 4 +- api/client/cli.go | 15 ++- api/client/create.go | 3 - api/client/hijack.go | 7 ++ api/client/info.go | 3 +- api/client/login.go | 24 ++-- api/client/logout.go | 7 +- api/client/pull.go | 2 - api/client/push.go | 2 - api/client/search.go | 2 - api/client/utils.go | 9 +- builder/evaluator.go | 4 +- builder/internals.go | 4 +- builder/job.go | 2 +- docs/sources/reference/commandline/cli.md | 29 +++++ integration-cli/docker_cli_config_test.go | 58 ++++++++++ registry/auth.go | 105 ++++++++++++----- registry/auth_test.go | 26 +++-- registry/config_file_test.go | 135 ++++++++++++++++++++++ 19 files changed, 360 insertions(+), 81 deletions(-) create mode 100644 integration-cli/docker_cli_config_test.go create mode 100644 registry/config_file_test.go diff --git a/api/client/build.go b/api/client/build.go index 788319edf..63cc63bc9 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -286,10 +286,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v.Set("dockerfile", *dockerfileName) - cli.LoadConfigFile() - headers := http.Header(make(map[string][]string)) - buf, err := json.Marshal(cli.configFile) + buf, err := json.Marshal(cli.configFile.AuthConfigs) if err != nil { return err } diff --git a/api/client/cli.go b/api/client/cli.go index dfa5fe520..3146b17b3 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "os" + "path/filepath" "reflect" "strings" "text/template" @@ -120,14 +121,6 @@ func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bo return flags } -func (cli *DockerCli) LoadConfigFile() (err error) { - cli.configFile, err = registry.LoadConfig(homedir.Get()) - if err != nil { - fmt.Fprintf(cli.err, "WARNING: %s\n", err) - } - return err -} - func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { // In order to attach to a container tty, input stream for the client must // be a tty itself: redirecting or piping the client standard input is @@ -184,9 +177,15 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a tr.Dial = (&net.Dialer{Timeout: timeout}).Dial } + configFile, e := registry.LoadConfig(filepath.Join(homedir.Get(), ".docker")) + if e != nil { + fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) + } + return &DockerCli{ proto: proto, addr: addr, + configFile: configFile, in: in, out: out, err: err, diff --git a/api/client/create.go b/api/client/create.go index bb84d5e46..d2987a67e 100644 --- a/api/client/create.go +++ b/api/client/create.go @@ -37,9 +37,6 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error { return err } - // Load the auth config file, to be able to pull the image - cli.LoadConfigFile() - // Resolve the Auth config relevant for this server authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) buf, err := json.Marshal(authConfig) diff --git a/api/client/hijack.go b/api/client/hijack.go index 163538416..5f4794a5e 100644 --- a/api/client/hijack.go +++ b/api/client/hijack.go @@ -142,6 +142,13 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea if err != nil { return err } + + // Add CLI Config's HTTP Headers BEFORE we set the Docker headers + // then the user can't change OUR headers + for k, v := range cli.configFile.HttpHeaders { + req.Header.Set(k, v) + } + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.Header.Set("Content-Type", "text/plain") req.Header.Set("Connection", "Upgrade") diff --git a/api/client/info.go b/api/client/info.go index 655788882..432ccac40 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -68,8 +68,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { } if info.IndexServerAddress != "" { - cli.LoadConfigFile() - u := cli.configFile.Configs[info.IndexServerAddress].Username + u := cli.configFile.AuthConfigs[info.IndexServerAddress].Username if len(u) > 0 { fmt.Fprintf(cli.out, "Username: %v\n", u) fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress) diff --git a/api/client/login.go b/api/client/login.go index b24ef7df7..e8e87fc5e 100644 --- a/api/client/login.go +++ b/api/client/login.go @@ -6,11 +6,9 @@ import ( "fmt" "io" "os" - "path" "strings" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" "github.com/docker/docker/registry" @@ -56,8 +54,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { return string(line) } - cli.LoadConfigFile() - authconfig, ok := cli.configFile.Configs[serverAddress] + authconfig, ok := cli.configFile.AuthConfigs[serverAddress] if !ok { authconfig = registry.AuthConfig{} } @@ -113,12 +110,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig.Password = password authconfig.Email = email authconfig.ServerAddress = serverAddress - cli.configFile.Configs[serverAddress] = authconfig + cli.configFile.AuthConfigs[serverAddress] = authconfig - stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], nil) + stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.AuthConfigs[serverAddress], nil) if statusCode == 401 { - delete(cli.configFile.Configs, serverAddress) - registry.SaveConfig(cli.configFile) + delete(cli.configFile.AuthConfigs, serverAddress) + if err2 := cli.configFile.Save(); err2 != nil { + fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2) + } return err } if err != nil { @@ -127,12 +126,15 @@ func (cli *DockerCli) CmdLogin(args ...string) error { var response types.AuthResponse if err := json.NewDecoder(stream).Decode(&response); err != nil { - cli.configFile, _ = registry.LoadConfig(homedir.Get()) + // Upon error, remove entry + delete(cli.configFile.AuthConfigs, serverAddress) return err } - registry.SaveConfig(cli.configFile) - fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s.\n", path.Join(homedir.Get(), registry.CONFIGFILE)) + if err := cli.configFile.Save(); err != nil { + return fmt.Errorf("Error saving config file: %v", err) + } + fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s\n", cli.configFile.Filename()) if response.Status != "" { fmt.Fprintf(cli.out, "%s\n", response.Status) diff --git a/api/client/logout.go b/api/client/logout.go index 9282f22f0..74d0c278f 100644 --- a/api/client/logout.go +++ b/api/client/logout.go @@ -22,14 +22,13 @@ func (cli *DockerCli) CmdLogout(args ...string) error { serverAddress = cmd.Arg(0) } - cli.LoadConfigFile() - if _, ok := cli.configFile.Configs[serverAddress]; !ok { + if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok { fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress) } else { fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress) - delete(cli.configFile.Configs, serverAddress) + delete(cli.configFile.AuthConfigs, serverAddress) - if err := registry.SaveConfig(cli.configFile); err != nil { + if err := cli.configFile.Save(); err != nil { return fmt.Errorf("Failed to save docker config: %v", err) } } diff --git a/api/client/pull.go b/api/client/pull.go index a554e1f45..17abe4bb6 100644 --- a/api/client/pull.go +++ b/api/client/pull.go @@ -42,8 +42,6 @@ func (cli *DockerCli) CmdPull(args ...string) error { return err } - cli.LoadConfigFile() - _, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull") return err } diff --git a/api/client/push.go b/api/client/push.go index a31a04ed4..d4fc4c5c9 100644 --- a/api/client/push.go +++ b/api/client/push.go @@ -20,8 +20,6 @@ func (cli *DockerCli) CmdPush(args ...string) error { name := cmd.Arg(0) - cli.LoadConfigFile() - remote, tag := parsers.ParseRepositoryTag(name) // Resolve the Repository name from fqn to RepositoryInfo diff --git a/api/client/search.go b/api/client/search.go index 5e0a22f01..2cff7708d 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -44,8 +44,6 @@ func (cli *DockerCli) CmdSearch(args ...string) error { return err } - cli.LoadConfigFile() - rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search") if err != nil { return err diff --git a/api/client/utils.go b/api/client/utils.go index cf11fefe5..026593d00 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -65,6 +65,13 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m if err != nil { return nil, "", -1, err } + + // Add CLI Config's HTTP Headers BEFORE we set the Docker headers + // then the user can't change OUR headers + for k, v := range cli.configFile.HttpHeaders { + req.Header.Set(k, v) + } + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.URL.Host = cli.addr req.URL.Scheme = cli.scheme @@ -299,7 +306,7 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { sigchan := make(chan os.Signal, 1) gosignal.Notify(sigchan, signal.SIGWINCH) go func() { - for _ = range sigchan { + for range sigchan { cli.resizeTty(id, isExec) } }() diff --git a/builder/evaluator.go b/builder/evaluator.go index c159e51bf..0eba4a6eb 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -101,8 +101,8 @@ type Builder struct { // the final configs of the Dockerfile but dont want the layers disableCommit bool - AuthConfig *registry.AuthConfig - AuthConfigFile *registry.ConfigFile + AuthConfig *registry.AuthConfig + ConfigFile *registry.ConfigFile // Deprecated, original writer used for ImagePull. To be removed. OutOld io.Writer diff --git a/builder/internals.go b/builder/internals.go index bf47714ee..c15cad4e5 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -437,13 +437,13 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { } pullRegistryAuth := b.AuthConfig - if len(b.AuthConfigFile.Configs) > 0 { + if len(b.ConfigFile.AuthConfigs) > 0 { // The request came with a full auth config file, we prefer to use that repoInfo, err := b.Daemon.RegistryService.ResolveRepository(remote) if err != nil { return nil, err } - resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(repoInfo.Index) + resolvedAuth := b.ConfigFile.ResolveAuthConfig(repoInfo.Index) pullRegistryAuth = &resolvedAuth } diff --git a/builder/job.go b/builder/job.go index 60f907119..930d7b7f1 100644 --- a/builder/job.go +++ b/builder/job.go @@ -150,7 +150,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { OutOld: job.Stdout, StreamFormatter: sf, AuthConfig: authConfig, - AuthConfigFile: configFile, + ConfigFile: configFile, dockerfileName: dockerfileName, cpuShares: cpuShares, cpuSetCpus: cpuSetCpus, diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 607f67076..821de99e3 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -48,6 +48,35 @@ These Go environment variables are case-insensitive. See the [Go specification](http://golang.org/pkg/net/http/) for details on these variables. +## Configuration Files + +The Docker command line stores its configuration files in a directory called +`.docker` within your `HOME` directory. Docker manages most of the files in +`.docker` and you should not modify them. However, you *can modify* the +`.docker/config.json` file to control certain aspects of how the `docker` +command behaves. + +Currently, you can modify the `docker` command behavior using environment +variables or command-line options. You can also use options within +`config.json` to modify some of the same behavior. When using these +mechanisms, you must keep in mind the order of precedence among them. Command +line options override environment variables and environment variables override +properties you specify in a `config.json` file. + +The `config.json` file stores a JSON encoding of a single `HttpHeaders` +property. The property specifies a set of headers to include in all +messages sent from the Docker client to the daemon. Docker does not try to +interpret or understand these header; it simply puts them into the messages. +Docker does not allow these headers to change any headers it sets for itself. + +Following is a sample `config.json` file: + + { + "HttpHeaders: { + "MyHeader": "MyValue" + } + } + ## Help To list the help on any command just execute the command, followed by the `--help` option. diff --git a/integration-cli/docker_cli_config_test.go b/integration-cli/docker_cli_config_test.go new file mode 100644 index 000000000..23ad70069 --- /dev/null +++ b/integration-cli/docker_cli_config_test.go @@ -0,0 +1,58 @@ +package main + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/docker/docker/pkg/homedir" +) + +func TestConfigHttpHeader(t *testing.T) { + testRequires(t, UnixCli) // Can't set/unset HOME on windows right now + // We either need a level of Go that supports Unsetenv (for cases + // when HOME/USERPROFILE isn't set), or we need to be able to use + // os/user but user.Current() only works if we aren't statically compiling + + var headers map[string][]string + + server := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + headers = r.Header + })) + defer server.Close() + + homeKey := homedir.Key() + homeVal := homedir.Get() + tmpDir, _ := ioutil.TempDir("", "fake-home") + defer os.RemoveAll(tmpDir) + + dotDocker := filepath.Join(tmpDir, ".docker") + os.Mkdir(dotDocker, 0600) + tmpCfg := filepath.Join(dotDocker, "config.json") + + defer func() { os.Setenv(homeKey, homeVal) }() + os.Setenv(homeKey, tmpDir) + + data := `{ + "HttpHeaders": { "MyHeader": "MyValue" } + }` + + err := ioutil.WriteFile(tmpCfg, []byte(data), 0600) + if err != nil { + t.Fatalf("Err creating file(%s): %v", tmpCfg, err) + } + + cmd := exec.Command(dockerBinary, "-H="+server.URL[7:], "ps") + out, _, _ := runCommandWithOutput(cmd) + + if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" { + t.Fatalf("Missing/bad header: %q\nout:%v", headers, out) + } + + logDone("config - add new http headers") +} diff --git a/registry/auth.go b/registry/auth.go index 51b781dd9..bccf58fc5 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -8,24 +8,27 @@ import ( "io/ioutil" "net/http" "os" - "path" + "path/filepath" "strings" "sync" "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/homedir" "github.com/docker/docker/pkg/requestdecorator" ) const ( // Where we store the config file - CONFIGFILE = ".dockercfg" + CONFIGFILE = "config.json" + OLD_CONFIGFILE = ".dockercfg" ) var ( ErrConfigFileMissing = errors.New("The Auth config file is missing") ) +// Registry Auth Info type AuthConfig struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` @@ -34,9 +37,11 @@ type AuthConfig struct { ServerAddress string `json:"serveraddress,omitempty"` } +// ~/.docker/config.json file info type ConfigFile struct { - Configs map[string]AuthConfig `json:"configs,omitempty"` - rootPath string + AuthConfigs map[string]AuthConfig `json:"auths"` + HttpHeaders map[string]string `json:"HttpHeaders,omitempty"` + filename string // Note: not serialized - for internal use only } type RequestAuthorization struct { @@ -147,18 +152,58 @@ func decodeAuth(authStr string) (string, string, error) { // load up the auth config information and return values // FIXME: use the internal golang config parser -func LoadConfig(rootPath string) (*ConfigFile, error) { - configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath} - confFile := path.Join(rootPath, CONFIGFILE) +func LoadConfig(configDir string) (*ConfigFile, error) { + if configDir == "" { + configDir = filepath.Join(homedir.Get(), ".docker") + } + + configFile := ConfigFile{ + AuthConfigs: make(map[string]AuthConfig), + filename: filepath.Join(configDir, CONFIGFILE), + } + + // Try happy path first - latest config file + if _, err := os.Stat(configFile.filename); err == nil { + file, err := os.Open(configFile.filename) + if err != nil { + return &configFile, err + } + defer file.Close() + + if err := json.NewDecoder(file).Decode(&configFile); err != nil { + return &configFile, err + } + + for addr, ac := range configFile.AuthConfigs { + ac.Username, ac.Password, err = decodeAuth(ac.Auth) + if err != nil { + return &configFile, err + } + ac.Auth = "" + ac.ServerAddress = addr + configFile.AuthConfigs[addr] = ac + } + + return &configFile, nil + } else if !os.IsNotExist(err) { + // if file is there but we can't stat it for any reason other + // than it doesn't exist then stop + return &configFile, err + } + + // Can't find latest config file so check for the old one + confFile := filepath.Join(homedir.Get(), OLD_CONFIGFILE) + if _, err := os.Stat(confFile); err != nil { return &configFile, nil //missing file is not an error } + b, err := ioutil.ReadFile(confFile) if err != nil { return &configFile, err } - if err := json.Unmarshal(b, &configFile.Configs); err != nil { + if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil { arr := strings.Split(string(b), "\n") if len(arr) < 2 { return &configFile, fmt.Errorf("The Auth config file is empty") @@ -179,48 +224,52 @@ func LoadConfig(rootPath string) (*ConfigFile, error) { authConfig.Email = origEmail[1] authConfig.ServerAddress = IndexServerAddress() // *TODO: Switch to using IndexServerName() instead? - configFile.Configs[IndexServerAddress()] = authConfig + configFile.AuthConfigs[IndexServerAddress()] = authConfig } else { - for k, authConfig := range configFile.Configs { + for k, authConfig := range configFile.AuthConfigs { authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) if err != nil { return &configFile, err } authConfig.Auth = "" authConfig.ServerAddress = k - configFile.Configs[k] = authConfig + configFile.AuthConfigs[k] = authConfig } } return &configFile, nil } -// save the auth config -func SaveConfig(configFile *ConfigFile) error { - confFile := path.Join(configFile.rootPath, CONFIGFILE) - if len(configFile.Configs) == 0 { - os.Remove(confFile) - return nil - } - - configs := make(map[string]AuthConfig, len(configFile.Configs)) - for k, authConfig := range configFile.Configs { +func (configFile *ConfigFile) Save() error { + // Encode sensitive data into a new/temp struct + tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs)) + for k, authConfig := range configFile.AuthConfigs { authCopy := authConfig authCopy.Auth = encodeAuth(&authCopy) authCopy.Username = "" authCopy.Password = "" authCopy.ServerAddress = "" - configs[k] = authCopy + tmpAuthConfigs[k] = authCopy } - b, err := json.MarshalIndent(configs, "", "\t") + saveAuthConfigs := configFile.AuthConfigs + configFile.AuthConfigs = tmpAuthConfigs + defer func() { configFile.AuthConfigs = saveAuthConfigs }() + + data, err := json.MarshalIndent(configFile, "", "\t") if err != nil { return err } - err = ioutil.WriteFile(confFile, b, 0600) + + if err := os.MkdirAll(filepath.Dir(configFile.filename), 0600); err != nil { + return err + } + + err = ioutil.WriteFile(configFile.filename, data, 0600) if err != nil { return err } + return nil } @@ -431,7 +480,7 @@ func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, regis func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { configKey := index.GetAuthConfigKey() // First try the happy case - if c, found := config.Configs[configKey]; found || index.Official { + if c, found := config.AuthConfigs[configKey]; found || index.Official { return c } @@ -450,7 +499,7 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { // Maybe they have a legacy config file, we will iterate the keys converting // them to the new format and testing - for registry, config := range config.Configs { + for registry, config := range config.AuthConfigs { if configKey == convertToHostname(registry) { return config } @@ -459,3 +508,7 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { // When all else fails, return an empty auth config return AuthConfig{} } + +func (config *ConfigFile) Filename() string { + return config.filename +} diff --git a/registry/auth_test.go b/registry/auth_test.go index 9cc299aab..b07aa7dbc 100644 --- a/registry/auth_test.go +++ b/registry/auth_test.go @@ -3,6 +3,7 @@ package registry import ( "io/ioutil" "os" + "path/filepath" "testing" ) @@ -31,13 +32,14 @@ func setupTempConfigFile() (*ConfigFile, error) { if err != nil { return nil, err } + root = filepath.Join(root, CONFIGFILE) configFile := &ConfigFile{ - rootPath: root, - Configs: make(map[string]AuthConfig), + AuthConfigs: make(map[string]AuthConfig), + filename: root, } for _, registry := range []string{"testIndex", IndexServerAddress()} { - configFile.Configs[registry] = AuthConfig{ + configFile.AuthConfigs[registry] = AuthConfig{ Username: "docker-user", Password: "docker-pass", Email: "docker@docker.io", @@ -52,14 +54,14 @@ func TestSameAuthDataPostSave(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.rootPath) + defer os.RemoveAll(configFile.filename) - err = SaveConfig(configFile) + err = configFile.Save() if err != nil { t.Fatal(err) } - authConfig := configFile.Configs["testIndex"] + authConfig := configFile.AuthConfigs["testIndex"] if authConfig.Username != "docker-user" { t.Fail() } @@ -79,9 +81,9 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.rootPath) + defer os.RemoveAll(configFile.filename) - indexConfig := configFile.Configs[IndexServerAddress()] + indexConfig := configFile.AuthConfigs[IndexServerAddress()] officialIndex := &IndexInfo{ Official: true, @@ -102,7 +104,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.rootPath) + defer os.RemoveAll(configFile.filename) registryAuth := AuthConfig{ Username: "foo-user", @@ -119,7 +121,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) { Password: "baz-pass", Email: "baz@example.com", } - configFile.Configs[IndexServerAddress()] = officialAuth + configFile.AuthConfigs[IndexServerAddress()] = officialAuth expectedAuths := map[string]AuthConfig{ "registry.example.com": registryAuth, @@ -157,12 +159,12 @@ func TestResolveAuthConfigFullURL(t *testing.T) { Name: configKey, } for _, registry := range registries { - configFile.Configs[registry] = configured + configFile.AuthConfigs[registry] = configured resolved := configFile.ResolveAuthConfig(index) if resolved.Email != configured.Email { t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email) } - delete(configFile.Configs, registry) + delete(configFile.AuthConfigs, registry) resolved = configFile.ResolveAuthConfig(index) if resolved.Email == configured.Email { t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email) diff --git a/registry/config_file_test.go b/registry/config_file_test.go new file mode 100644 index 000000000..9abb8ee95 --- /dev/null +++ b/registry/config_file_test.go @@ -0,0 +1,135 @@ +package registry + +import ( + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/docker/docker/pkg/homedir" +) + +func TestMissingFile(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on missing file: %q", err) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} + +func TestEmptyFile(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + fn := filepath.Join(tmpHome, CONFIGFILE) + ioutil.WriteFile(fn, []byte(""), 0600) + + _, err := LoadConfig(tmpHome) + if err == nil { + t.Fatalf("Was supposed to fail") + } +} + +func TestEmptyJson(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + fn := filepath.Join(tmpHome, CONFIGFILE) + ioutil.WriteFile(fn, []byte("{}"), 0600) + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on empty json file: %q", err) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} + +func TestOldJson(t *testing.T) { + if runtime.GOOS == "windows" { + return + } + + tmpHome, _ := ioutil.TempDir("", "config-test") + defer os.RemoveAll(tmpHome) + + homeKey := homedir.Key() + homeVal := homedir.Get() + + defer func() { os.Setenv(homeKey, homeVal) }() + os.Setenv(homeKey, tmpHome) + + fn := filepath.Join(tmpHome, OLD_CONFIGFILE) + js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}` + ioutil.WriteFile(fn, []byte(js), 0600) + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on empty json file: %q", err) + } + + ac := config.AuthConfigs["https://index.docker.io/v1/"] + if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" { + t.Fatalf("Missing data from parsing:\n%q", config) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) || + !strings.Contains(string(buf), "user@example.com") { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} + +func TestNewJson(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + fn := filepath.Join(tmpHome, CONFIGFILE) + js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }` + ioutil.WriteFile(fn, []byte(js), 0600) + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on empty json file: %q", err) + } + + ac := config.AuthConfigs["https://index.docker.io/v1/"] + if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" { + t.Fatalf("Missing data from parsing:\n%q", config) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) || + !strings.Contains(string(buf), "user@example.com") { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} From ff9f0134aefbee1d3d757454f42e8dd99afb528e Mon Sep 17 00:00:00 2001 From: Kent Johnson Date: Mon, 20 Apr 2015 13:46:55 -0600 Subject: [PATCH 533/999] Clarify data volume init with existing data Though I am not clear on the intent of the sentence if it means that existing data in the base image is copied into the new volume then the additions I propose make that more clear than the present language. Signed-off-by: Kent Johnson --- docs/sources/userguide/dockervolumes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/userguide/dockervolumes.md b/docs/sources/userguide/dockervolumes.md index c319ecee5..e80483fc2 100644 --- a/docs/sources/userguide/dockervolumes.md +++ b/docs/sources/userguide/dockervolumes.md @@ -25,8 +25,8 @@ System*](/terms/layer/#union-file-system). Data volumes provide several useful features for persistent or shared data: - Volumes are initialized when a container is created. If the container's - base image contains data at the specified mount point, that data is - copied into the new volume. + base image contains data at the specified mount point, that existing data is + copied into the new volume upon volume initialization. - Data volumes can be shared and reused among containers. - Changes to a data volume are made directly. - Changes to a data volume will not be included when you update an image. From 1f4ef5192c05723e231acc47813c4c8455a0166b Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Mon, 20 Apr 2015 11:07:01 -0700 Subject: [PATCH 534/999] Adding in 1.60 release notes Updating with thaJetzah's comments Adding in Fred's copy edits Signed-off-by: Mary Anthony --- docs/sources/release-notes.md | 174 ++++++++++++++++++++++------------ 1 file changed, 116 insertions(+), 58 deletions(-) diff --git a/docs/sources/release-notes.md b/docs/sources/release-notes.md index fe79d881d..87e5c4197 100644 --- a/docs/sources/release-notes.md +++ b/docs/sources/release-notes.md @@ -2,74 +2,132 @@ page_title: Docker 1.x Series Release Notes page_description: Release Notes for Docker 1.x. page_keywords: docker, documentation, about, technology, understanding, release -# Release Notes +# Release Notes Version 1.6.0 +(2015-04-16) You can view release notes for earlier version of Docker by selecting the -desired version from the drop-down list at the top right of this page. +desired version from the drop-down list at the top right of this page. For the +formal release announcement, see [the Docker +blog](https://blog.docker.com/2015/04/docker-release-1-6/). -## Version 1.5.0 -(2015-02-03) -For a complete list of patches, fixes, and other improvements, see the -[merge PR on GitHub](https://github.com/docker/docker/pull/10286). -*New Features* +## Docker Engine 1.6.0 Features -* [1.6] The Docker daemon will no longer ignore unknown commands - while processing a `Dockerfile`. Instead it will generate an error and halt - processing. -* The Docker daemon has now supports for IPv6 networking between containers - and on the `docker0` bridge. For more information see the - [IPv6 networking reference](/articles/networking/#ipv6). -* Docker container filesystems can now be set to`--read-only`, restricting your - container to writing to volumes [PR# 10093](https://github.com/docker/docker/pull/10093). -* A new `docker stats CONTAINERID` command has been added to allow users to view a - continuously updating stream of container resource usage statistics. See the - [`stats` command line reference](/reference/commandline/cli/#stats) and the - [container `stats` API reference](/reference/api/docker_remote_api_v1.17/#get-container-stats-based-on-resource-usage). - **Note**: this feature is only enabled for the `libcontainer` exec-driver at this point. -* Users can now specify the file to use as the `Dockerfile` by running - `docker build -f alternate.dockerfile .`. This will allow the definition of multiple - `Dockerfile`s for a single project. See the [`docker build` command reference]( -/reference/commandline/cli/#build) for more information. -* The v1 Open Image specification has been created to document the current Docker image - format and metadata. Please see [the Open Image specification document]( -https://github.com/docker/docker/blob/master/image/spec/v1.md) for more details. -* This release also includes a number of significant performance improvements in - build and image management ([PR #9720](https://github.com/docker/docker/pull/9720), - [PR #8827](https://github.com/docker/docker/pull/8827)) -* The `docker inspect` command now lists ExecIDs generated for each `docker exec` process. - See [PR #9800](https://github.com/docker/docker/pull/9800)) for more details. -* The `docker inspect` command now shows the number of container restarts when there - is a restart policy ([PR #9621](https://github.com/docker/docker/pull/9621)) -* This version of Docker is built using Go 1.4 +For a complete list of engine patches, fixes, and other improvements, see the +[merge PR on GitHub](https://github.com/docker/docker/pull/11635). You'll also +find [a changelog in the project +repository](https://github.com/docker/docker/blob/master/CHANGELOG.md). -> **Note:** -> Development history prior to version 1.0 can be found by -> searching in the [Docker GitHub repo](https://github.com/docker/docker). -## Known Issues +| Feature | Description | +|------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Container and Image Labels | Labels allow you to attach user-defined metadata to containers and images that can be used by your tools. For additional information on using labels, see [Apply custom metadata](http://docs.docker.com/userguide/labels-custom-metadata/#add-labels-to-images-the-label-instruction) in the documentation. | +| Windows Client preview | The Windows Client can be used just like the Mac OS X client is today with a remote host. Our testing infrastructure was scaled out to accommodate Windows Client testing on every PR to the Engine. See the Azure blog for [details on using this new client](http://azure.microsoft.com/blog/2015/04/16/docker-client-for-windows-is-now-available). | +| Logging drivers | The new logging driver follows the exec driver and storage driver concepts already available in Engine today. There is a new option `--log-driver` to `docker run` command. See the `run` reference for a [description on how to use this option](http://docs.docker.com/reference/run/#logging-drivers-log-driver). | +| Image digests | When you pull, build, or run images, you specify them in the form `namespace/repository:tag`, or even just `repository`. In this release, you are now able to pull, run, build and refer to images by a new content addressable identifier called a “digest” with the syntax `namespace/repo@digest`. See the the command line reference for [examples of using the digest](http://docs.docker.com/reference/commandline/cli/#listing-image-digests). | +| Custom cgroups | Containers are made from a combination of namespaces, capabilities, and cgroups. Docker already supports custom namespaces and capabilities. Additionally, in this release we’ve added support for custom cgroups. Using the `--cgroup-parent` flag, you can pass a specific `cgroup` to run a container in. See [the command line reference for more information](http://docs.docker.com/reference/commandline/cli/#create). | +| Ulimits | You can now specify the default `ulimit` settings for all containers when configuring the daemon. For example:`docker -d --default-ulimit nproc=1024:2048` See [Default Ulimits](http://docs.docker.com/reference/commandline/cli/#default-ulimits) in this documentation. | +| Commit and import Dockerfile | You can now make changes to images on the fly without having to re-build the entire image. The feature `commit --change` and `import --change` allows you to apply standard changes to a new image. These are expressed in the Dockerfile syntax and used to modify the image. For details on how to use these, see the [commit](http://docs.docker.com/reference/commandline/cli/#commit) and [import](http://docs.docker.com/reference/commandline/cli/#import). | -This section lists significant known issues present in Docker as of release -date. It is not exhaustive; it lists only issues with potentially significant -impact on users. This list will be updated as issues are resolved. +### Known Issues in Engine -* **Unexpected File Permissions in Containers** -An idiosyncrasy in AUFS prevents permissions from propagating predictably -between upper and lower layers. This can cause issues with accessing private -keys, database instances, etc. +This section lists significant known issues present in Docker as of release date. +For an exhaustive list of issues, see [the issues list on the project +repository](https://github.com/docker/docker/issues/). -For systems that have recent aufs version (i.e., `dirperm1` mount option can -be set), docker will attempt to fix the issue automatically by mounting -the layers with `dirperm1` option. More details on `dirperm1` option can be -found at [`aufs` man page](http://aufs.sourceforge.net/aufs3/man.html) - -For complete information and workarounds see +* *Unexpected File Permissions in Containers* +An idiosyncrasy in AUFS prevented permissions from propagating predictably +between upper and lower layers. This caused issues with accessing private +keys, database instances, etc. This issue was closed in this release: [Github Issue 783](https://github.com/docker/docker/issues/783). -* **Docker Hub incompatible with Safari 8** -Docker Hub has multiple issues displaying on Safari 8, the default browser -for OS X 10.10 (Yosemite). Users should access the hub using a different -browser. Most notably, changes in the way Safari handles cookies means that the -user is repeatedly logged out. For more information, see the [Docker -forum post](https://forums.docker.com/t/new-safari-in-yosemite-issue/300). + +* *Docker Hub incompatible with Safari 8* +Docker Hub had multiple issues displaying on Safari 8, the default browser for +OS X 10.10 (Yosemite). Most notably, changes in the way Safari handled cookies +means that the user was repeatedly logged out. +Recently, Safari fixed the bug that was causing all the issues. If you upgrade +to Safari 8.0.5 which was just released last week and see if that fixes your +issues. You might have to flush your cookies if it doesn't work right away. +For more information, see the [Docker forum +post](https://forums.docker.com/t/new-safari-in-yosemite-issue/300). + +## Docker Registry 2.0 Features + +This release includes Registry 2.0. The Docker Registry is a central server for +pushing and pulling images. In this release, it was completely rewritten in Go +around a new set of distribution APIs + +- **Webhook notifications**: You can now configure the Registry to send Webhooks +when images are pushed. Spin off a CI build, send a notification to IRC – +whatever you want! Included in the documentation is a detailed [notification +specification](http://docs.docker.com/registry/notifications/). + +- **Native TLS support**: This release makes it easier to secure a registry with +TLS. This documentation includes [expanded examples of secure +deployments](http://docs.docker.com/registry/deploying/). + +- **New Distribution APIs**: This release includes an expanded set of new +distribution APIs. You can read the [detailed specification +here](http://docs.docker.com/registry/spec/api/). + + +## Docker Compose 1.2 + +For a complete list of compose patches, fixes, and other improvements, see the +[changelog in the project +repository](https://github.com/docker/compose/blob/master/CHANGES.md). The +project also makes a [set of release +notes](https://github.com/docker/compose/releases/tag/1.2.0) on the project. + +- **extends**: You can use `extends` to share configuration between services +with the keyword “extends”. With extends, you can refer to a service defined +elsewhere and include its configuration in a locally-defined service, while also +adding or overriding configuration as necessary. The documentation describes +[how to use extends in your +configuration](http://docs.docker.com/compose/extends/#extending-services-in- +compose). + +- **Relative directory handling may cause breaking change**: Compose now treats +directories passed to build, filenames passed to `env_file` and volume host +paths passed to volumes as relative to the configuration file's directory. +Previously, they were treated as relative to the directory where you were +running `docker-compose`. In the majority of cases, the location of the +configuration file and where you ran `docker-compose` were the same directory. +Now, you can use the `-f|--file` argument to specify a configuration file in +another directory. + + +## Docker Swarm 0.2 + +You'll find the [release for download on +GitHub](https://github.com/docker/swarm/releases/tag/v0.2.0) and [the +documentation here](http://docs.docker.com/swarm/). This release includes the +following features: + +- **Spread strategy**: A new strategy for scheduling containers on your cluster +which evenly spreads them over available nodes. +- **More Docker commands supported**: More progress has been made towards +supporting the complete Docker API, such as pulling and inspecting images. +- **Clustering drivers**: There are not any third-party drivers yet, but the +first steps have been made towards making a pluggable driver interface that will +make it possible to use Swarm with clustering systems such as Mesos. + + +## Docker Machine 0.2 Pre-release + +You'll find the [release for download on +GitHub](https://github.com/docker/machine/releases) and [the documentation +here](http://docs.docker.com/machine/). For a complete list of machine changes +see [the changelog in the project +repository](https://github.com/docker/machine/blob/master/CHANGES.md#020-2015-03 +-22). + +- **Cleaner driver interface**: It is now much easier to write drivers for providers. +- **More reliable and consistent provisioning**: Provisioning servers is now +handled centrally by Machine instead of letting each driver individually do it. +- **Regenerate TLS certificates**: A new command has been added to regenerate a +host’s TLS certificates for good security practice and for if a host’s IP +address changes. + From 9a2c00975123c17033ac7b50762af82051ec73db Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 16 Apr 2015 11:11:26 -0700 Subject: [PATCH 535/999] Remove engine.Job references from builder.CmdBuild Signed-off-by: David Calavera --- api/server/server.go | 70 ++++++++++++--------- builder/job.go | 144 ++++++++++++++++++++++++++----------------- 2 files changed, 131 insertions(+), 83 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 50dc84ff7..65e139167 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -23,6 +23,7 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/builder" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" @@ -236,12 +237,12 @@ func writeJSON(w http.ResponseWriter, code int, v interface{}) error { return json.NewEncoder(w).Encode(v) } -func streamJSON(job *engine.Job, w http.ResponseWriter, flush bool) { +func streamJSON(out *engine.Output, w http.ResponseWriter, flush bool) { w.Header().Set("Content-Type", "application/json") if flush { - job.Stdout.Add(utils.NewWriteFlusher(w)) + out.Add(utils.NewWriteFlusher(w)) } else { - job.Stdout.Add(w) + out.Add(w) } } @@ -857,7 +858,7 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h job.Setenv("tag", r.Form.Get("tag")) if version.GreaterThan("1.0") { job.SetenvBool("json", true) - streamJSON(job, w, true) + streamJSON(job.Stdout, w, true) } else { job.Stdout.Add(utils.NewWriteFlusher(w)) } @@ -1207,7 +1208,7 @@ func (s *Server) getContainersByName(eng *engine.Engine, version version.Version if version.LessThan("1.12") { job.SetenvBool("raw", true) } - streamJSON(job, w, false) + streamJSON(job.Stdout, w, false) return job.Run() } @@ -1232,7 +1233,7 @@ func (s *Server) getImagesByName(eng *engine.Engine, version version.Version, w if version.LessThan("1.12") { job.SetenvBool("raw", true) } - streamJSON(job, w, false) + streamJSON(job.Stdout, w, false) return job.Run() } @@ -1245,9 +1246,11 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R authConfig = ®istry.AuthConfig{} configFileEncoded = r.Header.Get("X-Registry-Config") configFile = ®istry.ConfigFile{} - job = eng.Job("build") + job = builder.NewBuildConfig(eng.Logging, eng.Stderr) ) + b := &builder.BuilderJob{eng, getDaemon(eng)} + // This block can be removed when API versions prior to 1.9 are deprecated. // Both headers will be parsed and sent along to the daemon, but if a non-empty // ConfigFile is present, any value provided as an AuthConfig directly will @@ -1271,36 +1274,38 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R } if version.GreaterThanOrEqualTo("1.8") { - job.SetenvBool("json", true) - streamJSON(job, w, true) + job.JSONFormat = true + streamJSON(job.Stdout, w, true) } else { job.Stdout.Add(utils.NewWriteFlusher(w)) } if toBool(r.FormValue("forcerm")) && version.GreaterThanOrEqualTo("1.12") { - job.Setenv("rm", "1") + job.Remove = true } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { - job.Setenv("rm", "1") + job.Remove = true } else { - job.Setenv("rm", r.FormValue("rm")) + job.Remove = toBool(r.FormValue("rm")) } if toBool(r.FormValue("pull")) && version.GreaterThanOrEqualTo("1.16") { - job.Setenv("pull", "1") + job.Pull = true } job.Stdin.Add(r.Body) - job.Setenv("remote", r.FormValue("remote")) - job.Setenv("dockerfile", r.FormValue("dockerfile")) - job.Setenv("t", r.FormValue("t")) - job.Setenv("q", r.FormValue("q")) - job.Setenv("nocache", r.FormValue("nocache")) - job.Setenv("forcerm", r.FormValue("forcerm")) - job.SetenvJson("authConfig", authConfig) - job.SetenvJson("configFile", configFile) - job.Setenv("memswap", r.FormValue("memswap")) - job.Setenv("memory", r.FormValue("memory")) - job.Setenv("cpusetcpus", r.FormValue("cpusetcpus")) - job.Setenv("cpusetmems", r.FormValue("cpusetmems")) - job.Setenv("cpushares", r.FormValue("cpushares")) + + // FIXME(calavera): !!!!! Remote might not be used. Solve the mistery before merging + //job.Setenv("remote", r.FormValue("remote")) + job.DockerfileName = r.FormValue("dockerfile") + job.RepoName = r.FormValue("t") + job.SuppressOutput = toBool(r.FormValue("q")) + job.NoCache = toBool(r.FormValue("nocache")) + job.ForceRemove = toBool(r.FormValue("forcerm")) + job.AuthConfig = authConfig + job.ConfigFile = configFile + job.MemorySwap = toInt64(r.FormValue("memswap")) + job.Memory = toInt64(r.FormValue("memory")) + job.CpuShares = toInt64(r.FormValue("cpushares")) + job.CpuSetCpus = r.FormValue("cpusetcpus") + job.CpuSetMems = r.FormValue("cpusetmems") // Job cancellation. Note: not all job types support this. if closeNotifier, ok := w.(http.CloseNotifier); ok { @@ -1310,13 +1315,13 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R select { case <-finished: case <-closeNotifier.CloseNotify(): - logrus.Infof("Client disconnected, cancelling job: %s", job.Name) + logrus.Infof("Client disconnected, cancelling job: build") job.Cancel() } }() } - if err := job.Run(); err != nil { + if err := b.CmdBuild(job); err != nil { if !job.Stdout.Used() { return err } @@ -1676,3 +1681,12 @@ func toBool(s string) bool { s = strings.ToLower(strings.TrimSpace(s)) return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") } + +// FIXME(calavera): This is a copy of the Env.GetInt64 +func toInt64(s string) int64 { + val, err := strconv.ParseInt(s, 10, 64) + if err != nil { + return 0 + } + return val +} diff --git a/builder/job.go b/builder/job.go index 930d7b7f1..6ad64d716 100644 --- a/builder/job.go +++ b/builder/job.go @@ -9,6 +9,7 @@ import ( "os" "os/exec" "strings" + "sync" "github.com/docker/docker/api" "github.com/docker/docker/builder/parser" @@ -17,6 +18,7 @@ import ( "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/httputils" + "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/urlutil" @@ -41,41 +43,73 @@ type BuilderJob struct { Daemon *daemon.Daemon } +type Config struct { + DockerfileName string + RemoteURL string + RepoName string + SuppressOutput bool + NoCache bool + Remove bool + ForceRemove bool + Pull bool + JSONFormat bool + Memory int64 + MemorySwap int64 + CpuShares int64 + CpuSetCpus string + CpuSetMems string + AuthConfig *registry.AuthConfig + ConfigFile *registry.ConfigFile + + Stdout *engine.Output + Stderr *engine.Output + Stdin *engine.Input + // When closed, the job has been cancelled. + // Note: not all jobs implement cancellation. + // See Job.Cancel() and Job.WaitCancelled() + cancelled chan struct{} + cancelOnce sync.Once +} + +// When called, causes the Job.WaitCancelled channel to unblock. +func (b *Config) Cancel() { + b.cancelOnce.Do(func() { + close(b.cancelled) + }) +} + +// Returns a channel which is closed ("never blocks") when the job is cancelled. +func (b *Config) WaitCancelled() <-chan struct{} { + return b.cancelled +} + +func NewBuildConfig(logging bool, err io.Writer) *Config { + c := &Config{ + Stdout: engine.NewOutput(), + Stderr: engine.NewOutput(), + Stdin: engine.NewInput(), + cancelled: make(chan struct{}), + } + if logging { + c.Stderr.Add(ioutils.NopWriteCloser(err)) + } + return c +} + func (b *BuilderJob) Install() { - b.Engine.Register("build", b.CmdBuild) b.Engine.Register("build_config", b.CmdBuildConfig) } -func (b *BuilderJob) CmdBuild(job *engine.Job) error { - if len(job.Args) != 0 { - return fmt.Errorf("Usage: %s\n", job.Name) - } +func (b *BuilderJob) CmdBuild(buildConfig *Config) error { var ( - dockerfileName = job.Getenv("dockerfile") - remoteURL = job.Getenv("remote") - repoName = job.Getenv("t") - suppressOutput = job.GetenvBool("q") - noCache = job.GetenvBool("nocache") - rm = job.GetenvBool("rm") - forceRm = job.GetenvBool("forcerm") - pull = job.GetenvBool("pull") - memory = job.GetenvInt64("memory") - memorySwap = job.GetenvInt64("memswap") - cpuShares = job.GetenvInt64("cpushares") - cpuSetCpus = job.Getenv("cpusetcpus") - cpuSetMems = job.Getenv("cpusetmems") - authConfig = ®istry.AuthConfig{} - configFile = ®istry.ConfigFile{} - tag string - context io.ReadCloser + repoName string + tag string + context io.ReadCloser ) - job.GetenvJson("authConfig", authConfig) - job.GetenvJson("configFile", configFile) - - repoName, tag = parsers.ParseRepositoryTag(repoName) + repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName) if repoName != "" { - if err := registry.ValidateRepositoryName(repoName); err != nil { + if err := registry.ValidateRepositoryName(buildConfig.RepoName); err != nil { return err } if len(tag) > 0 { @@ -85,11 +119,11 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { } } - if remoteURL == "" { - context = ioutil.NopCloser(job.Stdin) - } else if urlutil.IsGitURL(remoteURL) { - if !urlutil.IsGitTransport(remoteURL) { - remoteURL = "https://" + remoteURL + if buildConfig.RemoteURL == "" { + context = ioutil.NopCloser(buildConfig.Stdin) + } else if urlutil.IsGitURL(buildConfig.RemoteURL) { + if !urlutil.IsGitTransport(buildConfig.RemoteURL) { + buildConfig.RemoteURL = "https://" + buildConfig.RemoteURL } root, err := ioutil.TempDir("", "docker-build-git") if err != nil { @@ -97,7 +131,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { } defer os.RemoveAll(root) - if output, err := exec.Command("git", "clone", "--recursive", remoteURL, root).CombinedOutput(); err != nil { + if output, err := exec.Command("git", "clone", "--recursive", buildConfig.RemoteURL, root).CombinedOutput(); err != nil { return fmt.Errorf("Error trying to use git: %s (%s)", err, output) } @@ -106,8 +140,8 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { return err } context = c - } else if urlutil.IsURL(remoteURL) { - f, err := httputils.Download(remoteURL) + } else if urlutil.IsURL(buildConfig.RemoteURL) { + f, err := httputils.Download(buildConfig.RemoteURL) if err != nil { return err } @@ -119,9 +153,9 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { // When we're downloading just a Dockerfile put it in // the default name - don't allow the client to move/specify it - dockerfileName = api.DefaultDockerfileName + buildConfig.DockerfileName = api.DefaultDockerfileName - c, err := archive.Generate(dockerfileName, string(dockerFile)) + c, err := archive.Generate(buildConfig.DockerfileName, string(dockerFile)) if err != nil { return err } @@ -129,35 +163,35 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { } defer context.Close() - sf := streamformatter.NewStreamFormatter(job.GetenvBool("json")) + sf := streamformatter.NewStreamFormatter(buildConfig.JSONFormat) builder := &Builder{ Daemon: b.Daemon, Engine: b.Engine, OutStream: &streamformatter.StdoutFormater{ - Writer: job.Stdout, + Writer: buildConfig.Stdout, StreamFormatter: sf, }, ErrStream: &streamformatter.StderrFormater{ - Writer: job.Stdout, + Writer: buildConfig.Stdout, StreamFormatter: sf, }, - Verbose: !suppressOutput, - UtilizeCache: !noCache, - Remove: rm, - ForceRemove: forceRm, - Pull: pull, - OutOld: job.Stdout, + Verbose: !buildConfig.SuppressOutput, + UtilizeCache: !buildConfig.NoCache, + Remove: buildConfig.Remove, + ForceRemove: buildConfig.ForceRemove, + Pull: buildConfig.Pull, + OutOld: buildConfig.Stdout, StreamFormatter: sf, - AuthConfig: authConfig, - ConfigFile: configFile, - dockerfileName: dockerfileName, - cpuShares: cpuShares, - cpuSetCpus: cpuSetCpus, - cpuSetMems: cpuSetMems, - memory: memory, - memorySwap: memorySwap, - cancelled: job.WaitCancelled(), + AuthConfig: buildConfig.AuthConfig, + ConfigFile: buildConfig.ConfigFile, + dockerfileName: buildConfig.DockerfileName, + cpuShares: buildConfig.CpuShares, + cpuSetCpus: buildConfig.CpuSetCpus, + cpuSetMems: buildConfig.CpuSetMems, + memory: buildConfig.Memory, + memorySwap: buildConfig.MemorySwap, + cancelled: buildConfig.WaitCancelled(), } id, err := builder.Run(context) From ae4063585e7936780154101f7fe416a080c6ff7c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 16 Apr 2015 14:26:33 -0700 Subject: [PATCH 536/999] Remove engine.Job from builder.CmdBuildConfig. Signed-off-by: David Calavera --- api/server/form.go | 20 ++++++ api/server/form_test.go | 55 +++++++++++++++++ api/server/server.go | 132 ++++++++++++++++++++-------------------- builder/job.go | 96 +++++++++++++---------------- daemon/commit.go | 50 +-------------- docker/daemon.go | 5 -- graph/import.go | 38 +++--------- 7 files changed, 195 insertions(+), 201 deletions(-) create mode 100644 api/server/form.go create mode 100644 api/server/form_test.go diff --git a/api/server/form.go b/api/server/form.go new file mode 100644 index 000000000..af1cd2075 --- /dev/null +++ b/api/server/form.go @@ -0,0 +1,20 @@ +package server + +import ( + "net/http" + "strconv" + "strings" +) + +func boolValue(r *http.Request, k string) bool { + s := strings.ToLower(strings.TrimSpace(r.FormValue(k))) + return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") +} + +func int64Value(r *http.Request, k string) int64 { + val, err := strconv.ParseInt(r.FormValue(k), 10, 64) + if err != nil { + return 0 + } + return val +} diff --git a/api/server/form_test.go b/api/server/form_test.go new file mode 100644 index 000000000..5cf6c82c1 --- /dev/null +++ b/api/server/form_test.go @@ -0,0 +1,55 @@ +package server + +import ( + "net/http" + "net/url" + "testing" +) + +func TestBoolValue(t *testing.T) { + cases := map[string]bool{ + "": false, + "0": false, + "no": false, + "false": false, + "none": false, + "1": true, + "yes": true, + "true": true, + "one": true, + "100": true, + } + + for c, e := range cases { + v := url.Values{} + v.Set("test", c) + r, _ := http.NewRequest("POST", "", nil) + r.Form = v + + a := boolValue(r, "test") + if a != e { + t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a) + } + } +} + +func TestInt64Value(t *testing.T) { + cases := map[string]int64{ + "": 0, + "asdf": 0, + "0": 0, + "1": 1, + } + + for c, e := range cases { + v := url.Values{} + v.Set("test", c) + r, _ := http.NewRequest("POST", "", nil) + r.Form = v + + a := int64Value(r, "test") + if a != e { + t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a) + } + } +} diff --git a/api/server/server.go b/api/server/server.go index 65e139167..1002526f9 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -381,7 +381,7 @@ func (s *Server) getImagesJSON(eng *engine.Engine, version version.Version, w ht Filters: r.Form.Get("filters"), // FIXME this parameter could just be a match filter Filter: r.Form.Get("filter"), - All: toBool(r.Form.Get("all")), + All: boolValue(r, "all"), } images, err := s.daemon.Repositories().Images(&imagesConfig) @@ -597,8 +597,8 @@ func (s *Server) getContainersJSON(eng *engine.Engine, version version.Version, } config := &daemon.ContainersConfig{ - All: toBool(r.Form.Get("all")), - Size: toBool(r.Form.Get("size")), + All: boolValue(r, "all"), + Size: boolValue(r, "size"), Since: r.Form.Get("since"), Before: r.Form.Get("before"), Filters: r.Form.Get("filters"), @@ -640,14 +640,14 @@ func (s *Server) getContainersLogs(eng *engine.Engine, version version.Version, } // Validate args here, because we can't return not StatusOK after job.Run() call - stdout, stderr := toBool(r.Form.Get("stdout")), toBool(r.Form.Get("stderr")) + stdout, stderr := boolValue(r, "stdout"), boolValue(r, "stderr") if !(stdout || stderr) { return fmt.Errorf("Bad parameters: you must choose at least one stream") } logsConfig := &daemon.ContainerLogsConfig{ - Follow: toBool(r.Form.Get("follow")), - Timestamps: toBool(r.Form.Get("timestamps")), + Follow: boolValue(r, "follow"), + Timestamps: boolValue(r, "timestamps"), Tail: r.Form.Get("tail"), UseStdout: stdout, UseStderr: stderr, @@ -671,7 +671,7 @@ func (s *Server) postImagesTag(eng *engine.Engine, version version.Version, w ht repo := r.Form.Get("repo") tag := r.Form.Get("tag") - force := toBool(r.Form.Get("force")) + force := boolValue(r, "force") if err := s.daemon.Repositories().Tag(repo, tag, vars["name"], force); err != nil { return err } @@ -690,11 +690,20 @@ func (s *Server) postCommit(eng *engine.Engine, version version.Version, w http. cont := r.Form.Get("container") - pause := toBool(r.Form.Get("pause")) + pause := boolValue(r, "pause") if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { pause = true } + c, _, err := runconfig.DecodeContainerConfig(r.Body) + if err != nil && err != io.EOF { //Do not fail if body is empty. + return err + } + + if c == nil { + c = &runconfig.Config{} + } + containerCommitConfig := &daemon.ContainerCommitConfig{ Pause: pause, Repo: r.Form.Get("repo"), @@ -702,10 +711,10 @@ func (s *Server) postCommit(eng *engine.Engine, version version.Version, w http. Author: r.Form.Get("author"), Comment: r.Form.Get("comment"), Changes: r.Form["changes"], - Config: r.Body, + Config: c, } - imgID, err := s.daemon.ContainerCommit(cont, containerCommitConfig) + imgID, err := builder.Commit(s.daemon, eng, cont, containerCommitConfig) if err != nil { return err } @@ -782,10 +791,15 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w imageImportConfig.Json = false } - if err := s.daemon.Repositories().Import(src, repo, tag, imageImportConfig, eng); err != nil { + newConfig, err := builder.BuildFromConfig(s.daemon, eng, &runconfig.Config{}, imageImportConfig.Changes) + if err != nil { return err } + imageImportConfig.ContainerConfig = newConfig + if err := s.daemon.Repositories().Import(src, repo, tag, imageImportConfig); err != nil { + return err + } } return nil @@ -977,9 +991,9 @@ func (s *Server) deleteContainers(eng *engine.Engine, version version.Version, w name := vars["name"] config := &daemon.ContainerRmConfig{ - ForceRemove: toBool(r.Form.Get("force")), - RemoveVolume: toBool(r.Form.Get("v")), - RemoveLink: toBool(r.Form.Get("link")), + ForceRemove: boolValue(r, "force"), + RemoveVolume: boolValue(r, "v"), + RemoveLink: boolValue(r, "link"), } if err := s.daemon.ContainerRm(name, config); err != nil { @@ -1004,8 +1018,8 @@ func (s *Server) deleteImages(eng *engine.Engine, version version.Version, w htt } name := vars["name"] - force := toBool(r.Form.Get("force")) - noprune := toBool(r.Form.Get("noprune")) + force := boolValue(r, "force") + noprune := boolValue(r, "noprune") list, err := s.daemon.ImageDelete(name, force, noprune) if err != nil { @@ -1152,19 +1166,19 @@ func (s *Server) postContainersAttach(eng *engine.Engine, version version.Versio } else { errStream = outStream } - logs := toBool(r.Form.Get("logs")) - stream := toBool(r.Form.Get("stream")) + logs := boolValue(r, "logs") + stream := boolValue(r, "stream") var stdin io.ReadCloser var stdout, stderr io.Writer - if toBool(r.Form.Get("stdin")) { + if boolValue(r, "stdin") { stdin = inStream } - if toBool(r.Form.Get("stdout")) { + if boolValue(r, "stdout") { stdout = outStream } - if toBool(r.Form.Get("stderr")) { + if boolValue(r, "stderr") { stderr = errStream } @@ -1246,11 +1260,9 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R authConfig = ®istry.AuthConfig{} configFileEncoded = r.Header.Get("X-Registry-Config") configFile = ®istry.ConfigFile{} - job = builder.NewBuildConfig(eng.Logging, eng.Stderr) + buildConfig = builder.NewBuildConfig() ) - b := &builder.BuilderJob{eng, getDaemon(eng)} - // This block can be removed when API versions prior to 1.9 are deprecated. // Both headers will be parsed and sent along to the daemon, but if a non-empty // ConfigFile is present, any value provided as an AuthConfig directly will @@ -1273,39 +1285,41 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R } } + stdout := engine.NewOutput() + stdout.Set(utils.NewWriteFlusher(w)) + if version.GreaterThanOrEqualTo("1.8") { - job.JSONFormat = true - streamJSON(job.Stdout, w, true) - } else { - job.Stdout.Add(utils.NewWriteFlusher(w)) + w.Header().Set("Content-Type", "application/json") + buildConfig.JSONFormat = true } - if toBool(r.FormValue("forcerm")) && version.GreaterThanOrEqualTo("1.12") { - job.Remove = true + if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { + buildConfig.Remove = true } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { - job.Remove = true + buildConfig.Remove = true } else { - job.Remove = toBool(r.FormValue("rm")) + buildConfig.Remove = boolValue(r, "rm") } - if toBool(r.FormValue("pull")) && version.GreaterThanOrEqualTo("1.16") { - job.Pull = true + if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { + buildConfig.Pull = true } - job.Stdin.Add(r.Body) - // FIXME(calavera): !!!!! Remote might not be used. Solve the mistery before merging - //job.Setenv("remote", r.FormValue("remote")) - job.DockerfileName = r.FormValue("dockerfile") - job.RepoName = r.FormValue("t") - job.SuppressOutput = toBool(r.FormValue("q")) - job.NoCache = toBool(r.FormValue("nocache")) - job.ForceRemove = toBool(r.FormValue("forcerm")) - job.AuthConfig = authConfig - job.ConfigFile = configFile - job.MemorySwap = toInt64(r.FormValue("memswap")) - job.Memory = toInt64(r.FormValue("memory")) - job.CpuShares = toInt64(r.FormValue("cpushares")) - job.CpuSetCpus = r.FormValue("cpusetcpus") - job.CpuSetMems = r.FormValue("cpusetmems") + buildConfig.Stdout = stdout + buildConfig.Context = r.Body + + buildConfig.RemoteURL = r.FormValue("remote") + buildConfig.DockerfileName = r.FormValue("dockerfile") + buildConfig.RepoName = r.FormValue("t") + buildConfig.SuppressOutput = boolValue(r, "q") + buildConfig.NoCache = boolValue(r, "nocache") + buildConfig.ForceRemove = boolValue(r, "forcerm") + buildConfig.AuthConfig = authConfig + buildConfig.ConfigFile = configFile + buildConfig.MemorySwap = int64Value(r, "memswap") + buildConfig.Memory = int64Value(r, "memory") + buildConfig.CpuShares = int64Value(r, "cpushares") + buildConfig.CpuSetCpus = r.FormValue("cpusetcpus") + buildConfig.CpuSetMems = r.FormValue("cpusetmems") // Job cancellation. Note: not all job types support this. if closeNotifier, ok := w.(http.CloseNotifier); ok { @@ -1316,13 +1330,13 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R case <-finished: case <-closeNotifier.CloseNotify(): logrus.Infof("Client disconnected, cancelling job: build") - job.Cancel() + buildConfig.Cancel() } }() } - if err := b.CmdBuild(job); err != nil { - if !job.Stdout.Used() { + if err := builder.Build(s.daemon, eng, buildConfig); err != nil { + if !stdout.Used() { return err } sf := streamformatter.NewStreamFormatter(version.GreaterThanOrEqualTo("1.8")) @@ -1676,17 +1690,3 @@ func allocateDaemonPort(addr string) error { } return nil } - -func toBool(s string) bool { - s = strings.ToLower(strings.TrimSpace(s)) - return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") -} - -// FIXME(calavera): This is a copy of the Env.GetInt64 -func toInt64(s string) int64 { - val, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return 0 - } - return val -} diff --git a/builder/job.go b/builder/job.go index 6ad64d716..8a1cf3054 100644 --- a/builder/job.go +++ b/builder/job.go @@ -2,7 +2,6 @@ package builder import ( "bytes" - "encoding/json" "fmt" "io" "io/ioutil" @@ -18,7 +17,6 @@ import ( "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/httputils" - "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/urlutil" @@ -38,11 +36,6 @@ var validCommitCommands = map[string]bool{ "onbuild": true, } -type BuilderJob struct { - Engine *engine.Engine - Daemon *daemon.Daemon -} - type Config struct { DockerfileName string RemoteURL string @@ -61,9 +54,8 @@ type Config struct { AuthConfig *registry.AuthConfig ConfigFile *registry.ConfigFile - Stdout *engine.Output - Stderr *engine.Output - Stdin *engine.Input + Stdout io.Writer + Context io.ReadCloser // When closed, the job has been cancelled. // Note: not all jobs implement cancellation. // See Job.Cancel() and Job.WaitCancelled() @@ -83,24 +75,15 @@ func (b *Config) WaitCancelled() <-chan struct{} { return b.cancelled } -func NewBuildConfig(logging bool, err io.Writer) *Config { - c := &Config{ - Stdout: engine.NewOutput(), - Stderr: engine.NewOutput(), - Stdin: engine.NewInput(), - cancelled: make(chan struct{}), +func NewBuildConfig() *Config { + return &Config{ + AuthConfig: ®istry.AuthConfig{}, + ConfigFile: ®istry.ConfigFile{}, + cancelled: make(chan struct{}), } - if logging { - c.Stderr.Add(ioutils.NopWriteCloser(err)) - } - return c } -func (b *BuilderJob) Install() { - b.Engine.Register("build_config", b.CmdBuildConfig) -} - -func (b *BuilderJob) CmdBuild(buildConfig *Config) error { +func Build(d *daemon.Daemon, e *engine.Engine, buildConfig *Config) error { var ( repoName string tag string @@ -109,7 +92,7 @@ func (b *BuilderJob) CmdBuild(buildConfig *Config) error { repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName) if repoName != "" { - if err := registry.ValidateRepositoryName(buildConfig.RepoName); err != nil { + if err := registry.ValidateRepositoryName(repoName); err != nil { return err } if len(tag) > 0 { @@ -120,7 +103,7 @@ func (b *BuilderJob) CmdBuild(buildConfig *Config) error { } if buildConfig.RemoteURL == "" { - context = ioutil.NopCloser(buildConfig.Stdin) + context = ioutil.NopCloser(buildConfig.Context) } else if urlutil.IsGitURL(buildConfig.RemoteURL) { if !urlutil.IsGitTransport(buildConfig.RemoteURL) { buildConfig.RemoteURL = "https://" + buildConfig.RemoteURL @@ -166,8 +149,8 @@ func (b *BuilderJob) CmdBuild(buildConfig *Config) error { sf := streamformatter.NewStreamFormatter(buildConfig.JSONFormat) builder := &Builder{ - Daemon: b.Daemon, - Engine: b.Engine, + Daemon: d, + Engine: e, OutStream: &streamformatter.StdoutFormater{ Writer: buildConfig.Stdout, StreamFormatter: sf, @@ -200,41 +183,28 @@ func (b *BuilderJob) CmdBuild(buildConfig *Config) error { } if repoName != "" { - b.Daemon.Repositories().Tag(repoName, tag, id, true) + return d.Repositories().Tag(repoName, tag, id, true) } return nil } -func (b *BuilderJob) CmdBuildConfig(job *engine.Job) error { - if len(job.Args) != 0 { - return fmt.Errorf("Usage: %s\n", job.Name) - } - - var ( - changes = job.GetenvList("changes") - newConfig runconfig.Config - ) - - if err := job.GetenvJson("config", &newConfig); err != nil { - return err - } - +func BuildFromConfig(d *daemon.Daemon, e *engine.Engine, c *runconfig.Config, changes []string) (*runconfig.Config, error) { ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) if err != nil { - return err + return nil, err } // ensure that the commands are valid for _, n := range ast.Children { if !validCommitCommands[n.Value] { - return fmt.Errorf("%s is not a valid change command", n.Value) + return nil, fmt.Errorf("%s is not a valid change command", n.Value) } } builder := &Builder{ - Daemon: b.Daemon, - Engine: b.Engine, - Config: &newConfig, + Daemon: d, + Engine: e, + Config: c, OutStream: ioutil.Discard, ErrStream: ioutil.Discard, disableCommit: true, @@ -242,12 +212,32 @@ func (b *BuilderJob) CmdBuildConfig(job *engine.Job) error { for i, n := range ast.Children { if err := builder.dispatch(i, n); err != nil { - return err + return nil, err } } - if err := json.NewEncoder(job.Stdout).Encode(builder.Config); err != nil { - return err + return builder.Config, nil +} + +func Commit(d *daemon.Daemon, eng *engine.Engine, name string, c *daemon.ContainerCommitConfig) (string, error) { + container, err := d.Get(name) + if err != nil { + return "", err } - return nil + + newConfig, err := BuildFromConfig(d, eng, c.Config, c.Changes) + if err != nil { + return "", err + } + + if err := runconfig.Merge(newConfig, container.Config); err != nil { + return "", err + } + + img, err := d.Commit(container, c.Repo, c.Tag, c.Comment, c.Author, c.Pause, newConfig) + if err != nil { + return "", err + } + + return img.ID, nil } diff --git a/daemon/commit.go b/daemon/commit.go index a60ed0819..0c49eb2c9 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -1,12 +1,6 @@ package daemon import ( - "bytes" - "encoding/json" - "io" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/runconfig" ) @@ -18,49 +12,7 @@ type ContainerCommitConfig struct { Author string Comment string Changes []string - Config io.ReadCloser -} - -func (daemon *Daemon) ContainerCommit(name string, c *ContainerCommitConfig) (string, error) { - container, err := daemon.Get(name) - if err != nil { - return "", err - } - - var ( - subenv engine.Env - config = container.Config - stdoutBuffer = bytes.NewBuffer(nil) - newConfig runconfig.Config - ) - - if err := subenv.Decode(c.Config); err != nil { - logrus.Errorf("%s", err) - } - - buildConfigJob := daemon.eng.Job("build_config") - buildConfigJob.Stdout.Add(stdoutBuffer) - buildConfigJob.SetenvList("changes", c.Changes) - // FIXME this should be remove when we remove deprecated config param - buildConfigJob.SetenvSubEnv("config", &subenv) - - if err := buildConfigJob.Run(); err != nil { - return "", err - } - if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil { - return "", err - } - - if err := runconfig.Merge(&newConfig, config); err != nil { - return "", err - } - - img, err := daemon.Commit(container, c.Repo, c.Tag, c.Comment, c.Author, c.Pause, &newConfig) - if err != nil { - return "", err - } - - return img.ID, nil + Config *runconfig.Config } // Commit creates a new filesystem image from the current state of a container. diff --git a/docker/daemon.go b/docker/daemon.go index 0602ddf65..c6241b606 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -11,7 +11,6 @@ import ( "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/builder" "github.com/docker/docker/daemon" _ "github.com/docker/docker/daemon/execdriver/lxc" _ "github.com/docker/docker/daemon/execdriver/native" @@ -141,9 +140,6 @@ func mainDaemon() { "graphdriver": d.GraphDriver().String(), }).Info("Docker daemon") - b := &builder.BuilderJob{eng, d} - b.Install() - // after the daemon is done setting up we can tell the api to start // accepting connections with specified daemon api.AcceptConnections(d) @@ -155,7 +151,6 @@ func mainDaemon() { if errAPI != nil { logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI) } - } // currentUserIsOwner checks whether the current user is the owner of the given diff --git a/graph/import.go b/graph/import.go index 5d86ba2cb..50e605c94 100644 --- a/graph/import.go +++ b/graph/import.go @@ -1,13 +1,10 @@ package graph import ( - "bytes" - "encoding/json" "io" "net/http" "net/url" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/progressreader" @@ -17,20 +14,18 @@ import ( ) type ImageImportConfig struct { - Changes []string - InConfig io.ReadCloser - Json bool - OutStream io.Writer - //OutStream WriteFlusher + Changes []string + InConfig io.ReadCloser + Json bool + OutStream io.Writer + ContainerConfig *runconfig.Config } -func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig *ImageImportConfig, eng *engine.Engine) error { +func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig *ImageImportConfig) error { var ( - sf = streamformatter.NewStreamFormatter(imageImportConfig.Json) - archive archive.ArchiveReader - resp *http.Response - stdoutBuffer = bytes.NewBuffer(nil) - newConfig runconfig.Config + sf = streamformatter.NewStreamFormatter(imageImportConfig.Json) + archive archive.ArchiveReader + resp *http.Response ) if src == "-" { @@ -63,20 +58,7 @@ func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig archive = progressReader } - buildConfigJob := eng.Job("build_config") - buildConfigJob.Stdout.Add(stdoutBuffer) - buildConfigJob.SetenvList("changes", imageImportConfig.Changes) - // FIXME this should be remove when we remove deprecated config param - //buildConfigJob.Setenv("config", job.Getenv("config")) - - if err := buildConfigJob.Run(); err != nil { - return err - } - if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil { - return err - } - - img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, &newConfig) + img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, imageImportConfig.ContainerConfig) if err != nil { return err } From 2ed4ed50be3a0379d83b9267f62c4c7f665b849f Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 16 Apr 2015 14:17:23 +0200 Subject: [PATCH 537/999] Add some stdcopy_test (coverage) Signed-off-by: Vincent Demeester --- pkg/stdcopy/stdcopy.go | 4 --- pkg/stdcopy/stdcopy_test.go | 65 +++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 4 deletions(-) diff --git a/pkg/stdcopy/stdcopy.go b/pkg/stdcopy/stdcopy.go index 4f78f20d1..dbb74e5a2 100644 --- a/pkg/stdcopy/stdcopy.go +++ b/pkg/stdcopy/stdcopy.go @@ -54,10 +54,6 @@ func (w *StdWriter) Write(buf []byte) (n int, err error) { // `t` indicates the id of the stream to encapsulate. // It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr. func NewStdWriter(w io.Writer, t StdType) *StdWriter { - if len(t) != StdWriterPrefixLen { - return nil - } - return &StdWriter{ Writer: w, prefix: t, diff --git a/pkg/stdcopy/stdcopy_test.go b/pkg/stdcopy/stdcopy_test.go index 14e6ed311..a9fd73a49 100644 --- a/pkg/stdcopy/stdcopy_test.go +++ b/pkg/stdcopy/stdcopy_test.go @@ -3,9 +3,74 @@ package stdcopy import ( "bytes" "io/ioutil" + "strings" "testing" ) +func TestNewStdWriter(t *testing.T) { + writer := NewStdWriter(ioutil.Discard, Stdout) + if writer == nil { + t.Fatalf("NewStdWriter with an invalid StdType should not return nil.") + } +} + +func TestWriteWithUnitializedStdWriter(t *testing.T) { + writer := StdWriter{ + Writer: nil, + prefix: Stdout, + sizeBuf: make([]byte, 4), + } + n, err := writer.Write([]byte("Something here")) + if n != 0 || err == nil { + t.Fatalf("Should fail when given an uncomplete or uninitialized StdWriter") + } +} + +func TestWriteWithNilBytes(t *testing.T) { + writer := NewStdWriter(ioutil.Discard, Stdout) + n, err := writer.Write(nil) + if err != nil { + t.Fatalf("Shouldn't have fail when given no data") + } + if n > 0 { + t.Fatalf("Write should have written 0 byte, but has written %d", n) + } +} + +func TestWrite(t *testing.T) { + writer := NewStdWriter(ioutil.Discard, Stdout) + data := []byte("Test StdWrite.Write") + n, err := writer.Write(data) + if err != nil { + t.Fatalf("Error while writing with StdWrite") + } + if n != len(data) { + t.Fatalf("Write should have writen %d byte but wrote %d.", len(data), n) + } +} + +func TestStdCopyWithInvalidInputHeader(t *testing.T) { + dstOut := NewStdWriter(ioutil.Discard, Stdout) + dstErr := NewStdWriter(ioutil.Discard, Stderr) + src := strings.NewReader("Invalid input") + _, err := StdCopy(dstOut, dstErr, src) + if err == nil { + t.Fatal("StdCopy with invalid input header should fail.") + } +} + +func TestStdCopyWithCorruptedPrefix(t *testing.T) { + data := []byte{0x01, 0x02, 0x03} + src := bytes.NewReader(data) + written, err := StdCopy(nil, nil, src) + if err != nil { + t.Fatalf("StdCopy should not return an error with corrupted prefix.") + } + if written != 0 { + t.Fatalf("StdCopy should have written 0, but has written %d", written) + } +} + func BenchmarkWrite(b *testing.B) { w := NewStdWriter(ioutil.Discard, Stdout) data := []byte("Test line for testing stdwriter performance\n") From 29c5596176769fc99a22b1d0a78dd5ea279fec69 Mon Sep 17 00:00:00 2001 From: Srini Brahmaroutu Date: Mon, 20 Apr 2015 21:07:21 +0000 Subject: [PATCH 538/999] moving integration tests to graph unit tests Addresses #12255 Signed-off-by: Srini Brahmaroutu --- {integration => graph}/graph_test.go | 47 ++++++++++------------------ 1 file changed, 17 insertions(+), 30 deletions(-) rename {integration => graph}/graph_test.go (93%) diff --git a/integration/graph_test.go b/graph/graph_test.go similarity index 93% rename from integration/graph_test.go rename to graph/graph_test.go index 8b6d7626f..81471b674 100644 --- a/integration/graph_test.go +++ b/graph/graph_test.go @@ -1,4 +1,4 @@ -package docker +package graph import ( "errors" @@ -11,9 +11,7 @@ import ( "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/daemon/graphdriver" - "github.com/docker/docker/graph" "github.com/docker/docker/image" - "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/stringid" ) @@ -47,6 +45,7 @@ func TestMount(t *testing.T) { if _, err := driver.Get(image.ID, ""); err != nil { t.Fatal(err) } + } func TestInit(t *testing.T) { @@ -166,18 +165,6 @@ func TestDeletePrefix(t *testing.T) { assertNImages(graph, t, 0) } -func createTestImage(graph *graph.Graph, t *testing.T) *image.Image { - archive, err := fakeTar() - if err != nil { - t.Fatal(err) - } - img, err := graph.Create(archive, "", "", "Test image", "", nil, nil) - if err != nil { - t.Fatal(err) - } - return img -} - func TestDelete(t *testing.T) { graph, _ := tempGraph(t) defer nukeGraph(graph) @@ -277,11 +264,19 @@ func TestByParent(t *testing.T) { } } -/* - * HELPER FUNCTIONS - */ +func createTestImage(graph *Graph, t *testing.T) *image.Image { + archive, err := fakeTar() + if err != nil { + t.Fatal(err) + } + img, err := graph.Create(archive, "", "", "Test image", "", nil, nil) + if err != nil { + t.Fatal(err) + } + return img +} -func assertNImages(graph *graph.Graph, t *testing.T, n int) { +func assertNImages(graph *Graph, t *testing.T, n int) { if images, err := graph.Map(); err != nil { t.Fatal(err) } else if actualN := len(images); actualN != n { @@ -289,7 +284,7 @@ func assertNImages(graph *graph.Graph, t *testing.T, n int) { } } -func tempGraph(t *testing.T) (*graph.Graph, graphdriver.Driver) { +func tempGraph(t *testing.T) (*Graph, graphdriver.Driver) { tmp, err := ioutil.TempDir("", "docker-graph-") if err != nil { t.Fatal(err) @@ -298,22 +293,14 @@ func tempGraph(t *testing.T) (*graph.Graph, graphdriver.Driver) { if err != nil { t.Fatal(err) } - graph, err := graph.NewGraph(tmp, driver) + graph, err := NewGraph(tmp, driver) if err != nil { t.Fatal(err) } return graph, driver } -func nukeGraph(graph *graph.Graph) { +func nukeGraph(graph *Graph) { graph.Driver().Cleanup() os.RemoveAll(graph.Root) } - -func testArchive(t *testing.T) archive.Archive { - archive, err := fakeTar() - if err != nil { - t.Fatal(err) - } - return archive -} From 92849fdcce257dfd61a5c95f57cde085ff22b431 Mon Sep 17 00:00:00 2001 From: Lorenzo Fontana Date: Mon, 20 Apr 2015 22:06:17 +0200 Subject: [PATCH 539/999] Removed go1.3.3 support Signed-off-by: Lorenzo Fontana --- Dockerfile | 6 +-- pkg/requestdecorator/requestdecorator_test.go | 40 ++----------------- project/PACKAGERS.md | 2 +- 3 files changed, 7 insertions(+), 41 deletions(-) diff --git a/Dockerfile b/Dockerfile index b1c7c4a6f..6be471a7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -98,9 +98,9 @@ RUN cd /usr/local/go/src \ ./make.bash --no-clean 2>&1; \ done -# We still support compiling with older Go, so need to grab older "gofmt" -ENV GOFMT_VERSION 1.3.3 -RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt +# This has been commented out and kept as reference because we don't support compiling with older Go anymore. +# ENV GOFMT_VERSION 1.3.3 +# RUN curl -sSL https://storage.googleapis.com/golang/go${GOFMT_VERSION}.$(go env GOOS)-$(go env GOARCH).tar.gz | tar -C /go/bin -xz --strip-components=2 go/bin/gofmt # Update this sha when we upgrade to go 1.5.0 ENV GO_TOOLS_COMMIT 069d2f3bcb68257b627205f0486d6cc69a231ff9 diff --git a/pkg/requestdecorator/requestdecorator_test.go b/pkg/requestdecorator/requestdecorator_test.go index f1f9ef756..ed6113546 100644 --- a/pkg/requestdecorator/requestdecorator_test.go +++ b/pkg/requestdecorator/requestdecorator_test.go @@ -1,45 +1,11 @@ package requestdecorator import ( - "encoding/base64" "net/http" "strings" "testing" ) -// The following 2 functions are here for 1.3.3 support -// After we drop 1.3.3 support we can use the functions supported -// in go v1.4.0 + -// BasicAuth returns the username and password provided in the request's -// Authorization header, if the request uses HTTP Basic Authentication. -// See RFC 2617, Section 2. -func basicAuth(r *http.Request) (username, password string, ok bool) { - auth := r.Header.Get("Authorization") - if auth == "" { - return - } - return parseBasicAuth(auth) -} - -// parseBasicAuth parses an HTTP Basic Authentication string. -// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true). -func parseBasicAuth(auth string) (username, password string, ok bool) { - const prefix = "Basic " - if !strings.HasPrefix(auth, prefix) { - return - } - c, err := base64.StdEncoding.DecodeString(auth[len(prefix):]) - if err != nil { - return - } - cs := string(c) - s := strings.IndexByte(cs, ':') - if s < 0 { - return - } - return cs[:s], cs[s+1:], true -} - func TestUAVersionInfo(t *testing.T) { uavi := NewUAVersionInfo("foo", "bar") if !uavi.isValid() { @@ -147,7 +113,7 @@ func TestAuthDecorator(t *testing.T) { t.Fatal(err) } - username, password, ok := basicAuth(reqDecorated) + username, password, ok := reqDecorated.BasicAuth() if !ok { t.Fatalf("Cannot retrieve basic auth info from request") } @@ -189,7 +155,7 @@ func TestRequestFactory(t *testing.T) { t.Fatal(err) } - username, password, ok := basicAuth(req) + username, password, ok := req.BasicAuth() if !ok { t.Fatalf("Cannot retrieve basic auth info from request") } @@ -220,7 +186,7 @@ func TestRequestFactoryNewRequestWithDecorators(t *testing.T) { t.Fatal(err) } - username, password, ok := basicAuth(req) + username, password, ok := req.BasicAuth() if !ok { t.Fatalf("Cannot retrieve basic auth info from request") } diff --git a/project/PACKAGERS.md b/project/PACKAGERS.md index 6acd4aef3..d321a900d 100644 --- a/project/PACKAGERS.md +++ b/project/PACKAGERS.md @@ -45,7 +45,7 @@ need to package Docker your way, without denaturing it in the process. To build Docker, you will need the following: * A recent version of Git and Mercurial -* Go version 1.3 or later +* Go version 1.4 or later * A clean checkout of the source added to a valid [Go workspace](https://golang.org/doc/code.html#Workspaces) under the path *src/github.com/docker/docker* (unless you plan to use `AUTO_GOPATH`, From bfeb98a23607c835c1d9241e282b84acd8dc3606 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Mon, 20 Apr 2015 14:09:41 -0700 Subject: [PATCH 540/999] Make .docker dir have 0700 perms not 0600 Thanks to @dmcgowan for noticing. Added a testcase to make sure Save() can create the dir and then read from it. Signed-off-by: Doug Davis --- registry/auth.go | 2 +- registry/config_file_test.go | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/registry/auth.go b/registry/auth.go index bccf58fc5..ef4985abc 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -261,7 +261,7 @@ func (configFile *ConfigFile) Save() error { return err } - if err := os.MkdirAll(filepath.Dir(configFile.filename), 0600); err != nil { + if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil { return err } diff --git a/registry/config_file_test.go b/registry/config_file_test.go index 9abb8ee95..6f8bd74f5 100644 --- a/registry/config_file_test.go +++ b/registry/config_file_test.go @@ -31,6 +31,28 @@ func TestMissingFile(t *testing.T) { } } +func TestSaveFileToDirs(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + + tmpHome += "/.docker" + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on missing file: %q", err) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} + func TestEmptyFile(t *testing.T) { tmpHome, _ := ioutil.TempDir("", "config-test") fn := filepath.Join(tmpHome, CONFIGFILE) From 3b05005a1262e53d042512e88c52a6dae0f2e93d Mon Sep 17 00:00:00 2001 From: David Calavera Date: Mon, 20 Apr 2015 14:18:14 -0700 Subject: [PATCH 541/999] Add flusher check to utils.WriteFlusher. That way we can know when the stream has been flushed. Signed-off-by: David Calavera --- api/server/server.go | 10 +++++----- utils/utils.go | 9 +++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 1002526f9..fb271916e 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1285,9 +1285,6 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R } } - stdout := engine.NewOutput() - stdout.Set(utils.NewWriteFlusher(w)) - if version.GreaterThanOrEqualTo("1.8") { w.Header().Set("Content-Type", "application/json") buildConfig.JSONFormat = true @@ -1304,7 +1301,8 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R buildConfig.Pull = true } - buildConfig.Stdout = stdout + output := utils.NewWriteFlusher(w) + buildConfig.Stdout = output buildConfig.Context = r.Body buildConfig.RemoteURL = r.FormValue("remote") @@ -1336,7 +1334,9 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R } if err := builder.Build(s.daemon, eng, buildConfig); err != nil { - if !stdout.Used() { + // Do not write the error in the http output if it's still empty. + // This prevents from writing a 200(OK) when there is an interal error. + if !output.Flushed() { return err } sf := streamformatter.NewStreamFormatter(version.GreaterThanOrEqualTo("1.8")) diff --git a/utils/utils.go b/utils/utils.go index a151fc3f0..ab5982678 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -128,12 +128,14 @@ type WriteFlusher struct { sync.Mutex w io.Writer flusher http.Flusher + flushed bool } func (wf *WriteFlusher) Write(b []byte) (n int, err error) { wf.Lock() defer wf.Unlock() n, err = wf.w.Write(b) + wf.flushed = true wf.flusher.Flush() return n, err } @@ -142,9 +144,16 @@ func (wf *WriteFlusher) Write(b []byte) (n int, err error) { func (wf *WriteFlusher) Flush() { wf.Lock() defer wf.Unlock() + wf.flushed = true wf.flusher.Flush() } +func (wf *WriteFlusher) Flushed() bool { + wf.Lock() + defer wf.Unlock() + return wf.flushed +} + func NewWriteFlusher(w io.Writer) *WriteFlusher { var flusher http.Flusher if f, ok := w.(http.Flusher); ok { From a8253ec7e7ec13c81d5195ddfe3cf2571ce6afff Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Mon, 20 Apr 2015 11:24:28 -0700 Subject: [PATCH 542/999] Early API version bump Signed-off-by: Arnaud Porterie --- project/RELEASE-CHECKLIST.md | 43 +++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/project/RELEASE-CHECKLIST.md b/project/RELEASE-CHECKLIST.md index 8a2070e58..d2b965080 100644 --- a/project/RELEASE-CHECKLIST.md +++ b/project/RELEASE-CHECKLIST.md @@ -49,7 +49,17 @@ git cherry-pick ... ``` -### 2. Update CHANGELOG.md +### 2. Bump the API version on master + +We don't want to stop contributions to master just because we are releasing. At +the same time, now that the release branch exists, we don't want API changes to +go to the now frozen API version. + +Create a new entry in `docs/sources/reference/api/` by copying the latest and +bumping the version number (in both the file's name and content), and submit +this in a PR against master. + +### 3. Update CHANGELOG.md You can run this command for reference with git 2.0: @@ -124,7 +134,7 @@ git log --format='%aN <%aE>' v0.7.0...bump_v0.8.0 | sort -uf Obviously, you'll need to adjust version numbers as necessary. If you just need a count, add a simple `| wc -l`. -### 3. Change the contents of the VERSION file +### 4. Change the contents of the VERSION file Before the big thing, you'll want to make successive release candidates and get people to test. The release candidate number `N` should be part of the version: @@ -134,7 +144,7 @@ export RC_VERSION=${VERSION}-rcN echo ${RC_VERSION#v} > VERSION ``` -### 4. Test the docs +### 5. Test the docs Make sure that your tree includes documentation for any modified or new features, syntax or semantic changes. @@ -153,7 +163,7 @@ To make a shared test at https://beta-docs.docker.io: make AWS_S3_BUCKET=beta-docs.docker.io BUILD_ROOT=yes docs-release ``` -### 5. Commit and create a pull request to the "release" branch +### 6. Commit and create a pull request to the "release" branch ```bash git add VERSION CHANGELOG.md @@ -166,7 +176,7 @@ That last command will give you the proper link to visit to ensure that you open the PR against the "release" branch instead of accidentally against "master" (like so many brave souls before you already have). -### 6. Publish release candidate binaries +### 7. Publish release candidate binaries To run this you will need access to the release credentials. Get them from the Core maintainers. @@ -219,7 +229,7 @@ We recommend announcing the release candidate on: - The [docker-maintainers](https://groups.google.com/a/dockerproject.org/forum/#!forum/maintainers) group - Any social media that can bring some attention to the release candidate -### 7. Iterate on successive release candidates +### 8. Iterate on successive release candidates Spend several days along with the community explicitly investing time and resources to try and break Docker in every possible way, documenting any @@ -269,7 +279,7 @@ git push -f $GITHUBUSER bump_$VERSION Repeat step 6 to tag the code, publish new binaries, announce availability, and get help testing. -### 8. Finalize the bump branch +### 9. Finalize the bump branch When you're happy with the quality of a release candidate, you can move on and create the real thing. @@ -285,9 +295,9 @@ git commit --amend You will then repeat step 6 to publish the binaries to test -### 9. Get 2 other maintainers to validate the pull request +### 10. Get 2 other maintainers to validate the pull request -### 10. Publish final binaries +### 11. Publish final binaries Once they're tested and reasonably believed to be working, run against get.docker.com: @@ -303,7 +313,7 @@ docker run \ hack/release.sh ``` -### 9. Apply tag +### 12. Apply tag It's very important that we don't make the tag until after the official release is uploaded to get.docker.com! @@ -313,12 +323,12 @@ git tag -a $VERSION -m $VERSION bump_$VERSION git push origin $VERSION ``` -### 10. Go to github to merge the `bump_$VERSION` branch into release +### 13. 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 +### 14. Update the docs branch If this is a MAJOR.MINOR.0 release, you need to make an branch for the previous release's documentation: @@ -350,7 +360,7 @@ distributed CDN system) is flushed. The `make docs-release` command will do this _if_ the `DISTRIBUTION_ID` is set correctly - this will take at least 15 minutes to run and you can check its progress with the CDN Cloudfront Chrome addin. -### 12. Create a new pull request to merge your bump commit back into master +### 15. Create a new pull request to merge your bump commit back into master ```bash git checkout master @@ -364,17 +374,14 @@ echo "https://github.com/$GITHUBUSER/docker/compare/docker:master...$GITHUBUSER: Again, get two maintainers to validate, then merge, then push that pretty blue button to delete your branch. -### 13. Update the API docs and VERSION files +### 16. Update the VERSION files Now that version X.Y.Z is out, time to start working on the next! Update the content of the `VERSION` file to be the next minor (incrementing Y) and add the `-dev` suffix. For example, after 1.5.0 release, the `VERSION` file gets updated to `1.6.0-dev` (as in "1.6.0 in the making"). -Also create a new entry in `docs/sources/reference/api/` by copying the latest -and bumping the version number (in both the file's name and content). - -### 14. Rejoice and Evangelize! +### 17. Rejoice and Evangelize! Congratulations! You're done. From f3d4c33213088ce3c381156760064f07da76de30 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 20 Apr 2015 17:13:48 -0700 Subject: [PATCH 543/999] actually depreciate -rm, -sig-proxy, -name, seriously its been forever Signed-off-by: Jessica Frazelle --- api/client/run.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/api/client/run.go b/api/client/run.go index 74c656af3..628e725f1 100644 --- a/api/client/run.go +++ b/api/client/run.go @@ -43,10 +43,10 @@ func (cli *DockerCli) CmdRun(args ...string) error { // These are flags not stored in Config/HostConfig var ( - flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits") + flAutoRemove = cmd.Bool([]string{"-rm"}, false, "Automatically remove the container when it exits") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Run container in background and print container ID") - flSigProxy = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy received signals to the process") - flName = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container") + flSigProxy = cmd.Bool([]string{"-sig-proxy"}, true, "Proxy received signals to the process") + flName = cmd.String([]string{"-name"}, "", "Assign a name to the container") flAttach *opts.ListOpts ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") From f731b01483ed7010824c5951cc4a27db907c2d47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Tue, 21 Apr 2015 11:33:52 +0200 Subject: [PATCH 544/999] Dockerfile: download go libraries before copy vendor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When ever something vendor/ changes the go dependencies have to downloaded again, which requires internet access and there for is potential slow. COPY and go install is much faster, while the git urls does not change not this often. Signed-off-by: Jörg Thalheim --- Dockerfile | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index b1c7c4a6f..3cfd59419 100644 --- a/Dockerfile +++ b/Dockerfile @@ -160,20 +160,21 @@ RUN ./contrib/download-frozen-image.sh /docker-frozen-images \ hello-world:frozen@e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5 # see also "hack/make/.ensure-frozen-images" (which needs to be updated any time this list is) -# Install man page generator -COPY vendor /go/src/github.com/docker/docker/vendor -# (copy vendor/ because go-md2man needs golang.org/x/net) +# Download man page generator RUN set -x \ && git clone -b v1.0.1 https://github.com/cpuguy83/go-md2man.git /go/src/github.com/cpuguy83/go-md2man \ - && git clone -b v1.2 https://github.com/russross/blackfriday.git /go/src/github.com/russross/blackfriday \ - && go install -v github.com/cpuguy83/go-md2man + && git clone -b v1.2 https://github.com/russross/blackfriday.git /go/src/github.com/russross/blackfriday -# install toml validator +# Download toml validator ENV TOMLV_COMMIT 9baf8a8a9f2ed20a8e54160840c492f937eeaf9a RUN set -x \ && git clone https://github.com/BurntSushi/toml.git /go/src/github.com/BurntSushi/toml \ - && (cd /go/src/github.com/BurntSushi/toml && git checkout -q $TOMLV_COMMIT) \ - && go install -v github.com/BurntSushi/toml/cmd/tomlv + && (cd /go/src/github.com/BurntSushi/toml && git checkout -q $TOMLV_COMMIT) + +# copy vendor/ because go-md2man needs golang.org/x/net +COPY vendor /go/src/github.com/docker/docker/vendor +RUN go install -v github.com/cpuguy83/go-md2man \ + github.com/BurntSushi/toml/cmd/tomlv # Wrap all commands in the "docker-in-docker" script to allow nested containers ENTRYPOINT ["hack/dind"] From 27811355bd15dd2d0f102dd3b02512c677d5da44 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 21 Apr 2015 18:14:39 +0200 Subject: [PATCH 545/999] Remove writeJSONEnv Signed-off-by: Antonio Murdaca --- api/server/server.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 9142e530d..94338097b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -223,14 +223,6 @@ func httpError(w http.ResponseWriter, err error) { http.Error(w, err.Error(), statusCode) } -// writeJSONEnv writes the engine.Env values to the http response stream as a -// json encoded body. -func writeJSONEnv(w http.ResponseWriter, code int, v engine.Env) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - return v.Encode(w) -} - // writeJSON writes the value v to the http response stream as json with standard // json encoding. func writeJSON(w http.ResponseWriter, code int, v interface{}) error { From 364287b74118de4f04d049426ef37fa9936d2065 Mon Sep 17 00:00:00 2001 From: Dan Walsh Date: Mon, 20 Apr 2015 14:26:39 -0400 Subject: [PATCH 546/999] Add journald as a supported logger for containers Docker-DCO-1.1-Signed-off-by: Dan Walsh (github: rhatdan) --- daemon/container.go | 7 ++++ daemon/logger/journald/journald.go | 35 +++++++++++++++++++ docs/man/docker-create.1.md | 2 +- docs/man/docker-run.1.md | 2 +- docs/man/docker.1.md | 2 +- .../reference/api/docker_remote_api_v1.19.md | 4 +-- docs/sources/reference/run.md | 4 +++ 7 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 daemon/logger/journald/journald.go diff --git a/daemon/container.go b/daemon/container.go index 9dc0696ea..93e60d4fe 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -21,6 +21,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/logger" + "github.com/docker/docker/daemon/logger/journald" "github.com/docker/docker/daemon/logger/jsonfilelog" "github.com/docker/docker/daemon/logger/syslog" "github.com/docker/docker/daemon/network" @@ -1420,6 +1421,12 @@ func (container *Container) startLogging() error { return err } l = dl + case "journald": + dl, err := journald.New(container.ID[:12]) + if err != nil { + return err + } + l = dl case "none": return nil default: diff --git a/daemon/logger/journald/journald.go b/daemon/logger/journald/journald.go new file mode 100644 index 000000000..5eb141ac8 --- /dev/null +++ b/daemon/logger/journald/journald.go @@ -0,0 +1,35 @@ +package journald + +import ( + "fmt" + + "github.com/coreos/go-systemd/journal" + "github.com/docker/docker/daemon/logger" +) + +type Journald struct { + Jmap map[string]string +} + +func New(id string) (logger.Logger, error) { + if !journal.Enabled() { + return nil, fmt.Errorf("journald is not enabled on this host") + } + jmap := map[string]string{"MESSAGE_ID": id} + return &Journald{Jmap: jmap}, nil +} + +func (s *Journald) Log(msg *logger.Message) error { + if msg.Source == "stderr" { + return journal.Send(string(msg.Line), journal.PriErr, s.Jmap) + } + return journal.Send(string(msg.Line), journal.PriInfo, s.Jmap) +} + +func (s *Journald) Close() error { + return nil +} + +func (s *Journald) Name() string { + return "Journald" +} diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index bb9cbdc8f..7aba222b2 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -133,7 +133,7 @@ two memory nodes. **--lxc-conf**=[] (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" -**--log-driver**="|*json-file*|*syslog*|*none*" +**--log-driver**="|*json-file*|*syslog*|*journald*|*none*" Logging driver for container. Default is defined by daemon `--log-driver` flag. **Warning**: `docker logs` command works only for `json-file` logging driver. diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index 2893437bb..f2ce4b777 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -238,7 +238,7 @@ which interface and port to use. **--lxc-conf**=[] (lxc exec-driver only) Add custom lxc options --lxc-conf="lxc.cgroup.cpuset.cpus = 0,1" -**--log-driver**="|*json-file*|*syslog*|*none*" +**--log-driver**="|*json-file*|*syslog*|*journald*|*none*" Logging driver for container. Default is defined by daemon `--log-driver` flag. **Warning**: `docker logs` command works only for `json-file` logging driver. diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index 53c54f903..0196b6364 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -95,7 +95,7 @@ unix://[/path/to/socket] to use. **--label**="[]" Set key=value labels to the daemon (displayed in `docker info`) -**--log-driver**="*json-file*|*syslog*|*none*" +**--log-driver**="*json-file*|*syslog*|*journald*|*none*" Default driver for container logs. Default is `json-file`. **Warning**: `docker logs` command works only for `json-file` logging driver. diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 3a4d05ea9..a3b580f1b 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -261,7 +261,7 @@ Json Parameters: systems, such as SELinux. - **LogConfig** - Log configuration for the container, specified as `{ "Type": "", "Config": {"key1": "val1"}}`. - Available types: `json-file`, `syslog`, `none`. + Available types: `json-file`, `syslog`, `journald`, `none`. `json-file` logging driver. - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. @@ -762,7 +762,7 @@ Json Parameters: systems, such as SELinux. - **LogConfig** - Log configuration for the container, specified as `{ "Type": "", "Config": {"key1": "val1"}}`. - Available types: `json-file`, `syslog`, `none`. + Available types: `json-file`, `syslog`, `journald`, `none`. `json-file` logging driver. - **CgroupParent** - Path to cgroups under which the cgroup for the container will be created. If the path is not absolute, the path is considered to be relative to the cgroups path of the init process. Cgroups will be created if they do not already exist. diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 10178b382..a0d66937f 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -788,6 +788,10 @@ command is available only for this logging driver Syslog logging driver for Docker. Writes log messages to syslog. `docker logs` command is not available for this logging driver +#### Logging driver: journald + +Journald logging driver for Docker. Writes log messages to journald. `docker logs` command is not available for this logging driver + ## Overriding Dockerfile image defaults When a developer builds an image from a [*Dockerfile*](/reference/builder) From 6dcdf832a3dddc8de17b7f8b1fb1ddb8b20f9077 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Sat, 18 Apr 2015 09:45:24 -0700 Subject: [PATCH 547/999] Add gocheck to vendored deps Signed-off-by: Alexander Morozov --- hack/vendor.sh | 2 + .../src/github.com/go-check/check/.gitignore | 4 + vendor/src/github.com/go-check/check/LICENSE | 25 + .../src/github.com/go-check/check/README.md | 20 + vendor/src/github.com/go-check/check/TODO | 2 + .../github.com/go-check/check/benchmark.go | 163 +++ .../go-check/check/benchmark_test.go | 91 ++ .../go-check/check/bootstrap_test.go | 82 ++ vendor/src/github.com/go-check/check/check.go | 945 ++++++++++++++++++ .../github.com/go-check/check/check_test.go | 207 ++++ .../src/github.com/go-check/check/checkers.go | 458 +++++++++ .../go-check/check/checkers_test.go | 272 +++++ .../github.com/go-check/check/export_test.go | 9 + .../github.com/go-check/check/fixture_test.go | 484 +++++++++ .../go-check/check/foundation_test.go | 335 +++++++ .../src/github.com/go-check/check/helpers.go | 231 +++++ .../github.com/go-check/check/helpers_test.go | 519 ++++++++++ .../src/github.com/go-check/check/printer.go | 168 ++++ .../github.com/go-check/check/printer_test.go | 104 ++ vendor/src/github.com/go-check/check/run.go | 175 ++++ .../src/github.com/go-check/check/run_test.go | 419 ++++++++ 21 files changed, 4715 insertions(+) create mode 100644 vendor/src/github.com/go-check/check/.gitignore create mode 100644 vendor/src/github.com/go-check/check/LICENSE create mode 100644 vendor/src/github.com/go-check/check/README.md create mode 100644 vendor/src/github.com/go-check/check/TODO create mode 100644 vendor/src/github.com/go-check/check/benchmark.go create mode 100644 vendor/src/github.com/go-check/check/benchmark_test.go create mode 100644 vendor/src/github.com/go-check/check/bootstrap_test.go create mode 100644 vendor/src/github.com/go-check/check/check.go create mode 100644 vendor/src/github.com/go-check/check/check_test.go create mode 100644 vendor/src/github.com/go-check/check/checkers.go create mode 100644 vendor/src/github.com/go-check/check/checkers_test.go create mode 100644 vendor/src/github.com/go-check/check/export_test.go create mode 100644 vendor/src/github.com/go-check/check/fixture_test.go create mode 100644 vendor/src/github.com/go-check/check/foundation_test.go create mode 100644 vendor/src/github.com/go-check/check/helpers.go create mode 100644 vendor/src/github.com/go-check/check/helpers_test.go create mode 100644 vendor/src/github.com/go-check/check/printer.go create mode 100644 vendor/src/github.com/go-check/check/printer_test.go create mode 100644 vendor/src/github.com/go-check/check/run.go create mode 100644 vendor/src/github.com/go-check/check/run_test.go diff --git a/hack/vendor.sh b/hack/vendor.sh index 2e0437e67..8fed05852 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -57,6 +57,8 @@ clone git github.com/Sirupsen/logrus v0.7.2 clone git github.com/go-fsnotify/fsnotify v1.0.4 +clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673 + # get Go tip's archive/tar, for xattr support and improved performance # TODO after Go 1.4 drops, bump our minimum supported version and drop this vendored dep if [ "$1" = '--go' ]; then diff --git a/vendor/src/github.com/go-check/check/.gitignore b/vendor/src/github.com/go-check/check/.gitignore new file mode 100644 index 000000000..191a5360b --- /dev/null +++ b/vendor/src/github.com/go-check/check/.gitignore @@ -0,0 +1,4 @@ +_* +*.swp +*.[568] +[568].out diff --git a/vendor/src/github.com/go-check/check/LICENSE b/vendor/src/github.com/go-check/check/LICENSE new file mode 100644 index 000000000..545cf2d33 --- /dev/null +++ b/vendor/src/github.com/go-check/check/LICENSE @@ -0,0 +1,25 @@ +Gocheck - A rich testing framework for Go + +Copyright (c) 2010-2013 Gustavo Niemeyer + +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 OWNER 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/go-check/check/README.md b/vendor/src/github.com/go-check/check/README.md new file mode 100644 index 000000000..0ca9e5726 --- /dev/null +++ b/vendor/src/github.com/go-check/check/README.md @@ -0,0 +1,20 @@ +Instructions +============ + +Install the package with: + + go get gopkg.in/check.v1 + +Import it with: + + import "gopkg.in/check.v1" + +and use _check_ as the package name inside the code. + +For more details, visit the project page: + +* http://labix.org/gocheck + +and the API documentation: + +* https://gopkg.in/check.v1 diff --git a/vendor/src/github.com/go-check/check/TODO b/vendor/src/github.com/go-check/check/TODO new file mode 100644 index 000000000..33498270e --- /dev/null +++ b/vendor/src/github.com/go-check/check/TODO @@ -0,0 +1,2 @@ +- Assert(slice, Contains, item) +- Parallel test support diff --git a/vendor/src/github.com/go-check/check/benchmark.go b/vendor/src/github.com/go-check/check/benchmark.go new file mode 100644 index 000000000..48cb8c811 --- /dev/null +++ b/vendor/src/github.com/go-check/check/benchmark.go @@ -0,0 +1,163 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package check + +import ( + "fmt" + "runtime" + "time" +) + +var memStats runtime.MemStats + +// testingB is a type passed to Benchmark functions to manage benchmark +// timing and to specify the number of iterations to run. +type timer struct { + start time.Time // Time test or benchmark started + duration time.Duration + N int + bytes int64 + timerOn bool + benchTime time.Duration + // The initial states of memStats.Mallocs and memStats.TotalAlloc. + startAllocs uint64 + startBytes uint64 + // The net total of this test after being run. + netAllocs uint64 + netBytes uint64 +} + +// StartTimer starts timing a test. This function is called automatically +// before a benchmark starts, but it can also used to resume timing after +// a call to StopTimer. +func (c *C) StartTimer() { + if !c.timerOn { + c.start = time.Now() + c.timerOn = true + + runtime.ReadMemStats(&memStats) + c.startAllocs = memStats.Mallocs + c.startBytes = memStats.TotalAlloc + } +} + +// StopTimer stops timing a test. This can be used to pause the timer +// while performing complex initialization that you don't +// want to measure. +func (c *C) StopTimer() { + if c.timerOn { + c.duration += time.Now().Sub(c.start) + c.timerOn = false + runtime.ReadMemStats(&memStats) + c.netAllocs += memStats.Mallocs - c.startAllocs + c.netBytes += memStats.TotalAlloc - c.startBytes + } +} + +// ResetTimer sets the elapsed benchmark time to zero. +// It does not affect whether the timer is running. +func (c *C) ResetTimer() { + if c.timerOn { + c.start = time.Now() + runtime.ReadMemStats(&memStats) + c.startAllocs = memStats.Mallocs + c.startBytes = memStats.TotalAlloc + } + c.duration = 0 + c.netAllocs = 0 + c.netBytes = 0 +} + +// SetBytes informs the number of bytes that the benchmark processes +// on each iteration. If this is called in a benchmark it will also +// report MB/s. +func (c *C) SetBytes(n int64) { + c.bytes = n +} + +func (c *C) nsPerOp() int64 { + if c.N <= 0 { + return 0 + } + return c.duration.Nanoseconds() / int64(c.N) +} + +func (c *C) mbPerSec() float64 { + if c.bytes <= 0 || c.duration <= 0 || c.N <= 0 { + return 0 + } + return (float64(c.bytes) * float64(c.N) / 1e6) / c.duration.Seconds() +} + +func (c *C) timerString() string { + if c.N <= 0 { + return fmt.Sprintf("%3.3fs", float64(c.duration.Nanoseconds())/1e9) + } + mbs := c.mbPerSec() + mb := "" + if mbs != 0 { + mb = fmt.Sprintf("\t%7.2f MB/s", mbs) + } + nsop := c.nsPerOp() + ns := fmt.Sprintf("%10d ns/op", nsop) + if c.N > 0 && nsop < 100 { + // The format specifiers here make sure that + // the ones digits line up for all three possible formats. + if nsop < 10 { + ns = fmt.Sprintf("%13.2f ns/op", float64(c.duration.Nanoseconds())/float64(c.N)) + } else { + ns = fmt.Sprintf("%12.1f ns/op", float64(c.duration.Nanoseconds())/float64(c.N)) + } + } + memStats := "" + if c.benchMem { + allocedBytes := fmt.Sprintf("%8d B/op", int64(c.netBytes)/int64(c.N)) + allocs := fmt.Sprintf("%8d allocs/op", int64(c.netAllocs)/int64(c.N)) + memStats = fmt.Sprintf("\t%s\t%s", allocedBytes, allocs) + } + return fmt.Sprintf("%8d\t%s%s%s", c.N, ns, mb, memStats) +} + +func min(x, y int) int { + if x > y { + return y + } + return x +} + +func max(x, y int) int { + if x < y { + return y + } + return x +} + +// roundDown10 rounds a number down to the nearest power of 10. +func roundDown10(n int) int { + var tens = 0 + // tens = floor(log_10(n)) + for n > 10 { + n = n / 10 + tens++ + } + // result = 10^tens + result := 1 + for i := 0; i < tens; i++ { + result *= 10 + } + return result +} + +// roundUp rounds x up to a number of the form [1eX, 2eX, 5eX]. +func roundUp(n int) int { + base := roundDown10(n) + if n < (2 * base) { + return 2 * base + } + if n < (5 * base) { + return 5 * base + } + return 10 * base +} diff --git a/vendor/src/github.com/go-check/check/benchmark_test.go b/vendor/src/github.com/go-check/check/benchmark_test.go new file mode 100644 index 000000000..4dd827c16 --- /dev/null +++ b/vendor/src/github.com/go-check/check/benchmark_test.go @@ -0,0 +1,91 @@ +// These tests verify the test running logic. + +package check_test + +import ( + "time" + . "gopkg.in/check.v1" +) + +var benchmarkS = Suite(&BenchmarkS{}) + +type BenchmarkS struct{} + +func (s *BenchmarkS) TestCountSuite(c *C) { + suitesRun += 1 +} + +func (s *BenchmarkS) TestBasicTestTiming(c *C) { + helper := FixtureHelper{sleepOn: "Test1", sleep: 1000000 * time.Nanosecond} + output := String{} + runConf := RunConf{Output: &output, Verbose: true} + Run(&helper, &runConf) + + expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test1\t0\\.001s\n" + + "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test2\t0\\.000s\n" + c.Assert(output.value, Matches, expected) +} + +func (s *BenchmarkS) TestStreamTestTiming(c *C) { + helper := FixtureHelper{sleepOn: "SetUpSuite", sleep: 1000000 * time.Nanosecond} + output := String{} + runConf := RunConf{Output: &output, Stream: true} + Run(&helper, &runConf) + + expected := "(?s).*\nPASS: check_test\\.go:[0-9]+: FixtureHelper\\.SetUpSuite\t *0\\.001s\n.*" + c.Assert(output.value, Matches, expected) +} + +func (s *BenchmarkS) TestBenchmark(c *C) { + helper := FixtureHelper{sleep: 100000} + output := String{} + runConf := RunConf{ + Output: &output, + Benchmark: true, + BenchmarkTime: 10000000, + Filter: "Benchmark1", + } + Run(&helper, &runConf) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Benchmark1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "SetUpTest") + c.Check(helper.calls[5], Equals, "Benchmark1") + c.Check(helper.calls[6], Equals, "TearDownTest") + // ... and more. + + expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Benchmark1\t *100\t *[12][0-9]{5} ns/op\n" + c.Assert(output.value, Matches, expected) +} + +func (s *BenchmarkS) TestBenchmarkBytes(c *C) { + helper := FixtureHelper{sleep: 100000} + output := String{} + runConf := RunConf{ + Output: &output, + Benchmark: true, + BenchmarkTime: 10000000, + Filter: "Benchmark2", + } + Run(&helper, &runConf) + + expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Benchmark2\t *100\t *[12][0-9]{5} ns/op\t *[4-9]\\.[0-9]{2} MB/s\n" + c.Assert(output.value, Matches, expected) +} + +func (s *BenchmarkS) TestBenchmarkMem(c *C) { + helper := FixtureHelper{sleep: 100000} + output := String{} + runConf := RunConf{ + Output: &output, + Benchmark: true, + BenchmarkMem: true, + BenchmarkTime: 10000000, + Filter: "Benchmark3", + } + Run(&helper, &runConf) + + expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Benchmark3\t *100\t *[12][0-9]{5} ns/op\t *[0-9]+ B/op\t *[1-9] allocs/op\n" + c.Assert(output.value, Matches, expected) +} diff --git a/vendor/src/github.com/go-check/check/bootstrap_test.go b/vendor/src/github.com/go-check/check/bootstrap_test.go new file mode 100644 index 000000000..e55f327c7 --- /dev/null +++ b/vendor/src/github.com/go-check/check/bootstrap_test.go @@ -0,0 +1,82 @@ +// These initial tests are for bootstrapping. They verify that we can +// basically use the testing infrastructure itself to check if the test +// system is working. +// +// These tests use will break down the test runner badly in case of +// errors because if they simply fail, we can't be sure the developer +// will ever see anything (because failing means the failing system +// somehow isn't working! :-) +// +// Do not assume *any* internal functionality works as expected besides +// what's actually tested here. + +package check_test + +import ( + "fmt" + "gopkg.in/check.v1" + "strings" +) + +type BootstrapS struct{} + +var boostrapS = check.Suite(&BootstrapS{}) + +func (s *BootstrapS) TestCountSuite(c *check.C) { + suitesRun += 1 +} + +func (s *BootstrapS) TestFailedAndFail(c *check.C) { + if c.Failed() { + critical("c.Failed() must be false first!") + } + c.Fail() + if !c.Failed() { + critical("c.Fail() didn't put the test in a failed state!") + } + c.Succeed() +} + +func (s *BootstrapS) TestFailedAndSucceed(c *check.C) { + c.Fail() + c.Succeed() + if c.Failed() { + critical("c.Succeed() didn't put the test back in a non-failed state") + } +} + +func (s *BootstrapS) TestLogAndGetTestLog(c *check.C) { + c.Log("Hello there!") + log := c.GetTestLog() + if log != "Hello there!\n" { + critical(fmt.Sprintf("Log() or GetTestLog() is not working! Got: %#v", log)) + } +} + +func (s *BootstrapS) TestLogfAndGetTestLog(c *check.C) { + c.Logf("Hello %v", "there!") + log := c.GetTestLog() + if log != "Hello there!\n" { + critical(fmt.Sprintf("Logf() or GetTestLog() is not working! Got: %#v", log)) + } +} + +func (s *BootstrapS) TestRunShowsErrors(c *check.C) { + output := String{} + check.Run(&FailHelper{}, &check.RunConf{Output: &output}) + if strings.Index(output.value, "Expected failure!") == -1 { + critical(fmt.Sprintf("RunWithWriter() output did not contain the "+ + "expected failure! Got: %#v", + output.value)) + } +} + +func (s *BootstrapS) TestRunDoesntShowSuccesses(c *check.C) { + output := String{} + check.Run(&SuccessHelper{}, &check.RunConf{Output: &output}) + if strings.Index(output.value, "Expected success!") != -1 { + critical(fmt.Sprintf("RunWithWriter() output contained a successful "+ + "test! Got: %#v", + output.value)) + } +} diff --git a/vendor/src/github.com/go-check/check/check.go b/vendor/src/github.com/go-check/check/check.go new file mode 100644 index 000000000..ca8c0f92d --- /dev/null +++ b/vendor/src/github.com/go-check/check/check.go @@ -0,0 +1,945 @@ +// Package check is a rich testing extension for Go's testing package. +// +// For details about the project, see: +// +// http://labix.org/gocheck +// +package check + +import ( + "bytes" + "errors" + "fmt" + "io" + "math/rand" + "os" + "path" + "path/filepath" + "reflect" + "regexp" + "runtime" + "strconv" + "strings" + "sync" + "time" +) + +// ----------------------------------------------------------------------- +// Internal type which deals with suite method calling. + +const ( + fixtureKd = iota + testKd +) + +type funcKind int + +const ( + succeededSt = iota + failedSt + skippedSt + panickedSt + fixturePanickedSt + missedSt +) + +type funcStatus int + +// A method value can't reach its own Method structure. +type methodType struct { + reflect.Value + Info reflect.Method +} + +func newMethod(receiver reflect.Value, i int) *methodType { + return &methodType{receiver.Method(i), receiver.Type().Method(i)} +} + +func (method *methodType) PC() uintptr { + return method.Info.Func.Pointer() +} + +func (method *methodType) suiteName() string { + t := method.Info.Type.In(0) + if t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t.Name() +} + +func (method *methodType) String() string { + return method.suiteName() + "." + method.Info.Name +} + +func (method *methodType) matches(re *regexp.Regexp) bool { + return (re.MatchString(method.Info.Name) || + re.MatchString(method.suiteName()) || + re.MatchString(method.String())) +} + +type C struct { + method *methodType + kind funcKind + testName string + status funcStatus + logb *logger + logw io.Writer + done chan *C + reason string + mustFail bool + tempDir *tempDir + benchMem bool + startTime time.Time + timer +} + +func (c *C) stopNow() { + runtime.Goexit() +} + +// logger is a concurrency safe byte.Buffer +type logger struct { + sync.Mutex + writer bytes.Buffer +} + +func (l *logger) Write(buf []byte) (int, error) { + l.Lock() + defer l.Unlock() + return l.writer.Write(buf) +} + +func (l *logger) WriteTo(w io.Writer) (int64, error) { + l.Lock() + defer l.Unlock() + return l.writer.WriteTo(w) +} + +func (l *logger) String() string { + l.Lock() + defer l.Unlock() + return l.writer.String() +} + +// ----------------------------------------------------------------------- +// Handling of temporary files and directories. + +type tempDir struct { + sync.Mutex + path string + counter int +} + +func (td *tempDir) newPath() string { + td.Lock() + defer td.Unlock() + if td.path == "" { + var err error + for i := 0; i != 100; i++ { + path := fmt.Sprintf("%s%ccheck-%d", os.TempDir(), os.PathSeparator, rand.Int()) + if err = os.Mkdir(path, 0700); err == nil { + td.path = path + break + } + } + if td.path == "" { + panic("Couldn't create temporary directory: " + err.Error()) + } + } + result := filepath.Join(td.path, strconv.Itoa(td.counter)) + td.counter += 1 + return result +} + +func (td *tempDir) removeAll() { + td.Lock() + defer td.Unlock() + if td.path != "" { + err := os.RemoveAll(td.path) + if err != nil { + fmt.Fprintf(os.Stderr, "WARNING: Error cleaning up temporaries: "+err.Error()) + } + } +} + +// Create a new temporary directory which is automatically removed after +// the suite finishes running. +func (c *C) MkDir() string { + path := c.tempDir.newPath() + if err := os.Mkdir(path, 0700); err != nil { + panic(fmt.Sprintf("Couldn't create temporary directory %s: %s", path, err.Error())) + } + return path +} + +// ----------------------------------------------------------------------- +// Low-level logging functions. + +func (c *C) log(args ...interface{}) { + c.writeLog([]byte(fmt.Sprint(args...) + "\n")) +} + +func (c *C) logf(format string, args ...interface{}) { + c.writeLog([]byte(fmt.Sprintf(format+"\n", args...))) +} + +func (c *C) logNewLine() { + c.writeLog([]byte{'\n'}) +} + +func (c *C) writeLog(buf []byte) { + c.logb.Write(buf) + if c.logw != nil { + c.logw.Write(buf) + } +} + +func hasStringOrError(x interface{}) (ok bool) { + _, ok = x.(fmt.Stringer) + if ok { + return + } + _, ok = x.(error) + return +} + +func (c *C) logValue(label string, value interface{}) { + if label == "" { + if hasStringOrError(value) { + c.logf("... %#v (%q)", value, value) + } else { + c.logf("... %#v", value) + } + } else if value == nil { + c.logf("... %s = nil", label) + } else { + if hasStringOrError(value) { + fv := fmt.Sprintf("%#v", value) + qv := fmt.Sprintf("%q", value) + if fv != qv { + c.logf("... %s %s = %s (%s)", label, reflect.TypeOf(value), fv, qv) + return + } + } + if s, ok := value.(string); ok && isMultiLine(s) { + c.logf(`... %s %s = "" +`, label, reflect.TypeOf(value)) + c.logMultiLine(s) + } else { + c.logf("... %s %s = %#v", label, reflect.TypeOf(value), value) + } + } +} + +func (c *C) logMultiLine(s string) { + b := make([]byte, 0, len(s)*2) + i := 0 + n := len(s) + for i < n { + j := i + 1 + for j < n && s[j-1] != '\n' { + j++ + } + b = append(b, "... "...) + b = strconv.AppendQuote(b, s[i:j]) + if j < n { + b = append(b, " +"...) + } + b = append(b, '\n') + i = j + } + c.writeLog(b) +} + +func isMultiLine(s string) bool { + for i := 0; i+1 < len(s); i++ { + if s[i] == '\n' { + return true + } + } + return false +} + +func (c *C) logString(issue string) { + c.log("... ", issue) +} + +func (c *C) logCaller(skip int) { + // This is a bit heavier than it ought to be. + skip += 1 // Our own frame. + pc, callerFile, callerLine, ok := runtime.Caller(skip) + if !ok { + return + } + var testFile string + var testLine int + testFunc := runtime.FuncForPC(c.method.PC()) + if runtime.FuncForPC(pc) != testFunc { + for { + skip += 1 + if pc, file, line, ok := runtime.Caller(skip); ok { + // Note that the test line may be different on + // distinct calls for the same test. Showing + // the "internal" line is helpful when debugging. + if runtime.FuncForPC(pc) == testFunc { + testFile, testLine = file, line + break + } + } else { + break + } + } + } + if testFile != "" && (testFile != callerFile || testLine != callerLine) { + c.logCode(testFile, testLine) + } + c.logCode(callerFile, callerLine) +} + +func (c *C) logCode(path string, line int) { + c.logf("%s:%d:", nicePath(path), line) + code, err := printLine(path, line) + if code == "" { + code = "..." // XXX Open the file and take the raw line. + if err != nil { + code += err.Error() + } + } + c.log(indent(code, " ")) +} + +var valueGo = filepath.Join("reflect", "value.go") +var asmGo = filepath.Join("runtime", "asm_") + +func (c *C) logPanic(skip int, value interface{}) { + skip++ // Our own frame. + initialSkip := skip + for ; ; skip++ { + if pc, file, line, ok := runtime.Caller(skip); ok { + if skip == initialSkip { + c.logf("... Panic: %s (PC=0x%X)\n", value, pc) + } + name := niceFuncName(pc) + path := nicePath(file) + if strings.Contains(path, "/gopkg.in/check.v") { + continue + } + if name == "Value.call" && strings.HasSuffix(path, valueGo) { + continue + } + if name == "call16" && strings.Contains(path, asmGo) { + continue + } + c.logf("%s:%d\n in %s", nicePath(file), line, name) + } else { + break + } + } +} + +func (c *C) logSoftPanic(issue string) { + c.log("... Panic: ", issue) +} + +func (c *C) logArgPanic(method *methodType, expectedType string) { + c.logf("... Panic: %s argument should be %s", + niceFuncName(method.PC()), expectedType) +} + +// ----------------------------------------------------------------------- +// Some simple formatting helpers. + +var initWD, initWDErr = os.Getwd() + +func init() { + if initWDErr == nil { + initWD = strings.Replace(initWD, "\\", "/", -1) + "/" + } +} + +func nicePath(path string) string { + if initWDErr == nil { + if strings.HasPrefix(path, initWD) { + return path[len(initWD):] + } + } + return path +} + +func niceFuncPath(pc uintptr) string { + function := runtime.FuncForPC(pc) + if function != nil { + filename, line := function.FileLine(pc) + return fmt.Sprintf("%s:%d", nicePath(filename), line) + } + return "" +} + +func niceFuncName(pc uintptr) string { + function := runtime.FuncForPC(pc) + if function != nil { + name := path.Base(function.Name()) + if i := strings.Index(name, "."); i > 0 { + name = name[i+1:] + } + if strings.HasPrefix(name, "(*") { + if i := strings.Index(name, ")"); i > 0 { + name = name[2:i] + name[i+1:] + } + } + if i := strings.LastIndex(name, ".*"); i != -1 { + name = name[:i] + "." + name[i+2:] + } + if i := strings.LastIndex(name, "·"); i != -1 { + name = name[:i] + "." + name[i+2:] + } + return name + } + return "" +} + +// ----------------------------------------------------------------------- +// Result tracker to aggregate call results. + +type Result struct { + Succeeded int + Failed int + Skipped int + Panicked int + FixturePanicked int + ExpectedFailures int + Missed int // Not even tried to run, related to a panic in the fixture. + RunError error // Houston, we've got a problem. + WorkDir string // If KeepWorkDir is true +} + +type resultTracker struct { + result Result + _lastWasProblem bool + _waiting int + _missed int + _expectChan chan *C + _doneChan chan *C + _stopChan chan bool +} + +func newResultTracker() *resultTracker { + return &resultTracker{_expectChan: make(chan *C), // Synchronous + _doneChan: make(chan *C, 32), // Asynchronous + _stopChan: make(chan bool)} // Synchronous +} + +func (tracker *resultTracker) start() { + go tracker._loopRoutine() +} + +func (tracker *resultTracker) waitAndStop() { + <-tracker._stopChan +} + +func (tracker *resultTracker) expectCall(c *C) { + tracker._expectChan <- c +} + +func (tracker *resultTracker) callDone(c *C) { + tracker._doneChan <- c +} + +func (tracker *resultTracker) _loopRoutine() { + for { + var c *C + if tracker._waiting > 0 { + // Calls still running. Can't stop. + select { + // XXX Reindent this (not now to make diff clear) + case c = <-tracker._expectChan: + tracker._waiting += 1 + case c = <-tracker._doneChan: + tracker._waiting -= 1 + switch c.status { + case succeededSt: + if c.kind == testKd { + if c.mustFail { + tracker.result.ExpectedFailures++ + } else { + tracker.result.Succeeded++ + } + } + case failedSt: + tracker.result.Failed++ + case panickedSt: + if c.kind == fixtureKd { + tracker.result.FixturePanicked++ + } else { + tracker.result.Panicked++ + } + case fixturePanickedSt: + // Track it as missed, since the panic + // was on the fixture, not on the test. + tracker.result.Missed++ + case missedSt: + tracker.result.Missed++ + case skippedSt: + if c.kind == testKd { + tracker.result.Skipped++ + } + } + } + } else { + // No calls. Can stop, but no done calls here. + select { + case tracker._stopChan <- true: + return + case c = <-tracker._expectChan: + tracker._waiting += 1 + case c = <-tracker._doneChan: + panic("Tracker got an unexpected done call.") + } + } + } +} + +// ----------------------------------------------------------------------- +// The underlying suite runner. + +type suiteRunner struct { + suite interface{} + setUpSuite, tearDownSuite *methodType + setUpTest, tearDownTest *methodType + tests []*methodType + tracker *resultTracker + tempDir *tempDir + keepDir bool + output *outputWriter + reportedProblemLast bool + benchTime time.Duration + benchMem bool +} + +type RunConf struct { + Output io.Writer + Stream bool + Verbose bool + Filter string + Benchmark bool + BenchmarkTime time.Duration // Defaults to 1 second + BenchmarkMem bool + KeepWorkDir bool +} + +// Create a new suiteRunner able to run all methods in the given suite. +func newSuiteRunner(suite interface{}, runConf *RunConf) *suiteRunner { + var conf RunConf + if runConf != nil { + conf = *runConf + } + if conf.Output == nil { + conf.Output = os.Stdout + } + if conf.Benchmark { + conf.Verbose = true + } + + suiteType := reflect.TypeOf(suite) + suiteNumMethods := suiteType.NumMethod() + suiteValue := reflect.ValueOf(suite) + + runner := &suiteRunner{ + suite: suite, + output: newOutputWriter(conf.Output, conf.Stream, conf.Verbose), + tracker: newResultTracker(), + benchTime: conf.BenchmarkTime, + benchMem: conf.BenchmarkMem, + tempDir: &tempDir{}, + keepDir: conf.KeepWorkDir, + tests: make([]*methodType, 0, suiteNumMethods), + } + if runner.benchTime == 0 { + runner.benchTime = 1 * time.Second + } + + var filterRegexp *regexp.Regexp + if conf.Filter != "" { + if regexp, err := regexp.Compile(conf.Filter); err != nil { + msg := "Bad filter expression: " + err.Error() + runner.tracker.result.RunError = errors.New(msg) + return runner + } else { + filterRegexp = regexp + } + } + + for i := 0; i != suiteNumMethods; i++ { + method := newMethod(suiteValue, i) + switch method.Info.Name { + case "SetUpSuite": + runner.setUpSuite = method + case "TearDownSuite": + runner.tearDownSuite = method + case "SetUpTest": + runner.setUpTest = method + case "TearDownTest": + runner.tearDownTest = method + default: + prefix := "Test" + if conf.Benchmark { + prefix = "Benchmark" + } + if !strings.HasPrefix(method.Info.Name, prefix) { + continue + } + if filterRegexp == nil || method.matches(filterRegexp) { + runner.tests = append(runner.tests, method) + } + } + } + return runner +} + +// Run all methods in the given suite. +func (runner *suiteRunner) run() *Result { + if runner.tracker.result.RunError == nil && len(runner.tests) > 0 { + runner.tracker.start() + if runner.checkFixtureArgs() { + c := runner.runFixture(runner.setUpSuite, "", nil) + if c == nil || c.status == succeededSt { + for i := 0; i != len(runner.tests); i++ { + c := runner.runTest(runner.tests[i]) + if c.status == fixturePanickedSt { + runner.skipTests(missedSt, runner.tests[i+1:]) + break + } + } + } else if c != nil && c.status == skippedSt { + runner.skipTests(skippedSt, runner.tests) + } else { + runner.skipTests(missedSt, runner.tests) + } + runner.runFixture(runner.tearDownSuite, "", nil) + } else { + runner.skipTests(missedSt, runner.tests) + } + runner.tracker.waitAndStop() + if runner.keepDir { + runner.tracker.result.WorkDir = runner.tempDir.path + } else { + runner.tempDir.removeAll() + } + } + return &runner.tracker.result +} + +// Create a call object with the given suite method, and fork a +// goroutine with the provided dispatcher for running it. +func (runner *suiteRunner) forkCall(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C { + var logw io.Writer + if runner.output.Stream { + logw = runner.output + } + if logb == nil { + logb = new(logger) + } + c := &C{ + method: method, + kind: kind, + testName: testName, + logb: logb, + logw: logw, + tempDir: runner.tempDir, + done: make(chan *C, 1), + timer: timer{benchTime: runner.benchTime}, + startTime: time.Now(), + benchMem: runner.benchMem, + } + runner.tracker.expectCall(c) + go (func() { + runner.reportCallStarted(c) + defer runner.callDone(c) + dispatcher(c) + })() + return c +} + +// Same as forkCall(), but wait for call to finish before returning. +func (runner *suiteRunner) runFunc(method *methodType, kind funcKind, testName string, logb *logger, dispatcher func(c *C)) *C { + c := runner.forkCall(method, kind, testName, logb, dispatcher) + <-c.done + return c +} + +// Handle a finished call. If there were any panics, update the call status +// accordingly. Then, mark the call as done and report to the tracker. +func (runner *suiteRunner) callDone(c *C) { + value := recover() + if value != nil { + switch v := value.(type) { + case *fixturePanic: + if v.status == skippedSt { + c.status = skippedSt + } else { + c.logSoftPanic("Fixture has panicked (see related PANIC)") + c.status = fixturePanickedSt + } + default: + c.logPanic(1, value) + c.status = panickedSt + } + } + if c.mustFail { + switch c.status { + case failedSt: + c.status = succeededSt + case succeededSt: + c.status = failedSt + c.logString("Error: Test succeeded, but was expected to fail") + c.logString("Reason: " + c.reason) + } + } + + runner.reportCallDone(c) + c.done <- c +} + +// Runs a fixture call synchronously. The fixture will still be run in a +// goroutine like all suite methods, but this method will not return +// while the fixture goroutine is not done, because the fixture must be +// run in a desired order. +func (runner *suiteRunner) runFixture(method *methodType, testName string, logb *logger) *C { + if method != nil { + c := runner.runFunc(method, fixtureKd, testName, logb, func(c *C) { + c.ResetTimer() + c.StartTimer() + defer c.StopTimer() + c.method.Call([]reflect.Value{reflect.ValueOf(c)}) + }) + return c + } + return nil +} + +// Run the fixture method with runFixture(), but panic with a fixturePanic{} +// in case the fixture method panics. This makes it easier to track the +// fixture panic together with other call panics within forkTest(). +func (runner *suiteRunner) runFixtureWithPanic(method *methodType, testName string, logb *logger, skipped *bool) *C { + if skipped != nil && *skipped { + return nil + } + c := runner.runFixture(method, testName, logb) + if c != nil && c.status != succeededSt { + if skipped != nil { + *skipped = c.status == skippedSt + } + panic(&fixturePanic{c.status, method}) + } + return c +} + +type fixturePanic struct { + status funcStatus + method *methodType +} + +// Run the suite test method, together with the test-specific fixture, +// asynchronously. +func (runner *suiteRunner) forkTest(method *methodType) *C { + testName := method.String() + return runner.forkCall(method, testKd, testName, nil, func(c *C) { + var skipped bool + defer runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, &skipped) + defer c.StopTimer() + benchN := 1 + for { + runner.runFixtureWithPanic(runner.setUpTest, testName, c.logb, &skipped) + mt := c.method.Type() + if mt.NumIn() != 1 || mt.In(0) != reflect.TypeOf(c) { + // Rather than a plain panic, provide a more helpful message when + // the argument type is incorrect. + c.status = panickedSt + c.logArgPanic(c.method, "*check.C") + return + } + if strings.HasPrefix(c.method.Info.Name, "Test") { + c.ResetTimer() + c.StartTimer() + c.method.Call([]reflect.Value{reflect.ValueOf(c)}) + return + } + if !strings.HasPrefix(c.method.Info.Name, "Benchmark") { + panic("unexpected method prefix: " + c.method.Info.Name) + } + + runtime.GC() + c.N = benchN + c.ResetTimer() + c.StartTimer() + c.method.Call([]reflect.Value{reflect.ValueOf(c)}) + c.StopTimer() + if c.status != succeededSt || c.duration >= c.benchTime || benchN >= 1e9 { + return + } + perOpN := int(1e9) + if c.nsPerOp() != 0 { + perOpN = int(c.benchTime.Nanoseconds() / c.nsPerOp()) + } + + // Logic taken from the stock testing package: + // - Run more iterations than we think we'll need for a second (1.5x). + // - Don't grow too fast in case we had timing errors previously. + // - Be sure to run at least one more than last time. + benchN = max(min(perOpN+perOpN/2, 100*benchN), benchN+1) + benchN = roundUp(benchN) + + skipped = true // Don't run the deferred one if this panics. + runner.runFixtureWithPanic(runner.tearDownTest, testName, nil, nil) + skipped = false + } + }) +} + +// Same as forkTest(), but wait for the test to finish before returning. +func (runner *suiteRunner) runTest(method *methodType) *C { + c := runner.forkTest(method) + <-c.done + return c +} + +// Helper to mark tests as skipped or missed. A bit heavy for what +// it does, but it enables homogeneous handling of tracking, including +// nice verbose output. +func (runner *suiteRunner) skipTests(status funcStatus, methods []*methodType) { + for _, method := range methods { + runner.runFunc(method, testKd, "", nil, func(c *C) { + c.status = status + }) + } +} + +// Verify if the fixture arguments are *check.C. In case of errors, +// log the error as a panic in the fixture method call, and return false. +func (runner *suiteRunner) checkFixtureArgs() bool { + succeeded := true + argType := reflect.TypeOf(&C{}) + for _, method := range []*methodType{runner.setUpSuite, runner.tearDownSuite, runner.setUpTest, runner.tearDownTest} { + if method != nil { + mt := method.Type() + if mt.NumIn() != 1 || mt.In(0) != argType { + succeeded = false + runner.runFunc(method, fixtureKd, "", nil, func(c *C) { + c.logArgPanic(method, "*check.C") + c.status = panickedSt + }) + } + } + } + return succeeded +} + +func (runner *suiteRunner) reportCallStarted(c *C) { + runner.output.WriteCallStarted("START", c) +} + +func (runner *suiteRunner) reportCallDone(c *C) { + runner.tracker.callDone(c) + switch c.status { + case succeededSt: + if c.mustFail { + runner.output.WriteCallSuccess("FAIL EXPECTED", c) + } else { + runner.output.WriteCallSuccess("PASS", c) + } + case skippedSt: + runner.output.WriteCallSuccess("SKIP", c) + case failedSt: + runner.output.WriteCallProblem("FAIL", c) + case panickedSt: + runner.output.WriteCallProblem("PANIC", c) + case fixturePanickedSt: + // That's a testKd call reporting that its fixture + // has panicked. The fixture call which caused the + // panic itself was tracked above. We'll report to + // aid debugging. + runner.output.WriteCallProblem("PANIC", c) + case missedSt: + runner.output.WriteCallSuccess("MISS", c) + } +} + +// ----------------------------------------------------------------------- +// Output writer manages atomic output writing according to settings. + +type outputWriter struct { + m sync.Mutex + writer io.Writer + wroteCallProblemLast bool + Stream bool + Verbose bool +} + +func newOutputWriter(writer io.Writer, stream, verbose bool) *outputWriter { + return &outputWriter{writer: writer, Stream: stream, Verbose: verbose} +} + +func (ow *outputWriter) Write(content []byte) (n int, err error) { + ow.m.Lock() + n, err = ow.writer.Write(content) + ow.m.Unlock() + return +} + +func (ow *outputWriter) WriteCallStarted(label string, c *C) { + if ow.Stream { + header := renderCallHeader(label, c, "", "\n") + ow.m.Lock() + ow.writer.Write([]byte(header)) + ow.m.Unlock() + } +} + +func (ow *outputWriter) WriteCallProblem(label string, c *C) { + var prefix string + if !ow.Stream { + prefix = "\n-----------------------------------" + + "-----------------------------------\n" + } + header := renderCallHeader(label, c, prefix, "\n\n") + ow.m.Lock() + ow.wroteCallProblemLast = true + ow.writer.Write([]byte(header)) + if !ow.Stream { + c.logb.WriteTo(ow.writer) + } + ow.m.Unlock() +} + +func (ow *outputWriter) WriteCallSuccess(label string, c *C) { + if ow.Stream || (ow.Verbose && c.kind == testKd) { + // TODO Use a buffer here. + var suffix string + if c.reason != "" { + suffix = " (" + c.reason + ")" + } + if c.status == succeededSt { + suffix += "\t" + c.timerString() + } + suffix += "\n" + if ow.Stream { + suffix += "\n" + } + header := renderCallHeader(label, c, "", suffix) + ow.m.Lock() + // Resist temptation of using line as prefix above due to race. + if !ow.Stream && ow.wroteCallProblemLast { + header = "\n-----------------------------------" + + "-----------------------------------\n" + + header + } + ow.wroteCallProblemLast = false + ow.writer.Write([]byte(header)) + ow.m.Unlock() + } +} + +func renderCallHeader(label string, c *C, prefix, suffix string) string { + pc := c.method.PC() + return fmt.Sprintf("%s%s: %s: %s%s", prefix, label, niceFuncPath(pc), + niceFuncName(pc), suffix) +} diff --git a/vendor/src/github.com/go-check/check/check_test.go b/vendor/src/github.com/go-check/check/check_test.go new file mode 100644 index 000000000..871b32527 --- /dev/null +++ b/vendor/src/github.com/go-check/check/check_test.go @@ -0,0 +1,207 @@ +// This file contains just a few generic helpers which are used by the +// other test files. + +package check_test + +import ( + "flag" + "fmt" + "os" + "regexp" + "runtime" + "testing" + "time" + + "gopkg.in/check.v1" +) + +// We count the number of suites run at least to get a vague hint that the +// test suite is behaving as it should. Otherwise a bug introduced at the +// very core of the system could go unperceived. +const suitesRunExpected = 8 + +var suitesRun int = 0 + +func Test(t *testing.T) { + check.TestingT(t) + if suitesRun != suitesRunExpected && flag.Lookup("check.f").Value.String() == "" { + critical(fmt.Sprintf("Expected %d suites to run rather than %d", + suitesRunExpected, suitesRun)) + } +} + +// ----------------------------------------------------------------------- +// Helper functions. + +// Break down badly. This is used in test cases which can't yet assume +// that the fundamental bits are working. +func critical(error string) { + fmt.Fprintln(os.Stderr, "CRITICAL: "+error) + os.Exit(1) +} + +// Return the file line where it's called. +func getMyLine() int { + if _, _, line, ok := runtime.Caller(1); ok { + return line + } + return -1 +} + +// ----------------------------------------------------------------------- +// Helper type implementing a basic io.Writer for testing output. + +// Type implementing the io.Writer interface for analyzing output. +type String struct { + value string +} + +// The only function required by the io.Writer interface. Will append +// written data to the String.value string. +func (s *String) Write(p []byte) (n int, err error) { + s.value += string(p) + return len(p), nil +} + +// Trivial wrapper to test errors happening on a different file +// than the test itself. +func checkEqualWrapper(c *check.C, obtained, expected interface{}) (result bool, line int) { + return c.Check(obtained, check.Equals, expected), getMyLine() +} + +// ----------------------------------------------------------------------- +// Helper suite for testing basic fail behavior. + +type FailHelper struct { + testLine int +} + +func (s *FailHelper) TestLogAndFail(c *check.C) { + s.testLine = getMyLine() - 1 + c.Log("Expected failure!") + c.Fail() +} + +// ----------------------------------------------------------------------- +// Helper suite for testing basic success behavior. + +type SuccessHelper struct{} + +func (s *SuccessHelper) TestLogAndSucceed(c *check.C) { + c.Log("Expected success!") +} + +// ----------------------------------------------------------------------- +// Helper suite for testing ordering and behavior of fixture. + +type FixtureHelper struct { + calls []string + panicOn string + skip bool + skipOnN int + sleepOn string + sleep time.Duration + bytes int64 +} + +func (s *FixtureHelper) trace(name string, c *check.C) { + s.calls = append(s.calls, name) + if name == s.panicOn { + panic(name) + } + if s.sleep > 0 && s.sleepOn == name { + time.Sleep(s.sleep) + } + if s.skip && s.skipOnN == len(s.calls)-1 { + c.Skip("skipOnN == n") + } +} + +func (s *FixtureHelper) SetUpSuite(c *check.C) { + s.trace("SetUpSuite", c) +} + +func (s *FixtureHelper) TearDownSuite(c *check.C) { + s.trace("TearDownSuite", c) +} + +func (s *FixtureHelper) SetUpTest(c *check.C) { + s.trace("SetUpTest", c) +} + +func (s *FixtureHelper) TearDownTest(c *check.C) { + s.trace("TearDownTest", c) +} + +func (s *FixtureHelper) Test1(c *check.C) { + s.trace("Test1", c) +} + +func (s *FixtureHelper) Test2(c *check.C) { + s.trace("Test2", c) +} + +func (s *FixtureHelper) Benchmark1(c *check.C) { + s.trace("Benchmark1", c) + for i := 0; i < c.N; i++ { + time.Sleep(s.sleep) + } +} + +func (s *FixtureHelper) Benchmark2(c *check.C) { + s.trace("Benchmark2", c) + c.SetBytes(1024) + for i := 0; i < c.N; i++ { + time.Sleep(s.sleep) + } +} + +func (s *FixtureHelper) Benchmark3(c *check.C) { + var x []int64 + s.trace("Benchmark3", c) + for i := 0; i < c.N; i++ { + time.Sleep(s.sleep) + x = make([]int64, 5) + _ = x + } +} + +// ----------------------------------------------------------------------- +// Helper which checks the state of the test and ensures that it matches +// the given expectations. Depends on c.Errorf() working, so shouldn't +// be used to test this one function. + +type expectedState struct { + name string + result interface{} + failed bool + log string +} + +// Verify the state of the test. Note that since this also verifies if +// the test is supposed to be in a failed state, no other checks should +// be done in addition to what is being tested. +func checkState(c *check.C, result interface{}, expected *expectedState) { + failed := c.Failed() + c.Succeed() + log := c.GetTestLog() + matched, matchError := regexp.MatchString("^"+expected.log+"$", log) + if matchError != nil { + c.Errorf("Error in matching expression used in testing %s", + expected.name) + } else if !matched { + c.Errorf("%s logged:\n----------\n%s----------\n\nExpected:\n----------\n%s\n----------", + expected.name, log, expected.log) + } + if result != expected.result { + c.Errorf("%s returned %#v rather than %#v", + expected.name, result, expected.result) + } + if failed != expected.failed { + if failed { + c.Errorf("%s has failed when it shouldn't", expected.name) + } else { + c.Errorf("%s has not failed when it should", expected.name) + } + } +} diff --git a/vendor/src/github.com/go-check/check/checkers.go b/vendor/src/github.com/go-check/check/checkers.go new file mode 100644 index 000000000..bac338729 --- /dev/null +++ b/vendor/src/github.com/go-check/check/checkers.go @@ -0,0 +1,458 @@ +package check + +import ( + "fmt" + "reflect" + "regexp" +) + +// ----------------------------------------------------------------------- +// CommentInterface and Commentf helper, to attach extra information to checks. + +type comment struct { + format string + args []interface{} +} + +// Commentf returns an infomational value to use with Assert or Check calls. +// If the checker test fails, the provided arguments will be passed to +// fmt.Sprintf, and will be presented next to the logged failure. +// +// For example: +// +// c.Assert(v, Equals, 42, Commentf("Iteration #%d failed.", i)) +// +// Note that if the comment is constant, a better option is to +// simply use a normal comment right above or next to the line, as +// it will also get printed with any errors: +// +// c.Assert(l, Equals, 8192) // Ensure buffer size is correct (bug #123) +// +func Commentf(format string, args ...interface{}) CommentInterface { + return &comment{format, args} +} + +// CommentInterface must be implemented by types that attach extra +// information to failed checks. See the Commentf function for details. +type CommentInterface interface { + CheckCommentString() string +} + +func (c *comment) CheckCommentString() string { + return fmt.Sprintf(c.format, c.args...) +} + +// ----------------------------------------------------------------------- +// The Checker interface. + +// The Checker interface must be provided by checkers used with +// the Assert and Check verification methods. +type Checker interface { + Info() *CheckerInfo + Check(params []interface{}, names []string) (result bool, error string) +} + +// See the Checker interface. +type CheckerInfo struct { + Name string + Params []string +} + +func (info *CheckerInfo) Info() *CheckerInfo { + return info +} + +// ----------------------------------------------------------------------- +// Not checker logic inverter. + +// The Not checker inverts the logic of the provided checker. The +// resulting checker will succeed where the original one failed, and +// vice-versa. +// +// For example: +// +// c.Assert(a, Not(Equals), b) +// +func Not(checker Checker) Checker { + return ¬Checker{checker} +} + +type notChecker struct { + sub Checker +} + +func (checker *notChecker) Info() *CheckerInfo { + info := *checker.sub.Info() + info.Name = "Not(" + info.Name + ")" + return &info +} + +func (checker *notChecker) Check(params []interface{}, names []string) (result bool, error string) { + result, error = checker.sub.Check(params, names) + result = !result + return +} + +// ----------------------------------------------------------------------- +// IsNil checker. + +type isNilChecker struct { + *CheckerInfo +} + +// The IsNil checker tests whether the obtained value is nil. +// +// For example: +// +// c.Assert(err, IsNil) +// +var IsNil Checker = &isNilChecker{ + &CheckerInfo{Name: "IsNil", Params: []string{"value"}}, +} + +func (checker *isNilChecker) Check(params []interface{}, names []string) (result bool, error string) { + return isNil(params[0]), "" +} + +func isNil(obtained interface{}) (result bool) { + if obtained == nil { + result = true + } else { + switch v := reflect.ValueOf(obtained); v.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: + return v.IsNil() + } + } + return +} + +// ----------------------------------------------------------------------- +// NotNil checker. Alias for Not(IsNil), since it's so common. + +type notNilChecker struct { + *CheckerInfo +} + +// The NotNil checker verifies that the obtained value is not nil. +// +// For example: +// +// c.Assert(iface, NotNil) +// +// This is an alias for Not(IsNil), made available since it's a +// fairly common check. +// +var NotNil Checker = ¬NilChecker{ + &CheckerInfo{Name: "NotNil", Params: []string{"value"}}, +} + +func (checker *notNilChecker) Check(params []interface{}, names []string) (result bool, error string) { + return !isNil(params[0]), "" +} + +// ----------------------------------------------------------------------- +// Equals checker. + +type equalsChecker struct { + *CheckerInfo +} + +// The Equals checker verifies that the obtained value is equal to +// the expected value, according to usual Go semantics for ==. +// +// For example: +// +// c.Assert(value, Equals, 42) +// +var Equals Checker = &equalsChecker{ + &CheckerInfo{Name: "Equals", Params: []string{"obtained", "expected"}}, +} + +func (checker *equalsChecker) Check(params []interface{}, names []string) (result bool, error string) { + defer func() { + if v := recover(); v != nil { + result = false + error = fmt.Sprint(v) + } + }() + return params[0] == params[1], "" +} + +// ----------------------------------------------------------------------- +// DeepEquals checker. + +type deepEqualsChecker struct { + *CheckerInfo +} + +// The DeepEquals checker verifies that the obtained value is deep-equal to +// the expected value. The check will work correctly even when facing +// slices, interfaces, and values of different types (which always fail +// the test). +// +// For example: +// +// c.Assert(value, DeepEquals, 42) +// c.Assert(array, DeepEquals, []string{"hi", "there"}) +// +var DeepEquals Checker = &deepEqualsChecker{ + &CheckerInfo{Name: "DeepEquals", Params: []string{"obtained", "expected"}}, +} + +func (checker *deepEqualsChecker) Check(params []interface{}, names []string) (result bool, error string) { + return reflect.DeepEqual(params[0], params[1]), "" +} + +// ----------------------------------------------------------------------- +// HasLen checker. + +type hasLenChecker struct { + *CheckerInfo +} + +// The HasLen checker verifies that the obtained value has the +// provided length. In many cases this is superior to using Equals +// in conjuction with the len function because in case the check +// fails the value itself will be printed, instead of its length, +// providing more details for figuring the problem. +// +// For example: +// +// c.Assert(list, HasLen, 5) +// +var HasLen Checker = &hasLenChecker{ + &CheckerInfo{Name: "HasLen", Params: []string{"obtained", "n"}}, +} + +func (checker *hasLenChecker) Check(params []interface{}, names []string) (result bool, error string) { + n, ok := params[1].(int) + if !ok { + return false, "n must be an int" + } + value := reflect.ValueOf(params[0]) + switch value.Kind() { + case reflect.Map, reflect.Array, reflect.Slice, reflect.Chan, reflect.String: + default: + return false, "obtained value type has no length" + } + return value.Len() == n, "" +} + +// ----------------------------------------------------------------------- +// ErrorMatches checker. + +type errorMatchesChecker struct { + *CheckerInfo +} + +// The ErrorMatches checker verifies that the error value +// is non nil and matches the regular expression provided. +// +// For example: +// +// c.Assert(err, ErrorMatches, "perm.*denied") +// +var ErrorMatches Checker = errorMatchesChecker{ + &CheckerInfo{Name: "ErrorMatches", Params: []string{"value", "regex"}}, +} + +func (checker errorMatchesChecker) Check(params []interface{}, names []string) (result bool, errStr string) { + if params[0] == nil { + return false, "Error value is nil" + } + err, ok := params[0].(error) + if !ok { + return false, "Value is not an error" + } + params[0] = err.Error() + names[0] = "error" + return matches(params[0], params[1]) +} + +// ----------------------------------------------------------------------- +// Matches checker. + +type matchesChecker struct { + *CheckerInfo +} + +// The Matches checker verifies that the string provided as the obtained +// value (or the string resulting from obtained.String()) matches the +// regular expression provided. +// +// For example: +// +// c.Assert(err, Matches, "perm.*denied") +// +var Matches Checker = &matchesChecker{ + &CheckerInfo{Name: "Matches", Params: []string{"value", "regex"}}, +} + +func (checker *matchesChecker) Check(params []interface{}, names []string) (result bool, error string) { + return matches(params[0], params[1]) +} + +func matches(value, regex interface{}) (result bool, error string) { + reStr, ok := regex.(string) + if !ok { + return false, "Regex must be a string" + } + valueStr, valueIsStr := value.(string) + if !valueIsStr { + if valueWithStr, valueHasStr := value.(fmt.Stringer); valueHasStr { + valueStr, valueIsStr = valueWithStr.String(), true + } + } + if valueIsStr { + matches, err := regexp.MatchString("^"+reStr+"$", valueStr) + if err != nil { + return false, "Can't compile regex: " + err.Error() + } + return matches, "" + } + return false, "Obtained value is not a string and has no .String()" +} + +// ----------------------------------------------------------------------- +// Panics checker. + +type panicsChecker struct { + *CheckerInfo +} + +// The Panics checker verifies that calling the provided zero-argument +// function will cause a panic which is deep-equal to the provided value. +// +// For example: +// +// c.Assert(func() { f(1, 2) }, Panics, &SomeErrorType{"BOOM"}). +// +// +var Panics Checker = &panicsChecker{ + &CheckerInfo{Name: "Panics", Params: []string{"function", "expected"}}, +} + +func (checker *panicsChecker) Check(params []interface{}, names []string) (result bool, error string) { + f := reflect.ValueOf(params[0]) + if f.Kind() != reflect.Func || f.Type().NumIn() != 0 { + return false, "Function must take zero arguments" + } + defer func() { + // If the function has not panicked, then don't do the check. + if error != "" { + return + } + params[0] = recover() + names[0] = "panic" + result = reflect.DeepEqual(params[0], params[1]) + }() + f.Call(nil) + return false, "Function has not panicked" +} + +type panicMatchesChecker struct { + *CheckerInfo +} + +// The PanicMatches checker verifies that calling the provided zero-argument +// function will cause a panic with an error value matching +// the regular expression provided. +// +// For example: +// +// c.Assert(func() { f(1, 2) }, PanicMatches, `open.*: no such file or directory`). +// +// +var PanicMatches Checker = &panicMatchesChecker{ + &CheckerInfo{Name: "PanicMatches", Params: []string{"function", "expected"}}, +} + +func (checker *panicMatchesChecker) Check(params []interface{}, names []string) (result bool, errmsg string) { + f := reflect.ValueOf(params[0]) + if f.Kind() != reflect.Func || f.Type().NumIn() != 0 { + return false, "Function must take zero arguments" + } + defer func() { + // If the function has not panicked, then don't do the check. + if errmsg != "" { + return + } + obtained := recover() + names[0] = "panic" + if e, ok := obtained.(error); ok { + params[0] = e.Error() + } else if _, ok := obtained.(string); ok { + params[0] = obtained + } else { + errmsg = "Panic value is not a string or an error" + return + } + result, errmsg = matches(params[0], params[1]) + }() + f.Call(nil) + return false, "Function has not panicked" +} + +// ----------------------------------------------------------------------- +// FitsTypeOf checker. + +type fitsTypeChecker struct { + *CheckerInfo +} + +// The FitsTypeOf checker verifies that the obtained value is +// assignable to a variable with the same type as the provided +// sample value. +// +// For example: +// +// c.Assert(value, FitsTypeOf, int64(0)) +// c.Assert(value, FitsTypeOf, os.Error(nil)) +// +var FitsTypeOf Checker = &fitsTypeChecker{ + &CheckerInfo{Name: "FitsTypeOf", Params: []string{"obtained", "sample"}}, +} + +func (checker *fitsTypeChecker) Check(params []interface{}, names []string) (result bool, error string) { + obtained := reflect.ValueOf(params[0]) + sample := reflect.ValueOf(params[1]) + if !obtained.IsValid() { + return false, "" + } + if !sample.IsValid() { + return false, "Invalid sample value" + } + return obtained.Type().AssignableTo(sample.Type()), "" +} + +// ----------------------------------------------------------------------- +// Implements checker. + +type implementsChecker struct { + *CheckerInfo +} + +// The Implements checker verifies that the obtained value +// implements the interface specified via a pointer to an interface +// variable. +// +// For example: +// +// var e os.Error +// c.Assert(err, Implements, &e) +// +var Implements Checker = &implementsChecker{ + &CheckerInfo{Name: "Implements", Params: []string{"obtained", "ifaceptr"}}, +} + +func (checker *implementsChecker) Check(params []interface{}, names []string) (result bool, error string) { + obtained := reflect.ValueOf(params[0]) + ifaceptr := reflect.ValueOf(params[1]) + if !obtained.IsValid() { + return false, "" + } + if !ifaceptr.IsValid() || ifaceptr.Kind() != reflect.Ptr || ifaceptr.Elem().Kind() != reflect.Interface { + return false, "ifaceptr should be a pointer to an interface variable" + } + return obtained.Type().Implements(ifaceptr.Elem().Type()), "" +} diff --git a/vendor/src/github.com/go-check/check/checkers_test.go b/vendor/src/github.com/go-check/check/checkers_test.go new file mode 100644 index 000000000..5c6974746 --- /dev/null +++ b/vendor/src/github.com/go-check/check/checkers_test.go @@ -0,0 +1,272 @@ +package check_test + +import ( + "errors" + "gopkg.in/check.v1" + "reflect" + "runtime" +) + +type CheckersS struct{} + +var _ = check.Suite(&CheckersS{}) + +func testInfo(c *check.C, checker check.Checker, name string, paramNames []string) { + info := checker.Info() + if info.Name != name { + c.Fatalf("Got name %s, expected %s", info.Name, name) + } + if !reflect.DeepEqual(info.Params, paramNames) { + c.Fatalf("Got param names %#v, expected %#v", info.Params, paramNames) + } +} + +func testCheck(c *check.C, checker check.Checker, result bool, error string, params ...interface{}) ([]interface{}, []string) { + info := checker.Info() + if len(params) != len(info.Params) { + c.Fatalf("unexpected param count in test; expected %d got %d", len(info.Params), len(params)) + } + names := append([]string{}, info.Params...) + result_, error_ := checker.Check(params, names) + if result_ != result || error_ != error { + c.Fatalf("%s.Check(%#v) returned (%#v, %#v) rather than (%#v, %#v)", + info.Name, params, result_, error_, result, error) + } + return params, names +} + +func (s *CheckersS) TestComment(c *check.C) { + bug := check.Commentf("a %d bc", 42) + comment := bug.CheckCommentString() + if comment != "a 42 bc" { + c.Fatalf("Commentf returned %#v", comment) + } +} + +func (s *CheckersS) TestIsNil(c *check.C) { + testInfo(c, check.IsNil, "IsNil", []string{"value"}) + + testCheck(c, check.IsNil, true, "", nil) + testCheck(c, check.IsNil, false, "", "a") + + testCheck(c, check.IsNil, true, "", (chan int)(nil)) + testCheck(c, check.IsNil, false, "", make(chan int)) + testCheck(c, check.IsNil, true, "", (error)(nil)) + testCheck(c, check.IsNil, false, "", errors.New("")) + testCheck(c, check.IsNil, true, "", ([]int)(nil)) + testCheck(c, check.IsNil, false, "", make([]int, 1)) + testCheck(c, check.IsNil, false, "", int(0)) +} + +func (s *CheckersS) TestNotNil(c *check.C) { + testInfo(c, check.NotNil, "NotNil", []string{"value"}) + + testCheck(c, check.NotNil, false, "", nil) + testCheck(c, check.NotNil, true, "", "a") + + testCheck(c, check.NotNil, false, "", (chan int)(nil)) + testCheck(c, check.NotNil, true, "", make(chan int)) + testCheck(c, check.NotNil, false, "", (error)(nil)) + testCheck(c, check.NotNil, true, "", errors.New("")) + testCheck(c, check.NotNil, false, "", ([]int)(nil)) + testCheck(c, check.NotNil, true, "", make([]int, 1)) +} + +func (s *CheckersS) TestNot(c *check.C) { + testInfo(c, check.Not(check.IsNil), "Not(IsNil)", []string{"value"}) + + testCheck(c, check.Not(check.IsNil), false, "", nil) + testCheck(c, check.Not(check.IsNil), true, "", "a") +} + +type simpleStruct struct { + i int +} + +func (s *CheckersS) TestEquals(c *check.C) { + testInfo(c, check.Equals, "Equals", []string{"obtained", "expected"}) + + // The simplest. + testCheck(c, check.Equals, true, "", 42, 42) + testCheck(c, check.Equals, false, "", 42, 43) + + // Different native types. + testCheck(c, check.Equals, false, "", int32(42), int64(42)) + + // With nil. + testCheck(c, check.Equals, false, "", 42, nil) + + // Slices + testCheck(c, check.Equals, false, "runtime error: comparing uncomparable type []uint8", []byte{1, 2}, []byte{1, 2}) + + // Struct values + testCheck(c, check.Equals, true, "", simpleStruct{1}, simpleStruct{1}) + testCheck(c, check.Equals, false, "", simpleStruct{1}, simpleStruct{2}) + + // Struct pointers + testCheck(c, check.Equals, false, "", &simpleStruct{1}, &simpleStruct{1}) + testCheck(c, check.Equals, false, "", &simpleStruct{1}, &simpleStruct{2}) +} + +func (s *CheckersS) TestDeepEquals(c *check.C) { + testInfo(c, check.DeepEquals, "DeepEquals", []string{"obtained", "expected"}) + + // The simplest. + testCheck(c, check.DeepEquals, true, "", 42, 42) + testCheck(c, check.DeepEquals, false, "", 42, 43) + + // Different native types. + testCheck(c, check.DeepEquals, false, "", int32(42), int64(42)) + + // With nil. + testCheck(c, check.DeepEquals, false, "", 42, nil) + + // Slices + testCheck(c, check.DeepEquals, true, "", []byte{1, 2}, []byte{1, 2}) + testCheck(c, check.DeepEquals, false, "", []byte{1, 2}, []byte{1, 3}) + + // Struct values + testCheck(c, check.DeepEquals, true, "", simpleStruct{1}, simpleStruct{1}) + testCheck(c, check.DeepEquals, false, "", simpleStruct{1}, simpleStruct{2}) + + // Struct pointers + testCheck(c, check.DeepEquals, true, "", &simpleStruct{1}, &simpleStruct{1}) + testCheck(c, check.DeepEquals, false, "", &simpleStruct{1}, &simpleStruct{2}) +} + +func (s *CheckersS) TestHasLen(c *check.C) { + testInfo(c, check.HasLen, "HasLen", []string{"obtained", "n"}) + + testCheck(c, check.HasLen, true, "", "abcd", 4) + testCheck(c, check.HasLen, true, "", []int{1, 2}, 2) + testCheck(c, check.HasLen, false, "", []int{1, 2}, 3) + + testCheck(c, check.HasLen, false, "n must be an int", []int{1, 2}, "2") + testCheck(c, check.HasLen, false, "obtained value type has no length", nil, 2) +} + +func (s *CheckersS) TestErrorMatches(c *check.C) { + testInfo(c, check.ErrorMatches, "ErrorMatches", []string{"value", "regex"}) + + testCheck(c, check.ErrorMatches, false, "Error value is nil", nil, "some error") + testCheck(c, check.ErrorMatches, false, "Value is not an error", 1, "some error") + testCheck(c, check.ErrorMatches, true, "", errors.New("some error"), "some error") + testCheck(c, check.ErrorMatches, true, "", errors.New("some error"), "so.*or") + + // Verify params mutation + params, names := testCheck(c, check.ErrorMatches, false, "", errors.New("some error"), "other error") + c.Assert(params[0], check.Equals, "some error") + c.Assert(names[0], check.Equals, "error") +} + +func (s *CheckersS) TestMatches(c *check.C) { + testInfo(c, check.Matches, "Matches", []string{"value", "regex"}) + + // Simple matching + testCheck(c, check.Matches, true, "", "abc", "abc") + testCheck(c, check.Matches, true, "", "abc", "a.c") + + // Must match fully + testCheck(c, check.Matches, false, "", "abc", "ab") + testCheck(c, check.Matches, false, "", "abc", "bc") + + // String()-enabled values accepted + testCheck(c, check.Matches, true, "", reflect.ValueOf("abc"), "a.c") + testCheck(c, check.Matches, false, "", reflect.ValueOf("abc"), "a.d") + + // Some error conditions. + testCheck(c, check.Matches, false, "Obtained value is not a string and has no .String()", 1, "a.c") + testCheck(c, check.Matches, false, "Can't compile regex: error parsing regexp: missing closing ]: `[c$`", "abc", "a[c") +} + +func (s *CheckersS) TestPanics(c *check.C) { + testInfo(c, check.Panics, "Panics", []string{"function", "expected"}) + + // Some errors. + testCheck(c, check.Panics, false, "Function has not panicked", func() bool { return false }, "BOOM") + testCheck(c, check.Panics, false, "Function must take zero arguments", 1, "BOOM") + + // Plain strings. + testCheck(c, check.Panics, true, "", func() { panic("BOOM") }, "BOOM") + testCheck(c, check.Panics, false, "", func() { panic("KABOOM") }, "BOOM") + testCheck(c, check.Panics, true, "", func() bool { panic("BOOM") }, "BOOM") + + // Error values. + testCheck(c, check.Panics, true, "", func() { panic(errors.New("BOOM")) }, errors.New("BOOM")) + testCheck(c, check.Panics, false, "", func() { panic(errors.New("KABOOM")) }, errors.New("BOOM")) + + type deep struct{ i int } + // Deep value + testCheck(c, check.Panics, true, "", func() { panic(&deep{99}) }, &deep{99}) + + // Verify params/names mutation + params, names := testCheck(c, check.Panics, false, "", func() { panic(errors.New("KABOOM")) }, errors.New("BOOM")) + c.Assert(params[0], check.ErrorMatches, "KABOOM") + c.Assert(names[0], check.Equals, "panic") + + // Verify a nil panic + testCheck(c, check.Panics, true, "", func() { panic(nil) }, nil) + testCheck(c, check.Panics, false, "", func() { panic(nil) }, "NOPE") +} + +func (s *CheckersS) TestPanicMatches(c *check.C) { + testInfo(c, check.PanicMatches, "PanicMatches", []string{"function", "expected"}) + + // Error matching. + testCheck(c, check.PanicMatches, true, "", func() { panic(errors.New("BOOM")) }, "BO.M") + testCheck(c, check.PanicMatches, false, "", func() { panic(errors.New("KABOOM")) }, "BO.M") + + // Some errors. + testCheck(c, check.PanicMatches, false, "Function has not panicked", func() bool { return false }, "BOOM") + testCheck(c, check.PanicMatches, false, "Function must take zero arguments", 1, "BOOM") + + // Plain strings. + testCheck(c, check.PanicMatches, true, "", func() { panic("BOOM") }, "BO.M") + testCheck(c, check.PanicMatches, false, "", func() { panic("KABOOM") }, "BOOM") + testCheck(c, check.PanicMatches, true, "", func() bool { panic("BOOM") }, "BO.M") + + // Verify params/names mutation + params, names := testCheck(c, check.PanicMatches, false, "", func() { panic(errors.New("KABOOM")) }, "BOOM") + c.Assert(params[0], check.Equals, "KABOOM") + c.Assert(names[0], check.Equals, "panic") + + // Verify a nil panic + testCheck(c, check.PanicMatches, false, "Panic value is not a string or an error", func() { panic(nil) }, "") +} + +func (s *CheckersS) TestFitsTypeOf(c *check.C) { + testInfo(c, check.FitsTypeOf, "FitsTypeOf", []string{"obtained", "sample"}) + + // Basic types + testCheck(c, check.FitsTypeOf, true, "", 1, 0) + testCheck(c, check.FitsTypeOf, false, "", 1, int64(0)) + + // Aliases + testCheck(c, check.FitsTypeOf, false, "", 1, errors.New("")) + testCheck(c, check.FitsTypeOf, false, "", "error", errors.New("")) + testCheck(c, check.FitsTypeOf, true, "", errors.New("error"), errors.New("")) + + // Structures + testCheck(c, check.FitsTypeOf, false, "", 1, simpleStruct{}) + testCheck(c, check.FitsTypeOf, false, "", simpleStruct{42}, &simpleStruct{}) + testCheck(c, check.FitsTypeOf, true, "", simpleStruct{42}, simpleStruct{}) + testCheck(c, check.FitsTypeOf, true, "", &simpleStruct{42}, &simpleStruct{}) + + // Some bad values + testCheck(c, check.FitsTypeOf, false, "Invalid sample value", 1, interface{}(nil)) + testCheck(c, check.FitsTypeOf, false, "", interface{}(nil), 0) +} + +func (s *CheckersS) TestImplements(c *check.C) { + testInfo(c, check.Implements, "Implements", []string{"obtained", "ifaceptr"}) + + var e error + var re runtime.Error + testCheck(c, check.Implements, true, "", errors.New(""), &e) + testCheck(c, check.Implements, false, "", errors.New(""), &re) + + // Some bad values + testCheck(c, check.Implements, false, "ifaceptr should be a pointer to an interface variable", 0, errors.New("")) + testCheck(c, check.Implements, false, "ifaceptr should be a pointer to an interface variable", 0, interface{}(nil)) + testCheck(c, check.Implements, false, "", interface{}(nil), &e) +} diff --git a/vendor/src/github.com/go-check/check/export_test.go b/vendor/src/github.com/go-check/check/export_test.go new file mode 100644 index 000000000..0e6cfe0f2 --- /dev/null +++ b/vendor/src/github.com/go-check/check/export_test.go @@ -0,0 +1,9 @@ +package check + +func PrintLine(filename string, line int) (string, error) { + return printLine(filename, line) +} + +func Indent(s, with string) string { + return indent(s, with) +} diff --git a/vendor/src/github.com/go-check/check/fixture_test.go b/vendor/src/github.com/go-check/check/fixture_test.go new file mode 100644 index 000000000..2bff9e163 --- /dev/null +++ b/vendor/src/github.com/go-check/check/fixture_test.go @@ -0,0 +1,484 @@ +// Tests for the behavior of the test fixture system. + +package check_test + +import ( + . "gopkg.in/check.v1" +) + +// ----------------------------------------------------------------------- +// Fixture test suite. + +type FixtureS struct{} + +var fixtureS = Suite(&FixtureS{}) + +func (s *FixtureS) TestCountSuite(c *C) { + suitesRun += 1 +} + +// ----------------------------------------------------------------------- +// Basic fixture ordering verification. + +func (s *FixtureS) TestOrder(c *C) { + helper := FixtureHelper{} + Run(&helper, nil) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "SetUpTest") + c.Check(helper.calls[5], Equals, "Test2") + c.Check(helper.calls[6], Equals, "TearDownTest") + c.Check(helper.calls[7], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 8) +} + +// ----------------------------------------------------------------------- +// Check the behavior when panics occur within tests and fixtures. + +func (s *FixtureS) TestPanicOnTest(c *C) { + helper := FixtureHelper{panicOn: "Test1"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "SetUpTest") + c.Check(helper.calls[5], Equals, "Test2") + c.Check(helper.calls[6], Equals, "TearDownTest") + c.Check(helper.calls[7], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 8) + + expected := "^\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: FixtureHelper.Test1\n\n" + + "\\.\\.\\. Panic: Test1 \\(PC=[xA-F0-9]+\\)\n\n" + + ".+:[0-9]+\n" + + " in (go)?panic\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.trace\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.Test1\n" + + "(.|\n)*$" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnSetUpTest(c *C) { + helper := FixtureHelper{panicOn: "SetUpTest"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "TearDownTest") + c.Check(helper.calls[3], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 4) + + expected := "^\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: " + + "FixtureHelper\\.SetUpTest\n\n" + + "\\.\\.\\. Panic: SetUpTest \\(PC=[xA-F0-9]+\\)\n\n" + + ".+:[0-9]+\n" + + " in (go)?panic\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.trace\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.SetUpTest\n" + + "(.|\n)*" + + "\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: " + + "FixtureHelper\\.Test1\n\n" + + "\\.\\.\\. Panic: Fixture has panicked " + + "\\(see related PANIC\\)\n$" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnTearDownTest(c *C) { + helper := FixtureHelper{panicOn: "TearDownTest"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 5) + + expected := "^\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: " + + "FixtureHelper.TearDownTest\n\n" + + "\\.\\.\\. Panic: TearDownTest \\(PC=[xA-F0-9]+\\)\n\n" + + ".+:[0-9]+\n" + + " in (go)?panic\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.trace\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.TearDownTest\n" + + "(.|\n)*" + + "\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: " + + "FixtureHelper\\.Test1\n\n" + + "\\.\\.\\. Panic: Fixture has panicked " + + "\\(see related PANIC\\)\n$" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnSetUpSuite(c *C) { + helper := FixtureHelper{panicOn: "SetUpSuite"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 2) + + expected := "^\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: " + + "FixtureHelper.SetUpSuite\n\n" + + "\\.\\.\\. Panic: SetUpSuite \\(PC=[xA-F0-9]+\\)\n\n" + + ".+:[0-9]+\n" + + " in (go)?panic\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.trace\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.SetUpSuite\n" + + "(.|\n)*$" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnTearDownSuite(c *C) { + helper := FixtureHelper{panicOn: "TearDownSuite"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "SetUpTest") + c.Check(helper.calls[5], Equals, "Test2") + c.Check(helper.calls[6], Equals, "TearDownTest") + c.Check(helper.calls[7], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 8) + + expected := "^\n-+\n" + + "PANIC: check_test\\.go:[0-9]+: " + + "FixtureHelper.TearDownSuite\n\n" + + "\\.\\.\\. Panic: TearDownSuite \\(PC=[xA-F0-9]+\\)\n\n" + + ".+:[0-9]+\n" + + " in (go)?panic\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.trace\n" + + ".*check_test.go:[0-9]+\n" + + " in FixtureHelper.TearDownSuite\n" + + "(.|\n)*$" + + c.Check(output.value, Matches, expected) +} + +// ----------------------------------------------------------------------- +// A wrong argument on a test or fixture will produce a nice error. + +func (s *FixtureS) TestPanicOnWrongTestArg(c *C) { + helper := WrongTestArgHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "TearDownTest") + c.Check(helper.calls[3], Equals, "SetUpTest") + c.Check(helper.calls[4], Equals, "Test2") + c.Check(helper.calls[5], Equals, "TearDownTest") + c.Check(helper.calls[6], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 7) + + expected := "^\n-+\n" + + "PANIC: fixture_test\\.go:[0-9]+: " + + "WrongTestArgHelper\\.Test1\n\n" + + "\\.\\.\\. Panic: WrongTestArgHelper\\.Test1 argument " + + "should be \\*check\\.C\n" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnWrongSetUpTestArg(c *C) { + helper := WrongSetUpTestArgHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(len(helper.calls), Equals, 0) + + expected := + "^\n-+\n" + + "PANIC: fixture_test\\.go:[0-9]+: " + + "WrongSetUpTestArgHelper\\.SetUpTest\n\n" + + "\\.\\.\\. Panic: WrongSetUpTestArgHelper\\.SetUpTest argument " + + "should be \\*check\\.C\n" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnWrongSetUpSuiteArg(c *C) { + helper := WrongSetUpSuiteArgHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(len(helper.calls), Equals, 0) + + expected := + "^\n-+\n" + + "PANIC: fixture_test\\.go:[0-9]+: " + + "WrongSetUpSuiteArgHelper\\.SetUpSuite\n\n" + + "\\.\\.\\. Panic: WrongSetUpSuiteArgHelper\\.SetUpSuite argument " + + "should be \\*check\\.C\n" + + c.Check(output.value, Matches, expected) +} + +// ----------------------------------------------------------------------- +// Nice errors also when tests or fixture have wrong arg count. + +func (s *FixtureS) TestPanicOnWrongTestArgCount(c *C) { + helper := WrongTestArgCountHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "TearDownTest") + c.Check(helper.calls[3], Equals, "SetUpTest") + c.Check(helper.calls[4], Equals, "Test2") + c.Check(helper.calls[5], Equals, "TearDownTest") + c.Check(helper.calls[6], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 7) + + expected := "^\n-+\n" + + "PANIC: fixture_test\\.go:[0-9]+: " + + "WrongTestArgCountHelper\\.Test1\n\n" + + "\\.\\.\\. Panic: WrongTestArgCountHelper\\.Test1 argument " + + "should be \\*check\\.C\n" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnWrongSetUpTestArgCount(c *C) { + helper := WrongSetUpTestArgCountHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(len(helper.calls), Equals, 0) + + expected := + "^\n-+\n" + + "PANIC: fixture_test\\.go:[0-9]+: " + + "WrongSetUpTestArgCountHelper\\.SetUpTest\n\n" + + "\\.\\.\\. Panic: WrongSetUpTestArgCountHelper\\.SetUpTest argument " + + "should be \\*check\\.C\n" + + c.Check(output.value, Matches, expected) +} + +func (s *FixtureS) TestPanicOnWrongSetUpSuiteArgCount(c *C) { + helper := WrongSetUpSuiteArgCountHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(len(helper.calls), Equals, 0) + + expected := + "^\n-+\n" + + "PANIC: fixture_test\\.go:[0-9]+: " + + "WrongSetUpSuiteArgCountHelper\\.SetUpSuite\n\n" + + "\\.\\.\\. Panic: WrongSetUpSuiteArgCountHelper" + + "\\.SetUpSuite argument should be \\*check\\.C\n" + + c.Check(output.value, Matches, expected) +} + +// ----------------------------------------------------------------------- +// Helper test suites with wrong function arguments. + +type WrongTestArgHelper struct { + FixtureHelper +} + +func (s *WrongTestArgHelper) Test1(t int) { +} + +type WrongSetUpTestArgHelper struct { + FixtureHelper +} + +func (s *WrongSetUpTestArgHelper) SetUpTest(t int) { +} + +type WrongSetUpSuiteArgHelper struct { + FixtureHelper +} + +func (s *WrongSetUpSuiteArgHelper) SetUpSuite(t int) { +} + +type WrongTestArgCountHelper struct { + FixtureHelper +} + +func (s *WrongTestArgCountHelper) Test1(c *C, i int) { +} + +type WrongSetUpTestArgCountHelper struct { + FixtureHelper +} + +func (s *WrongSetUpTestArgCountHelper) SetUpTest(c *C, i int) { +} + +type WrongSetUpSuiteArgCountHelper struct { + FixtureHelper +} + +func (s *WrongSetUpSuiteArgCountHelper) SetUpSuite(c *C, i int) { +} + +// ----------------------------------------------------------------------- +// Ensure fixture doesn't run without tests. + +type NoTestsHelper struct { + hasRun bool +} + +func (s *NoTestsHelper) SetUpSuite(c *C) { + s.hasRun = true +} + +func (s *NoTestsHelper) TearDownSuite(c *C) { + s.hasRun = true +} + +func (s *FixtureS) TestFixtureDoesntRunWithoutTests(c *C) { + helper := NoTestsHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Check(helper.hasRun, Equals, false) +} + +// ----------------------------------------------------------------------- +// Verify that checks and assertions work correctly inside the fixture. + +type FixtureCheckHelper struct { + fail string + completed bool +} + +func (s *FixtureCheckHelper) SetUpSuite(c *C) { + switch s.fail { + case "SetUpSuiteAssert": + c.Assert(false, Equals, true) + case "SetUpSuiteCheck": + c.Check(false, Equals, true) + } + s.completed = true +} + +func (s *FixtureCheckHelper) SetUpTest(c *C) { + switch s.fail { + case "SetUpTestAssert": + c.Assert(false, Equals, true) + case "SetUpTestCheck": + c.Check(false, Equals, true) + } + s.completed = true +} + +func (s *FixtureCheckHelper) Test(c *C) { + // Do nothing. +} + +func (s *FixtureS) TestSetUpSuiteCheck(c *C) { + helper := FixtureCheckHelper{fail: "SetUpSuiteCheck"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Assert(output.value, Matches, + "\n---+\n"+ + "FAIL: fixture_test\\.go:[0-9]+: "+ + "FixtureCheckHelper\\.SetUpSuite\n\n"+ + "fixture_test\\.go:[0-9]+:\n"+ + " c\\.Check\\(false, Equals, true\\)\n"+ + "\\.+ obtained bool = false\n"+ + "\\.+ expected bool = true\n\n") + c.Assert(helper.completed, Equals, true) +} + +func (s *FixtureS) TestSetUpSuiteAssert(c *C) { + helper := FixtureCheckHelper{fail: "SetUpSuiteAssert"} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Assert(output.value, Matches, + "\n---+\n"+ + "FAIL: fixture_test\\.go:[0-9]+: "+ + "FixtureCheckHelper\\.SetUpSuite\n\n"+ + "fixture_test\\.go:[0-9]+:\n"+ + " c\\.Assert\\(false, Equals, true\\)\n"+ + "\\.+ obtained bool = false\n"+ + "\\.+ expected bool = true\n\n") + c.Assert(helper.completed, Equals, false) +} + +// ----------------------------------------------------------------------- +// Verify that logging within SetUpTest() persists within the test log itself. + +type FixtureLogHelper struct { + c *C +} + +func (s *FixtureLogHelper) SetUpTest(c *C) { + s.c = c + c.Log("1") +} + +func (s *FixtureLogHelper) Test(c *C) { + c.Log("2") + s.c.Log("3") + c.Log("4") + c.Fail() +} + +func (s *FixtureLogHelper) TearDownTest(c *C) { + s.c.Log("5") +} + +func (s *FixtureS) TestFixtureLogging(c *C) { + helper := FixtureLogHelper{} + output := String{} + Run(&helper, &RunConf{Output: &output}) + c.Assert(output.value, Matches, + "\n---+\n"+ + "FAIL: fixture_test\\.go:[0-9]+: "+ + "FixtureLogHelper\\.Test\n\n"+ + "1\n2\n3\n4\n5\n") +} + +// ----------------------------------------------------------------------- +// Skip() within fixture methods. + +func (s *FixtureS) TestSkipSuite(c *C) { + helper := FixtureHelper{skip: true, skipOnN: 0} + output := String{} + result := Run(&helper, &RunConf{Output: &output}) + c.Assert(output.value, Equals, "") + c.Assert(helper.calls[0], Equals, "SetUpSuite") + c.Assert(helper.calls[1], Equals, "TearDownSuite") + c.Assert(len(helper.calls), Equals, 2) + c.Assert(result.Skipped, Equals, 2) +} + +func (s *FixtureS) TestSkipTest(c *C) { + helper := FixtureHelper{skip: true, skipOnN: 1} + output := String{} + result := Run(&helper, &RunConf{Output: &output}) + c.Assert(helper.calls[0], Equals, "SetUpSuite") + c.Assert(helper.calls[1], Equals, "SetUpTest") + c.Assert(helper.calls[2], Equals, "SetUpTest") + c.Assert(helper.calls[3], Equals, "Test2") + c.Assert(helper.calls[4], Equals, "TearDownTest") + c.Assert(helper.calls[5], Equals, "TearDownSuite") + c.Assert(len(helper.calls), Equals, 6) + c.Assert(result.Skipped, Equals, 1) +} diff --git a/vendor/src/github.com/go-check/check/foundation_test.go b/vendor/src/github.com/go-check/check/foundation_test.go new file mode 100644 index 000000000..8ecf7915f --- /dev/null +++ b/vendor/src/github.com/go-check/check/foundation_test.go @@ -0,0 +1,335 @@ +// These tests check that the foundations of gocheck are working properly. +// They already assume that fundamental failing is working already, though, +// since this was tested in bootstrap_test.go. Even then, some care may +// still have to be taken when using external functions, since they should +// of course not rely on functionality tested here. + +package check_test + +import ( + "fmt" + "gopkg.in/check.v1" + "log" + "os" + "regexp" + "strings" +) + +// ----------------------------------------------------------------------- +// Foundation test suite. + +type FoundationS struct{} + +var foundationS = check.Suite(&FoundationS{}) + +func (s *FoundationS) TestCountSuite(c *check.C) { + suitesRun += 1 +} + +func (s *FoundationS) TestErrorf(c *check.C) { + // Do not use checkState() here. It depends on Errorf() working. + expectedLog := fmt.Sprintf("foundation_test.go:%d:\n"+ + " c.Errorf(\"Error %%v!\", \"message\")\n"+ + "... Error: Error message!\n\n", + getMyLine()+1) + c.Errorf("Error %v!", "message") + failed := c.Failed() + c.Succeed() + if log := c.GetTestLog(); log != expectedLog { + c.Logf("Errorf() logged %#v rather than %#v", log, expectedLog) + c.Fail() + } + if !failed { + c.Logf("Errorf() didn't put the test in a failed state") + c.Fail() + } +} + +func (s *FoundationS) TestError(c *check.C) { + expectedLog := fmt.Sprintf("foundation_test.go:%d:\n"+ + " c\\.Error\\(\"Error \", \"message!\"\\)\n"+ + "\\.\\.\\. Error: Error message!\n\n", + getMyLine()+1) + c.Error("Error ", "message!") + checkState(c, nil, + &expectedState{ + name: "Error(`Error `, `message!`)", + failed: true, + log: expectedLog, + }) +} + +func (s *FoundationS) TestFailNow(c *check.C) { + defer (func() { + if !c.Failed() { + c.Error("FailNow() didn't fail the test") + } else { + c.Succeed() + if c.GetTestLog() != "" { + c.Error("Something got logged:\n" + c.GetTestLog()) + } + } + })() + + c.FailNow() + c.Log("FailNow() didn't stop the test") +} + +func (s *FoundationS) TestSucceedNow(c *check.C) { + defer (func() { + if c.Failed() { + c.Error("SucceedNow() didn't succeed the test") + } + if c.GetTestLog() != "" { + c.Error("Something got logged:\n" + c.GetTestLog()) + } + })() + + c.Fail() + c.SucceedNow() + c.Log("SucceedNow() didn't stop the test") +} + +func (s *FoundationS) TestFailureHeader(c *check.C) { + output := String{} + failHelper := FailHelper{} + check.Run(&failHelper, &check.RunConf{Output: &output}) + header := fmt.Sprintf(""+ + "\n-----------------------------------"+ + "-----------------------------------\n"+ + "FAIL: check_test.go:%d: FailHelper.TestLogAndFail\n", + failHelper.testLine) + if strings.Index(output.value, header) == -1 { + c.Errorf(""+ + "Failure didn't print a proper header.\n"+ + "... Got:\n%s... Expected something with:\n%s", + output.value, header) + } +} + +func (s *FoundationS) TestFatal(c *check.C) { + var line int + defer (func() { + if !c.Failed() { + c.Error("Fatal() didn't fail the test") + } else { + c.Succeed() + expected := fmt.Sprintf("foundation_test.go:%d:\n"+ + " c.Fatal(\"Die \", \"now!\")\n"+ + "... Error: Die now!\n\n", + line) + if c.GetTestLog() != expected { + c.Error("Incorrect log:", c.GetTestLog()) + } + } + })() + + line = getMyLine() + 1 + c.Fatal("Die ", "now!") + c.Log("Fatal() didn't stop the test") +} + +func (s *FoundationS) TestFatalf(c *check.C) { + var line int + defer (func() { + if !c.Failed() { + c.Error("Fatalf() didn't fail the test") + } else { + c.Succeed() + expected := fmt.Sprintf("foundation_test.go:%d:\n"+ + " c.Fatalf(\"Die %%s!\", \"now\")\n"+ + "... Error: Die now!\n\n", + line) + if c.GetTestLog() != expected { + c.Error("Incorrect log:", c.GetTestLog()) + } + } + })() + + line = getMyLine() + 1 + c.Fatalf("Die %s!", "now") + c.Log("Fatalf() didn't stop the test") +} + +func (s *FoundationS) TestCallerLoggingInsideTest(c *check.C) { + log := fmt.Sprintf(""+ + "foundation_test.go:%d:\n"+ + " result := c.Check\\(10, check.Equals, 20\\)\n"+ + "\\.\\.\\. obtained int = 10\n"+ + "\\.\\.\\. expected int = 20\n\n", + getMyLine()+1) + result := c.Check(10, check.Equals, 20) + checkState(c, result, + &expectedState{ + name: "Check(10, Equals, 20)", + result: false, + failed: true, + log: log, + }) +} + +func (s *FoundationS) TestCallerLoggingInDifferentFile(c *check.C) { + result, line := checkEqualWrapper(c, 10, 20) + testLine := getMyLine() - 1 + log := fmt.Sprintf(""+ + "foundation_test.go:%d:\n"+ + " result, line := checkEqualWrapper\\(c, 10, 20\\)\n"+ + "check_test.go:%d:\n"+ + " return c.Check\\(obtained, check.Equals, expected\\), getMyLine\\(\\)\n"+ + "\\.\\.\\. obtained int = 10\n"+ + "\\.\\.\\. expected int = 20\n\n", + testLine, line) + checkState(c, result, + &expectedState{ + name: "Check(10, Equals, 20)", + result: false, + failed: true, + log: log, + }) +} + +// ----------------------------------------------------------------------- +// ExpectFailure() inverts the logic of failure. + +type ExpectFailureSucceedHelper struct{} + +func (s *ExpectFailureSucceedHelper) TestSucceed(c *check.C) { + c.ExpectFailure("It booms!") + c.Error("Boom!") +} + +type ExpectFailureFailHelper struct{} + +func (s *ExpectFailureFailHelper) TestFail(c *check.C) { + c.ExpectFailure("Bug #XYZ") +} + +func (s *FoundationS) TestExpectFailureFail(c *check.C) { + helper := ExpectFailureFailHelper{} + output := String{} + result := check.Run(&helper, &check.RunConf{Output: &output}) + + expected := "" + + "^\n-+\n" + + "FAIL: foundation_test\\.go:[0-9]+:" + + " ExpectFailureFailHelper\\.TestFail\n\n" + + "\\.\\.\\. Error: Test succeeded, but was expected to fail\n" + + "\\.\\.\\. Reason: Bug #XYZ\n$" + + matched, err := regexp.MatchString(expected, output.value) + if err != nil { + c.Error("Bad expression: ", expected) + } else if !matched { + c.Error("ExpectFailure() didn't log properly:\n", output.value) + } + + c.Assert(result.ExpectedFailures, check.Equals, 0) +} + +func (s *FoundationS) TestExpectFailureSucceed(c *check.C) { + helper := ExpectFailureSucceedHelper{} + output := String{} + result := check.Run(&helper, &check.RunConf{Output: &output}) + + c.Assert(output.value, check.Equals, "") + c.Assert(result.ExpectedFailures, check.Equals, 1) +} + +func (s *FoundationS) TestExpectFailureSucceedVerbose(c *check.C) { + helper := ExpectFailureSucceedHelper{} + output := String{} + result := check.Run(&helper, &check.RunConf{Output: &output, Verbose: true}) + + expected := "" + + "FAIL EXPECTED: foundation_test\\.go:[0-9]+:" + + " ExpectFailureSucceedHelper\\.TestSucceed \\(It booms!\\)\t *[.0-9]+s\n" + + matched, err := regexp.MatchString(expected, output.value) + if err != nil { + c.Error("Bad expression: ", expected) + } else if !matched { + c.Error("ExpectFailure() didn't log properly:\n", output.value) + } + + c.Assert(result.ExpectedFailures, check.Equals, 1) +} + +// ----------------------------------------------------------------------- +// Skip() allows stopping a test without positive/negative results. + +type SkipTestHelper struct{} + +func (s *SkipTestHelper) TestFail(c *check.C) { + c.Skip("Wrong platform or whatever") + c.Error("Boom!") +} + +func (s *FoundationS) TestSkip(c *check.C) { + helper := SkipTestHelper{} + output := String{} + check.Run(&helper, &check.RunConf{Output: &output}) + + if output.value != "" { + c.Error("Skip() logged something:\n", output.value) + } +} + +func (s *FoundationS) TestSkipVerbose(c *check.C) { + helper := SkipTestHelper{} + output := String{} + check.Run(&helper, &check.RunConf{Output: &output, Verbose: true}) + + expected := "SKIP: foundation_test\\.go:[0-9]+: SkipTestHelper\\.TestFail" + + " \\(Wrong platform or whatever\\)" + matched, err := regexp.MatchString(expected, output.value) + if err != nil { + c.Error("Bad expression: ", expected) + } else if !matched { + c.Error("Skip() didn't log properly:\n", output.value) + } +} + +// ----------------------------------------------------------------------- +// Check minimum *log.Logger interface provided by *check.C. + +type minLogger interface { + Output(calldepth int, s string) error +} + +func (s *BootstrapS) TestMinLogger(c *check.C) { + var logger minLogger + logger = log.New(os.Stderr, "", 0) + logger = c + logger.Output(0, "Hello there") + expected := `\[LOG\] [0-9]+:[0-9][0-9]\.[0-9][0-9][0-9] +Hello there\n` + output := c.GetTestLog() + c.Assert(output, check.Matches, expected) +} + +// ----------------------------------------------------------------------- +// Ensure that suites with embedded types are working fine, including the +// the workaround for issue 906. + +type EmbeddedInternalS struct { + called bool +} + +type EmbeddedS struct { + EmbeddedInternalS +} + +var embeddedS = check.Suite(&EmbeddedS{}) + +func (s *EmbeddedS) TestCountSuite(c *check.C) { + suitesRun += 1 +} + +func (s *EmbeddedInternalS) TestMethod(c *check.C) { + c.Error("TestMethod() of the embedded type was called!?") +} + +func (s *EmbeddedS) TestMethod(c *check.C) { + // http://code.google.com/p/go/issues/detail?id=906 + c.Check(s.called, check.Equals, false) // Go issue 906 is affecting the runner? + s.called = true +} diff --git a/vendor/src/github.com/go-check/check/helpers.go b/vendor/src/github.com/go-check/check/helpers.go new file mode 100644 index 000000000..4b6c26da4 --- /dev/null +++ b/vendor/src/github.com/go-check/check/helpers.go @@ -0,0 +1,231 @@ +package check + +import ( + "fmt" + "strings" + "time" +) + +// TestName returns the current test name in the form "SuiteName.TestName" +func (c *C) TestName() string { + return c.testName +} + +// ----------------------------------------------------------------------- +// Basic succeeding/failing logic. + +// Failed returns whether the currently running test has already failed. +func (c *C) Failed() bool { + return c.status == failedSt +} + +// Fail marks the currently running test as failed. +// +// Something ought to have been previously logged so the developer can tell +// what went wrong. The higher level helper functions will fail the test +// and do the logging properly. +func (c *C) Fail() { + c.status = failedSt +} + +// FailNow marks the currently running test as failed and stops running it. +// Something ought to have been previously logged so the developer can tell +// what went wrong. The higher level helper functions will fail the test +// and do the logging properly. +func (c *C) FailNow() { + c.Fail() + c.stopNow() +} + +// Succeed marks the currently running test as succeeded, undoing any +// previous failures. +func (c *C) Succeed() { + c.status = succeededSt +} + +// SucceedNow marks the currently running test as succeeded, undoing any +// previous failures, and stops running the test. +func (c *C) SucceedNow() { + c.Succeed() + c.stopNow() +} + +// ExpectFailure informs that the running test is knowingly broken for +// the provided reason. If the test does not fail, an error will be reported +// to raise attention to this fact. This method is useful to temporarily +// disable tests which cover well known problems until a better time to +// fix the problem is found, without forgetting about the fact that a +// failure still exists. +func (c *C) ExpectFailure(reason string) { + if reason == "" { + panic("Missing reason why the test is expected to fail") + } + c.mustFail = true + c.reason = reason +} + +// Skip skips the running test for the provided reason. If run from within +// SetUpTest, the individual test being set up will be skipped, and if run +// from within SetUpSuite, the whole suite is skipped. +func (c *C) Skip(reason string) { + if reason == "" { + panic("Missing reason why the test is being skipped") + } + c.reason = reason + c.status = skippedSt + c.stopNow() +} + +// ----------------------------------------------------------------------- +// Basic logging. + +// GetTestLog returns the current test error output. +func (c *C) GetTestLog() string { + return c.logb.String() +} + +// Log logs some information into the test error output. +// The provided arguments are assembled together into a string with fmt.Sprint. +func (c *C) Log(args ...interface{}) { + c.log(args...) +} + +// Log logs some information into the test error output. +// The provided arguments are assembled together into a string with fmt.Sprintf. +func (c *C) Logf(format string, args ...interface{}) { + c.logf(format, args...) +} + +// Output enables *C to be used as a logger in functions that require only +// the minimum interface of *log.Logger. +func (c *C) Output(calldepth int, s string) error { + d := time.Now().Sub(c.startTime) + msec := d / time.Millisecond + sec := d / time.Second + min := d / time.Minute + + c.Logf("[LOG] %d:%02d.%03d %s", min, sec%60, msec%1000, s) + return nil +} + +// Error logs an error into the test error output and marks the test as failed. +// The provided arguments are assembled together into a string with fmt.Sprint. +func (c *C) Error(args ...interface{}) { + c.logCaller(1) + c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...))) + c.logNewLine() + c.Fail() +} + +// Errorf logs an error into the test error output and marks the test as failed. +// The provided arguments are assembled together into a string with fmt.Sprintf. +func (c *C) Errorf(format string, args ...interface{}) { + c.logCaller(1) + c.logString(fmt.Sprintf("Error: "+format, args...)) + c.logNewLine() + c.Fail() +} + +// Fatal logs an error into the test error output, marks the test as failed, and +// stops the test execution. The provided arguments are assembled together into +// a string with fmt.Sprint. +func (c *C) Fatal(args ...interface{}) { + c.logCaller(1) + c.logString(fmt.Sprint("Error: ", fmt.Sprint(args...))) + c.logNewLine() + c.FailNow() +} + +// Fatlaf logs an error into the test error output, marks the test as failed, and +// stops the test execution. The provided arguments are assembled together into +// a string with fmt.Sprintf. +func (c *C) Fatalf(format string, args ...interface{}) { + c.logCaller(1) + c.logString(fmt.Sprint("Error: ", fmt.Sprintf(format, args...))) + c.logNewLine() + c.FailNow() +} + +// ----------------------------------------------------------------------- +// Generic checks and assertions based on checkers. + +// Check verifies if the first value matches the expected value according +// to the provided checker. If they do not match, an error is logged, the +// test is marked as failed, and the test execution continues. +// +// Some checkers may not need the expected argument (e.g. IsNil). +// +// Extra arguments provided to the function are logged next to the reported +// problem when the matching fails. +func (c *C) Check(obtained interface{}, checker Checker, args ...interface{}) bool { + return c.internalCheck("Check", obtained, checker, args...) +} + +// Assert ensures that the first value matches the expected value according +// to the provided checker. If they do not match, an error is logged, the +// test is marked as failed, and the test execution stops. +// +// Some checkers may not need the expected argument (e.g. IsNil). +// +// Extra arguments provided to the function are logged next to the reported +// problem when the matching fails. +func (c *C) Assert(obtained interface{}, checker Checker, args ...interface{}) { + if !c.internalCheck("Assert", obtained, checker, args...) { + c.stopNow() + } +} + +func (c *C) internalCheck(funcName string, obtained interface{}, checker Checker, args ...interface{}) bool { + if checker == nil { + c.logCaller(2) + c.logString(fmt.Sprintf("%s(obtained, nil!?, ...):", funcName)) + c.logString("Oops.. you've provided a nil checker!") + c.logNewLine() + c.Fail() + return false + } + + // If the last argument is a bug info, extract it out. + var comment CommentInterface + if len(args) > 0 { + if c, ok := args[len(args)-1].(CommentInterface); ok { + comment = c + args = args[:len(args)-1] + } + } + + params := append([]interface{}{obtained}, args...) + info := checker.Info() + + if len(params) != len(info.Params) { + names := append([]string{info.Params[0], info.Name}, info.Params[1:]...) + c.logCaller(2) + c.logString(fmt.Sprintf("%s(%s):", funcName, strings.Join(names, ", "))) + c.logString(fmt.Sprintf("Wrong number of parameters for %s: want %d, got %d", info.Name, len(names), len(params)+1)) + c.logNewLine() + c.Fail() + return false + } + + // Copy since it may be mutated by Check. + names := append([]string{}, info.Params...) + + // Do the actual check. + result, error := checker.Check(params, names) + if !result || error != "" { + c.logCaller(2) + for i := 0; i != len(params); i++ { + c.logValue(names[i], params[i]) + } + if comment != nil { + c.logString(comment.CheckCommentString()) + } + if error != "" { + c.logString(error) + } + c.logNewLine() + c.Fail() + return false + } + return true +} diff --git a/vendor/src/github.com/go-check/check/helpers_test.go b/vendor/src/github.com/go-check/check/helpers_test.go new file mode 100644 index 000000000..4baa656ba --- /dev/null +++ b/vendor/src/github.com/go-check/check/helpers_test.go @@ -0,0 +1,519 @@ +// These tests verify the inner workings of the helper methods associated +// with check.T. + +package check_test + +import ( + "gopkg.in/check.v1" + "os" + "reflect" + "runtime" + "sync" +) + +var helpersS = check.Suite(&HelpersS{}) + +type HelpersS struct{} + +func (s *HelpersS) TestCountSuite(c *check.C) { + suitesRun += 1 +} + +// ----------------------------------------------------------------------- +// Fake checker and bug info to verify the behavior of Assert() and Check(). + +type MyChecker struct { + info *check.CheckerInfo + params []interface{} + names []string + result bool + error string +} + +func (checker *MyChecker) Info() *check.CheckerInfo { + if checker.info == nil { + return &check.CheckerInfo{Name: "MyChecker", Params: []string{"myobtained", "myexpected"}} + } + return checker.info +} + +func (checker *MyChecker) Check(params []interface{}, names []string) (bool, string) { + rparams := checker.params + rnames := checker.names + checker.params = append([]interface{}{}, params...) + checker.names = append([]string{}, names...) + if rparams != nil { + copy(params, rparams) + } + if rnames != nil { + copy(names, rnames) + } + return checker.result, checker.error +} + +type myCommentType string + +func (c myCommentType) CheckCommentString() string { + return string(c) +} + +func myComment(s string) myCommentType { + return myCommentType(s) +} + +// ----------------------------------------------------------------------- +// Ensure a real checker actually works fine. + +func (s *HelpersS) TestCheckerInterface(c *check.C) { + testHelperSuccess(c, "Check(1, Equals, 1)", true, func() interface{} { + return c.Check(1, check.Equals, 1) + }) +} + +// ----------------------------------------------------------------------- +// Tests for Check(), mostly the same as for Assert() following these. + +func (s *HelpersS) TestCheckSucceedWithExpected(c *check.C) { + checker := &MyChecker{result: true} + testHelperSuccess(c, "Check(1, checker, 2)", true, func() interface{} { + return c.Check(1, checker, 2) + }) + if !reflect.DeepEqual(checker.params, []interface{}{1, 2}) { + c.Fatalf("Bad params for check: %#v", checker.params) + } +} + +func (s *HelpersS) TestCheckSucceedWithoutExpected(c *check.C) { + checker := &MyChecker{result: true, info: &check.CheckerInfo{Params: []string{"myvalue"}}} + testHelperSuccess(c, "Check(1, checker)", true, func() interface{} { + return c.Check(1, checker) + }) + if !reflect.DeepEqual(checker.params, []interface{}{1}) { + c.Fatalf("Bad params for check: %#v", checker.params) + } +} + +func (s *HelpersS) TestCheckFailWithExpected(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker, 2\\)\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n\n" + testHelperFailure(c, "Check(1, checker, 2)", false, false, log, + func() interface{} { + return c.Check(1, checker, 2) + }) +} + +func (s *HelpersS) TestCheckFailWithExpectedAndComment(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker, 2, myComment\\(\"Hello world!\"\\)\\)\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n" + + "\\.+ Hello world!\n\n" + testHelperFailure(c, "Check(1, checker, 2, msg)", false, false, log, + func() interface{} { + return c.Check(1, checker, 2, myComment("Hello world!")) + }) +} + +func (s *HelpersS) TestCheckFailWithExpectedAndStaticComment(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " // Nice leading comment\\.\n" + + " return c\\.Check\\(1, checker, 2\\) // Hello there\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n\n" + testHelperFailure(c, "Check(1, checker, 2, msg)", false, false, log, + func() interface{} { + // Nice leading comment. + return c.Check(1, checker, 2) // Hello there + }) +} + +func (s *HelpersS) TestCheckFailWithoutExpected(c *check.C) { + checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker\\)\n" + + "\\.+ myvalue int = 1\n\n" + testHelperFailure(c, "Check(1, checker)", false, false, log, + func() interface{} { + return c.Check(1, checker) + }) +} + +func (s *HelpersS) TestCheckFailWithoutExpectedAndMessage(c *check.C) { + checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker, myComment\\(\"Hello world!\"\\)\\)\n" + + "\\.+ myvalue int = 1\n" + + "\\.+ Hello world!\n\n" + testHelperFailure(c, "Check(1, checker, msg)", false, false, log, + func() interface{} { + return c.Check(1, checker, myComment("Hello world!")) + }) +} + +func (s *HelpersS) TestCheckWithMissingExpected(c *check.C) { + checker := &MyChecker{result: true} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker\\)\n" + + "\\.+ Check\\(myobtained, MyChecker, myexpected\\):\n" + + "\\.+ Wrong number of parameters for MyChecker: " + + "want 3, got 2\n\n" + testHelperFailure(c, "Check(1, checker, !?)", false, false, log, + func() interface{} { + return c.Check(1, checker) + }) +} + +func (s *HelpersS) TestCheckWithTooManyExpected(c *check.C) { + checker := &MyChecker{result: true} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker, 2, 3\\)\n" + + "\\.+ Check\\(myobtained, MyChecker, myexpected\\):\n" + + "\\.+ Wrong number of parameters for MyChecker: " + + "want 3, got 4\n\n" + testHelperFailure(c, "Check(1, checker, 2, 3)", false, false, log, + func() interface{} { + return c.Check(1, checker, 2, 3) + }) +} + +func (s *HelpersS) TestCheckWithError(c *check.C) { + checker := &MyChecker{result: false, error: "Some not so cool data provided!"} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker, 2\\)\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n" + + "\\.+ Some not so cool data provided!\n\n" + testHelperFailure(c, "Check(1, checker, 2)", false, false, log, + func() interface{} { + return c.Check(1, checker, 2) + }) +} + +func (s *HelpersS) TestCheckWithNilChecker(c *check.C) { + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, nil\\)\n" + + "\\.+ Check\\(obtained, nil!\\?, \\.\\.\\.\\):\n" + + "\\.+ Oops\\.\\. you've provided a nil checker!\n\n" + testHelperFailure(c, "Check(obtained, nil)", false, false, log, + func() interface{} { + return c.Check(1, nil) + }) +} + +func (s *HelpersS) TestCheckWithParamsAndNamesMutation(c *check.C) { + checker := &MyChecker{result: false, params: []interface{}{3, 4}, names: []string{"newobtained", "newexpected"}} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " return c\\.Check\\(1, checker, 2\\)\n" + + "\\.+ newobtained int = 3\n" + + "\\.+ newexpected int = 4\n\n" + testHelperFailure(c, "Check(1, checker, 2) with mutation", false, false, log, + func() interface{} { + return c.Check(1, checker, 2) + }) +} + +// ----------------------------------------------------------------------- +// Tests for Assert(), mostly the same as for Check() above. + +func (s *HelpersS) TestAssertSucceedWithExpected(c *check.C) { + checker := &MyChecker{result: true} + testHelperSuccess(c, "Assert(1, checker, 2)", nil, func() interface{} { + c.Assert(1, checker, 2) + return nil + }) + if !reflect.DeepEqual(checker.params, []interface{}{1, 2}) { + c.Fatalf("Bad params for check: %#v", checker.params) + } +} + +func (s *HelpersS) TestAssertSucceedWithoutExpected(c *check.C) { + checker := &MyChecker{result: true, info: &check.CheckerInfo{Params: []string{"myvalue"}}} + testHelperSuccess(c, "Assert(1, checker)", nil, func() interface{} { + c.Assert(1, checker) + return nil + }) + if !reflect.DeepEqual(checker.params, []interface{}{1}) { + c.Fatalf("Bad params for check: %#v", checker.params) + } +} + +func (s *HelpersS) TestAssertFailWithExpected(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, checker, 2\\)\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n\n" + testHelperFailure(c, "Assert(1, checker, 2)", nil, true, log, + func() interface{} { + c.Assert(1, checker, 2) + return nil + }) +} + +func (s *HelpersS) TestAssertFailWithExpectedAndMessage(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, checker, 2, myComment\\(\"Hello world!\"\\)\\)\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n" + + "\\.+ Hello world!\n\n" + testHelperFailure(c, "Assert(1, checker, 2, msg)", nil, true, log, + func() interface{} { + c.Assert(1, checker, 2, myComment("Hello world!")) + return nil + }) +} + +func (s *HelpersS) TestAssertFailWithoutExpected(c *check.C) { + checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, checker\\)\n" + + "\\.+ myvalue int = 1\n\n" + testHelperFailure(c, "Assert(1, checker)", nil, true, log, + func() interface{} { + c.Assert(1, checker) + return nil + }) +} + +func (s *HelpersS) TestAssertFailWithoutExpectedAndMessage(c *check.C) { + checker := &MyChecker{result: false, info: &check.CheckerInfo{Params: []string{"myvalue"}}} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, checker, myComment\\(\"Hello world!\"\\)\\)\n" + + "\\.+ myvalue int = 1\n" + + "\\.+ Hello world!\n\n" + testHelperFailure(c, "Assert(1, checker, msg)", nil, true, log, + func() interface{} { + c.Assert(1, checker, myComment("Hello world!")) + return nil + }) +} + +func (s *HelpersS) TestAssertWithMissingExpected(c *check.C) { + checker := &MyChecker{result: true} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, checker\\)\n" + + "\\.+ Assert\\(myobtained, MyChecker, myexpected\\):\n" + + "\\.+ Wrong number of parameters for MyChecker: " + + "want 3, got 2\n\n" + testHelperFailure(c, "Assert(1, checker, !?)", nil, true, log, + func() interface{} { + c.Assert(1, checker) + return nil + }) +} + +func (s *HelpersS) TestAssertWithError(c *check.C) { + checker := &MyChecker{result: false, error: "Some not so cool data provided!"} + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, checker, 2\\)\n" + + "\\.+ myobtained int = 1\n" + + "\\.+ myexpected int = 2\n" + + "\\.+ Some not so cool data provided!\n\n" + testHelperFailure(c, "Assert(1, checker, 2)", nil, true, log, + func() interface{} { + c.Assert(1, checker, 2) + return nil + }) +} + +func (s *HelpersS) TestAssertWithNilChecker(c *check.C) { + log := "(?s)helpers_test\\.go:[0-9]+:.*\nhelpers_test\\.go:[0-9]+:\n" + + " c\\.Assert\\(1, nil\\)\n" + + "\\.+ Assert\\(obtained, nil!\\?, \\.\\.\\.\\):\n" + + "\\.+ Oops\\.\\. you've provided a nil checker!\n\n" + testHelperFailure(c, "Assert(obtained, nil)", nil, true, log, + func() interface{} { + c.Assert(1, nil) + return nil + }) +} + +// ----------------------------------------------------------------------- +// Ensure that values logged work properly in some interesting cases. + +func (s *HelpersS) TestValueLoggingWithArrays(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" + + " return c\\.Check\\(\\[\\]byte{1, 2}, checker, \\[\\]byte{1, 3}\\)\n" + + "\\.+ myobtained \\[\\]uint8 = \\[\\]byte{0x1, 0x2}\n" + + "\\.+ myexpected \\[\\]uint8 = \\[\\]byte{0x1, 0x3}\n\n" + testHelperFailure(c, "Check([]byte{1}, chk, []byte{3})", false, false, log, + func() interface{} { + return c.Check([]byte{1, 2}, checker, []byte{1, 3}) + }) +} + +func (s *HelpersS) TestValueLoggingWithMultiLine(c *check.C) { + checker := &MyChecker{result: false} + log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" + + " return c\\.Check\\(\"a\\\\nb\\\\n\", checker, \"a\\\\nb\\\\nc\"\\)\n" + + "\\.+ myobtained string = \"\" \\+\n" + + "\\.+ \"a\\\\n\" \\+\n" + + "\\.+ \"b\\\\n\"\n" + + "\\.+ myexpected string = \"\" \\+\n" + + "\\.+ \"a\\\\n\" \\+\n" + + "\\.+ \"b\\\\n\" \\+\n" + + "\\.+ \"c\"\n\n" + testHelperFailure(c, `Check("a\nb\n", chk, "a\nb\nc")`, false, false, log, + func() interface{} { + return c.Check("a\nb\n", checker, "a\nb\nc") + }) +} + +func (s *HelpersS) TestValueLoggingWithMultiLineException(c *check.C) { + // If the newline is at the end of the string, don't log as multi-line. + checker := &MyChecker{result: false} + log := "(?s)helpers_test.go:[0-9]+:.*\nhelpers_test.go:[0-9]+:\n" + + " return c\\.Check\\(\"a b\\\\n\", checker, \"a\\\\nb\"\\)\n" + + "\\.+ myobtained string = \"a b\\\\n\"\n" + + "\\.+ myexpected string = \"\" \\+\n" + + "\\.+ \"a\\\\n\" \\+\n" + + "\\.+ \"b\"\n\n" + testHelperFailure(c, `Check("a b\n", chk, "a\nb")`, false, false, log, + func() interface{} { + return c.Check("a b\n", checker, "a\nb") + }) +} + +// ----------------------------------------------------------------------- +// MakeDir() tests. + +type MkDirHelper struct { + path1 string + path2 string + isDir1 bool + isDir2 bool + isDir3 bool + isDir4 bool +} + +func (s *MkDirHelper) SetUpSuite(c *check.C) { + s.path1 = c.MkDir() + s.isDir1 = isDir(s.path1) +} + +func (s *MkDirHelper) Test(c *check.C) { + s.path2 = c.MkDir() + s.isDir2 = isDir(s.path2) +} + +func (s *MkDirHelper) TearDownSuite(c *check.C) { + s.isDir3 = isDir(s.path1) + s.isDir4 = isDir(s.path2) +} + +func (s *HelpersS) TestMkDir(c *check.C) { + helper := MkDirHelper{} + output := String{} + check.Run(&helper, &check.RunConf{Output: &output}) + c.Assert(output.value, check.Equals, "") + c.Check(helper.isDir1, check.Equals, true) + c.Check(helper.isDir2, check.Equals, true) + c.Check(helper.isDir3, check.Equals, true) + c.Check(helper.isDir4, check.Equals, true) + c.Check(helper.path1, check.Not(check.Equals), + helper.path2) + c.Check(isDir(helper.path1), check.Equals, false) + c.Check(isDir(helper.path2), check.Equals, false) +} + +func isDir(path string) bool { + if stat, err := os.Stat(path); err == nil { + return stat.IsDir() + } + return false +} + +// Concurrent logging should not corrupt the underling buffer. +// Use go test -race to detect the race in this test. +func (s *HelpersS) TestConcurrentLogging(c *check.C) { + defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(runtime.NumCPU())) + var start, stop sync.WaitGroup + start.Add(1) + for i, n := 0, runtime.NumCPU()*2; i < n; i++ { + stop.Add(1) + go func(i int) { + start.Wait() + for j := 0; j < 30; j++ { + c.Logf("Worker %d: line %d", i, j) + } + stop.Done() + }(i) + } + start.Done() + stop.Wait() +} + +// ----------------------------------------------------------------------- +// Test the TestName function + +type TestNameHelper struct { + name1 string + name2 string + name3 string + name4 string + name5 string +} + +func (s *TestNameHelper) SetUpSuite(c *check.C) { s.name1 = c.TestName() } +func (s *TestNameHelper) SetUpTest(c *check.C) { s.name2 = c.TestName() } +func (s *TestNameHelper) Test(c *check.C) { s.name3 = c.TestName() } +func (s *TestNameHelper) TearDownTest(c *check.C) { s.name4 = c.TestName() } +func (s *TestNameHelper) TearDownSuite(c *check.C) { s.name5 = c.TestName() } + +func (s *HelpersS) TestTestName(c *check.C) { + helper := TestNameHelper{} + output := String{} + check.Run(&helper, &check.RunConf{Output: &output}) + c.Check(helper.name1, check.Equals, "") + c.Check(helper.name2, check.Equals, "TestNameHelper.Test") + c.Check(helper.name3, check.Equals, "TestNameHelper.Test") + c.Check(helper.name4, check.Equals, "TestNameHelper.Test") + c.Check(helper.name5, check.Equals, "") +} + +// ----------------------------------------------------------------------- +// A couple of helper functions to test helper functions. :-) + +func testHelperSuccess(c *check.C, name string, expectedResult interface{}, closure func() interface{}) { + var result interface{} + defer (func() { + if err := recover(); err != nil { + panic(err) + } + checkState(c, result, + &expectedState{ + name: name, + result: expectedResult, + failed: false, + log: "", + }) + })() + result = closure() +} + +func testHelperFailure(c *check.C, name string, expectedResult interface{}, shouldStop bool, log string, closure func() interface{}) { + var result interface{} + defer (func() { + if err := recover(); err != nil { + panic(err) + } + checkState(c, result, + &expectedState{ + name: name, + result: expectedResult, + failed: true, + log: log, + }) + })() + result = closure() + if shouldStop { + c.Logf("%s didn't stop when it should", name) + } +} diff --git a/vendor/src/github.com/go-check/check/printer.go b/vendor/src/github.com/go-check/check/printer.go new file mode 100644 index 000000000..e0f7557b5 --- /dev/null +++ b/vendor/src/github.com/go-check/check/printer.go @@ -0,0 +1,168 @@ +package check + +import ( + "bytes" + "go/ast" + "go/parser" + "go/printer" + "go/token" + "os" +) + +func indent(s, with string) (r string) { + eol := true + for i := 0; i != len(s); i++ { + c := s[i] + switch { + case eol && c == '\n' || c == '\r': + case c == '\n' || c == '\r': + eol = true + case eol: + eol = false + s = s[:i] + with + s[i:] + i += len(with) + } + } + return s +} + +func printLine(filename string, line int) (string, error) { + fset := token.NewFileSet() + file, err := os.Open(filename) + if err != nil { + return "", err + } + fnode, err := parser.ParseFile(fset, filename, file, parser.ParseComments) + if err != nil { + return "", err + } + config := &printer.Config{Mode: printer.UseSpaces, Tabwidth: 4} + lp := &linePrinter{fset: fset, fnode: fnode, line: line, config: config} + ast.Walk(lp, fnode) + result := lp.output.Bytes() + // Comments leave \n at the end. + n := len(result) + for n > 0 && result[n-1] == '\n' { + n-- + } + return string(result[:n]), nil +} + +type linePrinter struct { + config *printer.Config + fset *token.FileSet + fnode *ast.File + line int + output bytes.Buffer + stmt ast.Stmt +} + +func (lp *linePrinter) emit() bool { + if lp.stmt != nil { + lp.trim(lp.stmt) + lp.printWithComments(lp.stmt) + lp.stmt = nil + return true + } + return false +} + +func (lp *linePrinter) printWithComments(n ast.Node) { + nfirst := lp.fset.Position(n.Pos()).Line + nlast := lp.fset.Position(n.End()).Line + for _, g := range lp.fnode.Comments { + cfirst := lp.fset.Position(g.Pos()).Line + clast := lp.fset.Position(g.End()).Line + if clast == nfirst-1 && lp.fset.Position(n.Pos()).Column == lp.fset.Position(g.Pos()).Column { + for _, c := range g.List { + lp.output.WriteString(c.Text) + lp.output.WriteByte('\n') + } + } + if cfirst >= nfirst && cfirst <= nlast && n.End() <= g.List[0].Slash { + // The printer will not include the comment if it starts past + // the node itself. Trick it into printing by overlapping the + // slash with the end of the statement. + g.List[0].Slash = n.End() - 1 + } + } + node := &printer.CommentedNode{n, lp.fnode.Comments} + lp.config.Fprint(&lp.output, lp.fset, node) +} + +func (lp *linePrinter) Visit(n ast.Node) (w ast.Visitor) { + if n == nil { + if lp.output.Len() == 0 { + lp.emit() + } + return nil + } + first := lp.fset.Position(n.Pos()).Line + last := lp.fset.Position(n.End()).Line + if first <= lp.line && last >= lp.line { + // Print the innermost statement containing the line. + if stmt, ok := n.(ast.Stmt); ok { + if _, ok := n.(*ast.BlockStmt); !ok { + lp.stmt = stmt + } + } + if first == lp.line && lp.emit() { + return nil + } + return lp + } + return nil +} + +func (lp *linePrinter) trim(n ast.Node) bool { + stmt, ok := n.(ast.Stmt) + if !ok { + return true + } + line := lp.fset.Position(n.Pos()).Line + if line != lp.line { + return false + } + switch stmt := stmt.(type) { + case *ast.IfStmt: + stmt.Body = lp.trimBlock(stmt.Body) + case *ast.SwitchStmt: + stmt.Body = lp.trimBlock(stmt.Body) + case *ast.TypeSwitchStmt: + stmt.Body = lp.trimBlock(stmt.Body) + case *ast.CaseClause: + stmt.Body = lp.trimList(stmt.Body) + case *ast.CommClause: + stmt.Body = lp.trimList(stmt.Body) + case *ast.BlockStmt: + stmt.List = lp.trimList(stmt.List) + } + return true +} + +func (lp *linePrinter) trimBlock(stmt *ast.BlockStmt) *ast.BlockStmt { + if !lp.trim(stmt) { + return lp.emptyBlock(stmt) + } + stmt.Rbrace = stmt.Lbrace + return stmt +} + +func (lp *linePrinter) trimList(stmts []ast.Stmt) []ast.Stmt { + for i := 0; i != len(stmts); i++ { + if !lp.trim(stmts[i]) { + stmts[i] = lp.emptyStmt(stmts[i]) + break + } + } + return stmts +} + +func (lp *linePrinter) emptyStmt(n ast.Node) *ast.ExprStmt { + return &ast.ExprStmt{&ast.Ellipsis{n.Pos(), nil}} +} + +func (lp *linePrinter) emptyBlock(n ast.Node) *ast.BlockStmt { + p := n.Pos() + return &ast.BlockStmt{p, []ast.Stmt{lp.emptyStmt(n)}, p} +} diff --git a/vendor/src/github.com/go-check/check/printer_test.go b/vendor/src/github.com/go-check/check/printer_test.go new file mode 100644 index 000000000..538b2d52e --- /dev/null +++ b/vendor/src/github.com/go-check/check/printer_test.go @@ -0,0 +1,104 @@ +package check_test + +import ( + . "gopkg.in/check.v1" +) + +var _ = Suite(&PrinterS{}) + +type PrinterS struct{} + +func (s *PrinterS) TestCountSuite(c *C) { + suitesRun += 1 +} + +var printTestFuncLine int + +func init() { + printTestFuncLine = getMyLine() + 3 +} + +func printTestFunc() { + println(1) // Comment1 + if 2 == 2 { // Comment2 + println(3) // Comment3 + } + switch 5 { + case 6: println(6) // Comment6 + println(7) + } + switch interface{}(9).(type) {// Comment9 + case int: println(10) + println(11) + } + select { + case <-(chan bool)(nil): println(14) + println(15) + default: println(16) + println(17) + } + println(19, + 20) + _ = func() { println(21) + println(22) + } + println(24, func() { + println(25) + }) + // Leading comment + // with multiple lines. + println(29) // Comment29 +} + +var printLineTests = []struct { + line int + output string +}{ + {1, "println(1) // Comment1"}, + {2, "if 2 == 2 { // Comment2\n ...\n}"}, + {3, "println(3) // Comment3"}, + {5, "switch 5 {\n...\n}"}, + {6, "case 6:\n println(6) // Comment6\n ..."}, + {7, "println(7)"}, + {9, "switch interface{}(9).(type) { // Comment9\n...\n}"}, + {10, "case int:\n println(10)\n ..."}, + {14, "case <-(chan bool)(nil):\n println(14)\n ..."}, + {15, "println(15)"}, + {16, "default:\n println(16)\n ..."}, + {17, "println(17)"}, + {19, "println(19,\n 20)"}, + {20, "println(19,\n 20)"}, + {21, "_ = func() {\n println(21)\n println(22)\n}"}, + {22, "println(22)"}, + {24, "println(24, func() {\n println(25)\n})"}, + {25, "println(25)"}, + {26, "println(24, func() {\n println(25)\n})"}, + {29, "// Leading comment\n// with multiple lines.\nprintln(29) // Comment29"}, +} + +func (s *PrinterS) TestPrintLine(c *C) { + for _, test := range printLineTests { + output, err := PrintLine("printer_test.go", printTestFuncLine+test.line) + c.Assert(err, IsNil) + c.Assert(output, Equals, test.output) + } +} + +var indentTests = []struct { + in, out string +}{ + {"", ""}, + {"\n", "\n"}, + {"a", ">>>a"}, + {"a\n", ">>>a\n"}, + {"a\nb", ">>>a\n>>>b"}, + {" ", ">>> "}, +} + +func (s *PrinterS) TestIndent(c *C) { + for _, test := range indentTests { + out := Indent(test.in, ">>>") + c.Assert(out, Equals, test.out) + } + +} diff --git a/vendor/src/github.com/go-check/check/run.go b/vendor/src/github.com/go-check/check/run.go new file mode 100644 index 000000000..da8fd7987 --- /dev/null +++ b/vendor/src/github.com/go-check/check/run.go @@ -0,0 +1,175 @@ +package check + +import ( + "bufio" + "flag" + "fmt" + "os" + "testing" + "time" +) + +// ----------------------------------------------------------------------- +// Test suite registry. + +var allSuites []interface{} + +// Suite registers the given value as a test suite to be run. Any methods +// starting with the Test prefix in the given value will be considered as +// a test method. +func Suite(suite interface{}) interface{} { + allSuites = append(allSuites, suite) + return suite +} + +// ----------------------------------------------------------------------- +// Public running interface. + +var ( + oldFilterFlag = flag.String("gocheck.f", "", "Regular expression selecting which tests and/or suites to run") + oldVerboseFlag = flag.Bool("gocheck.v", false, "Verbose mode") + oldStreamFlag = flag.Bool("gocheck.vv", false, "Super verbose mode (disables output caching)") + oldBenchFlag = flag.Bool("gocheck.b", false, "Run benchmarks") + oldBenchTime = flag.Duration("gocheck.btime", 1*time.Second, "approximate run time for each benchmark") + oldListFlag = flag.Bool("gocheck.list", false, "List the names of all tests that will be run") + oldWorkFlag = flag.Bool("gocheck.work", false, "Display and do not remove the test working directory") + + newFilterFlag = flag.String("check.f", "", "Regular expression selecting which tests and/or suites to run") + newVerboseFlag = flag.Bool("check.v", false, "Verbose mode") + newStreamFlag = flag.Bool("check.vv", false, "Super verbose mode (disables output caching)") + newBenchFlag = flag.Bool("check.b", false, "Run benchmarks") + newBenchTime = flag.Duration("check.btime", 1*time.Second, "approximate run time for each benchmark") + newBenchMem = flag.Bool("check.bmem", false, "Report memory benchmarks") + newListFlag = flag.Bool("check.list", false, "List the names of all tests that will be run") + newWorkFlag = flag.Bool("check.work", false, "Display and do not remove the test working directory") +) + +// TestingT runs all test suites registered with the Suite function, +// printing results to stdout, and reporting any failures back to +// the "testing" package. +func TestingT(testingT *testing.T) { + benchTime := *newBenchTime + if benchTime == 1*time.Second { + benchTime = *oldBenchTime + } + conf := &RunConf{ + Filter: *oldFilterFlag + *newFilterFlag, + Verbose: *oldVerboseFlag || *newVerboseFlag, + Stream: *oldStreamFlag || *newStreamFlag, + Benchmark: *oldBenchFlag || *newBenchFlag, + BenchmarkTime: benchTime, + BenchmarkMem: *newBenchMem, + KeepWorkDir: *oldWorkFlag || *newWorkFlag, + } + if *oldListFlag || *newListFlag { + w := bufio.NewWriter(os.Stdout) + for _, name := range ListAll(conf) { + fmt.Fprintln(w, name) + } + w.Flush() + return + } + result := RunAll(conf) + println(result.String()) + if !result.Passed() { + testingT.Fail() + } +} + +// RunAll runs all test suites registered with the Suite function, using the +// provided run configuration. +func RunAll(runConf *RunConf) *Result { + result := Result{} + for _, suite := range allSuites { + result.Add(Run(suite, runConf)) + } + return &result +} + +// Run runs the provided test suite using the provided run configuration. +func Run(suite interface{}, runConf *RunConf) *Result { + runner := newSuiteRunner(suite, runConf) + return runner.run() +} + +// ListAll returns the names of all the test functions registered with the +// Suite function that will be run with the provided run configuration. +func ListAll(runConf *RunConf) []string { + var names []string + for _, suite := range allSuites { + names = append(names, List(suite, runConf)...) + } + return names +} + +// List returns the names of the test functions in the given +// suite that will be run with the provided run configuration. +func List(suite interface{}, runConf *RunConf) []string { + var names []string + runner := newSuiteRunner(suite, runConf) + for _, t := range runner.tests { + names = append(names, t.String()) + } + return names +} + +// ----------------------------------------------------------------------- +// Result methods. + +func (r *Result) Add(other *Result) { + r.Succeeded += other.Succeeded + r.Skipped += other.Skipped + r.Failed += other.Failed + r.Panicked += other.Panicked + r.FixturePanicked += other.FixturePanicked + r.ExpectedFailures += other.ExpectedFailures + r.Missed += other.Missed + if r.WorkDir != "" && other.WorkDir != "" { + r.WorkDir += ":" + other.WorkDir + } else if other.WorkDir != "" { + r.WorkDir = other.WorkDir + } +} + +func (r *Result) Passed() bool { + return (r.Failed == 0 && r.Panicked == 0 && + r.FixturePanicked == 0 && r.Missed == 0 && + r.RunError == nil) +} + +func (r *Result) String() string { + if r.RunError != nil { + return "ERROR: " + r.RunError.Error() + } + + var value string + if r.Failed == 0 && r.Panicked == 0 && r.FixturePanicked == 0 && + r.Missed == 0 { + value = "OK: " + } else { + value = "OOPS: " + } + value += fmt.Sprintf("%d passed", r.Succeeded) + if r.Skipped != 0 { + value += fmt.Sprintf(", %d skipped", r.Skipped) + } + if r.ExpectedFailures != 0 { + value += fmt.Sprintf(", %d expected failures", r.ExpectedFailures) + } + if r.Failed != 0 { + value += fmt.Sprintf(", %d FAILED", r.Failed) + } + if r.Panicked != 0 { + value += fmt.Sprintf(", %d PANICKED", r.Panicked) + } + if r.FixturePanicked != 0 { + value += fmt.Sprintf(", %d FIXTURE-PANICKED", r.FixturePanicked) + } + if r.Missed != 0 { + value += fmt.Sprintf(", %d MISSED", r.Missed) + } + if r.WorkDir != "" { + value += "\nWORK=" + r.WorkDir + } + return value +} diff --git a/vendor/src/github.com/go-check/check/run_test.go b/vendor/src/github.com/go-check/check/run_test.go new file mode 100644 index 000000000..f41fffc3f --- /dev/null +++ b/vendor/src/github.com/go-check/check/run_test.go @@ -0,0 +1,419 @@ +// These tests verify the test running logic. + +package check_test + +import ( + "errors" + . "gopkg.in/check.v1" + "os" + "sync" +) + +var runnerS = Suite(&RunS{}) + +type RunS struct{} + +func (s *RunS) TestCountSuite(c *C) { + suitesRun += 1 +} + +// ----------------------------------------------------------------------- +// Tests ensuring result counting works properly. + +func (s *RunS) TestSuccess(c *C) { + output := String{} + result := Run(&SuccessHelper{}, &RunConf{Output: &output}) + c.Check(result.Succeeded, Equals, 1) + c.Check(result.Failed, Equals, 0) + c.Check(result.Skipped, Equals, 0) + c.Check(result.Panicked, Equals, 0) + c.Check(result.FixturePanicked, Equals, 0) + c.Check(result.Missed, Equals, 0) + c.Check(result.RunError, IsNil) +} + +func (s *RunS) TestFailure(c *C) { + output := String{} + result := Run(&FailHelper{}, &RunConf{Output: &output}) + c.Check(result.Succeeded, Equals, 0) + c.Check(result.Failed, Equals, 1) + c.Check(result.Skipped, Equals, 0) + c.Check(result.Panicked, Equals, 0) + c.Check(result.FixturePanicked, Equals, 0) + c.Check(result.Missed, Equals, 0) + c.Check(result.RunError, IsNil) +} + +func (s *RunS) TestFixture(c *C) { + output := String{} + result := Run(&FixtureHelper{}, &RunConf{Output: &output}) + c.Check(result.Succeeded, Equals, 2) + c.Check(result.Failed, Equals, 0) + c.Check(result.Skipped, Equals, 0) + c.Check(result.Panicked, Equals, 0) + c.Check(result.FixturePanicked, Equals, 0) + c.Check(result.Missed, Equals, 0) + c.Check(result.RunError, IsNil) +} + +func (s *RunS) TestPanicOnTest(c *C) { + output := String{} + helper := &FixtureHelper{panicOn: "Test1"} + result := Run(helper, &RunConf{Output: &output}) + c.Check(result.Succeeded, Equals, 1) + c.Check(result.Failed, Equals, 0) + c.Check(result.Skipped, Equals, 0) + c.Check(result.Panicked, Equals, 1) + c.Check(result.FixturePanicked, Equals, 0) + c.Check(result.Missed, Equals, 0) + c.Check(result.RunError, IsNil) +} + +func (s *RunS) TestPanicOnSetUpTest(c *C) { + output := String{} + helper := &FixtureHelper{panicOn: "SetUpTest"} + result := Run(helper, &RunConf{Output: &output}) + c.Check(result.Succeeded, Equals, 0) + c.Check(result.Failed, Equals, 0) + c.Check(result.Skipped, Equals, 0) + c.Check(result.Panicked, Equals, 0) + c.Check(result.FixturePanicked, Equals, 1) + c.Check(result.Missed, Equals, 2) + c.Check(result.RunError, IsNil) +} + +func (s *RunS) TestPanicOnSetUpSuite(c *C) { + output := String{} + helper := &FixtureHelper{panicOn: "SetUpSuite"} + result := Run(helper, &RunConf{Output: &output}) + c.Check(result.Succeeded, Equals, 0) + c.Check(result.Failed, Equals, 0) + c.Check(result.Skipped, Equals, 0) + c.Check(result.Panicked, Equals, 0) + c.Check(result.FixturePanicked, Equals, 1) + c.Check(result.Missed, Equals, 2) + c.Check(result.RunError, IsNil) +} + +// ----------------------------------------------------------------------- +// Check result aggregation. + +func (s *RunS) TestAdd(c *C) { + result := &Result{ + Succeeded: 1, + Skipped: 2, + Failed: 3, + Panicked: 4, + FixturePanicked: 5, + Missed: 6, + ExpectedFailures: 7, + } + result.Add(&Result{ + Succeeded: 10, + Skipped: 20, + Failed: 30, + Panicked: 40, + FixturePanicked: 50, + Missed: 60, + ExpectedFailures: 70, + }) + c.Check(result.Succeeded, Equals, 11) + c.Check(result.Skipped, Equals, 22) + c.Check(result.Failed, Equals, 33) + c.Check(result.Panicked, Equals, 44) + c.Check(result.FixturePanicked, Equals, 55) + c.Check(result.Missed, Equals, 66) + c.Check(result.ExpectedFailures, Equals, 77) + c.Check(result.RunError, IsNil) +} + +// ----------------------------------------------------------------------- +// Check the Passed() method. + +func (s *RunS) TestPassed(c *C) { + c.Assert((&Result{}).Passed(), Equals, true) + c.Assert((&Result{Succeeded: 1}).Passed(), Equals, true) + c.Assert((&Result{Skipped: 1}).Passed(), Equals, true) + c.Assert((&Result{Failed: 1}).Passed(), Equals, false) + c.Assert((&Result{Panicked: 1}).Passed(), Equals, false) + c.Assert((&Result{FixturePanicked: 1}).Passed(), Equals, false) + c.Assert((&Result{Missed: 1}).Passed(), Equals, false) + c.Assert((&Result{RunError: errors.New("!")}).Passed(), Equals, false) +} + +// ----------------------------------------------------------------------- +// Check that result printing is working correctly. + +func (s *RunS) TestPrintSuccess(c *C) { + result := &Result{Succeeded: 5} + c.Check(result.String(), Equals, "OK: 5 passed") +} + +func (s *RunS) TestPrintFailure(c *C) { + result := &Result{Failed: 5} + c.Check(result.String(), Equals, "OOPS: 0 passed, 5 FAILED") +} + +func (s *RunS) TestPrintSkipped(c *C) { + result := &Result{Skipped: 5} + c.Check(result.String(), Equals, "OK: 0 passed, 5 skipped") +} + +func (s *RunS) TestPrintExpectedFailures(c *C) { + result := &Result{ExpectedFailures: 5} + c.Check(result.String(), Equals, "OK: 0 passed, 5 expected failures") +} + +func (s *RunS) TestPrintPanicked(c *C) { + result := &Result{Panicked: 5} + c.Check(result.String(), Equals, "OOPS: 0 passed, 5 PANICKED") +} + +func (s *RunS) TestPrintFixturePanicked(c *C) { + result := &Result{FixturePanicked: 5} + c.Check(result.String(), Equals, "OOPS: 0 passed, 5 FIXTURE-PANICKED") +} + +func (s *RunS) TestPrintMissed(c *C) { + result := &Result{Missed: 5} + c.Check(result.String(), Equals, "OOPS: 0 passed, 5 MISSED") +} + +func (s *RunS) TestPrintAll(c *C) { + result := &Result{Succeeded: 1, Skipped: 2, ExpectedFailures: 3, + Panicked: 4, FixturePanicked: 5, Missed: 6} + c.Check(result.String(), Equals, + "OOPS: 1 passed, 2 skipped, 3 expected failures, 4 PANICKED, "+ + "5 FIXTURE-PANICKED, 6 MISSED") +} + +func (s *RunS) TestPrintRunError(c *C) { + result := &Result{Succeeded: 1, Failed: 1, + RunError: errors.New("Kaboom!")} + c.Check(result.String(), Equals, "ERROR: Kaboom!") +} + +// ----------------------------------------------------------------------- +// Verify that the method pattern flag works correctly. + +func (s *RunS) TestFilterTestName(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: "Test[91]"} + Run(&helper, &runConf) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 5) +} + +func (s *RunS) TestFilterTestNameWithAll(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: ".*"} + Run(&helper, &runConf) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "SetUpTest") + c.Check(helper.calls[5], Equals, "Test2") + c.Check(helper.calls[6], Equals, "TearDownTest") + c.Check(helper.calls[7], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 8) +} + +func (s *RunS) TestFilterSuiteName(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: "FixtureHelper"} + Run(&helper, &runConf) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test1") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "SetUpTest") + c.Check(helper.calls[5], Equals, "Test2") + c.Check(helper.calls[6], Equals, "TearDownTest") + c.Check(helper.calls[7], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 8) +} + +func (s *RunS) TestFilterSuiteNameAndTestName(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: "FixtureHelper\\.Test2"} + Run(&helper, &runConf) + c.Check(helper.calls[0], Equals, "SetUpSuite") + c.Check(helper.calls[1], Equals, "SetUpTest") + c.Check(helper.calls[2], Equals, "Test2") + c.Check(helper.calls[3], Equals, "TearDownTest") + c.Check(helper.calls[4], Equals, "TearDownSuite") + c.Check(len(helper.calls), Equals, 5) +} + +func (s *RunS) TestFilterAllOut(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: "NotFound"} + Run(&helper, &runConf) + c.Check(len(helper.calls), Equals, 0) +} + +func (s *RunS) TestRequirePartialMatch(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: "est"} + Run(&helper, &runConf) + c.Check(len(helper.calls), Equals, 8) +} + +func (s *RunS) TestFilterError(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Filter: "]["} + result := Run(&helper, &runConf) + c.Check(result.String(), Equals, + "ERROR: Bad filter expression: error parsing regexp: missing closing ]: `[`") + c.Check(len(helper.calls), Equals, 0) +} + +// ----------------------------------------------------------------------- +// Verify that List works correctly. + +func (s *RunS) TestListFiltered(c *C) { + names := List(&FixtureHelper{}, &RunConf{Filter: "1"}) + c.Assert(names, DeepEquals, []string{ + "FixtureHelper.Test1", + }) +} + +func (s *RunS) TestList(c *C) { + names := List(&FixtureHelper{}, &RunConf{}) + c.Assert(names, DeepEquals, []string{ + "FixtureHelper.Test1", + "FixtureHelper.Test2", + }) +} + +// ----------------------------------------------------------------------- +// Verify that verbose mode prints tests which pass as well. + +func (s *RunS) TestVerboseMode(c *C) { + helper := FixtureHelper{} + output := String{} + runConf := RunConf{Output: &output, Verbose: true} + Run(&helper, &runConf) + + expected := "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test1\t *[.0-9]+s\n" + + "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test2\t *[.0-9]+s\n" + + c.Assert(output.value, Matches, expected) +} + +func (s *RunS) TestVerboseModeWithFailBeforePass(c *C) { + helper := FixtureHelper{panicOn: "Test1"} + output := String{} + runConf := RunConf{Output: &output, Verbose: true} + Run(&helper, &runConf) + + expected := "(?s).*PANIC.*\n-+\n" + // Should have an extra line. + "PASS: check_test\\.go:[0-9]+: FixtureHelper\\.Test2\t *[.0-9]+s\n" + + c.Assert(output.value, Matches, expected) +} + +// ----------------------------------------------------------------------- +// Verify the stream output mode. In this mode there's no output caching. + +type StreamHelper struct { + l2 sync.Mutex + l3 sync.Mutex +} + +func (s *StreamHelper) SetUpSuite(c *C) { + c.Log("0") +} + +func (s *StreamHelper) Test1(c *C) { + c.Log("1") + s.l2.Lock() + s.l3.Lock() + go func() { + s.l2.Lock() // Wait for "2". + c.Log("3") + s.l3.Unlock() + }() +} + +func (s *StreamHelper) Test2(c *C) { + c.Log("2") + s.l2.Unlock() + s.l3.Lock() // Wait for "3". + c.Fail() + c.Log("4") +} + +func (s *RunS) TestStreamMode(c *C) { + helper := &StreamHelper{} + output := String{} + runConf := RunConf{Output: &output, Stream: true} + Run(helper, &runConf) + + expected := "START: run_test\\.go:[0-9]+: StreamHelper\\.SetUpSuite\n0\n" + + "PASS: run_test\\.go:[0-9]+: StreamHelper\\.SetUpSuite\t *[.0-9]+s\n\n" + + "START: run_test\\.go:[0-9]+: StreamHelper\\.Test1\n1\n" + + "PASS: run_test\\.go:[0-9]+: StreamHelper\\.Test1\t *[.0-9]+s\n\n" + + "START: run_test\\.go:[0-9]+: StreamHelper\\.Test2\n2\n3\n4\n" + + "FAIL: run_test\\.go:[0-9]+: StreamHelper\\.Test2\n\n" + + c.Assert(output.value, Matches, expected) +} + +type StreamMissHelper struct{} + +func (s *StreamMissHelper) SetUpSuite(c *C) { + c.Log("0") + c.Fail() +} + +func (s *StreamMissHelper) Test1(c *C) { + c.Log("1") +} + +func (s *RunS) TestStreamModeWithMiss(c *C) { + helper := &StreamMissHelper{} + output := String{} + runConf := RunConf{Output: &output, Stream: true} + Run(helper, &runConf) + + expected := "START: run_test\\.go:[0-9]+: StreamMissHelper\\.SetUpSuite\n0\n" + + "FAIL: run_test\\.go:[0-9]+: StreamMissHelper\\.SetUpSuite\n\n" + + "START: run_test\\.go:[0-9]+: StreamMissHelper\\.Test1\n" + + "MISS: run_test\\.go:[0-9]+: StreamMissHelper\\.Test1\n\n" + + c.Assert(output.value, Matches, expected) +} + +// ----------------------------------------------------------------------- +// Verify that that the keep work dir request indeed does so. + +type WorkDirSuite struct {} + +func (s *WorkDirSuite) Test(c *C) { + c.MkDir() +} + +func (s *RunS) TestKeepWorkDir(c *C) { + output := String{} + runConf := RunConf{Output: &output, Verbose: true, KeepWorkDir: true} + result := Run(&WorkDirSuite{}, &runConf) + + c.Assert(result.String(), Matches, ".*\nWORK=" + result.WorkDir) + + stat, err := os.Stat(result.WorkDir) + c.Assert(err, IsNil) + c.Assert(stat.IsDir(), Equals, true) +} From dc944ea7e48d11a2906e751d3e61daf08faee054 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Sat, 18 Apr 2015 09:46:47 -0700 Subject: [PATCH 548/999] Use suite for integration-cli It prints test name and duration for each test. Also performs deleteAllContainers after each test. Signed-off-by: Alexander Morozov --- integration-cli/check_test.go | 34 + integration-cli/docker_api_attach_test.go | 22 +- integration-cli/docker_api_containers_test.go | 369 ++-- .../docker_api_exec_resize_test.go | 14 +- integration-cli/docker_api_exec_test.go | 12 +- integration-cli/docker_api_images_test.go | 63 +- integration-cli/docker_api_info_test.go | 2 - integration-cli/docker_api_inspect_test.go | 19 +- integration-cli/docker_api_logs_test.go | 25 +- integration-cli/docker_api_resize_test.go | 36 +- integration-cli/docker_api_version_test.go | 10 +- integration-cli/docker_cli_attach_test.go | 73 +- .../docker_cli_attach_unix_test.go | 104 +- integration-cli/docker_cli_build_test.go | 1836 ++++++++--------- integration-cli/docker_cli_by_digest_test.go | 296 ++- integration-cli/docker_cli_commit_test.go | 115 +- integration-cli/docker_cli_config_test.go | 12 +- integration-cli/docker_cli_cp_test.go | 290 ++- integration-cli/docker_cli_create_test.go | 155 +- integration-cli/docker_cli_daemon_test.go | 449 ++-- integration-cli/docker_cli_diff_test.go | 37 +- integration-cli/docker_cli_events_test.go | 208 +- .../docker_cli_events_unix_test.go | 26 +- integration-cli/docker_cli_exec_test.go | 306 ++- .../docker_cli_export_import_test.go | 31 +- integration-cli/docker_cli_help_test.go | 35 +- integration-cli/docker_cli_history_test.go | 35 +- integration-cli/docker_cli_images_test.go | 87 +- integration-cli/docker_cli_import_test.go | 16 +- integration-cli/docker_cli_info_test.go | 10 +- integration-cli/docker_cli_inspect_test.go | 10 +- integration-cli/docker_cli_kill_test.go | 29 +- integration-cli/docker_cli_links_test.go | 189 +- integration-cli/docker_cli_login_test.go | 8 +- integration-cli/docker_cli_logs_test.go | 113 +- integration-cli/docker_cli_nat_test.go | 25 +- integration-cli/docker_cli_pause_test.go | 51 +- integration-cli/docker_cli_port_test.go | 97 +- integration-cli/docker_cli_proxy_test.go | 27 +- integration-cli/docker_cli_ps_test.go | 257 ++- integration-cli/docker_cli_pull_test.go | 56 +- integration-cli/docker_cli_push_test.go | 79 +- integration-cli/docker_cli_rename_test.go | 53 +- integration-cli/docker_cli_restart_test.go | 109 +- integration-cli/docker_cli_rm_test.go | 76 +- integration-cli/docker_cli_rmi_test.go | 121 +- integration-cli/docker_cli_run_test.go | 1819 ++++++---------- integration-cli/docker_cli_run_unix_test.go | 121 +- integration-cli/docker_cli_save_load_test.go | 121 +- .../docker_cli_save_load_unix_test.go | 31 +- integration-cli/docker_cli_search_test.go | 44 +- integration-cli/docker_cli_start_test.go | 125 +- integration-cli/docker_cli_tag_test.go | 62 +- integration-cli/docker_cli_top_test.go | 46 +- integration-cli/docker_cli_version_test.go | 10 +- integration-cli/docker_cli_wait_test.go | 43 +- integration-cli/docker_utils.go | 86 +- integration-cli/registry.go | 7 +- integration-cli/requirements.go | 7 +- integration-cli/utils.go | 4 - 60 files changed, 3746 insertions(+), 4807 deletions(-) create mode 100644 integration-cli/check_test.go diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go new file mode 100644 index 000000000..330bc373b --- /dev/null +++ b/integration-cli/check_test.go @@ -0,0 +1,34 @@ +package main + +import ( + "fmt" + "testing" + "time" + + "github.com/go-check/check" +) + +func Test(t *testing.T) { check.TestingT(t) } + +type TimerSuite struct { + start time.Time +} + +func (s *TimerSuite) SetUpTest(c *check.C) { + s.start = time.Now() +} + +func (s *TimerSuite) TearDownTest(c *check.C) { + fmt.Printf("%-60s%.2f\n", c.TestName(), time.Since(s.start).Seconds()) +} + +type DockerSuite struct { + TimerSuite +} + +func (s *DockerSuite) TearDownTest(c *check.C) { + deleteAllContainers() + s.TimerSuite.TearDownTest(c) +} + +var _ = check.Suite(&DockerSuite{}) diff --git a/integration-cli/docker_api_attach_test.go b/integration-cli/docker_api_attach_test.go index 3257798c5..ce7f85d30 100644 --- a/integration-cli/docker_api_attach_test.go +++ b/integration-cli/docker_api_attach_test.go @@ -4,23 +4,23 @@ import ( "bytes" "os/exec" "strings" - "testing" "time" + "github.com/go-check/check" + "code.google.com/p/go.net/websocket" ) -func TestGetContainersAttachWebsocket(t *testing.T) { +func (s *DockerSuite) TestGetContainersAttachWebsocket(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-dit", "busybox", "cat") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } - defer deleteAllContainers() rwc, err := sockConn(time.Duration(10 * time.Second)) if err != nil { - t.Fatal(err) + c.Fatal(err) } cleanedContainerID := strings.TrimSpace(out) @@ -29,12 +29,12 @@ func TestGetContainersAttachWebsocket(t *testing.T) { "http://localhost", ) if err != nil { - t.Fatal(err) + c.Fatal(err) } ws, err := websocket.NewClient(config, rwc) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ws.Close() @@ -43,7 +43,7 @@ func TestGetContainersAttachWebsocket(t *testing.T) { outChan := make(chan string) go func() { if _, err := ws.Read(actual); err != nil { - t.Fatal(err) + c.Fatal(err) } outChan <- "done" }() @@ -51,7 +51,7 @@ func TestGetContainersAttachWebsocket(t *testing.T) { inChan := make(chan string) go func() { if _, err := ws.Write(expected); err != nil { - t.Fatal(err) + c.Fatal(err) } inChan <- "done" }() @@ -60,8 +60,6 @@ func TestGetContainersAttachWebsocket(t *testing.T) { <-outChan if !bytes.Equal(expected, actual) { - t.Fatal("Expected output on websocket to match input") + c.Fatal("Expected output on websocket to match input") } - - logDone("container attach websocket - can echo input via cat") } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 1dea47845..db4093733 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -7,65 +7,59 @@ import ( "net/http" "os/exec" "strings" - "testing" "time" "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" + "github.com/go-check/check" ) -func TestContainerApiGetAll(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestContainerApiGetAll(c *check.C) { startCount, err := getContainerCount() if err != nil { - t.Fatalf("Cannot query container count: %v", err) + c.Fatalf("Cannot query container count: %v", err) } name := "getall" runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("Error on container creation: %v, output: %q", err, out) + c.Fatalf("Error on container creation: %v, output: %q", err, out) } _, body, err := sockRequest("GET", "/containers/json?all=1", nil) if err != nil { - t.Fatalf("GET all containers sockRequest failed: %v", err) + c.Fatalf("GET all containers sockRequest failed: %v", err) } var inspectJSON []struct { Names []string } if err = json.Unmarshal(body, &inspectJSON); err != nil { - t.Fatalf("unable to unmarshal response body: %v", err) + c.Fatalf("unable to unmarshal response body: %v", err) } if len(inspectJSON) != startCount+1 { - t.Fatalf("Expected %d container(s), %d found (started with: %d)", startCount+1, len(inspectJSON), startCount) + c.Fatalf("Expected %d container(s), %d found (started with: %d)", startCount+1, len(inspectJSON), startCount) } if actual := inspectJSON[0].Names[0]; actual != "/"+name { - t.Fatalf("Container Name mismatch. Expected: %q, received: %q\n", "/"+name, actual) + c.Fatalf("Container Name mismatch. Expected: %q, received: %q\n", "/"+name, actual) } - - logDone("container REST API - check GET json/all=1") } -func TestContainerApiGetExport(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestContainerApiGetExport(c *check.C) { name := "exportcontainer" runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("Error on container creation: %v, output: %q", err, out) + c.Fatalf("Error on container creation: %v, output: %q", err, out) } _, body, err := sockRequest("GET", "/containers/"+name+"/export", nil) if err != nil { - t.Fatalf("GET containers/export sockRequest failed: %v", err) + c.Fatalf("GET containers/export sockRequest failed: %v", err) } found := false @@ -75,7 +69,7 @@ func TestContainerApiGetExport(t *testing.T) { if err == io.EOF { break } - t.Fatal(err) + c.Fatal(err) } if h.Name == "test" { found = true @@ -84,25 +78,21 @@ func TestContainerApiGetExport(t *testing.T) { } if !found { - t.Fatalf("The created test file has not been found in the exported image") + c.Fatalf("The created test file has not been found in the exported image") } - - logDone("container REST API - check GET containers/export") } -func TestContainerApiGetChanges(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestContainerApiGetChanges(c *check.C) { name := "changescontainer" runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "rm", "/etc/passwd") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("Error on container creation: %v, output: %q", err, out) + c.Fatalf("Error on container creation: %v, output: %q", err, out) } _, body, err := sockRequest("GET", "/containers/"+name+"/changes", nil) if err != nil { - t.Fatalf("GET containers/changes sockRequest failed: %v", err) + c.Fatalf("GET containers/changes sockRequest failed: %v", err) } changes := []struct { @@ -110,7 +100,7 @@ func TestContainerApiGetChanges(t *testing.T) { Path string }{} if err = json.Unmarshal(body, &changes); err != nil { - t.Fatalf("unable to unmarshal response body: %v", err) + c.Fatalf("unable to unmarshal response body: %v", err) } // Check the changelog for removal of /etc/passwd @@ -121,14 +111,11 @@ func TestContainerApiGetChanges(t *testing.T) { } } if !success { - t.Fatalf("/etc/passwd has been removed but is not present in the diff") + c.Fatalf("/etc/passwd has been removed but is not present in the diff") } - - logDone("container REST API - check GET containers/changes") } -func TestContainerApiStartVolumeBinds(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerApiStartVolumeBinds(c *check.C) { name := "testing" config := map[string]interface{}{ "Image": "busybox", @@ -136,7 +123,7 @@ func TestContainerApiStartVolumeBinds(t *testing.T) { } if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - t.Fatal(err) + c.Fatal(err) } bindPath := randomUnixTmpDirPath("test") @@ -144,24 +131,21 @@ func TestContainerApiStartVolumeBinds(t *testing.T) { "Binds": []string{bindPath + ":/tmp"}, } if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { - t.Fatal(err) + c.Fatal(err) } pth, err := inspectFieldMap(name, "Volumes", "/tmp") if err != nil { - t.Fatal(err) + c.Fatal(err) } if pth != bindPath { - t.Fatalf("expected volume host path to be %s, got %s", bindPath, pth) + c.Fatalf("expected volume host path to be %s, got %s", bindPath, pth) } - - logDone("container REST API - check volume binds on start") } // Test for GH#10618 -func TestContainerApiStartDupVolumeBinds(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerApiStartDupVolumeBinds(c *check.C) { name := "testdups" config := map[string]interface{}{ "Image": "busybox", @@ -169,7 +153,7 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) { } if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - t.Fatal(err) + c.Fatal(err) } bindPath1 := randomUnixTmpDirPath("test1") @@ -179,22 +163,19 @@ func TestContainerApiStartDupVolumeBinds(t *testing.T) { "Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"}, } if _, body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil { - t.Fatal("expected container start to fail when duplicate volume binds to same container path") + c.Fatal("expected container start to fail when duplicate volume binds to same container path") } else { if !strings.Contains(string(body), "Duplicate volume") { - t.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err) + c.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err) } } - - logDone("container REST API - check for duplicate volume binds error on start") } -func TestContainerApiStartVolumesFrom(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) { volName := "voltst" volPath := "/tmp" if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } name := "testing" @@ -204,41 +185,38 @@ func TestContainerApiStartVolumesFrom(t *testing.T) { } if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - t.Fatal(err) + c.Fatal(err) } config = map[string]interface{}{ "VolumesFrom": []string{volName}, } if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { - t.Fatal(err) + c.Fatal(err) } pth, err := inspectFieldMap(name, "Volumes", volPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } pth2, err := inspectFieldMap(volName, "Volumes", volPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } if pth != pth2 { - t.Fatalf("expected volume host path to be %s, got %s", pth, pth2) + c.Fatalf("expected volume host path to be %s, got %s", pth, pth2) } - - logDone("container REST API - check VolumesFrom on start") } // Ensure that volumes-from has priority over binds/anything else // This is pretty much the same as TestRunApplyVolumesFromBeforeVolumes, except with passing the VolumesFrom and the bind on start -func TestVolumesFromHasPriority(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestVolumesFromHasPriority(c *check.C) { volName := "voltst2" volPath := "/tmp" if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } name := "testing" @@ -248,7 +226,7 @@ func TestVolumesFromHasPriority(t *testing.T) { } if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - t.Fatal(err) + c.Fatal(err) } bindPath := randomUnixTmpDirPath("test") @@ -257,34 +235,31 @@ func TestVolumesFromHasPriority(t *testing.T) { "Binds": []string{bindPath + ":/tmp"}, } if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { - t.Fatal(err) + c.Fatal(err) } pth, err := inspectFieldMap(name, "Volumes", volPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } pth2, err := inspectFieldMap(volName, "Volumes", volPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } if pth != pth2 { - t.Fatalf("expected volume host path to be %s, got %s", pth, pth2) + c.Fatalf("expected volume host path to be %s, got %s", pth, pth2) } - - logDone("container REST API - check VolumesFrom has priority") } -func TestGetContainerStats(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestGetContainerStats(c *check.C) { var ( name = "statscontainer" runCmd = exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "top") ) out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("Error on container creation: %v, output: %q", err, out) + c.Fatalf("Error on container creation: %v, output: %q", err, out) } type b struct { body []byte @@ -299,38 +274,36 @@ func TestGetContainerStats(t *testing.T) { // allow some time to stream the stats from the container time.Sleep(4 * time.Second) if _, err := runCommand(exec.Command(dockerBinary, "rm", "-f", name)); err != nil { - t.Fatal(err) + c.Fatal(err) } // collect the results from the stats stream or timeout and fail // if the stream was not disconnected. select { case <-time.After(2 * time.Second): - t.Fatal("stream was not closed after container was removed") + c.Fatal("stream was not closed after container was removed") case sr := <-bc: if sr.err != nil { - t.Fatal(sr.err) + c.Fatal(sr.err) } dec := json.NewDecoder(bytes.NewBuffer(sr.body)) var s *types.Stats // decode only one object from the stream if err := dec.Decode(&s); err != nil { - t.Fatal(err) + c.Fatal(err) } } - logDone("container REST API - check GET containers/stats") } -func TestGetStoppedContainerStats(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) { var ( name = "statscontainer" runCmd = exec.Command(dockerBinary, "create", "--name", name, "busybox", "top") ) out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("Error on container creation: %v, output: %q", err, out) + c.Fatalf("Error on container creation: %v, output: %q", err, out) } go func() { @@ -338,17 +311,15 @@ func TestGetStoppedContainerStats(t *testing.T) { // just send request and see if panic or error would happen on daemon side. _, _, err := sockRequest("GET", "/containers/"+name+"/stats", nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } }() // allow some time to send request and let daemon deal with it time.Sleep(1 * time.Second) - - logDone("container REST API - check GET stopped containers/stats") } -func TestBuildApiDockerfilePath(t *testing.T) { +func (s *DockerSuite) TestBuildApiDockerfilePath(c *check.C) { // Test to make sure we stop people from trying to leave the // build context when specifying the path to the dockerfile buffer := new(bytes.Buffer) @@ -360,33 +331,31 @@ func TestBuildApiDockerfilePath(t *testing.T) { Name: "Dockerfile", Size: int64(len(dockerfile)), }); err != nil { - t.Fatalf("failed to write tar file header: %v", err) + c.Fatalf("failed to write tar file header: %v", err) } if _, err := tw.Write(dockerfile); err != nil { - t.Fatalf("failed to write tar file content: %v", err) + c.Fatalf("failed to write tar file content: %v", err) } if err := tw.Close(); err != nil { - t.Fatalf("failed to close tar archive: %v", err) + c.Fatalf("failed to close tar archive: %v", err) } _, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") if err == nil { out, _ := readBody(body) - t.Fatalf("Build was supposed to fail: %s", out) + c.Fatalf("Build was supposed to fail: %s", out) } out, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(string(out), "must be within the build context") { - t.Fatalf("Didn't complain about leaving build context: %s", out) + c.Fatalf("Didn't complain about leaving build context: %s", out) } - - logDone("container REST API - check build w/bad Dockerfile path") } -func TestBuildApiDockerFileRemote(t *testing.T) { +func (s *DockerSuite) TestBuildApiDockerFileRemote(c *check.C) { server, err := fakeStorage(map[string]string{ "testD": `FROM busybox COPY * /tmp/ @@ -394,17 +363,17 @@ RUN find / -name ba* RUN find /tmp/`, }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() _, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") if err != nil { - t.Fatalf("Build failed: %s", err) + c.Fatalf("Build failed: %s", err) } buf, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Make sure Dockerfile exists. @@ -412,41 +381,37 @@ RUN find /tmp/`, out := string(buf) if !strings.Contains(out, "/tmp/Dockerfile") || strings.Contains(out, "baz") { - t.Fatalf("Incorrect output: %s", out) + c.Fatalf("Incorrect output: %s", out) } - - logDone("container REST API - check build with -f from remote") } -func TestBuildApiLowerDockerfile(t *testing.T) { +func (s *DockerSuite) TestBuildApiLowerDockerfile(c *check.C) { git, err := fakeGIT("repo", map[string]string{ "dockerfile": `FROM busybox RUN echo from dockerfile`, }, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer git.Close() _, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") if err != nil { buf, _ := readBody(body) - t.Fatalf("Build failed: %s\n%q", err, buf) + c.Fatalf("Build failed: %s\n%q", err, buf) } buf, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } out := string(buf) if !strings.Contains(out, "from dockerfile") { - t.Fatalf("Incorrect output: %s", out) + c.Fatalf("Incorrect output: %s", out) } - - logDone("container REST API - check build with lower dockerfile") } -func TestBuildApiBuildGitWithF(t *testing.T) { +func (s *DockerSuite) TestBuildApiBuildGitWithF(c *check.C) { git, err := fakeGIT("repo", map[string]string{ "baz": `FROM busybox RUN echo from baz`, @@ -454,7 +419,7 @@ RUN echo from baz`, RUN echo from Dockerfile`, }, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer git.Close() @@ -462,23 +427,21 @@ RUN echo from Dockerfile`, _, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") if err != nil { buf, _ := readBody(body) - t.Fatalf("Build failed: %s\n%q", err, buf) + c.Fatalf("Build failed: %s\n%q", err, buf) } buf, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } out := string(buf) if !strings.Contains(out, "from baz") { - t.Fatalf("Incorrect output: %s", out) + c.Fatalf("Incorrect output: %s", out) } - - logDone("container REST API - check build from git w/F") } -func TestBuildApiDoubleDockerfile(t *testing.T) { - testRequires(t, UnixCli) // dockerfile overwrites Dockerfile on Windows +func (s *DockerSuite) TestBuildApiDoubleDockerfile(c *check.C) { + testRequires(c, UnixCli) // dockerfile overwrites Dockerfile on Windows git, err := fakeGIT("repo", map[string]string{ "Dockerfile": `FROM busybox RUN echo from Dockerfile`, @@ -486,29 +449,27 @@ RUN echo from Dockerfile`, RUN echo from dockerfile`, }, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer git.Close() // Make sure it tries to 'dockerfile' query param value _, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") if err != nil { - t.Fatalf("Build failed: %s", err) + c.Fatalf("Build failed: %s", err) } buf, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } out := string(buf) if !strings.Contains(out, "from Dockerfile") { - t.Fatalf("Incorrect output: %s", out) + c.Fatalf("Incorrect output: %s", out) } - - logDone("container REST API - check build with two dockerfiles") } -func TestBuildApiDockerfileSymlink(t *testing.T) { +func (s *DockerSuite) TestBuildApiDockerfileSymlink(c *check.C) { // Test to make sure we stop people from trying to leave the // build context when specifying a symlink as the path to the dockerfile buffer := new(bytes.Buffer) @@ -520,20 +481,20 @@ func TestBuildApiDockerfileSymlink(t *testing.T) { Typeflag: tar.TypeSymlink, Linkname: "/etc/passwd", }); err != nil { - t.Fatalf("failed to write tar file header: %v", err) + c.Fatalf("failed to write tar file header: %v", err) } if err := tw.Close(); err != nil { - t.Fatalf("failed to close tar archive: %v", err) + c.Fatalf("failed to close tar archive: %v", err) } _, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") if err == nil { out, _ := readBody(body) - t.Fatalf("Build was supposed to fail: %s", out) + c.Fatalf("Build was supposed to fail: %s", out) } out, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } // The reason the error is "Cannot locate specified Dockerfile" is because @@ -541,96 +502,89 @@ func TestBuildApiDockerfileSymlink(t *testing.T) { // Dockerfile -> /etc/passwd becomes etc/passwd from the context which is // a nonexistent file. if !strings.Contains(string(out), "Cannot locate specified Dockerfile: Dockerfile") { - t.Fatalf("Didn't complain about leaving build context: %s", out) + c.Fatalf("Didn't complain about leaving build context: %s", out) } - - logDone("container REST API - check build w/bad Dockerfile symlink path") } // #9981 - Allow a docker created volume (ie, one in /var/lib/docker/volumes) to be used to overwrite (via passing in Binds on api start) an existing volume -func TestPostContainerBindNormalVolume(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestPostContainerBindNormalVolume(c *check.C) { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=one", "busybox")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } fooDir, err := inspectFieldMap("one", "Volumes", "/foo") if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "create", "-v", "/foo", "--name=two", "busybox")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}} if status, _, err := sockRequest("POST", "/containers/two/start", bindSpec); err != nil && status != http.StatusNoContent { - t.Fatal(err) + c.Fatal(err) } fooDir2, err := inspectFieldMap("two", "Volumes", "/foo") if err != nil { - t.Fatal(err) + c.Fatal(err) } if fooDir2 != fooDir { - t.Fatalf("expected volume path to be %s, got: %s", fooDir, fooDir2) + c.Fatalf("expected volume path to be %s, got: %s", fooDir, fooDir2) } - - logDone("container REST API - can use path from normal volume as bind-mount to overwrite another volume") } -func TestContainerApiPause(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerApiPause(c *check.C) { defer unpauseAllContainers() runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sleep", "30") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %s, %v", out, err) + c.Fatalf("failed to create a container: %s, %v", out, err) } ContainerID := strings.TrimSpace(out) if status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && status != http.StatusNoContent { - t.Fatalf("POST a container pause: sockRequest failed: %v", err) + c.Fatalf("POST a container pause: sockRequest failed: %v", err) } pausedContainers, err := getSliceOfPausedContainers() if err != nil { - t.Fatalf("error thrown while checking if containers were paused: %v", err) + c.Fatalf("error thrown while checking if containers were paused: %v", err) } if len(pausedContainers) != 1 || stringid.TruncateID(ContainerID) != pausedContainers[0] { - t.Fatalf("there should be one paused container and not %d", len(pausedContainers)) + c.Fatalf("there should be one paused container and not %d", len(pausedContainers)) } if status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && status != http.StatusNoContent { - t.Fatalf("POST a container pause: sockRequest failed: %v", err) + c.Fatalf("POST a container pause: sockRequest failed: %v", err) } pausedContainers, err = getSliceOfPausedContainers() if err != nil { - t.Fatalf("error thrown while checking if containers were paused: %v", err) + c.Fatalf("error thrown while checking if containers were paused: %v", err) } if pausedContainers != nil { - t.Fatalf("There should be no paused container.") + c.Fatalf("There should be no paused container.") } - - logDone("container REST API - check POST containers/pause and unpause") } -func TestContainerApiTop(t *testing.T) { - defer deleteAllContainers() - out, _ := dockerCmd(t, "run", "-d", "-i", "busybox", "/bin/sh", "-c", "cat") - id := strings.TrimSpace(out) +func (s *DockerSuite) TestContainerApiTop(c *check.C) { + out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "top").CombinedOutput() + if err != nil { + c.Fatal(err, out) + } + id := strings.TrimSpace(string(out)) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } type topResp struct { @@ -640,40 +594,41 @@ func TestContainerApiTop(t *testing.T) { var top topResp _, b, err := sockRequest("GET", "/containers/"+id+"/top?ps_args=aux", nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } if err := json.Unmarshal(b, &top); err != nil { - t.Fatal(err) + c.Fatal(err) } if len(top.Titles) != 11 { - t.Fatalf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles) + c.Fatalf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles) } if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" { - t.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles) + c.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles) } if len(top.Processes) != 2 { - t.Fatalf("expeted 2 processes, found %d: %v", len(top.Processes), top.Processes) + c.Fatalf("expected 2 processes, found %d: %v", len(top.Processes), top.Processes) } - if top.Processes[0][10] != "/bin/sh -c cat" { - t.Fatalf("expected `/bin/sh -c cat`, found: %s", top.Processes[0][10]) + if top.Processes[0][10] != "/bin/sh -c top" { + c.Fatalf("expected `/bin/sh -c top`, found: %s", top.Processes[0][10]) } - if top.Processes[1][10] != "cat" { - t.Fatalf("expected `cat`, found: %s", top.Processes[1][10]) + if top.Processes[1][10] != "top" { + c.Fatalf("expected `top`, found: %s", top.Processes[1][10]) } - - logDone("containers REST API - GET /containers//top") } -func TestContainerApiCommit(t *testing.T) { - out, _ := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /test") - id := strings.TrimSpace(out) +func (s *DockerSuite) TestContainerApiCommit(c *check.C) { + out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "touch /test").CombinedOutput() + if err != nil { + c.Fatal(err, out) + } + id := strings.TrimSpace(string(out)) name := "testcommit" _, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+id, nil) if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { - t.Fatal(err) + c.Fatal(err) } type resp struct { @@ -681,22 +636,25 @@ func TestContainerApiCommit(t *testing.T) { } var img resp if err := json.Unmarshal(b, &img); err != nil { - t.Fatal(err) + c.Fatal(err) } defer deleteImages(img.Id) - out, err = inspectField(img.Id, "Config.Cmd") - if out != "[/bin/sh -c touch /test]" { - t.Fatalf("got wrong Cmd from commit: %q", out) + cmd, err := inspectField(img.Id, "Config.Cmd") + if err != nil { + c.Fatal(err) + } + if cmd != "[/bin/sh -c touch /test]" { + c.Fatalf("got wrong Cmd from commit: %q", cmd) } // sanity check, make sure the image is what we think it is - dockerCmd(t, "run", img.Id, "ls", "/test") - - logDone("containers REST API - POST /commit") + out, err = exec.Command(dockerBinary, "run", img.Id, "ls", "/test").CombinedOutput() + if err != nil { + c.Fatal(out, err) + } } -func TestContainerApiCreate(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerApiCreate(c *check.C) { config := map[string]interface{}{ "Image": "busybox", "Cmd": []string{"/bin/sh", "-c", "touch /test && ls /test"}, @@ -704,26 +662,26 @@ func TestContainerApiCreate(t *testing.T) { _, b, err := sockRequest("POST", "/containers/create", config) if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { - t.Fatal(err) + c.Fatal(err) } type createResp struct { Id string } var container createResp if err := json.Unmarshal(b, &container); err != nil { - t.Fatal(err) + c.Fatal(err) } - out, _ := dockerCmd(t, "start", "-a", container.Id) - if strings.TrimSpace(out) != "/test" { - t.Fatalf("expected output `/test`, got %q", out) + out, err := exec.Command(dockerBinary, "start", "-a", container.Id).CombinedOutput() + if err != nil { + c.Fatal(out, err) + } + if strings.TrimSpace(string(out)) != "/test" { + c.Fatalf("expected output `/test`, got %q", out) } - - logDone("containers REST API - POST /containers/create") } -func TestContainerApiVerifyHeader(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerApiVerifyHeader(c *check.C) { config := map[string]interface{}{ "Image": "busybox", } @@ -731,7 +689,7 @@ func TestContainerApiVerifyHeader(t *testing.T) { create := func(ct string) (int, io.ReadCloser, error) { jsonData := bytes.NewBuffer(nil) if err := json.NewEncoder(jsonData).Encode(config); err != nil { - t.Fatal(err) + c.Fatal(err) } return sockRequestRaw("POST", "/containers/create", jsonData, ct) } @@ -740,14 +698,14 @@ func TestContainerApiVerifyHeader(t *testing.T) { _, body, err := create("") if err == nil { b, _ := readBody(body) - t.Fatalf("expected error when content-type is not set: %q", string(b)) + c.Fatalf("expected error when content-type is not set: %q", string(b)) } body.Close() // Try with wrong content-type _, body, err = create("application/xml") if err == nil { b, _ := readBody(body) - t.Fatalf("expected error when content-type is not set: %q", string(b)) + c.Fatalf("expected error when content-type is not set: %q", string(b)) } body.Close() @@ -755,16 +713,14 @@ func TestContainerApiVerifyHeader(t *testing.T) { _, body, err = create("application/json") if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { b, _ := readBody(body) - t.Fatalf("%v - %q", err, string(b)) + c.Fatalf("%v - %q", err, string(b)) } body.Close() - - logDone("containers REST API - verify create header") } // Issue 7941 - test to make sure a "null" in JSON is just ignored. // W/o this fix a null in JSON would be parsed into a string var as "null" -func TestContainerApiPostCreateNull(t *testing.T) { +func (s *DockerSuite) TestContainerApiPostCreateNull(c *check.C) { config := `{ "Hostname":"", "Domainname":"", @@ -792,33 +748,31 @@ func TestContainerApiPostCreateNull(t *testing.T) { _, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { b, _ := readBody(body) - t.Fatal(err, string(b)) + c.Fatal(err, string(b)) } b, err := readBody(body) if err != nil { - t.Fatal(err) + c.Fatal(err) } type createResp struct { Id string } var container createResp if err := json.Unmarshal(b, &container); err != nil { - t.Fatal(err) + c.Fatal(err) } out, err := inspectField(container.Id, "HostConfig.CpusetCpus") if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if out != "" { - t.Fatalf("expected empty string, got %q", out) + c.Fatalf("expected empty string, got %q", out) } - - logDone("containers REST API - Create Null") } -func TestCreateWithTooLowMemoryLimit(t *testing.T) { +func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { defer deleteAllContainers() config := `{ "Image": "busybox", @@ -831,22 +785,21 @@ func TestCreateWithTooLowMemoryLimit(t *testing.T) { _, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") b, err2 := readBody(body) if err2 != nil { - t.Fatal(err2) + c.Fatal(err2) } if err == nil || !strings.Contains(string(b), "Minimum memory limit allowed is 4MB") { - t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") + c.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") } - logDone("container REST API - create can't set too low memory limit") } -func TestStartWithTooLowMemoryLimit(t *testing.T) { +func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { defer deleteAllContainers() out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "busybox")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } containerID := strings.TrimSpace(out) @@ -859,12 +812,10 @@ func TestStartWithTooLowMemoryLimit(t *testing.T) { _, body, err := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json") b, err2 := readBody(body) if err2 != nil { - t.Fatal(err2) + c.Fatal(err2) } if err == nil || !strings.Contains(string(b), "Minimum memory limit allowed is 4MB") { - t.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") + c.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") } - - logDone("container REST API - start can't set too low memory limit") } diff --git a/integration-cli/docker_api_exec_resize_test.go b/integration-cli/docker_api_exec_resize_test.go index d4290408e..09a13bad2 100644 --- a/integration-cli/docker_api_exec_resize_test.go +++ b/integration-cli/docker_api_exec_resize_test.go @@ -4,26 +4,24 @@ import ( "net/http" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestExecResizeApiHeightWidthNoInt(t *testing.T) { +func (s *DockerSuite) TestExecResizeApiHeightWidthNoInt(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } - defer deleteAllContainers() cleanedContainerID := strings.TrimSpace(out) endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar" status, _, err := sockRequest("POST", endpoint, nil) if err == nil { - t.Fatal("Expected exec resize Request to fail") + c.Fatal("Expected exec resize Request to fail") } if status != http.StatusInternalServerError { - t.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) + c.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) } - - logDone("container exec resize - height, width no int fail") } diff --git a/integration-cli/docker_api_exec_test.go b/integration-cli/docker_api_exec_test.go index f898250a1..4299f00ec 100644 --- a/integration-cli/docker_api_exec_test.go +++ b/integration-cli/docker_api_exec_test.go @@ -6,22 +6,20 @@ import ( "bytes" "fmt" "os/exec" - "testing" + + "github.com/go-check/check" ) // Regression test for #9414 -func TestExecApiCreateNoCmd(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecApiCreateNoCmd(c *check.C) { name := "exec_test" runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } _, body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}) if err == nil || !bytes.Contains(body, []byte("No exec command specified")) { - t.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err) + c.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err) } - - logDone("exec create API - returns error when missing Cmd") } diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 1774e6b74..e22f67d2b 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -3,50 +3,50 @@ package main import ( "encoding/json" "net/url" + "os/exec" "strings" - "testing" "github.com/docker/docker/api/types" + "github.com/go-check/check" ) -func TestLegacyImages(t *testing.T) { +func (s *DockerSuite) TestLegacyImages(c *check.C) { _, body, err := sockRequest("GET", "/v1.6/images/json", nil) if err != nil { - t.Fatalf("Error on GET: %s", err) + c.Fatalf("Error on GET: %s", err) } images := []types.LegacyImage{} if err = json.Unmarshal(body, &images); err != nil { - t.Fatalf("Error on unmarshal: %s", err) + c.Fatalf("Error on unmarshal: %s", err) } if len(images) == 0 || images[0].Tag == "" || images[0].Repository == "" { - t.Fatalf("Bad data: %q", images) + c.Fatalf("Bad data: %q", images) } - - logDone("images - checking legacy json") } -func TestApiImagesFilter(t *testing.T) { +func (s *DockerSuite) TestApiImagesFilter(c *check.C) { name := "utest:tag1" name2 := "utest/docker:tag2" name3 := "utest:5000/docker:tag3" defer deleteImages(name, name2, name3) - dockerCmd(t, "tag", "busybox", name) - dockerCmd(t, "tag", "busybox", name2) - dockerCmd(t, "tag", "busybox", name3) - + for _, n := range []string{name, name2, name3} { + if out, err := exec.Command(dockerBinary, "tag", "busybox", n).CombinedOutput(); err != nil { + c.Fatal(err, out) + } + } type image struct{ RepoTags []string } getImages := func(filter string) []image { v := url.Values{} v.Set("filter", filter) _, b, err := sockRequest("GET", "/images/json?"+v.Encode(), nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } var images []image if err := json.Unmarshal(b, &images); err != nil { - t.Fatal(err) + c.Fatal(err) } return images @@ -54,48 +54,49 @@ func TestApiImagesFilter(t *testing.T) { errMsg := "incorrect number of matches returned" if images := getImages("utest*/*"); len(images[0].RepoTags) != 2 { - t.Fatal(errMsg) + c.Fatal(errMsg) } if images := getImages("utest"); len(images[0].RepoTags) != 1 { - t.Fatal(errMsg) + c.Fatal(errMsg) } if images := getImages("utest*"); len(images[0].RepoTags) != 1 { - t.Fatal(errMsg) + c.Fatal(errMsg) } if images := getImages("*5000*/*"); len(images[0].RepoTags) != 1 { - t.Fatal(errMsg) + c.Fatal(errMsg) } - - logDone("images - filter param is applied") } -func TestApiImagesSaveAndLoad(t *testing.T) { - testRequires(t, Network) +func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { + testRequires(c, Network) out, err := buildImage("saveandload", "FROM hello-world\nENV FOO bar", false) if err != nil { - t.Fatal(err) + c.Fatal(err) } id := strings.TrimSpace(out) defer deleteImages("saveandload") _, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer body.Close() - dockerCmd(t, "rmi", id) + if out, err := exec.Command(dockerBinary, "rmi", id).CombinedOutput(); err != nil { + c.Fatal(err, out) + } _, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer loadBody.Close() - out, _ = dockerCmd(t, "inspect", "--format='{{ .Id }}'", id) - if strings.TrimSpace(out) != id { - t.Fatal("load did not work properly") + inspectOut, err := exec.Command(dockerBinary, "inspect", "--format='{{ .Id }}'", id).CombinedOutput() + if err != nil { + c.Fatal(err, inspectOut) + } + if strings.TrimSpace(string(inspectOut)) != id { + c.Fatal("load did not work properly") } - - logDone("images API - save and load") } diff --git a/integration-cli/docker_api_info_test.go b/integration-cli/docker_api_info_test.go index a934ed6cc..5f7c9515c 100644 --- a/integration-cli/docker_api_info_test.go +++ b/integration-cli/docker_api_info_test.go @@ -33,6 +33,4 @@ func TestInfoApi(t *testing.T) { t.Errorf("couldn't find string %v in output", linePrefix) } } - - logDone("container REST API - check GET /info") } diff --git a/integration-cli/docker_api_inspect_test.go b/integration-cli/docker_api_inspect_test.go index 43144f916..982962261 100644 --- a/integration-cli/docker_api_inspect_test.go +++ b/integration-cli/docker_api_inspect_test.go @@ -4,16 +4,15 @@ import ( "encoding/json" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestInspectApiContainerResponse(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestInspectApiContainerResponse(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %s, %v", out, err) + c.Fatalf("failed to create a container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -29,12 +28,12 @@ func TestInspectApiContainerResponse(t *testing.T) { } _, body, err := sockRequest("GET", endpoint, nil) if err != nil { - t.Fatalf("sockRequest failed for %s version: %v", testVersion, err) + c.Fatalf("sockRequest failed for %s version: %v", testVersion, err) } var inspectJSON map[string]interface{} if err = json.Unmarshal(body, &inspectJSON); err != nil { - t.Fatalf("unable to unmarshal body for %s version: %v", testVersion, err) + c.Fatalf("unable to unmarshal body for %s version: %v", testVersion, err) } keys := []string{"State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings", "ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "ExecDriver", "MountLabel", "ProcessLabel", "Volumes", "VolumesRW"} @@ -47,14 +46,12 @@ func TestInspectApiContainerResponse(t *testing.T) { for _, key := range keys { if _, ok := inspectJSON[key]; !ok { - t.Fatalf("%s does not exist in response for %s version", key, testVersion) + c.Fatalf("%s does not exist in response for %s version", key, testVersion) } } //Issue #6830: type not properly converted to JSON/back if _, ok := inspectJSON["Path"].(bool); ok { - t.Fatalf("Path of `true` should not be converted to boolean `true` via JSON marshalling") + c.Fatalf("Path of `true` should not be converted to boolean `true` via JSON marshalling") } } - - logDone("container json - check keys in container json response") } diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go index 27eb31c33..bbf9d17cb 100644 --- a/integration-cli/docker_api_logs_test.go +++ b/integration-cli/docker_api_logs_test.go @@ -5,49 +5,44 @@ import ( "fmt" "net/http" "os/exec" - "testing" + + "github.com/go-check/check" ) -func TestLogsApiWithStdout(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLogsApiWithStdout(c *check.C) { name := "logs_test" runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "bin/sh", "-c", "sleep 10 && echo "+name) if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", name), nil) if err != nil || statusCode != http.StatusOK { - t.Fatalf("Expected %d from logs request, got %d", http.StatusOK, statusCode) + c.Fatalf("Expected %d from logs request, got %d", http.StatusOK, statusCode) } if !bytes.Contains(body, []byte(name)) { - t.Fatalf("Expected %s, got %s", name, string(body[:])) + c.Fatalf("Expected %s, got %s", name, string(body[:])) } - - logDone("logs API - with stdout ok") } -func TestLogsApiNoStdoutNorStderr(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLogsApiNoStdoutNorStderr(c *check.C) { name := "logs_test" runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "/bin/sh") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs", name), nil) if err == nil || statusCode != http.StatusBadRequest { - t.Fatalf("Expected %d from logs request, got %d", http.StatusBadRequest, statusCode) + c.Fatalf("Expected %d from logs request, got %d", http.StatusBadRequest, statusCode) } expected := "Bad parameters: you must choose at least one stream" if !bytes.Contains(body, []byte(expected)) { - t.Fatalf("Expected %s, got %s", expected, string(body[:])) + c.Fatalf("Expected %s, got %s", expected, string(body[:])) } - - logDone("logs API - returns error when no stdout nor stderr specified") } diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index 8c7b87eb4..6f5019b6d 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -4,72 +4,64 @@ import ( "net/http" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestResizeApiResponse(t *testing.T) { +func (s *DockerSuite) TestResizeApiResponse(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } - defer deleteAllContainers() cleanedContainerID := strings.TrimSpace(out) endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" _, _, err = sockRequest("POST", endpoint, nil) if err != nil { - t.Fatalf("resize Request failed %v", err) + c.Fatalf("resize Request failed %v", err) } - - logDone("container resize - when started") } -func TestResizeApiHeightWidthNoInt(t *testing.T) { +func (s *DockerSuite) TestResizeApiHeightWidthNoInt(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } - defer deleteAllContainers() cleanedContainerID := strings.TrimSpace(out) endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar" status, _, err := sockRequest("POST", endpoint, nil) if err == nil { - t.Fatal("Expected resize Request to fail") + c.Fatal("Expected resize Request to fail") } if status != http.StatusInternalServerError { - t.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) + c.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) } - - logDone("container resize - height, width no int fail") } -func TestResizeApiResponseWhenContainerNotStarted(t *testing.T) { +func (s *DockerSuite) TestResizeApiResponseWhenContainerNotStarted(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } - defer deleteAllContainers() cleanedContainerID := strings.TrimSpace(out) // make sure the exited container is not running runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" _, body, err := sockRequest("POST", endpoint, nil) if err == nil { - t.Fatalf("resize should fail when container is not started") + c.Fatalf("resize should fail when container is not started") } if !strings.Contains(string(body), "Cannot resize container") && !strings.Contains(string(body), cleanedContainerID) { - t.Fatalf("resize should fail with message 'Cannot resize container' but instead received %s", string(body)) + c.Fatalf("resize should fail with message 'Cannot resize container' but instead received %s", string(body)) } - - logDone("container resize - when not started should not resize") } diff --git a/integration-cli/docker_api_version_test.go b/integration-cli/docker_api_version_test.go index 2846fb1d3..d1ea6b9f8 100644 --- a/integration-cli/docker_api_version_test.go +++ b/integration-cli/docker_api_version_test.go @@ -2,23 +2,23 @@ package main import ( "encoding/json" - "testing" "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" + "github.com/go-check/check" ) -func TestGetVersion(t *testing.T) { +func (s *DockerSuite) TestGetVersion(c *check.C) { _, body, err := sockRequest("GET", "/version", nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } var v types.Version if err := json.Unmarshal(body, &v); err != nil { - t.Fatal(err) + c.Fatal(err) } if v.Version != dockerversion.VERSION { - t.Fatal("Version mismatch") + c.Fatal("Version mismatch") } } diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go index 08b109f88..11ae1584a 100644 --- a/integration-cli/docker_cli_attach_test.go +++ b/integration-cli/docker_cli_attach_test.go @@ -6,14 +6,14 @@ import ( "os/exec" "strings" "sync" - "testing" "time" + + "github.com/go-check/check" ) const attachWait = 5 * time.Second -func TestAttachMultipleAndRestart(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestAttachMultipleAndRestart(c *check.C) { endGroup := &sync.WaitGroup{} startGroup := &sync.WaitGroup{} @@ -21,7 +21,7 @@ func TestAttachMultipleAndRestart(t *testing.T) { startGroup.Add(3) if err := waitForContainer("attacher", "-d", "busybox", "/bin/sh", "-c", "while true; do sleep 1; echo hello; done"); err != nil { - t.Fatal(err) + c.Fatal(err) } startDone := make(chan struct{}) @@ -39,32 +39,32 @@ func TestAttachMultipleAndRestart(t *testing.T) { for i := 0; i < 3; i++ { go func() { - c := exec.Command(dockerBinary, "attach", "attacher") + cmd := exec.Command(dockerBinary, "attach", "attacher") defer func() { - c.Wait() + cmd.Wait() endGroup.Done() }() - out, err := c.StdoutPipe() + out, err := cmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } - if err := c.Start(); err != nil { - t.Fatal(err) + if err := cmd.Start(); err != nil { + c.Fatal(err) } buf := make([]byte, 1024) if _, err := out.Read(buf); err != nil && err != io.EOF { - t.Fatal(err) + c.Fatal(err) } startGroup.Done() if !strings.Contains(string(buf), "hello") { - t.Fatalf("unexpected output %s expected hello\n", string(buf)) + c.Fatalf("unexpected output %s expected hello\n", string(buf)) } }() } @@ -72,41 +72,39 @@ func TestAttachMultipleAndRestart(t *testing.T) { select { case <-startDone: case <-time.After(attachWait): - t.Fatalf("Attaches did not initialize properly") + c.Fatalf("Attaches did not initialize properly") } cmd := exec.Command(dockerBinary, "kill", "attacher") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } select { case <-endDone: case <-time.After(attachWait): - t.Fatalf("Attaches did not finish properly") + c.Fatalf("Attaches did not finish properly") } - logDone("attach - multiple attach") } -func TestAttachTtyWithoutStdin(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestAttachTtyWithoutStdin(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "-ti", "busybox") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to start container: %v (%v)", out, err) + c.Fatalf("failed to start container: %v (%v)", out, err) } id := strings.TrimSpace(out) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } defer func() { cmd := exec.Command(dockerBinary, "kill", id) if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("failed to kill container: %v (%v)", out, err) + c.Fatalf("failed to kill container: %v (%v)", out, err) } }() @@ -116,70 +114,67 @@ func TestAttachTtyWithoutStdin(t *testing.T) { cmd := exec.Command(dockerBinary, "attach", id) if _, err := cmd.StdinPipe(); err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "cannot enable tty mode" if out, _, err := runCommandWithOutput(cmd); err == nil { - t.Fatal("attach should have failed") + c.Fatal("attach should have failed") } else if !strings.Contains(out, expected) { - t.Fatalf("attach failed with error %q: expected %q", out, expected) + c.Fatalf("attach failed with error %q: expected %q", out, expected) } }() select { case <-done: case <-time.After(attachWait): - t.Fatal("attach is running but should have failed") + c.Fatal("attach is running but should have failed") } - logDone("attach - forbid piped stdin to tty enabled container") } -func TestAttachDisconnect(t *testing.T) { - defer deleteAllContainers() - out, _ := dockerCmd(t, "run", "-di", "busybox", "/bin/cat") +func (s *DockerSuite) TestAttachDisconnect(c *check.C) { + out, _ := dockerCmd(c, "run", "-di", "busybox", "/bin/cat") id := strings.TrimSpace(out) cmd := exec.Command(dockerBinary, "attach", id) stdin, err := cmd.StdinPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer stdin.Close() stdout, err := cmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer stdout.Close() if err := cmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer cmd.Process.Kill() if _, err := stdin.Write([]byte("hello\n")); err != nil { - t.Fatal(err) + c.Fatal(err) } out, err = bufio.NewReader(stdout).ReadString('\n') if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - t.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("exepected 'hello', got %q", out) } if err := stdin.Close(); err != nil { - t.Fatal(err) + c.Fatal(err) } // Expect container to still be running after stdin is closed running, err := inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if running != "true" { - t.Fatal("exepected container to still be running") + c.Fatal("exepected container to still be running") } - logDone("attach - disconnect") } diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index e17256cf7..5567c92a0 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -6,26 +6,25 @@ import ( "bufio" "os/exec" "strings" - "testing" "time" "github.com/docker/docker/pkg/stringid" + "github.com/go-check/check" "github.com/kr/pty" ) // #9860 -func TestAttachClosedOnContainerStop(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestAttachClosedOnContainerStop(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-dti", "busybox", "sleep", "2") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to start container: %v (%v)", out, err) + c.Fatalf("failed to start container: %v (%v)", out, err) } id := strings.TrimSpace(out) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } done := make(chan struct{}) @@ -35,7 +34,7 @@ func TestAttachClosedOnContainerStop(t *testing.T) { _, tty, err := pty.Open() if err != nil { - t.Fatalf("could not open pty: %v", err) + c.Fatalf("could not open pty: %v", err) } attachCmd := exec.Command(dockerBinary, "attach", id) attachCmd.Stdin = tty @@ -43,31 +42,29 @@ func TestAttachClosedOnContainerStop(t *testing.T) { attachCmd.Stderr = tty if err := attachCmd.Run(); err != nil { - t.Fatalf("attach returned error %s", err) + c.Fatalf("attach returned error %s", err) } }() waitCmd := exec.Command(dockerBinary, "wait", id) if out, _, err = runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", out, err) + c.Fatalf("error thrown while waiting for container: %s, %v", out, err) } select { case <-done: case <-time.After(attachWait): - t.Fatal("timed out without attach returning") + c.Fatal("timed out without attach returning") } - logDone("attach - return after container finished") } -func TestAttachAfterDetach(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestAttachAfterDetach(c *check.C) { name := "detachtest" cpty, tty, err := pty.Open() if err != nil { - t.Fatalf("Could not open pty: %v", err) + c.Fatalf("Could not open pty: %v", err) } cmd := exec.Command(dockerBinary, "run", "-ti", "--name", name, "busybox") cmd.Stdin = tty @@ -77,14 +74,14 @@ func TestAttachAfterDetach(t *testing.T) { detached := make(chan struct{}) go func() { if err := cmd.Run(); err != nil { - t.Fatalf("attach returned error %s", err) + c.Fatalf("attach returned error %s", err) } close(detached) }() time.Sleep(500 * time.Millisecond) if err := waitRun(name); err != nil { - t.Fatal(err) + c.Fatal(err) } cpty.Write([]byte{16}) time.Sleep(100 * time.Millisecond) @@ -94,7 +91,7 @@ func TestAttachAfterDetach(t *testing.T) { cpty, tty, err = pty.Open() if err != nil { - t.Fatalf("Could not open pty: %v", err) + c.Fatalf("Could not open pty: %v", err) } cmd = exec.Command(dockerBinary, "attach", name) @@ -103,7 +100,7 @@ func TestAttachAfterDetach(t *testing.T) { cmd.Stderr = tty if err := cmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } bytes := make([]byte, 10) @@ -123,34 +120,33 @@ func TestAttachAfterDetach(t *testing.T) { select { case err := <-readErr: if err != nil { - t.Fatal(err) + c.Fatal(err) } case <-time.After(2 * time.Second): - t.Fatal("timeout waiting for attach read") + c.Fatal("timeout waiting for attach read") } if err := cmd.Wait(); err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(string(bytes[:nBytes]), "/ #") { - t.Fatalf("failed to get a new prompt. got %s", string(bytes[:nBytes])) + c.Fatalf("failed to get a new prompt. got %s", string(bytes[:nBytes])) } - logDone("attach - reconnect after detaching") } // TestAttachDetach checks that attach in tty mode can be detached using the long container ID -func TestAttachDetach(t *testing.T) { - out, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") +func (s *DockerSuite) TestAttachDetach(c *check.C) { + out, _ := dockerCmd(c, "run", "-itd", "busybox", "cat") id := strings.TrimSpace(out) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } cpty, tty, err := pty.Open() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer cpty.Close() @@ -158,34 +154,34 @@ func TestAttachDetach(t *testing.T) { cmd.Stdin = tty stdout, err := cmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer stdout.Close() if err := cmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := waitRun(id); err != nil { - t.Fatalf("error waiting for container to start: %v", err) + c.Fatalf("error waiting for container to start: %v", err) } if _, err := cpty.Write([]byte("hello\n")); err != nil { - t.Fatal(err) + c.Fatal(err) } out, err = bufio.NewReader(stdout).ReadString('\n') if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - t.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("exepected 'hello', got %q", out) } // escape sequence if _, err := cpty.Write([]byte{16}); err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(100 * time.Millisecond) if _, err := cpty.Write([]byte{17}); err != nil { - t.Fatal(err) + c.Fatal(err) } ch := make(chan struct{}) @@ -196,36 +192,35 @@ func TestAttachDetach(t *testing.T) { running, err := inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if running != "true" { - t.Fatal("exepected container to still be running") + c.Fatal("exepected container to still be running") } go func() { - dockerCmd(t, "kill", id) + dockerCmd(c, "kill", id) }() select { case <-ch: case <-time.After(10 * time.Millisecond): - t.Fatal("timed out waiting for container to exit") + c.Fatal("timed out waiting for container to exit") } - logDone("attach - detach") } // TestAttachDetachTruncatedID checks that attach in tty mode can be detached -func TestAttachDetachTruncatedID(t *testing.T) { - out, _ := dockerCmd(t, "run", "-itd", "busybox", "cat") +func (s *DockerSuite) TestAttachDetachTruncatedID(c *check.C) { + out, _ := dockerCmd(c, "run", "-itd", "busybox", "cat") id := stringid.TruncateID(strings.TrimSpace(out)) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } cpty, tty, err := pty.Open() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer cpty.Close() @@ -233,31 +228,31 @@ func TestAttachDetachTruncatedID(t *testing.T) { cmd.Stdin = tty stdout, err := cmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer stdout.Close() if err := cmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := cpty.Write([]byte("hello\n")); err != nil { - t.Fatal(err) + c.Fatal(err) } out, err = bufio.NewReader(stdout).ReadString('\n') if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - t.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("exepected 'hello', got %q", out) } // escape sequence if _, err := cpty.Write([]byte{16}); err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(100 * time.Millisecond) if _, err := cpty.Write([]byte{17}); err != nil { - t.Fatal(err) + c.Fatal(err) } ch := make(chan struct{}) @@ -268,21 +263,20 @@ func TestAttachDetachTruncatedID(t *testing.T) { running, err := inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if running != "true" { - t.Fatal("exepected container to still be running") + c.Fatal("exepected container to still be running") } go func() { - dockerCmd(t, "kill", id) + dockerCmd(c, "kill", id) }() select { case <-ch: case <-time.After(10 * time.Millisecond): - t.Fatal("timed out waiting for container to exit") + c.Fatal("timed out waiting for container to exit") } - logDone("attach - detach truncated ID") } diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 44370d4b9..a4ccd18ea 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -16,16 +16,16 @@ import ( "strconv" "strings" "sync" - "testing" "text/template" "time" "github.com/docker/docker/builder/command" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/stringutils" + "github.com/go-check/check" ) -func TestBuildJSONEmptyRun(t *testing.T) { +func (s *DockerSuite) TestBuildJSONEmptyRun(c *check.C) { name := "testbuildjsonemptyrun" defer deleteImages(name) @@ -38,13 +38,12 @@ func TestBuildJSONEmptyRun(t *testing.T) { true) if err != nil { - t.Fatal("error when dealing with a RUN statement with empty JSON array") + c.Fatal("error when dealing with a RUN statement with empty JSON array") } - logDone("build - RUN with an empty array should not panic") } -func TestBuildEmptyWhitespace(t *testing.T) { +func (s *DockerSuite) TestBuildEmptyWhitespace(c *check.C) { name := "testbuildemptywhitespace" defer deleteImages(name) @@ -59,13 +58,12 @@ func TestBuildEmptyWhitespace(t *testing.T) { true) if err == nil { - t.Fatal("no error when dealing with a COPY statement with no content on the same line") + c.Fatal("no error when dealing with a COPY statement with no content on the same line") } - logDone("build - statements with whitespace and no content should generate a parse error") } -func TestBuildShCmdJSONEntrypoint(t *testing.T) { +func (s *DockerSuite) TestBuildShCmdJSONEntrypoint(c *check.C) { name := "testbuildshcmdjsonentrypoint" defer deleteImages(name) @@ -79,7 +77,7 @@ func TestBuildShCmdJSONEntrypoint(t *testing.T) { true) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput( @@ -90,17 +88,16 @@ func TestBuildShCmdJSONEntrypoint(t *testing.T) { name)) if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "/bin/sh -c echo test" { - t.Fatal("CMD did not contain /bin/sh -c") + c.Fatal("CMD did not contain /bin/sh -c") } - logDone("build - CMD should always contain /bin/sh -c when specified without JSON") } -func TestBuildEnvironmentReplacementUser(t *testing.T) { +func (s *DockerSuite) TestBuildEnvironmentReplacementUser(c *check.C) { name := "testbuildenvironmentreplacement" defer deleteImages(name) @@ -110,22 +107,21 @@ func TestBuildEnvironmentReplacementUser(t *testing.T) { USER ${user} `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.User") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != `"foo"` { - t.Fatal("User foo from environment not in Config.User on image") + c.Fatal("User foo from environment not in Config.User on image") } - logDone("build - user environment replacement") } -func TestBuildEnvironmentReplacementVolume(t *testing.T) { +func (s *DockerSuite) TestBuildEnvironmentReplacementVolume(c *check.C) { name := "testbuildenvironmentreplacement" defer deleteImages(name) @@ -135,28 +131,27 @@ func TestBuildEnvironmentReplacementVolume(t *testing.T) { VOLUME ${volume} `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Volumes") if err != nil { - t.Fatal(err) + c.Fatal(err) } var volumes map[string]interface{} if err := json.Unmarshal([]byte(res), &volumes); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, ok := volumes["/quux"]; !ok { - t.Fatal("Volume /quux from environment not in Config.Volumes on image") + c.Fatal("Volume /quux from environment not in Config.Volumes on image") } - logDone("build - volume environment replacement") } -func TestBuildEnvironmentReplacementExpose(t *testing.T) { +func (s *DockerSuite) TestBuildEnvironmentReplacementExpose(c *check.C) { name := "testbuildenvironmentreplacement" defer deleteImages(name) @@ -166,28 +161,27 @@ func TestBuildEnvironmentReplacementExpose(t *testing.T) { EXPOSE ${port} `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } var exposedPorts map[string]interface{} if err := json.Unmarshal([]byte(res), &exposedPorts); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, ok := exposedPorts["80/tcp"]; !ok { - t.Fatal("Exposed port 80 from environment not in Config.ExposedPorts on image") + c.Fatal("Exposed port 80 from environment not in Config.ExposedPorts on image") } - logDone("build - expose environment replacement") } -func TestBuildEnvironmentReplacementWorkdir(t *testing.T) { +func (s *DockerSuite) TestBuildEnvironmentReplacementWorkdir(c *check.C) { name := "testbuildenvironmentreplacement" defer deleteImages(name) @@ -199,13 +193,12 @@ func TestBuildEnvironmentReplacementWorkdir(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - workdir environment replacement") } -func TestBuildEnvironmentReplacementAddCopy(t *testing.T) { +func (s *DockerSuite) TestBuildEnvironmentReplacementAddCopy(c *check.C) { name := "testbuildenvironmentreplacement" defer deleteImages(name) @@ -230,18 +223,17 @@ func TestBuildEnvironmentReplacementAddCopy(t *testing.T) { }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add/copy environment replacement") } -func TestBuildEnvironmentReplacementEnv(t *testing.T) { +func (s *DockerSuite) TestBuildEnvironmentReplacementEnv(c *check.C) { name := "testbuildenvironmentreplacement" defer deleteImages(name) @@ -263,18 +255,18 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Env") if err != nil { - t.Fatal(err) + c.Fatal(err) } envResult := []string{} if err = unmarshalJSON([]byte(res), &envResult); err != nil { - t.Fatal(err) + c.Fatal(err) } found := false @@ -285,33 +277,32 @@ func TestBuildEnvironmentReplacementEnv(t *testing.T) { if parts[0] == "bar" { found = true if parts[1] != "zzz" { - t.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", parts[1]) + c.Fatalf("Could not find replaced var for env `bar`: got %q instead of `zzz`", parts[1]) } } else if strings.HasPrefix(parts[0], "env") { envCount++ if parts[1] != "zzz" { - t.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) + c.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) } } else if strings.HasPrefix(parts[0], "env") { envCount++ if parts[1] != "foo" { - t.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) + c.Fatalf("%s should be 'foo' but instead its %q", parts[0], parts[1]) } } } if !found { - t.Fatal("Never found the `bar` env variable") + c.Fatal("Never found the `bar` env variable") } if envCount != 4 { - t.Fatalf("Didn't find all env vars - only saw %d\n%s", envCount, envResult) + c.Fatalf("Didn't find all env vars - only saw %d\n%s", envCount, envResult) } - logDone("build - env environment replacement") } -func TestBuildHandleEscapes(t *testing.T) { +func (s *DockerSuite) TestBuildHandleEscapes(c *check.C) { name := "testbuildhandleescapes" defer deleteImages(name) @@ -324,22 +315,22 @@ func TestBuildHandleEscapes(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } var result map[string]map[string]struct{} res, err := inspectFieldJSON(name, "Config.Volumes") if err != nil { - t.Fatal(err) + c.Fatal(err) } if err = unmarshalJSON([]byte(res), &result); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, ok := result["bar"]; !ok { - t.Fatal("Could not find volume bar set from env foo in volumes table") + c.Fatal("Could not find volume bar set from env foo in volumes table") } deleteImages(name) @@ -352,20 +343,20 @@ func TestBuildHandleEscapes(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err = inspectFieldJSON(name, "Config.Volumes") if err != nil { - t.Fatal(err) + c.Fatal(err) } if err = unmarshalJSON([]byte(res), &result); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, ok := result["${FOO}"]; !ok { - t.Fatal("Could not find volume ${FOO} set from env foo in volumes table") + c.Fatal("Could not find volume ${FOO} set from env foo in volumes table") } deleteImages(name) @@ -382,26 +373,25 @@ func TestBuildHandleEscapes(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err = inspectFieldJSON(name, "Config.Volumes") if err != nil { - t.Fatal(err) + c.Fatal(err) } if err = unmarshalJSON([]byte(res), &result); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, ok := result[`\\\${FOO}`]; !ok { - t.Fatal(`Could not find volume \\\${FOO} set from env foo in volumes table`, result) + c.Fatal(`Could not find volume \\\${FOO} set from env foo in volumes table`, result) } - logDone("build - handle escapes") } -func TestBuildOnBuildLowercase(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildLowercase(c *check.C) { name := "testbuildonbuildlowercase" name2 := "testbuildonbuildlowercase2" @@ -414,7 +404,7 @@ func TestBuildOnBuildLowercase(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } _, out, err := buildImageWithOut(name2, fmt.Sprintf(` @@ -422,24 +412,22 @@ func TestBuildOnBuildLowercase(t *testing.T) { `, name), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "quux") { - t.Fatalf("Did not receive the expected echo text, got %s", out) + c.Fatalf("Did not receive the expected echo text, got %s", out) } if strings.Contains(out, "ONBUILD ONBUILD") { - t.Fatalf("Got an ONBUILD ONBUILD error with no error: got %s", out) + c.Fatalf("Got an ONBUILD ONBUILD error with no error: got %s", out) } - logDone("build - handle case-insensitive onbuild statement") } -func TestBuildEnvEscapes(t *testing.T) { +func (s *DockerSuite) TestBuildEnvEscapes(c *check.C) { name := "testbuildenvescapes" defer deleteImages(name) - defer deleteAllContainers() _, err := buildImage(name, ` FROM busybox @@ -451,20 +439,18 @@ func TestBuildEnvEscapes(t *testing.T) { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-t", name)) if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "$" { - t.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out)) + c.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out)) } - logDone("build - env should handle \\$ properly") } -func TestBuildEnvOverwrite(t *testing.T) { +func (s *DockerSuite) TestBuildEnvOverwrite(c *check.C) { name := "testbuildenvoverwrite" defer deleteImages(name) - defer deleteAllContainers() _, err := buildImage(name, ` @@ -475,32 +461,30 @@ func TestBuildEnvOverwrite(t *testing.T) { true) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-e", "TEST=bar", "-t", name)) if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "bar" { - t.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out)) + c.Fatalf("Env TEST was not overwritten with bar when foo was supplied to dockerfile: was %q", strings.TrimSpace(out)) } - logDone("build - env should overwrite builder ENV during run") } -func TestBuildOnBuildForbiddenMaintainerInSourceImage(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainerInSourceImage(c *check.C) { name := "testbuildonbuildforbiddenmaintainerinsourceimage" defer deleteImages("onbuild") defer deleteImages(name) - defer deleteAllContainers() createCmd := exec.Command(dockerBinary, "create", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(createCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -508,7 +492,7 @@ func TestBuildOnBuildForbiddenMaintainerInSourceImage(t *testing.T) { commitCmd := exec.Command(dockerBinary, "commit", "--run", "{\"OnBuild\":[\"MAINTAINER docker.io\"]}", cleanedContainerID, "onbuild") if _, err := runCommand(commitCmd); err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name, @@ -516,25 +500,23 @@ func TestBuildOnBuildForbiddenMaintainerInSourceImage(t *testing.T) { true) if err != nil { if !strings.Contains(err.Error(), "maintainer isn't allowed as an ONBUILD trigger") { - t.Fatalf("Wrong error %v, must be about MAINTAINER and ONBUILD in source image", err) + c.Fatalf("Wrong error %v, must be about MAINTAINER and ONBUILD in source image", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - onbuild forbidden maintainer in source image") } -func TestBuildOnBuildForbiddenFromInSourceImage(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildForbiddenFromInSourceImage(c *check.C) { name := "testbuildonbuildforbiddenfrominsourceimage" defer deleteImages("onbuild") defer deleteImages(name) - defer deleteAllContainers() createCmd := exec.Command(dockerBinary, "create", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(createCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -542,7 +524,7 @@ func TestBuildOnBuildForbiddenFromInSourceImage(t *testing.T) { commitCmd := exec.Command(dockerBinary, "commit", "--run", "{\"OnBuild\":[\"FROM busybox\"]}", cleanedContainerID, "onbuild") if _, err := runCommand(commitCmd); err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name, @@ -550,25 +532,23 @@ func TestBuildOnBuildForbiddenFromInSourceImage(t *testing.T) { true) if err != nil { if !strings.Contains(err.Error(), "from isn't allowed as an ONBUILD trigger") { - t.Fatalf("Wrong error %v, must be about FROM and ONBUILD in source image", err) + c.Fatalf("Wrong error %v, must be about FROM and ONBUILD in source image", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - onbuild forbidden from in source image") } -func TestBuildOnBuildForbiddenChainedInSourceImage(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildForbiddenChainedInSourceImage(c *check.C) { name := "testbuildonbuildforbiddenchainedinsourceimage" defer deleteImages("onbuild") defer deleteImages(name) - defer deleteAllContainers() createCmd := exec.Command(dockerBinary, "create", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(createCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -576,7 +556,7 @@ func TestBuildOnBuildForbiddenChainedInSourceImage(t *testing.T) { commitCmd := exec.Command(dockerBinary, "commit", "--run", "{\"OnBuild\":[\"ONBUILD RUN ls\"]}", cleanedContainerID, "onbuild") if _, err := runCommand(commitCmd); err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name, @@ -584,22 +564,20 @@ func TestBuildOnBuildForbiddenChainedInSourceImage(t *testing.T) { true) if err != nil { if !strings.Contains(err.Error(), "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") { - t.Fatalf("Wrong error %v, must be about chaining ONBUILD in source image", err) + c.Fatalf("Wrong error %v, must be about chaining ONBUILD in source image", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - onbuild forbidden chained in source image") } -func TestBuildOnBuildCmdEntrypointJSON(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildCmdEntrypointJSON(c *check.C) { name1 := "onbuildcmd" name2 := "onbuildgenerated" defer deleteImages(name2) defer deleteImages(name1) - defer deleteAllContainers() _, err := buildImage(name1, ` FROM busybox @@ -609,34 +587,32 @@ ONBUILD RUN ["true"]`, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name2, fmt.Sprintf(`FROM %s`, name1), false) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-t", name2)) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) { - t.Fatal("did not get echo output from onbuild", out) + c.Fatal("did not get echo output from onbuild", out) } - logDone("build - onbuild with json entrypoint/cmd") } -func TestBuildOnBuildEntrypointJSON(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildEntrypointJSON(c *check.C) { name1 := "onbuildcmd" name2 := "onbuildgenerated" defer deleteImages(name2) defer deleteImages(name1) - defer deleteAllContainers() _, err := buildImage(name1, ` FROM busybox @@ -644,28 +620,27 @@ ONBUILD ENTRYPOINT ["echo"]`, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name2, fmt.Sprintf("FROM %s\nCMD [\"hello world\"]\n", name1), false) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-t", name2)) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !regexp.MustCompile(`(?m)^hello world`).MatchString(out) { - t.Fatal("got malformed output from onbuild", out) + c.Fatal("got malformed output from onbuild", out) } - logDone("build - onbuild with json entrypoint") } -func TestBuildCacheADD(t *testing.T) { +func (s *DockerSuite) TestBuildCacheADD(c *check.C) { name := "testbuildtwoimageswithadd" defer deleteImages(name) server, err := fakeStorage(map[string]string{ @@ -673,7 +648,7 @@ func TestBuildCacheADD(t *testing.T) { "index.html": "world", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -681,10 +656,10 @@ func TestBuildCacheADD(t *testing.T) { fmt.Sprintf(`FROM scratch ADD %s/robots.txt /`, server.URL()), true); err != nil { - t.Fatal(err) + c.Fatal(err) } if err != nil { - t.Fatal(err) + c.Fatal(err) } deleteImages(name) _, out, err := buildImageWithOut(name, @@ -692,16 +667,15 @@ func TestBuildCacheADD(t *testing.T) { ADD %s/index.html /`, server.URL()), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.Contains(out, "Using cache") { - t.Fatal("2nd build used cache on ADD, it shouldn't") + c.Fatal("2nd build used cache on ADD, it shouldn't") } - logDone("build - build two images with remote ADD") } -func TestBuildLastModified(t *testing.T) { +func (s *DockerSuite) TestBuildLastModified(c *check.C) { name := "testbuildlastmodified" defer deleteImages(name) @@ -709,7 +683,7 @@ func TestBuildLastModified(t *testing.T) { "file": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -722,13 +696,13 @@ RUN ls -le /file` dockerfile := fmt.Sprintf(dFmt, server.URL()) if _, out, err = buildImageWithOut(name, dockerfile, false); err != nil { - t.Fatal(err) + c.Fatal(err) } originMTime := regexp.MustCompile(`root.*/file.*\n`).FindString(out) // Make sure our regexp is correct if strings.Index(originMTime, "/file") < 0 { - t.Fatalf("Missing ls info on 'file':\n%s", out) + c.Fatalf("Missing ls info on 'file':\n%s", out) } // Build it again and make sure the mtime of the file didn't change. @@ -736,12 +710,12 @@ RUN ls -le /file` time.Sleep(2 * time.Second) if _, out2, err = buildImageWithOut(name, dockerfile, false); err != nil { - t.Fatal(err) + c.Fatal(err) } newMTime := regexp.MustCompile(`root.*/file.*\n`).FindString(out2) if newMTime != originMTime { - t.Fatalf("MTime changed:\nOrigin:%s\nNew:%s", originMTime, newMTime) + c.Fatalf("MTime changed:\nOrigin:%s\nNew:%s", originMTime, newMTime) } // Now 'touch' the file and make sure the timestamp DID change this time @@ -750,25 +724,24 @@ RUN ls -le /file` "file": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() dockerfile = fmt.Sprintf(dFmt, server.URL()) if _, out2, err = buildImageWithOut(name, dockerfile, false); err != nil { - t.Fatal(err) + c.Fatal(err) } newMTime = regexp.MustCompile(`root.*/file.*\n`).FindString(out2) if newMTime == originMTime { - t.Fatalf("MTime didn't change:\nOrigin:%s\nNew:%s", originMTime, newMTime) + c.Fatalf("MTime didn't change:\nOrigin:%s\nNew:%s", originMTime, newMTime) } - logDone("build - use Last-Modified header") } -func TestBuildSixtySteps(t *testing.T) { +func (s *DockerSuite) TestBuildSixtySteps(c *check.C) { name := "foobuildsixtysteps" defer deleteImages(name) ctx, err := fakeContext("FROM scratch\n"+strings.Repeat("ADD foo /\n", 60), @@ -776,17 +749,16 @@ func TestBuildSixtySteps(t *testing.T) { "foo": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - build an image with sixty build steps") } -func TestBuildAddSingleFileToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildAddSingleFileToRoot(c *check.C) { name := "testaddimg" defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox @@ -802,18 +774,17 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add single file to root") } // Issue #3960: "ADD src ." hangs -func TestBuildAddSingleFileToWorkdir(t *testing.T) { +func (s *DockerSuite) TestBuildAddSingleFileToWorkdir(c *check.C) { name := "testaddsinglefiletoworkdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -822,26 +793,25 @@ ADD test_file .`, "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() done := make(chan struct{}) go func() { if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } close(done) }() select { case <-time.After(5 * time.Second): - t.Fatal("Build with adding to workdir timed out") + c.Fatal("Build with adding to workdir timed out") case <-done: } - logDone("build - add single file to workdir") } -func TestBuildAddSingleFileToExistDir(t *testing.T) { +func (s *DockerSuite) TestBuildAddSingleFileToExistDir(c *check.C) { name := "testaddsinglefiletoexistdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -858,22 +828,21 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add single file to existing dir") } -func TestBuildCopyAddMultipleFiles(t *testing.T) { +func (s *DockerSuite) TestBuildCopyAddMultipleFiles(c *check.C) { server, err := fakeStorage(map[string]string{ "robots.txt": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -905,16 +874,15 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - multiple file copy/add tests") } -func TestBuildAddMultipleFilesToFile(t *testing.T) { +func (s *DockerSuite) TestBuildAddMultipleFilesToFile(c *check.C) { name := "testaddmultiplefilestofile" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -926,18 +894,17 @@ func TestBuildAddMultipleFilesToFile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using ADD with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple add files to file") } -func TestBuildJSONAddMultipleFilesToFile(t *testing.T) { +func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFile(c *check.C) { name := "testjsonaddmultiplefilestofile" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -949,18 +916,17 @@ func TestBuildJSONAddMultipleFilesToFile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using ADD with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple add files to file json syntax") } -func TestBuildAddMultipleFilesToFileWild(t *testing.T) { +func (s *DockerSuite) TestBuildAddMultipleFilesToFileWild(c *check.C) { name := "testaddmultiplefilestofilewild" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -972,18 +938,17 @@ func TestBuildAddMultipleFilesToFileWild(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using ADD with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple add files to file wild") } -func TestBuildJSONAddMultipleFilesToFileWild(t *testing.T) { +func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFileWild(c *check.C) { name := "testjsonaddmultiplefilestofilewild" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -995,18 +960,17 @@ func TestBuildJSONAddMultipleFilesToFileWild(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using ADD with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple add files to file wild json syntax") } -func TestBuildCopyMultipleFilesToFile(t *testing.T) { +func (s *DockerSuite) TestBuildCopyMultipleFilesToFile(c *check.C) { name := "testcopymultiplefilestofile" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -1018,18 +982,17 @@ func TestBuildCopyMultipleFilesToFile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using COPY with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple copy files to file") } -func TestBuildJSONCopyMultipleFilesToFile(t *testing.T) { +func (s *DockerSuite) TestBuildJSONCopyMultipleFilesToFile(c *check.C) { name := "testjsoncopymultiplefilestofile" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -1041,18 +1004,17 @@ func TestBuildJSONCopyMultipleFilesToFile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using COPY with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple copy files to file json syntax") } -func TestBuildAddFileWithWhitespace(t *testing.T) { +func (s *DockerSuite) TestBuildAddFileWithWhitespace(c *check.C) { name := "testaddfilewithwhitespace" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1080,16 +1042,15 @@ RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add file with whitespace") } -func TestBuildCopyFileWithWhitespace(t *testing.T) { +func (s *DockerSuite) TestBuildCopyFileWithWhitespace(c *check.C) { name := "testcopyfilewithwhitespace" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1117,16 +1078,15 @@ RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy file with whitespace") } -func TestBuildAddMultipleFilesToFileWithWhitespace(t *testing.T) { +func (s *DockerSuite) TestBuildAddMultipleFilesToFileWithWhitespace(c *check.C) { name := "testaddmultiplefilestofilewithwhitespace" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1138,18 +1098,17 @@ func TestBuildAddMultipleFilesToFileWithWhitespace(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using ADD with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple add files to file with whitespace") } -func TestBuildCopyMultipleFilesToFileWithWhitespace(t *testing.T) { +func (s *DockerSuite) TestBuildCopyMultipleFilesToFileWithWhitespace(c *check.C) { name := "testcopymultiplefilestofilewithwhitespace" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1161,18 +1120,17 @@ func TestBuildCopyMultipleFilesToFileWithWhitespace(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "When using COPY with more than one source file, the destination must be a directory and end with a /" if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain %q) got:\n%v", expected, err) } - logDone("build - multiple copy files to file with whitespace") } -func TestBuildCopyWildcard(t *testing.T) { +func (s *DockerSuite) TestBuildCopyWildcard(c *check.C) { name := "testcopywildcard" defer deleteImages(name) server, err := fakeStorage(map[string]string{ @@ -1180,7 +1138,7 @@ func TestBuildCopyWildcard(t *testing.T) { "index.html": "world", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -1203,28 +1161,27 @@ func TestBuildCopyWildcard(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Now make sure we use a cache the 2nd time id2, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("didn't use the cache") + c.Fatal("didn't use the cache") } - logDone("build - copy wild card") } -func TestBuildCopyWildcardNoFind(t *testing.T) { +func (s *DockerSuite) TestBuildCopyWildcardNoFind(c *check.C) { name := "testcopywildcardnofind" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1232,21 +1189,20 @@ func TestBuildCopyWildcardNoFind(t *testing.T) { `, nil) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImageFromContext(name, ctx, true) if err == nil { - t.Fatal("should have failed to find a file") + c.Fatal("should have failed to find a file") } if !strings.Contains(err.Error(), "No source files were specified") { - t.Fatalf("Wrong error %v, must be about no source files", err) + c.Fatalf("Wrong error %v, must be about no source files", err) } - logDone("build - copy wild card no find") } -func TestBuildCopyWildcardCache(t *testing.T) { +func (s *DockerSuite) TestBuildCopyWildcardCache(c *check.C) { name := "testcopywildcardcache" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1256,12 +1212,12 @@ func TestBuildCopyWildcardCache(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Now make sure we use a cache the 2nd time even with wild cards. @@ -1271,17 +1227,16 @@ func TestBuildCopyWildcardCache(t *testing.T) { id2, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("didn't use the cache") + c.Fatal("didn't use the cache") } - logDone("build - copy wild card cache") } -func TestBuildAddSingleFileToNonExistingDir(t *testing.T) { +func (s *DockerSuite) TestBuildAddSingleFileToNonExistingDir(c *check.C) { name := "testaddsinglefiletononexistingdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1297,18 +1252,17 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add single file to non-existing dir") } -func TestBuildAddDirContentToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildAddDirContentToRoot(c *check.C) { name := "testadddircontenttoroot" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1323,17 +1277,16 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, "test_dir/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add directory contents to root") } -func TestBuildAddDirContentToExistingDir(t *testing.T) { +func (s *DockerSuite) TestBuildAddDirContentToExistingDir(c *check.C) { name := "testadddircontenttoexistingdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1350,17 +1303,16 @@ RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`, "test_dir/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add directory contents to existing dir") } -func TestBuildAddWholeDirToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildAddWholeDirToRoot(c *check.C) { name := "testaddwholedirtoroot" defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox @@ -1378,18 +1330,17 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte "test_dir/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add whole directory to root") } // Testing #5941 -func TestBuildAddEtcToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildAddEtcToRoot(c *check.C) { name := "testaddetctoroot" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -1398,18 +1349,17 @@ ADD . /`, "etc/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add etc directory to root") } // Testing #9401 -func TestBuildAddPreservesFilesSpecialBits(t *testing.T) { +func (s *DockerSuite) TestBuildAddPreservesFilesSpecialBits(c *check.C) { name := "testaddpreservesfilesspecialbits" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1423,17 +1373,16 @@ RUN [ $(ls -l /usr/bin/suidbin | awk '{print $1}') = '-rwsr-xr-x' ]`, "/data/usr/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add preserves files special bits") } -func TestBuildCopySingleFileToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildCopySingleFileToRoot(c *check.C) { name := "testcopysinglefiletoroot" defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox @@ -1449,18 +1398,17 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy single file to root") } // Issue #3960: "ADD src ." hangs - adapted for COPY -func TestBuildCopySingleFileToWorkdir(t *testing.T) { +func (s *DockerSuite) TestBuildCopySingleFileToWorkdir(c *check.C) { name := "testcopysinglefiletoworkdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1469,26 +1417,25 @@ COPY test_file .`, "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() done := make(chan struct{}) go func() { if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } close(done) }() select { case <-time.After(5 * time.Second): - t.Fatal("Build with adding to workdir timed out") + c.Fatal("Build with adding to workdir timed out") case <-done: } - logDone("build - copy single file to workdir") } -func TestBuildCopySingleFileToExistDir(t *testing.T) { +func (s *DockerSuite) TestBuildCopySingleFileToExistDir(c *check.C) { name := "testcopysinglefiletoexistdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1505,17 +1452,16 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy single file to existing dir") } -func TestBuildCopySingleFileToNonExistDir(t *testing.T) { +func (s *DockerSuite) TestBuildCopySingleFileToNonExistDir(c *check.C) { name := "testcopysinglefiletononexistdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1531,17 +1477,16 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, "test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy single file to non-existing dir") } -func TestBuildCopyDirContentToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildCopyDirContentToRoot(c *check.C) { name := "testcopydircontenttoroot" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1556,17 +1501,16 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, "test_dir/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy directory contents to root") } -func TestBuildCopyDirContentToExistDir(t *testing.T) { +func (s *DockerSuite) TestBuildCopyDirContentToExistDir(c *check.C) { name := "testcopydircontenttoexistdir" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -1583,17 +1527,16 @@ RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`, "test_dir/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy directory contents to existing dir") } -func TestBuildCopyWholeDirToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildCopyWholeDirToRoot(c *check.C) { name := "testcopywholedirtoroot" defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox @@ -1611,17 +1554,16 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte "test_dir/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy whole directory to root") } -func TestBuildCopyEtcToRoot(t *testing.T) { +func (s *DockerSuite) TestBuildCopyEtcToRoot(c *check.C) { name := "testcopyetctoroot" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -1630,29 +1572,27 @@ COPY . /`, "etc/test_file": "test1", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - copy etc directory to root") } -func TestBuildCopyDisallowRemote(t *testing.T) { +func (s *DockerSuite) TestBuildCopyDisallowRemote(c *check.C) { name := "testcopydisallowremote" defer deleteImages(name) _, out, err := buildImageWithOut(name, `FROM scratch COPY https://index.docker.io/robots.txt /`, true) if err == nil || !strings.Contains(out, "Source can't be a URL for COPY") { - t.Fatalf("Error should be about disallowed remote source, got err: %s, out: %q", err, out) + c.Fatalf("Error should be about disallowed remote source, got err: %s, out: %q", err, out) } - logDone("build - copy - disallow copy from remote") } -func TestBuildAddBadLinks(t *testing.T) { +func (s *DockerSuite) TestBuildAddBadLinks(c *check.C) { const ( dockerfile = ` FROM scratch @@ -1667,13 +1607,13 @@ func TestBuildAddBadLinks(t *testing.T) { defer deleteImages(name) ctx, err := fakeContext(dockerfile, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() tempDir, err := ioutil.TempDir("", "test-link-absolute-temp-") if err != nil { - t.Fatalf("failed to create temporary directory: %s", tempDir) + c.Fatalf("failed to create temporary directory: %s", tempDir) } defer os.RemoveAll(tempDir) @@ -1681,7 +1621,7 @@ func TestBuildAddBadLinks(t *testing.T) { if runtime.GOOS == "windows" { var driveLetter string if abs, err := filepath.Abs(tempDir); err != nil { - t.Fatal(err) + c.Fatal(err) } else { driveLetter = abs[:1] } @@ -1697,7 +1637,7 @@ func TestBuildAddBadLinks(t *testing.T) { tarOut, err := os.Create(tarPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } tarWriter := tar.NewWriter(tarOut) @@ -1713,7 +1653,7 @@ func TestBuildAddBadLinks(t *testing.T) { err = tarWriter.WriteHeader(header) if err != nil { - t.Fatal(err) + c.Fatal(err) } tarWriter.Close() @@ -1721,26 +1661,25 @@ func TestBuildAddBadLinks(t *testing.T) { foo, err := os.Create(fooPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer foo.Close() if _, err := foo.WriteString("test"); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := os.Stat(nonExistingFile); err == nil || err != nil && !os.IsNotExist(err) { - t.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile) + c.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile) } - logDone("build - ADD must add files in container") } -func TestBuildAddBadLinksVolume(t *testing.T) { +func (s *DockerSuite) TestBuildAddBadLinksVolume(c *check.C) { const ( dockerfileTemplate = ` FROM busybox @@ -1757,7 +1696,7 @@ func TestBuildAddBadLinksVolume(t *testing.T) { tempDir, err := ioutil.TempDir("", "test-link-absolute-volume-temp-") if err != nil { - t.Fatalf("failed to create temporary directory: %s", tempDir) + c.Fatalf("failed to create temporary directory: %s", tempDir) } defer os.RemoveAll(tempDir) @@ -1766,68 +1705,67 @@ func TestBuildAddBadLinksVolume(t *testing.T) { ctx, err := fakeContext(dockerfile, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() fooPath := filepath.Join(ctx.Dir, targetFile) foo, err := os.Create(fooPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer foo.Close() if _, err := foo.WriteString("test"); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := os.Stat(nonExistingFile); err == nil || err != nil && !os.IsNotExist(err) { - t.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile) + c.Fatalf("%s shouldn't have been written and it shouldn't exist", nonExistingFile) } - logDone("build - ADD should add files in volume") } // Issue #5270 - ensure we throw a better error than "unexpected EOF" // when we can't access files in the context. -func TestBuildWithInaccessibleFilesInContext(t *testing.T) { - testRequires(t, UnixCli) // test uses chown/chmod: not available on windows +func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) { + testRequires(c, UnixCli) // test uses chown/chmod: not available on windows { name := "testbuildinaccessiblefiles" defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{"fileWithoutReadAccess": "foo"}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() // This is used to ensure we detect inaccessible files early during build in the cli client pathToFileWithoutReadAccess := filepath.Join(ctx.Dir, "fileWithoutReadAccess") if err = os.Chown(pathToFileWithoutReadAccess, 0, 0); err != nil { - t.Fatalf("failed to chown file to root: %s", err) + c.Fatalf("failed to chown file to root: %s", err) } if err = os.Chmod(pathToFileWithoutReadAccess, 0700); err != nil { - t.Fatalf("failed to chmod file to 700: %s", err) + c.Fatalf("failed to chmod file to 700: %s", err) } buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)) buildCmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(buildCmd) if err == nil { - t.Fatalf("build should have failed: %s %s", err, out) + c.Fatalf("build should have failed: %s %s", err, out) } // check if we've detected the failure before we started building if !strings.Contains(out, "no permission to read from ") { - t.Fatalf("output should've contained the string: no permission to read from but contained: %s", out) + c.Fatalf("output should've contained the string: no permission to read from but contained: %s", out) } if !strings.Contains(out, "Error checking context is accessible") { - t.Fatalf("output should've contained the string: Error checking context is accessible") + c.Fatalf("output should've contained the string: Error checking context is accessible") } } { @@ -1835,7 +1773,7 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) { defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{"directoryWeCantStat/bar": "foo"}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() // This is used to ensure we detect inaccessible directories early during build in the cli client @@ -1843,29 +1781,29 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) { pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar") if err = os.Chown(pathToDirectoryWithoutReadAccess, 0, 0); err != nil { - t.Fatalf("failed to chown directory to root: %s", err) + c.Fatalf("failed to chown directory to root: %s", err) } if err = os.Chmod(pathToDirectoryWithoutReadAccess, 0444); err != nil { - t.Fatalf("failed to chmod directory to 444: %s", err) + c.Fatalf("failed to chmod directory to 444: %s", err) } if err = os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0700); err != nil { - t.Fatalf("failed to chmod file to 700: %s", err) + c.Fatalf("failed to chmod file to 700: %s", err) } buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)) buildCmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(buildCmd) if err == nil { - t.Fatalf("build should have failed: %s %s", err, out) + c.Fatalf("build should have failed: %s %s", err, out) } // check if we've detected the failure before we started building if !strings.Contains(out, "can't stat") { - t.Fatalf("output should've contained the string: can't access %s", out) + c.Fatalf("output should've contained the string: can't access %s", out) } if !strings.Contains(out, "Error checking context is accessible") { - t.Fatalf("output should've contained the string: Error checking context is accessible") + c.Fatalf("output should've contained the string: Error checking context is accessible") } } @@ -1874,19 +1812,19 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) { defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() target := "../../../../../../../../../../../../../../../../../../../azA" if err := os.Symlink(filepath.Join(ctx.Dir, "g"), target); err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.Remove(target) // This is used to ensure we don't follow links when checking if everything in the context is accessible // This test doesn't require that we run commands as an unprivileged user if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } } { @@ -1898,61 +1836,59 @@ func TestBuildWithInaccessibleFilesInContext(t *testing.T) { ".dockerignore": "directoryWeCantStat", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() // This is used to ensure we don't try to add inaccessible files when they are ignored by a .dockerignore pattern pathToDirectoryWithoutReadAccess := filepath.Join(ctx.Dir, "directoryWeCantStat") pathToFileInDirectoryWithoutReadAccess := filepath.Join(pathToDirectoryWithoutReadAccess, "bar") if err = os.Chown(pathToDirectoryWithoutReadAccess, 0, 0); err != nil { - t.Fatalf("failed to chown directory to root: %s", err) + c.Fatalf("failed to chown directory to root: %s", err) } if err = os.Chmod(pathToDirectoryWithoutReadAccess, 0444); err != nil { - t.Fatalf("failed to chmod directory to 755: %s", err) + c.Fatalf("failed to chmod directory to 755: %s", err) } if err = os.Chmod(pathToFileInDirectoryWithoutReadAccess, 0700); err != nil { - t.Fatalf("failed to chmod file to 444: %s", err) + c.Fatalf("failed to chmod file to 444: %s", err) } buildCmd := exec.Command("su", "unprivilegeduser", "-c", fmt.Sprintf("%s build -t %s .", dockerBinary, name)) buildCmd.Dir = ctx.Dir if out, _, err := runCommandWithOutput(buildCmd); err != nil { - t.Fatalf("build should have worked: %s %s", err, out) + c.Fatalf("build should have worked: %s %s", err, out) } } - logDone("build - ADD from context with inaccessible files must not pass") } -func TestBuildForceRm(t *testing.T) { +func (s *DockerSuite) TestBuildForceRm(c *check.C) { containerCountBefore, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } name := "testbuildforcerm" defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nRUN true\nRUN thiswillfail", nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() buildCmd := exec.Command(dockerBinary, "build", "-t", name, "--force-rm", ".") buildCmd.Dir = ctx.Dir if out, _, err := runCommandWithOutput(buildCmd); err == nil { - t.Fatalf("failed to build the image: %s, %v", out, err) + c.Fatalf("failed to build the image: %s, %v", out, err) } containerCountAfter, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } if containerCountBefore != containerCountAfter { - t.Fatalf("--force-rm shouldn't have left containers behind") + c.Fatalf("--force-rm shouldn't have left containers behind") } - logDone("build - ensure --force-rm doesn't leave containers behind") } // Test that an infinite sleep during a build is killed if the client disconnects. @@ -1962,18 +1898,17 @@ func TestBuildForceRm(t *testing.T) { // * Run a 1-year-long sleep from a docker build. // * When docker events sees container start, close the "docker build" command // * Wait for docker events to emit a dying event. -func TestBuildCancelationKillsSleep(t *testing.T) { +func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { var wg sync.WaitGroup defer wg.Wait() name := "testbuildcancelation" defer deleteImages(name) - defer deleteAllContainers() // (Note: one year, will never finish) ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() @@ -1984,7 +1919,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) { eventDie := make(chan struct{}) containerID := make(chan string) - startEpoch := daemonTime(t).Unix() + startEpoch := daemonTime(c).Unix() wg.Add(1) // Goroutine responsible for watching start/die events from `docker events` @@ -1997,7 +1932,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) { stdout, err := eventsCmd.StdoutPipe() err = eventsCmd.Start() if err != nil { - t.Fatalf("failed to start 'docker events': %s", err) + c.Fatalf("failed to start 'docker events': %s", err) } go func() { @@ -2025,7 +1960,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) { err = eventsCmd.Wait() if err != nil && !IsKilled(err) { - t.Fatalf("docker events had bad exit status: %s", err) + c.Fatalf("docker events had bad exit status: %s", err) } }() @@ -2035,7 +1970,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) { stdoutBuild, err := buildCmd.StdoutPipe() err = buildCmd.Start() if err != nil { - t.Fatalf("failed to run build: %s", err) + c.Fatalf("failed to run build: %s", err) } matchCID := regexp.MustCompile("Running in ") @@ -2050,7 +1985,7 @@ func TestBuildCancelationKillsSleep(t *testing.T) { select { case <-time.After(5 * time.Second): - t.Fatal("failed to observe build container start in timely fashion") + c.Fatal("failed to observe build container start in timely fashion") case <-eventStart: // Proceeds from here when we see the container fly past in the // output of "docker events". @@ -2061,54 +1996,53 @@ func TestBuildCancelationKillsSleep(t *testing.T) { // Causes the underlying build to be cancelled due to socket close. err = buildCmd.Process.Kill() if err != nil { - t.Fatalf("error killing build command: %s", err) + c.Fatalf("error killing build command: %s", err) } // Get the exit status of `docker build`, check it exited because killed. err = buildCmd.Wait() if err != nil && !IsKilled(err) { - t.Fatalf("wait failed during build run: %T %s", err, err) + c.Fatalf("wait failed during build run: %T %s", err, err) } select { case <-time.After(5 * time.Second): // If we don't get here in a timely fashion, it wasn't killed. - t.Fatal("container cancel did not succeed") + c.Fatal("container cancel did not succeed") case <-eventDie: // We saw the container shut down in the `docker events` stream, // as expected. } - logDone("build - ensure canceled job finishes immediately") } -func TestBuildRm(t *testing.T) { +func (s *DockerSuite) TestBuildRm(c *check.C) { name := "testbuildrm" defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD foo /\nADD foo /", map[string]string{"foo": "bar"}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() { containerCountBefore, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "--rm", "-t", name, ".") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "--rm", "-t", name, ".") if err != nil { - t.Fatal("failed to build the image", out) + c.Fatal("failed to build the image", out) } containerCountAfter, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } if containerCountBefore != containerCountAfter { - t.Fatalf("-rm shouldn't have left containers behind") + c.Fatalf("-rm shouldn't have left containers behind") } deleteImages(name) } @@ -2116,22 +2050,22 @@ func TestBuildRm(t *testing.T) { { containerCountBefore, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", name, ".") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", name, ".") if err != nil { - t.Fatal("failed to build the image", out) + c.Fatal("failed to build the image", out) } containerCountAfter, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } if containerCountBefore != containerCountAfter { - t.Fatalf("--rm shouldn't have left containers behind") + c.Fatalf("--rm shouldn't have left containers behind") } deleteImages(name) } @@ -2139,32 +2073,31 @@ func TestBuildRm(t *testing.T) { { containerCountBefore, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "--rm=false", "-t", name, ".") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "--rm=false", "-t", name, ".") if err != nil { - t.Fatal("failed to build the image", out) + c.Fatal("failed to build the image", out) } containerCountAfter, err := getContainerCount() if err != nil { - t.Fatalf("failed to get the container count: %s", err) + c.Fatalf("failed to get the container count: %s", err) } if containerCountBefore == containerCountAfter { - t.Fatalf("--rm=false should have left containers behind") + c.Fatalf("--rm=false should have left containers behind") } deleteAllContainers() deleteImages(name) } - logDone("build - ensure --rm doesn't leave containers behind and that --rm=true is the default") } -func TestBuildWithVolumes(t *testing.T) { +func (s *DockerSuite) TestBuildWithVolumes(c *check.C) { var ( result map[string]map[string]struct{} name = "testbuildvolumes" @@ -2191,28 +2124,27 @@ func TestBuildWithVolumes(t *testing.T) { `, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Volumes") if err != nil { - t.Fatal(err) + c.Fatal(err) } err = unmarshalJSON([]byte(res), &result) if err != nil { - t.Fatal(err) + c.Fatal(err) } equal := reflect.DeepEqual(&result, &expected) if !equal { - t.Fatalf("Volumes %s, expected %s", result, expected) + c.Fatalf("Volumes %s, expected %s", result, expected) } - logDone("build - with volumes") } -func TestBuildMaintainer(t *testing.T) { +func (s *DockerSuite) TestBuildMaintainer(c *check.C) { name := "testbuildmaintainer" expected := "dockerio" defer deleteImages(name) @@ -2221,19 +2153,18 @@ func TestBuildMaintainer(t *testing.T) { MAINTAINER dockerio`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Author") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Maintainer %s, expected %s", res, expected) + c.Fatalf("Maintainer %s, expected %s", res, expected) } - logDone("build - maintainer") } -func TestBuildUser(t *testing.T) { +func (s *DockerSuite) TestBuildUser(c *check.C) { name := "testbuilduser" expected := "dockerio" defer deleteImages(name) @@ -2244,19 +2175,18 @@ func TestBuildUser(t *testing.T) { RUN [ $(whoami) = 'dockerio' ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.User") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("User %s, expected %s", res, expected) + c.Fatalf("User %s, expected %s", res, expected) } - logDone("build - user") } -func TestBuildRelativeWorkdir(t *testing.T) { +func (s *DockerSuite) TestBuildRelativeWorkdir(c *check.C) { name := "testbuildrelativeworkdir" expected := "/test2/test3" defer deleteImages(name) @@ -2271,19 +2201,18 @@ func TestBuildRelativeWorkdir(t *testing.T) { RUN [ "$PWD" = '/test2/test3' ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.WorkingDir") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Workdir %s, expected %s", res, expected) + c.Fatalf("Workdir %s, expected %s", res, expected) } - logDone("build - relative workdir") } -func TestBuildWorkdirWithEnvVariables(t *testing.T) { +func (s *DockerSuite) TestBuildWorkdirWithEnvVariables(c *check.C) { name := "testbuildworkdirwithenvvariables" expected := "/test1/test2" defer deleteImages(name) @@ -2295,19 +2224,18 @@ func TestBuildWorkdirWithEnvVariables(t *testing.T) { WORKDIR $SUBDIRNAME/$MISSING_VAR`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.WorkingDir") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Workdir %s, expected %s", res, expected) + c.Fatalf("Workdir %s, expected %s", res, expected) } - logDone("build - workdir with env variables") } -func TestBuildRelativeCopy(t *testing.T) { +func (s *DockerSuite) TestBuildRelativeCopy(c *check.C) { name := "testbuildrelativecopy" defer deleteImages(name) dockerfile := ` @@ -2338,16 +2266,15 @@ func TestBuildRelativeCopy(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImageFromContext(name, ctx, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - relative copy/add") } -func TestBuildEnv(t *testing.T) { +func (s *DockerSuite) TestBuildEnv(c *check.C) { name := "testbuildenv" expected := "[PATH=/test:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=2375]" defer deleteImages(name) @@ -2358,74 +2285,70 @@ func TestBuildEnv(t *testing.T) { RUN [ $(env | grep PORT) = 'PORT=2375' ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Env") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Env %s, expected %s", res, expected) + c.Fatalf("Env %s, expected %s", res, expected) } - logDone("build - env") } -func TestBuildContextCleanup(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestBuildContextCleanup(c *check.C) { + testRequires(c, SameHostDaemon) name := "testbuildcontextcleanup" defer deleteImages(name) entries, err := ioutil.ReadDir("/var/lib/docker/tmp") if err != nil { - t.Fatalf("failed to list contents of tmp dir: %s", err) + c.Fatalf("failed to list contents of tmp dir: %s", err) } _, err = buildImage(name, `FROM scratch ENTRYPOINT ["/bin/echo"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } entriesFinal, err := ioutil.ReadDir("/var/lib/docker/tmp") if err != nil { - t.Fatalf("failed to list contents of tmp dir: %s", err) + c.Fatalf("failed to list contents of tmp dir: %s", err) } if err = compareDirectoryEntries(entries, entriesFinal); err != nil { - t.Fatalf("context should have been deleted, but wasn't") + c.Fatalf("context should have been deleted, but wasn't") } - logDone("build - verify context cleanup works properly") } -func TestBuildContextCleanupFailedBuild(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestBuildContextCleanupFailedBuild(c *check.C) { + testRequires(c, SameHostDaemon) name := "testbuildcontextcleanup" defer deleteImages(name) - defer deleteAllContainers() entries, err := ioutil.ReadDir("/var/lib/docker/tmp") if err != nil { - t.Fatalf("failed to list contents of tmp dir: %s", err) + c.Fatalf("failed to list contents of tmp dir: %s", err) } _, err = buildImage(name, `FROM scratch RUN /non/existing/command`, true) if err == nil { - t.Fatalf("expected build to fail, but it didn't") + c.Fatalf("expected build to fail, but it didn't") } entriesFinal, err := ioutil.ReadDir("/var/lib/docker/tmp") if err != nil { - t.Fatalf("failed to list contents of tmp dir: %s", err) + c.Fatalf("failed to list contents of tmp dir: %s", err) } if err = compareDirectoryEntries(entries, entriesFinal); err != nil { - t.Fatalf("context should have been deleted, but wasn't") + c.Fatalf("context should have been deleted, but wasn't") } - logDone("build - verify context cleanup works properly after an unsuccessful build") } -func TestBuildCmd(t *testing.T) { +func (s *DockerSuite) TestBuildCmd(c *check.C) { name := "testbuildcmd" expected := "[/bin/echo Hello World]" defer deleteImages(name) @@ -2434,19 +2357,18 @@ func TestBuildCmd(t *testing.T) { CMD ["/bin/echo", "Hello World"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Cmd") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Cmd %s, expected %s", res, expected) + c.Fatalf("Cmd %s, expected %s", res, expected) } - logDone("build - cmd") } -func TestBuildExpose(t *testing.T) { +func (s *DockerSuite) TestBuildExpose(c *check.C) { name := "testbuildexpose" expected := "map[2375/tcp:map[]]" defer deleteImages(name) @@ -2455,19 +2377,18 @@ func TestBuildExpose(t *testing.T) { EXPOSE 2375`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Exposed ports %s, expected %s", res, expected) + c.Fatalf("Exposed ports %s, expected %s", res, expected) } - logDone("build - expose") } -func TestBuildExposeMorePorts(t *testing.T) { +func (s *DockerSuite) TestBuildExposeMorePorts(c *check.C) { // start building docker file with a large number of ports portList := make([]string, 50) line := make([]string, 100) @@ -2496,43 +2417,42 @@ func TestBuildExposeMorePorts(t *testing.T) { defer deleteImages(name) _, err := buildImage(name, buf.String(), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // check if all the ports are saved inside Config.ExposedPorts res, err := inspectFieldJSON(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } var exposedPorts map[string]interface{} if err := json.Unmarshal([]byte(res), &exposedPorts); err != nil { - t.Fatal(err) + c.Fatal(err) } for _, p := range expectedPorts { ep := fmt.Sprintf("%d/tcp", p) if _, ok := exposedPorts[ep]; !ok { - t.Errorf("Port(%s) is not exposed", ep) + c.Errorf("Port(%s) is not exposed", ep) } else { delete(exposedPorts, ep) } } if len(exposedPorts) != 0 { - t.Errorf("Unexpected extra exposed ports %v", exposedPorts) + c.Errorf("Unexpected extra exposed ports %v", exposedPorts) } - logDone("build - expose large number of ports") } -func TestBuildExposeOrder(t *testing.T) { +func (s *DockerSuite) TestBuildExposeOrder(c *check.C) { buildID := func(name, exposed string) string { _, err := buildImage(name, fmt.Sprintf(`FROM scratch EXPOSE %s`, exposed), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id, err := inspectField(name, "Id") if err != nil { - t.Fatal(err) + c.Fatal(err) } return id } @@ -2541,12 +2461,11 @@ func TestBuildExposeOrder(t *testing.T) { id2 := buildID("testbuildexpose2", "2375 80") defer deleteImages("testbuildexpose1", "testbuildexpose2") if id1 != id2 { - t.Errorf("EXPOSE should invalidate the cache only when ports actually changed") + c.Errorf("EXPOSE should invalidate the cache only when ports actually changed") } - logDone("build - expose order") } -func TestBuildExposeUpperCaseProto(t *testing.T) { +func (s *DockerSuite) TestBuildExposeUpperCaseProto(c *check.C) { name := "testbuildexposeuppercaseproto" expected := "map[5678/udp:map[]]" defer deleteImages(name) @@ -2555,19 +2474,18 @@ func TestBuildExposeUpperCaseProto(t *testing.T) { EXPOSE 5678/UDP`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Exposed ports %s, expected %s", res, expected) + c.Fatalf("Exposed ports %s, expected %s", res, expected) } - logDone("build - expose port with upper case proto") } -func TestBuildExposeHostPort(t *testing.T) { +func (s *DockerSuite) TestBuildExposeHostPort(c *check.C) { // start building docker file with ip:hostPort:containerPort name := "testbuildexpose" expected := "map[5678/tcp:map[]]" @@ -2577,24 +2495,23 @@ func TestBuildExposeHostPort(t *testing.T) { EXPOSE 192.168.1.2:2375:5678`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "to map host ports to container ports (ip:hostPort:containerPort) is deprecated.") { - t.Fatal("Missing warning message") + c.Fatal("Missing warning message") } res, err := inspectField(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Exposed ports %s, expected %s", res, expected) + c.Fatalf("Exposed ports %s, expected %s", res, expected) } - logDone("build - ignore exposing host's port") } -func TestBuildEmptyEntrypointInheritance(t *testing.T) { +func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { name := "testbuildentrypointinheritance" name2 := "testbuildentrypointinheritance2" defer deleteImages(name, name2) @@ -2604,16 +2521,16 @@ func TestBuildEmptyEntrypointInheritance(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Entrypoint") if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "[/bin/echo]" if res != expected { - t.Fatalf("Entrypoint %s, expected %s", res, expected) + c.Fatalf("Entrypoint %s, expected %s", res, expected) } _, err = buildImage(name2, @@ -2621,23 +2538,22 @@ func TestBuildEmptyEntrypointInheritance(t *testing.T) { ENTRYPOINT []`, name), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err = inspectField(name2, "Config.Entrypoint") if err != nil { - t.Fatal(err) + c.Fatal(err) } expected = "[]" if res != expected { - t.Fatalf("Entrypoint %s, expected %s", res, expected) + c.Fatalf("Entrypoint %s, expected %s", res, expected) } - logDone("build - empty entrypoint inheritance") } -func TestBuildEmptyEntrypoint(t *testing.T) { +func (s *DockerSuite) TestBuildEmptyEntrypoint(c *check.C) { name := "testbuildentrypoint" defer deleteImages(name) expected := "[]" @@ -2647,20 +2563,19 @@ func TestBuildEmptyEntrypoint(t *testing.T) { ENTRYPOINT []`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Entrypoint") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Entrypoint %s, expected %s", res, expected) + c.Fatalf("Entrypoint %s, expected %s", res, expected) } - logDone("build - empty entrypoint") } -func TestBuildEntrypoint(t *testing.T) { +func (s *DockerSuite) TestBuildEntrypoint(c *check.C) { name := "testbuildentrypoint" expected := "[/bin/echo]" defer deleteImages(name) @@ -2669,21 +2584,20 @@ func TestBuildEntrypoint(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Entrypoint") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Entrypoint %s, expected %s", res, expected) + c.Fatalf("Entrypoint %s, expected %s", res, expected) } - logDone("build - entrypoint") } // #6445 ensure ONBUILD triggers aren't committed to grandchildren -func TestBuildOnBuildLimitedInheritence(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildLimitedInheritence(c *check.C) { var ( out2, out3 string ) @@ -2696,13 +2610,13 @@ func TestBuildOnBuildLimitedInheritence(t *testing.T) { ` ctx, err := fakeContext(dockerfile1, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() - out1, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", name1, ".") + out1, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", name1, ".") if err != nil { - t.Fatalf("build failed to complete: %s, %v", out1, err) + c.Fatalf("build failed to complete: %s, %v", out1, err) } defer deleteImages(name1) } @@ -2713,13 +2627,13 @@ func TestBuildOnBuildLimitedInheritence(t *testing.T) { ` ctx, err := fakeContext(dockerfile2, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() - out2, _, err = dockerCmdInDir(t, ctx.Dir, "build", "-t", name2, ".") + out2, _, err = dockerCmdInDir(c, ctx.Dir, "build", "-t", name2, ".") if err != nil { - t.Fatalf("build failed to complete: %s, %v", out2, err) + c.Fatalf("build failed to complete: %s, %v", out2, err) } defer deleteImages(name2) } @@ -2730,13 +2644,13 @@ func TestBuildOnBuildLimitedInheritence(t *testing.T) { ` ctx, err := fakeContext(dockerfile3, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() - out3, _, err = dockerCmdInDir(t, ctx.Dir, "build", "-t", name3, ".") + out3, _, err = dockerCmdInDir(c, ctx.Dir, "build", "-t", name3, ".") if err != nil { - t.Fatalf("build failed to complete: %s, %v", out3, err) + c.Fatalf("build failed to complete: %s, %v", out3, err) } defer deleteImages(name3) @@ -2744,18 +2658,17 @@ func TestBuildOnBuildLimitedInheritence(t *testing.T) { // ONBUILD should be run in second build. if !strings.Contains(out2, "ONBUILD PARENT") { - t.Fatalf("ONBUILD instruction did not run in child of ONBUILD parent") + c.Fatalf("ONBUILD instruction did not run in child of ONBUILD parent") } // ONBUILD should *not* be run in third build. if strings.Contains(out3, "ONBUILD PARENT") { - t.Fatalf("ONBUILD instruction ran in grandchild of ONBUILD parent") + c.Fatalf("ONBUILD instruction ran in grandchild of ONBUILD parent") } - logDone("build - onbuild") } -func TestBuildWithCache(t *testing.T) { +func (s *DockerSuite) TestBuildWithCache(c *check.C) { name := "testbuildwithcache" defer deleteImages(name) id1, err := buildImage(name, @@ -2765,7 +2678,7 @@ func TestBuildWithCache(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImage(name, `FROM scratch @@ -2774,15 +2687,14 @@ func TestBuildWithCache(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but hasn't.") + c.Fatal("The cache should have been used but hasn't.") } - logDone("build - with cache") } -func TestBuildWithoutCache(t *testing.T) { +func (s *DockerSuite) TestBuildWithoutCache(c *check.C) { name := "testbuildwithoutcache" name2 := "testbuildwithoutcache2" defer deleteImages(name, name2) @@ -2793,7 +2705,7 @@ func TestBuildWithoutCache(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImage(name2, @@ -2803,15 +2715,14 @@ func TestBuildWithoutCache(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } - logDone("build - without cache") } -func TestBuildConditionalCache(t *testing.T) { +func (s *DockerSuite) TestBuildConditionalCache(c *check.C) { name := "testbuildconditionalcache" name2 := "testbuildconditionalcache2" defer deleteImages(name, name2) @@ -2823,39 +2734,38 @@ func TestBuildConditionalCache(t *testing.T) { "foo": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatalf("Error building #1: %s", err) + c.Fatalf("Error building #1: %s", err) } if err := ctx.Add("foo", "bye"); err != nil { - t.Fatalf("Error modifying foo: %s", err) + c.Fatalf("Error modifying foo: %s", err) } id2, err := buildImageFromContext(name, ctx, false) if err != nil { - t.Fatalf("Error building #2: %s", err) + c.Fatalf("Error building #2: %s", err) } if id2 == id1 { - t.Fatal("Should not have used the cache") + c.Fatal("Should not have used the cache") } id3, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatalf("Error building #3: %s", err) + c.Fatalf("Error building #3: %s", err) } if id3 != id2 { - t.Fatal("Should have used the cache") + c.Fatal("Should have used the cache") } - logDone("build - conditional cache") } -func TestBuildADDLocalFileWithCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDLocalFileWithCache(c *check.C) { name := "testbuildaddlocalfilewithcache" name2 := "testbuildaddlocalfilewithcache2" defer deleteImages(name, name2) @@ -2869,23 +2779,22 @@ func TestBuildADDLocalFileWithCache(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but hasn't.") + c.Fatal("The cache should have been used but hasn't.") } - logDone("build - add local file with cache") } -func TestBuildADDMultipleLocalFileWithCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDMultipleLocalFileWithCache(c *check.C) { name := "testbuildaddmultiplelocalfilewithcache" name2 := "testbuildaddmultiplelocalfilewithcache2" defer deleteImages(name, name2) @@ -2899,23 +2808,22 @@ func TestBuildADDMultipleLocalFileWithCache(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but hasn't.") + c.Fatal("The cache should have been used but hasn't.") } - logDone("build - add multiple local files with cache") } -func TestBuildADDLocalFileWithoutCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDLocalFileWithoutCache(c *check.C) { name := "testbuildaddlocalfilewithoutcache" name2 := "testbuildaddlocalfilewithoutcache2" defer deleteImages(name, name2) @@ -2929,23 +2837,22 @@ func TestBuildADDLocalFileWithoutCache(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } - logDone("build - add local file without cache") } -func TestBuildCopyDirButNotFile(t *testing.T) { +func (s *DockerSuite) TestBuildCopyDirButNotFile(c *check.C) { name := "testbuildcopydirbutnotfile" name2 := "testbuildcopydirbutnotfile2" defer deleteImages(name, name2) @@ -2957,27 +2864,26 @@ func TestBuildCopyDirButNotFile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Check that adding file with similar name doesn't mess with cache if err := ctx.Add("dir_file", "hello2"); err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but wasn't") + c.Fatal("The cache should have been used but wasn't") } - logDone("build - add current directory but not file") } -func TestBuildADDCurrentDirWithCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDCurrentDirWithCache(c *check.C) { name := "testbuildaddcurrentdirwithcache" name2 := name + "2" name3 := name + "3" @@ -2993,57 +2899,56 @@ func TestBuildADDCurrentDirWithCache(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Check that adding file invalidate cache of "ADD ." if err := ctx.Add("bar", "hello2"); err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } // Check that changing file invalidate cache of "ADD ." if err := ctx.Add("foo", "hello1"); err != nil { - t.Fatal(err) + c.Fatal(err) } id3, err := buildImageFromContext(name3, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id2 == id3 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } // Check that changing file to same content invalidate cache of "ADD ." time.Sleep(1 * time.Second) // wait second because of mtime precision if err := ctx.Add("foo", "hello1"); err != nil { - t.Fatal(err) + c.Fatal(err) } id4, err := buildImageFromContext(name4, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id3 == id4 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } id5, err := buildImageFromContext(name5, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id4 != id5 { - t.Fatal("The cache should have been used but hasn't.") + c.Fatal("The cache should have been used but hasn't.") } - logDone("build - add current directory with cache") } -func TestBuildADDCurrentDirWithoutCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDCurrentDirWithoutCache(c *check.C) { name := "testbuildaddcurrentdirwithoutcache" name2 := "testbuildaddcurrentdirwithoutcache2" defer deleteImages(name, name2) @@ -3056,30 +2961,29 @@ func TestBuildADDCurrentDirWithoutCache(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } - logDone("build - add current directory without cache") } -func TestBuildADDRemoteFileWithCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDRemoteFileWithCache(c *check.C) { name := "testbuildaddremotefilewithcache" defer deleteImages(name) server, err := fakeStorage(map[string]string{ "baz": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -3089,7 +2993,7 @@ func TestBuildADDRemoteFileWithCache(t *testing.T) { ADD %s/baz /usr/lib/baz/quux`, server.URL()), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImage(name, fmt.Sprintf(`FROM scratch @@ -3097,15 +3001,14 @@ func TestBuildADDRemoteFileWithCache(t *testing.T) { ADD %s/baz /usr/lib/baz/quux`, server.URL()), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but hasn't.") + c.Fatal("The cache should have been used but hasn't.") } - logDone("build - add remote file with cache") } -func TestBuildADDRemoteFileWithoutCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDRemoteFileWithoutCache(c *check.C) { name := "testbuildaddremotefilewithoutcache" name2 := "testbuildaddremotefilewithoutcache2" defer deleteImages(name, name2) @@ -3113,7 +3016,7 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) { "baz": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -3123,7 +3026,7 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) { ADD %s/baz /usr/lib/baz/quux`, server.URL()), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImage(name2, fmt.Sprintf(`FROM scratch @@ -3131,15 +3034,14 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) { ADD %s/baz /usr/lib/baz/quux`, server.URL()), false) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } - logDone("build - add remote file without cache") } -func TestBuildADDRemoteFileMTime(t *testing.T) { +func (s *DockerSuite) TestBuildADDRemoteFileMTime(c *check.C) { name := "testbuildaddremotefilemtime" name2 := name + "2" name3 := name + "3" @@ -3150,7 +3052,7 @@ func TestBuildADDRemoteFileMTime(t *testing.T) { files := map[string]string{"baz": "hello"} server, err := fakeStorage(files) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -3158,21 +3060,21 @@ func TestBuildADDRemoteFileMTime(t *testing.T) { MAINTAINER dockerio ADD %s/baz /usr/lib/baz/quux`, server.URL()), nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but wasn't - #1") + c.Fatal("The cache should have been used but wasn't - #1") } // Now create a different server withsame contents (causes different mtim) @@ -3183,7 +3085,7 @@ func TestBuildADDRemoteFileMTime(t *testing.T) { server2, err := fakeStorage(files) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server2.Close() @@ -3191,36 +3093,35 @@ func TestBuildADDRemoteFileMTime(t *testing.T) { MAINTAINER dockerio ADD %s/baz /usr/lib/baz/quux`, server2.URL()), nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx2.Close() id3, err := buildImageFromContext(name3, ctx2, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id3 { - t.Fatal("The cache should not have been used but was") + c.Fatal("The cache should not have been used but was") } // And for good measure do it again and make sure cache is used this time id4, err := buildImageFromContext(name4, ctx2, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id3 != id4 { - t.Fatal("The cache should have been used but wasn't - #2") + c.Fatal("The cache should have been used but wasn't - #2") } - logDone("build - add remote file testing mtime") } -func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDLocalAndRemoteFilesWithCache(c *check.C) { name := "testbuildaddlocalandremotefilewithcache" defer deleteImages(name) server, err := fakeStorage(map[string]string{ "baz": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -3232,24 +3133,23 @@ func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) { "foo": "hello world", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 != id2 { - t.Fatal("The cache should have been used but hasn't.") + c.Fatal("The cache should have been used but hasn't.") } - logDone("build - add local and remote file with cache") } -func testContextTar(t *testing.T, compression archive.Compression) { +func testContextTar(c *check.C, compression archive.Compression) { ctx, err := fakeContext( `FROM busybox ADD foo /foo @@ -3260,11 +3160,11 @@ CMD ["cat", "/foo"]`, ) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } context, err := archive.Tar(ctx.Dir, compression) if err != nil { - t.Fatalf("failed to build context tar: %v", err) + c.Fatalf("failed to build context tar: %v", err) } name := "contexttar" buildCmd := exec.Command(dockerBinary, "build", "-t", name, "-") @@ -3272,38 +3172,35 @@ CMD ["cat", "/foo"]`, buildCmd.Stdin = context if out, _, err := runCommandWithOutput(buildCmd); err != nil { - t.Fatalf("build failed to complete: %v %v", out, err) + c.Fatalf("build failed to complete: %v %v", out, err) } } -func TestBuildContextTarGzip(t *testing.T) { - testContextTar(t, archive.Gzip) - logDone(fmt.Sprintf("build - build an image with a context tar, compression: %v", archive.Gzip)) +func (s *DockerSuite) TestBuildContextTarGzip(c *check.C) { + testContextTar(c, archive.Gzip) } -func TestBuildContextTarNoCompression(t *testing.T) { - testContextTar(t, archive.Uncompressed) - logDone(fmt.Sprintf("build - build an image with a context tar, compression: %v", archive.Uncompressed)) +func (s *DockerSuite) TestBuildContextTarNoCompression(c *check.C) { + testContextTar(c, archive.Uncompressed) } -func TestBuildNoContext(t *testing.T) { +func (s *DockerSuite) TestBuildNoContext(c *check.C) { buildCmd := exec.Command(dockerBinary, "build", "-t", "nocontext", "-") buildCmd.Stdin = strings.NewReader("FROM busybox\nCMD echo ok\n") if out, _, err := runCommandWithOutput(buildCmd); err != nil { - t.Fatalf("build failed to complete: %v %v", out, err) + c.Fatalf("build failed to complete: %v %v", out, err) } - if out, _ := dockerCmd(t, "run", "--rm", "nocontext"); out != "ok\n" { - t.Fatalf("run produced invalid output: %q, expected %q", out, "ok") + if out, _ := dockerCmd(c, "run", "--rm", "nocontext"); out != "ok\n" { + c.Fatalf("run produced invalid output: %q, expected %q", out, "ok") } deleteImages("nocontext") - logDone("build - build an image with no context") } // TODO: TestCaching -func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) { +func (s *DockerSuite) TestBuildADDLocalAndRemoteFilesWithoutCache(c *check.C) { name := "testbuildaddlocalandremotefilewithoutcache" name2 := "testbuildaddlocalandremotefilewithoutcache2" defer deleteImages(name, name2) @@ -3311,7 +3208,7 @@ func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) { "baz": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -3323,24 +3220,23 @@ func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) { "foo": "hello world", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() id1, err := buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } id2, err := buildImageFromContext(name2, ctx, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("The cache should have been invalided but hasn't.") + c.Fatal("The cache should have been invalided but hasn't.") } - logDone("build - add local and remote file without cache") } -func TestBuildWithVolumeOwnership(t *testing.T) { +func (s *DockerSuite) TestBuildWithVolumeOwnership(c *check.C) { name := "testbuildimg" defer deleteImages(name) @@ -3351,36 +3247,35 @@ func TestBuildWithVolumeOwnership(t *testing.T) { true) if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "--rm", "testbuildimg", "ls", "-la", "/test") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if expected := "drw-------"; !strings.Contains(out, expected) { - t.Fatalf("expected %s received %s", expected, out) + c.Fatalf("expected %s received %s", expected, out) } if expected := "daemon daemon"; !strings.Contains(out, expected) { - t.Fatalf("expected %s received %s", expected, out) + c.Fatalf("expected %s received %s", expected, out) } - logDone("build - volume ownership") } // testing #1405 - config.Cmd does not get cleaned up if // utilizing cache -func TestBuildEntrypointRunCleanup(t *testing.T) { +func (s *DockerSuite) TestBuildEntrypointRunCleanup(c *check.C) { name := "testbuildcmdcleanup" defer deleteImages(name) if _, err := buildImage(name, `FROM busybox RUN echo "hello"`, true); err != nil { - t.Fatal(err) + c.Fatal(err) } ctx, err := fakeContext(`FROM busybox @@ -3392,23 +3287,22 @@ func TestBuildEntrypointRunCleanup(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Cmd") if err != nil { - t.Fatal(err) + c.Fatal(err) } // Cmd must be cleaned up if expected := ""; res != expected { - t.Fatalf("Cmd %s, expected %s", res, expected) + c.Fatalf("Cmd %s, expected %s", res, expected) } - logDone("build - cleanup cmd after RUN") } -func TestBuildForbiddenContextPath(t *testing.T) { +func (s *DockerSuite) TestBuildForbiddenContextPath(c *check.C) { name := "testbuildforbidpath" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -3420,18 +3314,17 @@ func TestBuildForbiddenContextPath(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "Forbidden path outside the build context: ../../ " if _, err := buildImageFromContext(name, ctx, true); err == nil || !strings.Contains(err.Error(), expected) { - t.Fatalf("Wrong error: (should contain \"%s\") got:\n%v", expected, err) + c.Fatalf("Wrong error: (should contain \"%s\") got:\n%v", expected, err) } - logDone("build - forbidden context path") } -func TestBuildADDFileNotFound(t *testing.T) { +func (s *DockerSuite) TestBuildADDFileNotFound(c *check.C) { name := "testbuildaddnotfound" defer deleteImages(name) ctx, err := fakeContext(`FROM scratch @@ -3439,19 +3332,18 @@ func TestBuildADDFileNotFound(t *testing.T) { map[string]string{"bar": "hello"}) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { if !strings.Contains(err.Error(), "foo: no such file or directory") { - t.Fatalf("Wrong error %v, must be about missing foo file or directory", err) + c.Fatalf("Wrong error %v, must be about missing foo file or directory", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - add file not found") } -func TestBuildInheritance(t *testing.T) { +func (s *DockerSuite) TestBuildInheritance(c *check.C) { name := "testbuildinheritance" defer deleteImages(name) @@ -3460,11 +3352,11 @@ func TestBuildInheritance(t *testing.T) { EXPOSE 2375`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } ports1, err := inspectField(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name, @@ -3472,59 +3364,55 @@ func TestBuildInheritance(t *testing.T) { ENTRYPOINT ["/bin/echo"]`, name), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Entrypoint") if err != nil { - t.Fatal(err) + c.Fatal(err) } if expected := "[/bin/echo]"; res != expected { - t.Fatalf("Entrypoint %s, expected %s", res, expected) + c.Fatalf("Entrypoint %s, expected %s", res, expected) } ports2, err := inspectField(name, "Config.ExposedPorts") if err != nil { - t.Fatal(err) + c.Fatal(err) } if ports1 != ports2 { - t.Fatalf("Ports must be same: %s != %s", ports1, ports2) + c.Fatalf("Ports must be same: %s != %s", ports1, ports2) } - logDone("build - inheritance") } -func TestBuildFails(t *testing.T) { +func (s *DockerSuite) TestBuildFails(c *check.C) { name := "testbuildfails" defer deleteImages(name) - defer deleteAllContainers() _, err := buildImage(name, `FROM busybox RUN sh -c "exit 23"`, true) if err != nil { if !strings.Contains(err.Error(), "returned a non-zero code: 23") { - t.Fatalf("Wrong error %v, must be about non-zero code 23", err) + c.Fatalf("Wrong error %v, must be about non-zero code 23", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - unsuccessful") } -func TestBuildFailsDockerfileEmpty(t *testing.T) { +func (s *DockerSuite) TestBuildFailsDockerfileEmpty(c *check.C) { name := "testbuildfails" defer deleteImages(name) _, err := buildImage(name, ``, true) if err != nil { if !strings.Contains(err.Error(), "The Dockerfile (Dockerfile) cannot be empty") { - t.Fatalf("Wrong error %v, must be about empty Dockerfile", err) + c.Fatalf("Wrong error %v, must be about empty Dockerfile", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - unsuccessful with empty dockerfile") } -func TestBuildOnBuild(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuild(c *check.C) { name := "testbuildonbuild" defer deleteImages(name) _, err := buildImage(name, @@ -3532,19 +3420,18 @@ func TestBuildOnBuild(t *testing.T) { ONBUILD RUN touch foobar`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } _, err = buildImage(name, fmt.Sprintf(`FROM %s RUN [ -f foobar ]`, name), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - onbuild") } -func TestBuildOnBuildForbiddenChained(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildForbiddenChained(c *check.C) { name := "testbuildonbuildforbiddenchained" defer deleteImages(name) _, err := buildImage(name, @@ -3553,15 +3440,14 @@ func TestBuildOnBuildForbiddenChained(t *testing.T) { true) if err != nil { if !strings.Contains(err.Error(), "Chaining ONBUILD via `ONBUILD ONBUILD` isn't allowed") { - t.Fatalf("Wrong error %v, must be about chaining ONBUILD", err) + c.Fatalf("Wrong error %v, must be about chaining ONBUILD", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - onbuild forbidden chained") } -func TestBuildOnBuildForbiddenFrom(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildForbiddenFrom(c *check.C) { name := "testbuildonbuildforbiddenfrom" defer deleteImages(name) _, err := buildImage(name, @@ -3570,15 +3456,14 @@ func TestBuildOnBuildForbiddenFrom(t *testing.T) { true) if err != nil { if !strings.Contains(err.Error(), "FROM isn't allowed as an ONBUILD trigger") { - t.Fatalf("Wrong error %v, must be about FROM forbidden", err) + c.Fatalf("Wrong error %v, must be about FROM forbidden", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - onbuild forbidden from") } -func TestBuildOnBuildForbiddenMaintainer(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainer(c *check.C) { name := "testbuildonbuildforbiddenmaintainer" defer deleteImages(name) _, err := buildImage(name, @@ -3587,16 +3472,15 @@ func TestBuildOnBuildForbiddenMaintainer(t *testing.T) { true) if err != nil { if !strings.Contains(err.Error(), "MAINTAINER isn't allowed as an ONBUILD trigger") { - t.Fatalf("Wrong error %v, must be about MAINTAINER forbidden", err) + c.Fatalf("Wrong error %v, must be about MAINTAINER forbidden", err) } } else { - t.Fatal("Error must not be nil") + c.Fatal("Error must not be nil") } - logDone("build - onbuild forbidden maintainer") } // gh #2446 -func TestBuildAddToSymlinkDest(t *testing.T) { +func (s *DockerSuite) TestBuildAddToSymlinkDest(c *check.C) { name := "testbuildaddtosymlinkdest" defer deleteImages(name) ctx, err := fakeContext(`FROM busybox @@ -3609,16 +3493,15 @@ func TestBuildAddToSymlinkDest(t *testing.T) { "foo": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add to symlink destination") } -func TestBuildEscapeWhitespace(t *testing.T) { +func (s *DockerSuite) TestBuildEscapeWhitespace(c *check.C) { name := "testbuildescaping" defer deleteImages(name) @@ -3632,17 +3515,16 @@ docker.com>" res, err := inspectField(name, "Author") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != "\"Docker IO \"" { - t.Fatalf("Parsed string did not match the escaped string. Got: %q", res) + c.Fatalf("Parsed string did not match the escaped string. Got: %q", res) } - logDone("build - validate escaping whitespace") } -func TestBuildVerifyIntString(t *testing.T) { +func (s *DockerSuite) TestBuildVerifyIntString(c *check.C) { // Verify that strings that look like ints are still passed as strings name := "testbuildstringing" defer deleteImages(name) @@ -3654,17 +3536,16 @@ func TestBuildVerifyIntString(t *testing.T) { out, rc, err := runCommandWithOutput(exec.Command(dockerBinary, "inspect", name)) if rc != 0 || err != nil { - t.Fatalf("Unexcepted error from inspect: rc: %v err: %v", rc, err) + c.Fatalf("Unexcepted error from inspect: rc: %v err: %v", rc, err) } if !strings.Contains(out, "\"123\"") { - t.Fatalf("Output does not contain the int as a string:\n%s", out) + c.Fatalf("Output does not contain the int as a string:\n%s", out) } - logDone("build - verify int/strings as strings") } -func TestBuildDockerignore(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignore(c *check.C) { name := "testbuilddockerignore" defer deleteImages(name) dockerfile := ` @@ -3687,15 +3568,14 @@ func TestBuildDockerignore(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - test .dockerignore") } -func TestBuildDockerignoreCleanPaths(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignoreCleanPaths(c *check.C) { name := "testbuilddockerignorecleanpaths" defer deleteImages(name) dockerfile := ` @@ -3709,16 +3589,15 @@ func TestBuildDockerignoreCleanPaths(t *testing.T) { ".dockerignore": "./foo\ndir1//foo\n./dir1/../foo2", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - test .dockerignore with clean paths") } -func TestBuildDockerignoringDockerfile(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignoringDockerfile(c *check.C) { name := "testbuilddockerignoredockerfile" defer deleteImages(name) dockerfile := ` @@ -3731,24 +3610,23 @@ func TestBuildDockerignoringDockerfile(t *testing.T) { ".dockerignore": "Dockerfile\n", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't ignore Dockerfile correctly:%s", err) + c.Fatalf("Didn't ignore Dockerfile correctly:%s", err) } // now try it with ./Dockerfile ctx.Add(".dockerignore", "./Dockerfile\n") if _, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't ignore ./Dockerfile correctly:%s", err) + c.Fatalf("Didn't ignore ./Dockerfile correctly:%s", err) } - logDone("build - test .dockerignore of Dockerfile") } -func TestBuildDockerignoringRenamedDockerfile(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignoringRenamedDockerfile(c *check.C) { name := "testbuilddockerignoredockerfile" defer deleteImages(name) dockerfile := ` @@ -3763,24 +3641,23 @@ func TestBuildDockerignoringRenamedDockerfile(t *testing.T) { ".dockerignore": "MyDockerfile\n", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't ignore MyDockerfile correctly:%s", err) + c.Fatalf("Didn't ignore MyDockerfile correctly:%s", err) } // now try it with ./MyDockerfile ctx.Add(".dockerignore", "./MyDockerfile\n") if _, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't ignore ./MyDockerfile correctly:%s", err) + c.Fatalf("Didn't ignore ./MyDockerfile correctly:%s", err) } - logDone("build - test .dockerignore of renamed Dockerfile") } -func TestBuildDockerignoringDockerignore(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignoringDockerignore(c *check.C) { name := "testbuilddockerignoredockerignore" defer deleteImages(name) dockerfile := ` @@ -3794,15 +3671,14 @@ func TestBuildDockerignoringDockerignore(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't ignore .dockerignore correctly:%s", err) + c.Fatalf("Didn't ignore .dockerignore correctly:%s", err) } - logDone("build - test .dockerignore of .dockerignore") } -func TestBuildDockerignoreTouchDockerfile(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignoreTouchDockerfile(c *check.C) { var id1 string var id2 string @@ -3817,46 +3693,45 @@ func TestBuildDockerignoreTouchDockerfile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if id1, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't build it correctly:%s", err) + c.Fatalf("Didn't build it correctly:%s", err) } if id2, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't build it correctly:%s", err) + c.Fatalf("Didn't build it correctly:%s", err) } if id1 != id2 { - t.Fatalf("Didn't use the cache - 1") + c.Fatalf("Didn't use the cache - 1") } // Now make sure touching Dockerfile doesn't invalidate the cache if err = ctx.Add("Dockerfile", dockerfile+"\n# hi"); err != nil { - t.Fatalf("Didn't add Dockerfile: %s", err) + c.Fatalf("Didn't add Dockerfile: %s", err) } if id2, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't build it correctly:%s", err) + c.Fatalf("Didn't build it correctly:%s", err) } if id1 != id2 { - t.Fatalf("Didn't use the cache - 2") + c.Fatalf("Didn't use the cache - 2") } // One more time but just 'touch' it instead of changing the content if err = ctx.Add("Dockerfile", dockerfile+"\n# hi"); err != nil { - t.Fatalf("Didn't add Dockerfile: %s", err) + c.Fatalf("Didn't add Dockerfile: %s", err) } if id2, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("Didn't build it correctly:%s", err) + c.Fatalf("Didn't build it correctly:%s", err) } if id1 != id2 { - t.Fatalf("Didn't use the cache - 3") + c.Fatalf("Didn't use the cache - 3") } - logDone("build - test .dockerignore touch dockerfile") } -func TestBuildDockerignoringWholeDir(t *testing.T) { +func (s *DockerSuite) TestBuildDockerignoringWholeDir(c *check.C) { name := "testbuilddockerignorewholedir" defer deleteImages(name) dockerfile := ` @@ -3871,15 +3746,14 @@ func TestBuildDockerignoringWholeDir(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err = buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - test .dockerignore whole dir with .*") } -func TestBuildLineBreak(t *testing.T) { +func (s *DockerSuite) TestBuildLineBreak(c *check.C) { name := "testbuildlinebreak" defer deleteImages(name) _, err := buildImage(name, @@ -3891,12 +3765,11 @@ RUN [ "$(cat /tmp/passwd)" = "root:testpass" ] RUN [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - line break with \\") } -func TestBuildEOLInLine(t *testing.T) { +func (s *DockerSuite) TestBuildEOLInLine(c *check.C) { name := "testbuildeolinline" defer deleteImages(name) _, err := buildImage(name, @@ -3908,12 +3781,11 @@ RUN [ "$(cat /tmp/passwd)" = "root:testpass" ] RUN [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - end of line in dockerfile instruction") } -func TestBuildCommentsShebangs(t *testing.T) { +func (s *DockerSuite) TestBuildCommentsShebangs(c *check.C) { name := "testbuildcomments" defer deleteImages(name) _, err := buildImage(name, @@ -3928,12 +3800,11 @@ RUN [ "$(cat /hello.sh)" = $'#!/bin/sh\necho hello world' ] RUN [ "$(/hello.sh)" = "hello world" ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - comments and shebangs") } -func TestBuildUsersAndGroups(t *testing.T) { +func (s *DockerSuite) TestBuildUsersAndGroups(c *check.C) { name := "testbuildusers" defer deleteImages(name) _, err := buildImage(name, @@ -3992,12 +3863,11 @@ USER 1042:1043 RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1042:1043/1042:1043/1043:1043' ]`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - users and groups") } -func TestBuildEnvUsage(t *testing.T) { +func (s *DockerSuite) TestBuildEnvUsage(c *check.C) { name := "testbuildenvusage" defer deleteImages(name) dockerfile := `FROM busybox @@ -4023,18 +3893,17 @@ RUN [ "$ghi" = "def" ] "hello/docker/world": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() _, err = buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - environment variables usage") } -func TestBuildEnvUsage2(t *testing.T) { +func (s *DockerSuite) TestBuildEnvUsage2(c *check.C) { name := "testbuildenvusage2" defer deleteImages(name) dockerfile := `FROM busybox @@ -4127,18 +3996,17 @@ RUN [ "$eee1,$eee2,$eee3,$eee4" = 'foo,foo,foo,foo' ] "hello/docker/world": "hello", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() _, err = buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - environment variables usage2") } -func TestBuildAddScript(t *testing.T) { +func (s *DockerSuite) TestBuildAddScript(c *check.C) { name := "testbuildaddscript" defer deleteImages(name) dockerfile := ` @@ -4151,18 +4019,17 @@ RUN [ "$(cat /testfile)" = 'test!' ]` "test": "#!/bin/sh\necho 'test!' > /testfile", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() _, err = buildImageFromContext(name, ctx, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - add and run script") } -func TestBuildAddTar(t *testing.T) { +func (s *DockerSuite) TestBuildAddTar(c *check.C) { name := "testbuildaddtar" defer deleteImages(name) @@ -4185,7 +4052,7 @@ RUN cat /existing-directory-trailing-slash/test/foo | grep Hi` tmpDir, err := ioutil.TempDir("", "fake-context") testTar, err := os.Create(filepath.Join(tmpDir, "test.tar")) if err != nil { - t.Fatalf("failed to create test.tar archive: %v", err) + c.Fatalf("failed to create test.tar archive: %v", err) } defer testTar.Close() @@ -4195,30 +4062,29 @@ RUN cat /existing-directory-trailing-slash/test/foo | grep Hi` Name: "test/foo", Size: 2, }); err != nil { - t.Fatalf("failed to write tar file header: %v", err) + c.Fatalf("failed to write tar file header: %v", err) } if _, err := tw.Write([]byte("Hi")); err != nil { - t.Fatalf("failed to write tar file content: %v", err) + c.Fatalf("failed to write tar file content: %v", err) } if err := tw.Close(); err != nil { - t.Fatalf("failed to close tar archive: %v", err) + c.Fatalf("failed to close tar archive: %v", err) } if err := ioutil.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644); err != nil { - t.Fatalf("failed to open destination dockerfile: %v", err) + c.Fatalf("failed to open destination dockerfile: %v", err) } return fakeContextFromDir(tmpDir) }() defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("build failed to complete for TestBuildAddTar: %v", err) + c.Fatalf("build failed to complete for TestBuildAddTar: %v", err) } - logDone("build - ADD tar") } -func TestBuildAddTarXz(t *testing.T) { +func (s *DockerSuite) TestBuildAddTarXz(c *check.C) { name := "testbuildaddtarxz" defer deleteImages(name) @@ -4230,7 +4096,7 @@ func TestBuildAddTarXz(t *testing.T) { tmpDir, err := ioutil.TempDir("", "fake-context") testTar, err := os.Create(filepath.Join(tmpDir, "test.tar")) if err != nil { - t.Fatalf("failed to create test.tar archive: %v", err) + c.Fatalf("failed to create test.tar archive: %v", err) } defer testTar.Close() @@ -4240,23 +4106,23 @@ func TestBuildAddTarXz(t *testing.T) { Name: "test/foo", Size: 2, }); err != nil { - t.Fatalf("failed to write tar file header: %v", err) + c.Fatalf("failed to write tar file header: %v", err) } if _, err := tw.Write([]byte("Hi")); err != nil { - t.Fatalf("failed to write tar file content: %v", err) + c.Fatalf("failed to write tar file content: %v", err) } if err := tw.Close(); err != nil { - t.Fatalf("failed to close tar archive: %v", err) + c.Fatalf("failed to close tar archive: %v", err) } xzCompressCmd := exec.Command("xz", "-k", "test.tar") xzCompressCmd.Dir = tmpDir out, _, err := runCommandWithOutput(xzCompressCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := ioutil.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644); err != nil { - t.Fatalf("failed to open destination dockerfile: %v", err) + c.Fatalf("failed to open destination dockerfile: %v", err) } return fakeContextFromDir(tmpDir) }() @@ -4264,13 +4130,12 @@ func TestBuildAddTarXz(t *testing.T) { defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("build failed to complete for TestBuildAddTarXz: %v", err) + c.Fatalf("build failed to complete for TestBuildAddTarXz: %v", err) } - logDone("build - ADD tar.xz") } -func TestBuildAddTarXzGz(t *testing.T) { +func (s *DockerSuite) TestBuildAddTarXzGz(c *check.C) { name := "testbuildaddtarxzgz" defer deleteImages(name) @@ -4282,7 +4147,7 @@ func TestBuildAddTarXzGz(t *testing.T) { tmpDir, err := ioutil.TempDir("", "fake-context") testTar, err := os.Create(filepath.Join(tmpDir, "test.tar")) if err != nil { - t.Fatalf("failed to create test.tar archive: %v", err) + c.Fatalf("failed to create test.tar archive: %v", err) } defer testTar.Close() @@ -4292,31 +4157,31 @@ func TestBuildAddTarXzGz(t *testing.T) { Name: "test/foo", Size: 2, }); err != nil { - t.Fatalf("failed to write tar file header: %v", err) + c.Fatalf("failed to write tar file header: %v", err) } if _, err := tw.Write([]byte("Hi")); err != nil { - t.Fatalf("failed to write tar file content: %v", err) + c.Fatalf("failed to write tar file content: %v", err) } if err := tw.Close(); err != nil { - t.Fatalf("failed to close tar archive: %v", err) + c.Fatalf("failed to close tar archive: %v", err) } xzCompressCmd := exec.Command("xz", "-k", "test.tar") xzCompressCmd.Dir = tmpDir out, _, err := runCommandWithOutput(xzCompressCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } gzipCompressCmd := exec.Command("gzip", "test.tar.xz") gzipCompressCmd.Dir = tmpDir out, _, err = runCommandWithOutput(gzipCompressCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := ioutil.WriteFile(filepath.Join(tmpDir, "Dockerfile"), []byte(dockerfile), 0644); err != nil { - t.Fatalf("failed to open destination dockerfile: %v", err) + c.Fatalf("failed to open destination dockerfile: %v", err) } return fakeContextFromDir(tmpDir) }() @@ -4324,13 +4189,12 @@ func TestBuildAddTarXzGz(t *testing.T) { defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatalf("build failed to complete for TestBuildAddTarXz: %v", err) + c.Fatalf("build failed to complete for TestBuildAddTarXz: %v", err) } - logDone("build - ADD tar.xz.gz") } -func TestBuildFromGIT(t *testing.T) { +func (s *DockerSuite) TestBuildFromGIT(c *check.C) { name := "testbuildfromgit" defer deleteImages(name) git, err := fakeGIT("repo", map[string]string{ @@ -4341,25 +4205,24 @@ func TestBuildFromGIT(t *testing.T) { "first": "test git data", }, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer git.Close() _, err = buildImageFromPath(name, git.RepoURL, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Author") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != "docker" { - t.Fatalf("Maintainer should be docker, got %s", res) + c.Fatalf("Maintainer should be docker, got %s", res) } - logDone("build - build from GIT") } -func TestBuildCleanupCmdOnEntrypoint(t *testing.T) { +func (s *DockerSuite) TestBuildCleanupCmdOnEntrypoint(c *check.C) { name := "testbuildcmdcleanuponentrypoint" defer deleteImages(name) if _, err := buildImage(name, @@ -4367,32 +4230,31 @@ func TestBuildCleanupCmdOnEntrypoint(t *testing.T) { CMD ["test"] ENTRYPOINT ["echo"]`, true); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImage(name, fmt.Sprintf(`FROM %s ENTRYPOINT ["cat"]`, name), true); err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectField(name, "Config.Cmd") if err != nil { - t.Fatal(err) + c.Fatal(err) } if expected := ""; res != expected { - t.Fatalf("Cmd %s, expected %s", res, expected) + c.Fatalf("Cmd %s, expected %s", res, expected) } res, err = inspectField(name, "Config.Entrypoint") if err != nil { - t.Fatal(err) + c.Fatal(err) } if expected := "[cat]"; res != expected { - t.Fatalf("Entrypoint %s, expected %s", res, expected) + c.Fatalf("Entrypoint %s, expected %s", res, expected) } - logDone("build - cleanup cmd on ENTRYPOINT") } -func TestBuildClearCmd(t *testing.T) { +func (s *DockerSuite) TestBuildClearCmd(c *check.C) { name := "testbuildclearcmd" defer deleteImages(name) _, err := buildImage(name, @@ -4401,39 +4263,37 @@ func TestBuildClearCmd(t *testing.T) { CMD []`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Cmd") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != "[]" { - t.Fatalf("Cmd %s, expected %s", res, "[]") + c.Fatalf("Cmd %s, expected %s", res, "[]") } - logDone("build - clearcmd") } -func TestBuildEmptyCmd(t *testing.T) { +func (s *DockerSuite) TestBuildEmptyCmd(c *check.C) { name := "testbuildemptycmd" defer deleteImages(name) if _, err := buildImage(name, "FROM scratch\nMAINTAINER quux\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Cmd") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != "null" { - t.Fatalf("Cmd %s, expected %s", res, "null") + c.Fatalf("Cmd %s, expected %s", res, "null") } - logDone("build - empty cmd") } -func TestBuildOnBuildOutput(t *testing.T) { +func (s *DockerSuite) TestBuildOnBuildOutput(c *check.C) { name := "testbuildonbuildparent" defer deleteImages(name) if _, err := buildImage(name, "FROM busybox\nONBUILD RUN echo foo\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } childname := "testbuildonbuildchild" @@ -4441,50 +4301,47 @@ func TestBuildOnBuildOutput(t *testing.T) { _, out, err := buildImageWithOut(name, "FROM "+name+"\nMAINTAINER quux\n", true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "Trigger 0, RUN echo foo") { - t.Fatal("failed to find the ONBUILD output", out) + c.Fatal("failed to find the ONBUILD output", out) } - logDone("build - onbuild output") } -func TestBuildInvalidTag(t *testing.T) { +func (s *DockerSuite) TestBuildInvalidTag(c *check.C) { name := "abcd:" + stringutils.GenerateRandomAlphaOnlyString(200) defer deleteImages(name) _, out, err := buildImageWithOut(name, "FROM scratch\nMAINTAINER quux\n", true) // if the error doesnt check for illegal tag name, or the image is built // then this should fail if !strings.Contains(out, "Illegal tag name") || strings.Contains(out, "Sending build context to Docker daemon") { - t.Fatalf("failed to stop before building. Error: %s, Output: %s", err, out) + c.Fatalf("failed to stop before building. Error: %s, Output: %s", err, out) } - logDone("build - invalid tag") } -func TestBuildCmdShDashC(t *testing.T) { +func (s *DockerSuite) TestBuildCmdShDashC(c *check.C) { name := "testbuildcmdshc" defer deleteImages(name) if _, err := buildImage(name, "FROM busybox\nCMD echo cmd\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Cmd") if err != nil { - t.Fatal(err, res) + c.Fatal(err, res) } expected := `["/bin/sh","-c","echo cmd"]` if res != expected { - t.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res) + c.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res) } - logDone("build - cmd should have sh -c for non-json") } -func TestBuildCmdSpaces(t *testing.T) { +func (s *DockerSuite) TestBuildCmdSpaces(c *check.C) { // Test to make sure that when we strcat arrays we take into account // the arg separator to make sure ["echo","hi"] and ["echo hi"] don't // look the same @@ -4495,100 +4352,95 @@ func TestBuildCmdSpaces(t *testing.T) { var err error if id1, err = buildImage(name, "FROM busybox\nCMD [\"echo hi\"]\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } if id2, err = buildImage(name, "FROM busybox\nCMD [\"echo\", \"hi\"]\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("Should not have resulted in the same CMD") + c.Fatal("Should not have resulted in the same CMD") } // Now do the same with ENTRYPOINT if id1, err = buildImage(name, "FROM busybox\nENTRYPOINT [\"echo hi\"]\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } if id2, err = buildImage(name, "FROM busybox\nENTRYPOINT [\"echo\", \"hi\"]\n", true); err != nil { - t.Fatal(err) + c.Fatal(err) } if id1 == id2 { - t.Fatal("Should not have resulted in the same ENTRYPOINT") + c.Fatal("Should not have resulted in the same ENTRYPOINT") } - logDone("build - cmd with spaces") } -func TestBuildCmdJSONNoShDashC(t *testing.T) { +func (s *DockerSuite) TestBuildCmdJSONNoShDashC(c *check.C) { name := "testbuildcmdjson" defer deleteImages(name) if _, err := buildImage(name, "FROM busybox\nCMD [\"echo\", \"cmd\"]", true); err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Cmd") if err != nil { - t.Fatal(err, res) + c.Fatal(err, res) } expected := `["echo","cmd"]` if res != expected { - t.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res) + c.Fatalf("Expected value %s not in Config.Cmd: %s", expected, res) } - logDone("build - cmd should not have /bin/sh -c for json") } -func TestBuildErrorInvalidInstruction(t *testing.T) { +func (s *DockerSuite) TestBuildErrorInvalidInstruction(c *check.C) { name := "testbuildignoreinvalidinstruction" defer deleteImages(name) out, _, err := buildImageWithOut(name, "FROM busybox\nfoo bar", true) if err == nil { - t.Fatalf("Should have failed: %s", out) + c.Fatalf("Should have failed: %s", out) } - logDone("build - error invalid Dockerfile instruction") } -func TestBuildEntrypointInheritance(t *testing.T) { +func (s *DockerSuite) TestBuildEntrypointInheritance(c *check.C) { defer deleteImages("parent", "child") - defer deleteAllContainers() if _, err := buildImage("parent", ` FROM busybox ENTRYPOINT exit 130 `, true); err != nil { - t.Fatal(err) + c.Fatal(err) } status, _ := runCommand(exec.Command(dockerBinary, "run", "parent")) if status != 130 { - t.Fatalf("expected exit code 130 but received %d", status) + c.Fatalf("expected exit code 130 but received %d", status) } if _, err := buildImage("child", ` FROM parent ENTRYPOINT exit 5 `, true); err != nil { - t.Fatal(err) + c.Fatal(err) } status, _ = runCommand(exec.Command(dockerBinary, "run", "child")) if status != 5 { - t.Fatalf("expected exit code 5 but received %d", status) + c.Fatalf("expected exit code 5 but received %d", status) } - logDone("build - clear entrypoint") } -func TestBuildEntrypointInheritanceInspect(t *testing.T) { +func (s *DockerSuite) TestBuildEntrypointInheritanceInspect(c *check.C) { var ( name = "testbuildepinherit" name2 = "testbuildepinherit2" @@ -4596,40 +4448,38 @@ func TestBuildEntrypointInheritanceInspect(t *testing.T) { ) defer deleteImages(name, name2) - defer deleteAllContainers() if _, err := buildImage(name, "FROM busybox\nENTRYPOINT /foo/bar", true); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImage(name2, fmt.Sprintf("FROM %s\nENTRYPOINT echo quux", name), true); err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name2, "Config.Entrypoint") if err != nil { - t.Fatal(err, res) + c.Fatal(err, res) } if res != expected { - t.Fatalf("Expected value %s not in Config.Entrypoint: %s", expected, res) + c.Fatalf("Expected value %s not in Config.Entrypoint: %s", expected, res) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-t", name2)) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } expected = "quux" if strings.TrimSpace(out) != expected { - t.Fatalf("Expected output is %s, got %s", expected, out) + c.Fatalf("Expected output is %s, got %s", expected, out) } - logDone("build - entrypoint override inheritance properly") } -func TestBuildRunShEntrypoint(t *testing.T) { +func (s *DockerSuite) TestBuildRunShEntrypoint(c *check.C) { name := "testbuildentrypoint" defer deleteImages(name) _, err := buildImage(name, @@ -4637,19 +4487,18 @@ func TestBuildRunShEntrypoint(t *testing.T) { ENTRYPOINT /bin/echo`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", name)) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - logDone("build - entrypoint with /bin/echo running successfully") } -func TestBuildExoticShellInterpolation(t *testing.T) { +func (s *DockerSuite) TestBuildExoticShellInterpolation(c *check.C) { name := "testbuildexoticshellinterpolation" defer deleteImages(name) @@ -4673,13 +4522,12 @@ func TestBuildExoticShellInterpolation(t *testing.T) { RUN [ "${SOME_UNSET_VAR:-${SOME_VAR:-d.e.f}}" = 'a.b.c' ] `, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - exotic shell interpolation") } -func TestBuildVerifySingleQuoteFails(t *testing.T) { +func (s *DockerSuite) TestBuildVerifySingleQuoteFails(c *check.C) { // This testcase is supposed to generate an error because the // JSON array we're passing in on the CMD uses single quotes instead // of double quotes (per the JSON spec). This means we interpret it @@ -4687,7 +4535,6 @@ func TestBuildVerifySingleQuoteFails(t *testing.T) { // it should barf on it. name := "testbuildsinglequotefails" defer deleteImages(name) - defer deleteAllContainers() _, err := buildImage(name, `FROM busybox @@ -4696,13 +4543,12 @@ func TestBuildVerifySingleQuoteFails(t *testing.T) { _, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", name)) if err == nil { - t.Fatal("The image was not supposed to be able to run") + c.Fatal("The image was not supposed to be able to run") } - logDone("build - verify single quotes break the build") } -func TestBuildVerboseOut(t *testing.T) { +func (s *DockerSuite) TestBuildVerboseOut(c *check.C) { name := "testbuildverboseout" defer deleteImages(name) @@ -4712,36 +4558,34 @@ RUN echo 123`, false) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "\n123\n") { - t.Fatalf("Output should contain %q: %q", "123", out) + c.Fatalf("Output should contain %q: %q", "123", out) } - logDone("build - verbose output from commands") } -func TestBuildWithTabs(t *testing.T) { +func (s *DockerSuite) TestBuildWithTabs(c *check.C) { name := "testbuildwithtabs" defer deleteImages(name) _, err := buildImage(name, "FROM busybox\nRUN echo\tone\t\ttwo", true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "ContainerConfig.Cmd") if err != nil { - t.Fatal(err) + c.Fatal(err) } expected1 := `["/bin/sh","-c","echo\tone\t\ttwo"]` expected2 := `["/bin/sh","-c","echo\u0009one\u0009\u0009two"]` // syntactically equivalent, and what Go 1.3 generates if res != expected1 && res != expected2 { - t.Fatalf("Missing tabs.\nGot: %s\nExp: %s or %s", res, expected1, expected2) + c.Fatalf("Missing tabs.\nGot: %s\nExp: %s or %s", res, expected1, expected2) } - logDone("build - with tabs") } -func TestBuildLabels(t *testing.T) { +func (s *DockerSuite) TestBuildLabels(c *check.C) { name := "testbuildlabel" expected := `{"License":"GPL","Vendor":"Acme"}` defer deleteImages(name) @@ -4751,19 +4595,18 @@ func TestBuildLabels(t *testing.T) { LABEL License GPL`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } res, err := inspectFieldJSON(name, "Config.Labels") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != expected { - t.Fatalf("Labels %s, expected %s", res, expected) + c.Fatalf("Labels %s, expected %s", res, expected) } - logDone("build - label") } -func TestBuildLabelsCache(t *testing.T) { +func (s *DockerSuite) TestBuildLabelsCache(c *check.C) { name := "testbuildlabelcache" defer deleteImages(name) @@ -4771,28 +4614,28 @@ func TestBuildLabelsCache(t *testing.T) { `FROM busybox LABEL Vendor=Acme`, false) if err != nil { - t.Fatalf("Build 1 should have worked: %v", err) + c.Fatalf("Build 1 should have worked: %v", err) } id2, err := buildImage(name, `FROM busybox LABEL Vendor=Acme`, true) if err != nil || id1 != id2 { - t.Fatalf("Build 2 should have worked & used cache(%s,%s): %v", id1, id2, err) + c.Fatalf("Build 2 should have worked & used cache(%s,%s): %v", id1, id2, err) } id2, err = buildImage(name, `FROM busybox LABEL Vendor=Acme1`, true) if err != nil || id1 == id2 { - t.Fatalf("Build 3 should have worked & NOT used cache(%s,%s): %v", id1, id2, err) + c.Fatalf("Build 3 should have worked & NOT used cache(%s,%s): %v", id1, id2, err) } id2, err = buildImage(name, `FROM busybox LABEL Vendor Acme`, true) // Note: " " and "=" should be same if err != nil || id1 != id2 { - t.Fatalf("Build 4 should have worked & used cache(%s,%s): %v", id1, id2, err) + c.Fatalf("Build 4 should have worked & used cache(%s,%s): %v", id1, id2, err) } // Now make sure the cache isn't used by mistake @@ -4800,20 +4643,19 @@ func TestBuildLabelsCache(t *testing.T) { `FROM busybox LABEL f1=b1 f2=b2`, false) if err != nil { - t.Fatalf("Build 5 should have worked: %q", err) + c.Fatalf("Build 5 should have worked: %q", err) } id2, err = buildImage(name, `FROM busybox LABEL f1="b1 f2=b2"`, true) if err != nil || id1 == id2 { - t.Fatalf("Build 6 should have worked & NOT used the cache(%s,%s): %q", id1, id2, err) + c.Fatalf("Build 6 should have worked & NOT used the cache(%s,%s): %q", id1, id2, err) } - logDone("build - label cache") } -func TestBuildStderr(t *testing.T) { +func (s *DockerSuite) TestBuildStderr(c *check.C) { // This test just makes sure that no non-error output goes // to stderr name := "testbuildstderr" @@ -4821,7 +4663,7 @@ func TestBuildStderr(t *testing.T) { _, _, stderr, err := buildImageWithStdoutStderr(name, "FROM busybox\nRUN echo one", true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if runtime.GOOS == "windows" { @@ -4829,19 +4671,18 @@ func TestBuildStderr(t *testing.T) { lines := strings.Split(stderr, "\n") for _, v := range lines { if v != "" && !strings.Contains(v, "SECURITY WARNING:") { - t.Fatalf("Stderr contains unexpected output line: %q", v) + c.Fatalf("Stderr contains unexpected output line: %q", v) } } } else { if stderr != "" { - t.Fatalf("Stderr should have been empty, instead its: %q", stderr) + c.Fatalf("Stderr should have been empty, instead its: %q", stderr) } } - logDone("build - testing stderr") } -func TestBuildChownSingleFile(t *testing.T) { - testRequires(t, UnixCli) // test uses chown: not available on windows +func (s *DockerSuite) TestBuildChownSingleFile(c *check.C) { + testRequires(c, UnixCli) // test uses chown: not available on windows name := "testbuildchownsinglefile" defer deleteImages(name) @@ -4855,46 +4696,45 @@ RUN [ $(ls -l /test | awk '{print $3":"$4}') = 'root:root' ] "test": "test", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if err := os.Chown(filepath.Join(ctx.Dir, "test"), 4242, 4242); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - change permission on single file") } -func TestBuildSymlinkBreakout(t *testing.T) { +func (s *DockerSuite) TestBuildSymlinkBreakout(c *check.C) { name := "testbuildsymlinkbreakout" tmpdir, err := ioutil.TempDir("", name) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpdir) ctx := filepath.Join(tmpdir, "context") if err := os.MkdirAll(ctx, 0755); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := ioutil.WriteFile(filepath.Join(ctx, "Dockerfile"), []byte(` from busybox add symlink.tar / add inject /symlink/ `), 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } inject := filepath.Join(ctx, "inject") if err := ioutil.WriteFile(inject, nil, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } f, err := os.Create(filepath.Join(ctx, "symlink.tar")) if err != nil { - t.Fatal(err) + c.Fatal(err) } w := tar.NewWriter(f) w.WriteHeader(&tar.Header{ @@ -4914,17 +4754,16 @@ func TestBuildSymlinkBreakout(t *testing.T) { w.Close() f.Close() if _, err := buildImageFromContext(name, fakeContextFromDir(ctx), false); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := os.Lstat(filepath.Join(tmpdir, "inject")); err == nil { - t.Fatal("symlink breakout - inject") + c.Fatal("symlink breakout - inject") } else if !os.IsNotExist(err) { - t.Fatalf("unexpected error: %v", err) + c.Fatalf("unexpected error: %v", err) } - logDone("build - symlink breakout") } -func TestBuildXZHost(t *testing.T) { +func (s *DockerSuite) TestBuildXZHost(c *check.C) { name := "testbuildxzhost" defer deleteImages(name) @@ -4942,18 +4781,17 @@ RUN [ ! -e /injected ]`, }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("build - xz host is being used") } -func TestBuildVolumesRetainContents(t *testing.T) { +func (s *DockerSuite) TestBuildVolumesRetainContents(c *check.C) { var ( name = "testbuildvolumescontent" expected = "some text" @@ -4968,27 +4806,25 @@ CMD cat /foo/file`, "content": expected, }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err := buildImageFromContext(name, ctx, false); err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", name)) if err != nil { - t.Fatal(err) + c.Fatal(err) } if out != expected { - t.Fatalf("expected file contents for /foo/file to be %q but received %q", expected, out) + c.Fatalf("expected file contents for /foo/file to be %q but received %q", expected, out) } - logDone("build - volumes retain contents in build") } -func TestBuildRenamedDockerfile(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestBuildRenamedDockerfile(c *check.C) { ctx, err := fakeContext(`FROM busybox RUN echo from Dockerfile`, @@ -5001,99 +4837,98 @@ func TestBuildRenamedDockerfile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", "test1", ".") if err != nil { - t.Fatalf("Failed to build: %s\n%s", out, err) + c.Fatalf("Failed to build: %s\n%s", out, err) } if !strings.Contains(out, "from Dockerfile") { - t.Fatalf("test1 should have used Dockerfile, output:%s", out) + c.Fatalf("test1 should have used Dockerfile, output:%s", out) } - out, _, err = dockerCmdInDir(t, ctx.Dir, "build", "-f", filepath.Join("files", "Dockerfile"), "-t", "test2", ".") + out, _, err = dockerCmdInDir(c, ctx.Dir, "build", "-f", filepath.Join("files", "Dockerfile"), "-t", "test2", ".") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "from files/Dockerfile") { - t.Fatalf("test2 should have used files/Dockerfile, output:%s", out) + c.Fatalf("test2 should have used files/Dockerfile, output:%s", out) } - out, _, err = dockerCmdInDir(t, ctx.Dir, "build", fmt.Sprintf("--file=%s", filepath.Join("files", "dFile")), "-t", "test3", ".") + out, _, err = dockerCmdInDir(c, ctx.Dir, "build", fmt.Sprintf("--file=%s", filepath.Join("files", "dFile")), "-t", "test3", ".") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "from files/dFile") { - t.Fatalf("test3 should have used files/dFile, output:%s", out) + c.Fatalf("test3 should have used files/dFile, output:%s", out) } - out, _, err = dockerCmdInDir(t, ctx.Dir, "build", "--file=dFile", "-t", "test4", ".") + out, _, err = dockerCmdInDir(c, ctx.Dir, "build", "--file=dFile", "-t", "test4", ".") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, "from dFile") { - t.Fatalf("test4 should have used dFile, output:%s", out) + c.Fatalf("test4 should have used dFile, output:%s", out) } dirWithNoDockerfile, _ := ioutil.TempDir(os.TempDir(), "test5") nonDockerfileFile := filepath.Join(dirWithNoDockerfile, "notDockerfile") if _, err = os.Create(nonDockerfileFile); err != nil { - t.Fatal(err) + c.Fatal(err) } - out, _, err = dockerCmdInDir(t, ctx.Dir, "build", fmt.Sprintf("--file=%s", nonDockerfileFile), "-t", "test5", ".") + out, _, err = dockerCmdInDir(c, ctx.Dir, "build", fmt.Sprintf("--file=%s", nonDockerfileFile), "-t", "test5", ".") if err == nil { - t.Fatalf("test5 was supposed to fail to find passwd") + c.Fatalf("test5 was supposed to fail to find passwd") } if expected := fmt.Sprintf("The Dockerfile (%s) must be within the build context (.)", strings.Replace(nonDockerfileFile, `\`, `\\`, -1)); !strings.Contains(out, expected) { - t.Fatalf("wrong error messsage:%v\nexpected to contain=%v", out, expected) + c.Fatalf("wrong error messsage:%v\nexpected to contain=%v", out, expected) } - out, _, err = dockerCmdInDir(t, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join("..", "Dockerfile"), "-t", "test6", "..") + out, _, err = dockerCmdInDir(c, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join("..", "Dockerfile"), "-t", "test6", "..") if err != nil { - t.Fatalf("test6 failed: %s", err) + c.Fatalf("test6 failed: %s", err) } if !strings.Contains(out, "from Dockerfile") { - t.Fatalf("test6 should have used root Dockerfile, output:%s", out) + c.Fatalf("test6 should have used root Dockerfile, output:%s", out) } - out, _, err = dockerCmdInDir(t, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join(ctx.Dir, "files", "Dockerfile"), "-t", "test7", "..") + out, _, err = dockerCmdInDir(c, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join(ctx.Dir, "files", "Dockerfile"), "-t", "test7", "..") if err != nil { - t.Fatalf("test7 failed: %s", err) + c.Fatalf("test7 failed: %s", err) } if !strings.Contains(out, "from files/Dockerfile") { - t.Fatalf("test7 should have used files Dockerfile, output:%s", out) + c.Fatalf("test7 should have used files Dockerfile, output:%s", out) } - out, _, err = dockerCmdInDir(t, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join("..", "Dockerfile"), "-t", "test8", ".") + out, _, err = dockerCmdInDir(c, filepath.Join(ctx.Dir, "files"), "build", "-f", filepath.Join("..", "Dockerfile"), "-t", "test8", ".") if err == nil || !strings.Contains(out, "must be within the build context") { - t.Fatalf("test8 should have failed with Dockerfile out of context: %s", err) + c.Fatalf("test8 should have failed with Dockerfile out of context: %s", err) } tmpDir := os.TempDir() - out, _, err = dockerCmdInDir(t, tmpDir, "build", "-t", "test9", ctx.Dir) + out, _, err = dockerCmdInDir(c, tmpDir, "build", "-t", "test9", ctx.Dir) if err != nil { - t.Fatalf("test9 - failed: %s", err) + c.Fatalf("test9 - failed: %s", err) } if !strings.Contains(out, "from Dockerfile") { - t.Fatalf("test9 should have used root Dockerfile, output:%s", out) + c.Fatalf("test9 should have used root Dockerfile, output:%s", out) } - out, _, err = dockerCmdInDir(t, filepath.Join(ctx.Dir, "files"), "build", "-f", "dFile2", "-t", "test10", ".") + out, _, err = dockerCmdInDir(c, filepath.Join(ctx.Dir, "files"), "build", "-f", "dFile2", "-t", "test10", ".") if err != nil { - t.Fatalf("test10 should have worked: %s", err) + c.Fatalf("test10 should have worked: %s", err) } if !strings.Contains(out, "from files/dFile2") { - t.Fatalf("test10 should have used files/dFile2, output:%s", out) + c.Fatalf("test10 should have used files/dFile2, output:%s", out) } - logDone("build - rename dockerfile") } -func TestBuildFromMixedcaseDockerfile(t *testing.T) { - testRequires(t, UnixCli) // Dockerfile overwrites dockerfile on windows +func (s *DockerSuite) TestBuildFromMixedcaseDockerfile(c *check.C) { + testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows defer deleteImages("test1") ctx, err := fakeContext(`FROM busybox @@ -5103,23 +4938,22 @@ func TestBuildFromMixedcaseDockerfile(t *testing.T) { }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", "test1", ".") if err != nil { - t.Fatalf("Failed to build: %s\n%s", out, err) + c.Fatalf("Failed to build: %s\n%s", out, err) } if !strings.Contains(out, "from dockerfile") { - t.Fatalf("Missing proper output: %s", out) + c.Fatalf("Missing proper output: %s", out) } - logDone("build - mixedcase Dockerfile") } -func TestBuildWithTwoDockerfiles(t *testing.T) { - testRequires(t, UnixCli) // Dockerfile overwrites dockerfile on windows +func (s *DockerSuite) TestBuildWithTwoDockerfiles(c *check.C) { + testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows defer deleteImages("test1") ctx, err := fakeContext(`FROM busybox @@ -5129,22 +4963,21 @@ RUN echo from Dockerfile`, }) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-t", "test1", ".") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-t", "test1", ".") if err != nil { - t.Fatalf("Failed to build: %s\n%s", out, err) + c.Fatalf("Failed to build: %s\n%s", out, err) } if !strings.Contains(out, "from Dockerfile") { - t.Fatalf("Missing proper output: %s", out) + c.Fatalf("Missing proper output: %s", out) } - logDone("build - two Dockerfiles") } -func TestBuildFromURLWithF(t *testing.T) { +func (s *DockerSuite) TestBuildFromURLWithF(c *check.C) { defer deleteImages("test1") server, err := fakeStorage(map[string]string{"baz": `FROM busybox @@ -5152,7 +4985,7 @@ RUN echo from baz COPY * /tmp/ RUN find /tmp/`}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer server.Close() @@ -5161,26 +4994,25 @@ RUN echo from Dockerfile`, map[string]string{}) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } // Make sure that -f is ignored and that we don't use the Dockerfile // that's in the current dir - out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-f", "baz", "-t", "test1", server.URL()+"/baz") + out, _, err := dockerCmdInDir(c, ctx.Dir, "build", "-f", "baz", "-t", "test1", server.URL()+"/baz") if err != nil { - t.Fatalf("Failed to build: %s\n%s", out, err) + c.Fatalf("Failed to build: %s\n%s", out, err) } if !strings.Contains(out, "from baz") || strings.Contains(out, "/tmp/baz") || !strings.Contains(out, "/tmp/Dockerfile") { - t.Fatalf("Missing proper output: %s", out) + c.Fatalf("Missing proper output: %s", out) } - logDone("build - from URL with -f") } -func TestBuildFromStdinWithF(t *testing.T) { +func (s *DockerSuite) TestBuildFromStdinWithF(c *check.C) { defer deleteImages("test1") ctx, err := fakeContext(`FROM busybox @@ -5188,7 +5020,7 @@ RUN echo from Dockerfile`, map[string]string{}) defer ctx.Close() if err != nil { - t.Fatal(err) + c.Fatal(err) } // Make sure that -f is ignored and that we don't use the Dockerfile @@ -5201,19 +5033,18 @@ COPY * /tmp/ RUN find /tmp/`) out, status, err := runCommandWithOutput(dockerCommand) if err != nil || status != 0 { - t.Fatalf("Error building: %s", err) + c.Fatalf("Error building: %s", err) } if !strings.Contains(out, "from baz") || strings.Contains(out, "/tmp/baz") || !strings.Contains(out, "/tmp/Dockerfile") { - t.Fatalf("Missing proper output: %s", out) + c.Fatalf("Missing proper output: %s", out) } - logDone("build - from stdin with -f") } -func TestBuildFromOfficialNames(t *testing.T) { +func (s *DockerSuite) TestBuildFromOfficialNames(c *check.C) { name := "testbuildfromofficial" fromNames := []string{ "busybox", @@ -5227,45 +5058,44 @@ func TestBuildFromOfficialNames(t *testing.T) { imgName := fmt.Sprintf("%s%d", name, idx) _, err := buildImage(imgName, "FROM "+fromName, true) if err != nil { - t.Errorf("Build failed using FROM %s: %s", fromName, err) + c.Errorf("Build failed using FROM %s: %s", fromName, err) } deleteImages(imgName) } - logDone("build - from official names") } -func TestBuildDockerfileOutsideContext(t *testing.T) { - testRequires(t, UnixCli) // uses os.Symlink: not implemented in windows at the time of writing (go-1.4.2) +func (s *DockerSuite) TestBuildDockerfileOutsideContext(c *check.C) { + testRequires(c, UnixCli) // uses os.Symlink: not implemented in windows at the time of writing (go-1.4.2) name := "testbuilddockerfileoutsidecontext" tmpdir, err := ioutil.TempDir("", name) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpdir) ctx := filepath.Join(tmpdir, "context") if err := os.MkdirAll(ctx, 0755); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := ioutil.WriteFile(filepath.Join(ctx, "Dockerfile"), []byte("FROM scratch\nENV X Y"), 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } wd, err := os.Getwd() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.Chdir(wd) if err := os.Chdir(ctx); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := ioutil.WriteFile(filepath.Join(tmpdir, "outsideDockerfile"), []byte("FROM scratch\nENV x y"), 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := os.Symlink(filepath.Join("..", "outsideDockerfile"), filepath.Join(ctx, "dockerfile1")); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := os.Symlink(filepath.Join(tmpdir, "outsideDockerfile"), filepath.Join(ctx, "dockerfile2")); err != nil { - t.Fatal(err) + c.Fatal(err) } for _, dockerfilePath := range []string{ @@ -5275,10 +5105,10 @@ func TestBuildDockerfileOutsideContext(t *testing.T) { } { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "build", "-t", name, "--no-cache", "-f", dockerfilePath, ".")) if err == nil { - t.Fatalf("Expected error with %s. Out: %s", dockerfilePath, out) + c.Fatalf("Expected error with %s. Out: %s", dockerfilePath, out) } if !strings.Contains(out, "must be within the build context") && !strings.Contains(out, "Cannot locate Dockerfile") { - t.Fatalf("Unexpected error with %s. Out: %s", dockerfilePath, out) + c.Fatalf("Unexpected error with %s. Out: %s", dockerfilePath, out) } deleteImages(name) } @@ -5289,14 +5119,13 @@ func TestBuildDockerfileOutsideContext(t *testing.T) { // There is a Dockerfile in the context, but since there is no Dockerfile in the current directory, the following should fail out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "build", "-t", name, "--no-cache", "-f", "Dockerfile", ctx)) if err == nil { - t.Fatalf("Expected error. Out: %s", out) + c.Fatalf("Expected error. Out: %s", out) } deleteImages(name) - logDone("build - Dockerfile outside context") } -func TestBuildSpaces(t *testing.T) { +func (s *DockerSuite) TestBuildSpaces(c *check.C) { // Test to make sure that leading/trailing spaces on a command // doesn't change the error msg we get var ( @@ -5311,17 +5140,17 @@ func TestBuildSpaces(t *testing.T) { "Dockerfile": "FROM busybox\nCOPY\n", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err1 = buildImageFromContext(name, ctx, false); err1 == nil { - t.Fatal("Build 1 was supposed to fail, but didn't") + c.Fatal("Build 1 was supposed to fail, but didn't") } ctx.Add("Dockerfile", "FROM busybox\nCOPY ") if _, err2 = buildImageFromContext(name, ctx, false); err2 == nil { - t.Fatal("Build 2 was supposed to fail, but didn't") + c.Fatal("Build 2 was supposed to fail, but didn't") } removeLogTimestamps := func(s string) string { @@ -5334,12 +5163,12 @@ func TestBuildSpaces(t *testing.T) { // Ignore whitespace since that's what were verifying doesn't change stuff if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) { - t.Fatalf("Build 2's error wasn't the same as build 1's\n1:%s\n2:%s", err1, err2) + c.Fatalf("Build 2's error wasn't the same as build 1's\n1:%s\n2:%s", err1, err2) } ctx.Add("Dockerfile", "FROM busybox\n COPY") if _, err2 = buildImageFromContext(name, ctx, false); err2 == nil { - t.Fatal("Build 3 was supposed to fail, but didn't") + c.Fatal("Build 3 was supposed to fail, but didn't") } // Skip over the times @@ -5348,12 +5177,12 @@ func TestBuildSpaces(t *testing.T) { // Ignore whitespace since that's what were verifying doesn't change stuff if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) { - t.Fatalf("Build 3's error wasn't the same as build 1's\n1:%s\n3:%s", err1, err2) + c.Fatalf("Build 3's error wasn't the same as build 1's\n1:%s\n3:%s", err1, err2) } ctx.Add("Dockerfile", "FROM busybox\n COPY ") if _, err2 = buildImageFromContext(name, ctx, false); err2 == nil { - t.Fatal("Build 4 was supposed to fail, but didn't") + c.Fatal("Build 4 was supposed to fail, but didn't") } // Skip over the times @@ -5362,13 +5191,12 @@ func TestBuildSpaces(t *testing.T) { // Ignore whitespace since that's what were verifying doesn't change stuff if strings.Replace(e1, " ", "", -1) != strings.Replace(e2, " ", "", -1) { - t.Fatalf("Build 4's error wasn't the same as build 1's\n1:%s\n4:%s", err1, err2) + c.Fatalf("Build 4's error wasn't the same as build 1's\n1:%s\n4:%s", err1, err2) } - logDone("build - test spaces") } -func TestBuildSpacesWithQuotes(t *testing.T) { +func (s *DockerSuite) TestBuildSpacesWithQuotes(c *check.C) { // Test to make sure that spaces in quotes aren't lost name := "testspacesquotes" defer deleteImages(name) @@ -5379,19 +5207,18 @@ RUN echo " \ _, out, err := buildImageWithOut(name, dockerfile, false) if err != nil { - t.Fatal("Build failed:", err) + c.Fatal("Build failed:", err) } expecting := "\n foo \n" if !strings.Contains(out, expecting) { - t.Fatalf("Bad output: %q expecting to contian %q", out, expecting) + c.Fatalf("Bad output: %q expecting to contian %q", out, expecting) } - logDone("build - test spaces with quotes") } // #4393 -func TestBuildVolumeFileExistsinContainer(t *testing.T) { +func (s *DockerSuite) TestBuildVolumeFileExistsinContainer(c *check.C) { buildCmd := exec.Command(dockerBinary, "build", "-t", "docker-test-errcreatevolumewithfile", "-") buildCmd.Stdin = strings.NewReader(` FROM busybox @@ -5401,13 +5228,12 @@ func TestBuildVolumeFileExistsinContainer(t *testing.T) { out, _, err := runCommandWithOutput(buildCmd) if err == nil || !strings.Contains(out, "file exists") { - t.Fatalf("expected build to fail when file exists in container at requested volume path") + c.Fatalf("expected build to fail when file exists in container at requested volume path") } - logDone("build - errors when volume is specified where a file exists") } -func TestBuildMissingArgs(t *testing.T) { +func (s *DockerSuite) TestBuildMissingArgs(c *check.C) { // Test to make sure that all Dockerfile commands (except the ones listed // in skipCmds) will generate an error if no args are provided. // Note: INSERT is deprecated so we exclude it because of that. @@ -5418,8 +5244,6 @@ func TestBuildMissingArgs(t *testing.T) { "INSERT": {}, } - defer deleteAllContainers() - for cmd := range command.Commands { cmd = strings.ToUpper(cmd) if _, ok := skipCmds[cmd]; ok { @@ -5436,57 +5260,53 @@ func TestBuildMissingArgs(t *testing.T) { ctx, err := fakeContext(dockerfile, map[string]string{}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() var out string if out, err = buildImageFromContext("args", ctx, true); err == nil { - t.Fatalf("%s was supposed to fail. Out:%s", cmd, out) + c.Fatalf("%s was supposed to fail. Out:%s", cmd, out) } if !strings.Contains(err.Error(), cmd+" requires") { - t.Fatalf("%s returned the wrong type of error:%s", cmd, err) + c.Fatalf("%s returned the wrong type of error:%s", cmd, err) } } - logDone("build - verify missing args") } -func TestBuildEmptyScratch(t *testing.T) { +func (s *DockerSuite) TestBuildEmptyScratch(c *check.C) { defer deleteImages("sc") _, out, err := buildImageWithOut("sc", "FROM scratch", true) if err == nil { - t.Fatalf("Build was supposed to fail") + c.Fatalf("Build was supposed to fail") } if !strings.Contains(out, "No image was generated") { - t.Fatalf("Wrong error message: %v", out) + c.Fatalf("Wrong error message: %v", out) } - logDone("build - empty scratch Dockerfile") } -func TestBuildDotDotFile(t *testing.T) { +func (s *DockerSuite) TestBuildDotDotFile(c *check.C) { defer deleteImages("sc") ctx, err := fakeContext("FROM busybox\n", map[string]string{ "..gitme": "", }) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() if _, err = buildImageFromContext("sc", ctx, false); err != nil { - t.Fatalf("Build was supposed to work: %s", err) + c.Fatalf("Build was supposed to work: %s", err) } - logDone("build - ..file") } -func TestBuildNotVerbose(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestBuildNotVerbose(c *check.C) { defer deleteImages("verbose") ctx, err := fakeContext("FROM busybox\nENV abc=hi\nRUN echo $abc there", map[string]string{}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() @@ -5495,10 +5315,10 @@ func TestBuildNotVerbose(t *testing.T) { buildCmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(buildCmd) if err != nil { - t.Fatalf("failed to build the image w/o -q: %s, %v", out, err) + c.Fatalf("failed to build the image w/o -q: %s, %v", out, err) } if !strings.Contains(out, "hi there") { - t.Fatalf("missing output:%s\n", out) + c.Fatalf("missing output:%s\n", out) } // Now do it w/o verbose @@ -5506,25 +5326,23 @@ func TestBuildNotVerbose(t *testing.T) { buildCmd.Dir = ctx.Dir out, _, err = runCommandWithOutput(buildCmd) if err != nil { - t.Fatalf("failed to build the image w/ -q: %s, %v", out, err) + c.Fatalf("failed to build the image w/ -q: %s, %v", out, err) } if strings.Contains(out, "hi there") { - t.Fatalf("Bad output, should not contain 'hi there':%s", out) + c.Fatalf("Bad output, should not contain 'hi there':%s", out) } - logDone("build - not verbose") } -func TestBuildRUNoneJSON(t *testing.T) { +func (s *DockerSuite) TestBuildRUNoneJSON(c *check.C) { name := "testbuildrunonejson" - defer deleteAllContainers() defer deleteImages(name, "hello-world") ctx, err := fakeContext(`FROM hello-world:frozen RUN [ "/hello" ]`, map[string]string{}) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer ctx.Close() @@ -5532,19 +5350,17 @@ RUN [ "/hello" ]`, map[string]string{}) buildCmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(buildCmd) if err != nil { - t.Fatalf("failed to build the image: %s, %v", out, err) + c.Fatalf("failed to build the image: %s, %v", out, err) } if !strings.Contains(out, "Hello from Docker") { - t.Fatalf("bad output: %s", out) + c.Fatalf("bad output: %s", out) } - logDone("build - RUN with one JSON arg") } -func TestBuildResourceConstraintsAreUsed(t *testing.T) { +func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { name := "testbuildresourceconstraints" - defer deleteAllContainers() defer deleteImages(name, "hello-world") ctx, err := fakeContext(` @@ -5552,7 +5368,7 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { RUN ["/hello"] `, map[string]string{}) if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpuset-mems=0", "--cpu-shares=100", "-t", name, ".") @@ -5560,9 +5376,9 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - out, _ = dockerCmd(t, "ps", "-lq") + out, _ = dockerCmd(c, "ps", "-lq") cID := strings.TrimSpace(out) @@ -5576,40 +5392,39 @@ func TestBuildResourceConstraintsAreUsed(t *testing.T) { cfg, err := inspectFieldJSON(cID, "HostConfig") if err != nil { - t.Fatal(err) + c.Fatal(err) } var c1 hostConfig if err := json.Unmarshal([]byte(cfg), &c1); err != nil { - t.Fatal(err, cfg) + c.Fatal(err, cfg) } mem := int64(c1.Memory) if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 { - t.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", + c.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", mem, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares) } // Make sure constraints aren't saved to image - _, _ = dockerCmd(t, "run", "--name=test", name) + _, _ = dockerCmd(c, "run", "--name=test", name) cfg, err = inspectFieldJSON("test", "HostConfig") if err != nil { - t.Fatal(err) + c.Fatal(err) } var c2 hostConfig if err := json.Unmarshal([]byte(cfg), &c2); err != nil { - t.Fatal(err, cfg) + c.Fatal(err, cfg) } mem = int64(c2.Memory) if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 { - t.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", + c.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", mem, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares) } - logDone("build - resource constraints applied") } -func TestBuildEmptyStringVolume(t *testing.T) { +func (s *DockerSuite) TestBuildEmptyStringVolume(c *check.C) { name := "testbuildemptystringvolume" defer deleteImages(name) @@ -5619,8 +5434,7 @@ func TestBuildEmptyStringVolume(t *testing.T) { VOLUME $foo `, false) if err == nil { - t.Fatal("Should have failed to build") + c.Fatal("Should have failed to build") } - logDone("build - empty string volume") } diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index 24ebf0cf7..fc8c4600b 100644 --- a/integration-cli/docker_cli_by_digest_test.go +++ b/integration-cli/docker_cli_by_digest_test.go @@ -5,9 +5,9 @@ import ( "os/exec" "regexp" "strings" - "testing" "github.com/docker/docker/utils" + "github.com/go-check/check" ) var ( @@ -22,15 +22,15 @@ func setupImage() (string, error) { func setupImageWithTag(tag string) (string, error) { containerName := "busyboxbydigest" - c := exec.Command(dockerBinary, "run", "-d", "-e", "digest=1", "--name", containerName, "busybox") - if _, err := runCommand(c); err != nil { + cmd := exec.Command(dockerBinary, "run", "-d", "-e", "digest=1", "--name", containerName, "busybox") + if _, err := runCommand(cmd); err != nil { return "", err } // tag the image to upload it to the private registry repoAndTag := utils.ImageReference(repoName, tag) - c = exec.Command(dockerBinary, "commit", containerName, repoAndTag) - if out, _, err := runCommandWithOutput(c); err != nil { + cmd = exec.Command(dockerBinary, "commit", containerName, repoAndTag) + if out, _, err := runCommandWithOutput(cmd); err != nil { return "", fmt.Errorf("image tagging failed: %s, %v", out, err) } defer deleteImages(repoAndTag) @@ -41,15 +41,15 @@ func setupImageWithTag(tag string) (string, error) { } // push the image - c = exec.Command(dockerBinary, "push", repoAndTag) - out, _, err := runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "push", repoAndTag) + out, _, err := runCommandWithOutput(cmd) if err != nil { return "", fmt.Errorf("pushing the image to the private registry has failed: %s, %v", out, err) } // delete our local repo that we previously tagged - c = exec.Command(dockerBinary, "rmi", repoAndTag) - if out, _, err := runCommandWithOutput(c); err != nil { + cmd = exec.Command(dockerBinary, "rmi", repoAndTag) + if out, _, err := runCommandWithOutput(cmd); err != nil { return "", fmt.Errorf("error deleting images prior to real test: %s, %v", out, err) } @@ -63,194 +63,189 @@ func setupImageWithTag(tag string) (string, error) { return pushDigest, nil } -func TestPullByTagDisplaysDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPullByTagDisplaysDigest(c *check.C) { + defer setupRegistry(c)() pushDigest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } // pull from the registry using the tag - c := exec.Command(dockerBinary, "pull", repoName) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", repoName) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by tag: %s, %v", out, err) + c.Fatalf("error pulling by tag: %s, %v", out, err) } defer deleteImages(repoName) // the pull output includes "Digest: ", so find that matches := digestRegex.FindStringSubmatch(out) if len(matches) != 2 { - t.Fatalf("unable to parse digest from pull output: %s", out) + c.Fatalf("unable to parse digest from pull output: %s", out) } pullDigest := matches[1] // make sure the pushed and pull digests match if pushDigest != pullDigest { - t.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) + c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) } - logDone("by_digest - pull by tag displays digest") } -func TestPullByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPullByDigest(c *check.C) { + defer setupRegistry(c)() pushDigest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } // pull from the registry using the @ reference imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest) - c := exec.Command(dockerBinary, "pull", imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } defer deleteImages(imageReference) // the pull output includes "Digest: ", so find that matches := digestRegex.FindStringSubmatch(out) if len(matches) != 2 { - t.Fatalf("unable to parse digest from pull output: %s", out) + c.Fatalf("unable to parse digest from pull output: %s", out) } pullDigest := matches[1] // make sure the pushed and pull digests match if pushDigest != pullDigest { - t.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) + c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) } - logDone("by_digest - pull by digest") } -func TestCreateByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestCreateByDigest(c *check.C) { + defer setupRegistry(c)() pushDigest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest) containerName := "createByDigest" - c := exec.Command(dockerBinary, "create", "--name", containerName, imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "create", "--name", containerName, imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error creating by digest: %s, %v", out, err) + c.Fatalf("error creating by digest: %s, %v", out, err) } defer deleteContainer(containerName) res, err := inspectField(containerName, "Config.Image") if err != nil { - t.Fatalf("failed to get Config.Image: %s, %v", out, err) + c.Fatalf("failed to get Config.Image: %s, %v", out, err) } if res != imageReference { - t.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference) + c.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference) } - logDone("by_digest - create by digest") } -func TestRunByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestRunByDigest(c *check.C) { + defer setupRegistry(c)() pushDigest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest) containerName := "runByDigest" - c := exec.Command(dockerBinary, "run", "--name", containerName, imageReference, "sh", "-c", "echo found=$digest") - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "run", "--name", containerName, imageReference, "sh", "-c", "echo found=$digest") + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error run by digest: %s, %v", out, err) + c.Fatalf("error run by digest: %s, %v", out, err) } defer deleteContainer(containerName) foundRegex := regexp.MustCompile("found=([^\n]+)") matches := foundRegex.FindStringSubmatch(out) if len(matches) != 2 { - t.Fatalf("error locating expected 'found=1' output: %s", out) + c.Fatalf("error locating expected 'found=1' output: %s", out) } if matches[1] != "1" { - t.Fatalf("Expected %q, got %q", "1", matches[1]) + c.Fatalf("Expected %q, got %q", "1", matches[1]) } res, err := inspectField(containerName, "Config.Image") if err != nil { - t.Fatalf("failed to get Config.Image: %s, %v", out, err) + c.Fatalf("failed to get Config.Image: %s, %v", out, err) } if res != imageReference { - t.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference) + c.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference) } - logDone("by_digest - run by digest") } -func TestRemoveImageByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestRemoveImageByDigest(c *check.C) { + defer setupRegistry(c)() digest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference := fmt.Sprintf("%s@%s", repoName, digest) // pull from the registry using the @ reference - c := exec.Command(dockerBinary, "pull", imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // make sure inspect runs ok if _, err := inspectField(imageReference, "Id"); err != nil { - t.Fatalf("failed to inspect image: %v", err) + c.Fatalf("failed to inspect image: %v", err) } // do the delete if err := deleteImages(imageReference); err != nil { - t.Fatalf("unexpected error deleting image: %v", err) + c.Fatalf("unexpected error deleting image: %v", err) } // try to inspect again - it should error this time if _, err := inspectField(imageReference, "Id"); err == nil { - t.Fatalf("unexpected nil err trying to inspect what should be a non-existent image") + c.Fatalf("unexpected nil err trying to inspect what should be a non-existent image") } else if !strings.Contains(err.Error(), "No such image") { - t.Fatalf("expected 'No such image' output, got %v", err) + c.Fatalf("expected 'No such image' output, got %v", err) } - logDone("by_digest - remove image by digest") } -func TestBuildByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestBuildByDigest(c *check.C) { + defer setupRegistry(c)() digest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference := fmt.Sprintf("%s@%s", repoName, digest) // pull from the registry using the @ reference - c := exec.Command(dockerBinary, "pull", imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // get the image id imageID, err := inspectField(imageReference, "Id") if err != nil { - t.Fatalf("error getting image id: %v", err) + c.Fatalf("error getting image id: %v", err) } // do the build @@ -261,275 +256,270 @@ func TestBuildByDigest(t *testing.T) { CMD ["/bin/echo", "Hello World"]`, imageReference), true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // get the build's image id res, err := inspectField(name, "Config.Image") if err != nil { - t.Fatal(err) + c.Fatal(err) } // make sure they match if res != imageID { - t.Fatalf("Image %s, expected %s", res, imageID) + c.Fatalf("Image %s, expected %s", res, imageID) } - logDone("by_digest - build by digest") } -func TestTagByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestTagByDigest(c *check.C) { + defer setupRegistry(c)() digest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference := fmt.Sprintf("%s@%s", repoName, digest) // pull from the registry using the @ reference - c := exec.Command(dockerBinary, "pull", imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // tag it tag := "tagbydigest" - c = exec.Command(dockerBinary, "tag", imageReference, tag) - if _, err := runCommand(c); err != nil { - t.Fatalf("unexpected error tagging: %v", err) + cmd = exec.Command(dockerBinary, "tag", imageReference, tag) + if _, err := runCommand(cmd); err != nil { + c.Fatalf("unexpected error tagging: %v", err) } expectedID, err := inspectField(imageReference, "Id") if err != nil { - t.Fatalf("error getting original image id: %v", err) + c.Fatalf("error getting original image id: %v", err) } tagID, err := inspectField(tag, "Id") if err != nil { - t.Fatalf("error getting tagged image id: %v", err) + c.Fatalf("error getting tagged image id: %v", err) } if tagID != expectedID { - t.Fatalf("expected image id %q, got %q", expectedID, tagID) + c.Fatalf("expected image id %q, got %q", expectedID, tagID) } - logDone("by_digest - tag by digest") } -func TestListImagesWithoutDigests(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestListImagesWithoutDigests(c *check.C) { + defer setupRegistry(c)() digest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference := fmt.Sprintf("%s@%s", repoName, digest) // pull from the registry using the @ reference - c := exec.Command(dockerBinary, "pull", imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } - c = exec.Command(dockerBinary, "images") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error listing images: %s, %v", out, err) + c.Fatalf("error listing images: %s, %v", out, err) } if strings.Contains(out, "DIGEST") { - t.Fatalf("list output should not have contained DIGEST header: %s", out) + c.Fatalf("list output should not have contained DIGEST header: %s", out) } - logDone("by_digest - list images - digest header not displayed by default") } -func TestListImagesWithDigests(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestListImagesWithDigests(c *check.C) { + defer setupRegistry(c)() defer deleteImages(repoName+":tag1", repoName+":tag2") // setup image1 digest1, err := setupImageWithTag("tag1") if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference1 := fmt.Sprintf("%s@%s", repoName, digest1) defer deleteImages(imageReference1) - t.Logf("imageReference1 = %s", imageReference1) + c.Logf("imageReference1 = %s", imageReference1) // pull image1 by digest - c := exec.Command(dockerBinary, "pull", imageReference1) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference1) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // list images - c = exec.Command(dockerBinary, "images", "--digests") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images", "--digests") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error listing images: %s, %v", out, err) + c.Fatalf("error listing images: %s, %v", out, err) } // make sure repo shown, tag=, digest = $digest1 re1 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest1 + `\s`) if !re1.MatchString(out) { - t.Fatalf("expected %q: %s", re1.String(), out) + c.Fatalf("expected %q: %s", re1.String(), out) } // setup image2 digest2, err := setupImageWithTag("tag2") if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } imageReference2 := fmt.Sprintf("%s@%s", repoName, digest2) defer deleteImages(imageReference2) - t.Logf("imageReference2 = %s", imageReference2) + c.Logf("imageReference2 = %s", imageReference2) // pull image1 by digest - c = exec.Command(dockerBinary, "pull", imageReference1) - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "pull", imageReference1) + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // pull image2 by digest - c = exec.Command(dockerBinary, "pull", imageReference2) - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "pull", imageReference2) + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // list images - c = exec.Command(dockerBinary, "images", "--digests") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images", "--digests") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error listing images: %s, %v", out, err) + c.Fatalf("error listing images: %s, %v", out, err) } // make sure repo shown, tag=, digest = $digest1 if !re1.MatchString(out) { - t.Fatalf("expected %q: %s", re1.String(), out) + c.Fatalf("expected %q: %s", re1.String(), out) } // make sure repo shown, tag=, digest = $digest2 re2 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest2 + `\s`) if !re2.MatchString(out) { - t.Fatalf("expected %q: %s", re2.String(), out) + c.Fatalf("expected %q: %s", re2.String(), out) } // pull tag1 - c = exec.Command(dockerBinary, "pull", repoName+":tag1") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "pull", repoName+":tag1") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling tag1: %s, %v", out, err) + c.Fatalf("error pulling tag1: %s, %v", out, err) } // list images - c = exec.Command(dockerBinary, "images", "--digests") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images", "--digests") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error listing images: %s, %v", out, err) + c.Fatalf("error listing images: %s, %v", out, err) } // make sure image 1 has repo, tag, AND repo, , digest reWithTag1 := regexp.MustCompile(`\s*` + repoName + `\s*tag1\s*\s`) reWithDigest1 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest1 + `\s`) if !reWithTag1.MatchString(out) { - t.Fatalf("expected %q: %s", reWithTag1.String(), out) + c.Fatalf("expected %q: %s", reWithTag1.String(), out) } if !reWithDigest1.MatchString(out) { - t.Fatalf("expected %q: %s", reWithDigest1.String(), out) + c.Fatalf("expected %q: %s", reWithDigest1.String(), out) } // make sure image 2 has repo, , digest if !re2.MatchString(out) { - t.Fatalf("expected %q: %s", re2.String(), out) + c.Fatalf("expected %q: %s", re2.String(), out) } // pull tag 2 - c = exec.Command(dockerBinary, "pull", repoName+":tag2") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "pull", repoName+":tag2") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling tag2: %s, %v", out, err) + c.Fatalf("error pulling tag2: %s, %v", out, err) } // list images - c = exec.Command(dockerBinary, "images", "--digests") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images", "--digests") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error listing images: %s, %v", out, err) + c.Fatalf("error listing images: %s, %v", out, err) } // make sure image 1 has repo, tag, digest if !reWithTag1.MatchString(out) { - t.Fatalf("expected %q: %s", re1.String(), out) + c.Fatalf("expected %q: %s", re1.String(), out) } // make sure image 2 has repo, tag, digest reWithTag2 := regexp.MustCompile(`\s*` + repoName + `\s*tag2\s*\s`) reWithDigest2 := regexp.MustCompile(`\s*` + repoName + `\s*\s*` + digest2 + `\s`) if !reWithTag2.MatchString(out) { - t.Fatalf("expected %q: %s", reWithTag2.String(), out) + c.Fatalf("expected %q: %s", reWithTag2.String(), out) } if !reWithDigest2.MatchString(out) { - t.Fatalf("expected %q: %s", reWithDigest2.String(), out) + c.Fatalf("expected %q: %s", reWithDigest2.String(), out) } // list images - c = exec.Command(dockerBinary, "images", "--digests") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images", "--digests") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error listing images: %s, %v", out, err) + c.Fatalf("error listing images: %s, %v", out, err) } // make sure image 1 has repo, tag, digest if !reWithTag1.MatchString(out) { - t.Fatalf("expected %q: %s", re1.String(), out) + c.Fatalf("expected %q: %s", re1.String(), out) } // make sure image 2 has repo, tag, digest if !reWithTag2.MatchString(out) { - t.Fatalf("expected %q: %s", re2.String(), out) + c.Fatalf("expected %q: %s", re2.String(), out) } // make sure busybox has tag, but not digest busyboxRe := regexp.MustCompile(`\s*busybox\s*latest\s*\s`) if !busyboxRe.MatchString(out) { - t.Fatalf("expected %q: %s", busyboxRe.String(), out) + c.Fatalf("expected %q: %s", busyboxRe.String(), out) } - logDone("by_digest - list images with digests") } -func TestDeleteImageByIDOnlyPulledByDigest(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) { + defer setupRegistry(c)() pushDigest, err := setupImage() if err != nil { - t.Fatalf("error setting up image: %v", err) + c.Fatalf("error setting up image: %v", err) } // pull from the registry using the @ reference imageReference := fmt.Sprintf("%s@%s", repoName, pushDigest) - c := exec.Command(dockerBinary, "pull", imageReference) - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "pull", imageReference) + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error pulling by digest: %s, %v", out, err) + c.Fatalf("error pulling by digest: %s, %v", out, err) } // just in case... defer deleteImages(imageReference) imageID, err := inspectField(imageReference, ".Id") if err != nil { - t.Fatalf("error inspecting image id: %v", err) + c.Fatalf("error inspecting image id: %v", err) } - c = exec.Command(dockerBinary, "rmi", imageID) - if _, err := runCommand(c); err != nil { - t.Fatalf("error deleting image by id: %v", err) + cmd = exec.Command(dockerBinary, "rmi", imageID) + if _, err := runCommand(cmd); err != nil { + c.Fatalf("error deleting image by id: %v", err) } - logDone("by_digest - delete image by id only pulled by digest") } diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index 5d4288192..9bbff09cc 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -3,96 +3,94 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestCommitAfterContainerIsDone(t *testing.T) { +func (s *DockerSuite) TestCommitAfterContainerIsDone(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %s, %v", out, err) + c.Fatalf("failed to run container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID) if _, _, err = runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", out, err) + c.Fatalf("error thrown while waiting for container: %s, %v", out, err) } commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID) out, _, err = runCommandWithOutput(commitCmd) if err != nil { - t.Fatalf("failed to commit container to image: %s, %v", out, err) + c.Fatalf("failed to commit container to image: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedImageID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("failed to inspect image: %s, %v", out, err) + c.Fatalf("failed to inspect image: %s, %v", out, err) } deleteContainer(cleanedContainerID) deleteImages(cleanedImageID) - logDone("commit - echo foo and commit the image") } -func TestCommitWithoutPause(t *testing.T) { +func (s *DockerSuite) TestCommitWithoutPause(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %s, %v", out, err) + c.Fatalf("failed to run container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) waitCmd := exec.Command(dockerBinary, "wait", cleanedContainerID) if _, _, err = runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", out, err) + c.Fatalf("error thrown while waiting for container: %s, %v", out, err) } commitCmd := exec.Command(dockerBinary, "commit", "-p=false", cleanedContainerID) out, _, err = runCommandWithOutput(commitCmd) if err != nil { - t.Fatalf("failed to commit container to image: %s, %v", out, err) + c.Fatalf("failed to commit container to image: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedImageID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("failed to inspect image: %s, %v", out, err) + c.Fatalf("failed to inspect image: %s, %v", out, err) } deleteContainer(cleanedContainerID) deleteImages(cleanedImageID) - logDone("commit - echo foo and commit the image with --pause=false") } //test commit a paused container should not unpause it after commit -func TestCommitPausedContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCommitPausedContainer(c *check.C) { defer unpauseAllContainers() cmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox") out, _, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cleanedContainerID := strings.TrimSpace(out) cmd = exec.Command(dockerBinary, "pause", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("failed to pause container: %v, output: %q", err, out) + c.Fatalf("failed to pause container: %v, output: %q", err, out) } commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID) out, _, err = runCommandWithOutput(commitCmd) if err != nil { - t.Fatalf("failed to commit container to image: %s, %v", out, err) + c.Fatalf("failed to commit container to image: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) defer deleteImages(cleanedImageID) @@ -100,28 +98,26 @@ func TestCommitPausedContainer(t *testing.T) { cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Paused}}", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("failed to inspect container: %v, output: %q", err, out) + c.Fatalf("failed to inspect container: %v, output: %q", err, out) } if !strings.Contains(out, "true") { - t.Fatalf("commit should not unpause a paused container") + c.Fatalf("commit should not unpause a paused container") } - logDone("commit - commit a paused container will not unpause it") } -func TestCommitNewFile(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCommitNewFile(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "commiter", "busybox", "/bin/sh", "-c", "echo koye > /foo") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "commit", "commiter") imageID, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } imageID = strings.Trim(imageID, "\r\n") defer deleteImages(imageID) @@ -130,22 +126,20 @@ func TestCommitNewFile(t *testing.T) { out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "koye" { - t.Fatalf("expected output koye received %q", actual) + c.Fatalf("expected output koye received %q", actual) } - logDone("commit - commit file and read") } -func TestCommitHardlink(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCommitHardlink(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-t", "--name", "hardlinks", "busybox", "sh", "-c", "touch file1 && ln file1 file2 && ls -di file1 file2") firstOuput, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } chunks := strings.Split(strings.TrimSpace(firstOuput), " ") @@ -158,13 +152,13 @@ func TestCommitHardlink(t *testing.T) { } } if !found { - t.Fatalf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:]) + c.Fatalf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:]) } cmd = exec.Command(dockerBinary, "commit", "hardlinks", "hardlinks") imageID, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(imageID, err) + c.Fatal(imageID, err) } imageID = strings.Trim(imageID, "\r\n") defer deleteImages(imageID) @@ -172,7 +166,7 @@ func TestCommitHardlink(t *testing.T) { cmd = exec.Command(dockerBinary, "run", "-t", "hardlinks", "ls", "-di", "file1", "file2") secondOuput, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } chunks = strings.Split(strings.TrimSpace(secondOuput), " ") @@ -185,48 +179,44 @@ func TestCommitHardlink(t *testing.T) { } } if !found { - t.Fatalf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:]) + c.Fatalf("Failed to create hardlink in a container. Expected to find %q in %q", inode, chunks[1:]) } - logDone("commit - commit hardlinks") } -func TestCommitTTY(t *testing.T) { +func (s *DockerSuite) TestCommitTTY(c *check.C) { defer deleteImages("ttytest") - defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-t", "--name", "tty", "busybox", "/bin/ls") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "commit", "tty", "ttytest") imageID, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } imageID = strings.Trim(imageID, "\r\n") cmd = exec.Command(dockerBinary, "run", "ttytest", "/bin/ls") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("commit - commit tty") } -func TestCommitWithHostBindMount(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCommitWithHostBindMount(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "bind-commit", "-v", "/dev/null:/winning", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "commit", "bind-commit", "bindtest") imageID, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(imageID, err) + c.Fatal(imageID, err) } imageID = strings.Trim(imageID, "\r\n") @@ -235,18 +225,16 @@ func TestCommitWithHostBindMount(t *testing.T) { cmd = exec.Command(dockerBinary, "run", "bindtest", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("commit - commit bind mounted file") } -func TestCommitChange(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCommitChange(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "test", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "commit", @@ -257,7 +245,7 @@ func TestCommitChange(t *testing.T) { "test", "test-commit") imageId, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(imageId, err) + c.Fatal(imageId, err) } imageId = strings.Trim(imageId, "\r\n") defer deleteImages(imageId) @@ -270,29 +258,27 @@ func TestCommitChange(t *testing.T) { for conf, value := range expected { res, err := inspectField(imageId, conf) if err != nil { - t.Errorf("failed to get value %s, error: %s", conf, err) + c.Errorf("failed to get value %s, error: %s", conf, err) } if res != value { - t.Errorf("%s('%s'), expected %s", conf, res, value) + c.Errorf("%s('%s'), expected %s", conf, res, value) } } - logDone("commit - commit --change") } // TODO: commit --run is deprecated, remove this once --run is removed -func TestCommitMergeConfigRun(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCommitMergeConfigRun(c *check.C) { name := "commit-test" - out, _ := dockerCmd(t, "run", "-d", "-e=FOO=bar", "busybox", "/bin/sh", "-c", "echo testing > /tmp/foo") + out, _ := dockerCmd(c, "run", "-d", "-e=FOO=bar", "busybox", "/bin/sh", "-c", "echo testing > /tmp/foo") id := strings.TrimSpace(out) - dockerCmd(t, "commit", `--run={"Cmd": ["cat", "/tmp/foo"]}`, id, "commit-test") + dockerCmd(c, "commit", `--run={"Cmd": ["cat", "/tmp/foo"]}`, id, "commit-test") defer deleteImages("commit-test") - out, _ = dockerCmd(t, "run", "--name", name, "commit-test") + out, _ = dockerCmd(c, "run", "--name", name, "commit-test") if strings.TrimSpace(out) != "testing" { - t.Fatal("run config in commited container was not merged") + c.Fatal("run config in commited container was not merged") } type cfg struct { @@ -301,11 +287,11 @@ func TestCommitMergeConfigRun(t *testing.T) { } config1 := cfg{} if err := inspectFieldAndMarshall(id, "Config", &config1); err != nil { - t.Fatal(err) + c.Fatal(err) } config2 := cfg{} if err := inspectFieldAndMarshall(name, "Config", &config2); err != nil { - t.Fatal(err) + c.Fatal(err) } // Env has at least PATH loaded as well here, so let's just grab the FOO one @@ -324,8 +310,7 @@ func TestCommitMergeConfigRun(t *testing.T) { } if len(config1.Env) != len(config2.Env) || env1 != env2 && env2 != "" { - t.Fatalf("expected envs to match: %v - %v", config1.Env, config2.Env) + c.Fatalf("expected envs to match: %v - %v", config1.Env, config2.Env) } - logDone("commit - configs are merged with --run") } diff --git a/integration-cli/docker_cli_config_test.go b/integration-cli/docker_cli_config_test.go index 23ad70069..5ccd7af10 100644 --- a/integration-cli/docker_cli_config_test.go +++ b/integration-cli/docker_cli_config_test.go @@ -7,13 +7,13 @@ import ( "os" "os/exec" "path/filepath" - "testing" "github.com/docker/docker/pkg/homedir" + "github.com/go-check/check" ) -func TestConfigHttpHeader(t *testing.T) { - testRequires(t, UnixCli) // Can't set/unset HOME on windows right now +func (s *DockerSuite) TestConfigHttpHeader(c *check.C) { + testRequires(c, UnixCli) // Can't set/unset HOME on windows right now // We either need a level of Go that supports Unsetenv (for cases // when HOME/USERPROFILE isn't set), or we need to be able to use // os/user but user.Current() only works if we aren't statically compiling @@ -44,15 +44,13 @@ func TestConfigHttpHeader(t *testing.T) { err := ioutil.WriteFile(tmpCfg, []byte(data), 0600) if err != nil { - t.Fatalf("Err creating file(%s): %v", tmpCfg, err) + c.Fatalf("Err creating file(%s): %v", tmpCfg, err) } cmd := exec.Command(dockerBinary, "-H="+server.URL[7:], "ps") out, _, _ := runCommandWithOutput(cmd) if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" { - t.Fatalf("Missing/bad header: %q\nout:%v", headers, out) + c.Fatalf("Missing/bad header: %q\nout:%v", headers, out) } - - logDone("config - add new http headers") } diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index b577e8298..022b3cc9e 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -9,7 +9,8 @@ import ( "path" "path/filepath" "strings" - "testing" + + "github.com/go-check/check" ) const ( @@ -24,27 +25,27 @@ const ( // Test for #5656 // Check that garbage paths don't escape the container's rootfs -func TestCpGarbagePath(t *testing.T) { - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) +func (s *DockerSuite) TestCpGarbagePath(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { - t.Fatal(err) + c.Fatal(err) } hostFile, err := os.Create(cpFullPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer hostFile.Close() defer os.RemoveAll(cpTestPathParent) @@ -53,7 +54,7 @@ func TestCpGarbagePath(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } tmpname := filepath.Join(tmpdir, cpTestName) @@ -61,49 +62,48 @@ func TestCpGarbagePath(t *testing.T) { path := path.Join("../../../../../../../../../../../../", cpFullPath) - _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() test, err := ioutil.ReadAll(file) if err != nil { - t.Fatal(err) + c.Fatal(err) } if string(test) == cpHostContents { - t.Errorf("output matched host file -- garbage path can escape container rootfs") + c.Errorf("output matched host file -- garbage path can escape container rootfs") } if string(test) != cpContainerContents { - t.Errorf("output doesn't match the input for garbage path") + c.Errorf("output doesn't match the input for garbage path") } - logDone("cp - garbage paths relative to container's rootfs") } // Check that relative paths are relative to the container's rootfs -func TestCpRelativePath(t *testing.T) { - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) +func (s *DockerSuite) TestCpRelativePath(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { - t.Fatal(err) + c.Fatal(err) } hostFile, err := os.Create(cpFullPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer hostFile.Close() defer os.RemoveAll(cpTestPathParent) @@ -113,7 +113,7 @@ func TestCpRelativePath(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } tmpname := filepath.Join(tmpdir, cpTestName) @@ -125,52 +125,51 @@ func TestCpRelativePath(t *testing.T) { // get this unix-path manipulation on windows with filepath. relPath = cpFullPath[1:] } else { - t.Fatalf("path %s was assumed to be an absolute path", cpFullPath) + c.Fatalf("path %s was assumed to be an absolute path", cpFullPath) } - _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+relPath, tmpdir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+relPath, tmpdir) file, _ := os.Open(tmpname) defer file.Close() test, err := ioutil.ReadAll(file) if err != nil { - t.Fatal(err) + c.Fatal(err) } if string(test) == cpHostContents { - t.Errorf("output matched host file -- relative path can escape container rootfs") + c.Errorf("output matched host file -- relative path can escape container rootfs") } if string(test) != cpContainerContents { - t.Errorf("output doesn't match the input for relative path") + c.Errorf("output doesn't match the input for relative path") } - logDone("cp - relative paths relative to container's rootfs") } // Check that absolute paths are relative to the container's rootfs -func TestCpAbsolutePath(t *testing.T) { - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) +func (s *DockerSuite) TestCpAbsolutePath(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath) if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { - t.Fatal(err) + c.Fatal(err) } hostFile, err := os.Create(cpFullPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer hostFile.Close() defer os.RemoveAll(cpTestPathParent) @@ -180,7 +179,7 @@ func TestCpAbsolutePath(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } tmpname := filepath.Join(tmpdir, cpTestName) @@ -188,50 +187,49 @@ func TestCpAbsolutePath(t *testing.T) { path := cpFullPath - _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() test, err := ioutil.ReadAll(file) if err != nil { - t.Fatal(err) + c.Fatal(err) } if string(test) == cpHostContents { - t.Errorf("output matched host file -- absolute path can escape container rootfs") + c.Errorf("output matched host file -- absolute path can escape container rootfs") } if string(test) != cpContainerContents { - t.Errorf("output doesn't match the input for absolute path") + c.Errorf("output doesn't match the input for absolute path") } - logDone("cp - absolute paths relative to container's rootfs") } // Test for #5619 // Check that absolute symlinks are still relative to the container's rootfs -func TestCpAbsoluteSymlink(t *testing.T) { - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path") +func (s *DockerSuite) TestCpAbsoluteSymlink(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpFullPath+" container_path") if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { - t.Fatal(err) + c.Fatal(err) } hostFile, err := os.Create(cpFullPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer hostFile.Close() defer os.RemoveAll(cpTestPathParent) @@ -241,7 +239,7 @@ func TestCpAbsoluteSymlink(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } tmpname := filepath.Join(tmpdir, cpTestName) @@ -249,50 +247,49 @@ func TestCpAbsoluteSymlink(t *testing.T) { path := path.Join("/", "container_path") - _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() test, err := ioutil.ReadAll(file) if err != nil { - t.Fatal(err) + c.Fatal(err) } if string(test) == cpHostContents { - t.Errorf("output matched host file -- absolute symlink can escape container rootfs") + c.Errorf("output matched host file -- absolute symlink can escape container rootfs") } if string(test) != cpContainerContents { - t.Errorf("output doesn't match the input for absolute symlink") + c.Errorf("output doesn't match the input for absolute symlink") } - logDone("cp - absolute symlink relative to container's rootfs") } // Test for #5619 // Check that symlinks which are part of the resource path are still relative to the container's rootfs -func TestCpSymlinkComponent(t *testing.T) { - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path") +func (s *DockerSuite) TestCpSymlinkComponent(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir -p '"+cpTestPath+"' && echo -n '"+cpContainerContents+"' > "+cpFullPath+" && ln -s "+cpTestPath+" container_path") if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } if err := os.MkdirAll(cpTestPath, os.ModeDir); err != nil { - t.Fatal(err) + c.Fatal(err) } hostFile, err := os.Create(cpFullPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer hostFile.Close() defer os.RemoveAll(cpTestPathParent) @@ -302,7 +299,7 @@ func TestCpSymlinkComponent(t *testing.T) { tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } tmpname := filepath.Join(tmpdir, cpTestName) @@ -310,268 +307,263 @@ func TestCpSymlinkComponent(t *testing.T) { path := path.Join("/", "container_path", cpTestName) - _, _ = dockerCmd(t, "cp", cleanedContainerID+":"+path, tmpdir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":"+path, tmpdir) file, _ := os.Open(tmpname) defer file.Close() test, err := ioutil.ReadAll(file) if err != nil { - t.Fatal(err) + c.Fatal(err) } if string(test) == cpHostContents { - t.Errorf("output matched host file -- symlink path component can escape container rootfs") + c.Errorf("output matched host file -- symlink path component can escape container rootfs") } if string(test) != cpContainerContents { - t.Errorf("output doesn't match the input for symlink path component") + c.Errorf("output doesn't match the input for symlink path component") } - logDone("cp - symlink path components relative to container's rootfs") } // Check that cp with unprivileged user doesn't return any error -func TestCpUnprivilegedUser(t *testing.T) { - testRequires(t, UnixCli) // uses chmod/su: not available on windows +func (s *DockerSuite) TestCpUnprivilegedUser(c *check.C) { + testRequires(c, UnixCli) // uses chmod/su: not available on windows - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName) + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName) if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpdir) if err = os.Chmod(tmpdir, 0777); err != nil { - t.Fatal(err) + c.Fatal(err) } path := cpTestName _, _, err = runCommandWithOutput(exec.Command("su", "unprivilegeduser", "-c", dockerBinary+" cp "+cleanedContainerID+":"+path+" "+tmpdir)) if err != nil { - t.Fatalf("couldn't copy with unprivileged user: %s:%s %s", cleanedContainerID, path, err) + c.Fatalf("couldn't copy with unprivileged user: %s:%s %s", cleanedContainerID, path, err) } - logDone("cp - unprivileged user") } -func TestCpSpecialFiles(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestCpSpecialFiles(c *check.C) { + testRequires(c, SameHostDaemon) outDir, err := ioutil.TempDir("", "cp-test-special-files") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(outDir) - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo") + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo") if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } // Copy actual /etc/resolv.conf - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/etc/resolv.conf", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/resolv.conf", outDir) expected, err := ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/resolv.conf") actual, err := ioutil.ReadFile(outDir + "/resolv.conf") if !bytes.Equal(actual, expected) { - t.Fatalf("Expected copied file to be duplicate of the container resolvconf") + c.Fatalf("Expected copied file to be duplicate of the container resolvconf") } // Copy actual /etc/hosts - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/etc/hosts", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/hosts", outDir) expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hosts") actual, err = ioutil.ReadFile(outDir + "/hosts") if !bytes.Equal(actual, expected) { - t.Fatalf("Expected copied file to be duplicate of the container hosts") + c.Fatalf("Expected copied file to be duplicate of the container hosts") } // Copy actual /etc/resolv.conf - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/etc/hostname", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/etc/hostname", outDir) expected, err = ioutil.ReadFile("/var/lib/docker/containers/" + cleanedContainerID + "/hostname") actual, err = ioutil.ReadFile(outDir + "/hostname") if !bytes.Equal(actual, expected) { - t.Fatalf("Expected copied file to be duplicate of the container resolvconf") + c.Fatalf("Expected copied file to be duplicate of the container resolvconf") } - logDone("cp - special files (resolv.conf, hosts, hostname)") } -func TestCpVolumePath(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestCpVolumePath(c *check.C) { + testRequires(c, SameHostDaemon) tmpDir, err := ioutil.TempDir("", "cp-test-volumepath") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir) outDir, err := ioutil.TempDir("", "cp-test-volumepath-out") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(outDir) _, err = os.Create(tmpDir + "/test") if err != nil { - t.Fatal(err) + c.Fatal(err) } - out, exitCode := dockerCmd(t, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar") + out, exitCode := dockerCmd(c, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar") if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) - defer dockerCmd(t, "rm", "-fv", cleanedContainerID) + defer dockerCmd(c, "rm", "-fv", cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } // Copy actual volume path - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/foo", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/foo", outDir) stat, err := os.Stat(outDir + "/foo") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !stat.IsDir() { - t.Fatal("expected copied content to be dir") + c.Fatal("expected copied content to be dir") } stat, err = os.Stat(outDir + "/foo/bar") if err != nil { - t.Fatal(err) + c.Fatal(err) } if stat.IsDir() { - t.Fatal("Expected file `bar` to be a file") + c.Fatal("Expected file `bar` to be a file") } // Copy file nested in volume - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/foo/bar", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/foo/bar", outDir) stat, err = os.Stat(outDir + "/bar") if err != nil { - t.Fatal(err) + c.Fatal(err) } if stat.IsDir() { - t.Fatal("Expected file `bar` to be a file") + c.Fatal("Expected file `bar` to be a file") } // Copy Bind-mounted dir - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/baz", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/baz", outDir) stat, err = os.Stat(outDir + "/baz") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !stat.IsDir() { - t.Fatal("Expected `baz` to be a dir") + c.Fatal("Expected `baz` to be a dir") } // Copy file nested in bind-mounted dir - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/baz/test", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/baz/test", outDir) fb, err := ioutil.ReadFile(outDir + "/baz/test") if err != nil { - t.Fatal(err) + c.Fatal(err) } fb2, err := ioutil.ReadFile(tmpDir + "/test") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(fb, fb2) { - t.Fatalf("Expected copied file to be duplicate of bind-mounted file") + c.Fatalf("Expected copied file to be duplicate of bind-mounted file") } // Copy bind-mounted file - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/test", outDir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/test", outDir) fb, err = ioutil.ReadFile(outDir + "/test") if err != nil { - t.Fatal(err) + c.Fatal(err) } fb2, err = ioutil.ReadFile(tmpDir + "/test") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(fb, fb2) { - t.Fatalf("Expected copied file to be duplicate of bind-mounted file") + c.Fatalf("Expected copied file to be duplicate of bind-mounted file") } - logDone("cp - volume path") } -func TestCpToDot(t *testing.T) { - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") +func (s *DockerSuite) TestCpToDot(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpdir) cwd, err := os.Getwd() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.Chdir(cwd) if err := os.Chdir(tmpdir); err != nil { - t.Fatal(err) + c.Fatal(err) } - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/test", ".") + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/test", ".") content, err := ioutil.ReadFile("./test") if string(content) != "lololol\n" { - t.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") + c.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") } - logDone("cp - to dot path") } -func TestCpToStdout(t *testing.T) { - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") +func (s *DockerSuite) TestCpToStdout(c *check.C) { + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test") if exitCode != 0 { - t.Fatalf("failed to create a container:%s\n", out) + c.Fatalf("failed to create a container:%s\n", out) } cID := strings.TrimSpace(out) defer deleteContainer(cID) - out, _ = dockerCmd(t, "wait", cID) + out, _ = dockerCmd(c, "wait", cID) if strings.TrimSpace(out) != "0" { - t.Fatalf("failed to set up container:%s\n", out) + c.Fatalf("failed to set up container:%s\n", out) } out, _, err := runCommandPipelineWithOutput( @@ -579,40 +571,38 @@ func TestCpToStdout(t *testing.T) { exec.Command("tar", "-vtf", "-")) if err != nil { - t.Fatalf("Failed to run commands: %s", err) + c.Fatalf("Failed to run commands: %s", err) } if !strings.Contains(out, "test") || !strings.Contains(out, "-rw") { - t.Fatalf("Missing file from tar TOC:\n%s", out) + c.Fatalf("Missing file from tar TOC:\n%s", out) } - logDone("cp - to stdout") } -func TestCpNameHasColon(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestCpNameHasColon(c *check.C) { + testRequires(c, SameHostDaemon) - out, exitCode := dockerCmd(t, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t") + out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t") if exitCode != 0 { - t.Fatal("failed to create a container", out) + c.Fatal("failed to create a container", out) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) - out, _ = dockerCmd(t, "wait", cleanedContainerID) + out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out) + c.Fatal("failed to set up container", out) } tmpdir, err := ioutil.TempDir("", "docker-integration") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpdir) - _, _ = dockerCmd(t, "cp", cleanedContainerID+":/te:s:t", tmpdir) + _, _ = dockerCmd(c, "cp", cleanedContainerID+":/te:s:t", tmpdir) content, err := ioutil.ReadFile(tmpdir + "/te:s:t") if string(content) != "lololol\n" { - t.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") + c.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n") } - logDone("cp - copy filename has ':'") } diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index a8cc09106..5fbd50b6d 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -6,20 +6,19 @@ import ( "os/exec" "reflect" "strings" - "testing" "time" "github.com/docker/docker/nat" + "github.com/go-check/check" ) // Make sure we can create a simple container with some args -func TestCreateArgs(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCreateArgs(c *check.C) { runCmd := exec.Command(dockerBinary, "create", "busybox", "command", "arg1", "arg2", "arg with space") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -27,7 +26,7 @@ func TestCreateArgs(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("out should've been a container id: %s, %v", out, err) + c.Fatalf("out should've been a container id: %s, %v", out, err) } containers := []struct { @@ -38,40 +37,38 @@ func TestCreateArgs(t *testing.T) { Image string }{} if err := json.Unmarshal([]byte(out), &containers); err != nil { - t.Fatalf("Error inspecting the container: %s", err) + c.Fatalf("Error inspecting the container: %s", err) } if len(containers) != 1 { - t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) + c.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) } - c := containers[0] - if c.Path != "command" { - t.Fatalf("Unexpected container path. Expected command, received: %s", c.Path) + cont := containers[0] + if cont.Path != "command" { + c.Fatalf("Unexpected container path. Expected command, received: %s", cont.Path) } b := false expected := []string{"arg1", "arg2", "arg with space"} for i, arg := range expected { - if arg != c.Args[i] { + if arg != cont.Args[i] { b = true break } } - if len(c.Args) != len(expected) || b { - t.Fatalf("Unexpected args. Expected %v, received: %v", expected, c.Args) + if len(cont.Args) != len(expected) || b { + c.Fatalf("Unexpected args. Expected %v, received: %v", expected, cont.Args) } - logDone("create - args") } // Make sure we can set hostconfig options too -func TestCreateHostConfig(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCreateHostConfig(c *check.C) { runCmd := exec.Command(dockerBinary, "create", "-P", "busybox", "echo") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -79,7 +76,7 @@ func TestCreateHostConfig(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("out should've been a container id: %s, %v", out, err) + c.Fatalf("out should've been a container id: %s, %v", out, err) } containers := []struct { @@ -88,31 +85,29 @@ func TestCreateHostConfig(t *testing.T) { } }{} if err := json.Unmarshal([]byte(out), &containers); err != nil { - t.Fatalf("Error inspecting the container: %s", err) + c.Fatalf("Error inspecting the container: %s", err) } if len(containers) != 1 { - t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) + c.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) } - c := containers[0] - if c.HostConfig == nil { - t.Fatalf("Expected HostConfig, got none") + cont := containers[0] + if cont.HostConfig == nil { + c.Fatalf("Expected HostConfig, got none") } - if !c.HostConfig.PublishAllPorts { - t.Fatalf("Expected PublishAllPorts, got false") + if !cont.HostConfig.PublishAllPorts { + c.Fatalf("Expected PublishAllPorts, got false") } - logDone("create - hostconfig") } -func TestCreateWithPortRange(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCreateWithPortRange(c *check.C) { runCmd := exec.Command(dockerBinary, "create", "-p", "3300-3303:3300-3303/tcp", "busybox", "echo") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -120,7 +115,7 @@ func TestCreateWithPortRange(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("out should've been a container id: %s, %v", out, err) + c.Fatalf("out should've been a container id: %s, %v", out, err) } containers := []struct { @@ -129,39 +124,37 @@ func TestCreateWithPortRange(t *testing.T) { } }{} if err := json.Unmarshal([]byte(out), &containers); err != nil { - t.Fatalf("Error inspecting the container: %s", err) + c.Fatalf("Error inspecting the container: %s", err) } if len(containers) != 1 { - t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) + c.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) } - c := containers[0] - if c.HostConfig == nil { - t.Fatalf("Expected HostConfig, got none") + cont := containers[0] + if cont.HostConfig == nil { + c.Fatalf("Expected HostConfig, got none") } - if len(c.HostConfig.PortBindings) != 4 { - t.Fatalf("Expected 4 ports bindings, got %d", len(c.HostConfig.PortBindings)) + if len(cont.HostConfig.PortBindings) != 4 { + c.Fatalf("Expected 4 ports bindings, got %d", len(cont.HostConfig.PortBindings)) } - for k, v := range c.HostConfig.PortBindings { + for k, v := range cont.HostConfig.PortBindings { if len(v) != 1 { - t.Fatalf("Expected 1 ports binding, for the port %s but found %s", k, v) + c.Fatalf("Expected 1 ports binding, for the port %s but found %s", k, v) } if k.Port() != v[0].HostPort { - t.Fatalf("Expected host port %d to match published port %d", k.Port(), v[0].HostPort) + c.Fatalf("Expected host port %d to match published port %d", k.Port(), v[0].HostPort) } } - logDone("create - port range") } -func TestCreateWithiLargePortRange(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCreateWithiLargePortRange(c *check.C) { runCmd := exec.Command(dockerBinary, "create", "-p", "1-65535:1-65535/tcp", "busybox", "echo") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -169,7 +162,7 @@ func TestCreateWithiLargePortRange(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("out should've been a container id: %s, %v", out, err) + c.Fatalf("out should've been a container id: %s, %v", out, err) } containers := []struct { @@ -178,40 +171,38 @@ func TestCreateWithiLargePortRange(t *testing.T) { } }{} if err := json.Unmarshal([]byte(out), &containers); err != nil { - t.Fatalf("Error inspecting the container: %s", err) + c.Fatalf("Error inspecting the container: %s", err) } if len(containers) != 1 { - t.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) + c.Fatalf("Unexpected container count. Expected 0, received: %d", len(containers)) } - c := containers[0] - if c.HostConfig == nil { - t.Fatalf("Expected HostConfig, got none") + cont := containers[0] + if cont.HostConfig == nil { + c.Fatalf("Expected HostConfig, got none") } - if len(c.HostConfig.PortBindings) != 65535 { - t.Fatalf("Expected 65535 ports bindings, got %d", len(c.HostConfig.PortBindings)) + if len(cont.HostConfig.PortBindings) != 65535 { + c.Fatalf("Expected 65535 ports bindings, got %d", len(cont.HostConfig.PortBindings)) } - for k, v := range c.HostConfig.PortBindings { + for k, v := range cont.HostConfig.PortBindings { if len(v) != 1 { - t.Fatalf("Expected 1 ports binding, for the port %s but found %s", k, v) + c.Fatalf("Expected 1 ports binding, for the port %s but found %s", k, v) } if k.Port() != v[0].HostPort { - t.Fatalf("Expected host port %d to match published port %d", k.Port(), v[0].HostPort) + c.Fatalf("Expected host port %d to match published port %d", k.Port(), v[0].HostPort) } } - logDone("create - large port range") } // "test123" should be printed by docker create + start -func TestCreateEchoStdout(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestCreateEchoStdout(c *check.C) { runCmd := exec.Command(dockerBinary, "create", "busybox", "echo", "test123") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -219,62 +210,58 @@ func TestCreateEchoStdout(t *testing.T) { runCmd = exec.Command(dockerBinary, "start", "-ai", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out != "test123\n" { - t.Errorf("container should've printed 'test123', got %q", out) + c.Errorf("container should've printed 'test123', got %q", out) } - logDone("create - echo test123") } -func TestCreateVolumesCreated(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestCreateVolumesCreated(c *check.C) { + testRequires(c, SameHostDaemon) name := "test_create_volume" if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-v", "/foo", "busybox")); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } dir, err := inspectFieldMap(name, "Volumes", "/foo") if err != nil { - t.Fatalf("Error getting volume host path: %q", err) + c.Fatalf("Error getting volume host path: %q", err) } if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { - t.Fatalf("Volume was not created") + c.Fatalf("Volume was not created") } if err != nil { - t.Fatalf("Error statting volume host path: %q", err) + c.Fatalf("Error statting volume host path: %q", err) } - logDone("create - volumes are created") } -func TestCreateLabels(t *testing.T) { +func (s *DockerSuite) TestCreateLabels(c *check.C) { name := "test_create_labels" expected := map[string]string{"k1": "v1", "k2": "v2"} if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k1=v1", "--label", "k2=v2", "busybox")); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } actual := make(map[string]string) err := inspectFieldAndMarshall(name, "Config.Labels", &actual) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !reflect.DeepEqual(expected, actual) { - t.Fatalf("Expected %s got %s", expected, actual) + c.Fatalf("Expected %s got %s", expected, actual) } deleteAllContainers() - logDone("create - labels") } -func TestCreateLabelFromImage(t *testing.T) { +func (s *DockerSuite) TestCreateLabelFromImage(c *check.C) { imageName := "testcreatebuildlabel" defer deleteImages(imageName) _, err := buildImage(imageName, @@ -282,34 +269,32 @@ func TestCreateLabelFromImage(t *testing.T) { LABEL k1=v1 k2=v2`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } name := "test_create_labels_from_image" expected := map[string]string{"k2": "x", "k3": "v3", "k1": "v1"} if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "--name", name, "-l", "k2=x", "--label", "k3=v3", imageName)); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } actual := make(map[string]string) err = inspectFieldAndMarshall(name, "Config.Labels", &actual) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !reflect.DeepEqual(expected, actual) { - t.Fatalf("Expected %s got %s", expected, actual) + c.Fatalf("Expected %s got %s", expected, actual) } deleteAllContainers() - logDone("create - labels from image") } -func TestCreateHostnameWithNumber(t *testing.T) { - out, _ := dockerCmd(t, "run", "-h", "web.0", "busybox", "hostname") +func (s *DockerSuite) TestCreateHostnameWithNumber(c *check.C) { + out, _ := dockerCmd(c, "run", "-h", "web.0", "busybox", "hostname") if strings.TrimSpace(out) != "web.0" { - t.Fatalf("hostname not set, expected `web.0`, got: %s", out) + c.Fatalf("hostname not set, expected `web.0`, got: %s", out) } - logDone("create - use hostname with number") } diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index d81ad3c80..81cf0ab0c 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -10,41 +10,41 @@ import ( "os/exec" "path/filepath" "strings" - "testing" "time" "github.com/docker/libtrust" + "github.com/go-check/check" ) -func TestDaemonRestartWithRunningContainersPorts(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonRestartWithRunningContainersPorts(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatalf("Could not start daemon with busybox: %v", err) + c.Fatalf("Could not start daemon with busybox: %v", err) } defer d.Stop() if out, err := d.Cmd("run", "-d", "--name", "top1", "-p", "1234:80", "--restart", "always", "busybox:latest", "top"); err != nil { - t.Fatalf("Could not run top1: err=%v\n%s", err, out) + c.Fatalf("Could not run top1: err=%v\n%s", err, out) } // --restart=no by default if out, err := d.Cmd("run", "-d", "--name", "top2", "-p", "80", "busybox:latest", "top"); err != nil { - t.Fatalf("Could not run top2: err=%v\n%s", err, out) + c.Fatalf("Could not run top2: err=%v\n%s", err, out) } testRun := func(m map[string]bool, prefix string) { var format string - for c, shouldRun := range m { + for cont, shouldRun := range m { out, err := d.Cmd("ps") if err != nil { - t.Fatalf("Could not run ps: err=%v\n%q", err, out) + c.Fatalf("Could not run ps: err=%v\n%q", err, out) } if shouldRun { format = "%scontainer %q is not running" } else { format = "%scontainer %q is running" } - if shouldRun != strings.Contains(out, c) { - t.Fatalf(format, prefix, c) + if shouldRun != strings.Contains(out, cont) { + c.Fatalf(format, prefix, cont) } } } @@ -52,102 +52,97 @@ func TestDaemonRestartWithRunningContainersPorts(t *testing.T) { testRun(map[string]bool{"top1": true, "top2": true}, "") if err := d.Restart(); err != nil { - t.Fatalf("Could not restart daemon: %v", err) + c.Fatalf("Could not restart daemon: %v", err) } testRun(map[string]bool{"top1": true, "top2": false}, "After daemon restart: ") - logDone("daemon - running containers on daemon restart") } -func TestDaemonRestartWithVolumesRefs(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonRestartWithVolumesRefs(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() if out, err := d.Cmd("run", "-d", "--name", "volrestarttest1", "-v", "/foo", "busybox"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := d.Restart(); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := d.Cmd("run", "-d", "--volumes-from", "volrestarttest1", "--name", "volrestarttest2", "busybox", "top"); err != nil { - t.Fatal(err) + c.Fatal(err) } if out, err := d.Cmd("rm", "-fv", "volrestarttest2"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } v, err := d.Cmd("inspect", "--format", "{{ json .Volumes }}", "volrestarttest1") if err != nil { - t.Fatal(err) + c.Fatal(err) } volumes := make(map[string]string) json.Unmarshal([]byte(v), &volumes) if _, err := os.Stat(volumes["/foo"]); err != nil { - t.Fatalf("Expected volume to exist: %s - %s", volumes["/foo"], err) + c.Fatalf("Expected volume to exist: %s - %s", volumes["/foo"], err) } - logDone("daemon - volume refs are restored") } -func TestDaemonStartIptablesFalse(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonStartIptablesFalse(c *check.C) { + d := NewDaemon(c) if err := d.Start("--iptables=false"); err != nil { - t.Fatalf("we should have been able to start the daemon with passing iptables=false: %v", err) + c.Fatalf("we should have been able to start the daemon with passing iptables=false: %v", err) } d.Stop() - logDone("daemon - started daemon with iptables=false") } // Issue #8444: If docker0 bridge is modified (intentionally or unintentionally) and // no longer has an IP associated, we should gracefully handle that case and associate // an IP with it rather than fail daemon start -func TestDaemonStartBridgeWithoutIPAssociation(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonStartBridgeWithoutIPAssociation(c *check.C) { + d := NewDaemon(c) // rather than depending on brctl commands to verify docker0 is created and up // let's start the daemon and stop it, and then make a modification to run the // actual test if err := d.Start(); err != nil { - t.Fatalf("Could not start daemon: %v", err) + c.Fatalf("Could not start daemon: %v", err) } if err := d.Stop(); err != nil { - t.Fatalf("Could not stop daemon: %v", err) + c.Fatalf("Could not stop daemon: %v", err) } // now we will remove the ip from docker0 and then try starting the daemon ipCmd := exec.Command("ip", "addr", "flush", "dev", "docker0") stdout, stderr, _, err := runCommandWithStdoutStderr(ipCmd) if err != nil { - t.Fatalf("failed to remove docker0 IP association: %v, stdout: %q, stderr: %q", err, stdout, stderr) + c.Fatalf("failed to remove docker0 IP association: %v, stdout: %q, stderr: %q", err, stdout, stderr) } if err := d.Start(); err != nil { warning := "**WARNING: Docker bridge network in bad state--delete docker0 bridge interface to fix" - t.Fatalf("Could not start daemon when docker0 has no IP address: %v\n%s", err, warning) + c.Fatalf("Could not start daemon when docker0 has no IP address: %v\n%s", err, warning) } // cleanup - stop the daemon if test passed if err := d.Stop(); err != nil { - t.Fatalf("Could not stop daemon: %v", err) + c.Fatalf("Could not stop daemon: %v", err) } - logDone("daemon - successful daemon start when bridge has no IP association") } -func TestDaemonIptablesClean(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestDaemonIptablesClean(c *check.C) { - d := NewDaemon(t) + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatalf("Could not start daemon with busybox: %v", err) + c.Fatalf("Could not start daemon with busybox: %v", err) } defer d.Stop() if out, err := d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { - t.Fatalf("Could not run top: %s, %v", out, err) + c.Fatalf("Could not run top: %s, %v", out, err) } // get output from iptables with container running @@ -155,42 +150,40 @@ func TestDaemonIptablesClean(t *testing.T) { ipTablesCmd := exec.Command("iptables", "-nvL") out, _, err := runCommandWithOutput(ipTablesCmd) if err != nil { - t.Fatalf("Could not run iptables -nvL: %s, %v", out, err) + c.Fatalf("Could not run iptables -nvL: %s, %v", out, err) } if !strings.Contains(out, ipTablesSearchString) { - t.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out) + c.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out) } if err := d.Stop(); err != nil { - t.Fatalf("Could not stop daemon: %v", err) + c.Fatalf("Could not stop daemon: %v", err) } // get output from iptables after restart ipTablesCmd = exec.Command("iptables", "-nvL") out, _, err = runCommandWithOutput(ipTablesCmd) if err != nil { - t.Fatalf("Could not run iptables -nvL: %s, %v", out, err) + c.Fatalf("Could not run iptables -nvL: %s, %v", out, err) } if strings.Contains(out, ipTablesSearchString) { - t.Fatalf("iptables output should not have contained %q, but was %q", ipTablesSearchString, out) + c.Fatalf("iptables output should not have contained %q, but was %q", ipTablesSearchString, out) } - logDone("daemon - run,iptables - iptables rules cleaned after daemon restart") } -func TestDaemonIptablesCreate(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestDaemonIptablesCreate(c *check.C) { - d := NewDaemon(t) + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatalf("Could not start daemon with busybox: %v", err) + c.Fatalf("Could not start daemon with busybox: %v", err) } defer d.Stop() if out, err := d.Cmd("run", "-d", "--name", "top", "--restart=always", "-p", "80", "busybox:latest", "top"); err != nil { - t.Fatalf("Could not run top: %s, %v", out, err) + c.Fatalf("Could not run top: %s, %v", out, err) } // get output from iptables with container running @@ -198,101 +191,99 @@ func TestDaemonIptablesCreate(t *testing.T) { ipTablesCmd := exec.Command("iptables", "-nvL") out, _, err := runCommandWithOutput(ipTablesCmd) if err != nil { - t.Fatalf("Could not run iptables -nvL: %s, %v", out, err) + c.Fatalf("Could not run iptables -nvL: %s, %v", out, err) } if !strings.Contains(out, ipTablesSearchString) { - t.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out) + c.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out) } if err := d.Restart(); err != nil { - t.Fatalf("Could not restart daemon: %v", err) + c.Fatalf("Could not restart daemon: %v", err) } // make sure the container is not running runningOut, err := d.Cmd("inspect", "--format='{{.State.Running}}'", "top") if err != nil { - t.Fatalf("Could not inspect on container: %s, %v", out, err) + c.Fatalf("Could not inspect on container: %s, %v", out, err) } if strings.TrimSpace(runningOut) != "true" { - t.Fatalf("Container should have been restarted after daemon restart. Status running should have been true but was: %q", strings.TrimSpace(runningOut)) + c.Fatalf("Container should have been restarted after daemon restart. Status running should have been true but was: %q", strings.TrimSpace(runningOut)) } // get output from iptables after restart ipTablesCmd = exec.Command("iptables", "-nvL") out, _, err = runCommandWithOutput(ipTablesCmd) if err != nil { - t.Fatalf("Could not run iptables -nvL: %s, %v", out, err) + c.Fatalf("Could not run iptables -nvL: %s, %v", out, err) } if !strings.Contains(out, ipTablesSearchString) { - t.Fatalf("iptables output after restart should have contained %q, but was %q", ipTablesSearchString, out) + c.Fatalf("iptables output after restart should have contained %q, but was %q", ipTablesSearchString, out) } - logDone("daemon - run,iptables - iptables rules for always restarted container created after daemon restart") } -func TestDaemonLoggingLevel(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonLoggingLevel(c *check.C) { + d := NewDaemon(c) if err := d.Start("--log-level=bogus"); err == nil { - t.Fatal("Daemon should not have been able to start") + c.Fatal("Daemon should not have been able to start") } - d = NewDaemon(t) + d = NewDaemon(c) if err := d.Start("--log-level=debug"); err != nil { - t.Fatal(err) + c.Fatal(err) } d.Stop() content, _ := ioutil.ReadFile(d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { - t.Fatalf(`Missing level="debug" in log file:\n%s`, string(content)) + c.Fatalf(`Missing level="debug" in log file:\n%s`, string(content)) } - d = NewDaemon(t) + d = NewDaemon(c) if err := d.Start("--log-level=fatal"); err != nil { - t.Fatal(err) + c.Fatal(err) } d.Stop() content, _ = ioutil.ReadFile(d.logFile.Name()) if strings.Contains(string(content), `level=debug`) { - t.Fatalf(`Should not have level="debug" in log file:\n%s`, string(content)) + c.Fatalf(`Should not have level="debug" in log file:\n%s`, string(content)) } - d = NewDaemon(t) + d = NewDaemon(c) if err := d.Start("-D"); err != nil { - t.Fatal(err) + c.Fatal(err) } d.Stop() content, _ = ioutil.ReadFile(d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { - t.Fatalf(`Missing level="debug" in log file using -D:\n%s`, string(content)) + c.Fatalf(`Missing level="debug" in log file using -D:\n%s`, string(content)) } - d = NewDaemon(t) + d = NewDaemon(c) if err := d.Start("--debug"); err != nil { - t.Fatal(err) + c.Fatal(err) } d.Stop() content, _ = ioutil.ReadFile(d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { - t.Fatalf(`Missing level="debug" in log file using --debug:\n%s`, string(content)) + c.Fatalf(`Missing level="debug" in log file using --debug:\n%s`, string(content)) } - d = NewDaemon(t) + d = NewDaemon(c) if err := d.Start("--debug", "--log-level=fatal"); err != nil { - t.Fatal(err) + c.Fatal(err) } d.Stop() content, _ = ioutil.ReadFile(d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { - t.Fatalf(`Missing level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content)) + c.Fatalf(`Missing level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content)) } - logDone("daemon - Logging Level") } -func TestDaemonAllocatesListeningPort(t *testing.T) { +func (s *DockerSuite) TestDaemonAllocatesListeningPort(c *check.C) { listeningPorts := [][]string{ {"0.0.0.0", "0.0.0.0", "5678"}, {"127.0.0.1", "127.0.0.1", "1234"}, @@ -304,120 +295,116 @@ func TestDaemonAllocatesListeningPort(t *testing.T) { cmdArgs = append(cmdArgs, "--host", fmt.Sprintf("tcp://%s:%s", hostDirective[0], hostDirective[2])) } - d := NewDaemon(t) + d := NewDaemon(c) if err := d.StartWithBusybox(cmdArgs...); err != nil { - t.Fatalf("Could not start daemon with busybox: %v", err) + c.Fatalf("Could not start daemon with busybox: %v", err) } defer d.Stop() for _, hostDirective := range listeningPorts { output, err := d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", hostDirective[1], hostDirective[2]), "busybox", "true") if err == nil { - t.Fatalf("Container should not start, expected port already allocated error: %q", output) + c.Fatalf("Container should not start, expected port already allocated error: %q", output) } else if !strings.Contains(output, "port is already allocated") { - t.Fatalf("Expected port is already allocated error: %q", output) + c.Fatalf("Expected port is already allocated error: %q", output) } } - logDone("daemon - daemon listening port is allocated") } // #9629 -func TestDaemonVolumesBindsRefs(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonVolumesBindsRefs(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() tmp, err := ioutil.TempDir(os.TempDir(), "") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmp) if err := ioutil.WriteFile(tmp+"/test", []byte("testing"), 0655); err != nil { - t.Fatal(err) + c.Fatal(err) } if out, err := d.Cmd("create", "-v", tmp+":/foo", "--name=voltest", "busybox"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := d.Restart(); err != nil { - t.Fatal(err) + c.Fatal(err) } if out, err := d.Cmd("run", "--volumes-from=voltest", "--name=consumer", "busybox", "/bin/sh", "-c", "[ -f /foo/test ]"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - logDone("daemon - bind refs in data-containers survive daemon restart") } -func TestDaemonKeyGeneration(t *testing.T) { +func (s *DockerSuite) TestDaemonKeyGeneration(c *check.C) { // TODO: skip or update for Windows daemon os.Remove("/etc/docker/key.json") - d := NewDaemon(t) + d := NewDaemon(c) if err := d.Start(); err != nil { - t.Fatalf("Could not start daemon: %v", err) + c.Fatalf("Could not start daemon: %v", err) } d.Stop() k, err := libtrust.LoadKeyFile("/etc/docker/key.json") if err != nil { - t.Fatalf("Error opening key file") + c.Fatalf("Error opening key file") } kid := k.KeyID() // Test Key ID is a valid fingerprint (e.g. QQXN:JY5W:TBXI:MK3X:GX6P:PD5D:F56N:NHCS:LVRZ:JA46:R24J:XEFF) if len(kid) != 59 { - t.Fatalf("Bad key ID: %s", kid) + c.Fatalf("Bad key ID: %s", kid) } - logDone("daemon - key generation") } -func TestDaemonKeyMigration(t *testing.T) { +func (s *DockerSuite) TestDaemonKeyMigration(c *check.C) { // TODO: skip or update for Windows daemon os.Remove("/etc/docker/key.json") k1, err := libtrust.GenerateECP256PrivateKey() if err != nil { - t.Fatalf("Error generating private key: %s", err) + c.Fatalf("Error generating private key: %s", err) } if err := os.MkdirAll(filepath.Join(os.Getenv("HOME"), ".docker"), 0755); err != nil { - t.Fatalf("Error creating .docker directory: %s", err) + c.Fatalf("Error creating .docker directory: %s", err) } if err := libtrust.SaveKey(filepath.Join(os.Getenv("HOME"), ".docker", "key.json"), k1); err != nil { - t.Fatalf("Error saving private key: %s", err) + c.Fatalf("Error saving private key: %s", err) } - d := NewDaemon(t) + d := NewDaemon(c) if err := d.Start(); err != nil { - t.Fatalf("Could not start daemon: %v", err) + c.Fatalf("Could not start daemon: %v", err) } d.Stop() k2, err := libtrust.LoadKeyFile("/etc/docker/key.json") if err != nil { - t.Fatalf("Error opening key file") + c.Fatalf("Error opening key file") } if k1.KeyID() != k2.KeyID() { - t.Fatalf("Key not migrated") + c.Fatalf("Key not migrated") } - logDone("daemon - key migration") } // Simulate an older daemon (pre 1.3) coming up with volumes specified in containers // without corresponding volume json -func TestDaemonUpgradeWithVolumes(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonUpgradeWithVolumes(c *check.C) { + d := NewDaemon(c) graphDir := filepath.Join(os.TempDir(), "docker-test") defer os.RemoveAll(graphDir) if err := d.StartWithBusybox("-g", graphDir); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() @@ -425,199 +412,195 @@ func TestDaemonUpgradeWithVolumes(t *testing.T) { defer os.RemoveAll(tmpDir) if out, err := d.Cmd("create", "-v", tmpDir+":/foo", "--name=test", "busybox"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := d.Stop(); err != nil { - t.Fatal(err) + c.Fatal(err) } // Remove this since we're expecting the daemon to re-create it too if err := os.RemoveAll(tmpDir); err != nil { - t.Fatal(err) + c.Fatal(err) } configDir := filepath.Join(graphDir, "volumes") if err := os.RemoveAll(configDir); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := d.Start("-g", graphDir); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := os.Stat(tmpDir); os.IsNotExist(err) { - t.Fatalf("expected volume path %s to exist but it does not", tmpDir) + c.Fatalf("expected volume path %s to exist but it does not", tmpDir) } dir, err := ioutil.ReadDir(configDir) if err != nil { - t.Fatal(err) + c.Fatal(err) } if len(dir) == 0 { - t.Fatalf("expected volumes config dir to contain data for new volume") + c.Fatalf("expected volumes config dir to contain data for new volume") } // Now with just removing the volume config and not the volume data if err := d.Stop(); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := os.RemoveAll(configDir); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := d.Start("-g", graphDir); err != nil { - t.Fatal(err) + c.Fatal(err) } dir, err = ioutil.ReadDir(configDir) if err != nil { - t.Fatal(err) + c.Fatal(err) } if len(dir) == 0 { - t.Fatalf("expected volumes config dir to contain data for new volume") + c.Fatalf("expected volumes config dir to contain data for new volume") } - logDone("daemon - volumes from old(pre 1.3) daemon work") } // GH#11320 - verify that the daemon exits on failure properly // Note that this explicitly tests the conflict of {-b,--bridge} and {--bip} options as the means // to get a daemon init failure; no other tests for -b/--bip conflict are therefore required -func TestDaemonExitOnFailure(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonExitOnFailure(c *check.C) { + d := NewDaemon(c) defer d.Stop() //attempt to start daemon with incorrect flags (we know -b and --bip conflict) if err := d.Start("--bridge", "nosuchbridge", "--bip", "1.1.1.1"); err != nil { //verify we got the right error if !strings.Contains(err.Error(), "Daemon exited and never started") { - t.Fatalf("Expected daemon not to start, got %v", err) + c.Fatalf("Expected daemon not to start, got %v", err) } // look in the log and make sure we got the message that daemon is shutting down runCmd := exec.Command("grep", "Error starting daemon", d.LogfileName()) if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatalf("Expected 'Error starting daemon' message; but doesn't exist in log: %q, err: %v", out, err) + c.Fatalf("Expected 'Error starting daemon' message; but doesn't exist in log: %q, err: %v", out, err) } } else { //if we didn't get an error and the daemon is running, this is a failure d.Stop() - t.Fatal("Conflicting options should cause the daemon to error out with a failure") + c.Fatal("Conflicting options should cause the daemon to error out with a failure") } - logDone("daemon - verify no start on daemon init errors") } -func TestDaemonUlimitDefaults(t *testing.T) { - testRequires(t, NativeExecDriver) - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonUlimitDefaults(c *check.C) { + testRequires(c, NativeExecDriver) + d := NewDaemon(c) if err := d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() out, err := d.Cmd("run", "--ulimit", "nproc=2048", "--name=test", "busybox", "/bin/sh", "-c", "echo $(ulimit -n); echo $(ulimit -p)") if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } outArr := strings.Split(out, "\n") if len(outArr) < 2 { - t.Fatalf("got unexpected output: %s", out) + c.Fatalf("got unexpected output: %s", out) } nofile := strings.TrimSpace(outArr[0]) nproc := strings.TrimSpace(outArr[1]) if nofile != "42" { - t.Fatalf("expected `ulimit -n` to be `42`, got: %s", nofile) + c.Fatalf("expected `ulimit -n` to be `42`, got: %s", nofile) } if nproc != "2048" { - t.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc) + c.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc) } // Now restart daemon with a new default if err := d.Restart("--default-ulimit", "nofile=43"); err != nil { - t.Fatal(err) + c.Fatal(err) } out, err = d.Cmd("start", "-a", "test") if err != nil { - t.Fatal(err) + c.Fatal(err) } outArr = strings.Split(out, "\n") if len(outArr) < 2 { - t.Fatalf("got unexpected output: %s", out) + c.Fatalf("got unexpected output: %s", out) } nofile = strings.TrimSpace(outArr[0]) nproc = strings.TrimSpace(outArr[1]) if nofile != "43" { - t.Fatalf("expected `ulimit -n` to be `43`, got: %s", nofile) + c.Fatalf("expected `ulimit -n` to be `43`, got: %s", nofile) } if nproc != "2048" { - t.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc) + c.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc) } - logDone("daemon - default ulimits are applied") } // #11315 -func TestDaemonRestartRenameContainer(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonRestartRenameContainer(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() if out, err := d.Cmd("run", "--name=test", "busybox"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if out, err := d.Cmd("rename", "test", "test2"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := d.Restart(); err != nil { - t.Fatal(err) + c.Fatal(err) } if out, err := d.Cmd("start", "test2"); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - logDone("daemon - rename persists through daemon restart") } -func TestDaemonLoggingDriverDefault(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonLoggingDriverDefault(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() out, err := d.Cmd("run", "-d", "busybox", "echo", "testline") if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id := strings.TrimSpace(out) if out, err := d.Cmd("wait", id); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err != nil { - t.Fatal(err) + c.Fatal(err) } f, err := os.Open(logPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } var res struct { Log string `json:"log"` @@ -625,95 +608,92 @@ func TestDaemonLoggingDriverDefault(t *testing.T) { Time time.Time `json:"time"` } if err := json.NewDecoder(f).Decode(&res); err != nil { - t.Fatal(err) + c.Fatal(err) } if res.Log != "testline\n" { - t.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n") + c.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n") } if res.Stream != "stdout" { - t.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout") + c.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout") } if !time.Now().After(res.Time) { - t.Fatalf("Log time %v in future", res.Time) + c.Fatalf("Log time %v in future", res.Time) } - logDone("daemon - default 'json-file' logging driver") } -func TestDaemonLoggingDriverDefaultOverride(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonLoggingDriverDefaultOverride(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() out, err := d.Cmd("run", "-d", "--log-driver=none", "busybox", "echo", "testline") if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id := strings.TrimSpace(out) if out, err := d.Cmd("wait", id); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) { - t.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err) + c.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err) } - logDone("daemon - default logging driver override in run") } -func TestDaemonLoggingDriverNone(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonLoggingDriverNone(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox("--log-driver=none"); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() out, err := d.Cmd("run", "-d", "busybox", "echo", "testline") if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id := strings.TrimSpace(out) if out, err := d.Cmd("wait", id); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) { - t.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err) + c.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err) } - logDone("daemon - 'none' logging driver") } -func TestDaemonLoggingDriverNoneOverride(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonLoggingDriverNoneOverride(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox("--log-driver=none"); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() out, err := d.Cmd("run", "-d", "--log-driver=json-file", "busybox", "echo", "testline") if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id := strings.TrimSpace(out) if out, err := d.Cmd("wait", id); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err != nil { - t.Fatal(err) + c.Fatal(err) } f, err := os.Open(logPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } var res struct { Log string `json:"log"` @@ -721,63 +701,60 @@ func TestDaemonLoggingDriverNoneOverride(t *testing.T) { Time time.Time `json:"time"` } if err := json.NewDecoder(f).Decode(&res); err != nil { - t.Fatal(err) + c.Fatal(err) } if res.Log != "testline\n" { - t.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n") + c.Fatalf("Unexpected log line: %q, expected: %q", res.Log, "testline\n") } if res.Stream != "stdout" { - t.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout") + c.Fatalf("Unexpected stream: %q, expected: %q", res.Stream, "stdout") } if !time.Now().After(res.Time) { - t.Fatalf("Log time %v in future", res.Time) + c.Fatalf("Log time %v in future", res.Time) } - logDone("daemon - 'none' logging driver override in run") } -func TestDaemonLoggingDriverNoneLogsError(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonLoggingDriverNoneLogsError(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox("--log-driver=none"); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() out, err := d.Cmd("run", "-d", "busybox", "echo", "testline") if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id := strings.TrimSpace(out) out, err = d.Cmd("logs", id) if err == nil { - t.Fatalf("Logs should fail with \"none\" driver") + c.Fatalf("Logs should fail with \"none\" driver") } if !strings.Contains(out, `\"logs\" command is supported only for \"json-file\" logging driver`) { - t.Fatalf("There should be error about non-json-file driver, got %s", out) + c.Fatalf("There should be error about non-json-file driver, got %s", out) } - logDone("daemon - logs not available for non-json-file drivers") } -func TestDaemonDots(t *testing.T) { - defer deleteAllContainers() - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonDots(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() // Now create 4 containers if _, err := d.Cmd("create", "busybox"); err != nil { - t.Fatalf("Error creating container: %q", err) + c.Fatalf("Error creating container: %q", err) } if _, err := d.Cmd("create", "busybox"); err != nil { - t.Fatalf("Error creating container: %q", err) + c.Fatalf("Error creating container: %q", err) } if _, err := d.Cmd("create", "busybox"); err != nil { - t.Fatalf("Error creating container: %q", err) + c.Fatalf("Error creating container: %q", err) } if _, err := d.Cmd("create", "busybox"); err != nil { - t.Fatalf("Error creating container: %q", err) + c.Fatalf("Error creating container: %q", err) } d.Stop() @@ -786,56 +763,54 @@ func TestDaemonDots(t *testing.T) { d.Stop() content, _ := ioutil.ReadFile(d.logFile.Name()) if strings.Contains(string(content), "....") { - t.Fatalf("Debug level should not have ....\n%s", string(content)) + c.Fatalf("Debug level should not have ....\n%s", string(content)) } d.Start("--log-level=error") d.Stop() content, _ = ioutil.ReadFile(d.logFile.Name()) if strings.Contains(string(content), "....") { - t.Fatalf("Error level should not have ....\n%s", string(content)) + c.Fatalf("Error level should not have ....\n%s", string(content)) } d.Start("--log-level=info") d.Stop() content, _ = ioutil.ReadFile(d.logFile.Name()) if !strings.Contains(string(content), "....") { - t.Fatalf("Info level should have ....\n%s", string(content)) + c.Fatalf("Info level should have ....\n%s", string(content)) } - logDone("daemon - test dots on INFO") } -func TestDaemonUnixSockCleanedUp(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonUnixSockCleanedUp(c *check.C) { + d := NewDaemon(c) dir, err := ioutil.TempDir("", "socket-cleanup-test") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(dir) sockPath := filepath.Join(dir, "docker.sock") if err := d.Start("--host", "unix://"+sockPath); err != nil { - t.Fatal(err) + c.Fatal(err) } defer d.Stop() if _, err := os.Stat(sockPath); err != nil { - t.Fatal("socket does not exist") + c.Fatal("socket does not exist") } if err := d.Stop(); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := os.Stat(sockPath); err == nil || !os.IsNotExist(err) { - t.Fatal("unix socket is not cleaned up") + c.Fatal("unix socket is not cleaned up") } - logDone("daemon - unix socket is cleaned up") } -func TestDaemonwithwrongkey(t *testing.T) { +func (s *DockerSuite) TestDaemonwithwrongkey(c *check.C) { type Config struct { Crv string `json:"crv"` D string `json:"d"` @@ -846,24 +821,24 @@ func TestDaemonwithwrongkey(t *testing.T) { } os.Remove("/etc/docker/key.json") - d := NewDaemon(t) + d := NewDaemon(c) if err := d.Start(); err != nil { - t.Fatalf("Failed to start daemon: %v", err) + c.Fatalf("Failed to start daemon: %v", err) } if err := d.Stop(); err != nil { - t.Fatalf("Could not stop daemon: %v", err) + c.Fatalf("Could not stop daemon: %v", err) } config := &Config{} bytes, err := ioutil.ReadFile("/etc/docker/key.json") if err != nil { - t.Fatalf("Error reading key.json file: %s", err) + c.Fatalf("Error reading key.json file: %s", err) } // byte[] to Data-Struct if err := json.Unmarshal(bytes, &config); err != nil { - t.Fatalf("Error Unmarshal: %s", err) + c.Fatalf("Error Unmarshal: %s", err) } //replace config.Kid with the fake value @@ -872,50 +847,49 @@ func TestDaemonwithwrongkey(t *testing.T) { // NEW Data-Struct to byte[] newBytes, err := json.Marshal(&config) if err != nil { - t.Fatalf("Error Marshal: %s", err) + c.Fatalf("Error Marshal: %s", err) } // write back if err := ioutil.WriteFile("/etc/docker/key.json", newBytes, 0400); err != nil { - t.Fatalf("Error ioutil.WriteFile: %s", err) + c.Fatalf("Error ioutil.WriteFile: %s", err) } - d1 := NewDaemon(t) + d1 := NewDaemon(c) defer os.Remove("/etc/docker/key.json") if err := d1.Start(); err == nil { d1.Stop() - t.Fatalf("It should not be succssful to start daemon with wrong key: %v", err) + c.Fatalf("It should not be succssful to start daemon with wrong key: %v", err) } content, _ := ioutil.ReadFile(d1.logFile.Name()) if !strings.Contains(string(content), "Public Key ID does not match") { - t.Fatal("Missing KeyID message from daemon logs") + c.Fatal("Missing KeyID message from daemon logs") } - logDone("daemon - it should be failed to start daemon with wrong key") } -func TestDaemonRestartKillWait(t *testing.T) { - d := NewDaemon(t) +func (s *DockerSuite) TestDaemonRestartKillWait(c *check.C) { + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatalf("Could not start daemon with busybox: %v", err) + c.Fatalf("Could not start daemon with busybox: %v", err) } defer d.Stop() out, err := d.Cmd("run", "-id", "busybox", "/bin/cat") if err != nil { - t.Fatalf("Could not run /bin/cat: err=%v\n%s", err, out) + c.Fatalf("Could not run /bin/cat: err=%v\n%s", err, out) } containerID := strings.TrimSpace(out) if out, err := d.Cmd("kill", containerID); err != nil { - t.Fatalf("Could not kill %s: err=%v\n%s", containerID, err, out) + c.Fatalf("Could not kill %s: err=%v\n%s", containerID, err, out) } if err := d.Restart(); err != nil { - t.Fatalf("Could not restart daemon: %v", err) + c.Fatalf("Could not restart daemon: %v", err) } errchan := make(chan error) @@ -928,12 +902,11 @@ func TestDaemonRestartKillWait(t *testing.T) { select { case <-time.After(5 * time.Second): - t.Fatal("Waiting on a stopped (killed) container timed out") + c.Fatal("Waiting on a stopped (killed) container timed out") case err := <-errchan: if err != nil { - t.Fatal(err) + c.Fatal(err) } } - logDone("wait - wait on a stopped container doesn't timeout") } diff --git a/integration-cli/docker_cli_diff_test.go b/integration-cli/docker_cli_diff_test.go index f7f8cd7a9..332b128ed 100644 --- a/integration-cli/docker_cli_diff_test.go +++ b/integration-cli/docker_cli_diff_test.go @@ -3,16 +3,17 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // ensure that an added file shows up in docker diff -func TestDiffFilenameShownInOutput(t *testing.T) { +func (s *DockerSuite) TestDiffFilenameShownInOutput(c *check.C) { containerCmd := `echo foo > /root/bar` runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", containerCmd) out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to start the container: %s, %v", out, err) + c.Fatalf("failed to start the container: %s, %v", out, err) } cleanCID := strings.TrimSpace(out) @@ -20,7 +21,7 @@ func TestDiffFilenameShownInOutput(t *testing.T) { diffCmd := exec.Command(dockerBinary, "diff", cleanCID) out, _, err = runCommandWithOutput(diffCmd) if err != nil { - t.Fatalf("failed to run diff: %s %v", out, err) + c.Fatalf("failed to run diff: %s %v", out, err) } found := false @@ -31,15 +32,12 @@ func TestDiffFilenameShownInOutput(t *testing.T) { } } if !found { - t.Errorf("couldn't find the new file in docker diff's output: %v", out) + c.Errorf("couldn't find the new file in docker diff's output: %v", out) } - 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) { +func (s *DockerSuite) TestDiffEnsureDockerinitFilesAreIgnored(c *check.C) { // 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"} @@ -49,7 +47,7 @@ func TestDiffEnsureDockerinitFilesAreIgnored(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", containerCmd) out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanCID := strings.TrimSpace(out) @@ -57,26 +55,22 @@ func TestDiffEnsureDockerinitFilesAreIgnored(t *testing.T) { diffCmd := exec.Command(dockerBinary, "diff", cleanCID) out, _, err = runCommandWithOutput(diffCmd) if err != nil { - t.Fatalf("failed to run diff: %s, %v", out, err) + c.Fatalf("failed to run diff: %s, %v", out, err) } - 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) + c.Errorf("found file which should've been ignored %v in diff output", filename) } } } - - logDone("diff - check if ignored files show up in diff") } -func TestDiffEnsureOnlyKmsgAndPtmx(t *testing.T) { +func (s *DockerSuite) TestDiffEnsureOnlyKmsgAndPtmx(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sleep", "0") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanCID := strings.TrimSpace(out) @@ -84,9 +78,8 @@ func TestDiffEnsureOnlyKmsgAndPtmx(t *testing.T) { diffCmd := exec.Command(dockerBinary, "diff", cleanCID) out, _, err = runCommandWithOutput(diffCmd) if err != nil { - t.Fatalf("failed to run diff: %s, %v", out, err) + c.Fatalf("failed to run diff: %s, %v", out, err) } - deleteContainer(cleanCID) expected := map[string]bool{ "C /dev": true, @@ -109,9 +102,7 @@ func TestDiffEnsureOnlyKmsgAndPtmx(t *testing.T) { for _, line := range strings.Split(out, "\n") { if line != "" && !expected[line] { - t.Errorf("%q is shown in the diff but shouldn't", line) + c.Errorf("%q is shown in the diff but shouldn't", line) } } - - logDone("diff - ensure that only kmsg and ptmx in diff") } diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 3e4c005b2..b8e24260a 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -7,20 +7,21 @@ import ( "regexp" "strconv" "strings" - "testing" "time" + + "github.com/go-check/check" ) -func TestEventsUntag(t *testing.T) { +func (s *DockerSuite) TestEventsUntag(c *check.C) { image := "busybox" - dockerCmd(t, "tag", image, "utest:tag1") - dockerCmd(t, "tag", image, "utest:tag2") - dockerCmd(t, "rmi", "utest:tag1") - dockerCmd(t, "rmi", "utest:tag2") + dockerCmd(c, "tag", image, "utest:tag1") + dockerCmd(c, "tag", image, "utest:tag2") + dockerCmd(c, "rmi", "utest:tag1") + dockerCmd(c, "rmi", "utest:tag2") eventsCmd := exec.Command(dockerBinary, "events", "--since=1") out, exitCode, _, err := runCommandWithOutputForDuration(eventsCmd, time.Duration(time.Millisecond*200)) if exitCode != 0 || err != nil { - t.Fatalf("Failed to get events - exit code %d: %s", exitCode, err) + c.Fatalf("Failed to get events - exit code %d: %s", exitCode, err) } events := strings.Split(out, "\n") nEvents := len(events) @@ -29,126 +30,119 @@ func TestEventsUntag(t *testing.T) { // looking for. for _, v := range events[nEvents-3 : nEvents-1] { if !strings.Contains(v, "untag") { - t.Fatalf("event should be untag, not %#v", v) + c.Fatalf("event should be untag, not %#v", v) } } - logDone("events - untags are logged") } -func TestEventsContainerFailStartDie(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestEventsContainerFailStartDie(c *check.C) { - out, _ := dockerCmd(t, "images", "-q") + out, _ := dockerCmd(c, "images", "-q") image := strings.Split(out, "\n")[0] eventsCmd := exec.Command(dockerBinary, "run", "--name", "testeventdie", image, "blerg") _, _, err := runCommandWithOutput(eventsCmd) if err == nil { - t.Fatalf("Container run with command blerg should have failed, but it did not") + c.Fatalf("Container run with command blerg should have failed, but it did not") } - eventsCmd = exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + eventsCmd = exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ = runCommandWithOutput(eventsCmd) events := strings.Split(out, "\n") if len(events) <= 1 { - t.Fatalf("Missing expected event") + c.Fatalf("Missing expected event") } startEvent := strings.Fields(events[len(events)-3]) dieEvent := strings.Fields(events[len(events)-2]) if startEvent[len(startEvent)-1] != "start" { - t.Fatalf("event should be start, not %#v", startEvent) + c.Fatalf("event should be start, not %#v", startEvent) } if dieEvent[len(dieEvent)-1] != "die" { - t.Fatalf("event should be die, not %#v", dieEvent) + c.Fatalf("event should be die, not %#v", dieEvent) } - logDone("events - container unwilling to start logs die") } -func TestEventsLimit(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestEventsLimit(c *check.C) { for i := 0; i < 30; i++ { - dockerCmd(t, "run", "busybox", "echo", strconv.Itoa(i)) + dockerCmd(c, "run", "busybox", "echo", strconv.Itoa(i)) } - eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ := runCommandWithOutput(eventsCmd) events := strings.Split(out, "\n") nEvents := len(events) - 1 if nEvents != 64 { - t.Fatalf("events should be limited to 64, but received %d", nEvents) + c.Fatalf("events should be limited to 64, but received %d", nEvents) } - logDone("events - limited to 64 entries") } -func TestEventsContainerEvents(t *testing.T) { - dockerCmd(t, "run", "--rm", "busybox", "true") - eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix())) +func (s *DockerSuite) TestEventsContainerEvents(c *check.C) { + dockerCmd(c, "run", "--rm", "busybox", "true") + eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, exitCode, err := runCommandWithOutput(eventsCmd) if exitCode != 0 || err != nil { - t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) + c.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) } events := strings.Split(out, "\n") events = events[:len(events)-1] if len(events) < 4 { - t.Fatalf("Missing expected event") + c.Fatalf("Missing expected event") } createEvent := strings.Fields(events[len(events)-4]) startEvent := strings.Fields(events[len(events)-3]) dieEvent := strings.Fields(events[len(events)-2]) destroyEvent := strings.Fields(events[len(events)-1]) if createEvent[len(createEvent)-1] != "create" { - t.Fatalf("event should be create, not %#v", createEvent) + c.Fatalf("event should be create, not %#v", createEvent) } if startEvent[len(startEvent)-1] != "start" { - t.Fatalf("event should be start, not %#v", startEvent) + c.Fatalf("event should be start, not %#v", startEvent) } if dieEvent[len(dieEvent)-1] != "die" { - t.Fatalf("event should be die, not %#v", dieEvent) + c.Fatalf("event should be die, not %#v", dieEvent) } if destroyEvent[len(destroyEvent)-1] != "destroy" { - t.Fatalf("event should be destroy, not %#v", destroyEvent) + c.Fatalf("event should be destroy, not %#v", destroyEvent) } - logDone("events - container create, start, die, destroy is logged") } -func TestEventsContainerEventsSinceUnixEpoch(t *testing.T) { - dockerCmd(t, "run", "--rm", "busybox", "true") +func (s *DockerSuite) TestEventsContainerEventsSinceUnixEpoch(c *check.C) { + dockerCmd(c, "run", "--rm", "busybox", "true") timeBeginning := time.Unix(0, 0).Format(time.RFC3339Nano) timeBeginning = strings.Replace(timeBeginning, "Z", ".000000000Z", -1) eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since='%s'", timeBeginning), - fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, exitCode, err := runCommandWithOutput(eventsCmd) if exitCode != 0 || err != nil { - t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) + c.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) } events := strings.Split(out, "\n") events = events[:len(events)-1] if len(events) < 4 { - t.Fatalf("Missing expected event") + c.Fatalf("Missing expected event") } createEvent := strings.Fields(events[len(events)-4]) startEvent := strings.Fields(events[len(events)-3]) dieEvent := strings.Fields(events[len(events)-2]) destroyEvent := strings.Fields(events[len(events)-1]) if createEvent[len(createEvent)-1] != "create" { - t.Fatalf("event should be create, not %#v", createEvent) + c.Fatalf("event should be create, not %#v", createEvent) } if startEvent[len(startEvent)-1] != "start" { - t.Fatalf("event should be start, not %#v", startEvent) + c.Fatalf("event should be start, not %#v", startEvent) } if dieEvent[len(dieEvent)-1] != "die" { - t.Fatalf("event should be die, not %#v", dieEvent) + c.Fatalf("event should be die, not %#v", dieEvent) } if destroyEvent[len(destroyEvent)-1] != "destroy" { - t.Fatalf("event should be destroy, not %#v", destroyEvent) + c.Fatalf("event should be destroy, not %#v", destroyEvent) } - logDone("events - container create, start, die, destroy since Unix Epoch time") } -func TestEventsImageUntagDelete(t *testing.T) { +func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) { name := "testimageevents" defer deleteImages(name) _, err := buildImage(name, @@ -156,67 +150,64 @@ func TestEventsImageUntagDelete(t *testing.T) { MAINTAINER "docker"`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if err := deleteImages(name); err != nil { - t.Fatal(err) + c.Fatal(err) } - eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, exitCode, err := runCommandWithOutput(eventsCmd) if exitCode != 0 || err != nil { - t.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) + c.Fatalf("Failed to get events with exit code %d: %s", exitCode, err) } events := strings.Split(out, "\n") events = events[:len(events)-1] if len(events) < 2 { - t.Fatalf("Missing expected event") + c.Fatalf("Missing expected event") } untagEvent := strings.Fields(events[len(events)-2]) deleteEvent := strings.Fields(events[len(events)-1]) if untagEvent[len(untagEvent)-1] != "untag" { - t.Fatalf("untag should be untag, not %#v", untagEvent) + c.Fatalf("untag should be untag, not %#v", untagEvent) } if deleteEvent[len(deleteEvent)-1] != "delete" { - t.Fatalf("delete should be delete, not %#v", deleteEvent) + c.Fatalf("delete should be delete, not %#v", deleteEvent) } - logDone("events - image untag, delete is logged") } -func TestEventsImagePull(t *testing.T) { - since := daemonTime(t).Unix() - testRequires(t, Network) +func (s *DockerSuite) TestEventsImagePull(c *check.C) { + since := daemonTime(c).Unix() + testRequires(c, Network) defer deleteImages("hello-world") pullCmd := exec.Command(dockerBinary, "pull", "hello-world") if out, _, err := runCommandWithOutput(pullCmd); err != nil { - t.Fatalf("pulling the hello-world image from has failed: %s, %v", out, err) + c.Fatalf("pulling the hello-world image from has failed: %s, %v", out, err) } eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), - fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ := runCommandWithOutput(eventsCmd) events := strings.Split(strings.TrimSpace(out), "\n") event := strings.TrimSpace(events[len(events)-1]) if !strings.HasSuffix(event, "hello-world:latest: pull") { - t.Fatalf("Missing pull event - got:%q", event) + c.Fatalf("Missing pull event - got:%q", event) } - logDone("events - image pull is logged") } -func TestEventsImageImport(t *testing.T) { - defer deleteAllContainers() - since := daemonTime(t).Unix() +func (s *DockerSuite) TestEventsImageImport(c *check.C) { + since := daemonTime(c).Unix() runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal("failed to create a container", out, err) + c.Fatal("failed to create a container", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -225,25 +216,24 @@ func TestEventsImageImport(t *testing.T) { exec.Command(dockerBinary, "import", "-"), ) if err != nil { - t.Errorf("import failed with errors: %v, output: %q", err, out) + c.Errorf("import failed with errors: %v, output: %q", err, out) } eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), - fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ = runCommandWithOutput(eventsCmd) events := strings.Split(strings.TrimSpace(out), "\n") event := strings.TrimSpace(events[len(events)-1]) if !strings.HasSuffix(event, ": import") { - t.Fatalf("Missing import event - got:%q", event) + c.Fatalf("Missing import event - got:%q", event) } - logDone("events - image import is logged") } -func TestEventsFilters(t *testing.T) { +func (s *DockerSuite) TestEventsFilters(c *check.C) { parseEvents := func(out, match string) { events := strings.Split(out, "\n") events = events[:len(events)-1] @@ -251,67 +241,65 @@ func TestEventsFilters(t *testing.T) { eventFields := strings.Fields(event) eventName := eventFields[len(eventFields)-1] if ok, err := regexp.MatchString(match, eventName); err != nil || !ok { - t.Fatalf("event should match %s, got %#v, err: %v", match, eventFields, err) + c.Fatalf("event should match %s, got %#v, err: %v", match, eventFields, err) } } } - since := daemonTime(t).Unix() + since := daemonTime(c).Unix() out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", "busybox", "true")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--rm", "busybox", "true")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", "event=die")) + out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die")) if err != nil { - t.Fatalf("Failed to get events: %s", err) + c.Fatalf("Failed to get events: %s", err) } parseEvents(out, "die") - out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", "event=die", "--filter", "event=start")) + out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", "event=die", "--filter", "event=start")) if err != nil { - t.Fatalf("Failed to get events: %s", err) + c.Fatalf("Failed to get events: %s", err) } parseEvents(out, "((die)|(start))") // make sure we at least got 2 start events count := strings.Count(out, "start") if count < 2 { - t.Fatalf("should have had 2 start events but had %d, out: %s", count, out) + c.Fatalf("should have had 2 start events but had %d, out: %s", count, out) } - logDone("events - filters") } -func TestEventsFilterImageName(t *testing.T) { - since := daemonTime(t).Unix() - defer deleteAllContainers() +func (s *DockerSuite) TestEventsFilterImageName(c *check.C) { + since := daemonTime(c).Unix() out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_1", "-d", "busybox:latest", "true")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } container1 := strings.TrimSpace(out) out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "container_2", "-d", "busybox", "true")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } container2 := strings.TrimSpace(out) - s := "busybox" - eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(t).Unix()), "--filter", fmt.Sprintf("image=%s", s)) + name := "busybox" + eventsCmd := exec.Command(dockerBinary, "events", fmt.Sprintf("--since=%d", since), fmt.Sprintf("--until=%d", daemonTime(c).Unix()), "--filter", fmt.Sprintf("image=%s", name)) out, _, err = runCommandWithOutput(eventsCmd) if err != nil { - t.Fatalf("Failed to get events, error: %s(%s)", err, out) + c.Fatalf("Failed to get events, error: %s(%s)", err, out) } events := strings.Split(out, "\n") events = events[:len(events)-1] if len(events) == 0 { - t.Fatalf("Expected events but found none for the image busybox:latest") + c.Fatalf("Expected events but found none for the image busybox:latest") } count1 := 0 count2 := 0 @@ -324,27 +312,25 @@ func TestEventsFilterImageName(t *testing.T) { } } if count1 == 0 || count2 == 0 { - t.Fatalf("Expected events from each container but got %d from %s and %d from %s", count1, container1, count2, container2) + c.Fatalf("Expected events from each container but got %d from %s and %d from %s", count1, container1, count2, container2) } - logDone("events - filters using image") } -func TestEventsFilterContainer(t *testing.T) { - defer deleteAllContainers() - since := fmt.Sprintf("%d", daemonTime(t).Unix()) +func (s *DockerSuite) TestEventsFilterContainer(c *check.C) { + since := fmt.Sprintf("%d", daemonTime(c).Unix()) nameID := make(map[string]string) for _, name := range []string{"container_1", "container_2"} { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "true")) if err != nil { - t.Fatal(err) + c.Fatal(err) } nameID[name] = strings.TrimSpace(out) waitInspect(name, "{{.State.Runing }}", "false", 5) } - until := fmt.Sprintf("%d", daemonTime(t).Unix()) + until := fmt.Sprintf("%d", daemonTime(c).Unix()) checkEvents := func(id string, events []string) error { if len(events) != 3 { // create, start, die @@ -370,32 +356,31 @@ func TestEventsFilterContainer(t *testing.T) { eventsCmd := exec.Command(dockerBinary, "events", "--since", since, "--until", until, "--filter", "container="+name) out, _, err := runCommandWithOutput(eventsCmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } events := strings.Split(strings.TrimSuffix(out, "\n"), "\n") if err := checkEvents(ID, events); err != nil { - t.Fatal(err) + c.Fatal(err) } // filter by ID's eventsCmd = exec.Command(dockerBinary, "events", "--since", since, "--until", until, "--filter", "container="+ID) out, _, err = runCommandWithOutput(eventsCmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } events = strings.Split(strings.TrimSuffix(out, "\n"), "\n") if err := checkEvents(ID, events); err != nil { - t.Fatal(err) + c.Fatal(err) } } - logDone("events - filters using container name") } -func TestEventsStreaming(t *testing.T) { - start := daemonTime(t).Unix() +func (s *DockerSuite) TestEventsStreaming(c *check.C) { + start := daemonTime(c).Unix() finish := make(chan struct{}) defer close(finish) @@ -409,11 +394,11 @@ func TestEventsStreaming(t *testing.T) { eventsCmd := exec.Command(dockerBinary, "events", "--since", strconv.FormatInt(start, 10)) stdout, err := eventsCmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } err = eventsCmd.Start() if err != nil { - t.Fatalf("failed to start 'docker events': %s", err) + c.Fatalf("failed to start 'docker events': %s", err) } go func() { @@ -444,35 +429,35 @@ func TestEventsStreaming(t *testing.T) { err = eventsCmd.Wait() if err != nil && !IsKilled(err) { - t.Fatalf("docker events had bad exit status: %s", err) + c.Fatalf("docker events had bad exit status: %s", err) } }() runCmd := exec.Command(dockerBinary, "run", "-d", "busybox:latest", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) id <- cleanedContainerID select { case <-time.After(5 * time.Second): - t.Fatal("failed to observe container create in timely fashion") + c.Fatal("failed to observe container create in timely fashion") case <-eventCreate: // ignore, done } select { case <-time.After(5 * time.Second): - t.Fatal("failed to observe container start in timely fashion") + c.Fatal("failed to observe container start in timely fashion") case <-eventStart: // ignore, done } select { case <-time.After(5 * time.Second): - t.Fatal("failed to observe container die in timely fashion") + c.Fatal("failed to observe container die in timely fashion") case <-eventDie: // ignore, done } @@ -480,15 +465,14 @@ func TestEventsStreaming(t *testing.T) { rmCmd := exec.Command(dockerBinary, "rm", cleanedContainerID) out, _, err = runCommandWithOutput(rmCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } select { case <-time.After(5 * time.Second): - t.Fatal("failed to observe container destroy in timely fashion") + c.Fatal("failed to observe container destroy in timely fashion") case <-eventDestroy: // ignore, done } - logDone("events - streamed to stdout") } diff --git a/integration-cli/docker_cli_events_unix_test.go b/integration-cli/docker_cli_events_unix_test.go index 4e5428350..1a08f2b3c 100644 --- a/integration-cli/docker_cli_events_unix_test.go +++ b/integration-cli/docker_cli_events_unix_test.go @@ -8,48 +8,46 @@ import ( "io/ioutil" "os" "os/exec" - "testing" "unicode" + "github.com/go-check/check" "github.com/kr/pty" ) // #5979 -func TestEventsRedirectStdout(t *testing.T) { - since := daemonTime(t).Unix() - dockerCmd(t, "run", "busybox", "true") - defer deleteAllContainers() +func (s *DockerSuite) TestEventsRedirectStdout(c *check.C) { + since := daemonTime(c).Unix() + dockerCmd(c, "run", "busybox", "true") file, err := ioutil.TempFile("", "") if err != nil { - t.Fatalf("could not create temp file: %v", err) + c.Fatalf("could not create temp file: %v", err) } defer os.Remove(file.Name()) - command := fmt.Sprintf("%s events --since=%d --until=%d > %s", dockerBinary, since, daemonTime(t).Unix(), file.Name()) + command := fmt.Sprintf("%s events --since=%d --until=%d > %s", dockerBinary, since, daemonTime(c).Unix(), file.Name()) _, tty, err := pty.Open() if err != nil { - t.Fatalf("Could not open pty: %v", err) + c.Fatalf("Could not open pty: %v", err) } cmd := exec.Command("sh", "-c", command) cmd.Stdin = tty cmd.Stdout = tty cmd.Stderr = tty if err := cmd.Run(); err != nil { - t.Fatalf("run err for command %q: %v", command, err) + c.Fatalf("run err for command %q: %v", command, err) } scanner := bufio.NewScanner(file) for scanner.Scan() { - for _, c := range scanner.Text() { - if unicode.IsControl(c) { - t.Fatalf("found control character %v", []byte(string(c))) + for _, ch := range scanner.Text() { + if unicode.IsControl(ch) { + c.Fatalf("found control character %v", []byte(string(ch))) } } } if err := scanner.Err(); err != nil { - t.Fatalf("Scan err for command %q: %v", command, err) + c.Fatalf("Scan err for command %q: %v", command, err) } - logDone("events - redirect stdout") } diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 6a0999eef..7fc3d4f25 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -12,38 +12,36 @@ import ( "sort" "strings" "sync" - "testing" "time" + + "github.com/go-check/check" ) -func TestExec(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExec(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } execCmd := exec.Command(dockerBinary, "exec", "testing", "cat", "/tmp/file") out, _, err := runCommandWithOutput(execCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.Trim(out, "\r\n") if expected := "test"; out != expected { - t.Errorf("container exec should've printed %q but printed %q", expected, out) + c.Errorf("container exec should've printed %q but printed %q", expected, out) } - logDone("exec - basic test") } -func TestExecInteractiveStdinClose(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecInteractiveStdinClose(c *check.C) { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "busybox", "/bin/cat")) if err != nil { - t.Fatal(err) + c.Fatal(err) } contId := strings.TrimSpace(out) @@ -55,16 +53,16 @@ func TestExecInteractiveStdinClose(t *testing.T) { cmd := exec.Command(dockerBinary, "exec", "-i", contId, "/bin/ls", "/") cmd.Stdin = os.Stdin if err != nil { - t.Fatal(err) + c.Fatal(err) } out, err := cmd.CombinedOutput() if err != nil { - t.Fatal(err, string(out)) + c.Fatal(err, string(out)) } if string(out) == "" { - t.Fatalf("Output was empty, likely blocked by standard input") + c.Fatalf("Output was empty, likely blocked by standard input") } returnchan <- struct{}{} @@ -73,163 +71,153 @@ func TestExecInteractiveStdinClose(t *testing.T) { select { case <-returnchan: case <-time.After(10 * time.Second): - t.Fatal("timed out running docker exec") + c.Fatal("timed out running docker exec") } - logDone("exec - interactive mode closes stdin after execution") } -func TestExecInteractive(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecInteractive(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } execCmd := exec.Command(dockerBinary, "exec", "-i", "testing", "sh") stdin, err := execCmd.StdinPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } stdout, err := execCmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } if err := execCmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := stdin.Write([]byte("cat /tmp/file\n")); err != nil { - t.Fatal(err) + c.Fatal(err) } r := bufio.NewReader(stdout) line, err := r.ReadString('\n') if err != nil { - t.Fatal(err) + c.Fatal(err) } line = strings.TrimSpace(line) if line != "test" { - t.Fatalf("Output should be 'test', got '%q'", line) + c.Fatalf("Output should be 'test', got '%q'", line) } if err := stdin.Close(); err != nil { - t.Fatal(err) + c.Fatal(err) } finish := make(chan struct{}) go func() { if err := execCmd.Wait(); err != nil { - t.Fatal(err) + c.Fatal(err) } close(finish) }() select { case <-finish: case <-time.After(1 * time.Second): - t.Fatal("docker exec failed to exit on stdin close") + c.Fatal("docker exec failed to exit on stdin close") } - logDone("exec - Interactive test") } -func TestExecAfterContainerRestart(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecAfterContainerRestart(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "restart", cleanedContainerID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "exec", cleanedContainerID, "echo", "hello") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } outStr := strings.TrimSpace(out) if outStr != "hello" { - t.Errorf("container should've printed hello, instead printed %q", outStr) + c.Errorf("container should've printed hello, instead printed %q", outStr) } - logDone("exec - exec running container after container restart") } -func TestExecAfterDaemonRestart(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestExecAfterDaemonRestart(c *check.C) { + testRequires(c, SameHostDaemon) - d := NewDaemon(t) + d := NewDaemon(c) if err := d.StartWithBusybox(); err != nil { - t.Fatalf("Could not start daemon with busybox: %v", err) + c.Fatalf("Could not start daemon with busybox: %v", err) } defer d.Stop() if out, err := d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { - t.Fatalf("Could not run top: err=%v\n%s", err, out) + c.Fatalf("Could not run top: err=%v\n%s", err, out) } if err := d.Restart(); err != nil { - t.Fatalf("Could not restart daemon: %v", err) + c.Fatalf("Could not restart daemon: %v", err) } if out, err := d.Cmd("start", "top"); err != nil { - t.Fatalf("Could not start top after daemon restart: err=%v\n%s", err, out) + c.Fatalf("Could not start top after daemon restart: err=%v\n%s", err, out) } out, err := d.Cmd("exec", "top", "echo", "hello") if err != nil { - t.Fatalf("Could not exec on container top: err=%v\n%s", err, out) + c.Fatalf("Could not exec on container top: err=%v\n%s", err, out) } outStr := strings.TrimSpace(string(out)) if outStr != "hello" { - t.Errorf("container should've printed hello, instead printed %q", outStr) + c.Errorf("container should've printed hello, instead printed %q", outStr) } - logDone("exec - exec running container after daemon restart") } // Regression test for #9155, #9044 -func TestExecEnv(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecEnv(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-e", "LALA=value1", "-e", "LALA=value2", "-d", "--name", "testing", "busybox", "top") if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } execCmd := exec.Command(dockerBinary, "exec", "testing", "env") out, _, err := runCommandWithOutput(execCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if strings.Contains(out, "LALA=value1") || !strings.Contains(out, "LALA=value2") || !strings.Contains(out, "HOME=/root") { - t.Errorf("exec env(%q), expect %q, %q", out, "LALA=value2", "HOME=/root") + c.Errorf("exec env(%q), expect %q, %q", out, "LALA=value2", "HOME=/root") } - logDone("exec - exec inherits correct env") } -func TestExecExitStatus(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecExitStatus(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "top", "busybox", "top") if out, _, _, err := runCommandWithStdoutStderr(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // Test normal (non-detached) case first @@ -237,20 +225,18 @@ func TestExecExitStatus(t *testing.T) { ec, _ := runCommand(cmd) if ec != 23 { - t.Fatalf("Should have had an ExitCode of 23, not: %d", ec) + c.Fatalf("Should have had an ExitCode of 23, not: %d", ec) } - logDone("exec - exec non-zero ExitStatus") } -func TestExecPausedContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecPausedContainer(c *check.C) { defer unpauseAllContainers() runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } ContainerID := strings.TrimSpace(out) @@ -258,82 +244,78 @@ func TestExecPausedContainer(t *testing.T) { pausedCmd := exec.Command(dockerBinary, "pause", "testing") out, _, _, err = runCommandWithStdoutStderr(pausedCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } execCmd := exec.Command(dockerBinary, "exec", "-i", "-t", ContainerID, "echo", "hello") out, _, err = runCommandWithOutput(execCmd) if err == nil { - t.Fatal("container should fail to exec new command if it is paused") + c.Fatal("container should fail to exec new command if it is paused") } expected := ContainerID + " is paused, unpause the container before exec" if !strings.Contains(out, expected) { - t.Fatal("container should not exec new command if it is paused") + c.Fatal("container should not exec new command if it is paused") } - logDone("exec - exec should not exec a pause container") } // regression test for #9476 -func TestExecTtyCloseStdin(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecTtyCloseStdin(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "-it", "--name", "exec_tty_stdin", "busybox") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cmd = exec.Command(dockerBinary, "exec", "-i", "exec_tty_stdin", "cat") stdinRw, err := cmd.StdinPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } stdinRw.Write([]byte("test")) stdinRw.Close() if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cmd = exec.Command(dockerBinary, "top", "exec_tty_stdin") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } outArr := strings.Split(out, "\n") if len(outArr) > 3 || strings.Contains(out, "nsenter-exec") { // This is the really bad part if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rm", "-f", "exec_tty_stdin")); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - t.Fatalf("exec process left running\n\t %s", out) + c.Fatalf("exec process left running\n\t %s", out) } - logDone("exec - stdin is closed properly with tty enabled") } -func TestExecTtyWithoutStdin(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecTtyWithoutStdin(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "-ti", "busybox") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to start container: %v (%v)", out, err) + c.Fatalf("failed to start container: %v (%v)", out, err) } id := strings.TrimSpace(out) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } defer func() { cmd := exec.Command(dockerBinary, "kill", id) if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("failed to kill container: %v (%v)", out, err) + c.Fatalf("failed to kill container: %v (%v)", out, err) } }() @@ -343,86 +325,80 @@ func TestExecTtyWithoutStdin(t *testing.T) { cmd := exec.Command(dockerBinary, "exec", "-ti", id, "true") if _, err := cmd.StdinPipe(); err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "cannot enable tty mode" if out, _, err := runCommandWithOutput(cmd); err == nil { - t.Fatal("exec should have failed") + c.Fatal("exec should have failed") } else if !strings.Contains(out, expected) { - t.Fatalf("exec failed with error %q: expected %q", out, expected) + c.Fatalf("exec failed with error %q: expected %q", out, expected) } }() select { case <-done: case <-time.After(3 * time.Second): - t.Fatal("exec is running but should have failed") + c.Fatal("exec is running but should have failed") } - logDone("exec - forbid piped stdin to tty enabled container") } -func TestExecParseError(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecParseError(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "top", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // Test normal (non-detached) case first cmd := exec.Command(dockerBinary, "exec", "top") if _, stderr, code, err := runCommandWithStdoutStderr(cmd); err == nil || !strings.Contains(stderr, "See '"+dockerBinary+" exec --help'") || code == 0 { - t.Fatalf("Should have thrown error & point to help: %s", stderr) + c.Fatalf("Should have thrown error & point to help: %s", stderr) } - logDone("exec - error on parseExec should point to help") } -func TestExecStopNotHanging(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecStopNotHanging(c *check.C) { if out, err := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "top").CombinedOutput(); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if err := exec.Command(dockerBinary, "exec", "testing", "top").Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } wait := make(chan struct{}) go func() { if out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput(); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } close(wait) }() select { case <-time.After(3 * time.Second): - t.Fatal("Container stop timed out") + c.Fatal("Container stop timed out") case <-wait: } - logDone("exec - container with exec not hanging on stop") } -func TestExecCgroup(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecCgroup(c *check.C) { var cmd *exec.Cmd cmd = exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "top") _, err := runCommand(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "exec", "testing", "cat", "/proc/1/cgroup") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerCgroups := sort.StringSlice(strings.Split(string(out), "\n")) var wg sync.WaitGroup - var s sync.Mutex + var mu sync.Mutex execCgroups := []sort.StringSlice{} // exec a few times concurrently to get consistent failure for i := 0; i < 5; i++ { @@ -431,13 +407,13 @@ func TestExecCgroup(t *testing.T) { cmd := exec.Command(dockerBinary, "exec", "testing", "cat", "/proc/self/cgroup") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cg := sort.StringSlice(strings.Split(string(out), "\n")) - s.Lock() + mu.Lock() execCgroups = append(execCgroups, cg) - s.Unlock() + mu.Unlock() wg.Done() }() } @@ -454,86 +430,81 @@ func TestExecCgroup(t *testing.T) { for _, name := range containerCgroups { fmt.Printf(" %s\n", name) } - t.Fatal("cgroups mismatched") + c.Fatal("cgroups mismatched") } } - logDone("exec - exec has the container cgroups") } -func TestInspectExecID(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestInspectExecID(c *check.C) { out, exitCode, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "top")) if exitCode != 0 || err != nil { - t.Fatalf("failed to run container: %s, %v", out, err) + c.Fatalf("failed to run container: %s, %v", out, err) } id := strings.TrimSuffix(out, "\n") out, err = inspectField(id, "ExecIDs") if err != nil { - t.Fatalf("failed to inspect container: %s, %v", out, err) + c.Fatalf("failed to inspect container: %s, %v", out, err) } if out != "" { - t.Fatalf("ExecIDs should be empty, got: %s", out) + c.Fatalf("ExecIDs should be empty, got: %s", out) } exitCode, err = runCommand(exec.Command(dockerBinary, "exec", "-d", id, "ls", "/")) if exitCode != 0 || err != nil { - t.Fatalf("failed to exec in container: %s, %v", out, err) + c.Fatalf("failed to exec in container: %s, %v", out, err) } out, err = inspectField(id, "ExecIDs") if err != nil { - t.Fatalf("failed to inspect container: %s, %v", out, err) + c.Fatalf("failed to inspect container: %s, %v", out, err) } out = strings.TrimSuffix(out, "\n") if out == "[]" || out == "" { - t.Fatalf("ExecIDs should not be empty, got: %s", out) + c.Fatalf("ExecIDs should not be empty, got: %s", out) } - logDone("inspect - inspect a container with ExecIDs") } -func TestLinksPingLinkedContainersOnRename(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksPingLinkedContainersOnRename(c *check.C) { var out string - out, _ = dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") + out, _ = dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") idA := strings.TrimSpace(out) if idA == "" { - t.Fatal(out, "id should not be nil") + c.Fatal(out, "id should not be nil") } - out, _ = dockerCmd(t, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") + out, _ = dockerCmd(c, "run", "-d", "--link", "container1:alias1", "--name", "container2", "busybox", "top") idB := strings.TrimSpace(out) if idB == "" { - t.Fatal(out, "id should not be nil") + c.Fatal(out, "id should not be nil") } execCmd := exec.Command(dockerBinary, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") out, _, err := runCommandWithOutput(execCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - dockerCmd(t, "rename", "container1", "container_new") + dockerCmd(c, "rename", "container1", "container_new") execCmd = exec.Command(dockerBinary, "exec", "container2", "ping", "-c", "1", "alias1", "-W", "1") out, _, err = runCommandWithOutput(execCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - logDone("links - ping linked container upon rename") } -func TestRunExecDir(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunExecDir(c *check.C) { + testRequires(c, SameHostDaemon) cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(out) execDir := filepath.Join(execDriverPath, id) @@ -542,92 +513,90 @@ func TestRunExecDir(t *testing.T) { { fi, err := os.Stat(execDir) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !fi.IsDir() { - t.Fatalf("%q must be a directory", execDir) + c.Fatalf("%q must be a directory", execDir) } fi, err = os.Stat(stateFile) if err != nil { - t.Fatal(err) + c.Fatal(err) } } stopCmd := exec.Command(dockerBinary, "stop", id) out, _, err = runCommandWithOutput(stopCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } { _, err := os.Stat(execDir) if err == nil { - t.Fatal(err) + c.Fatal(err) } if err == nil { - t.Fatalf("Exec directory %q exists for removed container!", execDir) + c.Fatalf("Exec directory %q exists for removed container!", execDir) } if !os.IsNotExist(err) { - t.Fatalf("Error should be about non-existing, got %s", err) + c.Fatalf("Error should be about non-existing, got %s", err) } } startCmd := exec.Command(dockerBinary, "start", id) out, _, err = runCommandWithOutput(startCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } { fi, err := os.Stat(execDir) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !fi.IsDir() { - t.Fatalf("%q must be a directory", execDir) + c.Fatalf("%q must be a directory", execDir) } fi, err = os.Stat(stateFile) if err != nil { - t.Fatal(err) + c.Fatal(err) } } rmCmd := exec.Command(dockerBinary, "rm", "-f", id) out, _, err = runCommandWithOutput(rmCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } { _, err := os.Stat(execDir) if err == nil { - t.Fatal(err) + c.Fatal(err) } if err == nil { - t.Fatalf("Exec directory %q is exists for removed container!", execDir) + c.Fatalf("Exec directory %q is exists for removed container!", execDir) } if !os.IsNotExist(err) { - t.Fatalf("Error should be about non-existing, got %s", err) + c.Fatalf("Error should be about non-existing, got %s", err) } } - logDone("run - check execdriver dir behavior") } -func TestRunMutableNetworkFiles(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunMutableNetworkFiles(c *check.C) { + testRequires(c, SameHostDaemon) for _, fn := range []string{"resolv.conf", "hosts"} { deleteAllContainers() content, err := runCommandAndReadContainerFile(fn, exec.Command(dockerBinary, "run", "-d", "--name", "c1", "busybox", "sh", "-c", fmt.Sprintf("echo success >/etc/%s && top", fn))) if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(string(content)) != "success" { - t.Fatal("Content was not what was modified in the container", string(content)) + c.Fatal("Content was not what was modified in the container", string(content)) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "c2", "busybox", "top")) if err != nil { - t.Fatal(err) + c.Fatal(err) } contID := strings.TrimSpace(out) @@ -636,88 +605,83 @@ func TestRunMutableNetworkFiles(t *testing.T) { f, err := os.OpenFile(netFilePath, os.O_WRONLY|os.O_SYNC|os.O_APPEND, 0644) if err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := f.Seek(0, 0); err != nil { f.Close() - t.Fatal(err) + c.Fatal(err) } if err := f.Truncate(0); err != nil { f.Close() - t.Fatal(err) + c.Fatal(err) } if _, err := f.Write([]byte("success2\n")); err != nil { f.Close() - t.Fatal(err) + c.Fatal(err) } f.Close() res, err := exec.Command(dockerBinary, "exec", contID, "cat", "/etc/"+fn).CombinedOutput() if err != nil { - t.Fatalf("Output: %s, error: %s", res, err) + c.Fatalf("Output: %s, error: %s", res, err) } if string(res) != "success2\n" { - t.Fatalf("Expected content of %s: %q, got: %q", fn, "success2\n", res) + c.Fatalf("Expected content of %s: %q, got: %q", fn, "success2\n", res) } } - logDone("run - mutable network files") } -func TestExecWithUser(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecWithUser(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cmd := exec.Command(dockerBinary, "exec", "-u", "1", "parent", "id") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { - t.Fatalf("exec with user by id expected daemon user got %s", out) + c.Fatalf("exec with user by id expected daemon user got %s", out) } cmd = exec.Command(dockerBinary, "exec", "-u", "root", "parent", "id") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "uid=0(root) gid=0(root)") { - t.Fatalf("exec with user by root expected root user got %s", out) + c.Fatalf("exec with user by root expected root user got %s", out) } - logDone("exec - with user") } -func TestExecWithPrivileged(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestExecWithPrivileged(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "--cap-drop=ALL", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cmd := exec.Command(dockerBinary, "exec", "parent", "sh", "-c", "mknod /tmp/sda b 8 0") out, _, err := runCommandWithOutput(cmd) if err == nil || !strings.Contains(out, "Operation not permitted") { - t.Fatalf("exec mknod in --cap-drop=ALL container without --privileged should failed") + c.Fatalf("exec mknod in --cap-drop=ALL container without --privileged should failed") } cmd = exec.Command(dockerBinary, "exec", "--privileged", "parent", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.TrimSpace(out); actual != "ok" { - t.Fatalf("exec mknod in --cap-drop=ALL container with --privileged failed: %v, output: %q", err, out) + c.Fatalf("exec mknod in --cap-drop=ALL container with --privileged failed: %v, output: %q", err, out) } - logDone("exec - exec command in a container with privileged") } diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go index 3df8d60f8..59a510630 100644 --- a/integration-cli/docker_cli_export_import_test.go +++ b/integration-cli/docker_cli_export_import_test.go @@ -4,11 +4,12 @@ import ( "os" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // export an image and try to import it into a new one -func TestExportContainerAndImportImage(t *testing.T) { +func (s *DockerSuite) TestExportContainerAndImportImage(c *check.C) { containerID := "testexportcontainerandimportimage" defer deleteImages("repo/testexp:v1") @@ -17,39 +18,38 @@ func TestExportContainerAndImportImage(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal("failed to create a container", out, err) + c.Fatal("failed to create a container", out, err) } inspectCmd := exec.Command(dockerBinary, "inspect", containerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("output should've been a container id: %s %s ", containerID, err) + c.Fatalf("output should've been a container id: %s %s ", containerID, err) } exportCmd := exec.Command(dockerBinary, "export", containerID) if out, _, err = runCommandWithOutput(exportCmd); err != nil { - t.Fatalf("failed to export container: %s, %v", out, err) + c.Fatalf("failed to export container: %s, %v", out, err) } importCmd := exec.Command(dockerBinary, "import", "-", "repo/testexp:v1") importCmd.Stdin = strings.NewReader(out) out, _, err = runCommandWithOutput(importCmd) if err != nil { - t.Fatalf("failed to import image: %s, %v", out, err) + c.Fatalf("failed to import image: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) inspectCmd = exec.Command(dockerBinary, "inspect", cleanedImageID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("output should've been an image id: %s, %v", out, err) + c.Fatalf("output should've been an image id: %s, %v", out, err) } - logDone("export - export/import a container/image") } // Used to test output flag in the export command -func TestExportContainerWithOutputAndImportImage(t *testing.T) { +func (s *DockerSuite) TestExportContainerWithOutputAndImportImage(c *check.C) { containerID := "testexportcontainerwithoutputandimportimage" defer deleteImages("repo/testexp:v1") @@ -58,40 +58,39 @@ func TestExportContainerWithOutputAndImportImage(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal("failed to create a container", out, err) + c.Fatal("failed to create a container", out, err) } inspectCmd := exec.Command(dockerBinary, "inspect", containerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("output should've been a container id: %s %s ", containerID, err) + c.Fatalf("output should've been a container id: %s %s ", containerID, err) } defer os.Remove("testexp.tar") exportCmd := exec.Command(dockerBinary, "export", "--output=testexp.tar", containerID) if out, _, err = runCommandWithOutput(exportCmd); err != nil { - t.Fatalf("failed to export container: %s, %v", out, err) + c.Fatalf("failed to export container: %s, %v", out, err) } out, _, err = runCommandWithOutput(exec.Command("cat", "testexp.tar")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } importCmd := exec.Command(dockerBinary, "import", "-", "repo/testexp:v1") importCmd.Stdin = strings.NewReader(out) out, _, err = runCommandWithOutput(importCmd) if err != nil { - t.Fatalf("failed to import image: %s, %v", out, err) + c.Fatalf("failed to import image: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) inspectCmd = exec.Command(dockerBinary, "inspect", cleanedImageID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("output should've been an image id: %s, %v", out, err) + c.Fatalf("output should've been an image id: %s, %v", out, err) } - logDone("export - export/import a container/image with output flag") } diff --git a/integration-cli/docker_cli_help_test.go b/integration-cli/docker_cli_help_test.go index 8fc5cd1aa..d6903e4fb 100644 --- a/integration-cli/docker_cli_help_test.go +++ b/integration-cli/docker_cli_help_test.go @@ -5,13 +5,13 @@ import ( "os/exec" "runtime" "strings" - "testing" "unicode" "github.com/docker/docker/pkg/homedir" + "github.com/go-check/check" ) -func TestHelpTextVerify(t *testing.T) { +func (s *DockerSuite) TestHelpTextVerify(c *check.C) { // Make sure main help text fits within 80 chars and that // on non-windows system we use ~ when possible (to shorten things). // Test for HOME set to its default value and set to "/" on linux @@ -51,26 +51,26 @@ func TestHelpTextVerify(t *testing.T) { helpCmd.Env = newEnvs out, ec, err := runCommandWithOutput(helpCmd) if err != nil || ec != 0 { - t.Fatalf("docker help should have worked\nout:%s\nec:%d", out, ec) + c.Fatalf("docker help should have worked\nout:%s\nec:%d", out, ec) } lines := strings.Split(out, "\n") for _, line := range lines { if len(line) > 80 { - t.Fatalf("Line is too long(%d chars):\n%s", len(line), line) + c.Fatalf("Line is too long(%d chars):\n%s", len(line), line) } // All lines should not end with a space if strings.HasSuffix(line, " ") { - t.Fatalf("Line should not end with a space: %s", line) + c.Fatalf("Line should not end with a space: %s", line) } if scanForHome && strings.Contains(line, `=`+home) { - t.Fatalf("Line should use '%q' instead of %q:\n%s", homedir.GetShortcutString(), home, line) + c.Fatalf("Line should use '%q' instead of %q:\n%s", homedir.GetShortcutString(), home, line) } if runtime.GOOS != "windows" { i := strings.Index(line, homedir.GetShortcutString()) if i >= 0 && i != len(line)-1 && line[i+1] != '/' { - t.Fatalf("Main help should not have used home shortcut:\n%s", line) + c.Fatalf("Main help should not have used home shortcut:\n%s", line) } } } @@ -82,11 +82,11 @@ func TestHelpTextVerify(t *testing.T) { helpCmd.Env = newEnvs out, ec, err = runCommandWithOutput(helpCmd) if err != nil || ec != 0 { - t.Fatalf("docker help should have worked\nout:%s\nec:%d", out, ec) + c.Fatalf("docker help should have worked\nout:%s\nec:%d", out, ec) } i := strings.Index(out, "Commands:") if i < 0 { - t.Fatalf("Missing 'Commands:' in:\n%s", out) + c.Fatalf("Missing 'Commands:' in:\n%s", out) } // Grab all chars starting at "Commands:" @@ -106,39 +106,39 @@ func TestHelpTextVerify(t *testing.T) { helpCmd.Env = newEnvs out, ec, err := runCommandWithOutput(helpCmd) if err != nil || ec != 0 { - t.Fatalf("Error on %q help: %s\nexit code:%d", cmd, out, ec) + c.Fatalf("Error on %q help: %s\nexit code:%d", cmd, out, ec) } lines := strings.Split(out, "\n") for _, line := range lines { if len(line) > 80 { - t.Fatalf("Help for %q is too long(%d chars):\n%s", cmd, + c.Fatalf("Help for %q is too long(%d chars):\n%s", cmd, len(line), line) } if scanForHome && strings.Contains(line, `"`+home) { - t.Fatalf("Help for %q should use ~ instead of %q on:\n%s", + c.Fatalf("Help for %q should use ~ instead of %q on:\n%s", cmd, home, line) } i := strings.Index(line, "~") if i >= 0 && i != len(line)-1 && line[i+1] != '/' { - t.Fatalf("Help for %q should not have used ~:\n%s", cmd, line) + c.Fatalf("Help for %q should not have used ~:\n%s", cmd, line) } // If a line starts with 4 spaces then assume someone // added a multi-line description for an option and we need // to flag it if strings.HasPrefix(line, " ") { - t.Fatalf("Help for %q should not have a multi-line option: %s", cmd, line) + c.Fatalf("Help for %q should not have a multi-line option: %s", cmd, line) } // Options should NOT end with a period if strings.HasPrefix(line, " -") && strings.HasSuffix(line, ".") { - t.Fatalf("Help for %q should not end with a period: %s", cmd, line) + c.Fatalf("Help for %q should not end with a period: %s", cmd, line) } // Options should NOT end with a space if strings.HasSuffix(line, " ") { - t.Fatalf("Help for %q should not end with a space: %s", cmd, line) + c.Fatalf("Help for %q should not end with a space: %s", cmd, line) } } @@ -146,10 +146,9 @@ func TestHelpTextVerify(t *testing.T) { expected := 39 if len(cmds) != expected { - t.Fatalf("Wrong # of cmds(%d), it should be: %d\nThe list:\n%q", + c.Fatalf("Wrong # of cmds(%d), it should be: %d\nThe list:\n%q", len(cmds), expected, cmds) } } - logDone("help - verify text") } diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go index 9fd2180d3..a3ba18abd 100644 --- a/integration-cli/docker_cli_history_test.go +++ b/integration-cli/docker_cli_history_test.go @@ -4,12 +4,13 @@ import ( "fmt" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // This is a heisen-test. Because the created timestamp of images and the behavior of // sort is not predictable it doesn't always fail. -func TestBuildHistory(t *testing.T) { +func (s *DockerSuite) TestBuildHistory(c *check.C) { name := "testbuildhistory" defer deleteImages(name) _, err := buildImage(name, `FROM busybox @@ -42,12 +43,12 @@ RUN echo "Z"`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, exitCode, err := runCommandWithOutput(exec.Command(dockerBinary, "history", "testbuildhistory")) if err != nil || exitCode != 0 { - t.Fatalf("failed to get image history: %s, %v", out, err) + c.Fatalf("failed to get image history: %s, %v", out, err) } actualValues := strings.Split(out, "\n")[1:27] @@ -58,32 +59,29 @@ RUN echo "Z"`, actualValue := actualValues[i] if !strings.Contains(actualValue, echoValue) { - t.Fatalf("Expected layer \"%s\", but was: %s", expectedValues[i], actualValue) + c.Fatalf("Expected layer \"%s\", but was: %s", expectedValues[i], actualValue) } } - logDone("history - build history") } -func TestHistoryExistentImage(t *testing.T) { +func (s *DockerSuite) TestHistoryExistentImage(c *check.C) { historyCmd := exec.Command(dockerBinary, "history", "busybox") _, exitCode, err := runCommandWithOutput(historyCmd) if err != nil || exitCode != 0 { - t.Fatal("failed to get image history") + c.Fatal("failed to get image history") } - logDone("history - history on existent image must pass") } -func TestHistoryNonExistentImage(t *testing.T) { +func (s *DockerSuite) TestHistoryNonExistentImage(c *check.C) { historyCmd := exec.Command(dockerBinary, "history", "testHistoryNonExistentImage") _, exitCode, err := runCommandWithOutput(historyCmd) if err == nil || exitCode == 0 { - t.Fatal("history on a non-existent image didn't result in a non-zero exit status") + c.Fatal("history on a non-existent image didn't result in a non-zero exit status") } - logDone("history - history on non-existent image must pass") } -func TestHistoryImageWithComment(t *testing.T) { +func (s *DockerSuite) TestHistoryImageWithComment(c *check.C) { name := "testhistoryimagewithcomment" defer deleteContainer(name) defer deleteImages(name) @@ -93,26 +91,26 @@ func TestHistoryImageWithComment(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to run container: %s, %v", out, err) + c.Fatalf("failed to run container: %s, %v", out, err) } waitCmd := exec.Command(dockerBinary, "wait", name) if out, _, err := runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", out, err) + c.Fatalf("error thrown while waiting for container: %s, %v", out, err) } comment := "This_is_a_comment" commitCmd := exec.Command(dockerBinary, "commit", "-m="+comment, name, name) if out, _, err := runCommandWithOutput(commitCmd); err != nil { - t.Fatalf("failed to commit container to image: %s, %v", out, err) + c.Fatalf("failed to commit container to image: %s, %v", out, err) } // test docker history to check comment messages historyCmd := exec.Command(dockerBinary, "history", name) out, exitCode, err := runCommandWithOutput(historyCmd) if err != nil || exitCode != 0 { - t.Fatalf("failed to get image history: %s, %v", out, err) + c.Fatalf("failed to get image history: %s, %v", out, err) } outputTabs := strings.Fields(strings.Split(out, "\n")[1]) @@ -120,8 +118,7 @@ func TestHistoryImageWithComment(t *testing.T) { actualValue := outputTabs[len(outputTabs)-1] if !strings.Contains(actualValue, comment) { - t.Fatalf("Expected comments %q, but found %q", comment, actualValue) + c.Fatalf("Expected comments %q, but found %q", comment, actualValue) } - logDone("history - history on image with comment") } diff --git a/integration-cli/docker_cli_images_test.go b/integration-cli/docker_cli_images_test.go index 28b091efd..cf219e88b 100644 --- a/integration-cli/docker_cli_images_test.go +++ b/integration-cli/docker_cli_images_test.go @@ -6,27 +6,26 @@ import ( "reflect" "sort" "strings" - "testing" "time" "github.com/docker/docker/pkg/stringid" + "github.com/go-check/check" ) -func TestImagesEnsureImageIsListed(t *testing.T) { +func (s *DockerSuite) TestImagesEnsureImageIsListed(c *check.C) { imagesCmd := exec.Command(dockerBinary, "images") out, _, err := runCommandWithOutput(imagesCmd) if err != nil { - t.Fatalf("listing images failed with errors: %s, %v", out, err) + c.Fatalf("listing images failed with errors: %s, %v", out, err) } if !strings.Contains(out, "busybox") { - t.Fatal("images should've listed busybox") + c.Fatal("images should've listed busybox") } - logDone("images - busybox should be listed") } -func TestImagesOrderedByCreationDate(t *testing.T) { +func (s *DockerSuite) TestImagesOrderedByCreationDate(c *check.C) { defer deleteImages("order:test_a") defer deleteImages("order:test_c") defer deleteImages("order:test_b") @@ -34,56 +33,53 @@ func TestImagesOrderedByCreationDate(t *testing.T) { `FROM scratch MAINTAINER dockerio1`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(time.Second) id2, err := buildImage("order:test_c", `FROM scratch MAINTAINER dockerio2`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(time.Second) id3, err := buildImage("order:test_b", `FROM scratch MAINTAINER dockerio3`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "images", "-q", "--no-trunc")) if err != nil { - t.Fatalf("listing images failed with errors: %s, %v", out, err) + c.Fatalf("listing images failed with errors: %s, %v", out, err) } imgs := strings.Split(out, "\n") if imgs[0] != id3 { - t.Fatalf("First image must be %s, got %s", id3, imgs[0]) + c.Fatalf("First image must be %s, got %s", id3, imgs[0]) } if imgs[1] != id2 { - t.Fatalf("Second image must be %s, got %s", id2, imgs[1]) + c.Fatalf("Second image must be %s, got %s", id2, imgs[1]) } if imgs[2] != id1 { - t.Fatalf("Third image must be %s, got %s", id1, imgs[2]) + c.Fatalf("Third image must be %s, got %s", id1, imgs[2]) } - logDone("images - ordering by creation date") } -func TestImagesErrorWithInvalidFilterNameTest(t *testing.T) { +func (s *DockerSuite) TestImagesErrorWithInvalidFilterNameTest(c *check.C) { imagesCmd := exec.Command(dockerBinary, "images", "-f", "FOO=123") out, _, err := runCommandWithOutput(imagesCmd) if !strings.Contains(out, "Invalid filter") { - t.Fatalf("error should occur when listing images with invalid filter name FOO, %s, %v", out, err) + c.Fatalf("error should occur when listing images with invalid filter name FOO, %s, %v", out, err) } - logDone("images - invalid filter name check working") } -func TestImagesFilterLabel(t *testing.T) { +func (s *DockerSuite) TestImagesFilterLabel(c *check.C) { imageName1 := "images_filter_test1" imageName2 := "images_filter_test2" imageName3 := "images_filter_test3" - defer deleteAllContainers() defer deleteImages(imageName1) defer deleteImages(imageName2) defer deleteImages(imageName3) @@ -91,51 +87,49 @@ func TestImagesFilterLabel(t *testing.T) { `FROM scratch LABEL match me`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } image2ID, err := buildImage(imageName2, `FROM scratch LABEL match="me too"`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } image3ID, err := buildImage(imageName3, `FROM scratch LABEL nomatch me`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.TrimSpace(out) if (!strings.Contains(out, image1ID) && !strings.Contains(out, image2ID)) || strings.Contains(out, image3ID) { - t.Fatalf("Expected ids %s,%s got %s", image1ID, image2ID, out) + c.Fatalf("Expected ids %s,%s got %s", image1ID, image2ID, out) } cmd = exec.Command(dockerBinary, "images", "--no-trunc", "-q", "-f", "label=match=me too") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.TrimSpace(out) if out != image2ID { - t.Fatalf("Expected %s got %s", image2ID, out) + c.Fatalf("Expected %s got %s", image2ID, out) } - logDone("images - filter label") } -func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) { +func (s *DockerSuite) TestImagesFilterSpaceTrimCase(c *check.C) { imageName := "images_filter_test" - defer deleteAllContainers() defer deleteImages(imageName) buildImage(imageName, `FROM scratch @@ -156,7 +150,7 @@ func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) { cmd := exec.Command(dockerBinary, "images", "-q", "-f", filter) out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } listing := strings.Split(out, "\n") sort.Strings(listing) @@ -172,50 +166,47 @@ func TestImagesFilterWhiteSpaceTrimmingAndLowerCasingWorking(t *testing.T) { } fmt.Print("") } - t.Fatalf("All output must be the same") + c.Fatalf("All output must be the same") } } - logDone("images - white space trimming and lower casing") } -func TestImagesEnsureDanglingImageOnlyListedOnce(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestImagesEnsureDanglingImageOnlyListedOnce(c *check.C) { // create container 1 - c := exec.Command(dockerBinary, "run", "-d", "busybox", "true") - out, _, err := runCommandWithOutput(c) + cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") + out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error running busybox: %s, %v", out, err) + c.Fatalf("error running busybox: %s, %v", out, err) } containerId1 := strings.TrimSpace(out) // tag as foobox - c = exec.Command(dockerBinary, "commit", containerId1, "foobox") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "commit", containerId1, "foobox") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error tagging foobox: %s", err) + c.Fatalf("error tagging foobox: %s", err) } imageId := stringid.TruncateID(strings.TrimSpace(out)) defer deleteImages(imageId) // overwrite the tag, making the previous image dangling - c = exec.Command(dockerBinary, "tag", "-f", "busybox", "foobox") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "tag", "-f", "busybox", "foobox") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("error tagging foobox: %s", err) + c.Fatalf("error tagging foobox: %s", err) } defer deleteImages("foobox") - c = exec.Command(dockerBinary, "images", "-q", "-f", "dangling=true") - out, _, err = runCommandWithOutput(c) + cmd = exec.Command(dockerBinary, "images", "-q", "-f", "dangling=true") + out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("listing images failed with errors: %s, %v", out, err) + c.Fatalf("listing images failed with errors: %s, %v", out, err) } if e, a := 1, strings.Count(out, imageId); e != a { - t.Fatalf("expected 1 dangling image, got %d: %s", a, out) + c.Fatalf("expected 1 dangling image, got %d: %s", a, out) } - logDone("images - dangling image only listed once") } diff --git a/integration-cli/docker_cli_import_test.go b/integration-cli/docker_cli_import_test.go index 087d08bd5..dd06ef822 100644 --- a/integration-cli/docker_cli_import_test.go +++ b/integration-cli/docker_cli_import_test.go @@ -3,14 +3,15 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestImportDisplay(t *testing.T) { +func (s *DockerSuite) TestImportDisplay(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal("failed to create a container", out, err) + c.Fatal("failed to create a container", out, err) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) @@ -20,11 +21,11 @@ func TestImportDisplay(t *testing.T) { exec.Command(dockerBinary, "import", "-"), ) if err != nil { - t.Errorf("import failed with errors: %v, output: %q", err, out) + c.Errorf("import failed with errors: %v, output: %q", err, out) } if n := strings.Count(out, "\n"); n != 1 { - t.Fatalf("display is messed up: %d '\\n' instead of 1:\n%s", n, out) + c.Fatalf("display is messed up: %d '\\n' instead of 1:\n%s", n, out) } image := strings.TrimSpace(out) defer deleteImages(image) @@ -32,12 +33,11 @@ func TestImportDisplay(t *testing.T) { runCmd = exec.Command(dockerBinary, "run", "--rm", image, "true") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal("failed to create a container", out, err) + c.Fatal("failed to create a container", out, err) } if out != "" { - t.Fatalf("command output should've been nothing, was %q", out) + c.Fatalf("command output should've been nothing, was %q", out) } - logDone("import - display is fine, imported image runs") } diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go index e6b79f01f..a7a931e85 100644 --- a/integration-cli/docker_cli_info_test.go +++ b/integration-cli/docker_cli_info_test.go @@ -3,15 +3,16 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // ensure docker info succeeds -func TestInfoEnsureSucceeds(t *testing.T) { +func (s *DockerSuite) TestInfoEnsureSucceeds(c *check.C) { versionCmd := exec.Command(dockerBinary, "info") out, exitCode, err := runCommandWithOutput(versionCmd) if err != nil || exitCode != 0 { - t.Fatalf("failed to execute docker info: %s, %v", out, err) + c.Fatalf("failed to execute docker info: %s, %v", out, err) } // always shown fields @@ -29,9 +30,8 @@ func TestInfoEnsureSucceeds(t *testing.T) { for _, linePrefix := range stringsToCheck { if !strings.Contains(out, linePrefix) { - t.Errorf("couldn't find string %v in output", linePrefix) + c.Errorf("couldn't find string %v in output", linePrefix) } } - logDone("info - verify that it works") } diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go index cf42217ac..73eb7c8af 100644 --- a/integration-cli/docker_cli_inspect_test.go +++ b/integration-cli/docker_cli_inspect_test.go @@ -3,21 +3,21 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestInspectImage(t *testing.T) { +func (s *DockerSuite) TestInspectImage(c *check.C) { imageTest := "emptyfs" imageTestID := "511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158" imagesCmd := exec.Command(dockerBinary, "inspect", "--format='{{.Id}}'", imageTest) out, exitCode, err := runCommandWithOutput(imagesCmd) if exitCode != 0 || err != nil { - t.Fatalf("failed to inspect image: %s, %v", out, err) + c.Fatalf("failed to inspect image: %s, %v", out, err) } if id := strings.TrimSuffix(out, "\n"); id != imageTestID { - t.Fatalf("Expected id: %s for image: %s but received id: %s", imageTestID, imageTest, id) + c.Fatalf("Expected id: %s for image: %s but received id: %s", imageTestID, imageTest, id) } - logDone("inspect - inspect an image") } diff --git a/integration-cli/docker_cli_kill_test.go b/integration-cli/docker_cli_kill_test.go index cd86c0c56..d08709671 100644 --- a/integration-cli/docker_cli_kill_test.go +++ b/integration-cli/docker_cli_kill_test.go @@ -3,73 +3,72 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestKillContainer(t *testing.T) { +func (s *DockerSuite) TestKillContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("out should've been a container id: %s, %v", out, err) + c.Fatalf("out should've been a container id: %s, %v", out, err) } killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) if out, _, err = runCommandWithOutput(killCmd); err != nil { - t.Fatalf("failed to kill container: %s, %v", out, err) + c.Fatalf("failed to kill container: %s, %v", out, err) } listRunningContainersCmd := exec.Command(dockerBinary, "ps", "-q") out, _, err = runCommandWithOutput(listRunningContainersCmd) if err != nil { - t.Fatalf("failed to list running containers: %s, %v", out, err) + c.Fatalf("failed to list running containers: %s, %v", out, err) } if strings.Contains(out, cleanedContainerID) { - t.Fatal("killed container is still running") + c.Fatal("killed container is still running") } deleteContainer(cleanedContainerID) - logDone("kill - kill container running top") } -func TestKillDifferentUserContainer(t *testing.T) { +func (s *DockerSuite) TestKillDifferentUserContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-u", "daemon", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("out should've been a container id: %s, %v", out, err) + c.Fatalf("out should've been a container id: %s, %v", out, err) } killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) if out, _, err = runCommandWithOutput(killCmd); err != nil { - t.Fatalf("failed to kill container: %s, %v", out, err) + c.Fatalf("failed to kill container: %s, %v", out, err) } listRunningContainersCmd := exec.Command(dockerBinary, "ps", "-q") out, _, err = runCommandWithOutput(listRunningContainersCmd) if err != nil { - t.Fatalf("failed to list running containers: %s, %v", out, err) + c.Fatalf("failed to list running containers: %s, %v", out, err) } if strings.Contains(out, cleanedContainerID) { - t.Fatal("killed container is still running") + c.Fatal("killed container is still running") } deleteContainer(cleanedContainerID) - logDone("kill - kill container running top from a different user") } diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index 0f48cbb1e..95b340be2 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -8,89 +8,81 @@ import ( "reflect" "regexp" "strings" - "testing" "time" "github.com/docker/docker/pkg/iptables" + "github.com/go-check/check" ) -func TestLinksEtcHostsRegularFile(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksEtcHostsRegularFile(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--net=host", "busybox", "ls", "-la", "/etc/hosts") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !strings.HasPrefix(out, "-") { - t.Errorf("/etc/hosts should be a regular file") + c.Errorf("/etc/hosts should be a regular file") } - logDone("link - /etc/hosts is a regular file") } -func TestLinksEtcHostsContentMatch(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestLinksEtcHostsContentMatch(c *check.C) { + testRequires(c, SameHostDaemon) runCmd := exec.Command(dockerBinary, "run", "--net=host", "busybox", "cat", "/etc/hosts") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } hosts, err := ioutil.ReadFile("/etc/hosts") if os.IsNotExist(err) { - t.Skip("/etc/hosts does not exist, skip this test") + c.Skip("/etc/hosts does not exist, skip this test") } if out != string(hosts) { - t.Errorf("container") + c.Errorf("container") } - logDone("link - /etc/hosts matches hosts copy") } -func TestLinksPingUnlinkedContainers(t *testing.T) { +func (s *DockerSuite) TestLinksPingUnlinkedContainers(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--rm", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1") exitCode, err := runCommand(runCmd) if exitCode == 0 { - t.Fatal("run ping did not fail") + c.Fatal("run ping did not fail") } else if exitCode != 1 { - t.Fatalf("run ping failed with errors: %v", err) + c.Fatalf("run ping failed with errors: %v", err) } - logDone("links - ping unlinked container") } // Test for appropriate error when calling --link with an invalid target container -func TestLinksInvalidContainerTarget(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksInvalidContainerTarget(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--link", "bogus:alias", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err == nil { - t.Fatal("an invalid container target should produce an error") + c.Fatal("an invalid container target should produce an error") } if !strings.Contains(out, "Could not get container") { - t.Fatalf("error output expected 'Could not get container', but got %q instead; err: %v", out, err) + c.Fatalf("error output expected 'Could not get container', but got %q instead; err: %v", out, err) } - logDone("links - linking to non-existent container should not work") } -func TestLinksPingLinkedContainers(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksPingLinkedContainers(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "container1", "--hostname", "fred", "busybox", "top") if _, err := runCommand(runCmd); err != nil { - t.Fatal(err) + c.Fatal(err) } runCmd = exec.Command(dockerBinary, "run", "-d", "--name", "container2", "--hostname", "wilma", "busybox", "top") if _, err := runCommand(runCmd); err != nil { - t.Fatal(err) + c.Fatal(err) } runArgs := []string{"run", "--rm", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "sh", "-c"} @@ -98,74 +90,68 @@ func TestLinksPingLinkedContainers(t *testing.T) { // test ping by alias, ping by name, and ping by hostname // 1. Ping by alias - dockerCmd(t, append(runArgs, fmt.Sprintf(pingCmd, "alias1", "alias2"))...) + dockerCmd(c, append(runArgs, fmt.Sprintf(pingCmd, "alias1", "alias2"))...) // 2. Ping by container name - dockerCmd(t, append(runArgs, fmt.Sprintf(pingCmd, "container1", "container2"))...) + dockerCmd(c, append(runArgs, fmt.Sprintf(pingCmd, "container1", "container2"))...) // 3. Ping by hostname - dockerCmd(t, append(runArgs, fmt.Sprintf(pingCmd, "fred", "wilma"))...) + dockerCmd(c, append(runArgs, fmt.Sprintf(pingCmd, "fred", "wilma"))...) - logDone("links - ping linked container") } -func TestLinksPingLinkedContainersAfterRename(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksPingLinkedContainersAfterRename(c *check.C) { - out, _ := dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") + out, _ := dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") idA := strings.TrimSpace(out) - out, _ = dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") + out, _ = dockerCmd(c, "run", "-d", "--name", "container2", "busybox", "top") idB := strings.TrimSpace(out) - dockerCmd(t, "rename", "container1", "container_new") - dockerCmd(t, "run", "--rm", "--link", "container_new:alias1", "--link", "container2:alias2", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1") - dockerCmd(t, "kill", idA) - dockerCmd(t, "kill", idB) + dockerCmd(c, "rename", "container1", "container_new") + dockerCmd(c, "run", "--rm", "--link", "container_new:alias1", "--link", "container2:alias2", "busybox", "sh", "-c", "ping -c 1 alias1 -W 1 && ping -c 1 alias2 -W 1") + dockerCmd(c, "kill", idA) + dockerCmd(c, "kill", idB) - logDone("links - ping linked container after rename") } -func TestLinksIpTablesRulesWhenLinkAndUnlink(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestLinksIpTablesRulesWhenLinkAndUnlink(c *check.C) { + testRequires(c, SameHostDaemon) - dockerCmd(t, "run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "top") - dockerCmd(t, "run", "-d", "--name", "parent", "--link", "child:http", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "parent", "--link", "child:http", "busybox", "top") - childIP := findContainerIP(t, "child") - parentIP := findContainerIP(t, "parent") + childIP := findContainerIP(c, "child") + parentIP := findContainerIP(c, "parent") sourceRule := []string{"-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", childIP, "--sport", "80", "-d", parentIP, "-j", "ACCEPT"} destinationRule := []string{"-i", "docker0", "-o", "docker0", "-p", "tcp", "-s", parentIP, "--dport", "80", "-d", childIP, "-j", "ACCEPT"} if !iptables.Exists("filter", "DOCKER", sourceRule...) || !iptables.Exists("filter", "DOCKER", destinationRule...) { - t.Fatal("Iptables rules not found") + c.Fatal("Iptables rules not found") } - dockerCmd(t, "rm", "--link", "parent/http") + dockerCmd(c, "rm", "--link", "parent/http") if iptables.Exists("filter", "DOCKER", sourceRule...) || iptables.Exists("filter", "DOCKER", destinationRule...) { - t.Fatal("Iptables rules should be removed when unlink") + c.Fatal("Iptables rules should be removed when unlink") } - dockerCmd(t, "kill", "child") - dockerCmd(t, "kill", "parent") + dockerCmd(c, "kill", "child") + dockerCmd(c, "kill", "parent") - logDone("link - verify iptables when link and unlink") } -func TestLinksInspectLinksStarted(t *testing.T) { +func (s *DockerSuite) TestLinksInspectLinksStarted(c *check.C) { var ( expected = map[string]struct{}{"/container1:/testinspectlink/alias1": {}, "/container2:/testinspectlink/alias2": {}} result []string ) - defer deleteAllContainers() - dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") - dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") - dockerCmd(t, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "container2", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "top") links, err := inspectFieldJSON("testinspectlink", "HostConfig.Links") if err != nil { - t.Fatal(err) + c.Fatal(err) } err = unmarshalJSON([]byte(links), &result) if err != nil { - t.Fatal(err) + c.Fatal(err) } output := convertSliceOfStringsToMap(result) @@ -173,28 +159,26 @@ func TestLinksInspectLinksStarted(t *testing.T) { equal := reflect.DeepEqual(output, expected) if !equal { - t.Fatalf("Links %s, expected %s", result, expected) + c.Fatalf("Links %s, expected %s", result, expected) } - logDone("link - links in started container inspect") } -func TestLinksInspectLinksStopped(t *testing.T) { +func (s *DockerSuite) TestLinksInspectLinksStopped(c *check.C) { var ( expected = map[string]struct{}{"/container1:/testinspectlink/alias1": {}, "/container2:/testinspectlink/alias2": {}} result []string ) - defer deleteAllContainers() - dockerCmd(t, "run", "-d", "--name", "container1", "busybox", "top") - dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "top") - dockerCmd(t, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "true") + dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "container2", "busybox", "top") + dockerCmd(c, "run", "-d", "--name", "testinspectlink", "--link", "container1:alias1", "--link", "container2:alias2", "busybox", "true") links, err := inspectFieldJSON("testinspectlink", "HostConfig.Links") if err != nil { - t.Fatal(err) + c.Fatal(err) } err = unmarshalJSON([]byte(links), &result) if err != nil { - t.Fatal(err) + c.Fatal(err) } output := convertSliceOfStringsToMap(result) @@ -202,47 +186,42 @@ func TestLinksInspectLinksStopped(t *testing.T) { equal := reflect.DeepEqual(output, expected) if !equal { - t.Fatalf("Links %s, but expected %s", result, expected) + c.Fatalf("Links %s, but expected %s", result, expected) } - logDone("link - links in stopped container inspect") } -func TestLinksNotStartedParentNotFail(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksNotStartedParentNotFail(c *check.C) { runCmd := exec.Command(dockerBinary, "create", "--name=first", "busybox", "top") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "create", "--name=second", "--link=first:first", "busybox", "top") out, _, _, err = runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "start", "first") out, _, _, err = runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - logDone("link - container start successfully updating stopped parent links") } -func TestLinksHostsFilesInject(t *testing.T) { - testRequires(t, SameHostDaemon, ExecSupport) - - defer deleteAllContainers() +func (s *DockerSuite) TestLinksHostsFilesInject(c *check.C) { + testRequires(c, SameHostDaemon, ExecSupport) out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "--name", "one", "busybox", "top")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } idOne := strings.TrimSpace(out) out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "--name", "two", "--link", "one:onetwo", "busybox", "top")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } idTwo := strings.TrimSpace(out) @@ -251,89 +230,83 @@ func TestLinksHostsFilesInject(t *testing.T) { contentOne, err := readContainerFileWithExec(idOne, "/etc/hosts") if err != nil { - t.Fatal(err, string(contentOne)) + c.Fatal(err, string(contentOne)) } contentTwo, err := readContainerFileWithExec(idTwo, "/etc/hosts") if err != nil { - t.Fatal(err, string(contentTwo)) + c.Fatal(err, string(contentTwo)) } if !strings.Contains(string(contentTwo), "onetwo") { - t.Fatal("Host is not present in updated hosts file", string(contentTwo)) + c.Fatal("Host is not present in updated hosts file", string(contentTwo)) } - logDone("link - ensure containers hosts files are updated with the link alias.") } -func TestLinksNetworkHostContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestLinksNetworkHostContainer(c *check.C) { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--net", "host", "--name", "host_container", "busybox", "top")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "should_fail", "--link", "host_container:tester", "busybox", "true")) if err == nil || !strings.Contains(out, "--net=host can't be used with links. This would result in undefined behavior.") { - t.Fatalf("Running container linking to a container with --net host should have failed: %s", out) + c.Fatalf("Running container linking to a container with --net host should have failed: %s", out) } - logDone("link - error thrown when linking to container with --net host") } -func TestLinksUpdateOnRestart(t *testing.T) { - testRequires(t, SameHostDaemon, ExecSupport) - - defer deleteAllContainers() +func (s *DockerSuite) TestLinksUpdateOnRestart(c *check.C) { + testRequires(c, SameHostDaemon, ExecSupport) if out, err := exec.Command(dockerBinary, "run", "-d", "--name", "one", "busybox", "top").CombinedOutput(); err != nil { - t.Fatal(err, string(out)) + c.Fatal(err, string(out)) } out, err := exec.Command(dockerBinary, "run", "-d", "--name", "two", "--link", "one:onetwo", "--link", "one:one", "busybox", "top").CombinedOutput() if err != nil { - t.Fatal(err, string(out)) + c.Fatal(err, string(out)) } id := strings.TrimSpace(string(out)) realIP, err := inspectField("one", "NetworkSettings.IPAddress") if err != nil { - t.Fatal(err) + c.Fatal(err) } content, err := readContainerFileWithExec(id, "/etc/hosts") if err != nil { - t.Fatal(err, string(content)) + c.Fatal(err, string(content)) } getIP := func(hosts []byte, hostname string) string { re := regexp.MustCompile(fmt.Sprintf(`(\S*)\t%s`, regexp.QuoteMeta(hostname))) matches := re.FindSubmatch(hosts) if matches == nil { - t.Fatalf("Hostname %s have no matches in hosts", hostname) + c.Fatalf("Hostname %s have no matches in hosts", hostname) } return string(matches[1]) } if ip := getIP(content, "one"); ip != realIP { - t.Fatalf("For 'one' alias expected IP: %s, got: %s", realIP, ip) + c.Fatalf("For 'one' alias expected IP: %s, got: %s", realIP, ip) } if ip := getIP(content, "onetwo"); ip != realIP { - t.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip) + c.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip) } if out, err := exec.Command(dockerBinary, "restart", "one").CombinedOutput(); err != nil { - t.Fatal(err, string(out)) + c.Fatal(err, string(out)) } realIP, err = inspectField("one", "NetworkSettings.IPAddress") if err != nil { - t.Fatal(err) + c.Fatal(err) } content, err = readContainerFileWithExec(id, "/etc/hosts") if err != nil { - t.Fatal(err, string(content)) + c.Fatal(err, string(content)) } if ip := getIP(content, "one"); ip != realIP { - t.Fatalf("For 'one' alias expected IP: %s, got: %s", realIP, ip) + c.Fatalf("For 'one' alias expected IP: %s, got: %s", realIP, ip) } if ip := getIP(content, "onetwo"); ip != realIP { - t.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip) + c.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip) } - logDone("link - ensure containers hosts files are updated on restart") } diff --git a/integration-cli/docker_cli_login_test.go b/integration-cli/docker_cli_login_test.go index 9bf90f3ad..3b4431d2d 100644 --- a/integration-cli/docker_cli_login_test.go +++ b/integration-cli/docker_cli_login_test.go @@ -3,10 +3,11 @@ package main import ( "bytes" "os/exec" - "testing" + + "github.com/go-check/check" ) -func TestLoginWithoutTTY(t *testing.T) { +func (s *DockerSuite) TestLoginWithoutTTY(c *check.C) { cmd := exec.Command(dockerBinary, "login") // Send to stdin so the process does not get the TTY @@ -14,8 +15,7 @@ func TestLoginWithoutTTY(t *testing.T) { // run the command and block until it's done if err := cmd.Run(); err == nil { - t.Fatal("Expected non nil err when loginning in & TTY not available") + c.Fatal("Expected non nil err when loginning in & TTY not available") } - logDone("login - login without TTY") } diff --git a/integration-cli/docker_cli_logs_test.go b/integration-cli/docker_cli_logs_test.go index c236ef085..7f04a328b 100644 --- a/integration-cli/docker_cli_logs_test.go +++ b/integration-cli/docker_cli_logs_test.go @@ -5,19 +5,19 @@ import ( "os/exec" "regexp" "strings" - "testing" "time" "github.com/docker/docker/pkg/timeutils" + "github.com/go-check/check" ) // This used to work, it test a log of PageSize-1 (gh#4851) -func TestLogsContainerSmallerThanPage(t *testing.T) { +func (s *DockerSuite) TestLogsContainerSmallerThanPage(c *check.C) { 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) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -26,25 +26,24 @@ func TestLogsContainerSmallerThanPage(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } if len(out) != testLen+1 { - t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) + c.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) } 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) { +func (s *DockerSuite) TestLogsContainerBiggerThanPage(c *check.C) { 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) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -53,25 +52,24 @@ func TestLogsContainerBiggerThanPage(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } if len(out) != testLen+1 { - t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) + c.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) } 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) { +func (s *DockerSuite) TestLogsContainerMuchBiggerThanPage(c *check.C) { 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) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -80,25 +78,24 @@ func TestLogsContainerMuchBiggerThanPage(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } if len(out) != testLen+1 { - t.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) + c.Fatalf("Expected log length of %d, received %d\n", testLen+1, len(out)) } deleteContainer(cleanedContainerID) - logDone("logs - logs container running echo much bigger than page size") } -func TestLogsTimestamps(t *testing.T) { +func (s *DockerSuite) TestLogsTimestamps(c *check.C) { testLen := 100 runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo =; done;", testLen)) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -107,13 +104,13 @@ func TestLogsTimestamps(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", "-t", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } lines := strings.Split(out, "\n") if len(lines) != testLen+1 { - t.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines)) + c.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines)) } ts := regexp.MustCompile(`^.* `) @@ -122,26 +119,25 @@ func TestLogsTimestamps(t *testing.T) { if l != "" { _, err := time.Parse(timeutils.RFC3339NanoFixed+" ", ts.FindString(l)) if err != nil { - t.Fatalf("Failed to parse timestamp from %v: %v", l, err) + c.Fatalf("Failed to parse timestamp from %v: %v", l, err) } if l[29] != 'Z' { // ensure we have padded 0's - t.Fatalf("Timestamp isn't padded properly: %s", l) + c.Fatalf("Timestamp isn't padded properly: %s", l) } } } deleteContainer(cleanedContainerID) - logDone("logs - logs with timestamps") } -func TestLogsSeparateStderr(t *testing.T) { +func (s *DockerSuite) TestLogsSeparateStderr(c *check.C) { msg := "stderr_log" runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("echo %s 1>&2", msg)) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -150,30 +146,29 @@ func TestLogsSeparateStderr(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) stdout, stderr, _, err := runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } if stdout != "" { - t.Fatalf("Expected empty stdout stream, got %v", stdout) + c.Fatalf("Expected empty stdout stream, got %v", stdout) } stderr = strings.TrimSpace(stderr) if stderr != msg { - t.Fatalf("Expected %v in stderr stream, got %v", msg, stderr) + c.Fatalf("Expected %v in stderr stream, got %v", msg, stderr) } deleteContainer(cleanedContainerID) - logDone("logs - separate stderr (without pseudo-tty)") } -func TestLogsStderrInStdout(t *testing.T) { +func (s *DockerSuite) TestLogsStderrInStdout(c *check.C) { msg := "stderr_log" runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "busybox", "sh", "-c", fmt.Sprintf("echo %s 1>&2", msg)) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -182,30 +177,29 @@ func TestLogsStderrInStdout(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", cleanedContainerID) stdout, stderr, _, err := runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } if stderr != "" { - t.Fatalf("Expected empty stderr stream, got %v", stdout) + c.Fatalf("Expected empty stderr stream, got %v", stdout) } stdout = strings.TrimSpace(stdout) if stdout != msg { - t.Fatalf("Expected %v in stdout stream, got %v", msg, stdout) + c.Fatalf("Expected %v in stdout stream, got %v", msg, stdout) } deleteContainer(cleanedContainerID) - logDone("logs - stderr in stdout (with pseudo-tty)") } -func TestLogsTail(t *testing.T) { +func (s *DockerSuite) TestLogsTail(c *check.C) { testLen := 100 runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", fmt.Sprintf("for i in $(seq 1 %d); do echo =; done;", testLen)) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -214,49 +208,48 @@ func TestLogsTail(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", "--tail", "5", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } lines := strings.Split(out, "\n") if len(lines) != 6 { - t.Fatalf("Expected log %d lines, received %d\n", 6, len(lines)) + c.Fatalf("Expected log %d lines, received %d\n", 6, len(lines)) } logsCmd = exec.Command(dockerBinary, "logs", "--tail", "all", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } lines = strings.Split(out, "\n") if len(lines) != testLen+1 { - t.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines)) + c.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines)) } logsCmd = exec.Command(dockerBinary, "logs", "--tail", "random", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(logsCmd) if err != nil { - t.Fatalf("failed to log container: %s, %v", out, err) + c.Fatalf("failed to log container: %s, %v", out, err) } lines = strings.Split(out, "\n") if len(lines) != testLen+1 { - t.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines)) + c.Fatalf("Expected log %d lines, received %d\n", testLen+1, len(lines)) } deleteContainer(cleanedContainerID) - logDone("logs - logs tail") } -func TestLogsFollowStopped(t *testing.T) { +func (s *DockerSuite) TestLogsFollowStopped(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -264,34 +257,33 @@ func TestLogsFollowStopped(t *testing.T) { logsCmd := exec.Command(dockerBinary, "logs", "-f", cleanedContainerID) if err := logsCmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } - c := make(chan struct{}) + ch := make(chan struct{}) go func() { if err := logsCmd.Wait(); err != nil { - t.Fatal(err) + c.Fatal(err) } - close(c) + close(ch) }() select { - case <-c: + case <-ch: case <-time.After(1 * time.Second): - t.Fatal("Following logs is hanged") + c.Fatal("Following logs is hanged") } deleteContainer(cleanedContainerID) - logDone("logs - logs follow stopped container") } // Regression test for #8832 -func TestLogsFollowSlowStdoutConsumer(t *testing.T) { +func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", `usleep 200000;yes X | head -c 200000`) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("run failed with errors: %s, %v", out, err) + c.Fatalf("run failed with errors: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -308,30 +300,29 @@ func TestLogsFollowSlowStdoutConsumer(t *testing.T) { stdout, err := logCmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } if err := logCmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } // First read slowly bytes1, err := consumeWithSpeed(stdout, 10, 50*time.Millisecond, stopSlowRead) if err != nil { - t.Fatal(err) + c.Fatal(err) } // After the container has finished we can continue reading fast bytes2, err := consumeWithSpeed(stdout, 32*1024, 0, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } actual := bytes1 + bytes2 expected := 200000 if actual != expected { - t.Fatalf("Invalid bytes read: %d, expected %d", actual, expected) + c.Fatalf("Invalid bytes read: %d, expected %d", actual, expected) } - logDone("logs - follow slow consumer") } diff --git a/integration-cli/docker_cli_nat_test.go b/integration-cli/docker_cli_nat_test.go index 35bd378e4..875b6540a 100644 --- a/integration-cli/docker_cli_nat_test.go +++ b/integration-cli/docker_cli_nat_test.go @@ -5,32 +5,32 @@ import ( "net" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestNetworkNat(t *testing.T) { - testRequires(t, SameHostDaemon, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestNetworkNat(c *check.C) { + testRequires(c, SameHostDaemon, NativeExecDriver) iface, err := net.InterfaceByName("eth0") if err != nil { - t.Skipf("Test not running with `make test`. Interface eth0 not found: %s", err) + c.Skip(fmt.Sprintf("Test not running with `make test`. Interface eth0 not found: %v", err)) } ifaceAddrs, err := iface.Addrs() if err != nil || len(ifaceAddrs) == 0 { - t.Fatalf("Error retrieving addresses for eth0: %v (%d addresses)", err, len(ifaceAddrs)) + c.Fatalf("Error retrieving addresses for eth0: %v (%d addresses)", err, len(ifaceAddrs)) } ifaceIP, _, err := net.ParseCIDR(ifaceAddrs[0].String()) if err != nil { - t.Fatalf("Error retrieving the up for eth0: %s", err) + c.Fatalf("Error retrieving the up for eth0: %s", err) } runCmd := exec.Command(dockerBinary, "run", "-dt", "-p", "8080:8080", "busybox", "nc", "-lp", "8080") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -38,25 +38,24 @@ func TestNetworkNat(t *testing.T) { runCmd = exec.Command(dockerBinary, "run", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s 8080", ifaceIP)) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to retrieve logs for container: %s, %v", out, err) + c.Fatalf("failed to retrieve logs for container: %s, %v", out, err) } out = strings.Trim(out, "\r\n") if expected := "hello world"; out != expected { - t.Fatalf("Unexpected output. Expected: %q, received: %q for iface %s", expected, out, ifaceIP) + c.Fatalf("Unexpected output. Expected: %q, received: %q for iface %s", expected, out, ifaceIP) } killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) if out, _, err = runCommandWithOutput(killCmd); err != nil { - t.Fatalf("failed to kill container: %s, %v", out, err) + c.Fatalf("failed to kill container: %s, %v", out, err) } - logDone("network - make sure nat works through the host") } diff --git a/integration-cli/docker_cli_pause_test.go b/integration-cli/docker_cli_pause_test.go index 6c620e7cd..0256fb92b 100644 --- a/integration-cli/docker_cli_pause_test.go +++ b/integration-cli/docker_cli_pause_test.go @@ -4,78 +4,76 @@ import ( "fmt" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestPause(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPause(c *check.C) { defer unpauseAllContainers() name := "testeventpause" - out, _ := dockerCmd(t, "images", "-q") + out, _ := dockerCmd(c, "images", "-q") image := strings.Split(out, "\n")[0] - dockerCmd(t, "run", "-d", "--name", name, image, "top") + dockerCmd(c, "run", "-d", "--name", name, image, "top") - dockerCmd(t, "pause", name) + dockerCmd(c, "pause", name) pausedContainers, err := getSliceOfPausedContainers() if err != nil { - t.Fatalf("error thrown while checking if containers were paused: %v", err) + c.Fatalf("error thrown while checking if containers were paused: %v", err) } if len(pausedContainers) != 1 { - t.Fatalf("there should be one paused container and not %d", len(pausedContainers)) + c.Fatalf("there should be one paused container and not %d", len(pausedContainers)) } - dockerCmd(t, "unpause", name) + dockerCmd(c, "unpause", name) - eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ = runCommandWithOutput(eventsCmd) events := strings.Split(out, "\n") if len(events) <= 1 { - t.Fatalf("Missing expected event") + c.Fatalf("Missing expected event") } pauseEvent := strings.Fields(events[len(events)-3]) unpauseEvent := strings.Fields(events[len(events)-2]) if pauseEvent[len(pauseEvent)-1] != "pause" { - t.Fatalf("event should be pause, not %#v", pauseEvent) + c.Fatalf("event should be pause, not %#v", pauseEvent) } if unpauseEvent[len(unpauseEvent)-1] != "unpause" { - t.Fatalf("event should be unpause, not %#v", unpauseEvent) + c.Fatalf("event should be unpause, not %#v", unpauseEvent) } - logDone("pause - pause/unpause is logged") } -func TestPauseMultipleContainers(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPauseMultipleContainers(c *check.C) { defer unpauseAllContainers() containers := []string{ "testpausewithmorecontainers1", "testpausewithmorecontainers2", } - out, _ := dockerCmd(t, "images", "-q") + out, _ := dockerCmd(c, "images", "-q") image := strings.Split(out, "\n")[0] for _, name := range containers { - dockerCmd(t, "run", "-d", "--name", name, image, "top") + dockerCmd(c, "run", "-d", "--name", name, image, "top") } - dockerCmd(t, append([]string{"pause"}, containers...)...) + dockerCmd(c, append([]string{"pause"}, containers...)...) pausedContainers, err := getSliceOfPausedContainers() if err != nil { - t.Fatalf("error thrown while checking if containers were paused: %v", err) + c.Fatalf("error thrown while checking if containers were paused: %v", err) } if len(pausedContainers) != len(containers) { - t.Fatalf("there should be %d paused container and not %d", len(containers), len(pausedContainers)) + c.Fatalf("there should be %d paused container and not %d", len(containers), len(pausedContainers)) } - dockerCmd(t, append([]string{"unpause"}, containers...)...) + dockerCmd(c, append([]string{"unpause"}, containers...)...) - eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix())) + eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ = runCommandWithOutput(eventsCmd) events := strings.Split(out, "\n") if len(events) <= len(containers)*3-2 { - t.Fatalf("Missing expected event") + c.Fatalf("Missing expected event") } pauseEvents := make([][]string, len(containers)) @@ -87,14 +85,13 @@ func TestPauseMultipleContainers(t *testing.T) { for _, pauseEvent := range pauseEvents { if pauseEvent[len(pauseEvent)-1] != "pause" { - t.Fatalf("event should be pause, not %#v", pauseEvent) + c.Fatalf("event should be pause, not %#v", pauseEvent) } } for _, unpauseEvent := range unpauseEvents { if unpauseEvent[len(unpauseEvent)-1] != "unpause" { - t.Fatalf("event should be unpause, not %#v", unpauseEvent) + c.Fatalf("event should be unpause, not %#v", unpauseEvent) } } - logDone("pause - multi pause/unpause is logged") } diff --git a/integration-cli/docker_cli_port_test.go b/integration-cli/docker_cli_port_test.go index 8fe6c2dc6..f0cb66396 100644 --- a/integration-cli/docker_cli_port_test.go +++ b/integration-cli/docker_cli_port_test.go @@ -5,42 +5,42 @@ import ( "os/exec" "sort" "strings" - "testing" + + "github.com/go-check/check" ) -func TestPortList(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPortList(c *check.C) { // one port runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "9876:80", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", firstID, "80") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{"0.0.0.0:9876"}) { - t.Error("Port list is not correct") + if !assertPortList(c, out, []string{"0.0.0.0:9876"}) { + c.Error("Port list is not correct") } runCmd = exec.Command(dockerBinary, "port", firstID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{"80/tcp -> 0.0.0.0:9876"}) { - t.Error("Port list is not correct") + if !assertPortList(c, out, []string{"80/tcp -> 0.0.0.0:9876"}) { + c.Error("Port list is not correct") } runCmd = exec.Command(dockerBinary, "rm", "-f", firstID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // three port @@ -51,36 +51,36 @@ func TestPortList(t *testing.T) { "busybox", "top") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } ID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", ID, "80") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{"0.0.0.0:9876"}) { - t.Error("Port list is not correct") + if !assertPortList(c, out, []string{"0.0.0.0:9876"}) { + c.Error("Port list is not correct") } runCmd = exec.Command(dockerBinary, "port", ID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{ + if !assertPortList(c, out, []string{ "80/tcp -> 0.0.0.0:9876", "81/tcp -> 0.0.0.0:9877", "82/tcp -> 0.0.0.0:9878"}) { - t.Error("Port list is not correct") + c.Error("Port list is not correct") } runCmd = exec.Command(dockerBinary, "rm", "-f", ID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // more and one port mapped to the same container port @@ -92,46 +92,45 @@ func TestPortList(t *testing.T) { "busybox", "top") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } ID = strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", ID, "80") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{"0.0.0.0:9876", "0.0.0.0:9999"}) { - t.Error("Port list is not correct") + if !assertPortList(c, out, []string{"0.0.0.0:9876", "0.0.0.0:9999"}) { + c.Error("Port list is not correct") } runCmd = exec.Command(dockerBinary, "port", ID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{ + if !assertPortList(c, out, []string{ "80/tcp -> 0.0.0.0:9876", "80/tcp -> 0.0.0.0:9999", "81/tcp -> 0.0.0.0:9877", "82/tcp -> 0.0.0.0:9878"}) { - t.Error("Port list is not correct\n", out) + c.Error("Port list is not correct\n", out) } runCmd = exec.Command(dockerBinary, "rm", "-f", ID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - logDone("port - test port list") } -func assertPortList(t *testing.T, out string, expected []string) bool { +func assertPortList(c *check.C, out string, expected []string) bool { //lines := strings.Split(out, "\n") lines := strings.Split(strings.Trim(out, "\n "), "\n") if len(lines) != len(expected) { - t.Errorf("different size lists %s, %d, %d", out, len(lines), len(expected)) + c.Errorf("different size lists %s, %d, %d", out, len(lines), len(expected)) return false } sort.Strings(lines) @@ -139,7 +138,7 @@ func assertPortList(t *testing.T, out string, expected []string) bool { for i := 0; i < len(expected); i++ { if lines[i] != expected[i] { - t.Error("|" + lines[i] + "!=" + expected[i] + "|") + c.Error("|" + lines[i] + "!=" + expected[i] + "|") return false } } @@ -147,84 +146,78 @@ func assertPortList(t *testing.T, out string, expected []string) bool { return true } -func TestPortHostBinding(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestPortHostBinding(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "-p", "9876:80", "busybox", "nc", "-l", "-p", "80") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", firstID, "80") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - if !assertPortList(t, out, []string{"0.0.0.0:9876"}) { - t.Error("Port list is not correct") + if !assertPortList(c, out, []string{"0.0.0.0:9876"}) { + c.Error("Port list is not correct") } runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", "nc", "localhost", "9876") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "rm", "-f", firstID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", "nc", "localhost", "9876") if out, _, err = runCommandWithOutput(runCmd); err == nil { - t.Error("Port is still bound after the Container is removed") + c.Error("Port is still bound after the Container is removed") } - logDone("port - test host binding done") } -func TestPortExposeHostBinding(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestPortExposeHostBinding(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "-P", "--expose", "80", "busybox", "nc", "-l", "-p", "80") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "port", firstID, "80") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } _, exposedPort, err := net.SplitHostPort(out) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", "nc", "localhost", strings.TrimSpace(exposedPort)) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "rm", "-f", firstID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", "nc", "localhost", strings.TrimSpace(exposedPort)) if out, _, err = runCommandWithOutput(runCmd); err == nil { - t.Error("Port is still bound after the Container is removed") + c.Error("Port is still bound after the Container is removed") } - logDone("port - test port expose done") } diff --git a/integration-cli/docker_cli_proxy_test.go b/integration-cli/docker_cli_proxy_test.go index 55c544003..c07ed6406 100644 --- a/integration-cli/docker_cli_proxy_test.go +++ b/integration-cli/docker_cli_proxy_test.go @@ -4,30 +4,30 @@ import ( "net" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestCliProxyDisableProxyUnixSock(t *testing.T) { - testRequires(t, SameHostDaemon) // test is valid when DOCKER_HOST=unix://.. +func (s *DockerSuite) TestCliProxyDisableProxyUnixSock(c *check.C) { + testRequires(c, SameHostDaemon) // test is valid when DOCKER_HOST=unix://.. cmd := exec.Command(dockerBinary, "info") cmd.Env = appendBaseEnv([]string{"HTTP_PROXY=http://127.0.0.1:9999"}) if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - logDone("cli proxy - HTTP_PROXY is not used when connecting to unix sock") } // Can't use localhost here since go has a special case to not use proxy if connecting to localhost // See https://golang.org/pkg/net/http/#ProxyFromEnvironment -func TestCliProxyProxyTCPSock(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestCliProxyProxyTCPSock(c *check.C) { + testRequires(c, SameHostDaemon) // get the IP to use to connect since we can't use localhost addrs, err := net.InterfaceAddrs() if err != nil { - t.Fatal(err) + c.Fatal(err) } var ip string for _, addr := range addrs { @@ -40,25 +40,24 @@ func TestCliProxyProxyTCPSock(t *testing.T) { } if ip == "" { - t.Fatal("could not find ip to connect to") + c.Fatal("could not find ip to connect to") } - d := NewDaemon(t) + d := NewDaemon(c) if err := d.Start("-H", "tcp://"+ip+":2375"); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "info") cmd.Env = []string{"DOCKER_HOST=tcp://" + ip + ":2375", "HTTP_PROXY=127.0.0.1:9999"} if out, _, err := runCommandWithOutput(cmd); err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } // Test with no_proxy cmd.Env = append(cmd.Env, "NO_PROXY="+ip) if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "info")); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - logDone("cli proxy - HTTP_PROXY is used for TCP sock") } diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index deb426fce..d70215350 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -6,24 +6,24 @@ import ( "reflect" "strconv" "strings" - "testing" "time" + + "github.com/go-check/check" ) -func TestPsListContainers(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsListContainers(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } secondID := strings.TrimSpace(out) @@ -31,53 +31,53 @@ func TestPsListContainers(t *testing.T) { runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } thirdID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } fourthID := strings.TrimSpace(out) // make sure the second is running if err := waitRun(secondID); err != nil { - t.Fatalf("waiting for container failed: %v", err) + c.Fatalf("waiting for container failed: %v", err) } // make sure third one is not running runCmd = exec.Command(dockerBinary, "wait", thirdID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // make sure the forth is running if err := waitRun(fourthID); err != nil { - t.Fatalf("waiting for container failed: %v", err) + c.Fatalf("waiting for container failed: %v", err) } // all runCmd = exec.Command(dockerBinary, "ps", "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, []string{fourthID, thirdID, secondID, firstID}) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // running runCmd = exec.Command(dockerBinary, "ps") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, []string{fourthID, secondID, firstID}) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // from here all flag '-a' is ignored @@ -86,156 +86,155 @@ func TestPsListContainers(t *testing.T) { runCmd = exec.Command(dockerBinary, "ps", "-n=2", "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected := []string{fourthID, thirdID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "-n=2") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // since runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected = []string{fourthID, thirdID, secondID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--since", firstID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // before runCmd = exec.Command(dockerBinary, "ps", "--before", thirdID, "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected = []string{secondID, firstID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--before", thirdID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // since & before runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID, "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected = []string{thirdID, secondID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // since & limit runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "-n=2", "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected = []string{fourthID, thirdID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "-n=2") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // before & limit runCmd = exec.Command(dockerBinary, "ps", "--before", fourthID, "-n=1", "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected = []string{thirdID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--before", fourthID, "-n=1") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } // since & before & limit runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID, "-n=1", "-a") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } expected = []string{thirdID} if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } runCmd = exec.Command(dockerBinary, "ps", "--since", firstID, "--before", fourthID, "-n=1") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if !assertContainerList(out, expected) { - t.Errorf("Container list is not in the correct order: %s", out) + c.Errorf("Container list is not in the correct order: %s", out) } - logDone("ps - test ps options") } func assertContainerList(out string, expected []string) bool { @@ -255,8 +254,7 @@ func assertContainerList(out string, expected []string) bool { return true } -func TestPsListContainersSize(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsListContainersSize(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello") runCommandWithOutput(cmd) @@ -267,18 +265,18 @@ func TestPsListContainersSize(t *testing.T) { baseFoundsize := baseLines[1][baseSizeIndex:] baseBytes, err := strconv.Atoi(strings.Split(baseFoundsize, " ")[0]) if err != nil { - t.Fatal(err) + c.Fatal(err) } name := "test_size" runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sh", "-c", "echo 1 > test") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id, err := getIDByName(name) if err != nil { - t.Fatal(err) + c.Fatal(err) } runCmd = exec.Command(dockerBinary, "ps", "-s", "-n=1") @@ -290,54 +288,52 @@ func TestPsListContainersSize(t *testing.T) { select { case <-wait: case <-time.After(3 * time.Second): - t.Fatalf("Calling \"docker ps -s\" timed out!") + c.Fatalf("Calling \"docker ps -s\" timed out!") } if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } lines := strings.Split(strings.Trim(out, "\n "), "\n") if len(lines) != 2 { - t.Fatalf("Expected 2 lines for 'ps -s -n=1' output, got %d", len(lines)) + c.Fatalf("Expected 2 lines for 'ps -s -n=1' output, got %d", len(lines)) } sizeIndex := strings.Index(lines[0], "SIZE") idIndex := strings.Index(lines[0], "CONTAINER ID") foundID := lines[1][idIndex : idIndex+12] if foundID != id[:12] { - t.Fatalf("Expected id %s, got %s", id[:12], foundID) + c.Fatalf("Expected id %s, got %s", id[:12], foundID) } expectedSize := fmt.Sprintf("%d B", (2 + baseBytes)) foundSize := lines[1][sizeIndex:] if foundSize != expectedSize { - t.Fatalf("Expected size %q, got %q", expectedSize, foundSize) + c.Fatalf("Expected size %q, got %q", expectedSize, foundSize) } - logDone("ps - test ps size") } -func TestPsListContainersFilterStatus(t *testing.T) { +func (s *DockerSuite) TestPsListContainersFilterStatus(c *check.C) { // FIXME: this should test paused, but it makes things hang and its wonky // this is because paused containers can't be controlled by signals - defer deleteAllContainers() // start exited container runCmd := exec.Command(dockerBinary, "run", "-d", "busybox") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) // make sure the exited cintainer is not running runCmd = exec.Command(dockerBinary, "wait", firstID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // start running container runCmd = exec.Command(dockerBinary, "run", "-itd", "busybox") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } secondID := strings.TrimSpace(out) @@ -345,313 +341,302 @@ func TestPsListContainersFilterStatus(t *testing.T) { runCmd = exec.Command(dockerBinary, "ps", "-q", "--filter=status=exited") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut := strings.TrimSpace(out) if containerOut != firstID[:12] { - t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out) + c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out) } runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--filter=status=running") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut = strings.TrimSpace(out) if containerOut != secondID[:12] { - t.Fatalf("Expected id %s, got %s for running filter, output: %q", secondID[:12], containerOut, out) + c.Fatalf("Expected id %s, got %s for running filter, output: %q", secondID[:12], containerOut, out) } - logDone("ps - test ps filter status") } -func TestPsListContainersFilterID(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsListContainersFilterID(c *check.C) { // start container runCmd := exec.Command(dockerBinary, "run", "-d", "busybox") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) // start another container runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // filter containers by id runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--filter=id="+firstID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut := strings.TrimSpace(out) if containerOut != firstID[:12] { - t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out) + c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out) } - logDone("ps - test ps filter id") } -func TestPsListContainersFilterName(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsListContainersFilterName(c *check.C) { // start container runCmd := exec.Command(dockerBinary, "run", "-d", "--name=a_name_to_match", "busybox") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) // start another container runCmd = exec.Command(dockerBinary, "run", "-d", "--name=b_name_to_match", "busybox", "top") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // filter containers by name runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--filter=name=a_name_to_match") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut := strings.TrimSpace(out) if containerOut != firstID[:12] { - t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out) + c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID[:12], containerOut, out) } - logDone("ps - test ps filter name") } -func TestPsListContainersFilterLabel(t *testing.T) { +func (s *DockerSuite) TestPsListContainersFilterLabel(c *check.C) { // start container runCmd := exec.Command(dockerBinary, "run", "-d", "-l", "match=me", "-l", "second=tag", "busybox") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstID := strings.TrimSpace(out) // start another container runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "match=me too", "busybox") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } secondID := strings.TrimSpace(out) // start third container runCmd = exec.Command(dockerBinary, "run", "-d", "-l", "nomatch=me", "busybox") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } thirdID := strings.TrimSpace(out) // filter containers by exact match runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut := strings.TrimSpace(out) if containerOut != firstID { - t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out) + c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out) } // filter containers by two labels runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me", "--filter=label=second=tag") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut = strings.TrimSpace(out) if containerOut != firstID { - t.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out) + c.Fatalf("Expected id %s, got %s for exited filter, output: %q", firstID, containerOut, out) } // filter containers by two labels, but expect not found because of AND behavior runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match=me", "--filter=label=second=tag-no") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut = strings.TrimSpace(out) if containerOut != "" { - t.Fatalf("Expected nothing, got %s for exited filter, output: %q", containerOut, out) + c.Fatalf("Expected nothing, got %s for exited filter, output: %q", containerOut, out) } // filter containers by exact key runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=label=match") if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerOut = strings.TrimSpace(out) if (!strings.Contains(containerOut, firstID) || !strings.Contains(containerOut, secondID)) || strings.Contains(containerOut, thirdID) { - t.Fatalf("Expected ids %s,%s, got %s for exited filter, output: %q", firstID, secondID, containerOut, out) + c.Fatalf("Expected ids %s,%s, got %s for exited filter, output: %q", firstID, secondID, containerOut, out) } deleteAllContainers() - logDone("ps - test ps filter label") } -func TestPsListContainersFilterExited(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsListContainersFilterExited(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "top", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "run", "--name", "zero1", "busybox", "true") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } firstZero, err := getIDByName("zero1") if err != nil { - t.Fatal(err) + c.Fatal(err) } runCmd = exec.Command(dockerBinary, "run", "--name", "zero2", "busybox", "true") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } secondZero, err := getIDByName("zero2") if err != nil { - t.Fatal(err) + c.Fatal(err) } runCmd = exec.Command(dockerBinary, "run", "--name", "nonzero1", "busybox", "false") if out, _, err := runCommandWithOutput(runCmd); err == nil { - t.Fatal("Should fail.", out, err) + c.Fatal("Should fail.", out, err) } firstNonZero, err := getIDByName("nonzero1") if err != nil { - t.Fatal(err) + c.Fatal(err) } runCmd = exec.Command(dockerBinary, "run", "--name", "nonzero2", "busybox", "false") if out, _, err := runCommandWithOutput(runCmd); err == nil { - t.Fatal("Should fail.", out, err) + c.Fatal("Should fail.", out, err) } secondNonZero, err := getIDByName("nonzero2") if err != nil { - t.Fatal(err) + c.Fatal(err) } // filter containers by exited=0 runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=exited=0") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } ids := strings.Split(strings.TrimSpace(out), "\n") if len(ids) != 2 { - t.Fatalf("Should be 2 zero exited containers got %d: %s", len(ids), out) + c.Fatalf("Should be 2 zero exited containers got %d: %s", len(ids), out) } if ids[0] != secondZero { - t.Fatalf("First in list should be %q, got %q", secondZero, ids[0]) + c.Fatalf("First in list should be %q, got %q", secondZero, ids[0]) } if ids[1] != firstZero { - t.Fatalf("Second in list should be %q, got %q", firstZero, ids[1]) + c.Fatalf("Second in list should be %q, got %q", firstZero, ids[1]) } runCmd = exec.Command(dockerBinary, "ps", "-a", "-q", "--no-trunc", "--filter=exited=1") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } ids = strings.Split(strings.TrimSpace(out), "\n") if len(ids) != 2 { - t.Fatalf("Should be 2 zero exited containerst got %d", len(ids)) + c.Fatalf("Should be 2 zero exited containerst got %d", len(ids)) } if ids[0] != secondNonZero { - t.Fatalf("First in list should be %q, got %q", secondNonZero, ids[0]) + c.Fatalf("First in list should be %q, got %q", secondNonZero, ids[0]) } if ids[1] != firstNonZero { - t.Fatalf("Second in list should be %q, got %q", firstNonZero, ids[1]) + c.Fatalf("Second in list should be %q, got %q", firstNonZero, ids[1]) } - logDone("ps - test ps filter exited") } -func TestPsRightTagName(t *testing.T) { +func (s *DockerSuite) TestPsRightTagName(c *check.C) { tag := "asybox:shmatest" - defer deleteAllContainers() defer deleteImages(tag) if out, err := exec.Command(dockerBinary, "tag", "busybox", tag).CombinedOutput(); err != nil { - t.Fatalf("Failed to tag image: %s, out: %q", err, out) + c.Fatalf("Failed to tag image: %s, out: %q", err, out) } var id1 string if out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "top").CombinedOutput(); err != nil { - t.Fatalf("Failed to run container: %s, out: %q", err, out) + c.Fatalf("Failed to run container: %s, out: %q", err, out) } else { id1 = strings.TrimSpace(string(out)) } var id2 string if out, err := exec.Command(dockerBinary, "run", "-d", tag, "top").CombinedOutput(); err != nil { - t.Fatalf("Failed to run container: %s, out: %q", err, out) + c.Fatalf("Failed to run container: %s, out: %q", err, out) } else { id2 = strings.TrimSpace(string(out)) } var imageID string if out, err := exec.Command(dockerBinary, "inspect", "-f", "{{.Id}}", "busybox").CombinedOutput(); err != nil { - t.Fatalf("failed to get the image ID of busybox: %s, %v", out, err) + c.Fatalf("failed to get the image ID of busybox: %s, %v", out, err) } else { imageID = strings.TrimSpace(string(out)) } var id3 string if out, err := exec.Command(dockerBinary, "run", "-d", imageID, "top").CombinedOutput(); err != nil { - t.Fatalf("Failed to run container: %s, out: %q", err, out) + c.Fatalf("Failed to run container: %s, out: %q", err, out) } else { id3 = strings.TrimSpace(string(out)) } out, err := exec.Command(dockerBinary, "ps", "--no-trunc").CombinedOutput() if err != nil { - t.Fatalf("Failed to run 'ps': %s, out: %q", err, out) + c.Fatalf("Failed to run 'ps': %s, out: %q", err, out) } lines := strings.Split(strings.TrimSpace(string(out)), "\n") // skip header lines = lines[1:] if len(lines) != 3 { - t.Fatalf("There should be 3 running container, got %d", len(lines)) + c.Fatalf("There should be 3 running container, got %d", len(lines)) } for _, line := range lines { f := strings.Fields(line) switch f[0] { case id1: if f[1] != "busybox" { - t.Fatalf("Expected %s tag for id %s, got %s", "busybox", id1, f[1]) + c.Fatalf("Expected %s tag for id %s, got %s", "busybox", id1, f[1]) } case id2: if f[1] != tag { - t.Fatalf("Expected %s tag for id %s, got %s", tag, id2, f[1]) + c.Fatalf("Expected %s tag for id %s, got %s", tag, id2, f[1]) } case id3: if f[1] != imageID { - t.Fatalf("Expected %s imageID for id %s, got %s", tag, id3, f[1]) + c.Fatalf("Expected %s imageID for id %s, got %s", tag, id3, f[1]) } default: - t.Fatalf("Unexpected id %s, expected %s and %s and %s", f[0], id1, id2, id3) + c.Fatalf("Unexpected id %s, expected %s and %s and %s", f[0], id1, id2, id3) } } - logDone("ps - right tags for containers") } -func TestPsLinkedWithNoTrunc(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsLinkedWithNoTrunc(c *check.C) { if out, err := exec.Command(dockerBinary, "run", "--name=first", "-d", "busybox", "top").CombinedOutput(); err != nil { - t.Fatalf("Output: %s, err: %s", out, err) + c.Fatalf("Output: %s, err: %s", out, err) } if out, err := exec.Command(dockerBinary, "run", "--name=second", "--link=first:first", "-d", "busybox", "top").CombinedOutput(); err != nil { - t.Fatalf("Output: %s, err: %s", out, err) + c.Fatalf("Output: %s, err: %s", out, err) } out, err := exec.Command(dockerBinary, "ps", "--no-trunc").CombinedOutput() if err != nil { - t.Fatalf("Output: %s, err: %s", out, err) + c.Fatalf("Output: %s, err: %s", out, err) } lines := strings.Split(strings.TrimSpace(string(out)), "\n") // strip header @@ -663,28 +648,26 @@ func TestPsLinkedWithNoTrunc(t *testing.T) { names = append(names, fields[len(fields)-1]) } if !reflect.DeepEqual(expected, names) { - t.Fatalf("Expected array: %v, got: %v", expected, names) + c.Fatalf("Expected array: %v, got: %v", expected, names) } } -func TestPsGroupPortRange(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestPsGroupPortRange(c *check.C) { portRange := "3800-3900" out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "porttest", "-p", portRange+":"+portRange, "busybox", "top")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "ps")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // check that the port range is in the output if !strings.Contains(string(out), portRange) { - t.Fatalf("docker ps output should have had the port range %q: %s", portRange, string(out)) + c.Fatalf("docker ps output should have had the port range %q: %s", portRange, string(out)) } - logDone("ps - port range") } diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index f9fd17852..8cf09a726 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -4,12 +4,13 @@ import ( "fmt" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // See issue docker/docker#8141 -func TestPullImageWithAliases(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPullImageWithAliases(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) defer deleteImages(repoName) @@ -22,40 +23,39 @@ func TestPullImageWithAliases(t *testing.T) { // Tag and push the same image multiple times. for _, repo := range repos { if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repo)); err != nil { - t.Fatalf("Failed to tag image %v: error %v, output %q", repos, err, out) + c.Fatalf("Failed to tag image %v: error %v, output %q", repos, err, out) } defer deleteImages(repo) if out, err := exec.Command(dockerBinary, "push", repo).CombinedOutput(); err != nil { - t.Fatalf("Failed to push image %v: error %v, output %q", repo, err, string(out)) + c.Fatalf("Failed to push image %v: error %v, output %q", repo, err, string(out)) } } // Clear local images store. args := append([]string{"rmi"}, repos...) if out, err := exec.Command(dockerBinary, args...).CombinedOutput(); err != nil { - t.Fatalf("Failed to clean images: error %v, output %q", err, string(out)) + c.Fatalf("Failed to clean images: error %v, output %q", err, string(out)) } // Pull a single tag and verify it doesn't bring down all aliases. pullCmd := exec.Command(dockerBinary, "pull", repos[0]) if out, _, err := runCommandWithOutput(pullCmd); err != nil { - t.Fatalf("Failed to pull %v: error %v, output %q", repoName, err, out) + c.Fatalf("Failed to pull %v: error %v, output %q", repoName, err, out) } if err := exec.Command(dockerBinary, "inspect", repos[0]).Run(); err != nil { - t.Fatalf("Image %v was not pulled down", repos[0]) + c.Fatalf("Image %v was not pulled down", repos[0]) } for _, repo := range repos[1:] { if err := exec.Command(dockerBinary, "inspect", repo).Run(); err == nil { - t.Fatalf("Image %v shouldn't have been pulled down", repo) + c.Fatalf("Image %v shouldn't have been pulled down", repo) } } - logDone("pull - image with aliases") } // pulling library/hello-world should show verified message -func TestPullVerified(t *testing.T) { - t.Skip("Skipping hub dependent test") +func (s *DockerSuite) TestPullVerified(c *check.C) { + c.Skip("Skipping hub dependent test") // Image must be pulled from central repository to get verified message // unless keychain is manually updated to contain the daemon's sign key. @@ -68,49 +68,46 @@ func TestPullVerified(t *testing.T) { pullCmd := exec.Command(dockerBinary, "pull", verifiedName) if out, exitCode, err := runCommandWithOutput(pullCmd); err != nil || !strings.Contains(out, expected) { if err != nil || exitCode != 0 { - t.Skipf("pulling the '%s' image from the registry has failed: %s", verifiedName, err) + c.Skip(fmt.Sprintf("pulling the '%s' image from the registry has failed: %v", verifiedName, err)) } - t.Fatalf("pulling a verified image failed. expected: %s\ngot: %s, %v", expected, out, err) + c.Fatalf("pulling a verified image failed. expected: %s\ngot: %s, %v", expected, out, err) } // pull it again pullCmd = exec.Command(dockerBinary, "pull", verifiedName) if out, exitCode, err := runCommandWithOutput(pullCmd); err != nil || strings.Contains(out, expected) { if err != nil || exitCode != 0 { - t.Skipf("pulling the '%s' image from the registry has failed: %s", verifiedName, err) + c.Skip(fmt.Sprintf("pulling the '%s' image from the registry has failed: %v", verifiedName, err)) } - t.Fatalf("pulling a verified image failed. unexpected verify message\ngot: %s, %v", out, err) + c.Fatalf("pulling a verified image failed. unexpected verify message\ngot: %s, %v", out, err) } - logDone("pull - pull verified") } // pulling an image from the central registry should work -func TestPullImageFromCentralRegistry(t *testing.T) { - testRequires(t, Network) +func (s *DockerSuite) TestPullImageFromCentralRegistry(c *check.C) { + testRequires(c, Network) defer deleteImages("hello-world") pullCmd := exec.Command(dockerBinary, "pull", "hello-world") if out, _, err := runCommandWithOutput(pullCmd); err != nil { - t.Fatalf("pulling the hello-world image from the registry has failed: %s, %v", out, err) + c.Fatalf("pulling the hello-world image from the registry has failed: %s, %v", out, err) } - logDone("pull - pull hello-world") } // pulling a non-existing image from the central registry should return a non-zero exit code -func TestPullNonExistingImage(t *testing.T) { +func (s *DockerSuite) TestPullNonExistingImage(c *check.C) { pullCmd := exec.Command(dockerBinary, "pull", "fooblahblah1234") if out, _, err := runCommandWithOutput(pullCmd); err == nil { - t.Fatalf("expected non-zero exit status when pulling non-existing image: %s", out) + c.Fatalf("expected non-zero exit status when pulling non-existing image: %s", out) } - logDone("pull - pull fooblahblah1234 (non-existing image)") } // pulling an image from the central registry using official names should work // ensure all pulls result in the same image -func TestPullImageOfficialNames(t *testing.T) { - testRequires(t, Network) +func (s *DockerSuite) TestPullImageOfficialNames(c *check.C) { + testRequires(c, Network) names := []string{ "docker.io/hello-world", @@ -123,7 +120,7 @@ func TestPullImageOfficialNames(t *testing.T) { pullCmd := exec.Command(dockerBinary, "pull", name) out, exitCode, err := runCommandWithOutput(pullCmd) if err != nil || exitCode != 0 { - t.Errorf("pulling the '%s' image from the registry has failed: %s", name, err) + c.Errorf("pulling the '%s' image from the registry has failed: %s", name, err) continue } @@ -131,10 +128,9 @@ func TestPullImageOfficialNames(t *testing.T) { imagesCmd := exec.Command(dockerBinary, "images") out, _, err = runCommandWithOutput(imagesCmd) if err != nil { - t.Errorf("listing images failed with errors: %v", err) + c.Errorf("listing images failed with errors: %v", err) } else if strings.Contains(out, name) { - t.Errorf("images should not have listed '%s'", name) + c.Errorf("images should not have listed '%s'", name) } } - logDone("pull - pull official names") } diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index f1274ba70..5c74fe8dc 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -6,72 +6,68 @@ import ( "os" "os/exec" "strings" - "testing" "time" "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" + "github.com/go-check/check" ) // pulling an image from the central registry should work -func TestPushBusyboxImage(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPushBusyboxImage(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) // tag the image to upload it to the private registry tagCmd := exec.Command(dockerBinary, "tag", "busybox", repoName) if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatalf("image tagging failed: %s, %v", out, err) + c.Fatalf("image tagging failed: %s, %v", out, err) } defer deleteImages(repoName) pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err != nil { - t.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) + c.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) } - logDone("push - busybox to private registry") } // pushing an image without a prefix should throw an error -func TestPushUnprefixedRepo(t *testing.T) { +func (s *DockerSuite) TestPushUnprefixedRepo(c *check.C) { pushCmd := exec.Command(dockerBinary, "push", "busybox") if out, _, err := runCommandWithOutput(pushCmd); err == nil { - t.Fatalf("pushing an unprefixed repo didn't result in a non-zero exit status: %s", out) + c.Fatalf("pushing an unprefixed repo didn't result in a non-zero exit status: %s", out) } - logDone("push - unprefixed busybox repo must not pass") } -func TestPushUntagged(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPushUntagged(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) expected := "Repository does not exist" pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err == nil { - t.Fatalf("pushing the image to the private registry should have failed: outuput %q", out) + c.Fatalf("pushing the image to the private registry should have failed: outuput %q", out) } else if !strings.Contains(out, expected) { - t.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out) + c.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out) } - logDone("push - untagged image") } -func TestPushBadTag(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPushBadTag(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox:latest", privateRegistryURL) expected := "does not exist" pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err == nil { - t.Fatalf("pushing the image to the private registry should have failed: outuput %q", out) + c.Fatalf("pushing the image to the private registry should have failed: outuput %q", out) } else if !strings.Contains(out, expected) { - t.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out) + c.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out) } - logDone("push - image with bad tag") } -func TestPushMultipleTags(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPushMultipleTags(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) repoTag1 := fmt.Sprintf("%v/dockercli/busybox:t1", privateRegistryURL) @@ -79,80 +75,79 @@ func TestPushMultipleTags(t *testing.T) { // tag the image to upload it tot he private registry tagCmd1 := exec.Command(dockerBinary, "tag", "busybox", repoTag1) if out, _, err := runCommandWithOutput(tagCmd1); err != nil { - t.Fatalf("image tagging failed: %s, %v", out, err) + c.Fatalf("image tagging failed: %s, %v", out, err) } defer deleteImages(repoTag1) tagCmd2 := exec.Command(dockerBinary, "tag", "busybox", repoTag2) if out, _, err := runCommandWithOutput(tagCmd2); err != nil { - t.Fatalf("image tagging failed: %s, %v", out, err) + c.Fatalf("image tagging failed: %s, %v", out, err) } defer deleteImages(repoTag2) pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err != nil { - t.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) + c.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) } - logDone("push - multiple tags to private registry") } -func TestPushInterrupt(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPushInterrupt(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) // tag the image to upload it tot he private registry tagCmd := exec.Command(dockerBinary, "tag", "busybox", repoName) if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatalf("image tagging failed: %s, %v", out, err) + c.Fatalf("image tagging failed: %s, %v", out, err) } defer deleteImages(repoName) pushCmd := exec.Command(dockerBinary, "push", repoName) if err := pushCmd.Start(); err != nil { - t.Fatalf("Failed to start pushing to private registry: %v", err) + c.Fatalf("Failed to start pushing to private registry: %v", err) } // Interrupt push (yes, we have no idea at what point it will get killed). time.Sleep(200 * time.Millisecond) if err := pushCmd.Process.Kill(); err != nil { - t.Fatalf("Failed to kill push process: %v", err) + c.Fatalf("Failed to kill push process: %v", err) } // Try agin pushCmd = exec.Command(dockerBinary, "push", repoName) - if err := pushCmd.Start(); err != nil { - t.Fatalf("Failed to start pushing to private registry: %v", err) + if out, err := pushCmd.CombinedOutput(); err == nil { + str := string(out) + if !strings.Contains(str, "already in progress") { + c.Fatalf("Push should be continued on daemon side, but seems ok: %v, %s", err, out) + } } - - logDone("push - interrupted") } -func TestPushEmptyLayer(t *testing.T) { - defer setupRegistry(t)() +func (s *DockerSuite) TestPushEmptyLayer(c *check.C) { + defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/emptylayer", privateRegistryURL) emptyTarball, err := ioutil.TempFile("", "empty_tarball") if err != nil { - t.Fatalf("Unable to create test file: %v", err) + c.Fatalf("Unable to create test file: %v", err) } tw := tar.NewWriter(emptyTarball) err = tw.Close() if err != nil { - t.Fatalf("Error creating empty tarball: %v", err) + c.Fatalf("Error creating empty tarball: %v", err) } freader, err := os.Open(emptyTarball.Name()) if err != nil { - t.Fatalf("Could not open test tarball: %v", err) + c.Fatalf("Could not open test tarball: %v", err) } importCmd := exec.Command(dockerBinary, "import", "-", repoName) importCmd.Stdin = freader out, _, err := runCommandWithOutput(importCmd) if err != nil { - t.Errorf("import failed with errors: %v, output: %q", err, out) + c.Errorf("import failed with errors: %v, output: %q", err, out) } // Now verify we can push it pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err != nil { - t.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) + c.Fatalf("pushing the image to the private registry has failed: %s, %v", out, err) } - logDone("push - empty layer config to private registry") } diff --git a/integration-cli/docker_cli_rename_test.go b/integration-cli/docker_cli_rename_test.go index ed24d971d..fcd87b54b 100644 --- a/integration-cli/docker_cli_rename_test.go +++ b/integration-cli/docker_cli_rename_test.go @@ -3,16 +3,16 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestRenameStoppedContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRenameStoppedContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -20,7 +20,7 @@ func TestRenameStoppedContainer(t *testing.T) { runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } name, err := inspectField(cleanedContainerID, "Name") @@ -28,94 +28,87 @@ func TestRenameStoppedContainer(t *testing.T) { runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } name, err = inspectField(cleanedContainerID, "Name") if err != nil { - t.Fatal(err) + c.Fatal(err) } if name != "/new_name" { - t.Fatal("Failed to rename container ", name) + c.Fatal("Failed to rename container ", name) } - logDone("rename - stopped container") } -func TestRenameRunningContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRenameRunningContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } name, err := inspectField(cleanedContainerID, "Name") if err != nil { - t.Fatal(err) + c.Fatal(err) } if name != "/new_name" { - t.Fatal("Failed to rename container ") + c.Fatal("Failed to rename container ") } - logDone("rename - running container") } -func TestRenameCheckNames(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRenameCheckNames(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } name, err := inspectField("new_name", "Name") if err != nil { - t.Fatal(err) + c.Fatal(err) } if name != "/new_name" { - t.Fatal("Failed to rename container ") + c.Fatal("Failed to rename container ") } name, err = inspectField("first_name", "Name") if err == nil && !strings.Contains(err.Error(), "No such image or container: first_name") { - t.Fatal(err) + c.Fatal(err) } - logDone("rename - old name released") } -func TestRenameInvalidName(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRenameInvalidName(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--name", "myname", "-d", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } runCmd = exec.Command(dockerBinary, "rename", "myname", "new:invalid") if out, _, err := runCommandWithOutput(runCmd); err == nil || !strings.Contains(out, "Invalid container name") { - t.Fatalf("Renaming container to invalid name should have failed: %s\n%v", out, err) + c.Fatalf("Renaming container to invalid name should have failed: %s\n%v", out, err) } runCmd = exec.Command(dockerBinary, "ps", "-a") if out, _, err := runCommandWithOutput(runCmd); err != nil || !strings.Contains(out, "myname") { - t.Fatalf("Output of docker ps should have included 'myname': %s\n%v", out, err) + c.Fatalf("Output of docker ps should have included 'myname': %s\n%v", out, err) } - logDone("rename - invalid container name") } diff --git a/integration-cli/docker_cli_restart_test.go b/integration-cli/docker_cli_restart_test.go index dde450dcd..2b9d5e232 100644 --- a/integration-cli/docker_cli_restart_test.go +++ b/integration-cli/docker_cli_restart_test.go @@ -3,61 +3,59 @@ package main import ( "os/exec" "strings" - "testing" "time" + + "github.com/go-check/check" ) -func TestRestartStoppedContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRestartStoppedContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "foobar") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "wait", cleanedContainerID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out != "foobar\n" { - t.Errorf("container should've printed 'foobar'") + c.Errorf("container should've printed 'foobar'") } runCmd = exec.Command(dockerBinary, "restart", cleanedContainerID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out != "foobar\nfoobar\n" { - t.Errorf("container should've printed 'foobar' twice") + c.Errorf("container should've printed 'foobar' twice") } - logDone("restart - echo foobar for stopped container") } -func TestRestartRunningContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRestartRunningContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "echo foobar && sleep 30 && echo 'should not print this'") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -67,41 +65,39 @@ func TestRestartRunningContainer(t *testing.T) { runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out != "foobar\n" { - t.Errorf("container should've printed 'foobar'") + c.Errorf("container should've printed 'foobar'") } runCmd = exec.Command(dockerBinary, "restart", "-t", "1", cleanedContainerID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } time.Sleep(1 * time.Second) if out != "foobar\nfoobar\n" { - t.Errorf("container should've printed 'foobar' twice") + c.Errorf("container should've printed 'foobar' twice") } - logDone("restart - echo foobar for running container") } // 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) { - defer deleteAllContainers() +func (s *DockerSuite) TestRestartWithVolumes(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "-v", "/test", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -109,148 +105,139 @@ func TestRestartWithVolumes(t *testing.T) { runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ len .Volumes }}", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out = strings.Trim(out, " \n\r"); out != "1" { - t.Errorf("expect 1 volume received %s", out) + c.Errorf("expect 1 volume received %s", out) } runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ .Volumes }}", cleanedContainerID) volumes, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(volumes, err) + c.Fatal(volumes, err) } runCmd = exec.Command(dockerBinary, "restart", cleanedContainerID) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ len .Volumes }}", cleanedContainerID) out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out = strings.Trim(out, " \n\r"); out != "1" { - t.Errorf("expect 1 volume after restart received %s", out) + c.Errorf("expect 1 volume after restart received %s", out) } runCmd = exec.Command(dockerBinary, "inspect", "--format", "{{ .Volumes }}", cleanedContainerID) volumesAfterRestart, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(volumesAfterRestart, err) + c.Fatal(volumesAfterRestart, err) } if volumes != volumesAfterRestart { volumes = strings.Trim(volumes, " \n\r") volumesAfterRestart = strings.Trim(volumesAfterRestart, " \n\r") - t.Errorf("expected volume path: %s Actual path: %s", volumes, volumesAfterRestart) + c.Errorf("expected volume path: %s Actual path: %s", volumes, volumesAfterRestart) } - logDone("restart - does not create a new volume on restart") } -func TestRestartPolicyNO(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRestartPolicyNO(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--restart=no", "busybox", "false") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(string(out)) name, err := inspectField(id, "HostConfig.RestartPolicy.Name") if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if name != "no" { - t.Fatalf("Container restart policy name is %s, expected %s", name, "no") + c.Fatalf("Container restart policy name is %s, expected %s", name, "no") } - logDone("restart - recording restart policy name for --restart=no") } -func TestRestartPolicyAlways(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRestartPolicyAlways(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--restart=always", "busybox", "false") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(string(out)) name, err := inspectField(id, "HostConfig.RestartPolicy.Name") if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if name != "always" { - t.Fatalf("Container restart policy name is %s, expected %s", name, "always") + c.Fatalf("Container restart policy name is %s, expected %s", name, "always") } MaximumRetryCount, err := inspectField(id, "HostConfig.RestartPolicy.MaximumRetryCount") if err != nil { - t.Fatal(err) + c.Fatal(err) } // MaximumRetryCount=0 if the restart policy is always if MaximumRetryCount != "0" { - t.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "0") + c.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "0") } - logDone("restart - recording restart policy name for --restart=always") } -func TestRestartPolicyOnFailure(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRestartPolicyOnFailure(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--restart=on-failure:1", "busybox", "false") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(string(out)) name, err := inspectField(id, "HostConfig.RestartPolicy.Name") if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if name != "on-failure" { - t.Fatalf("Container restart policy name is %s, expected %s", name, "on-failure") + c.Fatalf("Container restart policy name is %s, expected %s", name, "on-failure") } - logDone("restart - recording restart policy name for --restart=on-failure") } // a good container with --restart=on-failure:3 // MaximumRetryCount!=0; RestartCount=0 -func TestContainerRestartwithGoodContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestContainerRestartwithGoodContainer(c *check.C) { out, err := exec.Command(dockerBinary, "run", "-d", "--restart=on-failure:3", "busybox", "true").CombinedOutput() if err != nil { - t.Fatal(string(out), err) + c.Fatal(string(out), err) } id := strings.TrimSpace(string(out)) if err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false false", 5); err != nil { - t.Fatal(err) + c.Fatal(err) } count, err := inspectField(id, "RestartCount") if err != nil { - t.Fatal(err) + c.Fatal(err) } if count != "0" { - t.Fatalf("Container was restarted %s times, expected %d", count, 0) + c.Fatalf("Container was restarted %s times, expected %d", count, 0) } MaximumRetryCount, err := inspectField(id, "HostConfig.RestartPolicy.MaximumRetryCount") if err != nil { - t.Fatal(err) + c.Fatal(err) } if MaximumRetryCount != "3" { - t.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "3") + c.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "3") } - logDone("restart - for a good container with restart policy, MaximumRetryCount is not 0 and RestartCount is 0") } diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index 5f9a5dda5..8668bc70b 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -5,93 +5,83 @@ import ( "os" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestRmContainerWithRemovedVolume(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRmContainerWithRemovedVolume(c *check.C) { + testRequires(c, SameHostDaemon) cmd := exec.Command(dockerBinary, "run", "--name", "losemyvolumes", "-v", "/tmp/testing:/test", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := os.Remove("/tmp/testing"); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "rm", "-v", "losemyvolumes") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - logDone("rm - removed volume") } -func TestRmContainerWithVolume(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmContainerWithVolume(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "foo", "-v", "/srv", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "rm", "-v", "foo") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("rm - volume") } -func TestRmRunningContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmRunningContainer(c *check.C) { - createRunningContainer(t, "foo") + createRunningContainer(c, "foo") // Test cannot remove running container cmd := exec.Command(dockerBinary, "rm", "foo") if _, err := runCommand(cmd); err == nil { - t.Fatalf("Expected error, can't rm a running container") + c.Fatalf("Expected error, can't rm a running container") } - logDone("rm - running container") } -func TestRmRunningContainerCheckError409(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmRunningContainerCheckError409(c *check.C) { - createRunningContainer(t, "foo") + createRunningContainer(c, "foo") endpoint := "/containers/foo" status, _, err := sockRequest("DELETE", endpoint, nil) if err == nil { - t.Fatalf("Expected error, can't rm a running container") + c.Fatalf("Expected error, can't rm a running container") } else if status != http.StatusConflict { - t.Fatalf("Expected error to contain '409 Conflict' but found %s", err) + c.Fatalf("Expected error to contain '409 Conflict' but found %s", err) } - logDone("rm - running container with Error 409") } -func TestRmForceRemoveRunningContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmForceRemoveRunningContainer(c *check.C) { - createRunningContainer(t, "foo") + createRunningContainer(c, "foo") // Stop then remove with -s cmd := exec.Command(dockerBinary, "rm", "-f", "foo") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("rm - running container with --force=true") } -func TestRmContainerOrphaning(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmContainerOrphaning(c *check.C) { dockerfile1 := `FROM busybox:latest ENTRYPOINT ["/bin/true"]` @@ -104,45 +94,43 @@ func TestRmContainerOrphaning(t *testing.T) { img1, err := buildImage(img, dockerfile1, true) defer deleteImages(img1) if err != nil { - t.Fatalf("Could not build image %s: %v", img, err) + c.Fatalf("Could not build image %s: %v", img, err) } // run container on first image if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", img)); err != nil { - t.Fatalf("Could not run image %s: %v: %s", img, err, out) + c.Fatalf("Could not run image %s: %v: %s", img, err, out) } // rebuild dockerfile with a small addition at the end if _, err := buildImage(img, dockerfile2, true); err != nil { - t.Fatalf("Could not rebuild image %s: %v", img, err) + c.Fatalf("Could not rebuild image %s: %v", img, err) } // try to remove the image, should error out. if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", img)); err == nil { - t.Fatalf("Expected to error out removing the image, but succeeded: %s", out) + c.Fatalf("Expected to error out removing the image, but succeeded: %s", out) } // check if we deleted the first image out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "images", "-q", "--no-trunc")) if err != nil { - t.Fatalf("%v: %s", err, out) + c.Fatalf("%v: %s", err, out) } if !strings.Contains(out, img1) { - t.Fatalf("Orphaned container (could not find %q in docker images): %s", img1, out) + c.Fatalf("Orphaned container (could not find %q in docker images): %s", img1, out) } - logDone("rm - container orphaning") } -func TestRmInvalidContainer(t *testing.T) { +func (s *DockerSuite) TestRmInvalidContainer(c *check.C) { if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rm", "unknown")); err == nil { - t.Fatal("Expected error on rm unknown container, got none") + c.Fatal("Expected error on rm unknown container, got none") } else if !strings.Contains(out, "failed to remove one or more containers") { - t.Fatalf("Expected output to contain 'failed to remove one or more containers', got %q", out) + c.Fatalf("Expected output to contain 'failed to remove one or more containers', got %q", out) } - logDone("rm - delete unknown container") } -func createRunningContainer(t *testing.T, name string) { +func createRunningContainer(c *check.C, name string) { cmd := exec.Command(dockerBinary, "run", "-dt", "--name", name, "busybox", "top") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } } diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index f6b12aa6c..786a3e306 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -3,17 +3,18 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestRmiWithContainerFails(t *testing.T) { +func (s *DockerSuite) TestRmiWithContainerFails(c *check.C) { errSubstr := "is using it" // create a container runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %s, %v", out, err) + c.Fatalf("failed to create a container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -22,123 +23,117 @@ func TestRmiWithContainerFails(t *testing.T) { runCmd = exec.Command(dockerBinary, "rmi", "busybox") out, _, err = runCommandWithOutput(runCmd) if err == nil { - t.Fatalf("Container %q is using image, should not be able to rmi: %q", cleanedContainerID, out) + c.Fatalf("Container %q is using image, should not be able to rmi: %q", cleanedContainerID, out) } if !strings.Contains(out, errSubstr) { - t.Fatalf("Container %q is using image, error message should contain %q: %v", cleanedContainerID, errSubstr, out) + c.Fatalf("Container %q is using image, error message should contain %q: %v", cleanedContainerID, errSubstr, out) } // make sure it didn't delete the busybox name - images, _ := dockerCmd(t, "images") + images, _ := dockerCmd(c, "images") if !strings.Contains(images, "busybox") { - t.Fatalf("The name 'busybox' should not have been removed from images: %q", images) + c.Fatalf("The name 'busybox' should not have been removed from images: %q", images) } deleteContainer(cleanedContainerID) - logDone("rmi - container using image while rmi, should not remove image name") } -func TestRmiTag(t *testing.T) { - imagesBefore, _ := dockerCmd(t, "images", "-a") - dockerCmd(t, "tag", "busybox", "utest:tag1") - dockerCmd(t, "tag", "busybox", "utest/docker:tag2") - dockerCmd(t, "tag", "busybox", "utest:5000/docker:tag3") +func (s *DockerSuite) TestRmiTag(c *check.C) { + imagesBefore, _ := dockerCmd(c, "images", "-a") + dockerCmd(c, "tag", "busybox", "utest:tag1") + dockerCmd(c, "tag", "busybox", "utest/docker:tag2") + dockerCmd(c, "tag", "busybox", "utest:5000/docker:tag3") { - imagesAfter, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(c, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+3 { - t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) + c.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } } - dockerCmd(t, "rmi", "utest/docker:tag2") + dockerCmd(c, "rmi", "utest/docker:tag2") { - imagesAfter, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(c, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+2 { - t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) + c.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } } - dockerCmd(t, "rmi", "utest:5000/docker:tag3") + dockerCmd(c, "rmi", "utest:5000/docker:tag3") { - imagesAfter, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(c, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+1 { - t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) + c.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } } - dockerCmd(t, "rmi", "utest:tag1") + dockerCmd(c, "rmi", "utest:tag1") { - imagesAfter, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(c, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+0 { - t.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) + c.Fatalf("before: %q\n\nafter: %q\n", imagesBefore, imagesAfter) } } - logDone("rmi - tag,rmi - tagging the same images multiple times then removing tags") } -func TestRmiImgIDForce(t *testing.T) { +func (s *DockerSuite) TestRmiImgIDForce(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir '/busybox-test'") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container:%s, %v", out, err) + c.Fatalf("failed to create a container:%s, %v", out, err) } containerID := strings.TrimSpace(out) runCmd = exec.Command(dockerBinary, "commit", containerID, "busybox-test") out, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to commit a new busybox-test:%s, %v", out, err) + c.Fatalf("failed to commit a new busybox-test:%s, %v", out, err) } - imagesBefore, _ := dockerCmd(t, "images", "-a") - dockerCmd(t, "tag", "busybox-test", "utest:tag1") - dockerCmd(t, "tag", "busybox-test", "utest:tag2") - dockerCmd(t, "tag", "busybox-test", "utest/docker:tag3") - dockerCmd(t, "tag", "busybox-test", "utest:5000/docker:tag4") + imagesBefore, _ := dockerCmd(c, "images", "-a") + dockerCmd(c, "tag", "busybox-test", "utest:tag1") + dockerCmd(c, "tag", "busybox-test", "utest:tag2") + dockerCmd(c, "tag", "busybox-test", "utest/docker:tag3") + dockerCmd(c, "tag", "busybox-test", "utest:5000/docker:tag4") { - imagesAfter, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(c, "images", "-a") if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+4 { - t.Fatalf("tag busybox to create 4 more images with same imageID; docker images shows: %q\n", imagesAfter) + c.Fatalf("tag busybox to create 4 more images with same imageID; docker images shows: %q\n", imagesAfter) } } - out, _ = dockerCmd(t, "inspect", "-f", "{{.Id}}", "busybox-test") + out, _ = dockerCmd(c, "inspect", "-f", "{{.Id}}", "busybox-test") imgID := strings.TrimSpace(out) - dockerCmd(t, "rmi", "-f", imgID) + dockerCmd(c, "rmi", "-f", imgID) { - imagesAfter, _ := dockerCmd(t, "images", "-a") + imagesAfter, _ := dockerCmd(c, "images", "-a") if strings.Contains(imagesAfter, imgID[:12]) { - t.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter) + c.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter) } } - logDone("rmi - imgID,rmi -f imgID delete all tagged repos of specific imgID") } -func TestRmiTagWithExistingContainers(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmiTagWithExistingContainers(c *check.C) { container := "test-delete-tag" newtag := "busybox:newtag" bb := "busybox:latest" if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", bb, newtag)); err != nil { - t.Fatalf("Could not tag busybox: %v: %s", err, out) + c.Fatalf("Could not tag busybox: %v: %s", err, out) } if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", container, bb, "/bin/true")); err != nil { - t.Fatalf("Could not run busybox: %v: %s", err, out) + c.Fatalf("Could not run busybox: %v: %s", err, out) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", newtag)) if err != nil { - t.Fatalf("Could not remove tag %s: %v: %s", newtag, err, out) + c.Fatalf("Could not remove tag %s: %v: %s", newtag, err, out) } if d := strings.Count(out, "Untagged: "); d != 1 { - t.Fatalf("Expected 1 untagged entry got %d: %q", d, out) + c.Fatalf("Expected 1 untagged entry got %d: %q", d, out) } - logDone("rmi - delete tag with existing containers") } -func TestRmiForceWithExistingContainers(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmiForceWithExistingContainers(c *check.C) { image := "busybox-clone" @@ -147,64 +142,60 @@ func TestRmiForceWithExistingContainers(t *testing.T) { MAINTAINER foo`) if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("Could not build %s: %s, %v", image, out, err) + c.Fatalf("Could not build %s: %s, %v", image, out, err) } if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "test-force-rmi", image, "/bin/true")); err != nil { - t.Fatalf("Could not run container: %s, %v", out, err) + c.Fatalf("Could not run container: %s, %v", out, err) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rmi", "-f", image)) if err != nil { - t.Fatalf("Could not remove image %s: %s, %v", image, out, err) + c.Fatalf("Could not remove image %s: %s, %v", image, out, err) } - logDone("rmi - force delete with existing containers") } -func TestRmiWithMultipleRepositories(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRmiWithMultipleRepositories(c *check.C) { newRepo := "127.0.0.1:5000/busybox" oldRepo := "busybox" newTag := "busybox:test" cmd := exec.Command(dockerBinary, "tag", oldRepo, newRepo) out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("Could not tag busybox: %v: %s", err, out) + c.Fatalf("Could not tag busybox: %v: %s", err, out) } cmd = exec.Command(dockerBinary, "run", "--name", "test", oldRepo, "touch", "/home/abcd") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %s", err, out) + c.Fatalf("failed to run container: %v, output: %s", err, out) } cmd = exec.Command(dockerBinary, "commit", "test", newTag) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to commit container: %v, output: %s", err, out) + c.Fatalf("failed to commit container: %v, output: %s", err, out) } cmd = exec.Command(dockerBinary, "rmi", newTag) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to remove image: %v, output: %s", err, out) + c.Fatalf("failed to remove image: %v, output: %s", err, out) } if !strings.Contains(out, "Untagged: "+newTag) { - t.Fatalf("Could not remove image %s: %s, %v", newTag, out, err) + c.Fatalf("Could not remove image %s: %s, %v", newTag, out, err) } - logDone("rmi - delete a image which its dependency tagged to multiple repositories success") } -func TestRmiBlank(t *testing.T) { +func (s *DockerSuite) TestRmiBlank(c *check.C) { // try to delete a blank image name runCmd := exec.Command(dockerBinary, "rmi", "") out, _, err := runCommandWithOutput(runCmd) if err == nil { - t.Fatal("Should have failed to delete '' image") + c.Fatal("Should have failed to delete '' image") } if strings.Contains(out, "No such image") { - t.Fatalf("Wrong error message generated: %s", out) + c.Fatalf("Wrong error message generated: %s", out) } - logDone("rmi - blank image name") } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 98f923fa4..7e12fc5a9 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -16,713 +16,593 @@ import ( "strconv" "strings" "sync" - "testing" "time" "github.com/docker/docker/nat" "github.com/docker/docker/pkg/resolvconf" + "github.com/go-check/check" ) // "test123" should be printed by docker run -func TestRunEchoStdout(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunEchoStdout(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "busybox", "echo", "test123") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if out != "test123\n" { - t.Errorf("container should've printed 'test123'") + c.Fatalf("container should've printed 'test123'") } - - logDone("run - echo test123") } // "test" should be printed -func TestRunEchoStdoutWithMemoryLimit(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunEchoStdoutWithMemoryLimit(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-m", "16m", "busybox", "echo", "test") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.Trim(out, "\r\n") if expected := "test"; out != expected { - t.Errorf("container should've printed %q but printed %q", expected, out) - + c.Fatalf("container should've printed %q but printed %q", expected, out) } - - logDone("run - echo with memory limit") } // should run without memory swap -func TestRunWithoutMemoryswapLimit(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithoutMemoryswapLimit(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-m", "16m", "--memory-swap", "-1", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to run container, output: %q", out) + c.Fatalf("failed to run container, output: %q", out) } - - logDone("run - without memory swap limit") } // "test" should be printed -func TestRunEchoStdoutWitCPULimit(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunEchoStdoutWitCPULimit(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-c", "1000", "busybox", "echo", "test") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if out != "test\n" { - t.Errorf("container should've printed 'test'") + c.Errorf("container should've printed 'test'") } - - logDone("run - echo with CPU limit") } // "test" should be printed -func TestRunEchoStdoutWithCPUAndMemoryLimit(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunEchoStdoutWithCPUAndMemoryLimit(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-c", "1000", "-m", "16m", "busybox", "echo", "test") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if out != "test\n" { - t.Errorf("container should've printed 'test', got %q instead", out) + c.Errorf("container should've printed 'test', got %q instead", out) } - - logDone("run - echo with CPU and memory limit") } // "test" should be printed -func TestRunEchoStdoutWitCPUQuota(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunEchoStdoutWitCPUQuota(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--cpu-quota", "8000", "--name", "test", "busybox", "echo", "test") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.TrimSpace(out) if strings.Contains(out, "Your kernel does not support CPU cfs quota") { - t.Skip("Your kernel does not support CPU cfs quota, skip this test") + c.Skip("Your kernel does not support CPU cfs quota, skip this test") } if out != "test" { - t.Errorf("container should've printed 'test'") + c.Errorf("container should've printed 'test'") } cmd := exec.Command(dockerBinary, "inspect", "-f", "{{.HostConfig.CpuQuota}}", "test") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to inspect container: %s, %v", out, err) + c.Fatalf("failed to inspect container: %s, %v", out, err) } out = strings.TrimSpace(out) if out != "8000" { - t.Errorf("setting the CPU CFS quota failed") + c.Errorf("setting the CPU CFS quota failed") } - - logDone("run - echo with CPU quota") } // "test" should be printed -func TestRunEchoNamedContainer(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunEchoNamedContainer(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--name", "testfoonamedcontainer", "busybox", "echo", "test") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if out != "test\n" { - t.Errorf("container should've printed 'test'") + c.Errorf("container should've printed 'test'") } if err := deleteContainer("testfoonamedcontainer"); err != nil { - t.Errorf("failed to remove the named container: %v", err) + c.Errorf("failed to remove the named container: %v", err) } - - logDone("run - echo with named container") } // docker run should not leak file descriptors -func TestRunLeakyFileDescriptors(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunLeakyFileDescriptors(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "busybox", "ls", "-C", "/proc/self/fd") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } // normally, we should only get 0, 1, and 2, but 3 gets created by "ls" when it does "opendir" on the "fd" directory if out != "0 1 2 3\n" { - t.Errorf("container should've printed '0 1 2 3', not: %s", out) + c.Errorf("container should've printed '0 1 2 3', not: %s", out) } - - logDone("run - check file descriptor leakage") } // it should be possible to lookup Google DNS // this will fail when Internet access is unavailable -func TestRunLookupGoogleDns(t *testing.T) { - testRequires(t, Network) - defer deleteAllContainers() +func (s *DockerSuite) TestRunLookupGoogleDns(c *check.C) { + testRequires(c, Network) out, _, _, err := runCommandWithStdoutStderr(exec.Command(dockerBinary, "run", "busybox", "nslookup", "google.com")) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } - - logDone("run - nslookup google.com") } // the exit code should be 0 // some versions of lxc might make this test fail -func TestRunExitCodeZero(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunExitCodeZero(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "busybox", "true") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Errorf("container should've exited with exit code 0: %s, %v", out, err) + c.Errorf("container should've exited with exit code 0: %s, %v", out, err) } - - logDone("run - exit with 0") } // the exit code should be 1 // some versions of lxc might make this test fail -func TestRunExitCodeOne(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunExitCodeOne(c *check.C) { 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) + c.Fatal(err) } if exitCode != 1 { - t.Errorf("container should've exited with exit code 1") + c.Errorf("container should've exited with exit code 1") } - - 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) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunStdinPipe(c *check.C) { runCmd := exec.Command("bash", "-c", `echo "blahblah" | docker run -i -a stdin busybox cat`) out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", out) if out, _, err := runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("out should've been a container id: %s %v", out, err) + c.Fatalf("out should've been a container id: %s %v", out, err) } waitCmd := exec.Command(dockerBinary, "wait", out) if waitOut, _, err := runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err) + c.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err) } logsCmd := exec.Command(dockerBinary, "logs", out) logsOut, _, err := runCommandWithOutput(logsCmd) if err != nil { - t.Fatalf("error thrown while trying to get container logs: %s, %v", logsOut, err) + c.Fatalf("error thrown while trying to get container logs: %s, %v", logsOut, err) } containerLogs := strings.TrimSpace(logsOut) if containerLogs != "blahblah" { - t.Errorf("logs didn't print the container's logs %s", containerLogs) + c.Errorf("logs didn't print the container's logs %s", containerLogs) } rmCmd := exec.Command(dockerBinary, "rm", out) if out, _, err = runCommandWithOutput(rmCmd); err != nil { - t.Fatalf("rm failed to remove container: %s, %v", out, err) + c.Fatalf("rm failed to remove container: %s, %v", out, err) } - - logDone("run - pipe in with -i -a stdin") } // the container's ID should be printed when starting a container in detached mode -func TestRunDetachedContainerIDPrinting(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunDetachedContainerIDPrinting(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.TrimSpace(out) inspectCmd := exec.Command(dockerBinary, "inspect", out) if inspectOut, _, err := runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("out should've been a container id: %s %v", inspectOut, err) + c.Fatalf("out should've been a container id: %s %v", inspectOut, err) } waitCmd := exec.Command(dockerBinary, "wait", out) if waitOut, _, err := runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err) + c.Fatalf("error thrown while waiting for container: %s, %v", waitOut, err) } rmCmd := exec.Command(dockerBinary, "rm", out) rmOut, _, err := runCommandWithOutput(rmCmd) if err != nil { - t.Fatalf("rm failed to remove container: %s, %v", rmOut, err) + c.Fatalf("rm failed to remove container: %s, %v", rmOut, err) } rmOut = strings.TrimSpace(rmOut) if rmOut != out { - t.Errorf("rm didn't print the container ID %s %s", out, rmOut) + c.Errorf("rm didn't print the container ID %s %s", out, rmOut) } - - logDone("run - print container ID in detached mode") } // the working directory should be set correctly -func TestRunWorkingDirectory(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWorkingDirectory(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-w", "/root", "busybox", "pwd") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.TrimSpace(out) if out != "/root" { - t.Errorf("-w failed to set working directory") + c.Errorf("-w failed to set working directory") } runCmd = exec.Command(dockerBinary, "run", "--workdir", "/root", "busybox", "pwd") out, _, _, err = runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.TrimSpace(out) if out != "/root" { - t.Errorf("--workdir failed to set working directory") + c.Errorf("--workdir failed to set working directory") } - - logDone("run - run with working directory set by -w/--workdir") } // pinging Google's DNS resolver should fail when we disable the networking -func TestRunWithoutNetworking(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithoutNetworking(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--net=none", "busybox", "ping", "-c", "1", "8.8.8.8") out, _, exitCode, err := runCommandWithStdoutStderr(runCmd) if err != nil && exitCode != 1 { - t.Fatal(out, err) + c.Fatal(out, err) } if exitCode != 1 { - t.Errorf("--net=none should've disabled the network; the container shouldn't have been able to ping 8.8.8.8") + c.Errorf("--net=none 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) + c.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") + c.Errorf("-n=false should've disabled the network; the container shouldn't have been able to ping 8.8.8.8") } - - logDone("run - disable networking with --net=none/-n=false") } //test --link use container name to link target -func TestRunLinksContainerWithContainerName(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunLinksContainerWithContainerName(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-i", "-t", "-d", "--name", "parent", "busybox") out, _, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.NetworkSettings.IPAddress}}", "parent") ip, _, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("failed to inspect container: %v, output: %q", err, ip) + c.Fatalf("failed to inspect container: %v, output: %q", err, ip) } ip = strings.TrimSpace(ip) cmd = exec.Command(dockerBinary, "run", "--link", "parent:test", "busybox", "/bin/cat", "/etc/hosts") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if !strings.Contains(out, ip+" test") { - t.Fatalf("use a container name to link target failed") + c.Fatalf("use a container name to link target failed") } - - logDone("run - use a container name to link target work") } //test --link use container id to link target -func TestRunLinksContainerWithContainerId(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunLinksContainerWithContainerId(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-i", "-t", "-d", "busybox") cID, _, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, cID) + c.Fatalf("failed to run container: %v, output: %q", err, cID) } cID = strings.TrimSpace(cID) cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.NetworkSettings.IPAddress}}", cID) ip, _, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatalf("faild to inspect container: %v, output: %q", err, ip) + c.Fatalf("faild to inspect container: %v, output: %q", err, ip) } ip = strings.TrimSpace(ip) cmd = exec.Command(dockerBinary, "run", "--link", cID+":test", "busybox", "/bin/cat", "/etc/hosts") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if !strings.Contains(out, ip+" test") { - t.Fatalf("use a container id to link target failed") + c.Fatalf("use a container id to link target failed") } - - logDone("run - use a container id to link target work") } -func TestRunLinkToContainerNetMode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunLinkToContainerNetMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "test", "-d", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "run", "--name", "parent", "-d", "--net=container:test", "busybox", "top") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "run", "-d", "--link=parent:parent", "busybox", "top") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "run", "--name", "child", "-d", "--net=container:parent", "busybox", "top") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "run", "-d", "--link=child:child", "busybox", "top") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } - - logDone("run - link to a container which net mode is container success") } -func TestRunModeNetContainerHostname(t *testing.T) { - testRequires(t, ExecSupport) - defer deleteAllContainers() +func (s *DockerSuite) TestRunModeNetContainerHostname(c *check.C) { + testRequires(c, ExecSupport) cmd := exec.Command(dockerBinary, "run", "-i", "-d", "--name", "parent", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "exec", "parent", "cat", "/etc/hostname") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to exec command: %v, output: %q", err, out) + c.Fatalf("failed to exec command: %v, output: %q", err, out) } cmd = exec.Command(dockerBinary, "run", "--net=container:parent", "busybox", "cat", "/etc/hostname") out1, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out1) + c.Fatalf("failed to run container: %v, output: %q", err, out1) } if out1 != out { - t.Fatal("containers with shared net namespace should have same hostname") + c.Fatal("containers with shared net namespace should have same hostname") } - - logDone("run - containers with shared net namespace have same hostname") } // Regression test for #4741 -func TestRunWithVolumesAsFiles(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithVolumesAsFiles(c *check.C) { 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) + c.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) + c.Fatal("2", out, stderr, err) } - - logDone("run - regression test for #4741 - volumes from as files") } // Regression test for #4979 -func TestRunWithVolumesFromExited(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithVolumesFromExited(c *check.C) { 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) + c.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) + c.Fatal("2", out, stderr, err) } - - logDone("run - regression test for #4979 - volumes-from on exited container") } // Volume path is a symlink which also exists on the host, and the host side is a file not a dir // But the volume call is just a normal volume, not a bind mount -func TestRunCreateVolumesInSymlinkDir(t *testing.T) { - testRequires(t, SameHostDaemon) - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunCreateVolumesInSymlinkDir(c *check.C) { + testRequires(c, SameHostDaemon) + testRequires(c, NativeExecDriver) name := "test-volume-symlink" dir, err := ioutil.TempDir("", name) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(dir) f, err := os.OpenFile(filepath.Join(dir, "test"), os.O_CREATE, 0700) if err != nil { - t.Fatal(err) + c.Fatal(err) } f.Close() dockerFile := fmt.Sprintf("FROM busybox\nRUN mkdir -p %s\nRUN ln -s %s /test", dir, dir) if _, err := buildImage(name, dockerFile, false); err != nil { - t.Fatal(err) + c.Fatal(err) } defer deleteImages(name) - dockerCmd(t, "run", "-v", "/test/test", name) - - logDone("run - create volume in symlink directory") + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-v", "/test/test", name)) + if err != nil { + c.Fatalf("Failed with errors: %s, %v", out, err) + } } // Regression test for #4830 -func TestRunWithRelativePath(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithRelativePath(c *check.C) { 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") + c.Fatalf("relative path should result in an error") } - - logDone("run - volume with relative path") } -func TestRunVolumesMountedAsReadonly(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunVolumesMountedAsReadonly(c *check.C) { 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) + c.Fatalf("run should fail because volume is ro: exit code %d", code) } - - logDone("run - volumes as readonly mount") } -func TestRunVolumesFromInReadonlyMode(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunVolumesFromInReadonlyMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.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) + c.Fatalf("run should fail because volume is ro: exit code %d", code) } - - logDone("run - volumes from as readonly mount") } // Regression test for #1201 -func TestRunVolumesFromInReadWriteMode(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunVolumesFromInReadWriteMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:rw", "busybox", "touch", "/test/file") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("running --volumes-from parent:rw failed with output: %q\nerror: %v", out, err) + c.Fatalf("running --volumes-from parent:rw failed with output: %q\nerror: %v", out, err) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:bar", "busybox", "touch", "/test/file") if out, _, err := runCommandWithOutput(cmd); err == nil || !strings.Contains(out, "invalid mode for volumes-from: bar") { - t.Fatalf("running --volumes-from foo:bar should have failed with invalid mount mode: %q", out) + c.Fatalf("running --volumes-from foo:bar should have failed with invalid mount mode: %q", out) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent", "busybox", "touch", "/test/file") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatalf("running --volumes-from parent failed with output: %q\nerror: %v", out, err) + c.Fatalf("running --volumes-from parent failed with output: %q\nerror: %v", out, err) } - - logDone("run - volumes from as read write mount") } -func TestVolumesFromGetsProperMode(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestVolumesFromGetsProperMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test:/test:ro", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } // Expect this "rw" mode to be be ignored since the inherited volume is "ro" cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent:rw", "busybox", "touch", "/test/file") if _, err := runCommand(cmd); err == nil { - t.Fatal("Expected volumes-from to inherit read-only volume even when passing in `rw`") + c.Fatal("Expected volumes-from to inherit read-only volume even when passing in `rw`") } cmd = exec.Command(dockerBinary, "run", "--name", "parent2", "-v", "/test:/test:ro", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } // Expect this to be read-only since both are "ro" cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent2:ro", "busybox", "touch", "/test/file") if _, err := runCommand(cmd); err == nil { - t.Fatal("Expected volumes-from to inherit read-only volume even when passing in `ro`") + c.Fatal("Expected volumes-from to inherit read-only volume even when passing in `ro`") } - - logDone("run - volumes from ignores `rw` if inherrited volume is `ro`") } // Test for GH#10618 -func TestRunNoDupVolumes(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunNoDupVolumes(c *check.C) { mountstr1 := randomUnixTmpDirPath("test1") + ":/someplace" mountstr2 := randomUnixTmpDirPath("test2") + ":/someplace" cmd := exec.Command(dockerBinary, "run", "-v", mountstr1, "-v", mountstr2, "busybox", "true") if out, _, err := runCommandWithOutput(cmd); err == nil { - t.Fatal("Expected error about duplicate volume definitions") + c.Fatal("Expected error about duplicate volume definitions") } else { if !strings.Contains(out, "Duplicate volume") { - t.Fatalf("Expected 'duplicate volume' error, got %v", err) + c.Fatalf("Expected 'duplicate volume' error, got %v", err) } } - - logDone("run - don't allow multiple (bind) volumes on the same container target") } // Test for #1351 -func TestRunApplyVolumesFromBeforeVolumes(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunApplyVolumesFromBeforeVolumes(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "parent", "-v", "/test", "busybox", "touch", "/test/foo") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "parent", "-v", "/test", "busybox", "cat", "/test/foo") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - - logDone("run - volumes from mounted first") } -func TestRunMultipleVolumesFrom(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunMultipleVolumesFrom(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--name", "parent1", "-v", "/test", "busybox", "touch", "/test/foo") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", "--name", "parent2", "-v", "/other", "busybox", "touch", "/other/bar") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.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) + c.Fatal(err) } - - logDone("run - multiple volumes from") } // this tests verifies the ID format for the container -func TestRunVerifyContainerID(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunVerifyContainerID(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, exit, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } if exit != 0 { - t.Fatalf("expected exit code 0 received %d", exit) + c.Fatalf("expected exit code 0 received %d", exit) } match, err := regexp.MatchString("^[0-9a-f]{64}$", strings.TrimSuffix(out, "\n")) if err != nil { - t.Fatal(err) + c.Fatal(err) } if !match { - t.Fatalf("Invalid container ID: %s", out) + c.Fatalf("Invalid container ID: %s", out) } - - logDone("run - verify container ID") } // Test that creating a container with a volume doesn't crash. Regression test for #995. -func TestRunCreateVolume(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCreateVolume(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-v", "/var/lib/data", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - - logDone("run - create docker managed volume") } // Test that creating a volume with a symlink in its path works correctly. Test for #5152. // Note that this bug happens only with symlinks with a target that starts with '/'. -func TestRunCreateVolumeWithSymlink(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCreateVolumeWithSymlink(c *check.C) { image := "docker-test-createvolumewithsymlink" defer deleteImages(image) @@ -732,41 +612,37 @@ func TestRunCreateVolumeWithSymlink(t *testing.T) { buildCmd.Dir = workingDirectory err := buildCmd.Run() if err != nil { - t.Fatalf("could not build '%s': %v", image, err) + c.Fatalf("could not build '%s': %v", image, err) } cmd := exec.Command(dockerBinary, "run", "-v", "/bar/foo", "--name", "test-createvolumewithsymlink", image, "sh", "-c", "mount | grep -q /home/foo") exitCode, err := runCommand(cmd) if err != nil || exitCode != 0 { - t.Fatalf("[run] err: %v, exitcode: %d", err, exitCode) + c.Fatalf("[run] err: %v, exitcode: %d", err, exitCode) } var volPath string cmd = exec.Command(dockerBinary, "inspect", "-f", "{{range .Volumes}}{{.}}{{end}}", "test-createvolumewithsymlink") volPath, exitCode, err = runCommandWithOutput(cmd) if err != nil || exitCode != 0 { - t.Fatalf("[inspect] err: %v, exitcode: %d", err, exitCode) + c.Fatalf("[inspect] err: %v, exitcode: %d", err, exitCode) } cmd = exec.Command(dockerBinary, "rm", "-v", "test-createvolumewithsymlink") exitCode, err = runCommand(cmd) if err != nil || exitCode != 0 { - t.Fatalf("[rm] err: %v, exitcode: %d", err, exitCode) + c.Fatalf("[rm] err: %v, exitcode: %d", err, exitCode) } f, err := os.Open(volPath) defer f.Close() if !os.IsNotExist(err) { - t.Fatalf("[open] (expecting 'file does not exist' error) err: %v, volPath: %s", err, volPath) + c.Fatalf("[open] (expecting 'file does not exist' error) err: %v, volPath: %s", err, volPath) } - - logDone("run - create volume with symlink") } // Tests that a volume path that has a symlink exists in a container mounting it with `--volumes-from`. -func TestRunVolumesFromSymlinkPath(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunVolumesFromSymlinkPath(c *check.C) { name := "docker-test-volumesfromsymlinkpath" defer deleteImages(name) @@ -777,145 +653,109 @@ func TestRunVolumesFromSymlinkPath(t *testing.T) { buildCmd.Dir = workingDirectory err := buildCmd.Run() if err != nil { - t.Fatalf("could not build 'docker-test-volumesfromsymlinkpath': %v", err) + c.Fatalf("could not build 'docker-test-volumesfromsymlinkpath': %v", err) } cmd := exec.Command(dockerBinary, "run", "--name", "test-volumesfromsymlinkpath", name) exitCode, err := runCommand(cmd) if err != nil || exitCode != 0 { - t.Fatalf("[run] (volume) err: %v, exitcode: %d", err, exitCode) + c.Fatalf("[run] (volume) err: %v, exitcode: %d", err, exitCode) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "test-volumesfromsymlinkpath", "busybox", "sh", "-c", "ls /foo | grep -q bar") exitCode, err = runCommand(cmd) if err != nil || exitCode != 0 { - t.Fatalf("[run] err: %v, exitcode: %d", err, exitCode) + c.Fatalf("[run] err: %v, exitcode: %d", err, exitCode) } - - logDone("run - volumes-from symlink path") } -func TestRunExitCode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunExitCode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "/bin/sh", "-c", "exit 72") exit, err := runCommand(cmd) if err == nil { - t.Fatal("should not have a non nil error") + c.Fatal("should not have a non nil error") } if exit != 72 { - t.Fatalf("expected exit code 72 received %d", exit) + c.Fatalf("expected exit code 72 received %d", exit) } - - logDone("run - correct exit code") } -func TestRunUserDefaultsToRoot(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserDefaultsToRoot(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "id") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "uid=0(root) gid=0(root)") { - t.Fatalf("expected root user got %s", out) + c.Fatalf("expected root user got %s", out) } - - logDone("run - default user") } -func TestRunUserByName(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserByName(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-u", "root", "busybox", "id") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "uid=0(root) gid=0(root)") { - t.Fatalf("expected root user got %s", out) + c.Fatalf("expected root user got %s", out) } - - logDone("run - user by name") } -func TestRunUserByID(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserByID(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-u", "1", "busybox", "id") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") { - t.Fatalf("expected daemon user got %s", out) + c.Fatalf("expected daemon user got %s", out) } - - logDone("run - user by id") } -func TestRunUserByIDBig(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserByIDBig(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-u", "2147483648", "busybox", "id") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal("No error, but must be.", out) + c.Fatal("No error, but must be.", out) } if !strings.Contains(out, "Uids and gids must be in range") { - t.Fatalf("expected error about uids range, got %s", out) + c.Fatalf("expected error about uids range, got %s", out) } - - logDone("run - user by id, id too big") } -func TestRunUserByIDNegative(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserByIDNegative(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-u", "-1", "busybox", "id") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal("No error, but must be.", out) + c.Fatal("No error, but must be.", out) } if !strings.Contains(out, "Uids and gids must be in range") { - t.Fatalf("expected error about uids range, got %s", out) + c.Fatalf("expected error about uids range, got %s", out) } - - logDone("run - user by id, id negative") } -func TestRunUserByIDZero(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserByIDZero(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-u", "0", "busybox", "id") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "uid=0(root) gid=0(root) groups=10(wheel)") { - t.Fatalf("expected daemon user got %s", out) + c.Fatalf("expected daemon user got %s", out) } - - logDone("run - user by id, zero uid") } -func TestRunUserNotFound(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUserNotFound(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-u", "notme", "busybox", "id") _, err := runCommand(cmd) if err == nil { - t.Fatal("unknown user should cause container to fail") + c.Fatal("unknown user should cause container to fail") } - - logDone("run - user not found") } -func TestRunTwoConcurrentContainers(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunTwoConcurrentContainers(c *check.C) { group := sync.WaitGroup{} group.Add(2) @@ -924,19 +764,15 @@ func TestRunTwoConcurrentContainers(t *testing.T) { defer group.Done() cmd := exec.Command(dockerBinary, "run", "busybox", "sleep", "2") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } }() } group.Wait() - - logDone("run - two concurrent containers") } -func TestRunEnvironment(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunEnvironment(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-h", "testing", "-e=FALSE=true", "-e=TRUE", "-e=TRICKY", "-e=HOME=", "busybox", "env") cmd.Env = append(os.Environ(), "TRUE=false", @@ -945,7 +781,7 @@ func TestRunEnvironment(t *testing.T) { out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } actualEnvLxc := strings.Split(strings.TrimSpace(out), "\n") @@ -969,29 +805,26 @@ func TestRunEnvironment(t *testing.T) { } sort.Strings(goodEnv) if len(goodEnv) != len(actualEnv) { - t.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", ")) + c.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", ")) } for i := range goodEnv { if actualEnv[i] != goodEnv[i] { - t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) + c.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) } } - - logDone("run - verify environment") } -func TestRunEnvironmentErase(t *testing.T) { +func (s *DockerSuite) TestRunEnvironmentErase(c *check.C) { // Test to make sure that when we use -e on env vars that are // not set in our local env that they're removed (if present) in // the container - defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-e", "FOO", "-e", "HOSTNAME", "busybox", "env") cmd.Env = appendBaseEnv([]string{}) out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } actualEnvLxc := strings.Split(strings.TrimSpace(out), "\n") @@ -1009,28 +842,25 @@ func TestRunEnvironmentErase(t *testing.T) { } sort.Strings(goodEnv) if len(goodEnv) != len(actualEnv) { - t.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", ")) + c.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", ")) } for i := range goodEnv { if actualEnv[i] != goodEnv[i] { - t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) + c.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) } } - - logDone("run - verify environment erase") } -func TestRunEnvironmentOverride(t *testing.T) { +func (s *DockerSuite) TestRunEnvironmentOverride(c *check.C) { // Test to make sure that when we use -e on env vars that are // already in the env that we're overriding them - defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-e", "HOSTNAME", "-e", "HOME=/root2", "busybox", "env") cmd.Env = appendBaseEnv([]string{"HOSTNAME=bar"}) out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } actualEnvLxc := strings.Split(strings.TrimSpace(out), "\n") @@ -1049,60 +879,47 @@ func TestRunEnvironmentOverride(t *testing.T) { } sort.Strings(goodEnv) if len(goodEnv) != len(actualEnv) { - t.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", ")) + c.Fatalf("Wrong environment: should be %d variables, not: %q\n", len(goodEnv), strings.Join(actualEnv, ", ")) } for i := range goodEnv { if actualEnv[i] != goodEnv[i] { - t.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) + c.Fatalf("Wrong environment variable: should be %s, not %s", goodEnv[i], actualEnv[i]) } } - - logDone("run - verify environment override") } -func TestRunContainerNetwork(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunContainerNetwork(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "ping", "-c", "1", "127.0.0.1") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - - logDone("run - test container network via ping") } // Issue #4681 -func TestRunLoopbackWhenNetworkDisabled(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunLoopbackWhenNetworkDisabled(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--net=none", "busybox", "ping", "-c", "1", "127.0.0.1") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - - logDone("run - test container loopback when networking disabled") } -func TestRunNetHostNotAllowedWithLinks(t *testing.T) { - defer deleteAllContainers() - - _, _ = dockerCmd(t, "run", "--name", "linked", "busybox", "true") +func (s *DockerSuite) TestRunNetHostNotAllowedWithLinks(c *check.C) { + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", "linked", "busybox", "true")) + if err != nil { + c.Fatalf("Failed with errors: %s, %v", out, err) + } cmd := exec.Command(dockerBinary, "run", "--net=host", "--link", "linked:linked", "busybox", "true") - _, _, err := runCommandWithOutput(cmd) + _, _, err = runCommandWithOutput(cmd) if err == nil { - t.Fatal("Expected error") + c.Fatal("Expected error") } - - logDone("run - don't allow --net=host to be used with links") } -func TestRunLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunLoopbackOnlyExistsWhenNetworkingDisabled(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--net=none", "busybox", "ip", "-o", "-4", "a", "show", "up") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } var ( @@ -1117,14 +934,12 @@ func TestRunLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { } if count != 1 { - t.Fatalf("Wrong interface count in container %d", count) + c.Fatalf("Wrong interface count in container %d", count) } if !strings.HasPrefix(out, "1: lo") { - t.Fatalf("Wrong interface in test container: expected [1: lo], got %s", out) + c.Fatalf("Wrong interface in test container: expected [1: lo], got %s", out) } - - logDone("run - test loopback only exists when networking disabled") } // #7851 hostname outside container shows FQDN, inside only shortname @@ -1132,304 +947,220 @@ func TestRunLoopbackOnlyExistsWhenNetworkingDisabled(t *testing.T) { // and use "--net=host" (as the original issue submitter did), as the same // codepath is executed with "docker run -h ". Both were manually // tested, but this testcase takes the simpler path of using "run -h .." -func TestRunFullHostnameSet(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunFullHostnameSet(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-h", "foo.bar.baz", "busybox", "hostname") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "foo.bar.baz" { - t.Fatalf("expected hostname 'foo.bar.baz', received %s", actual) + c.Fatalf("expected hostname 'foo.bar.baz', received %s", actual) } - - logDone("run - test fully qualified hostname set with -h") } -func TestRunPrivilegedCanMknod(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunPrivilegedCanMknod(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) + c.Fatalf("expected output ok received %s", actual) } - - logDone("run - test privileged can mknod") } -func TestRunUnPrivilegedCanMknod(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUnPrivilegedCanMknod(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) + c.Fatalf("expected output ok received %s", actual) } - - logDone("run - test un-privileged can mknod") } -func TestRunCapDropInvalid(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunCapDropInvalid(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-drop=CHPASS", "busybox", "ls") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } - - logDone("run - test --cap-drop=CHPASS invalid") } -func TestRunCapDropCannotMknod(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapDropCannotMknod(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-drop=MKNOD", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) + c.Fatalf("expected output not ok received %s", actual) } - - logDone("run - test --cap-drop=MKNOD cannot mknod") } -func TestRunCapDropCannotMknodLowerCase(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapDropCannotMknodLowerCase(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-drop=mknod", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) + c.Fatalf("expected output not ok received %s", actual) } - - logDone("run - test --cap-drop=mknod cannot mknod lowercase") } -func TestRunCapDropALLCannotMknod(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapDropALLCannotMknod(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-drop=ALL", "--cap-add=SETGID", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) + c.Fatalf("expected output not ok received %s", actual) } - - logDone("run - test --cap-drop=ALL cannot mknod") } -func TestRunCapDropALLAddMknodCanMknod(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapDropALLAddMknodCanMknod(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-drop=ALL", "--cap-add=MKNOD", "--cap-add=SETGID", "busybox", "sh", "-c", "mknod /tmp/sda b 8 0 && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) + c.Fatalf("expected output ok received %s", actual) } - - logDone("run - test --cap-drop=ALL --cap-add=MKNOD can mknod") } -func TestRunCapAddInvalid(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapAddInvalid(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-add=CHPASS", "busybox", "ls") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } - - logDone("run - test --cap-add=CHPASS invalid") } -func TestRunCapAddCanDownInterface(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapAddCanDownInterface(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-add=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) + c.Fatalf("expected output ok received %s", actual) } - - logDone("run - test --cap-add=NET_ADMIN can set eth0 down") } -func TestRunCapAddALLCanDownInterface(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapAddALLCanDownInterface(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-add=ALL", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) + c.Fatalf("expected output ok received %s", actual) } - - logDone("run - test --cap-add=ALL can set eth0 down") } -func TestRunCapAddALLDropNetAdminCanDownInterface(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCapAddALLDropNetAdminCanDownInterface(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cap-add=ALL", "--cap-drop=NET_ADMIN", "busybox", "sh", "-c", "ip link set eth0 down && echo ok") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) + c.Fatalf("expected output not ok received %s", actual) } - - logDone("run - test --cap-add=ALL --cap-drop=NET_ADMIN cannot set eth0 down") } -func TestRunPrivilegedCanMount(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunPrivilegedCanMount(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } if actual := strings.Trim(out, "\r\n"); actual != "ok" { - t.Fatalf("expected output ok received %s", actual) + c.Fatalf("expected output ok received %s", actual) } - - logDone("run - test privileged can mount") } -func TestRunUnPrivilegedCannotMount(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUnPrivilegedCannotMount(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "mount -t tmpfs none /tmp && echo ok") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual == "ok" { - t.Fatalf("expected output not ok received %s", actual) + c.Fatalf("expected output not ok received %s", actual) } - - logDone("run - test un-privileged cannot mount") } -func TestRunSysNotWritableInNonPrivilegedContainers(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunSysNotWritableInNonPrivilegedContainers(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "touch", "/sys/kernel/profiling") if code, err := runCommand(cmd); err == nil || code == 0 { - t.Fatal("sys should not be writable in a non privileged container") + c.Fatal("sys should not be writable in a non privileged container") } - - logDone("run - sys not writable in non privileged container") } -func TestRunSysWritableInPrivilegedContainers(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunSysWritableInPrivilegedContainers(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "touch", "/sys/kernel/profiling") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("sys should be writable in privileged container") + c.Fatalf("sys should be writable in privileged container") } - - logDone("run - sys writable in privileged container") } -func TestRunProcNotWritableInNonPrivilegedContainers(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunProcNotWritableInNonPrivilegedContainers(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "touch", "/proc/sysrq-trigger") if code, err := runCommand(cmd); err == nil || code == 0 { - t.Fatal("proc should not be writable in a non privileged container") + c.Fatal("proc should not be writable in a non privileged container") } - - logDone("run - proc not writable in non privileged container") } -func TestRunProcWritableInPrivilegedContainers(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunProcWritableInPrivilegedContainers(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--privileged", "busybox", "touch", "/proc/sysrq-trigger") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("proc should be writable in privileged container") + c.Fatalf("proc should be writable in privileged container") } - logDone("run - proc writable in privileged container") } -func TestRunWithCpuset(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithCpuset(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cpuset", "0", "busybox", "true") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("container should run successfully with cpuset of 0: %s", err) + c.Fatalf("container should run successfully with cpuset of 0: %s", err) } - - logDone("run - cpuset 0") } -func TestRunWithCpusetCpus(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithCpusetCpus(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cpuset-cpus", "0", "busybox", "true") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("container should run successfully with cpuset-cpus of 0: %s", err) + c.Fatalf("container should run successfully with cpuset-cpus of 0: %s", err) } - - logDone("run - cpuset-cpus 0") } -func TestRunWithCpusetMems(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithCpusetMems(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--cpuset-mems", "0", "busybox", "true") if code, err := runCommand(cmd); err != nil || code != 0 { - t.Fatalf("container should run successfully with cpuset-mems of 0: %s", err) + c.Fatalf("container should run successfully with cpuset-mems of 0: %s", err) } - - logDone("run - cpuset-mems 0") } -func TestRunDeviceNumbers(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunDeviceNumbers(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "ls -l /dev/null") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } deviceLineFields := strings.Fields(out) deviceLineFields[6] = "" @@ -1438,131 +1169,107 @@ func TestRunDeviceNumbers(t *testing.T) { expected := []string{"crw-rw-rw-", "1", "root", "root", "1,", "3", "", "", "", "/dev/null"} if !(reflect.DeepEqual(deviceLineFields, expected)) { - t.Fatalf("expected output\ncrw-rw-rw- 1 root root 1, 3 May 24 13:29 /dev/null\n received\n %s\n", out) + c.Fatalf("expected output\ncrw-rw-rw- 1 root root 1, 3 May 24 13:29 /dev/null\n received\n %s\n", out) } - - logDone("run - test device numbers") } -func TestRunThatCharacterDevicesActLikeCharacterDevices(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunThatCharacterDevicesActLikeCharacterDevices(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "dd if=/dev/zero of=/zero bs=1k count=5 2> /dev/null ; du -h /zero") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual[0] == '0' { - t.Fatalf("expected a new file called /zero to be create that is greater than 0 bytes long, but du says: %s", actual) + c.Fatalf("expected a new file called /zero to be create that is greater than 0 bytes long, but du says: %s", actual) } - - logDone("run - test that character devices work.") } -func TestRunUnprivilegedWithChroot(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunUnprivilegedWithChroot(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "chroot", "/", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } - - logDone("run - unprivileged with chroot") } -func TestRunAddingOptionalDevices(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAddingOptionalDevices(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--device", "/dev/zero:/dev/nulo", "busybox", "sh", "-c", "ls /dev/nulo") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "/dev/nulo" { - t.Fatalf("expected output /dev/nulo, received %s", actual) + c.Fatalf("expected output /dev/nulo, received %s", actual) } - - logDone("run - test --device argument") } -func TestRunModeHostname(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunModeHostname(c *check.C) { + testRequires(c, SameHostDaemon) cmd := exec.Command(dockerBinary, "run", "-h=testhostname", "busybox", "cat", "/etc/hostname") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); actual != "testhostname" { - t.Fatalf("expected 'testhostname', but says: %q", actual) + c.Fatalf("expected 'testhostname', but says: %q", actual) } cmd = exec.Command(dockerBinary, "run", "--net=host", "busybox", "cat", "/etc/hostname") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } hostname, err := os.Hostname() if err != nil { - t.Fatal(err) + c.Fatal(err) } if actual := strings.Trim(out, "\r\n"); actual != hostname { - t.Fatalf("expected %q, but says: %q", hostname, actual) + c.Fatalf("expected %q, but says: %q", hostname, actual) } - - logDone("run - hostname and several network modes") } -func TestRunRootWorkdir(t *testing.T) { - defer deleteAllContainers() - - s, _ := dockerCmd(t, "run", "--workdir", "/", "busybox", "pwd") - if s != "/\n" { - t.Fatalf("pwd returned %q (expected /\\n)", s) +func (s *DockerSuite) TestRunRootWorkdir(c *check.C) { + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--workdir", "/", "busybox", "pwd")) + if err != nil { + c.Fatalf("Failed with errors: %s, %v", out, err) + } + if out != "/\n" { + c.Fatalf("pwd returned %q (expected /\\n)", s) } - - logDone("run - workdir /") } -func TestRunAllowBindMountingRoot(t *testing.T) { - defer deleteAllContainers() - - _, _ = dockerCmd(t, "run", "-v", "/:/host", "busybox", "ls", "/host") - - logDone("run - bind mount / as volume") +func (s *DockerSuite) TestRunAllowBindMountingRoot(c *check.C) { + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-v", "/:/host", "busybox", "ls", "/host")) + if err != nil { + c.Fatalf("Failed with errors: %s, %v", out, err) + } } -func TestRunDisallowBindMountingRootToRoot(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunDisallowBindMountingRootToRoot(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-v", "/:/", "busybox", "ls", "/host") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal(out, err) + c.Fatal(out, err) } - - logDone("run - bind mount /:/ as volume should not work") } // Verify that a container gets default DNS when only localhost resolvers exist -func TestRunDnsDefaultOptions(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunDnsDefaultOptions(c *check.C) { + testRequires(c, SameHostDaemon) // preserve original resolv.conf for restoring after test origResolvConf, err := ioutil.ReadFile("/etc/resolv.conf") if os.IsNotExist(err) { - t.Fatalf("/etc/resolv.conf does not exist") + c.Fatalf("/etc/resolv.conf does not exist") } // defer restored original conf defer func() { if err := ioutil.WriteFile("/etc/resolv.conf", origResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } }() @@ -1571,14 +1278,14 @@ func TestRunDnsDefaultOptions(t *testing.T) { // GetNameservers(), leading to a replacement of nameservers with the default set tmpResolvConf := []byte("nameserver 127.0.0.1\n#nameserver 127.0.2.1\nnameserver ::1") if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "busybox", "cat", "/etc/resolv.conf") actual, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, actual) + c.Fatal(err, actual) } // check that the actual defaults are appended to the commented out @@ -1586,54 +1293,47 @@ func TestRunDnsDefaultOptions(t *testing.T) { // NOTE: if we ever change the defaults from google dns, this will break expected := "#nameserver 127.0.2.1\n\nnameserver 8.8.8.8\nnameserver 8.8.4.4" if actual != expected { - t.Fatalf("expected resolv.conf be: %q, but was: %q", expected, actual) + c.Fatalf("expected resolv.conf be: %q, but was: %q", expected, actual) } - - logDone("run - dns default options") } -func TestRunDnsOptions(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunDnsOptions(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "--dns-search=mydomain", "busybox", "cat", "/etc/resolv.conf") out, stderr, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } // The client will get a warning on stderr when setting DNS to a localhost address; verify this: if !strings.Contains(stderr, "Localhost DNS setting") { - t.Fatalf("Expected warning on stderr about localhost resolver, but got %q", stderr) + c.Fatalf("Expected warning on stderr about localhost resolver, but got %q", stderr) } actual := strings.Replace(strings.Trim(out, "\r\n"), "\n", " ", -1) if actual != "nameserver 127.0.0.1 search mydomain" { - t.Fatalf("expected 'nameserver 127.0.0.1 search mydomain', but says: %q", actual) + c.Fatalf("expected 'nameserver 127.0.0.1 search mydomain', but says: %q", actual) } cmd = exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "--dns-search=.", "busybox", "cat", "/etc/resolv.conf") out, _, _, err = runCommandWithStdoutStderr(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } actual = strings.Replace(strings.Trim(strings.Trim(out, "\r\n"), " "), "\n", " ", -1) if actual != "nameserver 127.0.0.1" { - t.Fatalf("expected 'nameserver 127.0.0.1', but says: %q", actual) + c.Fatalf("expected 'nameserver 127.0.0.1', but says: %q", actual) } - - logDone("run - dns options") } -func TestRunDnsOptionsBasedOnHostResolvConf(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunDnsOptionsBasedOnHostResolvConf(c *check.C) { + testRequires(c, SameHostDaemon) origResolvConf, err := ioutil.ReadFile("/etc/resolv.conf") if os.IsNotExist(err) { - t.Fatalf("/etc/resolv.conf does not exist") + c.Fatalf("/etc/resolv.conf does not exist") } hostNamservers := resolvconf.GetNameservers(origResolvConf) @@ -1642,58 +1342,58 @@ func TestRunDnsOptionsBasedOnHostResolvConf(t *testing.T) { var out string cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "busybox", "cat", "/etc/resolv.conf") if out, _, _, err = runCommandWithStdoutStderr(cmd); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actualNameservers := resolvconf.GetNameservers([]byte(out)); string(actualNameservers[0]) != "127.0.0.1" { - t.Fatalf("expected '127.0.0.1', but says: %q", string(actualNameservers[0])) + c.Fatalf("expected '127.0.0.1', but says: %q", string(actualNameservers[0])) } actualSearch := resolvconf.GetSearchDomains([]byte(out)) if len(actualSearch) != len(hostSearch) { - t.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch)) + c.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch)) } for i := range actualSearch { if actualSearch[i] != hostSearch[i] { - t.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i]) + c.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i]) } } cmd = exec.Command(dockerBinary, "run", "--dns-search=mydomain", "busybox", "cat", "/etc/resolv.conf") if out, _, err = runCommandWithOutput(cmd); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } actualNameservers := resolvconf.GetNameservers([]byte(out)) if len(actualNameservers) != len(hostNamservers) { - t.Fatalf("expected %q nameserver(s), but it has: %q", len(hostNamservers), len(actualNameservers)) + c.Fatalf("expected %q nameserver(s), but it has: %q", len(hostNamservers), len(actualNameservers)) } for i := range actualNameservers { if actualNameservers[i] != hostNamservers[i] { - t.Fatalf("expected %q nameserver, but says: %q", actualNameservers[i], hostNamservers[i]) + c.Fatalf("expected %q nameserver, but says: %q", actualNameservers[i], hostNamservers[i]) } } if actualSearch = resolvconf.GetSearchDomains([]byte(out)); string(actualSearch[0]) != "mydomain" { - t.Fatalf("expected 'mydomain', but says: %q", string(actualSearch[0])) + c.Fatalf("expected 'mydomain', but says: %q", string(actualSearch[0])) } // test with file tmpResolvConf := []byte("search example.com\nnameserver 12.34.56.78\nnameserver 127.0.0.1") if err := ioutil.WriteFile("/etc/resolv.conf", tmpResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } // put the old resolvconf back defer func() { if err := ioutil.WriteFile("/etc/resolv.conf", origResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } }() resolvConf, err := ioutil.ReadFile("/etc/resolv.conf") if os.IsNotExist(err) { - t.Fatalf("/etc/resolv.conf does not exist") + c.Fatalf("/etc/resolv.conf does not exist") } hostNamservers = resolvconf.GetNameservers(resolvConf) @@ -1702,35 +1402,32 @@ func TestRunDnsOptionsBasedOnHostResolvConf(t *testing.T) { cmd = exec.Command(dockerBinary, "run", "busybox", "cat", "/etc/resolv.conf") if out, _, err = runCommandWithOutput(cmd); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actualNameservers = resolvconf.GetNameservers([]byte(out)); string(actualNameservers[0]) != "12.34.56.78" || len(actualNameservers) != 1 { - t.Fatalf("expected '12.34.56.78', but has: %v", actualNameservers) + c.Fatalf("expected '12.34.56.78', but has: %v", actualNameservers) } actualSearch = resolvconf.GetSearchDomains([]byte(out)) if len(actualSearch) != len(hostSearch) { - t.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch)) + c.Fatalf("expected %q search domain(s), but it has: %q", len(hostSearch), len(actualSearch)) } for i := range actualSearch { if actualSearch[i] != hostSearch[i] { - t.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i]) + c.Fatalf("expected %q domain, but says: %q", actualSearch[i], hostSearch[i]) } } - defer deleteAllContainers() - - logDone("run - dns options based on host resolv.conf") } // Test the file watch notifier on docker host's /etc/resolv.conf // A go-routine is responsible for auto-updating containers which are // stopped and have an unmodified copy of resolv.conf, as well as // marking running containers as requiring an update on next restart -func TestRunResolvconfUpdater(t *testing.T) { +func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { // Because overlay doesn't support inotify properly, we need to skip // this test if the docker daemon has Storage Driver == overlay - testRequires(t, SameHostDaemon, NotOverlay) + testRequires(c, SameHostDaemon, NotOverlay) tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78") tmpLocalhostResolvConf := []byte("nameserver 127.0.0.1") @@ -1738,97 +1435,97 @@ func TestRunResolvconfUpdater(t *testing.T) { //take a copy of resolv.conf for restoring after test completes resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf") if err != nil { - t.Fatal(err) + c.Fatal(err) } // This test case is meant to test monitoring resolv.conf when it is - // a regular file not a bind mount. So we unmount resolv.conf and replace + // a regular file not a bind mounc. So we unmount resolv.conf and replace // it with a file containing the original settings. cmd := exec.Command("umount", "/etc/resolv.conf") if _, err = runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } //cleanup defer func() { deleteAllContainers() if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } }() //1. test that a non-running container gets an updated resolv.conf cmd = exec.Command(dockerBinary, "run", "--name='first'", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } containerID1, err := getIDByName("first") if err != nil { - t.Fatal(err) + c.Fatal(err) } // replace resolv.conf with our temporary copy bytesResolvConf := []byte(tmpResolvConf) if err := ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(time.Second / 2) // check for update in container containerResolv, err := readContainerFile(containerID1, "resolv.conf") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(containerResolv, bytesResolvConf) { - t.Fatalf("Stopped container does not have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv)) + c.Fatalf("Stopped container does not have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv)) } //2. test that a non-running container does not receive resolv.conf updates // if it modified the container copy of the starting point resolv.conf cmd = exec.Command(dockerBinary, "run", "--name='second'", "busybox", "sh", "-c", "echo 'search mylittlepony.com' >>/etc/resolv.conf") if _, err = runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } containerID2, err := getIDByName("second") if err != nil { - t.Fatal(err) + c.Fatal(err) } containerResolvHashBefore, err := readContainerFile(containerID2, "resolv.conf.hash") if err != nil { - t.Fatal(err) + c.Fatal(err) } //make a change to resolv.conf (in this case replacing our tmp copy with orig copy) if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(time.Second / 2) containerResolvHashAfter, err := readContainerFile(containerID2, "resolv.conf.hash") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(containerResolvHashBefore, containerResolvHashAfter) { - t.Fatalf("Stopped container with modified resolv.conf should not have been updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter) + c.Fatalf("Stopped container with modified resolv.conf should not have been updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter) } //3. test that a running container's resolv.conf is not modified while running cmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } runningContainerID := strings.TrimSpace(out) containerResolvHashBefore, err = readContainerFile(runningContainerID, "resolv.conf.hash") if err != nil { - t.Fatal(err) + c.Fatal(err) } // replace resolv.conf if err := ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } // make sure the updater has time to run to validate we really aren't @@ -1836,27 +1533,27 @@ func TestRunResolvconfUpdater(t *testing.T) { time.Sleep(time.Second / 2) containerResolvHashAfter, err = readContainerFile(runningContainerID, "resolv.conf.hash") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(containerResolvHashBefore, containerResolvHashAfter) { - t.Fatalf("Running container's resolv.conf should not be updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter) + c.Fatalf("Running container's resolv.conf should not be updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter) } //4. test that a running container's resolv.conf is updated upon restart // (the above container is still running..) cmd = exec.Command(dockerBinary, "restart", runningContainerID) if _, err = runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } // check for update in container containerResolv, err = readContainerFile(runningContainerID, "resolv.conf") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(containerResolv, bytesResolvConf) { - t.Fatalf("Restarted container should have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv)) + c.Fatalf("Restarted container should have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv)) } //5. test that additions of a localhost resolver are cleaned from @@ -1865,7 +1562,7 @@ func TestRunResolvconfUpdater(t *testing.T) { // replace resolv.conf with a localhost-only nameserver copy bytesResolvConf = []byte(tmpLocalhostResolvConf) if err = ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(time.Second / 2) @@ -1873,12 +1570,12 @@ func TestRunResolvconfUpdater(t *testing.T) { // after the cleanup of resolv.conf found only a localhost nameserver: containerResolv, err = readContainerFile(containerID1, "resolv.conf") if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "\nnameserver 8.8.8.8\nnameserver 8.8.4.4" if !bytes.Equal(containerResolv, []byte(expected)) { - t.Fatalf("Container does not have cleaned/replaced DNS in resolv.conf; expected %q, got %q", expected, string(containerResolv)) + c.Fatalf("Container does not have cleaned/replaced DNS in resolv.conf; expected %q, got %q", expected, string(containerResolv)) } //6. Test that replacing (as opposed to modifying) resolv.conf triggers an update @@ -1886,194 +1583,171 @@ func TestRunResolvconfUpdater(t *testing.T) { // Restore the original resolv.conf if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } // Run the container so it picks up the old settings cmd = exec.Command(dockerBinary, "run", "--name='third'", "busybox", "true") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } containerID3, err := getIDByName("third") if err != nil { - t.Fatal(err) + c.Fatal(err) } // Create a modified resolv.conf.aside and override resolv.conf with it bytesResolvConf = []byte(tmpResolvConf) if err := ioutil.WriteFile("/etc/resolv.conf.aside", bytesResolvConf, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } err = os.Rename("/etc/resolv.conf.aside", "/etc/resolv.conf") if err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(time.Second / 2) // check for update in container containerResolv, err = readContainerFile(containerID3, "resolv.conf") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !bytes.Equal(containerResolv, bytesResolvConf) { - t.Fatalf("Stopped container does not have updated resolv.conf; expected\n%q\n got\n%q", tmpResolvConf, string(containerResolv)) + c.Fatalf("Stopped container does not have updated resolv.conf; expected\n%q\n got\n%q", tmpResolvConf, string(containerResolv)) } //cleanup, restore original resolv.conf happens in defer func() - logDone("run - resolv.conf updater") } -func TestRunAddHost(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunAddHost(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--add-host=extra:86.75.30.9", "busybox", "grep", "extra", "/etc/hosts") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } actual := strings.Trim(out, "\r\n") if actual != "86.75.30.9\textra" { - t.Fatalf("expected '86.75.30.9\textra', but says: %q", actual) + c.Fatalf("expected '86.75.30.9\textra', but says: %q", actual) } - - logDone("run - add-host option") } // Regression test for #6983 -func TestRunAttachStdErrOnlyTTYMode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAttachStdErrOnlyTTYMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-t", "-a", "stderr", "busybox", "true") exitCode, err := runCommand(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } else if exitCode != 0 { - t.Fatalf("Container should have exited with error code 0") + c.Fatalf("Container should have exited with error code 0") } - - logDone("run - Attach stderr only with -t") } // Regression test for #6983 -func TestRunAttachStdOutOnlyTTYMode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAttachStdOutOnlyTTYMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-t", "-a", "stdout", "busybox", "true") exitCode, err := runCommand(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } else if exitCode != 0 { - t.Fatalf("Container should have exited with error code 0") + c.Fatalf("Container should have exited with error code 0") } - - logDone("run - Attach stdout only with -t") } // Regression test for #6983 -func TestRunAttachStdOutAndErrTTYMode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAttachStdOutAndErrTTYMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-t", "-a", "stdout", "-a", "stderr", "busybox", "true") exitCode, err := runCommand(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } else if exitCode != 0 { - t.Fatalf("Container should have exited with error code 0") + c.Fatalf("Container should have exited with error code 0") } - - logDone("run - Attach stderr and stdout with -t") } // Test for #10388 - this will run the same test as TestRunAttachStdOutAndErrTTYMode // but using --attach instead of -a to make sure we read the flag correctly -func TestRunAttachWithDettach(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAttachWithDettach(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--attach", "stdout", "busybox", "true") _, stderr, _, err := runCommandWithStdoutStderr(cmd) if err == nil { - t.Fatal("Container should have exited with error code different than 0") + c.Fatal("Container should have exited with error code different than 0") } else if !strings.Contains(stderr, "Conflicting options: -a and -d") { - t.Fatal("Should have been returned an error with conflicting options -a and -d") + c.Fatal("Should have been returned an error with conflicting options -a and -d") } - - logDone("run - Attach stdout with -d") } -func TestRunState(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunState(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(out) state, err := inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if state != "true" { - t.Fatal("Container state is 'not running'") + c.Fatal("Container state is 'not running'") } pid1, err := inspectField(id, "State.Pid") if err != nil { - t.Fatal(err) + c.Fatal(err) } if pid1 == "0" { - t.Fatal("Container state Pid 0") + c.Fatal("Container state Pid 0") } cmd = exec.Command(dockerBinary, "stop", id) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } state, err = inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if state != "false" { - t.Fatal("Container state is 'running'") + c.Fatal("Container state is 'running'") } pid2, err := inspectField(id, "State.Pid") if err != nil { - t.Fatal(err) + c.Fatal(err) } if pid2 == pid1 { - t.Fatalf("Container state Pid %s, but expected %s", pid2, pid1) + c.Fatalf("Container state Pid %s, but expected %s", pid2, pid1) } cmd = exec.Command(dockerBinary, "start", id) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } state, err = inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if state != "true" { - t.Fatal("Container state is 'not running'") + c.Fatal("Container state is 'not running'") } pid3, err := inspectField(id, "State.Pid") if err != nil { - t.Fatal(err) + c.Fatal(err) } if pid3 == pid1 { - t.Fatalf("Container state Pid %s, but expected %s", pid2, pid1) + c.Fatalf("Container state Pid %s, but expected %s", pid2, pid1) } - logDone("run - test container state.") } // Test for #1737 -func TestRunCopyVolumeUidGid(t *testing.T) { +func (s *DockerSuite) TestRunCopyVolumeUidGid(c *check.C) { name := "testrunvolumesuidgid" defer deleteImages(name) - defer deleteAllContainers() _, err := buildImage(name, `FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd @@ -2081,178 +1755,164 @@ func TestRunCopyVolumeUidGid(t *testing.T) { RUN mkdir -p /hello && touch /hello/test && chown dockerio.dockerio /hello`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Test that the uid and gid is copied from the image to the volume cmd := exec.Command(dockerBinary, "run", "--rm", "-v", "/hello", name, "sh", "-c", "ls -l / | grep hello | awk '{print $3\":\"$4}'") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out = strings.TrimSpace(out) if out != "dockerio:dockerio" { - t.Fatalf("Wrong /hello ownership: %s, expected dockerio:dockerio", out) + c.Fatalf("Wrong /hello ownership: %s, expected dockerio:dockerio", out) } - - logDone("run - copy uid/gid for volume") } // Test for #1582 -func TestRunCopyVolumeContent(t *testing.T) { +func (s *DockerSuite) TestRunCopyVolumeContent(c *check.C) { name := "testruncopyvolumecontent" defer deleteImages(name) - defer deleteAllContainers() _, err := buildImage(name, `FROM busybox RUN mkdir -p /hello/local && echo hello > /hello/local/world`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Test that the content is copied from the image to the volume cmd := exec.Command(dockerBinary, "run", "--rm", "-v", "/hello", name, "find", "/hello") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !(strings.Contains(out, "/hello/local/world") && strings.Contains(out, "/hello/local")) { - t.Fatal("Container failed to transfer content to volume") + c.Fatal("Container failed to transfer content to volume") } - logDone("run - copy volume content") } -func TestRunCleanupCmdOnEntrypoint(t *testing.T) { +func (s *DockerSuite) TestRunCleanupCmdOnEntrypoint(c *check.C) { name := "testrunmdcleanuponentrypoint" defer deleteImages(name) - defer deleteAllContainers() if _, err := buildImage(name, `FROM busybox ENTRYPOINT ["echo"] CMD ["testingpoint"]`, true); err != nil { - t.Fatal(err) + c.Fatal(err) } runCmd := exec.Command(dockerBinary, "run", "--entrypoint", "whoami", name) out, exit, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("Error: %v, out: %q", err, out) + c.Fatalf("Error: %v, out: %q", err, out) } if exit != 0 { - t.Fatalf("expected exit code 0 received %d, out: %q", exit, out) + c.Fatalf("expected exit code 0 received %d, out: %q", exit, out) } out = strings.TrimSpace(out) if out != "root" { - t.Fatalf("Expected output root, got %q", out) + c.Fatalf("Expected output root, got %q", out) } - logDone("run - cleanup cmd on --entrypoint") } // TestRunWorkdirExistsAndIsFile checks that if 'docker run -w' with existing file can be detected -func TestRunWorkdirExistsAndIsFile(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunWorkdirExistsAndIsFile(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-w", "/bin/cat", "busybox") out, exit, err := runCommandWithOutput(runCmd) if !(err != nil && exit == 1 && strings.Contains(out, "Cannot mkdir: /bin/cat is not a directory")) { - t.Fatalf("Docker must complains about making dir, but we got out: %s, exit: %d, err: %s", out, exit, err) + c.Fatalf("Docker must complains about making dir, but we got out: %s, exit: %d, err: %s", out, exit, err) } - logDone("run - error on existing file for workdir") } -func TestRunExitOnStdinClose(t *testing.T) { +func (s *DockerSuite) TestRunExitOnStdinClose(c *check.C) { name := "testrunexitonstdinclose" - defer deleteAllContainers() runCmd := exec.Command(dockerBinary, "run", "--name", name, "-i", "busybox", "/bin/cat") stdin, err := runCmd.StdinPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } stdout, err := runCmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } if err := runCmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := stdin.Write([]byte("hello\n")); err != nil { - t.Fatal(err) + c.Fatal(err) } r := bufio.NewReader(stdout) line, err := r.ReadString('\n') if err != nil { - t.Fatal(err) + c.Fatal(err) } line = strings.TrimSpace(line) if line != "hello" { - t.Fatalf("Output should be 'hello', got '%q'", line) + c.Fatalf("Output should be 'hello', got '%q'", line) } if err := stdin.Close(); err != nil { - t.Fatal(err) + c.Fatal(err) } finish := make(chan struct{}) go func() { if err := runCmd.Wait(); err != nil { - t.Fatal(err) + c.Fatal(err) } close(finish) }() select { case <-finish: case <-time.After(1 * time.Second): - t.Fatal("docker run failed to exit on stdin close") + c.Fatal("docker run failed to exit on stdin close") } state, err := inspectField(name, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if state != "false" { - t.Fatal("Container must be stopped after stdin closing") + c.Fatal("Container must be stopped after stdin closing") } - logDone("run - exit on stdin closing") } // Test for #2267 -func TestRunWriteHostsFileAndNotCommit(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWriteHostsFileAndNotCommit(c *check.C) { name := "writehosts" cmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sh", "-c", "echo test2267 >> /etc/hosts && cat /etc/hosts") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "test2267") { - t.Fatal("/etc/hosts should contain 'test2267'") + c.Fatal("/etc/hosts should contain 'test2267'") } cmd = exec.Command(dockerBinary, "diff", name) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, t) { - t.Fatal("diff should be empty") + if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, c) { + c.Fatal("diff should be empty") } - - logDone("run - write to /etc/hosts and not commited") } -func eqToBaseDiff(out string, t *testing.T) bool { +func eqToBaseDiff(out string, c *check.C) bool { cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello") out1, _, err := runCommandWithOutput(cmd) cID := strings.TrimSpace(out1) cmd = exec.Command(dockerBinary, "diff", cID) baseDiff, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, baseDiff) + c.Fatal(err, baseDiff) } baseArr := strings.Split(baseDiff, "\n") sort.Strings(baseArr) @@ -2276,346 +1936,299 @@ func sliceEq(a, b []string) bool { } // Test for #2267 -func TestRunWriteHostnameFileAndNotCommit(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWriteHostnameFileAndNotCommit(c *check.C) { name := "writehostname" cmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sh", "-c", "echo test2267 >> /etc/hostname && cat /etc/hostname") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "test2267") { - t.Fatal("/etc/hostname should contain 'test2267'") + c.Fatal("/etc/hostname should contain 'test2267'") } cmd = exec.Command(dockerBinary, "diff", name) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, t) { - t.Fatal("diff should be empty") + if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, c) { + c.Fatal("diff should be empty") } - - logDone("run - write to /etc/hostname and not commited") } // Test for #2267 -func TestRunWriteResolvFileAndNotCommit(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWriteResolvFileAndNotCommit(c *check.C) { name := "writeresolv" cmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sh", "-c", "echo test2267 >> /etc/resolv.conf && cat /etc/resolv.conf") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "test2267") { - t.Fatal("/etc/resolv.conf should contain 'test2267'") + c.Fatal("/etc/resolv.conf should contain 'test2267'") } cmd = exec.Command(dockerBinary, "diff", name) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, t) { - t.Fatal("diff should be empty") + if len(strings.Trim(out, "\r\n")) != 0 && !eqToBaseDiff(out, c) { + c.Fatal("diff should be empty") } - - logDone("run - write to /etc/resolv.conf and not commited") } -func TestRunWithBadDevice(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithBadDevice(c *check.C) { name := "baddevice" cmd := exec.Command(dockerBinary, "run", "--name", name, "--device", "/etc", "busybox", "true") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatal("Run should fail with bad device") + c.Fatal("Run should fail with bad device") } expected := `\"/etc\": not a device node` if !strings.Contains(out, expected) { - t.Fatalf("Output should contain %q, actual out: %q", expected, out) + c.Fatalf("Output should contain %q, actual out: %q", expected, out) } - logDone("run - error with bad device") } -func TestRunEntrypoint(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunEntrypoint(c *check.C) { name := "entrypoint" cmd := exec.Command(dockerBinary, "run", "--name", name, "--entrypoint", "/bin/echo", "busybox", "-n", "foobar") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } expected := "foobar" if out != expected { - t.Fatalf("Output should be %q, actual out: %q", expected, out) + c.Fatalf("Output should be %q, actual out: %q", expected, out) } - logDone("run - entrypoint") } -func TestRunBindMounts(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunBindMounts(c *check.C) { + testRequires(c, SameHostDaemon) tmpDir, err := ioutil.TempDir("", "docker-test-container") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir) - writeFile(path.Join(tmpDir, "touch-me"), "", t) + writeFile(path.Join(tmpDir, "touch-me"), "", c) // Test reading from a read-only bind mount cmd := exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp:ro", tmpDir), "busybox", "ls", "/tmp") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if !strings.Contains(out, "touch-me") { - t.Fatal("Container failed to read from bind mount") + c.Fatal("Container failed to read from bind mount") } // test writing to bind mount cmd = exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp:rw", tmpDir), "busybox", "touch", "/tmp/holla") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist + readFile(path.Join(tmpDir, "holla"), c) // Will fail if the file doesn't exist // test mounting to an illegal destination directory cmd = exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:.", tmpDir), "busybox", "ls", ".") _, err = runCommand(cmd) if err == nil { - t.Fatal("Container bind mounted illegal directory") + c.Fatal("Container bind mounted illegal directory") } // test mount a file cmd = exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s/holla:/tmp/holla:rw", tmpDir), "busybox", "sh", "-c", "echo -n 'yotta' > /tmp/holla") _, err = runCommand(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - content := readFile(path.Join(tmpDir, "holla"), t) // Will fail if the file doesn't exist + content := readFile(path.Join(tmpDir, "holla"), c) // Will fail if the file doesn't exist expected := "yotta" if content != expected { - t.Fatalf("Output should be %q, actual out: %q", expected, content) + c.Fatalf("Output should be %q, actual out: %q", expected, content) } - - logDone("run - bind mounts") } // Ensure that CIDFile gets deleted if it's empty // Perform this test by making `docker run` fail -func TestRunCidFileCleanupIfEmpty(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCidFileCleanupIfEmpty(c *check.C) { tmpDir, err := ioutil.TempDir("", "TestRunCidFile") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir) tmpCidFile := path.Join(tmpDir, "cid") cmd := exec.Command(dockerBinary, "run", "--cidfile", tmpCidFile, "emptyfs") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatalf("Run without command must fail. out=%s", out) + c.Fatalf("Run without command must fail. out=%s", out) } else if !strings.Contains(out, "No command specified") { - t.Fatalf("Run without command failed with wrong output. out=%s\nerr=%v", out, err) + c.Fatalf("Run without command failed with wrong outpuc. out=%s\nerr=%v", out, err) } if _, err := os.Stat(tmpCidFile); err == nil { - t.Fatalf("empty CIDFile %q should've been deleted", tmpCidFile) + c.Fatalf("empty CIDFile %q should've been deleted", tmpCidFile) } - logDone("run - cleanup empty cidfile on error") } // #2098 - Docker cidFiles only contain short version of the containerId -//sudo docker run --cidfile /tmp/docker_test.cid ubuntu echo "test" +//sudo docker run --cidfile /tmp/docker_tesc.cid ubuntu echo "test" // TestRunCidFile tests that run --cidfile returns the longid -func TestRunCidFileCheckIDLength(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCidFileCheckIDLength(c *check.C) { tmpDir, err := ioutil.TempDir("", "TestRunCidFile") if err != nil { - t.Fatal(err) + c.Fatal(err) } tmpCidFile := path.Join(tmpDir, "cid") defer os.RemoveAll(tmpDir) cmd := exec.Command(dockerBinary, "run", "-d", "--cidfile", tmpCidFile, "busybox", "true") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } id := strings.TrimSpace(out) buffer, err := ioutil.ReadFile(tmpCidFile) if err != nil { - t.Fatal(err) + c.Fatal(err) } cid := string(buffer) if len(cid) != 64 { - t.Fatalf("--cidfile should be a long id, not %q", id) + c.Fatalf("--cidfile should be a long id, not %q", id) } if cid != id { - t.Fatalf("cid must be equal to %s, got %s", id, cid) + c.Fatalf("cid must be equal to %s, got %s", id, cid) } - - logDone("run - cidfile contains long id") } -func TestRunNetworkNotInitializedNoneMode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunNetworkNotInitializedNoneMode(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--net=none", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } id := strings.TrimSpace(out) res, err := inspectField(id, "NetworkSettings.IPAddress") if err != nil { - t.Fatal(err) + c.Fatal(err) } if res != "" { - t.Fatalf("For 'none' mode network must not be initialized, but container got IP: %s", res) + c.Fatalf("For 'none' mode network must not be initialized, but container got IP: %s", res) } - - logDone("run - network must not be initialized in 'none' mode") } -func TestRunSetMacAddress(t *testing.T) { +func (s *DockerSuite) TestRunSetMacAddress(c *check.C) { mac := "12:34:56:78:9a:bc" - defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-i", "--rm", fmt.Sprintf("--mac-address=%s", mac), "busybox", "/bin/sh", "-c", "ip link show eth0 | tail -1 | awk '{print $2}'") out, ec, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("exec failed:\nexit code=%v\noutput=%s", ec, out) + c.Fatalf("exec failed:\nexit code=%v\noutput=%s", ec, out) } actualMac := strings.TrimSpace(out) if actualMac != mac { - t.Fatalf("Set MAC address with --mac-address failed. The container has an incorrect MAC address: %q, expected: %q", actualMac, mac) + c.Fatalf("Set MAC address with --mac-address failed. The container has an incorrect MAC address: %q, expected: %q", actualMac, mac) } - - logDone("run - setting MAC address with --mac-address") } -func TestRunInspectMacAddress(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunInspectMacAddress(c *check.C) { mac := "12:34:56:78:9a:bc" cmd := exec.Command(dockerBinary, "run", "-d", "--mac-address="+mac, "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } id := strings.TrimSpace(out) inspectedMac, err := inspectField(id, "NetworkSettings.MacAddress") if err != nil { - t.Fatal(err) + c.Fatal(err) } if inspectedMac != mac { - t.Fatalf("docker inspect outputs wrong MAC address: %q, should be: %q", inspectedMac, mac) + c.Fatalf("docker inspect outputs wrong MAC address: %q, should be: %q", inspectedMac, mac) } - - logDone("run - inspecting MAC address") } // test docker run use a invalid mac address -func TestRunWithInvalidMacAddress(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithInvalidMacAddress(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--mac-address", "92:d0:c6:0a:29", "busybox") out, _, err := runCommandWithOutput(runCmd) //use a invalid mac address should with a error out if err == nil || !strings.Contains(out, "is not a valid mac address") { - t.Fatalf("run with an invalid --mac-address should with error out") + c.Fatalf("run with an invalid --mac-address should with error out") } - - logDone("run - can't use an invalid mac address") } -func TestRunDeallocatePortOnMissingIptablesRule(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunDeallocatePortOnMissingIptablesRule(c *check.C) { + testRequires(c, SameHostDaemon) cmd := exec.Command(dockerBinary, "run", "-d", "-p", "23:23", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } id := strings.TrimSpace(out) ip, err := inspectField(id, "NetworkSettings.IPAddress") if err != nil { - t.Fatal(err) + c.Fatal(err) } iptCmd := exec.Command("iptables", "-D", "DOCKER", "-d", fmt.Sprintf("%s/32", ip), "!", "-i", "docker0", "-o", "docker0", "-p", "tcp", "-m", "tcp", "--dport", "23", "-j", "ACCEPT") out, _, err = runCommandWithOutput(iptCmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if err := deleteContainer(id); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", "-d", "-p", "23:23", "busybox", "top") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - - logDone("run - port should be deallocated even on iptables error") } -func TestRunPortInUse(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunPortInUse(c *check.C) { + testRequires(c, SameHostDaemon) port := "1234" l, err := net.Listen("tcp", ":"+port) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer l.Close() cmd := exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err == nil { - t.Fatalf("Binding on used port must fail") + c.Fatalf("Binding on used port must fail") } if !strings.Contains(out, "address already in use") { - t.Fatalf("Out must be about \"address already in use\", got %s", out) + c.Fatalf("Out must be about \"address already in use\", got %s", out) } - - logDone("run - error out if port already in use") } // https://github.com/docker/docker/issues/8428 -func TestRunPortProxy(t *testing.T) { - testRequires(t, SameHostDaemon) - - defer deleteAllContainers() +func (s *DockerSuite) TestRunPortProxy(c *check.C) { + testRequires(c, SameHostDaemon) port := "12345" cmd := exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("Failed to run and bind port %s, output: %s, error: %s", port, out, err) + c.Fatalf("Failed to run and bind port %s, output: %s, error: %s", port, out, err) } - // connect for 10 times here. This will trigger 10 EPIPES in the child + // connett for 10 times here. This will trigger 10 EPIPES in the child // process and kill it when it writes to a closed stdout/stderr for i := 0; i < 10; i++ { net.Dial("tcp", fmt.Sprintf("0.0.0.0:%s", port)) @@ -2624,343 +2237,308 @@ func TestRunPortProxy(t *testing.T) { listPs := exec.Command("sh", "-c", "ps ax | grep docker") out, _, err = runCommandWithOutput(listPs) if err != nil { - t.Errorf("list docker process failed with output %s, error %s", out, err) + c.Errorf("list docker process failed with output %s, error %s", out, err) } if strings.Contains(out, "docker ") { - t.Errorf("Unexpected defunct docker process") + c.Errorf("Unexpected defunct docker process") } if !strings.Contains(out, "docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 12345") { - t.Errorf("Failed to find docker-proxy process, got %s", out) + c.Errorf("Failed to find docker-proxy process, got %s", out) } - - logDone("run - proxy should work with unavailable port") } // Regression test for #7792 -func TestRunMountOrdering(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunMountOrdering(c *check.C) { + testRequires(c, SameHostDaemon) tmpDir, err := ioutil.TempDir("", "docker_nested_mount_test") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir) tmpDir2, err := ioutil.TempDir("", "docker_nested_mount_test2") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir2) - // Create a temporary tmpfs mount. + // Create a temporary tmpfs mounc. fooDir := filepath.Join(tmpDir, "foo") if err := os.MkdirAll(filepath.Join(tmpDir, "foo"), 0755); err != nil { - t.Fatalf("failed to mkdir at %s - %s", fooDir, err) + c.Fatalf("failed to mkdir at %s - %s", fooDir, err) } if err := ioutil.WriteFile(fmt.Sprintf("%s/touch-me", fooDir), []byte{}, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := ioutil.WriteFile(fmt.Sprintf("%s/touch-me", tmpDir), []byte{}, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := ioutil.WriteFile(fmt.Sprintf("%s/touch-me", tmpDir2), []byte{}, 0644); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp", tmpDir), "-v", fmt.Sprintf("%s:/tmp/foo", fooDir), "-v", fmt.Sprintf("%s:/tmp/tmp2", tmpDir2), "-v", fmt.Sprintf("%s:/tmp/tmp2/foo", fooDir), "busybox:latest", "sh", "-c", "ls /tmp/touch-me && ls /tmp/foo/touch-me && ls /tmp/tmp2/touch-me && ls /tmp/tmp2/foo/touch-me") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - - logDone("run - volumes are mounted in the correct order") } // Regression test for https://github.com/docker/docker/issues/8259 -func TestRunReuseBindVolumeThatIsSymlink(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunReuseBindVolumeThatIsSymlink(c *check.C) { + testRequires(c, SameHostDaemon) tmpDir, err := ioutil.TempDir(os.TempDir(), "testlink") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir) linkPath := os.TempDir() + "/testlink2" if err := os.Symlink(tmpDir, linkPath); err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(linkPath) // Create first container cmd := exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp/test", linkPath), "busybox", "ls", "-lh", "/tmp/test") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } // Create second container with same symlinked path // This will fail if the referenced issue is hit with a "Volume exists" error cmd = exec.Command(dockerBinary, "run", "-v", fmt.Sprintf("%s:/tmp/test", linkPath), "busybox", "ls", "-lh", "/tmp/test") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } - - logDone("run - can remount old bindmount volume") } //GH#10604: Test an "/etc" volume doesn't overlay special bind mounts in container -func TestRunCreateVolumeEtc(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunCreateVolumeEtc(c *check.C) { cmd := exec.Command(dockerBinary, "run", "--dns=127.0.0.1", "-v", "/etc", "busybox", "cat", "/etc/resolv.conf") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if !strings.Contains(out, "nameserver 127.0.0.1") { - t.Fatal("/etc volume mount hides /etc/resolv.conf") + c.Fatal("/etc volume mount hides /etc/resolv.conf") } cmd = exec.Command(dockerBinary, "run", "-h=test123", "-v", "/etc", "busybox", "cat", "/etc/hostname") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } if !strings.Contains(out, "test123") { - t.Fatal("/etc volume mount hides /etc/hostname") + c.Fatal("/etc volume mount hides /etc/hostname") } cmd = exec.Command(dockerBinary, "run", "--add-host=test:192.168.0.1", "-v", "/etc", "busybox", "cat", "/etc/hosts") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.Replace(out, "\n", " ", -1) if !strings.Contains(out, "192.168.0.1\ttest") || !strings.Contains(out, "127.0.0.1\tlocalhost") { - t.Fatal("/etc volume mount hides /etc/hosts") + c.Fatal("/etc volume mount hides /etc/hosts") } - - logDone("run - verify /etc volume doesn't hide special bind mounts") } -func TestVolumesNoCopyData(t *testing.T) { +func (s *DockerSuite) TestVolumesNoCopyData(c *check.C) { defer deleteImages("dataimage") - defer deleteAllContainers() if _, err := buildImage("dataimage", `FROM busybox RUN mkdir -p /foo RUN touch /foo/bar`, true); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "--name", "test", "-v", "/foo", "busybox") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", "--volumes-from", "test", "dataimage", "ls", "-lh", "/foo/bar") if out, _, err := runCommandWithOutput(cmd); err == nil || !strings.Contains(out, "No such file or directory") { - t.Fatalf("Data was copied on volumes-from but shouldn't be:\n%q", out) + c.Fatalf("Data was copied on volumes-from but shouldn't be:\n%q", out) } tmpDir := randomUnixTmpDirPath("docker_test_bind_mount_copy_data") cmd = exec.Command(dockerBinary, "run", "-v", tmpDir+":/foo", "dataimage", "ls", "-lh", "/foo/bar") if out, _, err := runCommandWithOutput(cmd); err == nil || !strings.Contains(out, "No such file or directory") { - t.Fatalf("Data was copied on bind-mount but shouldn't be:\n%q", out) + c.Fatalf("Data was copied on bind-mount but shouldn't be:\n%q", out) } - - logDone("run - volumes do not copy data for volumes-from and bindmounts") } -func TestRunVolumesNotRecreatedOnStart(t *testing.T) { - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunVolumesNotRecreatedOnStart(c *check.C) { + testRequires(c, SameHostDaemon) // Clear out any remnants from other tests deleteAllContainers() info, err := ioutil.ReadDir(volumesConfigPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } if len(info) > 0 { for _, f := range info { if err := os.RemoveAll(volumesConfigPath + "/" + f.Name()); err != nil { - t.Fatal(err) + c.Fatal(err) } } } - defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-v", "/foo", "--name", "lone_starr", "busybox") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "start", "lone_starr") if _, err := runCommand(cmd); err != nil { - t.Fatal(err) + c.Fatal(err) } info, err = ioutil.ReadDir(volumesConfigPath) if err != nil { - t.Fatal(err) + c.Fatal(err) } if len(info) != 1 { - t.Fatalf("Expected only 1 volume have %v", len(info)) + c.Fatalf("Expected only 1 volume have %v", len(info)) } - - logDone("run - volumes not recreated on start") } -func TestRunNoOutputFromPullInStdout(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunNoOutputFromPullInStdout(c *check.C) { // just run with unknown image cmd := exec.Command(dockerBinary, "run", "asdfsg") stdout := bytes.NewBuffer(nil) cmd.Stdout = stdout if err := cmd.Run(); err == nil { - t.Fatal("Run with unknown image should fail") + c.Fatal("Run with unknown image should fail") } if stdout.Len() != 0 { - t.Fatalf("Stdout contains output from pull: %s", stdout) + c.Fatalf("Stdout contains output from pull: %s", stdout) } - logDone("run - no output from pull in stdout") } -func TestRunVolumesCleanPaths(t *testing.T) { +func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) { if _, err := buildImage("run_volumes_clean_paths", `FROM busybox VOLUME /foo/`, true); err != nil { - t.Fatal(err) + c.Fatal(err) } defer deleteImages("run_volumes_clean_paths") - defer deleteAllContainers() cmd := exec.Command(dockerBinary, "run", "-v", "/foo", "-v", "/bar/", "--name", "dark_helmet", "run_volumes_clean_paths") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out, err := inspectFieldMap("dark_helmet", "Volumes", "/foo/") if err != nil { - t.Fatal(err) + c.Fatal(err) } if out != "" { - t.Fatalf("Found unexpected volume entry for '/foo/' in volumes\n%q", out) + c.Fatalf("Found unexpected volume entry for '/foo/' in volumes\n%q", out) } out, err = inspectFieldMap("dark_helmet", "Volumes", "/foo") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, volumesStoragePath) { - t.Fatalf("Volume was not defined for /foo\n%q", out) + c.Fatalf("Volume was not defined for /foo\n%q", out) } out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar/") if err != nil { - t.Fatal(err) + c.Fatal(err) } if out != "" { - t.Fatalf("Found unexpected volume entry for '/bar/' in volumes\n%q", out) + c.Fatalf("Found unexpected volume entry for '/bar/' in volumes\n%q", out) } out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar") if err != nil { - t.Fatal(err) + c.Fatal(err) } if !strings.Contains(out, volumesStoragePath) { - t.Fatalf("Volume was not defined for /bar\n%q", out) + c.Fatalf("Volume was not defined for /bar\n%q", out) } - - logDone("run - volume paths are cleaned") } // Regression test for #3631 -func TestRunSlowStdoutConsumer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunSlowStdoutConsumer(c *check.C) { + cont := exec.Command(dockerBinary, "run", "--rm", "busybox", "/bin/sh", "-c", "dd if=/dev/zero of=/dev/stdout bs=1024 count=2000 | catv") - c := exec.Command(dockerBinary, "run", "--rm", "busybox", "/bin/sh", "-c", "dd if=/dev/zero of=/dev/stdout bs=1024 count=2000 | catv") - - stdout, err := c.StdoutPipe() + stdout, err := cont.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } - if err := c.Start(); err != nil { - t.Fatal(err) + if err := cont.Start(); err != nil { + c.Fatal(err) } n, err := consumeWithSpeed(stdout, 10000, 5*time.Millisecond, nil) if err != nil { - t.Fatal(err) + c.Fatal(err) } expected := 2 * 1024 * 2000 if n != expected { - t.Fatalf("Expected %d, got %d", expected, n) + c.Fatalf("Expected %d, got %d", expected, n) } - - logDone("run - slow consumer") } -func TestRunAllowPortRangeThroughExpose(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAllowPortRangeThroughExpose(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--expose", "3000-3003", "-P", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err) + c.Fatal(err) } id := strings.TrimSpace(out) portstr, err := inspectFieldJSON(id, "NetworkSettings.Ports") if err != nil { - t.Fatal(err) + c.Fatal(err) } var ports nat.PortMap if err = unmarshalJSON([]byte(portstr), &ports); err != nil { - t.Fatal(err) + c.Fatal(err) } for port, binding := range ports { portnum, _ := strconv.Atoi(strings.Split(string(port), "/")[0]) if portnum < 3000 || portnum > 3003 { - t.Fatalf("Port %d is out of range ", portnum) + c.Fatalf("Port %d is out of range ", portnum) } if binding == nil || len(binding) != 1 || len(binding[0].HostPort) == 0 { - t.Fatalf("Port is not mapped for the port %d", port) + c.Fatalf("Port is not mapped for the port %d", port) } } if err := deleteContainer(id); err != nil { - t.Fatal(err) + c.Fatal(err) } - logDone("run - allow port range through --expose flag") } // test docker run expose a invalid port -func TestRunExposePort(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunExposePort(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--expose", "80000", "busybox") out, _, err := runCommandWithOutput(runCmd) //expose a invalid port should with a error out if err == nil || !strings.Contains(out, "Invalid range format for --expose") { - t.Fatalf("run --expose a invalid port should with error out") + c.Fatalf("run --expose a invalid port should with error out") } - - logDone("run - can't expose a invalid port") } -func TestRunUnknownCommand(t *testing.T) { - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunUnknownCommand(c *check.C) { + testRequires(c, NativeExecDriver) runCmd := exec.Command(dockerBinary, "create", "busybox", "/bin/nada") cID, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("Failed to create container: %v, output: %q", err, cID) + c.Fatalf("Failed to create container: %v, output: %q", err, cID) } cID = strings.TrimSpace(cID) @@ -2972,176 +2550,159 @@ func TestRunUnknownCommand(t *testing.T) { rc = strings.TrimSpace(rc) if err2 != nil { - t.Fatalf("Error getting status of container: %v", err2) + c.Fatalf("Error getting status of container: %v", err2) } if rc == "0" { - t.Fatalf("ExitCode(%v) cannot be 0", rc) + c.Fatalf("ExitCode(%v) cannot be 0", rc) } - - logDone("run - Unknown Command") } -func TestRunModeIpcHost(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunModeIpcHost(c *check.C) { + testRequires(c, SameHostDaemon) hostIpc, err := os.Readlink("/proc/1/ns/ipc") if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "--ipc=host", "busybox", "readlink", "/proc/self/ns/ipc") out2, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if hostIpc != out2 { - t.Fatalf("IPC different with --ipc=host %s != %s\n", hostIpc, out2) + c.Fatalf("IPC different with --ipc=host %s != %s\n", hostIpc, out2) } cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/ipc") out2, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if hostIpc == out2 { - t.Fatalf("IPC should be different without --ipc=host %s == %s\n", hostIpc, out2) + c.Fatalf("IPC should be different without --ipc=host %s == %s\n", hostIpc, out2) } - - logDone("run - ipc host mode") } -func TestRunModeIpcContainer(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestRunModeIpcContainer(c *check.C) { + testRequires(c, SameHostDaemon) cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(out) state, err := inspectField(id, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if state != "true" { - t.Fatal("Container state is 'not running'") + c.Fatal("Container state is 'not running'") } pid1, err := inspectField(id, "State.Pid") if err != nil { - t.Fatal(err) + c.Fatal(err) } parentContainerIpc, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/ipc", pid1)) if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", fmt.Sprintf("--ipc=container:%s", id), "busybox", "readlink", "/proc/self/ns/ipc") out2, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if parentContainerIpc != out2 { - t.Fatalf("IPC different with --ipc=container:%s %s != %s\n", id, parentContainerIpc, out2) + c.Fatalf("IPC different with --ipc=container:%s %s != %s\n", id, parentContainerIpc, out2) } - - logDone("run - ipc container mode") } -func TestRunModeIpcContainerNotExists(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunModeIpcContainerNotExists(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--ipc", "container:abcd1234", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if !strings.Contains(out, "abcd1234") || err == nil { - t.Fatalf("run IPC from a non exists container should with correct error out") + c.Fatalf("run IPC from a non exists container should with correct error out") } - - logDone("run - ipc from a non exists container failed with correct error out") } -func TestContainerNetworkMode(t *testing.T) { - defer deleteAllContainers() - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestContainerNetworkMode(c *check.C) { + testRequires(c, SameHostDaemon) cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } id := strings.TrimSpace(out) if err := waitRun(id); err != nil { - t.Fatal(err) + c.Fatal(err) } pid1, err := inspectField(id, "State.Pid") if err != nil { - t.Fatal(err) + c.Fatal(err) } parentContainerNet, err := os.Readlink(fmt.Sprintf("/proc/%s/ns/net", pid1)) if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd = exec.Command(dockerBinary, "run", fmt.Sprintf("--net=container:%s", id), "busybox", "readlink", "/proc/self/ns/net") out2, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if parentContainerNet != out2 { - t.Fatalf("NET different with --net=container:%s %s != %s\n", id, parentContainerNet, out2) + c.Fatalf("NET different with --net=container:%s %s != %s\n", id, parentContainerNet, out2) } - - logDone("run - container shared network namespace") } -func TestRunModePidHost(t *testing.T) { - testRequires(t, NativeExecDriver, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunModePidHost(c *check.C) { + testRequires(c, NativeExecDriver, SameHostDaemon) hostPid, err := os.Readlink("/proc/1/ns/pid") if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "--pid=host", "busybox", "readlink", "/proc/self/ns/pid") out2, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if hostPid != out2 { - t.Fatalf("PID different with --pid=host %s != %s\n", hostPid, out2) + c.Fatalf("PID different with --pid=host %s != %s\n", hostPid, out2) } cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/pid") out2, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if hostPid == out2 { - t.Fatalf("PID should be different without --pid=host %s == %s\n", hostPid, out2) + c.Fatalf("PID should be different without --pid=host %s == %s\n", hostPid, out2) } - - logDone("run - pid host mode") } -func TestRunTLSverify(t *testing.T) { +func (s *DockerSuite) TestRunTLSverify(c *check.C) { cmd := exec.Command(dockerBinary, "ps") out, ec, err := runCommandWithOutput(cmd) if err != nil || ec != 0 { - t.Fatalf("Should have worked: %v:\n%v", err, out) + c.Fatalf("Should have worked: %v:\n%v", err, out) } // Regardless of whether we specify true or false we need to @@ -3150,195 +2711,172 @@ func TestRunTLSverify(t *testing.T) { cmd = exec.Command(dockerBinary, "--tlsverify=false", "ps") out, ec, err = runCommandWithOutput(cmd) if err == nil || ec == 0 || !strings.Contains(out, "trying to connect") { - t.Fatalf("Should have failed: \nec:%v\nout:%v\nerr:%v", ec, out, err) + c.Fatalf("Should have failed: \net:%v\nout:%v\nerr:%v", ec, out, err) } cmd = exec.Command(dockerBinary, "--tlsverify=true", "ps") out, ec, err = runCommandWithOutput(cmd) if err == nil || ec == 0 || !strings.Contains(out, "cert") { - t.Fatalf("Should have failed: \nec:%v\nout:%v\nerr:%v", ec, out, err) + c.Fatalf("Should have failed: \net:%v\nout:%v\nerr:%v", ec, out, err) } - - logDone("run - verify tls is set for --tlsverify") } -func TestRunPortFromDockerRangeInUse(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunPortFromDockerRangeInUse(c *check.C) { // first find allocator current position cmd := exec.Command(dockerBinary, "run", "-d", "-p", ":80", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } id := strings.TrimSpace(out) cmd = exec.Command(dockerBinary, "port", id) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.TrimSpace(out) if out == "" { - t.Fatal("docker port command output is empty") + c.Fatal("docker port command output is empty") } out = strings.Split(out, ":")[1] lastPort, err := strconv.Atoi(out) if err != nil { - t.Fatal(err) + c.Fatal(err) } port := lastPort + 1 l, err := net.Listen("tcp", ":"+strconv.Itoa(port)) if err != nil { - t.Fatal(err) + c.Fatal(err) } defer l.Close() cmd = exec.Command(dockerBinary, "run", "-d", "-p", ":80", "busybox", "top") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatalf(out, err) + c.Fatalf(out, err) } id = strings.TrimSpace(out) cmd = exec.Command(dockerBinary, "port", id) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } - - logDone("run - find another port if port from autorange already bound") } -func TestRunTtyWithPipe(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunTtyWithPipe(c *check.C) { done := make(chan struct{}) go func() { defer close(done) cmd := exec.Command(dockerBinary, "run", "-ti", "busybox", "true") if _, err := cmd.StdinPipe(); err != nil { - t.Fatal(err) + c.Fatal(err) } expected := "cannot enable tty mode" if out, _, err := runCommandWithOutput(cmd); err == nil { - t.Fatal("run should have failed") + c.Fatal("run should have failed") } else if !strings.Contains(out, expected) { - t.Fatalf("run failed with error %q: expected %q", out, expected) + c.Fatalf("run failed with error %q: expected %q", out, expected) } }() select { case <-done: case <-time.After(3 * time.Second): - t.Fatal("container is running but should have failed") + c.Fatal("container is running but should have failed") } - - logDone("run - forbid piped stdin with tty") } -func TestRunNonLocalMacAddress(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunNonLocalMacAddress(c *check.C) { addr := "00:16:3E:08:00:50" cmd := exec.Command(dockerBinary, "run", "--mac-address", addr, "busybox", "ifconfig") if out, _, err := runCommandWithOutput(cmd); err != nil || !strings.Contains(out, addr) { - t.Fatalf("Output should have contained %q: %s, %v", addr, out, err) + c.Fatalf("Output should have contained %q: %s, %v", addr, out, err) } - - logDone("run - use non-local mac-address") } -func TestRunNetHost(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunNetHost(c *check.C) { + testRequires(c, SameHostDaemon) hostNet, err := os.Readlink("/proc/1/ns/net") if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "--net=host", "busybox", "readlink", "/proc/self/ns/net") out2, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if hostNet != out2 { - t.Fatalf("Net namespace different with --net=host %s != %s\n", hostNet, out2) + c.Fatalf("Net namespace different with --net=host %s != %s\n", hostNet, out2) } cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/net") out2, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out2) + c.Fatal(err, out2) } out2 = strings.Trim(out2, "\n") if hostNet == out2 { - t.Fatalf("Net namespace should be different without --net=host %s == %s\n", hostNet, out2) + c.Fatalf("Net namespace should be different without --net=host %s == %s\n", hostNet, out2) } - - logDone("run - net host mode") } -func TestRunNetContainerWhichHost(t *testing.T) { - testRequires(t, SameHostDaemon) - defer deleteAllContainers() +func (s *DockerSuite) TestRunNetContainerWhichHost(c *check.C) { + testRequires(c, SameHostDaemon) hostNet, err := os.Readlink("/proc/1/ns/net") if err != nil { - t.Fatal(err) + c.Fatal(err) } cmd := exec.Command(dockerBinary, "run", "-d", "--net=host", "--name=test", "busybox", "top") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } cmd = exec.Command(dockerBinary, "run", "--net=container:test", "busybox", "readlink", "/proc/self/ns/net") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } out = strings.Trim(out, "\n") if hostNet != out { - t.Fatalf("Container should have host network namespace") + c.Fatalf("Container should have host network namespace") } - - logDone("run - net container mode, where container in host mode") } -func TestRunAllowPortRangeThroughPublish(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunAllowPortRangeThroughPublish(c *check.C) { cmd := exec.Command(dockerBinary, "run", "-d", "--expose", "3000-3003", "-p", "3000-3003", "busybox", "top") out, _, err := runCommandWithOutput(cmd) id := strings.TrimSpace(out) portstr, err := inspectFieldJSON(id, "NetworkSettings.Ports") if err != nil { - t.Fatal(err) + c.Fatal(err) } var ports nat.PortMap err = unmarshalJSON([]byte(portstr), &ports) for port, binding := range ports { portnum, _ := strconv.Atoi(strings.Split(string(port), "/")[0]) if portnum < 3000 || portnum > 3003 { - t.Fatalf("Port %d is out of range ", portnum) + c.Fatalf("Port %d is out of range ", portnum) } if binding == nil || len(binding) != 1 || len(binding[0].HostPort) == 0 { - t.Fatal("Port is not mapped for the port "+port, out) + c.Fatal("Port is not mapped for the port "+port, out) } } - logDone("run - allow port range through --expose flag") } -func TestRunOOMExitCode(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunOOMExitCode(c *check.C) { done := make(chan struct{}) go func() { defer close(done) @@ -3346,167 +2884,143 @@ func TestRunOOMExitCode(t *testing.T) { runCmd := exec.Command(dockerBinary, "run", "-m", "4MB", "busybox", "sh", "-c", "x=a; while true; do x=$x$x$x$x; done") out, exitCode, _ := runCommandWithOutput(runCmd) if expected := 137; exitCode != expected { - t.Fatalf("wrong exit code for OOM container: expected %d, got %d (output: %q)", expected, exitCode, out) + c.Fatalf("wrong exit code for OOM container: expected %d, got %d (output: %q)", expected, exitCode, out) } }() select { case <-done: case <-time.After(30 * time.Second): - t.Fatal("Timeout waiting for container to die on OOM") + c.Fatal("Timeout waiting for container to die on OOM") } - - logDone("run - exit code on oom") } -func TestRunSetDefaultRestartPolicy(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunSetDefaultRestartPolicy(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "test", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } cmd := exec.Command(dockerBinary, "inspect", "-f", "{{.HostConfig.RestartPolicy.Name}}", "test") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("failed to inspect container: %v, output: %q", err, out) + c.Fatalf("failed to inspect container: %v, output: %q", err, out) } out = strings.Trim(out, "\r\n") if out != "no" { - t.Fatalf("Set default restart policy failed") + c.Fatalf("Set default restart policy failed") } - - logDone("run - set default restart policy success") } -func TestRunRestartMaxRetries(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunRestartMaxRetries(c *check.C) { out, err := exec.Command(dockerBinary, "run", "-d", "--restart=on-failure:3", "busybox", "false").CombinedOutput() if err != nil { - t.Fatal(string(out), err) + c.Fatal(string(out), err) } id := strings.TrimSpace(string(out)) if err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false false", 10); err != nil { - t.Fatal(err) + c.Fatal(err) } count, err := inspectField(id, "RestartCount") if err != nil { - t.Fatal(err) + c.Fatal(err) } if count != "3" { - t.Fatalf("Container was restarted %s times, expected %d", count, 3) + c.Fatalf("Container was restarted %s times, expected %d", count, 3) } MaximumRetryCount, err := inspectField(id, "HostConfig.RestartPolicy.MaximumRetryCount") if err != nil { - t.Fatal(err) + c.Fatal(err) } if MaximumRetryCount != "3" { - t.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "3") + c.Fatalf("Container Maximum Retry Count is %s, expected %s", MaximumRetryCount, "3") } - logDone("run - test max-retries for --restart") } -func TestRunContainerWithWritableRootfs(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunContainerWithWritableRootfs(c *check.C) { out, err := exec.Command(dockerBinary, "run", "--rm", "busybox", "touch", "/file").CombinedOutput() if err != nil { - t.Fatal(string(out), err) + c.Fatal(string(out), err) } - logDone("run - writable rootfs") } -func TestRunContainerWithReadonlyRootfs(t *testing.T) { - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunContainerWithReadonlyRootfs(c *check.C) { + testRequires(c, NativeExecDriver) out, err := exec.Command(dockerBinary, "run", "--read-only", "--rm", "busybox", "touch", "/file").CombinedOutput() if err == nil { - t.Fatal("expected container to error on run with read only error") + c.Fatal("expected container to error on run with read only error") } expected := "Read-only file system" if !strings.Contains(string(out), expected) { - t.Fatalf("expected output from failure to contain %s but contains %s", expected, out) + c.Fatalf("expected output from failure to contain %s but contains %s", expected, out) } - logDone("run - read only rootfs") } -func TestRunVolumesFromRestartAfterRemoved(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunVolumesFromRestartAfterRemoved(c *check.C) { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "voltest", "-v", "/foo", "busybox")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "restarter", "--volumes-from", "voltest", "busybox", "top")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // Remove the main volume container and restart the consuming container out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "rm", "-f", "voltest")) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } // This should not fail since the volumes-from were already applied out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "restart", "restarter")) if err != nil { - t.Fatalf("expected container to restart successfully: %v\n%s", err, out) + c.Fatalf("expected container to restart successfully: %v\n%s", err, out) } - - logDone("run - can restart a volumes-from container after producer is removed") } // run container with --rm should remove container if exit code != 0 -func TestRunContainerWithRmFlagExitCodeNotEqualToZero(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunContainerWithRmFlagExitCodeNotEqualToZero(c *check.C) { name := "flowers" runCmd := exec.Command(dockerBinary, "run", "--name", name, "--rm", "busybox", "ls", "/notexists") out, _, err := runCommandWithOutput(runCmd) if err == nil { - t.Fatal("Expected docker run to fail", out, err) + c.Fatal("Expected docker run to fail", out, err) } out, err = getAllContainers() if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out != "" { - t.Fatal("Expected not to have containers", out) + c.Fatal("Expected not to have containers", out) } - - logDone("run - container is removed if run with --rm and exit code != 0") } -func TestRunContainerWithRmFlagCannotStartContainer(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunContainerWithRmFlagCannotStartContainer(c *check.C) { name := "sparkles" runCmd := exec.Command(dockerBinary, "run", "--name", name, "--rm", "busybox", "commandNotFound") out, _, err := runCommandWithOutput(runCmd) if err == nil { - t.Fatal("Expected docker run to fail", out, err) + c.Fatal("Expected docker run to fail", out, err) } out, err = getAllContainers() if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } if out != "" { - t.Fatal("Expected not to have containers", out) + c.Fatal("Expected not to have containers", out) } - - logDone("run - container is removed if run with --rm and cannot start") } -func TestRunPidHostWithChildIsKillable(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunPidHostWithChildIsKillable(c *check.C) { name := "ibuildthecloud" if out, err := exec.Command(dockerBinary, "run", "-d", "--pid=host", "--name", name, "busybox", "sh", "-c", "sleep 30; echo hi").CombinedOutput(); err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } time.Sleep(1 * time.Second) errchan := make(chan error) @@ -3519,10 +3033,9 @@ func TestRunPidHostWithChildIsKillable(t *testing.T) { select { case err := <-errchan: if err != nil { - t.Fatal(err) + c.Fatal(err) } case <-time.After(5 * time.Second): - t.Fatal("Kill container timed out") + c.Fatal("Kill container timed out") } - logDone("run - can kill container with pid-host and some childs of pid 1") } diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 211e6c1f5..74fae1735 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -11,22 +11,19 @@ import ( "path" "path/filepath" "strings" - "testing" "time" "github.com/docker/docker/pkg/mount" + "github.com/go-check/check" "github.com/kr/pty" ) // #6509 -func TestRunRedirectStdout(t *testing.T) { - - defer deleteAllContainers() - +func (s *DockerSuite) TestRunRedirectStdout(c *check.C) { checkRedirect := func(command string) { _, tty, err := pty.Open() if err != nil { - t.Fatalf("Could not open pty: %v", err) + c.Fatalf("Could not open pty: %v", err) } cmd := exec.Command("sh", "-c", command) cmd.Stdin = tty @@ -34,35 +31,31 @@ func TestRunRedirectStdout(t *testing.T) { cmd.Stderr = tty ch := make(chan struct{}) if err := cmd.Start(); err != nil { - t.Fatalf("start err: %v", err) + c.Fatalf("start err: %v", err) } go func() { if err := cmd.Wait(); err != nil { - t.Fatalf("wait err=%v", err) + c.Fatalf("wait err=%v", err) } close(ch) }() select { case <-time.After(10 * time.Second): - t.Fatal("command timeout") + c.Fatal("command timeout") case <-ch: } } checkRedirect(dockerBinary + " run -i busybox cat /etc/passwd | grep -q root") checkRedirect(dockerBinary + " run busybox cat /etc/passwd | grep -q root") - - logDone("run - redirect stdout") } // Test recursive bind mount works by default -func TestRunWithVolumesIsRecursive(t *testing.T) { - defer deleteAllContainers() - +func (s *DockerSuite) TestRunWithVolumesIsRecursive(c *check.C) { tmpDir, err := ioutil.TempDir("", "docker_recursive_mount_test") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer os.RemoveAll(tmpDir) @@ -70,68 +63,62 @@ func TestRunWithVolumesIsRecursive(t *testing.T) { // Create a temporary tmpfs mount. tmpfsDir := filepath.Join(tmpDir, "tmpfs") if err := os.MkdirAll(tmpfsDir, 0777); err != nil { - t.Fatalf("failed to mkdir at %s - %s", tmpfsDir, err) + c.Fatalf("failed to mkdir at %s - %s", tmpfsDir, err) } if err := mount.Mount("tmpfs", tmpfsDir, "tmpfs", ""); err != nil { - t.Fatalf("failed to create a tmpfs mount at %s - %s", tmpfsDir, err) + c.Fatalf("failed to create a tmpfs mount at %s - %s", tmpfsDir, err) } f, err := ioutil.TempFile(tmpfsDir, "touch-me") if err != nil { - t.Fatal(err) + c.Fatal(err) } defer f.Close() runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", fmt.Sprintf("%s:/tmp:ro", tmpDir), "busybox:latest", "ls", "/tmp/tmpfs") out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd) if err != nil && exitCode != 0 { - t.Fatal(out, stderr, err) + c.Fatal(out, stderr, err) } if !strings.Contains(out, filepath.Base(f.Name())) { - t.Fatal("Recursive bind mount test failed. Expected file not found") + c.Fatal("Recursive bind mount test failed. Expected file not found") } - - logDone("run - volumes are bind mounted recursively") } -func TestRunWithUlimits(t *testing.T) { - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunWithUlimits(c *check.C) { + testRequires(c, NativeExecDriver) out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name=testulimits", "--ulimit", "nofile=42", "busybox", "/bin/sh", "-c", "ulimit -n")) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } ul := strings.TrimSpace(out) if ul != "42" { - t.Fatalf("expected `ulimit -n` to be 42, got %s", ul) + c.Fatalf("expected `ulimit -n` to be 42, got %s", ul) } - - logDone("run - ulimits are set") } -func TestRunContainerWithCgroupParent(t *testing.T) { - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunContainerWithCgroupParent(c *check.C) { + testRequires(c, NativeExecDriver) cgroupParent := "test" data, err := ioutil.ReadFile("/proc/self/cgroup") if err != nil { - t.Fatalf("failed to read '/proc/self/cgroup - %v", err) + c.Fatalf("failed to read '/proc/self/cgroup - %v", err) } selfCgroupPaths := parseCgroupPaths(string(data)) selfCpuCgroup, found := selfCgroupPaths["memory"] if !found { - t.Fatalf("unable to find self cpu cgroup path. CgroupsPath: %v", selfCgroupPaths) + c.Fatalf("unable to find self cpu cgroup path. CgroupsPath: %v", selfCgroupPaths) } out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--cgroup-parent", cgroupParent, "--rm", "busybox", "cat", "/proc/self/cgroup")) if err != nil { - t.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err) + c.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err) } cgroupPaths := parseCgroupPaths(string(out)) if len(cgroupPaths) == 0 { - t.Fatalf("unexpected output - %q", string(out)) + c.Fatalf("unexpected output - %q", string(out)) } found = false expectedCgroupPrefix := path.Join(selfCpuCgroup, cgroupParent) @@ -142,24 +129,22 @@ func TestRunContainerWithCgroupParent(t *testing.T) { } } if !found { - t.Fatalf("unexpected cgroup paths. Expected at least one cgroup path to have prefix %q. Cgroup Paths: %v", expectedCgroupPrefix, cgroupPaths) + c.Fatalf("unexpected cgroup paths. Expected at least one cgroup path to have prefix %q. Cgroup Paths: %v", expectedCgroupPrefix, cgroupPaths) } - logDone("run - cgroup parent") } -func TestRunContainerWithCgroupParentAbsPath(t *testing.T) { - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunContainerWithCgroupParentAbsPath(c *check.C) { + testRequires(c, NativeExecDriver) cgroupParent := "/cgroup-parent/test" out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--cgroup-parent", cgroupParent, "--rm", "busybox", "cat", "/proc/self/cgroup")) if err != nil { - t.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err) + c.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err) } cgroupPaths := parseCgroupPaths(string(out)) if len(cgroupPaths) == 0 { - t.Fatalf("unexpected output - %q", string(out)) + c.Fatalf("unexpected output - %q", string(out)) } found := false for _, path := range cgroupPaths { @@ -169,81 +154,75 @@ func TestRunContainerWithCgroupParentAbsPath(t *testing.T) { } } if !found { - t.Fatalf("unexpected cgroup paths. Expected at least one cgroup path to have prefix %q. Cgroup Paths: %v", cgroupParent, cgroupPaths) + c.Fatalf("unexpected cgroup paths. Expected at least one cgroup path to have prefix %q. Cgroup Paths: %v", cgroupParent, cgroupPaths) } - - logDone("run - cgroup parent with absolute cgroup path") } -func TestRunDeviceDirectory(t *testing.T) { - testRequires(t, NativeExecDriver) - defer deleteAllContainers() +func (s *DockerSuite) TestRunDeviceDirectory(c *check.C) { + testRequires(c, NativeExecDriver) cmd := exec.Command(dockerBinary, "run", "--device", "/dev/snd:/dev/snd", "busybox", "sh", "-c", "ls /dev/snd/") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); !strings.Contains(out, "timer") { - t.Fatalf("expected output /dev/snd/timer, received %s", actual) + c.Fatalf("expected output /dev/snd/timer, received %s", actual) } cmd = exec.Command(dockerBinary, "run", "--device", "/dev/snd:/dev/othersnd", "busybox", "sh", "-c", "ls /dev/othersnd/") out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } if actual := strings.Trim(out, "\r\n"); !strings.Contains(out, "seq") { - t.Fatalf("expected output /dev/othersnd/seq, received %s", actual) + c.Fatalf("expected output /dev/othersnd/seq, received %s", actual) } - - logDone("run - test --device directory mounts all internal devices") } // TestRunDetach checks attaching and detaching with the escape sequence. -func TestRunAttachDetach(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestRunAttachDetach(c *check.C) { name := "attach-detach" cmd := exec.Command(dockerBinary, "run", "--name", name, "-it", "busybox", "cat") stdout, err := cmd.StdoutPipe() if err != nil { - t.Fatal(err) + c.Fatal(err) } cpty, tty, err := pty.Open() if err != nil { - t.Fatal(err) + c.Fatal(err) } defer cpty.Close() cmd.Stdin = tty if err := cmd.Start(); err != nil { - t.Fatal(err) + c.Fatal(err) } if err := waitRun(name); err != nil { - t.Fatal(err) + c.Fatal(err) } if _, err := cpty.Write([]byte("hello\n")); err != nil { - t.Fatal(err) + c.Fatal(err) } out, err := bufio.NewReader(stdout).ReadString('\n') if err != nil { - t.Fatal(err) + c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - t.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("exepected 'hello', got %q", out) } // escape sequence if _, err := cpty.Write([]byte{16}); err != nil { - t.Fatal(err) + c.Fatal(err) } time.Sleep(100 * time.Millisecond) if _, err := cpty.Write([]byte{17}); err != nil { - t.Fatal(err) + c.Fatal(err) } ch := make(chan struct{}) @@ -254,21 +233,19 @@ func TestRunAttachDetach(t *testing.T) { running, err := inspectField(name, "State.Running") if err != nil { - t.Fatal(err) + c.Fatal(err) } if running != "true" { - t.Fatal("exepected container to still be running") + c.Fatal("exepected container to still be running") } go func() { - dockerCmd(t, "kill", name) + exec.Command(dockerBinary, "kill", name).Run() }() select { case <-ch: case <-time.After(10 * time.Millisecond): - t.Fatal("timed out waiting for container to exit") + c.Fatal("timed out waiting for container to exit") } - - logDone("run - attach detach") } diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go index c7bfb945d..ead436a92 100644 --- a/integration-cli/docker_cli_save_load_test.go +++ b/integration-cli/docker_cli_save_load_test.go @@ -9,15 +9,16 @@ import ( "reflect" "sort" "strings" - "testing" + + "github.com/go-check/check" ) // save a repo using gz compression and try to load it using stdout -func TestSaveXzAndLoadRepoStdout(t *testing.T) { +func (s *DockerSuite) TestSaveXzAndLoadRepoStdout(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %v %v", out, err) + c.Fatalf("failed to create a container: %v %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -27,19 +28,19 @@ func TestSaveXzAndLoadRepoStdout(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("output should've been a container id: %v %v", cleanedContainerID, err) + c.Fatalf("output should've been a container id: %v %v", cleanedContainerID, err) } commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName) out, _, err = runCommandWithOutput(commitCmd) if err != nil { - t.Fatalf("failed to commit container: %v %v", out, err) + c.Fatalf("failed to commit container: %v %v", out, err) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) before, _, err := runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("the repo should exist before saving it: %v %v", before, err) + c.Fatalf("the repo should exist before saving it: %v %v", before, err) } repoTarball, _, err := runCommandPipelineWithOutput( @@ -47,7 +48,7 @@ func TestSaveXzAndLoadRepoStdout(t *testing.T) { exec.Command("xz", "-c"), exec.Command("gzip", "-c")) if err != nil { - t.Fatalf("failed to save repo: %v %v", out, err) + c.Fatalf("failed to save repo: %v %v", out, err) } deleteImages(repoName) @@ -55,26 +56,25 @@ func TestSaveXzAndLoadRepoStdout(t *testing.T) { loadCmd.Stdin = strings.NewReader(repoTarball) out, _, err = runCommandWithOutput(loadCmd) if err == nil { - t.Fatalf("expected error, but succeeded with no error and output: %v", out) + c.Fatalf("expected error, but succeeded with no error and output: %v", out) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) after, _, err := runCommandWithOutput(inspectCmd) if err == nil { - t.Fatalf("the repo should not exist: %v", after) + c.Fatalf("the repo should not exist: %v", after) } deleteImages(repoName) - logDone("load - save a repo with xz compression & load it using stdout") } // save a repo using xz+gz compression and try to load it using stdout -func TestSaveXzGzAndLoadRepoStdout(t *testing.T) { +func (s *DockerSuite) TestSaveXzGzAndLoadRepoStdout(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %v %v", out, err) + c.Fatalf("failed to create a container: %v %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -84,19 +84,19 @@ func TestSaveXzGzAndLoadRepoStdout(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) out, _, err = runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("output should've been a container id: %v %v", cleanedContainerID, err) + c.Fatalf("output should've been a container id: %v %v", cleanedContainerID, err) } commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName) out, _, err = runCommandWithOutput(commitCmd) if err != nil { - t.Fatalf("failed to commit container: %v %v", out, err) + c.Fatalf("failed to commit container: %v %v", out, err) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) before, _, err := runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("the repo should exist before saving it: %v %v", before, err) + c.Fatalf("the repo should exist before saving it: %v %v", before, err) } out, _, err = runCommandPipelineWithOutput( @@ -104,7 +104,7 @@ func TestSaveXzGzAndLoadRepoStdout(t *testing.T) { exec.Command("xz", "-c"), exec.Command("gzip", "-c")) if err != nil { - t.Fatalf("failed to save repo: %v %v", out, err) + c.Fatalf("failed to save repo: %v %v", out, err) } deleteImages(repoName) @@ -113,34 +113,33 @@ func TestSaveXzGzAndLoadRepoStdout(t *testing.T) { loadCmd.Stdin = strings.NewReader(out) out, _, err = runCommandWithOutput(loadCmd) if err == nil { - t.Fatalf("expected error, but succeeded with no error and output: %v", out) + c.Fatalf("expected error, but succeeded with no error and output: %v", out) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) after, _, err := runCommandWithOutput(inspectCmd) if err == nil { - t.Fatalf("the repo should not exist: %v", after) + c.Fatalf("the repo should not exist: %v", after) } deleteContainer(cleanedContainerID) deleteImages(repoName) - logDone("load - save a repo with xz+gz compression & load it using stdout") } -func TestSaveSingleTag(t *testing.T) { +func (s *DockerSuite) TestSaveSingleTag(c *check.C) { repoName := "foobar-save-single-tag-test" tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", fmt.Sprintf("%v:latest", repoName)) defer deleteImages(repoName) if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatalf("failed to tag repo: %s, %v", out, err) + c.Fatalf("failed to tag repo: %s, %v", out, err) } idCmd := exec.Command(dockerBinary, "images", "-q", "--no-trunc", repoName) out, _, err := runCommandWithOutput(idCmd) if err != nil { - t.Fatalf("failed to get repo ID: %s, %v", out, err) + c.Fatalf("failed to get repo ID: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) @@ -149,25 +148,24 @@ func TestSaveSingleTag(t *testing.T) { exec.Command("tar", "t"), exec.Command("grep", "-E", fmt.Sprintf("(^repositories$|%v)", cleanedImageID))) if err != nil { - t.Fatalf("failed to save repo with image ID and 'repositories' file: %s, %v", out, err) + c.Fatalf("failed to save repo with image ID and 'repositories' file: %s, %v", out, err) } - logDone("save - save a specific image:tag") } -func TestSaveImageId(t *testing.T) { +func (s *DockerSuite) TestSaveImageId(c *check.C) { repoName := "foobar-save-image-id-test" tagCmd := exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v:latest", repoName)) defer deleteImages(repoName) if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatalf("failed to tag repo: %s, %v", out, err) + c.Fatalf("failed to tag repo: %s, %v", out, err) } idLongCmd := exec.Command(dockerBinary, "images", "-q", "--no-trunc", repoName) out, _, err := runCommandWithOutput(idLongCmd) if err != nil { - t.Fatalf("failed to get repo ID: %s, %v", out, err) + c.Fatalf("failed to get repo ID: %s, %v", out, err) } cleanedLongImageID := strings.TrimSpace(out) @@ -175,7 +173,7 @@ func TestSaveImageId(t *testing.T) { idShortCmd := exec.Command(dockerBinary, "images", "-q", repoName) out, _, err = runCommandWithOutput(idShortCmd) if err != nil { - t.Fatalf("failed to get repo short ID: %s, %v", out, err) + c.Fatalf("failed to get repo short ID: %s, %v", out, err) } cleanedShortImageID := strings.TrimSpace(out) @@ -184,19 +182,19 @@ func TestSaveImageId(t *testing.T) { tarCmd := exec.Command("tar", "t") tarCmd.Stdin, err = saveCmd.StdoutPipe() if err != nil { - t.Fatalf("cannot set stdout pipe for tar: %v", err) + c.Fatalf("cannot set stdout pipe for tar: %v", err) } grepCmd := exec.Command("grep", cleanedLongImageID) grepCmd.Stdin, err = tarCmd.StdoutPipe() if err != nil { - t.Fatalf("cannot set stdout pipe for grep: %v", err) + c.Fatalf("cannot set stdout pipe for grep: %v", err) } if err = tarCmd.Start(); err != nil { - t.Fatalf("tar failed with error: %v", err) + c.Fatalf("tar failed with error: %v", err) } if err = saveCmd.Start(); err != nil { - t.Fatalf("docker save failed with error: %v", err) + c.Fatalf("docker save failed with error: %v", err) } defer saveCmd.Wait() defer tarCmd.Wait() @@ -204,18 +202,17 @@ func TestSaveImageId(t *testing.T) { out, _, err = runCommandWithOutput(grepCmd) if err != nil { - t.Fatalf("failed to save repo with image ID: %s, %v", out, err) + c.Fatalf("failed to save repo with image ID: %s, %v", out, err) } - logDone("save - save a image by ID") } // save a repo and try to load it using flags -func TestSaveAndLoadRepoFlags(t *testing.T) { +func (s *DockerSuite) TestSaveAndLoadRepoFlags(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %s, %v", out, err) + c.Fatalf("failed to create a container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -225,19 +222,19 @@ func TestSaveAndLoadRepoFlags(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("output should've been a container id: %s, %v", out, err) + c.Fatalf("output should've been a container id: %s, %v", out, err) } commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName) deleteImages(repoName) if out, _, err = runCommandWithOutput(commitCmd); err != nil { - t.Fatalf("failed to commit container: %s, %v", out, err) + c.Fatalf("failed to commit container: %s, %v", out, err) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) before, _, err := runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("the repo should exist before saving it: %s, %v", before, err) + c.Fatalf("the repo should exist before saving it: %s, %v", before, err) } @@ -245,29 +242,28 @@ func TestSaveAndLoadRepoFlags(t *testing.T) { exec.Command(dockerBinary, "save", repoName), exec.Command(dockerBinary, "load")) if err != nil { - t.Fatalf("failed to save and load repo: %s, %v", out, err) + c.Fatalf("failed to save and load repo: %s, %v", out, err) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) after, _, err := runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("the repo should exist after loading it: %s, %v", after, err) + c.Fatalf("the repo should exist after loading it: %s, %v", after, err) } if before != after { - t.Fatalf("inspect is not the same after a save / load") + c.Fatalf("inspect is not the same after a save / load") } - logDone("save - save a repo using -o && load a repo using -i") } -func TestSaveMultipleNames(t *testing.T) { +func (s *DockerSuite) TestSaveMultipleNames(c *check.C) { repoName := "foobar-save-multi-name-test" // Make one image tagCmd := exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v-one:latest", repoName)) if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatalf("failed to tag repo: %s, %v", out, err) + c.Fatalf("failed to tag repo: %s, %v", out, err) } defer deleteImages(repoName + "-one") @@ -275,7 +271,7 @@ func TestSaveMultipleNames(t *testing.T) { tagCmd = exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v-two:latest", repoName)) out, _, err := runCommandWithOutput(tagCmd) if err != nil { - t.Fatalf("failed to tag repo: %s, %v", out, err) + c.Fatalf("failed to tag repo: %s, %v", out, err) } defer deleteImages(repoName + "-two") @@ -285,13 +281,12 @@ func TestSaveMultipleNames(t *testing.T) { exec.Command("grep", "-q", "-E", "(-one|-two)"), ) if err != nil { - t.Fatalf("failed to save multiple repos: %s, %v", out, err) + c.Fatalf("failed to save multiple repos: %s, %v", out, err) } - logDone("save - save by multiple names") } -func TestSaveRepoWithMultipleImages(t *testing.T) { +func (s *DockerSuite) TestSaveRepoWithMultipleImages(c *check.C) { makeImage := func(from string, tag string) string { runCmd := exec.Command(dockerBinary, "run", "-d", from, "true") @@ -300,14 +295,14 @@ func TestSaveRepoWithMultipleImages(t *testing.T) { err error ) if out, _, err = runCommandWithOutput(runCmd); err != nil { - t.Fatalf("failed to create a container: %v %v", out, err) + c.Fatalf("failed to create a container: %v %v", out, err) } cleanedContainerID := strings.TrimSpace(out) defer deleteContainer(cleanedContainerID) commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, tag) if out, _, err = runCommandWithOutput(commitCmd); err != nil { - t.Fatalf("failed to commit container: %v %v", out, err) + c.Fatalf("failed to commit container: %v %v", out, err) } imageID := strings.TrimSpace(out) return imageID @@ -331,14 +326,14 @@ func TestSaveRepoWithMultipleImages(t *testing.T) { exec.Command("grep", "VERSION"), exec.Command("cut", "-d", "/", "-f1")) if err != nil { - t.Fatalf("failed to save multiple images: %s, %v", out, err) + c.Fatalf("failed to save multiple images: %s, %v", out, err) } actual := strings.Split(strings.TrimSpace(out), "\n") // make the list of expected layers out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "history", "-q", "--no-trunc", "busybox:latest")) if err != nil { - t.Fatalf("failed to get history: %s, %v", out, err) + c.Fatalf("failed to get history: %s, %v", out, err) } expected := append(strings.Split(strings.TrimSpace(out), "\n"), idFoo, idBar) @@ -346,21 +341,20 @@ func TestSaveRepoWithMultipleImages(t *testing.T) { sort.Strings(actual) sort.Strings(expected) if !reflect.DeepEqual(expected, actual) { - t.Fatalf("achive does not contains the right layers: got %v, expected %v", actual, expected) + c.Fatalf("achive does not contains the right layers: got %v, expected %v", actual, expected) } - logDone("save - save repository with multiple images") } // Issue #6722 #5892 ensure directories are included in changes -func TestSaveDirectoryPermissions(t *testing.T) { +func (s *DockerSuite) TestSaveDirectoryPermissions(c *check.C) { layerEntries := []string{"opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"} layerEntriesAUFS := []string{"./", ".wh..wh.aufs", ".wh..wh.orph/", ".wh..wh.plnk/", "opt/", "opt/a/", "opt/a/b/", "opt/a/b/c"} name := "save-directory-permissions" tmpDir, err := ioutil.TempDir("", "save-layers-with-directories") if err != nil { - t.Errorf("failed to create temporary directory: %s", err) + c.Errorf("failed to create temporary directory: %s", err) } extractionDirectory := filepath.Join(tmpDir, "image-extraction-dir") os.Mkdir(extractionDirectory, 0777) @@ -373,19 +367,19 @@ func TestSaveDirectoryPermissions(t *testing.T) { RUN touch /opt/a/b/c && chown user:user /opt/a/b/c`, true) if err != nil { - t.Fatal(err) + c.Fatal(err) } if out, _, err := runCommandPipelineWithOutput( exec.Command(dockerBinary, "save", name), exec.Command("tar", "-xf", "-", "-C", extractionDirectory), ); err != nil { - t.Errorf("failed to save and extract image: %s", out) + c.Errorf("failed to save and extract image: %s", out) } dirs, err := ioutil.ReadDir(extractionDirectory) if err != nil { - t.Errorf("failed to get a listing of the layer directories: %s", err) + c.Errorf("failed to get a listing of the layer directories: %s", err) } found := false @@ -396,7 +390,7 @@ func TestSaveDirectoryPermissions(t *testing.T) { f, err := os.Open(layerPath) if err != nil { - t.Fatalf("failed to open %s: %s", layerPath, err) + c.Fatalf("failed to open %s: %s", layerPath, err) } entries, err := ListTar(f) @@ -406,7 +400,7 @@ func TestSaveDirectoryPermissions(t *testing.T) { } } if err != nil { - t.Fatalf("encountered error while listing tar entries: %s", err) + c.Fatalf("encountered error while listing tar entries: %s", err) } if reflect.DeepEqual(entriesSansDev, layerEntries) || reflect.DeepEqual(entriesSansDev, layerEntriesAUFS) { @@ -417,8 +411,7 @@ func TestSaveDirectoryPermissions(t *testing.T) { } if !found { - t.Fatalf("failed to find the layer with the right content listing") + c.Fatalf("failed to find the layer with the right content listing") } - logDone("save - ensure directories exist in exported layers") } diff --git a/integration-cli/docker_cli_save_load_unix_test.go b/integration-cli/docker_cli_save_load_unix_test.go index 7eb948d7a..658666d6b 100644 --- a/integration-cli/docker_cli_save_load_unix_test.go +++ b/integration-cli/docker_cli_save_load_unix_test.go @@ -8,17 +8,17 @@ import ( "os" "os/exec" "strings" - "testing" "github.com/docker/docker/vendor/src/github.com/kr/pty" + "github.com/go-check/check" ) // save a repo and try to load it using stdout -func TestSaveAndLoadRepoStdout(t *testing.T) { +func (s *DockerSuite) TestSaveAndLoadRepoStdout(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to create a container: %s, %v", out, err) + c.Fatalf("failed to create a container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -27,25 +27,25 @@ func TestSaveAndLoadRepoStdout(t *testing.T) { inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID) if out, _, err = runCommandWithOutput(inspectCmd); err != nil { - t.Fatalf("output should've been a container id: %s, %v", out, err) + c.Fatalf("output should've been a container id: %s, %v", out, err) } commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName) if out, _, err = runCommandWithOutput(commitCmd); err != nil { - t.Fatalf("failed to commit container: %s, %v", out, err) + c.Fatalf("failed to commit container: %s, %v", out, err) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) before, _, err := runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("the repo should exist before saving it: %s, %v", before, err) + c.Fatalf("the repo should exist before saving it: %s, %v", before, err) } saveCmdTemplate := `%v save %v > /tmp/foobar-save-load-test.tar` saveCmdFinal := fmt.Sprintf(saveCmdTemplate, dockerBinary, repoName) saveCmd := exec.Command("bash", "-c", saveCmdFinal) if out, _, err = runCommandWithOutput(saveCmd); err != nil { - t.Fatalf("failed to save repo: %s, %v", out, err) + c.Fatalf("failed to save repo: %s, %v", out, err) } deleteImages(repoName) @@ -53,17 +53,17 @@ func TestSaveAndLoadRepoStdout(t *testing.T) { loadCmdFinal := `cat /tmp/foobar-save-load-test.tar | docker load` loadCmd := exec.Command("bash", "-c", loadCmdFinal) if out, _, err = runCommandWithOutput(loadCmd); err != nil { - t.Fatalf("failed to load repo: %s, %v", out, err) + c.Fatalf("failed to load repo: %s, %v", out, err) } inspectCmd = exec.Command(dockerBinary, "inspect", repoName) after, _, err := runCommandWithOutput(inspectCmd) if err != nil { - t.Fatalf("the repo should exist after loading it: %s %v", after, err) + c.Fatalf("the repo should exist after loading it: %s %v", after, err) } if before != after { - t.Fatalf("inspect is not the same after a save / load") + c.Fatalf("inspect is not the same after a save / load") } deleteContainer(cleanedContainerID) @@ -73,29 +73,28 @@ func TestSaveAndLoadRepoStdout(t *testing.T) { pty, tty, err := pty.Open() if err != nil { - t.Fatalf("Could not open pty: %v", err) + c.Fatalf("Could not open pty: %v", err) } cmd := exec.Command(dockerBinary, "save", repoName) cmd.Stdin = tty cmd.Stdout = tty cmd.Stderr = tty if err := cmd.Start(); err != nil { - t.Fatalf("start err: %v", err) + c.Fatalf("start err: %v", err) } if err := cmd.Wait(); err == nil { - t.Fatal("did not break writing to a TTY") + c.Fatal("did not break writing to a TTY") } buf := make([]byte, 1024) n, err := pty.Read(buf) if err != nil { - t.Fatal("could not read tty output") + c.Fatal("could not read tty output") } if !bytes.Contains(buf[:n], []byte("Cowardly refusing")) { - t.Fatal("help output is not being yielded", out) + c.Fatal("help output is not being yielded", out) } - logDone("save - save/load a repo using stdout") } diff --git a/integration-cli/docker_cli_search_test.go b/integration-cli/docker_cli_search_test.go index fcfd9eceb..c5ecdd03b 100644 --- a/integration-cli/docker_cli_search_test.go +++ b/integration-cli/docker_cli_search_test.go @@ -3,95 +3,93 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // search for repos named "registry" on the central registry -func TestSearchOnCentralRegistry(t *testing.T) { - testRequires(t, Network) +func (s *DockerSuite) TestSearchOnCentralRegistry(c *check.C) { + testRequires(c, Network) searchCmd := exec.Command(dockerBinary, "search", "busybox") out, exitCode, err := runCommandWithOutput(searchCmd) if err != nil || exitCode != 0 { - t.Fatalf("failed to search on the central registry: %s, %v", out, err) + c.Fatalf("failed to search on the central registry: %s, %v", out, err) } if !strings.Contains(out, "Busybox base image.") { - t.Fatal("couldn't find any repository named (or containing) 'Busybox base image.'") + c.Fatal("couldn't find any repository named (or containing) 'Busybox base image.'") } - logDone("search - search for repositories named (or containing) 'Busybox base image.'") } -func TestSearchStarsOptionWithWrongParameter(t *testing.T) { +func (s *DockerSuite) TestSearchStarsOptionWithWrongParameter(c *check.C) { searchCmdStarsChars := exec.Command(dockerBinary, "search", "--stars=a", "busybox") out, exitCode, err := runCommandWithOutput(searchCmdStarsChars) if err == nil || exitCode == 0 { - t.Fatalf("Should not get right information: %s, %v", out, err) + c.Fatalf("Should not get right information: %s, %v", out, err) } if !strings.Contains(out, "invalid value") { - t.Fatal("couldn't find the invalid value warning") + c.Fatal("couldn't find the invalid value warning") } searchCmdStarsNegativeNumber := exec.Command(dockerBinary, "search", "-s=-1", "busybox") out, exitCode, err = runCommandWithOutput(searchCmdStarsNegativeNumber) if err == nil || exitCode == 0 { - t.Fatalf("Should not get right information: %s, %v", out, err) + c.Fatalf("Should not get right information: %s, %v", out, err) } if !strings.Contains(out, "invalid value") { - t.Fatal("couldn't find the invalid value warning") + c.Fatal("couldn't find the invalid value warning") } - logDone("search - Verify search with wrong parameter.") } -func TestSearchCmdOptions(t *testing.T) { - testRequires(t, Network) +func (s *DockerSuite) TestSearchCmdOptions(c *check.C) { + testRequires(c, Network) searchCmdhelp := exec.Command(dockerBinary, "search", "--help") out, exitCode, err := runCommandWithOutput(searchCmdhelp) if err != nil || exitCode != 0 { - t.Fatalf("failed to get search help information: %s, %v", out, err) + c.Fatalf("failed to get search help information: %s, %v", out, err) } if !strings.Contains(out, "Usage: docker search [OPTIONS] TERM") { - t.Fatalf("failed to show docker search usage: %s, %v", out, err) + c.Fatalf("failed to show docker search usage: %s, %v", out, err) } searchCmd := exec.Command(dockerBinary, "search", "busybox") outSearchCmd, exitCode, err := runCommandWithOutput(searchCmd) if err != nil || exitCode != 0 { - t.Fatalf("failed to search on the central registry: %s, %v", outSearchCmd, err) + c.Fatalf("failed to search on the central registry: %s, %v", outSearchCmd, err) } searchCmdautomated := exec.Command(dockerBinary, "search", "--automated=true", "busybox") outSearchCmdautomated, exitCode, err := runCommandWithOutput(searchCmdautomated) //The busybox is a busybox base image, not an AUTOMATED image. if err != nil || exitCode != 0 { - t.Fatalf("failed to search with automated=true on the central registry: %s, %v", outSearchCmdautomated, err) + c.Fatalf("failed to search with automated=true on the central registry: %s, %v", outSearchCmdautomated, err) } outSearchCmdautomatedSlice := strings.Split(outSearchCmdautomated, "\n") for i := range outSearchCmdautomatedSlice { if strings.HasPrefix(outSearchCmdautomatedSlice[i], "busybox ") { - t.Fatalf("The busybox is not an AUTOMATED image: %s, %v", out, err) + c.Fatalf("The busybox is not an AUTOMATED image: %s, %v", out, err) } } searchCmdStars := exec.Command(dockerBinary, "search", "-s=2", "busybox") outSearchCmdStars, exitCode, err := runCommandWithOutput(searchCmdStars) if err != nil || exitCode != 0 { - t.Fatalf("failed to search with stars=2 on the central registry: %s, %v", outSearchCmdStars, err) + c.Fatalf("failed to search with stars=2 on the central registry: %s, %v", outSearchCmdStars, err) } if strings.Count(outSearchCmdStars, "[OK]") > strings.Count(outSearchCmd, "[OK]") { - t.Fatalf("The quantity of images with stars should be less than that of all images: %s, %v", outSearchCmdStars, err) + c.Fatalf("The quantity of images with stars should be less than that of all images: %s, %v", outSearchCmdStars, err) } searchCmdOptions := exec.Command(dockerBinary, "search", "--stars=2", "--automated=true", "--no-trunc=true", "busybox") out, exitCode, err = runCommandWithOutput(searchCmdOptions) if err != nil || exitCode != 0 { - t.Fatalf("failed to search with stars&automated&no-trunc options on the central registry: %s, %v", out, err) + c.Fatalf("failed to search with stars&automated&no-trunc options on the central registry: %s, %v", out, err) } - logDone("search - have a try for search options.") } diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index 342209dc4..52afac1af 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -4,20 +4,20 @@ import ( "fmt" "os/exec" "strings" - "testing" "time" + + "github.com/go-check/check" ) // Regression test for https://github.com/docker/docker/issues/7843 -func TestStartAttachReturnsOnError(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartAttachReturnsOnError(c *check.C) { - dockerCmd(t, "run", "-d", "--name", "test", "busybox") - dockerCmd(t, "wait", "test") + dockerCmd(c, "run", "-d", "--name", "test", "busybox") + dockerCmd(c, "wait", "test") // Expect this to fail because the above container is stopped, this is what we want if _, err := runCommand(exec.Command(dockerBinary, "run", "-d", "--name", "test2", "--link", "test:test", "busybox")); err == nil { - t.Fatal("Expected error but got none") + c.Fatal("Expected error but got none") } ch := make(chan struct{}) @@ -25,7 +25,7 @@ func TestStartAttachReturnsOnError(t *testing.T) { // Attempt to start attached to the container that won't start // This should return an error immediately since the container can't be started if _, err := runCommand(exec.Command(dockerBinary, "start", "-a", "test2")); err == nil { - t.Fatal("Expected error but got none") + c.Fatal("Expected error but got none") } close(ch) }() @@ -33,20 +33,18 @@ func TestStartAttachReturnsOnError(t *testing.T) { select { case <-ch: case <-time.After(time.Second): - t.Fatalf("Attach did not exit properly") + c.Fatalf("Attach did not exit properly") } - logDone("start - error on start with attach exits") } // gh#8555: Exit code should be passed through when using start -a -func TestStartAttachCorrectExitCode(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartAttachCorrectExitCode(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 2; exit 1") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } out = strings.TrimSpace(out) @@ -54,167 +52,157 @@ func TestStartAttachCorrectExitCode(t *testing.T) { // make sure the container has exited before trying the "start -a" waitCmd := exec.Command(dockerBinary, "wait", out) if _, _, err = runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("Failed to wait on container: %v", err) + c.Fatalf("Failed to wait on container: %v", err) } startCmd := exec.Command(dockerBinary, "start", "-a", out) startOut, exitCode, err := runCommandWithOutput(startCmd) if err != nil && !strings.Contains("exit status 1", fmt.Sprintf("%s", err)) { - t.Fatalf("start command failed unexpectedly with error: %v, output: %q", err, startOut) + c.Fatalf("start command failed unexpectedly with error: %v, output: %q", err, startOut) } if exitCode != 1 { - t.Fatalf("start -a did not respond with proper exit code: expected 1, got %d", exitCode) + c.Fatalf("start -a did not respond with proper exit code: expected 1, got %d", exitCode) } - logDone("start - correct exit code returned with -a") } -func TestStartAttachSilent(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartAttachSilent(c *check.C) { name := "teststartattachcorrectexitcode" runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "echo", "test") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { - t.Fatalf("failed to run container: %v, output: %q", err, out) + c.Fatalf("failed to run container: %v, output: %q", err, out) } // make sure the container has exited before trying the "start -a" waitCmd := exec.Command(dockerBinary, "wait", name) if _, _, err = runCommandWithOutput(waitCmd); err != nil { - t.Fatalf("wait command failed with error: %v", err) + c.Fatalf("wait command failed with error: %v", err) } startCmd := exec.Command(dockerBinary, "start", "-a", name) startOut, _, err := runCommandWithOutput(startCmd) if err != nil { - t.Fatalf("start command failed unexpectedly with error: %v, output: %q", err, startOut) + c.Fatalf("start command failed unexpectedly with error: %v, output: %q", err, startOut) } if expected := "test\n"; startOut != expected { - t.Fatalf("start -a produced unexpected output: expected %q, got %q", expected, startOut) + c.Fatalf("start -a produced unexpected output: expected %q, got %q", expected, startOut) } - logDone("start - don't echo container ID when attaching") } -func TestStartRecordError(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartRecordError(c *check.C) { // when container runs successfully, we should not have state.Error - dockerCmd(t, "run", "-d", "-p", "9999:9999", "--name", "test", "busybox", "top") + dockerCmd(c, "run", "-d", "-p", "9999:9999", "--name", "test", "busybox", "top") stateErr, err := inspectField("test", "State.Error") if err != nil { - t.Fatalf("Failed to inspect %q state's error, got error %q", "test", err) + c.Fatalf("Failed to inspect %q state's error, got error %q", "test", err) } if stateErr != "" { - t.Fatalf("Expected to not have state error but got state.Error(%q)", stateErr) + c.Fatalf("Expected to not have state error but got state.Error(%q)", stateErr) } // Expect this to fail and records error because of ports conflict out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "test2", "-p", "9999:9999", "busybox", "top")) if err == nil { - t.Fatalf("Expected error but got none, output %q", out) + c.Fatalf("Expected error but got none, output %q", out) } stateErr, err = inspectField("test2", "State.Error") if err != nil { - t.Fatalf("Failed to inspect %q state's error, got error %q", "test2", err) + c.Fatalf("Failed to inspect %q state's error, got error %q", "test2", err) } expected := "port is already allocated" if stateErr == "" || !strings.Contains(stateErr, expected) { - t.Fatalf("State.Error(%q) does not include %q", stateErr, expected) + c.Fatalf("State.Error(%q) does not include %q", stateErr, expected) } // Expect the conflict to be resolved when we stop the initial container - dockerCmd(t, "stop", "test") - dockerCmd(t, "start", "test2") + dockerCmd(c, "stop", "test") + dockerCmd(c, "start", "test2") stateErr, err = inspectField("test2", "State.Error") if err != nil { - t.Fatalf("Failed to inspect %q state's error, got error %q", "test", err) + c.Fatalf("Failed to inspect %q state's error, got error %q", "test", err) } if stateErr != "" { - t.Fatalf("Expected to not have state error but got state.Error(%q)", stateErr) + c.Fatalf("Expected to not have state error but got state.Error(%q)", stateErr) } - logDone("start - set state error when start is unsuccessful") } // gh#8726: a failed Start() breaks --volumes-from on subsequent Start()'s -func TestStartVolumesFromFailsCleanly(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartVolumesFromFailsCleanly(c *check.C) { // Create the first data volume - dockerCmd(t, "run", "-d", "--name", "data_before", "-v", "/foo", "busybox") + dockerCmd(c, "run", "-d", "--name", "data_before", "-v", "/foo", "busybox") // Expect this to fail because the data test after contaienr doesn't exist yet if _, err := runCommand(exec.Command(dockerBinary, "run", "-d", "--name", "consumer", "--volumes-from", "data_before", "--volumes-from", "data_after", "busybox")); err == nil { - t.Fatal("Expected error but got none") + c.Fatal("Expected error but got none") } // Create the second data volume - dockerCmd(t, "run", "-d", "--name", "data_after", "-v", "/bar", "busybox") + dockerCmd(c, "run", "-d", "--name", "data_after", "-v", "/bar", "busybox") // Now, all the volumes should be there - dockerCmd(t, "start", "consumer") + dockerCmd(c, "start", "consumer") // Check that we have the volumes we want - out, _ := dockerCmd(t, "inspect", "--format='{{ len .Volumes }}'", "consumer") + out, _ := dockerCmd(c, "inspect", "--format='{{ len .Volumes }}'", "consumer") nVolumes := strings.Trim(out, " \r\n'") if nVolumes != "2" { - t.Fatalf("Missing volumes: expected 2, got %s", nVolumes) + c.Fatalf("Missing volumes: expected 2, got %s", nVolumes) } - logDone("start - missing containers in --volumes-from did not affect subsequent runs") } -func TestStartPausedContainer(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartPausedContainer(c *check.C) { defer unpauseAllContainers() runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "top") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "pause", "testing") if out, _, err := runCommandWithOutput(runCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } runCmd = exec.Command(dockerBinary, "start", "testing") if out, _, err := runCommandWithOutput(runCmd); err == nil || !strings.Contains(out, "Cannot start a paused container, try unpause instead.") { - t.Fatalf("an error should have been shown that you cannot start paused container: %s\n%v", out, err) + c.Fatalf("an error should have been shown that you cannot start paused container: %s\n%v", out, err) } - logDone("start - error should show if trying to start paused container") } -func TestStartMultipleContainers(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestStartMultipleContainers(c *check.C) { // run a container named 'parent' and create two container link to `parent` cmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "busybox", "top") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } for _, container := range []string{"child_first", "child_second"} { cmd = exec.Command(dockerBinary, "create", "--name", container, "--link", "parent:parent", "busybox", "top") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } } // stop 'parent' container cmd = exec.Command(dockerBinary, "stop", "parent") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", "parent") out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.Trim(out, "\r\n") if out != "false" { - t.Fatal("Container should be stopped") + c.Fatal("Container should be stopped") } // start all the three containers, container `child_first` start first which should be faild @@ -222,35 +210,33 @@ func TestStartMultipleContainers(t *testing.T) { cmd = exec.Command(dockerBinary, "start", "child_first", "parent", "child_second") out, _, err = runCommandWithOutput(cmd) if !strings.Contains(out, "Cannot start container child_first") || err == nil { - t.Fatal("Expected error but got none") + c.Fatal("Expected error but got none") } for container, expected := range map[string]string{"parent": "true", "child_first": "false", "child_second": "true"} { cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", container) out, _, err = runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.Trim(out, "\r\n") if out != expected { - t.Fatal("Container running state wrong") + c.Fatal("Container running state wrong") } } - logDone("start - start multiple containers continue on one failed") } -func TestStartAttachMultipleContainers(t *testing.T) { +func (s *DockerSuite) TestStartAttachMultipleContainers(c *check.C) { var cmd *exec.Cmd - defer deleteAllContainers() // run multiple containers to test for _, container := range []string{"test1", "test2", "test3"} { cmd = exec.Command(dockerBinary, "run", "-d", "--name", container, "busybox", "top") if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } } @@ -258,7 +244,7 @@ func TestStartAttachMultipleContainers(t *testing.T) { for _, container := range []string{"test1", "test2", "test3"} { cmd = exec.Command(dockerBinary, "stop", container) if out, _, err := runCommandWithOutput(cmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } } @@ -267,7 +253,7 @@ func TestStartAttachMultipleContainers(t *testing.T) { cmd = exec.Command(dockerBinary, "start", option, "test1", "test2", "test3") out, _, err := runCommandWithOutput(cmd) if !strings.Contains(out, "You cannot start and attach multiple containers at once.") || err == nil { - t.Fatal("Expected error but got none") + c.Fatal("Expected error but got none") } } @@ -276,13 +262,12 @@ func TestStartAttachMultipleContainers(t *testing.T) { cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", container) out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } out = strings.Trim(out, "\r\n") if out != expected { - t.Fatal("Container running state wrong") + c.Fatal("Container running state wrong") } } - logDone("start - error on start and attach multiple containers at once") } diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go index 8a5d32271..1b4d36b7b 100644 --- a/integration-cli/docker_cli_tag_test.go +++ b/integration-cli/docker_cli_tag_test.go @@ -3,48 +3,46 @@ package main import ( "os/exec" "strings" - "testing" "github.com/docker/docker/pkg/stringutils" + "github.com/go-check/check" ) // tagging a named image in a new unprefixed repo should work -func TestTagUnprefixedRepoByName(t *testing.T) { +func (s *DockerSuite) TestTagUnprefixedRepoByName(c *check.C) { if err := pullImageIfNotExist("busybox:latest"); err != nil { - t.Fatal("couldn't find the busybox:latest image locally and failed to pull it") + c.Fatal("couldn't find the busybox:latest image locally and failed to pull it") } tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", "testfoobarbaz") if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatal(out, err) + c.Fatal(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) { +func (s *DockerSuite) TestTagUnprefixedRepoByID(c *check.C) { getIDCmd := exec.Command(dockerBinary, "inspect", "-f", "{{.Id}}", "busybox") out, _, err := runCommandWithOutput(getIDCmd) if err != nil { - t.Fatalf("failed to get the image ID of busybox: %s, %v", out, err) + c.Fatalf("failed to get the image ID of busybox: %s, %v", out, err) } cleanedImageID := strings.TrimSpace(out) tagCmd := exec.Command(dockerBinary, "tag", cleanedImageID, "testfoobarbaz") if out, _, err = runCommandWithOutput(tagCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } deleteImages("testfoobarbaz") - logDone("tag - busybox's image ID -> testfoobarbaz") } // ensure we don't allow the use of invalid repository names; these tag operations should fail -func TestTagInvalidUnprefixedRepo(t *testing.T) { +func (s *DockerSuite) TestTagInvalidUnprefixedRepo(c *check.C) { invalidRepos := []string{"fo$z$", "Foo@3cc", "Foo$3", "Foo*3", "Fo^3", "Foo!3", "F)xcz(", "fo%asd"} @@ -52,14 +50,13 @@ func TestTagInvalidUnprefixedRepo(t *testing.T) { tagCmd := exec.Command(dockerBinary, "tag", "busybox", repo) _, _, err := runCommandWithOutput(tagCmd) if err == nil { - t.Fatalf("tag busybox %v should have failed", repo) + c.Fatalf("tag busybox %v should have failed", repo) } } - logDone("tag - busybox invalid repo names --> must not work") } // ensure we don't allow the use of invalid tags; these tag operations should fail -func TestTagInvalidPrefixedRepo(t *testing.T) { +func (s *DockerSuite) TestTagInvalidPrefixedRepo(c *check.C) { longTag := stringutils.GenerateRandomAlphaOnlyString(121) invalidTags := []string{"repo:fo$z$", "repo:Foo@3cc", "repo:Foo$3", "repo:Foo*3", "repo:Fo^3", "repo:Foo!3", "repo:%goodbye", "repo:#hashtagit", "repo:F)xcz(", "repo:-foo", "repo:..", longTag} @@ -68,16 +65,15 @@ func TestTagInvalidPrefixedRepo(t *testing.T) { tagCmd := exec.Command(dockerBinary, "tag", "busybox", repotag) _, _, err := runCommandWithOutput(tagCmd) if err == nil { - t.Fatalf("tag busybox %v should have failed", repotag) + c.Fatalf("tag busybox %v should have failed", repotag) } } - logDone("tag - busybox with invalid repo:tagnames --> must not work") } // ensure we allow the use of valid tags -func TestTagValidPrefixedRepo(t *testing.T) { +func (s *DockerSuite) TestTagValidPrefixedRepo(c *check.C) { if err := pullImageIfNotExist("busybox:latest"); err != nil { - t.Fatal("couldn't find the busybox:latest image locally and failed to pull it") + c.Fatal("couldn't find the busybox:latest image locally and failed to pull it") } validRepos := []string{"fooo/bar", "fooaa/test", "foooo:t"} @@ -86,56 +82,53 @@ func TestTagValidPrefixedRepo(t *testing.T) { tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", repo) _, _, err := runCommandWithOutput(tagCmd) if err != nil { - t.Errorf("tag busybox %v should have worked: %s", repo, err) + c.Errorf("tag busybox %v should have worked: %s", repo, err) continue } deleteImages(repo) } - logDone("tag - tag valid prefixed repo") } // tag an image with an existed tag name without -f option should fail -func TestTagExistedNameWithoutForce(t *testing.T) { +func (s *DockerSuite) TestTagExistedNameWithoutForce(c *check.C) { if err := pullImageIfNotExist("busybox:latest"); err != nil { - t.Fatal("couldn't find the busybox:latest image locally and failed to pull it") + c.Fatal("couldn't find the busybox:latest image locally and failed to pull it") } tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", "busybox:test") if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } tagCmd = exec.Command(dockerBinary, "tag", "busybox:latest", "busybox:test") out, _, err := runCommandWithOutput(tagCmd) if err == nil || !strings.Contains(out, "Conflict: Tag test is already set to image") { - t.Fatal("tag busybox busybox:test should have failed,because busybox:test is existed") + c.Fatal("tag busybox busybox:test should have failed,because busybox:test is existed") } deleteImages("busybox:test") - logDone("tag - busybox with an existed tag name without -f option --> must not work") } // tag an image with an existed tag name with -f option should work -func TestTagExistedNameWithForce(t *testing.T) { +func (s *DockerSuite) TestTagExistedNameWithForce(c *check.C) { if err := pullImageIfNotExist("busybox:latest"); err != nil { - t.Fatal("couldn't find the busybox:latest image locally and failed to pull it") + c.Fatal("couldn't find the busybox:latest image locally and failed to pull it") } tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", "busybox:test") if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } tagCmd = exec.Command(dockerBinary, "tag", "-f", "busybox:latest", "busybox:test") if out, _, err := runCommandWithOutput(tagCmd); err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } deleteImages("busybox:test") - logDone("tag - busybox with an existed tag name with -f option work") } // ensure tagging using official names works // ensure all tags result in the same name -func TestTagOfficialNames(t *testing.T) { +func (s *DockerSuite) TestTagOfficialNames(c *check.C) { names := []string{ "docker.io/busybox", "index.docker.io/busybox", @@ -148,7 +141,7 @@ func TestTagOfficialNames(t *testing.T) { tagCmd := exec.Command(dockerBinary, "tag", "-f", "busybox:latest", name+":latest") out, exitCode, err := runCommandWithOutput(tagCmd) if err != nil || exitCode != 0 { - t.Errorf("tag busybox %v should have worked: %s, %s", name, err, out) + c.Errorf("tag busybox %v should have worked: %s, %s", name, err, out) continue } @@ -156,9 +149,9 @@ func TestTagOfficialNames(t *testing.T) { imagesCmd := exec.Command(dockerBinary, "images") out, _, err = runCommandWithOutput(imagesCmd) if err != nil { - t.Errorf("listing images failed with errors: %v, %s", err, out) + c.Errorf("listing images failed with errors: %v, %s", err, out) } else if strings.Contains(out, name) { - t.Errorf("images should not have listed '%s'", name) + c.Errorf("images should not have listed '%s'", name) deleteImages(name + ":latest") } } @@ -167,10 +160,9 @@ func TestTagOfficialNames(t *testing.T) { tagCmd := exec.Command(dockerBinary, "tag", "-f", name+":latest", "fooo/bar:latest") _, exitCode, err := runCommandWithOutput(tagCmd) if err != nil || exitCode != 0 { - t.Errorf("tag %v fooo/bar should have worked: %s", name, err) + c.Errorf("tag %v fooo/bar should have worked: %s", name, err) continue } deleteImages("fooo/bar:latest") } - logDone("tag - tag official names") } diff --git a/integration-cli/docker_cli_top_test.go b/integration-cli/docker_cli_top_test.go index b5dca0be9..7e75a38d5 100644 --- a/integration-cli/docker_cli_top_test.go +++ b/integration-cli/docker_cli_top_test.go @@ -3,14 +3,15 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) -func TestTopMultipleArgs(t *testing.T) { +func (s *DockerSuite) TestTopMultipleArgs(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to start the container: %s, %v", out, err) + c.Fatalf("failed to start the container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -19,21 +20,20 @@ func TestTopMultipleArgs(t *testing.T) { topCmd := exec.Command(dockerBinary, "top", cleanedContainerID, "-o", "pid") out, _, err = runCommandWithOutput(topCmd) if err != nil { - t.Fatalf("failed to run top: %s, %v", out, err) + c.Fatalf("failed to run top: %s, %v", out, err) } if !strings.Contains(out, "PID") { - t.Fatalf("did not see PID after top -o pid: %s", out) + c.Fatalf("did not see PID after top -o pid: %s", out) } - logDone("top - multiple arguments") } -func TestTopNonPrivileged(t *testing.T) { +func (s *DockerSuite) TestTopNonPrivileged(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to start the container: %s, %v", out, err) + c.Fatalf("failed to start the container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -41,38 +41,37 @@ func TestTopNonPrivileged(t *testing.T) { topCmd := exec.Command(dockerBinary, "top", cleanedContainerID) out1, _, err := runCommandWithOutput(topCmd) if err != nil { - t.Fatalf("failed to run top: %s, %v", out1, err) + c.Fatalf("failed to run top: %s, %v", out1, err) } topCmd = exec.Command(dockerBinary, "top", cleanedContainerID) out2, _, err := runCommandWithOutput(topCmd) if err != nil { - t.Fatalf("failed to run top: %s, %v", out2, err) + c.Fatalf("failed to run top: %s, %v", out2, err) } killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) if out, _, err = runCommandWithOutput(killCmd); err != nil { - t.Fatalf("failed to kill container: %s, %v", out, err) + c.Fatalf("failed to kill container: %s, %v", out, err) } deleteContainer(cleanedContainerID) if !strings.Contains(out1, "top") && !strings.Contains(out2, "top") { - t.Fatal("top should've listed `top` in the process list, but failed twice") + c.Fatal("top should've listed `top` in the process list, but failed twice") } else if !strings.Contains(out1, "top") { - t.Fatal("top should've listed `top` in the process list, but failed the first time") + c.Fatal("top should've listed `top` in the process list, but failed the first time") } else if !strings.Contains(out2, "top") { - t.Fatal("top should've listed `top` in the process list, but failed the second itime") + c.Fatal("top should've listed `top` in the process list, but failed the second itime") } - logDone("top - top process should be listed in non privileged mode") } -func TestTopPrivileged(t *testing.T) { +func (s *DockerSuite) TestTopPrivileged(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "--privileged", "-i", "-d", "busybox", "top") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatalf("failed to start the container: %s, %v", out, err) + c.Fatalf("failed to start the container: %s, %v", out, err) } cleanedContainerID := strings.TrimSpace(out) @@ -80,29 +79,28 @@ func TestTopPrivileged(t *testing.T) { topCmd := exec.Command(dockerBinary, "top", cleanedContainerID) out1, _, err := runCommandWithOutput(topCmd) if err != nil { - t.Fatalf("failed to run top: %s, %v", out1, err) + c.Fatalf("failed to run top: %s, %v", out1, err) } topCmd = exec.Command(dockerBinary, "top", cleanedContainerID) out2, _, err := runCommandWithOutput(topCmd) if err != nil { - t.Fatalf("failed to run top: %s, %v", out2, err) + c.Fatalf("failed to run top: %s, %v", out2, err) } killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) if out, _, err = runCommandWithOutput(killCmd); err != nil { - t.Fatalf("failed to kill container: %s, %v", out, err) + c.Fatalf("failed to kill container: %s, %v", out, err) } deleteContainer(cleanedContainerID) if !strings.Contains(out1, "top") && !strings.Contains(out2, "top") { - t.Fatal("top should've listed `top` in the process list, but failed twice") + c.Fatal("top should've listed `top` in the process list, but failed twice") } else if !strings.Contains(out1, "top") { - t.Fatal("top should've listed `top` in the process list, but failed the first time") + c.Fatal("top should've listed `top` in the process list, but failed the first time") } else if !strings.Contains(out2, "top") { - t.Fatal("top should've listed `top` in the process list, but failed the second itime") + c.Fatal("top should've listed `top` in the process list, but failed the second itime") } - logDone("top - top process should be listed in privileged mode") } diff --git a/integration-cli/docker_cli_version_test.go b/integration-cli/docker_cli_version_test.go index ceaeba8e2..3616da988 100644 --- a/integration-cli/docker_cli_version_test.go +++ b/integration-cli/docker_cli_version_test.go @@ -3,15 +3,16 @@ package main import ( "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) // ensure docker version works -func TestVersionEnsureSucceeds(t *testing.T) { +func (s *DockerSuite) TestVersionEnsureSucceeds(c *check.C) { versionCmd := exec.Command(dockerBinary, "version") out, _, err := runCommandWithOutput(versionCmd) if err != nil { - t.Fatalf("failed to execute docker version: %s, %v", out, err) + c.Fatalf("failed to execute docker version: %s, %v", out, err) } stringsToCheck := []string{ @@ -29,9 +30,8 @@ func TestVersionEnsureSucceeds(t *testing.T) { for _, linePrefix := range stringsToCheck { if !strings.Contains(out, linePrefix) { - t.Errorf("couldn't find string %v in output", linePrefix) + c.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_cli_wait_test.go b/integration-cli/docker_cli_wait_test.go index cc0e778ea..09d0272bc 100644 --- a/integration-cli/docker_cli_wait_test.go +++ b/integration-cli/docker_cli_wait_test.go @@ -3,18 +3,18 @@ package main import ( "os/exec" "strings" - "testing" "time" + + "github.com/go-check/check" ) // non-blocking wait with 0 exit code -func TestWaitNonBlockedExitZero(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestWaitNonBlockedExitZero(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerID := strings.TrimSpace(out) @@ -23,13 +23,13 @@ func TestWaitNonBlockedExitZero(t *testing.T) { runCmd = exec.Command(dockerBinary, "inspect", "--format='{{.State.Running}}'", containerID) status, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(status, err) + c.Fatal(status, err) } status = strings.TrimSpace(status) time.Sleep(time.Second) if i >= 60 { - t.Fatal("Container should have stopped by now") + c.Fatal("Container should have stopped by now") } } @@ -37,20 +37,18 @@ func TestWaitNonBlockedExitZero(t *testing.T) { out, _, err = runCommandWithOutput(runCmd) if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + c.Fatal("failed to set up container", out, err) } - logDone("wait - non-blocking wait with 0 exit code") } // blocking wait with 0 exit code -func TestWaitBlockedExitZero(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestWaitBlockedExitZero(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerID := strings.TrimSpace(out) @@ -58,20 +56,18 @@ func TestWaitBlockedExitZero(t *testing.T) { out, _, err = runCommandWithOutput(runCmd) if err != nil || strings.TrimSpace(out) != "0" { - t.Fatal("failed to set up container", out, err) + c.Fatal("failed to set up container", out, err) } - logDone("wait - blocking wait with 0 exit code") } // non-blocking wait with random exit code -func TestWaitNonBlockedExitRandom(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestWaitNonBlockedExitRandom(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "exit 99") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerID := strings.TrimSpace(out) @@ -80,13 +76,13 @@ func TestWaitNonBlockedExitRandom(t *testing.T) { runCmd = exec.Command(dockerBinary, "inspect", "--format='{{.State.Running}}'", containerID) status, _, err = runCommandWithOutput(runCmd) if err != nil { - t.Fatal(status, err) + c.Fatal(status, err) } status = strings.TrimSpace(status) time.Sleep(time.Second) if i >= 60 { - t.Fatal("Container should have stopped by now") + c.Fatal("Container should have stopped by now") } } @@ -94,20 +90,18 @@ func TestWaitNonBlockedExitRandom(t *testing.T) { out, _, err = runCommandWithOutput(runCmd) if err != nil || strings.TrimSpace(out) != "99" { - t.Fatal("failed to set up container", out, err) + c.Fatal("failed to set up container", out, err) } - logDone("wait - non-blocking wait with random exit code") } // blocking wait with random exit code -func TestWaitBlockedExitRandom(t *testing.T) { - defer deleteAllContainers() +func (s *DockerSuite) TestWaitBlockedExitRandom(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10; exit 99") out, _, err := runCommandWithOutput(runCmd) if err != nil { - t.Fatal(out, err) + c.Fatal(out, err) } containerID := strings.TrimSpace(out) @@ -115,8 +109,7 @@ func TestWaitBlockedExitRandom(t *testing.T) { out, _, err = runCommandWithOutput(runCmd) if err != nil || strings.TrimSpace(out) != "99" { - t.Fatal("failed to set up container", out, err) + c.Fatal("failed to set up container", out, err) } - logDone("wait - blocking wait with random exit code") } diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 4e622b84c..855352973 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -18,17 +18,17 @@ import ( "path/filepath" "strconv" "strings" - "testing" "time" "github.com/docker/docker/api" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stringutils" + "github.com/go-check/check" ) // Daemon represents a Docker daemon for the testing framework. type Daemon struct { - t *testing.T + c *check.C logFile *os.File folder string stdin io.WriteCloser @@ -42,24 +42,24 @@ type Daemon struct { // NewDaemon returns a Daemon instance to be used for testing. // This will create a directory such as daemon123456789 in the folder specified by $DEST. // The daemon will not automatically start. -func NewDaemon(t *testing.T) *Daemon { +func NewDaemon(c *check.C) *Daemon { dest := os.Getenv("DEST") if dest == "" { - t.Fatal("Please set the DEST environment variable") + c.Fatal("Please set the DEST environment variable") } dir := filepath.Join(dest, fmt.Sprintf("daemon%d", time.Now().UnixNano()%100000000)) daemonFolder, err := filepath.Abs(dir) if err != nil { - t.Fatalf("Could not make %q an absolute path: %v", dir, err) + c.Fatalf("Could not make %q an absolute path: %v", dir, err) } if err := os.MkdirAll(filepath.Join(daemonFolder, "graph"), 0600); err != nil { - t.Fatalf("Could not create %s/graph directory", daemonFolder) + c.Fatalf("Could not create %s/graph directory", daemonFolder) } return &Daemon{ - t: t, + c: c, folder: daemonFolder, storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"), execDriver: os.Getenv("DOCKER_EXECDRIVER"), @@ -71,7 +71,7 @@ func NewDaemon(t *testing.T) *Daemon { func (d *Daemon) Start(arg ...string) error { dockerBinary, err := exec.LookPath(dockerBinary) if err != nil { - d.t.Fatalf("could not find docker binary in $PATH: %v", err) + d.c.Fatalf("could not find docker binary in $PATH: %v", err) } args := []string{ @@ -105,7 +105,7 @@ func (d *Daemon) Start(arg ...string) error { d.logFile, err = os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) if err != nil { - d.t.Fatalf("Could not create %s/docker.log: %v", d.folder, err) + d.c.Fatalf("Could not create %s/docker.log: %v", d.folder, err) } d.cmd.Stdout = d.logFile @@ -119,7 +119,7 @@ func (d *Daemon) Start(arg ...string) error { go func() { wait <- d.cmd.Wait() - d.t.Log("exiting daemon") + d.c.Log("exiting daemon") close(wait) }() @@ -129,7 +129,7 @@ func (d *Daemon) Start(arg ...string) error { // make sure daemon is ready to receive requests startTime := time.Now().Unix() for { - d.t.Log("waiting for daemon to start") + d.c.Log("waiting for daemon to start") if time.Now().Unix()-startTime > 5 { // After 5 seconds, give up return errors.New("Daemon exited and never started") @@ -148,7 +148,7 @@ func (d *Daemon) Start(arg ...string) error { req, err := http.NewRequest("GET", "/_ping", nil) if err != nil { - d.t.Fatalf("could not create new request: %v", err) + d.c.Fatalf("could not create new request: %v", err) } resp, err := client.Do(req) @@ -156,10 +156,10 @@ func (d *Daemon) Start(arg ...string) error { continue } if resp.StatusCode != http.StatusOK { - d.t.Logf("received status != 200 OK: %s", resp.Status) + d.c.Logf("received status != 200 OK: %s", resp.Status) } - d.t.Log("daemon started") + d.c.Log("daemon started") return nil } } @@ -186,7 +186,7 @@ func (d *Daemon) StartWithBusybox(arg ...string) error { return fmt.Errorf("could not load busybox image: %v", err) } if err := os.Remove(bb); err != nil { - d.t.Logf("Could not remove %s: %v", bb, err) + d.c.Logf("Could not remove %s: %v", bb, err) } return nil } @@ -218,7 +218,7 @@ out1: return err case <-time.After(15 * time.Second): // time for stopping jobs and run onShutdown hooks - d.t.Log("timeout") + d.c.Log("timeout") break out1 } } @@ -231,10 +231,10 @@ out2: case <-tick: i++ if i > 4 { - d.t.Logf("tried to interrupt daemon for %d times, now try to kill it", i) + d.c.Logf("tried to interrupt daemon for %d times, now try to kill it", i) break out2 } - d.t.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid) + d.c.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid) if err := d.cmd.Process.Signal(os.Interrupt); err != nil { return fmt.Errorf("could not send signal: %v", err) } @@ -242,7 +242,7 @@ out2: } if err := d.cmd.Process.Kill(); err != nil { - d.t.Logf("Could not kill daemon: %v", err) + d.c.Logf("Could not kill daemon: %v", err) return err } @@ -478,10 +478,10 @@ func pullImageIfNotExist(image string) (err error) { return } -func dockerCmd(t *testing.T, args ...string) (string, int) { +func dockerCmd(c *check.C, args ...string) (string, int) { out, status, err := runCommandWithOutput(exec.Command(dockerBinary, args...)) if err != nil { - t.Fatalf("%q failed with errors: %s, %v", strings.Join(args, " "), out, err) + c.Fatalf("%q failed with errors: %s, %v", strings.Join(args, " "), out, err) } return out, status } @@ -496,7 +496,7 @@ func dockerCmdWithTimeout(timeout time.Duration, args ...string) (string, int, e } // execute a docker command in a directory -func dockerCmdInDir(t *testing.T, path string, args ...string) (string, int, error) { +func dockerCmdInDir(c *check.C, path string, args ...string) (string, int, error) { dockerCommand := exec.Command(dockerBinary, args...) dockerCommand.Dir = path out, status, err := runCommandWithOutput(dockerCommand) @@ -517,11 +517,11 @@ func dockerCmdInDirWithTimeout(timeout time.Duration, path string, args ...strin return out, status, err } -func findContainerIP(t *testing.T, id string) string { +func findContainerIP(c *check.C, id string) string { cmd := exec.Command(dockerBinary, "inspect", "--format='{{ .NetworkSettings.IPAddress }}'", id) out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatal(err, out) + c.Fatal(err, out) } return strings.Trim(out, " \r\n'") @@ -779,12 +779,12 @@ func getIDByName(name string) (string, error) { // getContainerState returns the exit code of the container // and true if it's running // the exit code should be ignored if it's running -func getContainerState(t *testing.T, id string) (int, bool, error) { +func getContainerState(c *check.C, id string) (int, bool, error) { var ( exitStatus int running bool ) - out, exitCode := dockerCmd(t, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id) + out, exitCode := dockerCmd(c, "inspect", "--format={{.State.Running}} {{.State.ExitCode}}", id) if exitCode != 0 { return 0, false, fmt.Errorf("%q doesn't exist: %s", id, out) } @@ -985,28 +985,28 @@ func fakeGIT(name string, files map[string]string, enforceLocalServer bool) (*Fa // Write `content` to the file at path `dst`, creating it if necessary, // as well as any missing directories. // The file is truncated if it already exists. -// Call t.Fatal() at the first error. -func writeFile(dst, content string, t *testing.T) { +// Call c.Fatal() at the first error. +func writeFile(dst, content string, c *check.C) { // Create subdirectories if necessary if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) { - t.Fatal(err) + c.Fatal(err) } f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Write content (truncate if it exists) if _, err := io.Copy(f, strings.NewReader(content)); err != nil { - t.Fatal(err) + c.Fatal(err) } } // Return the contents of file at path `src`. -// Call t.Fatal() at the first error (including if the file doesn't exist) -func readFile(src string, t *testing.T) (content string) { +// Call c.Fatal() at the first error (including if the file doesn't exist) +func readFile(src string, c *check.C) (content string) { data, err := ioutil.ReadFile(src) if err != nil { - t.Fatal(err) + c.Fatal(err) } return string(data) @@ -1051,14 +1051,14 @@ func readContainerFileWithExec(containerId, filename string) ([]byte, error) { } // daemonTime provides the current time on the daemon host -func daemonTime(t *testing.T) time.Time { +func daemonTime(c *check.C) time.Time { if isLocalDaemon { return time.Now() } _, body, err := sockRequest("GET", "/info", nil) if err != nil { - t.Fatalf("daemonTime: failed to get /info: %v", err) + c.Fatalf("daemonTime: failed to get /info: %v", err) } type infoJSON struct { @@ -1066,21 +1066,21 @@ func daemonTime(t *testing.T) time.Time { } var info infoJSON if err = json.Unmarshal(body, &info); err != nil { - t.Fatalf("unable to unmarshal /info response: %v", err) + c.Fatalf("unable to unmarshal /info response: %v", err) } dt, err := time.Parse(time.RFC3339Nano, info.SystemTime) if err != nil { - t.Fatal(err) + c.Fatal(err) } return dt } -func setupRegistry(t *testing.T) func() { - testRequires(t, RegistryHosting) - reg, err := newTestRegistryV2(t) +func setupRegistry(c *check.C) func() { + testRequires(c, RegistryHosting) + reg, err := newTestRegistryV2(c) if err != nil { - t.Fatal(err) + c.Fatal(err) } // Wait for registry to be ready to serve requests. @@ -1092,7 +1092,7 @@ func setupRegistry(t *testing.T) func() { } if err != nil { - t.Fatal("Timeout waiting for test registry to become available") + c.Fatal("Timeout waiting for test registry to become available") } return func() { reg.Close() } diff --git a/integration-cli/registry.go b/integration-cli/registry.go index 8290e710f..2801eacb5 100644 --- a/integration-cli/registry.go +++ b/integration-cli/registry.go @@ -7,7 +7,8 @@ import ( "os" "os/exec" "path/filepath" - "testing" + + "github.com/go-check/check" ) const v2binary = "registry-v2" @@ -17,7 +18,7 @@ type testRegistryV2 struct { dir string } -func newTestRegistryV2(t *testing.T) (*testRegistryV2, error) { +func newTestRegistryV2(c *check.C) (*testRegistryV2, error) { template := `version: 0.1 loglevel: debug storage: @@ -43,7 +44,7 @@ http: if err := cmd.Start(); err != nil { os.RemoveAll(tmp) if os.IsNotExist(err) { - t.Skip() + c.Skip(err.Error()) } return nil, err } diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go index 9769e2d3a..7499fc506 100644 --- a/integration-cli/requirements.go +++ b/integration-cli/requirements.go @@ -7,7 +7,8 @@ import ( "net/http" "os/exec" "strings" - "testing" + + "github.com/go-check/check" ) type TestCondition func() bool @@ -92,10 +93,10 @@ var ( // testRequires checks if the environment satisfies the requirements // for the test to run or skips the tests. -func testRequires(t *testing.T, requirements ...TestRequirement) { +func testRequires(c *check.C, requirements ...TestRequirement) { for _, r := range requirements { if !r.Condition() { - t.Skip(r.SkipMessage) + c.Skip(r.SkipMessage) } } } diff --git a/integration-cli/utils.go b/integration-cli/utils.go index c3a84bbc5..4ca7158ae 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -168,10 +168,6 @@ func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in return runCommandWithOutput(cmds[len(cmds)-1]) } -func logDone(message string) { - fmt.Printf("[PASSED]: %.69s\n", message) -} - func unmarshalJSON(data []byte, result interface{}) error { err := json.Unmarshal(data, result) if err != nil { From ba0017595ed9ac30273832b93365b0cb1cf60c05 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 21 Apr 2015 19:59:45 +0200 Subject: [PATCH 549/999] Remove job image_tarlayer Signed-off-by: Antonio Murdaca --- graph/export.go | 4 +--- graph/service.go | 25 ++++++++++--------------- 2 files changed, 11 insertions(+), 18 deletions(-) diff --git a/graph/export.go b/graph/export.go index f689ba10e..2450d5027 100644 --- a/graph/export.go +++ b/graph/export.go @@ -139,9 +139,7 @@ func (s *TagStore) exportImage(eng *engine.Engine, name, tempdir string) error { if err != nil { return err } - job = eng.Job("image_tarlayer", n) - job.Stdout.Add(fsTar) - if err := job.Run(); err != nil { + if err := s.ImageTarLayer(n, fsTar); err != nil { return err } diff --git a/graph/service.go b/graph/service.go index e16d4ac04..007a57d13 100644 --- a/graph/service.go +++ b/graph/service.go @@ -11,14 +11,13 @@ import ( func (s *TagStore) Install(eng *engine.Engine) error { for name, handler := range map[string]engine.Handler{ - "image_set": s.CmdSet, - "image_get": s.CmdGet, - "image_inspect": s.CmdLookup, - "image_tarlayer": s.CmdTarLayer, - "image_export": s.CmdImageExport, - "viz": s.CmdViz, - "load": s.CmdLoad, - "push": s.CmdPush, + "image_set": s.CmdSet, + "image_get": s.CmdGet, + "image_inspect": s.CmdLookup, + "image_export": s.CmdImageExport, + "viz": s.CmdViz, + "load": s.CmdLoad, + "push": s.CmdPush, } { if err := eng.Register(name, handler); err != nil { return fmt.Errorf("Could not register %q: %v", name, err) @@ -152,12 +151,8 @@ func (s *TagStore) CmdLookup(job *engine.Job) error { return fmt.Errorf("No such image: %s", name) } -// CmdTarLayer return the tarLayer of the image -func (s *TagStore) CmdTarLayer(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("usage: %s NAME", job.Name) - } - name := job.Args[0] +// ImageTarLayer return the tarLayer of the image +func (s *TagStore) ImageTarLayer(name string, dest io.Writer) error { if image, err := s.LookupImage(name); err == nil && image != nil { fs, err := image.TarLayer() if err != nil { @@ -165,7 +160,7 @@ func (s *TagStore) CmdTarLayer(job *engine.Job) error { } defer fs.Close() - written, err := io.Copy(job.Stdout, fs) + written, err := io.Copy(dest, fs) if err != nil { return err } From d07fe1836579b0d55813cae8736be31a2891501a Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 21 Apr 2015 19:42:06 +0200 Subject: [PATCH 550/999] Remove job image_get Signed-off-by: Antonio Murdaca --- graph/export.go | 7 +++---- graph/load.go | 2 +- graph/service.go | 41 ----------------------------------------- 3 files changed, 4 insertions(+), 46 deletions(-) diff --git a/graph/export.go b/graph/export.go index 2450d5027..56b5fba71 100644 --- a/graph/export.go +++ b/graph/export.go @@ -144,12 +144,11 @@ func (s *TagStore) exportImage(eng *engine.Engine, name, tempdir string) error { } // find parent - job = eng.Job("image_get", n) - info, _ := job.Stdout.AddEnv() - if err := job.Run(); err != nil { + img, err := s.LookupImage(n) + if err != nil { return err } - n = info.Get("Parent") + n = img.Parent } return nil } diff --git a/graph/load.go b/graph/load.go index bf2cc6d70..5272eb139 100644 --- a/graph/load.go +++ b/graph/load.go @@ -80,7 +80,7 @@ func (s *TagStore) CmdLoad(job *engine.Job) error { } func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error { - if err := eng.Job("image_get", address).Run(); err != nil { + if _, err := s.LookupImage(address); err != nil { logrus.Debugf("Loading %s", address) imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json")) diff --git a/graph/service.go b/graph/service.go index 007a57d13..b829b130f 100644 --- a/graph/service.go +++ b/graph/service.go @@ -12,7 +12,6 @@ import ( func (s *TagStore) Install(eng *engine.Engine) error { for name, handler := range map[string]engine.Handler{ "image_set": s.CmdSet, - "image_get": s.CmdGet, "image_inspect": s.CmdLookup, "image_export": s.CmdImageExport, "viz": s.CmdViz, @@ -73,46 +72,6 @@ func (s *TagStore) CmdSet(job *engine.Job) error { return nil } -// CmdGet returns information about an image. -// If the image doesn't exist, an empty object is returned, to allow -// checking for an image's existence. -func (s *TagStore) CmdGet(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("usage: %s NAME", job.Name) - } - name := job.Args[0] - res := &engine.Env{} - img, err := s.LookupImage(name) - // Note: if the image doesn't exist, LookupImage returns - // nil, nil. - if err != nil { - return err - } - if img != nil { - // We don't directly expose all fields of the Image objects, - // to maintain a clean public API which we can maintain over - // time even if the underlying structure changes. - // We should have done this with the Image object to begin with... - // but we didn't, so now we're doing it here. - // - // Fields that we're probably better off not including: - // - Config/ContainerConfig. Those structs have the same sprawl problem, - // so we shouldn't include them wholesale either. - // - Comment: initially created to fulfill the "every image is a git commit" - // metaphor, in practice people either ignore it or use it as a - // generic description field which it isn't. On deprecation shortlist. - res.SetAuto("Created", img.Created) - res.SetJson("Author", img.Author) - res.Set("Os", img.OS) - res.Set("Architecture", img.Architecture) - res.Set("DockerVersion", img.DockerVersion) - res.SetJson("Id", img.ID) - res.SetJson("Parent", img.Parent) - } - res.WriteTo(job.Stdout) - return nil -} - // CmdLookup return an image encoded in JSON func (s *TagStore) CmdLookup(job *engine.Job) error { if len(job.Args) != 1 { From a4676503d9531b50b26643df5ea3975bdec6b4df Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Tue, 21 Apr 2015 12:47:09 -0700 Subject: [PATCH 551/999] Add Image Digest doc in userguide/dockerimages Fixes #12551 Signed-off-by: Ankush Agarwal --- docs/sources/userguide/dockerimages.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md index f97231506..fc5dbe74a 100644 --- a/docs/sources/userguide/dockerimages.md +++ b/docs/sources/userguide/dockerimages.md @@ -505,6 +505,25 @@ Let's see our new tag using the `docker images` command. ouruser/sinatra devel 5db5f8471261 11 hours ago 446.7 MB ouruser/sinatra v2 5db5f8471261 11 hours ago 446.7 MB +## Image Digests + +Images that use the v2 or later format have a content-addressable identifier +called a `digest`. As long as the input used to generate the image is +unchanged, the digest value is predictable. To list image digest values, use +the `--digests` flag: + + $ docker images --digests | head + REPOSITORY TAG DIGEST IMAGE ID CREATED VIRTUAL SIZE + ouruser/sinatra latest sha256:cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf 5db5f8471261 11 hours ago 446.7 MB + +When pushing or pulling to a 2.0 registry, the `push` or `pull` command +output includes the image digest. You can `pull` using a digest value. + + $ docker pull ouruser/sinatra@cbbf2f9a99b47fc460d422812b6a5adff7dfee951d8fa2e4a98caa0382cfbdbf + +You can also reference by digest in `create`, `run`, and `rmi` commands, as well as the +`FROM` image reference in a Dockerfile. + ## Push an image to Docker Hub Once you've built or created a new image you can push it to [Docker From a3c4801c9291c0db78c5cc0748d5d2dd5e44a98f Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Tue, 21 Apr 2015 21:59:59 +0200 Subject: [PATCH 552/999] Remove not needed call to container.readHostConfig() Signed-off-by: Antonio Murdaca --- daemon/container.go | 6 ++++-- daemon/daemon.go | 2 -- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 9dc0696ea..a611f0da8 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -178,11 +178,13 @@ func (container *Container) readHostConfig() error { return nil } - data, err := ioutil.ReadFile(pth) + f, err := os.Open(pth) if err != nil { return err } - return json.Unmarshal(data, container.hostConfig) + defer f.Close() + + return json.NewDecoder(f).Decode(&container.hostConfig) } func (container *Container) WriteHostConfig() error { diff --git a/daemon/daemon.go b/daemon/daemon.go index d08d22cfd..3a9e7c8c8 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -194,8 +194,6 @@ func (daemon *Daemon) load(id string) (*Container, error) { return container, fmt.Errorf("Container %s is stored at %s", container.ID, id) } - container.readHostConfig() - return container, nil } From 79a7fedcd8f9aaa42ec4e7f0fdd1f915c27e850b Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 21 Apr 2015 10:22:36 -0700 Subject: [PATCH 553/999] Remove image_set engine job It was unused Signed-off-by: Alexander Morozov --- graph/service.go | 49 ------------------------------------------------ 1 file changed, 49 deletions(-) diff --git a/graph/service.go b/graph/service.go index b829b130f..022d5d499 100644 --- a/graph/service.go +++ b/graph/service.go @@ -6,12 +6,10 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/engine" - "github.com/docker/docker/image" ) func (s *TagStore) Install(eng *engine.Engine) error { for name, handler := range map[string]engine.Handler{ - "image_set": s.CmdSet, "image_inspect": s.CmdLookup, "image_export": s.CmdImageExport, "viz": s.CmdViz, @@ -25,53 +23,6 @@ func (s *TagStore) Install(eng *engine.Engine) error { return nil } -// CmdSet stores a new image in the graph. -// Images are stored in the graph using 4 elements: -// - A user-defined ID -// - A collection of metadata describing the image -// - A directory tree stored as a tar archive (also called the "layer") -// - A reference to a "parent" ID on top of which the layer should be applied -// -// NOTE: even though the parent ID is only useful in relation to the layer and how -// to apply it (ie you could represent the full directory tree as 'parent_layer + layer', -// it is treated as a top-level property of the image. This is an artifact of early -// design and should probably be cleaned up in the future to simplify the design. -// -// Syntax: image_set ID -// Input: -// - Layer content must be streamed in tar format on stdin. An empty input is -// valid and represents a nil layer. -// -// - Image metadata must be passed in the command environment. -// 'json': a json-encoded object with all image metadata. -// It will be stored as-is, without any encoding/decoding artifacts. -// That is a requirement of the current registry client implementation, -// because a re-encoded json might invalidate the image checksum at -// the next upload, even with functionaly identical content. -func (s *TagStore) CmdSet(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("usage: %s NAME", job.Name) - } - var ( - imgJSON = []byte(job.Getenv("json")) - layer = job.Stdin - ) - if len(imgJSON) == 0 { - return fmt.Errorf("mandatory key 'json' is not set") - } - // We have to pass an *image.Image object, even though it will be completely - // ignored in favor of the redundant json data. - // FIXME: the current prototype of Graph.Register is redundant. - img, err := image.NewImgJSON(imgJSON) - if err != nil { - return err - } - if err := s.graph.Register(img, layer); err != nil { - return err - } - return nil -} - // CmdLookup return an image encoded in JSON func (s *TagStore) CmdLookup(job *engine.Job) error { if len(job.Args) != 1 { From bc7a43cb44fc0541939f10186e326e3139403605 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Thu, 16 Apr 2015 15:04:07 -0700 Subject: [PATCH 554/999] Putting into our new format for cloud Adding in Seb's comments Updating with Fred's comments Signed-off-by: Mary Anthony --- docs/sources/installation/amazon.md | 56 +++++------------------------ 1 file changed, 9 insertions(+), 47 deletions(-) diff --git a/docs/sources/installation/amazon.md b/docs/sources/installation/amazon.md index 60a6653b7..3fdeb7228 100644 --- a/docs/sources/installation/amazon.md +++ b/docs/sources/installation/amazon.md @@ -2,52 +2,14 @@ page_title: Installation on Amazon EC2 page_description: Installation instructions for Docker on Amazon EC2. page_keywords: amazon ec2, virtualization, cloud, docker, documentation, installation -# Amazon EC2 +## Amazon EC2 -There are several ways to install Docker on AWS EC2. You can use Amazon Linux, which includes the Docker packages in its Software Repository, or opt for any of the other supported Linux images, for example a [*Standard Ubuntu Installation*](#standard-ubuntu-installation). +You can install Docker on any AWS EC2 Amazon Machine Image (AMI) which runs an +operating system that Docker supports. Amazon's website includes specific +instructions for [installing on Amazon +Linux](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html#install_docker). To install on +another AMI, follow the instructions for its specific operating +system in this installation guide. -**You'll need an** [AWS account](http://aws.amazon.com/) **first, of -course.** - -## Amazon QuickStart with Amazon Linux AMI 2014.09.1 - -The latest Amazon Linux AMI, 2014.09.1, is Docker ready. Docker packages can be installed from Amazon's provided Software -Repository. - -1. **Choose an image:** - - Launch the [Create Instance - Wizard](https://console.aws.amazon.com/ec2/v2/home?#LaunchInstanceWizard:) - menu on your AWS Console. - - In the Quick Start menu, select the Amazon provided AMI for Amazon Linux 2014.09.1 - - For testing you can use the default (possibly free) - `t2.micro` instance (more info on - [pricing](http://aws.amazon.com/ec2/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. Add the ec2-user to the docker group : - `sudo usermod -a -G docker ec2-user` -5. Restart the machine and log back in - `sudo shutdown -r now` -6. Once connected to the instance, type - `sudo yum install -y docker ; sudo service docker start` - to install and start Docker - -**If this is your first AWS instance, you may need to set up your Security Group to allow SSH.** By default all incoming ports to your new instance will be blocked by the AWS Security Group, so you might just get timeouts when you try to connect. - -Once you`ve got Docker installed, you're ready to try it out – head on -over to the [User Guide](/userguide). - -## Standard Ubuntu Installation - -If you want a more hands-on installation, then you can follow the -[*Ubuntu*](/installation/ubuntulinux) instructions installing Docker -on any EC2 instance running Ubuntu. Just follow Step 1 from the Amazon -QuickStart above to pick an image (or use one of your -own) and skip the step with the *User Data*. Then continue with the -[*Ubuntu*](/installation/ubuntulinux) instructions. - -Continue with the [User Guide](/userguide/). +For detailed information on Amazon AWS support for Docker, refer to [Amazon's +documentation](http://docs.aws.amazon.com/AmazonECS/latest/developerguide/docker-basics.html). From a2f74aa4b449479dc3953b129c839ca90b089494 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 21 Apr 2015 14:23:48 -0700 Subject: [PATCH 555/999] Remove chain of engine passing from builder to loadManifest Signed-off-by: Alexander Morozov --- api/server/server.go | 8 ++++---- builder/evaluator.go | 2 -- builder/internals.go | 2 +- builder/job.go | 11 ++++------- graph/manifest.go | 3 +-- graph/pull.go | 15 +++++++-------- integration/runtime_test.go | 2 +- 7 files changed, 18 insertions(+), 25 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 94338097b..6e1f35a4b 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -708,7 +708,7 @@ func (s *Server) postCommit(eng *engine.Engine, version version.Version, w http. Config: c, } - imgID, err := builder.Commit(s.daemon, eng, cont, containerCommitConfig) + imgID, err := builder.Commit(s.daemon, cont, containerCommitConfig) if err != nil { return err } @@ -764,7 +764,7 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w imagePullConfig.Json = false } - if err := s.daemon.Repositories().Pull(image, tag, imagePullConfig, eng); err != nil { + if err := s.daemon.Repositories().Pull(image, tag, imagePullConfig); err != nil { return err } } else { //import @@ -785,7 +785,7 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w imageImportConfig.Json = false } - newConfig, err := builder.BuildFromConfig(s.daemon, eng, &runconfig.Config{}, imageImportConfig.Changes) + newConfig, err := builder.BuildFromConfig(s.daemon, &runconfig.Config{}, imageImportConfig.Changes) if err != nil { return err } @@ -1327,7 +1327,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R }() } - if err := builder.Build(s.daemon, eng, buildConfig); err != nil { + if err := builder.Build(s.daemon, buildConfig); err != nil { // Do not write the error in the http output if it's still empty. // This prevents from writing a 200(OK) when there is an interal error. if !output.Flushed() { diff --git a/builder/evaluator.go b/builder/evaluator.go index 0eba4a6eb..7cbba0351 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -31,7 +31,6 @@ import ( "github.com/docker/docker/builder/command" "github.com/docker/docker/builder/parser" "github.com/docker/docker/daemon" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/stringid" @@ -80,7 +79,6 @@ func init() { // processing as it evaluates the parsing result. type Builder struct { Daemon *daemon.Daemon - Engine *engine.Engine // effectively stdio for the run. Because it is not stdio, I said // "Effectively". Do not use stdio anywhere in this package for any reason. diff --git a/builder/internals.go b/builder/internals.go index ae5f2ab3f..9574351ca 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -454,7 +454,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { Json: b.StreamFormatter.Json(), } - if err := b.Daemon.Repositories().Pull(remote, tag, imagePullConfig, b.Engine); err != nil { + if err := b.Daemon.Repositories().Pull(remote, tag, imagePullConfig); err != nil { return nil, err } diff --git a/builder/job.go b/builder/job.go index 8a1cf3054..4c6e55b0a 100644 --- a/builder/job.go +++ b/builder/job.go @@ -13,7 +13,6 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/builder/parser" "github.com/docker/docker/daemon" - "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/httputils" @@ -83,7 +82,7 @@ func NewBuildConfig() *Config { } } -func Build(d *daemon.Daemon, e *engine.Engine, buildConfig *Config) error { +func Build(d *daemon.Daemon, buildConfig *Config) error { var ( repoName string tag string @@ -150,7 +149,6 @@ func Build(d *daemon.Daemon, e *engine.Engine, buildConfig *Config) error { builder := &Builder{ Daemon: d, - Engine: e, OutStream: &streamformatter.StdoutFormater{ Writer: buildConfig.Stdout, StreamFormatter: sf, @@ -188,7 +186,7 @@ func Build(d *daemon.Daemon, e *engine.Engine, buildConfig *Config) error { return nil } -func BuildFromConfig(d *daemon.Daemon, e *engine.Engine, c *runconfig.Config, changes []string) (*runconfig.Config, error) { +func BuildFromConfig(d *daemon.Daemon, c *runconfig.Config, changes []string) (*runconfig.Config, error) { ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) if err != nil { return nil, err @@ -203,7 +201,6 @@ func BuildFromConfig(d *daemon.Daemon, e *engine.Engine, c *runconfig.Config, ch builder := &Builder{ Daemon: d, - Engine: e, Config: c, OutStream: ioutil.Discard, ErrStream: ioutil.Discard, @@ -219,13 +216,13 @@ func BuildFromConfig(d *daemon.Daemon, e *engine.Engine, c *runconfig.Config, ch return builder.Config, nil } -func Commit(d *daemon.Daemon, eng *engine.Engine, name string, c *daemon.ContainerCommitConfig) (string, error) { +func Commit(d *daemon.Daemon, name string, c *daemon.ContainerCommitConfig) (string, error) { container, err := d.Get(name) if err != nil { return "", err } - newConfig, err := BuildFromConfig(d, eng, c.Config, c.Changes) + newConfig, err := BuildFromConfig(d, c.Config, c.Changes) if err != nil { return "", err } diff --git a/graph/manifest.go b/graph/manifest.go index e6d5ebc39..053a185ba 100644 --- a/graph/manifest.go +++ b/graph/manifest.go @@ -6,7 +6,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" - "github.com/docker/docker/engine" "github.com/docker/docker/registry" "github.com/docker/docker/trust" "github.com/docker/docker/utils" @@ -18,7 +17,7 @@ import ( // contains no signatures by a trusted key for the name in the manifest, the // image is not considered verified. The parsed manifest object and a boolean // for whether the manifest is verified is returned. -func (s *TagStore) loadManifest(eng *engine.Engine, manifestBytes []byte, dgst, ref string) (*registry.ManifestData, bool, error) { +func (s *TagStore) loadManifest(manifestBytes []byte, dgst, ref string) (*registry.ManifestData, bool, error) { sig, err := libtrust.ParsePrettySignature(manifestBytes, "signatures") if err != nil { return nil, false, fmt.Errorf("error parsing payload: %s", err) diff --git a/graph/pull.go b/graph/pull.go index edc67df9d..b62591ffb 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -12,7 +12,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" @@ -29,7 +28,7 @@ type ImagePullConfig struct { OutStream io.Writer } -func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig, eng *engine.Engine) error { +func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig) error { var ( sf = streamformatter.NewStreamFormatter(imagePullConfig.Json) ) @@ -74,7 +73,7 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf } logrus.Debugf("pulling v2 repository with local name %q", repoInfo.LocalName) - if err := s.pullV2Repository(eng, r, imagePullConfig.OutStream, repoInfo, tag, sf, imagePullConfig.Parallel); err == nil { + if err := s.pullV2Repository(r, imagePullConfig.OutStream, repoInfo, tag, sf, imagePullConfig.Parallel); err == nil { s.eventsService.Log("pull", logName, "") return nil } else if err != registry.ErrDoesNotExist && err != ErrV2RegistryUnavailable { @@ -369,7 +368,7 @@ type downloadInfo struct { err chan error } -func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, parallel bool) error { +func (s *TagStore) pullV2Repository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, parallel bool) error { endpoint, err := r.V2RegistryEndpoint(repoInfo.Index) if err != nil { if repoInfo.Index.Official { @@ -393,14 +392,14 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out return registry.ErrDoesNotExist } for _, t := range tags { - if downloaded, err := s.pullV2Tag(eng, r, out, endpoint, repoInfo, t, sf, parallel, auth); err != nil { + if downloaded, err := s.pullV2Tag(r, out, endpoint, repoInfo, t, sf, parallel, auth); err != nil { return err } else if downloaded { layersDownloaded = true } } } else { - if downloaded, err := s.pullV2Tag(eng, r, out, endpoint, repoInfo, tag, sf, parallel, auth); err != nil { + if downloaded, err := s.pullV2Tag(r, out, endpoint, repoInfo, tag, sf, parallel, auth); err != nil { return err } else if downloaded { layersDownloaded = true @@ -415,7 +414,7 @@ func (s *TagStore) pullV2Repository(eng *engine.Engine, r *registry.Session, out return nil } -func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, parallel bool, auth *registry.RequestAuthorization) (bool, error) { +func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, parallel bool, auth *registry.RequestAuthorization) (bool, error) { logrus.Debugf("Pulling tag from V2 registry: %q", tag) manifestBytes, manifestDigest, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth) @@ -425,7 +424,7 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri // loadManifest ensures that the manifest payload has the expected digest // if the tag is a digest reference. - manifest, verified, err := s.loadManifest(eng, manifestBytes, manifestDigest, tag) + manifest, verified, err := s.loadManifest(manifestBytes, manifestDigest, tag) if err != nil { return false, fmt.Errorf("error verifying manifest: %s", err) } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 2e106972e..0c412c862 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -138,7 +138,7 @@ func setupBaseImage() { AuthConfig: ®istry.AuthConfig{}, } d := getDaemon(eng) - if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig, eng); err != nil { + if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig); err != nil { logrus.Fatalf("Unable to pull the test image: %s", err) } } From b3e29926cef104c9ef99ff05ed1490cf821bb7b0 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Tue, 21 Apr 2015 18:14:59 -0400 Subject: [PATCH 556/999] make.sh: Define a new build tag libdm_no_deferred_remove libdm started offering deferred remove functionality from version 1.02.89. As docker still builds against older libdm, define a tag libdm_no_deferred_remove to determine whether we are compiling against new libdm or older one and enable/disable deferred remove functionality accordingly. Signed-off-by: Vincent Batts Signed-off-by: Vivek Goyal --- hack/make.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hack/make.sh b/hack/make.sh index 3bcb265b3..aa98ef128 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -107,6 +107,15 @@ if \ DOCKER_BUILDTAGS+=' btrfs_noversion' fi +# test whether "libdevmapper.h" is new enough to support deferred remove +# functionality. +if \ + command -v gcc &> /dev/null \ + && ! ( echo -e '#include \nint main() { dm_task_deferred_remove(NULL); }'| gcc -ldevmapper -xc - &> /dev/null ) \ +; then + DOCKER_BUILDTAGS+=' libdm_no_deferred_remove' +fi + # Use these flags when compiling the tests and final binary IAMSTATIC='true' From 6964ab94befd8723585556e560219e0eef48a488 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Tue, 21 Apr 2015 18:14:59 -0400 Subject: [PATCH 557/999] devicemapper: Add helper functions to allow deferred device removal A lot of time device mapper devices leak across mount namespace which docker does not know about and when docker tries to deactivate/delete device, operation fails as device is open in some mount namespace. Create a mechanism where one can defer the device deactivation/deletion so that docker operation does not fail and device automatically goes away when last reference to it is dropped. Signed-off-by: Vivek Goyal --- pkg/devicemapper/devmapper.go | 20 +++++++++++++++++++ pkg/devicemapper/devmapper_wrapper.go | 1 + .../devmapper_wrapper_deferred_remove.go | 15 ++++++++++++++ .../devmapper_wrapper_no_deferred_remove.go | 10 ++++++++++ 4 files changed, 46 insertions(+) create mode 100644 pkg/devicemapper/devmapper_wrapper_deferred_remove.go create mode 100644 pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go diff --git a/pkg/devicemapper/devmapper.go b/pkg/devicemapper/devmapper.go index bb89f7fac..42876d60c 100644 --- a/pkg/devicemapper/devmapper.go +++ b/pkg/devicemapper/devmapper.go @@ -55,6 +55,7 @@ var ( ErrTaskGetDeps = errors.New("dm_task_get_deps failed") ErrTaskGetInfo = errors.New("dm_task_get_info failed") ErrTaskGetDriverVersion = errors.New("dm_task_get_driver_version failed") + ErrTaskDeferredRemove = errors.New("dm_task_deferred_remove failed") ErrTaskSetCookie = errors.New("dm_task_set_cookie failed") ErrNilCookie = errors.New("cookie ptr can't be nil") ErrAttachLoopbackDevice = errors.New("loopback mounting failed") @@ -371,6 +372,25 @@ func RemoveDevice(name string) error { return nil } +func RemoveDeviceDeferred(name string) error { + logrus.Debugf("[devmapper] RemoveDeviceDeferred START(%s)", name) + defer logrus.Debugf("[devmapper] RemoveDeviceDeferred END(%s)", name) + task, err := TaskCreateNamed(DeviceRemove, name) + if task == nil { + return err + } + + if err := DmTaskDeferredRemove(task.unmanaged); err != 1 { + return ErrTaskDeferredRemove + } + + if err = task.Run(); err != nil { + return fmt.Errorf("Error running RemoveDeviceDeferred %s", err) + } + + return nil +} + func GetBlockDeviceSize(file *os.File) (uint64, error) { size, err := ioctlBlkGetSize64(file.Fd()) if err != nil { diff --git a/pkg/devicemapper/devmapper_wrapper.go b/pkg/devicemapper/devmapper_wrapper.go index e436cca32..fc841d952 100644 --- a/pkg/devicemapper/devmapper_wrapper.go +++ b/pkg/devicemapper/devmapper_wrapper.go @@ -112,6 +112,7 @@ var ( DmUdevGetSyncSupport = dmUdevGetSyncSupportFct DmCookieSupported = dmCookieSupportedFct LogWithErrnoInit = logWithErrnoInitFct + DmTaskDeferredRemove = dmTaskDeferredRemoveFct ) func free(p *C.char) { diff --git a/pkg/devicemapper/devmapper_wrapper_deferred_remove.go b/pkg/devicemapper/devmapper_wrapper_deferred_remove.go new file mode 100644 index 000000000..3d52f3fff --- /dev/null +++ b/pkg/devicemapper/devmapper_wrapper_deferred_remove.go @@ -0,0 +1,15 @@ +// +build linux,!libdm_no_deferred_remove + +package devicemapper + +/* +#cgo LDFLAGS: -L. -ldevmapper +#include +*/ +import "C" + +const LibraryDeferredRemovalSupport = true + +func dmTaskDeferredRemoveFct(task *CDmTask) int { + return int(C.dm_task_deferred_remove((*C.struct_dm_task)(task))) +} diff --git a/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go b/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go new file mode 100644 index 000000000..6366065fd --- /dev/null +++ b/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go @@ -0,0 +1,10 @@ +// +build linux,libdm_no_deferred_remove + +package devicemapper + +const LibraryDeferredRemovalSupport = false + +func dmTaskDeferredRemoveFct(task *CDmTask) int { + // Error. Nobody should be calling it. + return -1 +} From 15c158b20725fd62e2ee0a72ffaf1617852cd0d9 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Tue, 21 Apr 2015 18:14:59 -0400 Subject: [PATCH 558/999] devmapper: Provide a new parameter dm.deferred_device_removal Provide a new command line knob dm.deferred_device_removal which will enable deferred device deactivation if driver and library support it. This patch also checks for library support and driver version. Signed-off-by: Vivek Goyal --- daemon/graphdriver/devmapper/README.md | 20 +++++++ daemon/graphdriver/devmapper/deviceset.go | 65 ++++++++++++++++++++++- 2 files changed, 83 insertions(+), 2 deletions(-) diff --git a/daemon/graphdriver/devmapper/README.md b/daemon/graphdriver/devmapper/README.md index a090b731f..bd5b67c49 100644 --- a/daemon/graphdriver/devmapper/README.md +++ b/daemon/graphdriver/devmapper/README.md @@ -252,3 +252,23 @@ Here is the list of supported options: > Otherwise, set this flag for migrating existing Docker daemons to a > daemon with a supported environment. + * `dm.use_deferred_removal` + + Enables use of deferred device removal if libdm and kernel driver + support the mechanism. + + Deferred device removal means that if device is busy when devices is + being removed/deactivated, then a deferred removal is scheduled on + device. And devices automatically goes away when last user of device + exits. + + For example, when contianer exits, its associated thin device is + removed. If that devices has leaked into some other mount namespace + can can't be removed now, container exit will still be successful + and this option will just schedule device for deferred removal and + will not wait in a loop trying to remove a busy device. + + Example use: + + ``docker -d --storage-opt dm.use_deferred_device_removal=true`` + diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index b5d67fa11..0856758cb 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -37,7 +37,9 @@ var ( // We retry device removal so many a times that even error messages // will fill up console during normal operation. So only log Fatal // messages by default. - DMLogLevel int = devicemapper.LogLevelFatal + DMLogLevel int = devicemapper.LogLevelFatal + DriverDeferredRemovalSupport bool = false + EnableDeferredRemoval bool = false ) const deviceSetMetaFile string = "deviceset-metadata" @@ -103,6 +105,7 @@ type DeviceSet struct { thinPoolDevice string Transaction `json:"-"` overrideUdevSyncCheck bool + deferredRemove bool // use deferred removal } type DiskUsage struct { @@ -960,16 +963,67 @@ func (devices *DeviceSet) closeTransaction() error { return nil } +func determineDriverCapabilities(version string) error { + /* + * Driver version 4.27.0 and greater support deferred activation + * feature. + */ + + logrus.Debugf("devicemapper: driver version is %s", version) + + versionSplit := strings.Split(version, ".") + major, err := strconv.Atoi(versionSplit[0]) + if err != nil { + return graphdriver.ErrNotSupported + } + + if major > 4 { + DriverDeferredRemovalSupport = true + return nil + } + + if major < 4 { + return nil + } + + minor, err := strconv.Atoi(versionSplit[1]) + if err != nil { + return graphdriver.ErrNotSupported + } + + /* + * If major is 4 and minor is 27, then there is no need to + * check for patch level as it can not be less than 0. + */ + if minor >= 27 { + DriverDeferredRemovalSupport = true + return nil + } + + return nil +} + func (devices *DeviceSet) initDevmapper(doInit bool) error { // give ourselves to libdm as a log handler devicemapper.LogInit(devices) - _, err := devicemapper.GetDriverVersion() + version, err := devicemapper.GetDriverVersion() if err != nil { // Can't even get driver version, assume not supported return graphdriver.ErrNotSupported } + if err := determineDriverCapabilities(version); err != nil { + return graphdriver.ErrNotSupported + } + + // If user asked for deferred removal and both library and driver + // supports deferred removal use it. + if EnableDeferredRemoval && DriverDeferredRemovalSupport && devicemapper.LibraryDeferredRemovalSupport == true { + logrus.Debugf("devmapper: Deferred removal support enabled.") + devices.deferredRemove = true + } + // https://github.com/docker/docker/issues/4036 if supported := devicemapper.UdevSetSyncSupport(true); !supported { logrus.Errorf("Udev sync is not supported. This will lead to unexpected behavior, data loss and errors. For more information, see https://docs.docker.com/reference/commandline/cli/#daemon-storage-driver-option") @@ -1671,6 +1725,13 @@ func NewDeviceSet(root string, doInit bool, options []string) (*DeviceSet, error if err != nil { return nil, err } + + case "dm.use_deferred_removal": + EnableDeferredRemoval, err = strconv.ParseBool(val) + if err != nil { + return nil, err + } + default: return nil, fmt.Errorf("Unknown option %s\n", key) } From e37c7203bb1d840e9383ac08bf87afda3e722344 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Tue, 21 Apr 2015 18:14:59 -0400 Subject: [PATCH 559/999] devmapper: Use deferred removal Make use of deferred removal of devices. Signed-off-by: Vivek Goyal --- daemon/graphdriver/devmapper/deviceset.go | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 0856758cb..99bffacd5 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -1287,12 +1287,20 @@ func (devices *DeviceSet) deactivateDevice(info *DevInfo) error { if err != nil { return err } - if devinfo.Exists != 0 { + + if devinfo.Exists == 0 { + return nil + } + + if devices.deferredRemove { + if err := devicemapper.RemoveDeviceDeferred(info.Name()); err != nil { + return err + } + } else { if err := devices.removeDevice(info.Name()); err != nil { return err } } - return nil } From 66a53819aea2ab1ab0d50be1f8d32fcb2427cd78 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Tue, 21 Apr 2015 18:14:59 -0400 Subject: [PATCH 560/999] devmapper: Export deferred removal status in status This will help with debugging as one could just do "docker info" and figure out of deferred removal is enabled or not. Signed-off-by: Vivek Goyal --- daemon/graphdriver/devmapper/deviceset.go | 20 +++++++++++--------- daemon/graphdriver/devmapper/driver.go | 1 + 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 99bffacd5..8dccb2ed8 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -115,15 +115,16 @@ type DiskUsage struct { } type Status struct { - PoolName string - DataFile string // actual block device for data - DataLoopback string // loopback file, if used - MetadataFile string // actual block device for metadata - MetadataLoopback string // loopback file, if used - Data DiskUsage - Metadata DiskUsage - SectorSize uint64 - UdevSyncSupported bool + PoolName string + DataFile string // actual block device for data + DataLoopback string // loopback file, if used + MetadataFile string // actual block device for metadata + MetadataLoopback string // loopback file, if used + Data DiskUsage + Metadata DiskUsage + SectorSize uint64 + UdevSyncSupported bool + DeferredRemoveEnabled bool } type DevStatus struct { @@ -1623,6 +1624,7 @@ func (devices *DeviceSet) Status() *Status { status.MetadataFile = devices.MetadataDevicePath() status.MetadataLoopback = devices.metadataLoopFile status.UdevSyncSupported = devicemapper.UdevSyncSupported() + status.DeferredRemoveEnabled = devices.deferredRemove totalSizeInSectors, _, dataUsed, dataTotal, metadataUsed, metadataTotal, err := devices.poolStatus() if err == nil { diff --git a/daemon/graphdriver/devmapper/driver.go b/daemon/graphdriver/devmapper/driver.go index fad0a0c55..bdf7f874f 100644 --- a/daemon/graphdriver/devmapper/driver.go +++ b/daemon/graphdriver/devmapper/driver.go @@ -77,6 +77,7 @@ func (d *Driver) Status() [][2]string { {"Metadata Space Total", fmt.Sprintf("%s", units.HumanSize(float64(s.Metadata.Total)))}, {"Metadata Space Available", fmt.Sprintf("%s", units.HumanSize(float64(s.Metadata.Available)))}, {"Udev Sync Supported", fmt.Sprintf("%v", s.UdevSyncSupported)}, + {"Deferred Removal Enabled", fmt.Sprintf("%v", s.DeferredRemoveEnabled)}, } if len(s.DataLoopback) > 0 { status = append(status, [2]string{"Data loop file", s.DataLoopback}) From 20b38f427aa05186bd09c8c4201dcc95ed56aa46 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Tue, 21 Apr 2015 18:14:59 -0400 Subject: [PATCH 561/999] devicemapper: Create helpers to cancel deferred deactivation If a device has been scheduled for deferred deactivation and container is started again and we need to activate device again, we need to cancel the deferred deactivation which is already scheduled on the device. Create a method for the same. Signed-off-by: Vivek Goyal --- pkg/devicemapper/devmapper.go | 32 +++++++++++++++++++++++++++++++ pkg/devicemapper/devmapper_log.go | 4 ++++ 2 files changed, 36 insertions(+) diff --git a/pkg/devicemapper/devmapper.go b/pkg/devicemapper/devmapper.go index 42876d60c..04ad91284 100644 --- a/pkg/devicemapper/devmapper.go +++ b/pkg/devicemapper/devmapper.go @@ -70,9 +70,11 @@ var ( ErrLoopbackSetCapacity = errors.New("Unable set loopback capacity") ErrBusy = errors.New("Device is Busy") ErrDeviceIdExists = errors.New("Device Id Exists") + ErrEnxio = errors.New("No such device or address") dmSawBusy bool dmSawExist bool + dmSawEnxio bool // No Such Device or Address ) type ( @@ -391,6 +393,36 @@ func RemoveDeviceDeferred(name string) error { return nil } +// Useful helper for cleanup +func CancelDeferredRemove(deviceName string) error { + task, err := TaskCreateNamed(DeviceTargetMsg, deviceName) + if task == nil { + return err + } + + if err := task.SetSector(0); err != nil { + return fmt.Errorf("Can't set sector %s", err) + } + + if err := task.SetMessage(fmt.Sprintf("@cancel_deferred_remove")); err != nil { + return fmt.Errorf("Can't set message %s", err) + } + + dmSawBusy = false + dmSawEnxio = false + if err := task.Run(); err != nil { + // A device might be being deleted already + if dmSawBusy { + return ErrBusy + } else if dmSawEnxio { + return ErrEnxio + } + return fmt.Errorf("Error running CancelDeferredRemove %s", err) + + } + return nil +} + func GetBlockDeviceSize(file *os.File) (uint64, error) { size, err := ioctlBlkGetSize64(file.Fd()) if err != nil { diff --git a/pkg/devicemapper/devmapper_log.go b/pkg/devicemapper/devmapper_log.go index d6550bd62..f66a20884 100644 --- a/pkg/devicemapper/devmapper_log.go +++ b/pkg/devicemapper/devmapper_log.go @@ -22,6 +22,10 @@ func DevmapperLogCallback(level C.int, file *C.char, line C.int, dm_errno_or_cla if strings.Contains(msg, "File exists") { dmSawExist = true } + + if strings.Contains(msg, "No such device or address") { + dmSawEnxio = true + } } if dmLogger != nil { From 4986ce7cfbe74610d4fa2c4e79ceefe49c1aa155 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Tue, 21 Apr 2015 18:14:59 -0400 Subject: [PATCH 562/999] devicemapper: Create a method to get device info with deferred remove field Deferred reove functionality was added to library later. So in old version of library it did not report deferred_remove field. Create a new function which also gets deferred_remove field and it will be called only on newer version of library. Signed-off-by: Vivek Goyal --- pkg/devicemapper/devmapper.go | 40 ++++++++++++---- pkg/devicemapper/devmapper_wrapper.go | 47 ++++++++++--------- .../devmapper_wrapper_deferred_remove.go | 18 +++++++ .../devmapper_wrapper_no_deferred_remove.go | 4 ++ 4 files changed, 76 insertions(+), 33 deletions(-) diff --git a/pkg/devicemapper/devmapper.go b/pkg/devicemapper/devmapper.go index 04ad91284..e7f17b88c 100644 --- a/pkg/devicemapper/devmapper.go +++ b/pkg/devicemapper/devmapper.go @@ -87,16 +87,17 @@ type ( Device []uint64 } Info struct { - Exists int - Suspended int - LiveTable int - InactiveTable int - OpenCount int32 - EventNr uint32 - Major uint32 - Minor uint32 - ReadOnly int - TargetCount int32 + Exists int + Suspended int + LiveTable int + InactiveTable int + OpenCount int32 + EventNr uint32 + Major uint32 + Minor uint32 + ReadOnly int + TargetCount int32 + DeferredRemove int } TaskType int AddNodeType int @@ -222,6 +223,14 @@ func (t *Task) GetInfo() (*Info, error) { return info, nil } +func (t *Task) GetInfoWithDeferred() (*Info, error) { + info := &Info{} + if res := DmTaskGetInfoWithDeferred(t.unmanaged, info); res != 1 { + return nil, ErrTaskGetInfo + } + return info, nil +} + func (t *Task) GetDriverVersion() (string, error) { res := DmTaskGetDriverVersion(t.unmanaged) if res == "" { @@ -531,6 +540,17 @@ func GetInfo(name string) (*Info, error) { return task.GetInfo() } +func GetInfoWithDeferred(name string) (*Info, error) { + task, err := TaskCreateNamed(DeviceInfo, name) + if task == nil { + return nil, err + } + if err := task.Run(); err != nil { + return nil, err + } + return task.GetInfoWithDeferred() +} + func GetDriverVersion() (string, error) { task := TaskCreate(DeviceVersion) if task == nil { diff --git a/pkg/devicemapper/devmapper_wrapper.go b/pkg/devicemapper/devmapper_wrapper.go index fc841d952..87c200376 100644 --- a/pkg/devicemapper/devmapper_wrapper.go +++ b/pkg/devicemapper/devmapper_wrapper.go @@ -90,29 +90,30 @@ const ( ) var ( - DmGetLibraryVersion = dmGetLibraryVersionFct - DmGetNextTarget = dmGetNextTargetFct - DmLogInitVerbose = dmLogInitVerboseFct - DmSetDevDir = dmSetDevDirFct - DmTaskAddTarget = dmTaskAddTargetFct - DmTaskCreate = dmTaskCreateFct - DmTaskDestroy = dmTaskDestroyFct - DmTaskGetDeps = dmTaskGetDepsFct - DmTaskGetInfo = dmTaskGetInfoFct - DmTaskGetDriverVersion = dmTaskGetDriverVersionFct - DmTaskRun = dmTaskRunFct - DmTaskSetAddNode = dmTaskSetAddNodeFct - DmTaskSetCookie = dmTaskSetCookieFct - DmTaskSetMessage = dmTaskSetMessageFct - DmTaskSetName = dmTaskSetNameFct - DmTaskSetRo = dmTaskSetRoFct - DmTaskSetSector = dmTaskSetSectorFct - DmUdevWait = dmUdevWaitFct - DmUdevSetSyncSupport = dmUdevSetSyncSupportFct - DmUdevGetSyncSupport = dmUdevGetSyncSupportFct - DmCookieSupported = dmCookieSupportedFct - LogWithErrnoInit = logWithErrnoInitFct - DmTaskDeferredRemove = dmTaskDeferredRemoveFct + DmGetLibraryVersion = dmGetLibraryVersionFct + DmGetNextTarget = dmGetNextTargetFct + DmLogInitVerbose = dmLogInitVerboseFct + DmSetDevDir = dmSetDevDirFct + DmTaskAddTarget = dmTaskAddTargetFct + DmTaskCreate = dmTaskCreateFct + DmTaskDestroy = dmTaskDestroyFct + DmTaskGetDeps = dmTaskGetDepsFct + DmTaskGetInfo = dmTaskGetInfoFct + DmTaskGetDriverVersion = dmTaskGetDriverVersionFct + DmTaskRun = dmTaskRunFct + DmTaskSetAddNode = dmTaskSetAddNodeFct + DmTaskSetCookie = dmTaskSetCookieFct + DmTaskSetMessage = dmTaskSetMessageFct + DmTaskSetName = dmTaskSetNameFct + DmTaskSetRo = dmTaskSetRoFct + DmTaskSetSector = dmTaskSetSectorFct + DmUdevWait = dmUdevWaitFct + DmUdevSetSyncSupport = dmUdevSetSyncSupportFct + DmUdevGetSyncSupport = dmUdevGetSyncSupportFct + DmCookieSupported = dmCookieSupportedFct + LogWithErrnoInit = logWithErrnoInitFct + DmTaskDeferredRemove = dmTaskDeferredRemoveFct + DmTaskGetInfoWithDeferred = dmTaskGetInfoWithDeferredFct ) func free(p *C.char) { diff --git a/pkg/devicemapper/devmapper_wrapper_deferred_remove.go b/pkg/devicemapper/devmapper_wrapper_deferred_remove.go index 3d52f3fff..ced482c96 100644 --- a/pkg/devicemapper/devmapper_wrapper_deferred_remove.go +++ b/pkg/devicemapper/devmapper_wrapper_deferred_remove.go @@ -13,3 +13,21 @@ const LibraryDeferredRemovalSupport = true func dmTaskDeferredRemoveFct(task *CDmTask) int { return int(C.dm_task_deferred_remove((*C.struct_dm_task)(task))) } + +func dmTaskGetInfoWithDeferredFct(task *CDmTask, info *Info) int { + Cinfo := C.struct_dm_info{} + defer func() { + info.Exists = int(Cinfo.exists) + info.Suspended = int(Cinfo.suspended) + info.LiveTable = int(Cinfo.live_table) + info.InactiveTable = int(Cinfo.inactive_table) + info.OpenCount = int32(Cinfo.open_count) + info.EventNr = uint32(Cinfo.event_nr) + info.Major = uint32(Cinfo.major) + info.Minor = uint32(Cinfo.minor) + info.ReadOnly = int(Cinfo.read_only) + info.TargetCount = int32(Cinfo.target_count) + info.DeferredRemove = int(Cinfo.deferred_remove) + }() + return int(C.dm_task_get_info((*C.struct_dm_task)(task), &Cinfo)) +} diff --git a/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go b/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go index 6366065fd..16631bf19 100644 --- a/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go +++ b/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go @@ -8,3 +8,7 @@ func dmTaskDeferredRemoveFct(task *CDmTask) int { // Error. Nobody should be calling it. return -1 } + +func dmTaskGetInfoWithDeferredFct(task *CDmTask, info *Info) int { + return -1 +} From ddc8acebecfdc7dbc0357f5c009fb3ee0a2ae906 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Tue, 21 Apr 2015 18:14:59 -0400 Subject: [PATCH 563/999] devmapper: Cancel deferred deactivation if device is reactivated If device is being reactivated before it could go away and deferred deactivation is scheduled on it, cancel it. Signed-off-by: Vivek Goyal --- daemon/graphdriver/devmapper/deviceset.go | 45 +++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index 8dccb2ed8..3d4e64eb9 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -438,6 +438,12 @@ func (devices *DeviceSet) registerDevice(id int, hash string, size uint64, trans func (devices *DeviceSet) activateDeviceIfNeeded(info *DevInfo) error { logrus.Debugf("activateDeviceIfNeeded(%v)", info.Hash) + // Make sure deferred removal on device is canceled, if one was + // scheduled. + if err := devices.cancelDeferredRemoval(info); err != nil { + return fmt.Errorf("Deivce Deferred Removal Cancellation Failed: %s", err) + } + if devinfo, _ := devicemapper.GetInfo(info.Name()); devinfo != nil && devinfo.Exists != 0 { return nil } @@ -1331,6 +1337,45 @@ func (devices *DeviceSet) removeDevice(devname string) error { return err } +func (devices *DeviceSet) cancelDeferredRemoval(info *DevInfo) error { + if !devices.deferredRemove { + return nil + } + + logrus.Debugf("[devmapper] cancelDeferredRemoval START(%s)", info.Name()) + defer logrus.Debugf("[devmapper] cancelDeferredRemoval END(%s)", info.Name) + + devinfo, err := devicemapper.GetInfoWithDeferred(info.Name()) + + if devinfo != nil && devinfo.DeferredRemove == 0 { + return nil + } + + // Cancel deferred remove + for i := 0; i < 100; i++ { + err = devicemapper.CancelDeferredRemove(info.Name()) + if err == nil { + break + } + + if err == devicemapper.ErrEnxio { + // Device is probably already gone. Return success. + return nil + } + + if err != devicemapper.ErrBusy { + return err + } + + // If we see EBUSY it may be a transient error, + // sleep a bit a retry a few times. + devices.Unlock() + time.Sleep(100 * time.Millisecond) + devices.Lock() + } + return err +} + func (devices *DeviceSet) Shutdown() error { logrus.Debugf("[deviceset %s] Shutdown()", devices.devicePrefix) logrus.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) From f3dc35169780e1555b4116986649b729cb80b5d1 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Wed, 22 Apr 2015 08:15:00 +0800 Subject: [PATCH 564/999] remove redundant warning And warning is not supposed to have a prefix WARNING. Signed-off-by: Qiang Huang --- pkg/sysinfo/sysinfo.go | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index d1dcea3bf..76a61fa95 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -23,33 +23,32 @@ func New(quiet bool) *SysInfo { sysInfo := &SysInfo{} if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil { if !quiet { - logrus.Warnf("%s", err) + logrus.Warnf("%v", err) } } else { _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes")) _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) sysInfo.MemoryLimit = err1 == nil && err2 == nil if !sysInfo.MemoryLimit && !quiet { - logrus.Warnf("Your kernel does not support cgroup memory limit.") + logrus.Warn("Your kernel does not support cgroup memory limit.") } _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) sysInfo.SwapLimit = err == nil if !sysInfo.SwapLimit && !quiet { - logrus.Warnf("Your kernel does not support cgroup swap limit.") + logrus.Warn("Your kernel does not support cgroup swap limit.") } } if cgroupCpuMountpoint, err := cgroups.FindCgroupMountpoint("cpu"); err != nil { if !quiet { - logrus.Warnf("WARING: %s\n", err) + logrus.Warnf("%v", err) } } else { _, err1 := ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_quota_us")) - logrus.Warnf("%s", cgroupCpuMountpoint) sysInfo.CpuCfsQuota = err1 == nil if !sysInfo.CpuCfsQuota && !quiet { - logrus.Warnf("WARING: Your kernel does not support cgroup cfs quotas") + logrus.Warn("Your kernel does not support cgroup cfs quotas") } } From 9424fc14bb7465f9874e6e3560cf62b560dbd13d Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Tue, 21 Apr 2015 17:46:43 -0700 Subject: [PATCH 565/999] Add Configuring Docker article [WIP] Fixes #12088 Signed-off-by: Ankush Agarwal --- docs/mkdocs.yml | 1 + docs/sources/articles/configuring.md | 60 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 docs/sources/articles/configuring.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 49c9b80b7..59819afee 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -109,6 +109,7 @@ pages: - ['articles/dockerfile_best-practices.md', 'Articles', 'Best practices for writing Dockerfiles'] - ['articles/certificates.md', 'Articles', 'Using certificates for repository client verification'] - ['articles/using_supervisord.md', 'Articles', 'Using Supervisor'] +- ['articles/configuring.md', 'Articles', 'Configuring Docker'] - ['articles/cfengine_process_management.md', 'Articles', 'Process management with CFEngine'] - ['articles/puppet.md', 'Articles', 'Using Puppet'] - ['articles/chef.md', 'Articles', 'Using Chef'] diff --git a/docs/sources/articles/configuring.md b/docs/sources/articles/configuring.md new file mode 100644 index 000000000..5fae38c5b --- /dev/null +++ b/docs/sources/articles/configuring.md @@ -0,0 +1,60 @@ +page_title: Configuring Docker +page_description: Configuring the Docker daemon on various distributions +page_keywords: docker, daemon, configuration + +# Configuring Docker on various distributions + +After successfully installing the Docker daemon on a distribution, it runs with it's default +config. Usually it is required to change the default config to meet one's personal requirements. + +Docker can be configured by passing the config flags to the daemon directly if the daemon +is started directly. Usually that is not the case. A process manager (like SysVinit, Upstart, +systemd, etc) is responsible for starting and running the daemon. + +Some common config options are + +* `-D` : Enable debug mode + +* `-H` : Daemon socket(s) to connect to + +* `--tls` : Enable or disable TLS authentication + +The complete list of flags can found at [Docker Command Line Reference](/reference/commandline/cli/) + +## Ubuntu + +After successfully [installing Docker for Ubuntu](/installation/ubuntulinux/), you can check the +running status using (if running Upstart) + + $ sudo status docker + docker start/running, process 989 + +You can start/stop/restart `docker` using + + $ sudo start docker + + $ sudo stop docker + + $ sudo restart docker + + +### Configuring Docker + +Docker options can be configured by editing the file `/etc/default/docker`. If this file does not +exist, it needs to be createdThis file contains a variable named `DOCKER_OPTS`. All the +config options need to be placed in this variable. For example + + DOCKER_OPTS=" --dns 8.8.8.8 -D --tls=false -H tcp://0.0.0.0:2375 " + +The above daemon options : + +1. Set dns server for all containers + +2. Enable Debug mode + +3. Set tls to false + +4. Make the daemon listen for connections on `tcp://0.0.0.0:2375` + +After saving the file, restart docker using `sudo restart docker`. Verify that the daemon is +running with the options specified by running `ps aux | grep docker | grep -v grep` From 54ff1dcb829e67d62dd4686bdc19522d63c3e0d3 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Tue, 21 Apr 2015 18:36:37 -0700 Subject: [PATCH 566/999] Fixing a few links in registry Signed-off-by: Mary Anthony --- docs/Dockerfile | 6 ++++++ docs/mkdocs.yml | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/docs/Dockerfile b/docs/Dockerfile index 91cf52bc1..e30d4bbd5 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -49,6 +49,12 @@ ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/api.md \ https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/json.md \ /docs/sources/registry/spec/ + +ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storage-drivers/s3.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storage-drivers/azure.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storage-drivers/filesystem.md \ + https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/storage-drivers/inmemory.md \ + /docs/sources/registry/storage-drivers/ ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs/spec/auth/token.md /docs/sources/registry/spec/auth/token.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 49c9b80b7..5479bb59a 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -171,6 +171,12 @@ pages: - ['reference/api/remote_api_client_libraries.md', 'Reference', 'Docker Remote API Client Libraries'] - ['reference/api/docker_io_accounts_api.md', 'Reference', 'Docker Hub Accounts API'] +# Hidden registry files +- ['registry/storage-drivers/azure.md', '**HIDDEN**' ] +- ['registry/storage-drivers/filesystem.md', '**HIDDEN**' ] +- ['registry/storage-drivers/inmemory.md', '**HIDDEN**' ] +- ['registry/storage-drivers/s3.md', '**HIDDEN**' ] + - ['jsearch.md', '**HIDDEN**'] # - ['static_files/README.md', 'static_files', 'README'] @@ -184,6 +190,8 @@ pages: - ['terms/image.md', '**HIDDEN**'] + + # Project: - ['project/index.md', '**HIDDEN**'] - ['project/who-written-for.md', 'Contributor Guide', 'README first'] From 90a8e45604f42d60d58b4cefa37a5e5d3112b64a Mon Sep 17 00:00:00 2001 From: Gosuke Miyashita Date: Sat, 21 Mar 2015 01:52:05 +0900 Subject: [PATCH 567/999] Append icc related iptables rules, not INSERT Signed-off-by: Gosuke Miyashita --- daemon/networkdriver/bridge/driver.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index 8f240ef59..0ea4d5dca 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -340,7 +340,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { if !iptables.Exists(iptables.Filter, "FORWARD", dropArgs...) { logrus.Debugf("Disable inter-container communication") - if output, err := iptables.Raw(append([]string{"-I", "FORWARD"}, dropArgs...)...); err != nil { + if output, err := iptables.Raw(append([]string{"-A", "FORWARD"}, dropArgs...)...); err != nil { return fmt.Errorf("Unable to prevent intercontainer communication: %s", err) } else if len(output) != 0 { return fmt.Errorf("Error disabling intercontainer communication: %s", output) @@ -351,7 +351,7 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { if !iptables.Exists(iptables.Filter, "FORWARD", acceptArgs...) { logrus.Debugf("Enable inter-container communication") - if output, err := iptables.Raw(append([]string{"-I", "FORWARD"}, acceptArgs...)...); err != nil { + if output, err := iptables.Raw(append([]string{"-A", "FORWARD"}, acceptArgs...)...); err != nil { return fmt.Errorf("Unable to allow intercontainer communication: %s", err) } else if len(output) != 0 { return fmt.Errorf("Error enabling intercontainer communication: %s", output) From 58065d0dd9adec4b2f397a453652cc8cc7237a17 Mon Sep 17 00:00:00 2001 From: Peggy Li Date: Tue, 21 Apr 2015 23:45:18 -0700 Subject: [PATCH 568/999] Fix golint errors in docker/api/client Signed-off-by: Peggy Li --- api/client/cli.go | 7 +++++++ api/client/images.go | 12 ++++++------ api/client/rm.go | 3 +++ api/client/search.go | 1 + api/client/utils.go | 9 +++++---- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/api/client/cli.go b/api/client/cli.go index 3146b17b3..0a1fb2ef8 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -97,6 +97,11 @@ func (cli *DockerCli) Cmd(args ...string) error { return cli.CmdHelp() } +// Subcmd is a subcommand of the main "docker" command. +// A subcommand represents an action that can be performed +// from the Docker command line client. +// +// To see all available subcommands, run "docker --help". func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bool) *flag.FlagSet { var errorHandling flag.ErrorHandling if exitOnError { @@ -121,6 +126,8 @@ func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bo return flags } +// CheckTtyInput checks if we are trying to attach to a container tty +// from a non-tty client input stream, and if so, returns an error. func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { // In order to attach to a container tty, input stream for the client must // be a tty itself: redirecting or piping the client standard input is diff --git a/api/client/images.go b/api/client/images.go index b47c6d65f..32440d48d 100644 --- a/api/client/images.go +++ b/api/client/images.go @@ -19,19 +19,19 @@ import ( ) // FIXME: --viz and --tree are deprecated. Remove them in a future version. -func (cli *DockerCli) WalkTree(noTrunc bool, images []*types.Image, byParent map[string][]*types.Image, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *types.Image, prefix string)) { +func (cli *DockerCli) walkTree(noTrunc bool, images []*types.Image, byParent map[string][]*types.Image, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *types.Image, prefix string)) { length := len(images) if length > 1 { for index, image := range images { if index+1 == length { printNode(cli, noTrunc, image, prefix+"└─") if subimages, exists := byParent[image.ID]; exists { - cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) + cli.walkTree(noTrunc, subimages, byParent, prefix+" ", printNode) } } else { printNode(cli, noTrunc, image, prefix+"\u251C─") if subimages, exists := byParent[image.ID]; exists { - cli.WalkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) + cli.walkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) } } } @@ -39,7 +39,7 @@ func (cli *DockerCli) WalkTree(noTrunc bool, images []*types.Image, byParent map for _, image := range images { printNode(cli, noTrunc, image, prefix+"└─") if subimages, exists := byParent[image.ID]; exists { - cli.WalkTree(noTrunc, subimages, byParent, prefix+" ", printNode) + cli.walkTree(noTrunc, subimages, byParent, prefix+" ", printNode) } } } @@ -181,9 +181,9 @@ func (cli *DockerCli) CmdImages(args ...string) error { if startImage != nil { root := []*types.Image{startImage} - cli.WalkTree(*noTrunc, root, byParent, "", printNode) + cli.walkTree(*noTrunc, root, byParent, "", printNode) } else if matchName == "" { - cli.WalkTree(*noTrunc, roots, byParent, "", printNode) + cli.walkTree(*noTrunc, roots, byParent, "", printNode) } if *flViz { fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") diff --git a/api/client/rm.go b/api/client/rm.go index 1d49e98a8..1ecc0d657 100644 --- a/api/client/rm.go +++ b/api/client/rm.go @@ -7,6 +7,9 @@ import ( flag "github.com/docker/docker/pkg/mflag" ) +// CmdRm removes one or more containers. +// +// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...] func (cli *DockerCli) CmdRm(args ...string) error { cmd := cli.Subcmd("rm", "CONTAINER [CONTAINER...]", "Remove one or more containers", true) v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container") diff --git a/api/client/search.go b/api/client/search.go index 2cff7708d..4e493b234 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -14,6 +14,7 @@ import ( "github.com/docker/docker/registry" ) +// ByStars sorts search results in ascending order by number of stars. type ByStars []registry.SearchResult func (r ByStars) Len() int { return len(r) } diff --git a/api/client/utils.go b/api/client/utils.go index 026593d00..8efbd26c9 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -29,9 +29,10 @@ import ( ) var ( - ErrConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") + errConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?") ) +// HTTPClient creates a new HTP client with the cli's client transport instance. func (cli *DockerCli) HTTPClient() *http.Client { return &http.Client{Transport: cli.transport} } @@ -93,7 +94,7 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m } if err != nil { if strings.Contains(err.Error(), "connection refused") { - return nil, "", statusCode, ErrConnectionRefused + return nil, "", statusCode, errConnectionRefused } if cli.tlsConfig == nil { @@ -250,7 +251,7 @@ func getExitCode(cli *DockerCli, containerID string) (bool, int, error) { stream, _, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil) if err != nil { // If we can't connect, then the daemon probably died. - if err != ErrConnectionRefused { + if err != errConnectionRefused { return false, -1, err } return false, -1, nil @@ -271,7 +272,7 @@ func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) { stream, _, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil) if err != nil { // If we can't connect, then the daemon probably died. - if err != ErrConnectionRefused { + if err != errConnectionRefused { return false, -1, err } return false, -1, nil From b121d94369164391369cd02a559a7b64b482f59d Mon Sep 17 00:00:00 2001 From: Peter Dave Hello Date: Wed, 22 Apr 2015 18:45:29 +0800 Subject: [PATCH 569/999] Use svg instead of png to get better image quality Signed-off-by: Peter Dave Hello --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 03aa0139c..5603a55a7 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ Under the hood, Docker is built on the following components: Contributing to Docker ====================== -[![GoDoc](https://godoc.org/github.com/docker/docker?status.png)](https://godoc.org/github.com/docker/docker) +[![GoDoc](https://godoc.org/github.com/docker/docker?status.svg)](https://godoc.org/github.com/docker/docker) [![Jenkins Build Status](https://jenkins.dockerproject.com/job/Docker%20Master/badge/icon)](https://jenkins.dockerproject.com/job/Docker%20Master/) Want to hack on Docker? Awesome! We have [instructions to help you get From 2dd88af79b0e1af6df0551b993cd9fffbd5881ee Mon Sep 17 00:00:00 2001 From: Andrew Martin Date: Wed, 22 Apr 2015 11:37:47 +0100 Subject: [PATCH 570/999] Add kali to install script https://www.kali.org/ is a Debian derivative. This script completes succesfully using the Debian install path Signed-off-by: Andrew Martin --- hack/install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/install.sh b/hack/install.sh index 32e506cdd..e15565fc7 100755 --- a/hack/install.sh +++ b/hack/install.sh @@ -126,7 +126,7 @@ do_install() { exit 0 ;; - ubuntu|debian|linuxmint|'elementary os') + ubuntu|debian|linuxmint|'elementary os'|kali) export DEBIAN_FRONTEND=noninteractive did_apt_get_update= From eeb8ceb9edb61f4b8889360e4c4a2c4250c2d9f6 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sat, 18 Apr 2015 14:58:57 +0200 Subject: [PATCH 571/999] Fix TestEventsImageImport racy, fixes #12499 Signed-off-by: Antonio Murdaca --- integration-cli/docker_cli_events_test.go | 43 +++++++++++++++++------ 1 file changed, 32 insertions(+), 11 deletions(-) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index b8e24260a..35a9c0a21 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -204,6 +204,31 @@ func (s *DockerSuite) TestEventsImagePull(c *check.C) { func (s *DockerSuite) TestEventsImageImport(c *check.C) { since := daemonTime(c).Unix() + id := make(chan string) + eventImport := make(chan struct{}) + eventsCmd := exec.Command(dockerBinary, "events", "--since", strconv.FormatInt(since, 10)) + stdout, err := eventsCmd.StdoutPipe() + if err != nil { + c.Fatal(err) + } + err = eventsCmd.Start() + if err != nil { + c.Fatal(err) + } + defer eventsCmd.Process.Kill() + + go func() { + containerID := <-id + + matchImport := regexp.MustCompile(containerID + `: import$`) + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + if matchImport.MatchString(scanner.Text()) { + close(eventImport) + } + } + }() + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { @@ -218,19 +243,15 @@ func (s *DockerSuite) TestEventsImageImport(c *check.C) { if err != nil { c.Errorf("import failed with errors: %v, output: %q", err, out) } + newContainerID := strings.TrimSpace(out) + id <- newContainerID - eventsCmd := exec.Command(dockerBinary, "events", - fmt.Sprintf("--since=%d", since), - fmt.Sprintf("--until=%d", daemonTime(c).Unix())) - out, _, _ = runCommandWithOutput(eventsCmd) - - events := strings.Split(strings.TrimSpace(out), "\n") - event := strings.TrimSpace(events[len(events)-1]) - - if !strings.HasSuffix(event, ": import") { - c.Fatalf("Missing import event - got:%q", event) + select { + case <-time.After(5 * time.Second): + c.Fatal("failed to observe image import in timely fashion") + case <-eventImport: + // ignore, done } - } func (s *DockerSuite) TestEventsFilters(c *check.C) { From 9689aab5ec0141a70c2134d18cb581ec5a923c5f Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Wed, 22 Apr 2015 10:35:58 -0700 Subject: [PATCH 572/999] Update with @moxiegirl's patch and add direct config Signed-off-by: Ankush Agarwal --- docs/sources/articles/configuring.md | 84 ++++++++++++++++++++-------- 1 file changed, 61 insertions(+), 23 deletions(-) diff --git a/docs/sources/articles/configuring.md b/docs/sources/articles/configuring.md index 5fae38c5b..35d0eb8e5 100644 --- a/docs/sources/articles/configuring.md +++ b/docs/sources/articles/configuring.md @@ -4,27 +4,46 @@ page_keywords: docker, daemon, configuration # Configuring Docker on various distributions -After successfully installing the Docker daemon on a distribution, it runs with it's default -config. Usually it is required to change the default config to meet one's personal requirements. - -Docker can be configured by passing the config flags to the daemon directly if the daemon -is started directly. Usually that is not the case. A process manager (like SysVinit, Upstart, -systemd, etc) is responsible for starting and running the daemon. +After successfully installing Docker, the `docker` daemon runs with it's default +configuration. You can configure the `docker` daemon by passing configuration +flags to it directly when you start it. -Some common config options are +In a production environment, system administrators typically configure the +`docker` daemon to start and stop according to an organization's requirements. In most +cases, the system administrator configures a process manager such as `SysVinit`, `Upstart`, +or `systemd` to manage the `docker` daemon's start and stop. -* `-D` : Enable debug mode +Some of the daemon's options are: -* `-H` : Daemon socket(s) to connect to +| Flag | Description | +|-----------------------|-----------------------------------------------------------| +| `-D`, `--debug=false` | Enable or disable debug mode. By default, this is false. | +| `-H`,`--host=[]` | Daemon socket(s) to connect to. | +| `--tls=false` | Enable or disable TLS. By default, this is false. | -* `--tls` : Enable or disable TLS authentication +The command line reference has the [complete list of daemon flags](/reference/commandline/cli/#daemon). + +## Direct Configuration + +If you're running the `docker` daemon directly by running `docker -d` instead of using a process manager, +you can append the config options to the run command directly. + + +Here is a an example of running the `docker` daemon with config options: + + docker -d -D --tls=false -H tcp://0.0.0.0:2375 + +These options : + +- Enable `-D` (debug) mode +- Set `tls` to false +- Listen for connections on `tcp://0.0.0.0:2375` -The complete list of flags can found at [Docker Command Line Reference](/reference/commandline/cli/) ## Ubuntu After successfully [installing Docker for Ubuntu](/installation/ubuntulinux/), you can check the -running status using (if running Upstart) +running status using Upstart in this way: $ sudo status docker docker start/running, process 989 @@ -40,21 +59,40 @@ You can start/stop/restart `docker` using ### Configuring Docker -Docker options can be configured by editing the file `/etc/default/docker`. If this file does not -exist, it needs to be createdThis file contains a variable named `DOCKER_OPTS`. All the -config options need to be placed in this variable. For example +You configure the `docker` daemon in the `/etc/default/docker` file on your +system. You do this by specifying values in a `DOCKER_OPTS` variable. +To configure Docker options: - DOCKER_OPTS=" --dns 8.8.8.8 -D --tls=false -H tcp://0.0.0.0:2375 " +1. Log into your system as a user with `sudo` or `root` privileges. -The above daemon options : +2. If you don't have one, create the `/etc/default/docker` file in your system. -1. Set dns server for all containers + Depending on how you installed Docker, you may already have this file. -2. Enable Debug mode +3. Open the file with your favorite editor. -3. Set tls to false + $ sudo vi /etc/default/docker + +4. Add a `DOCKER_OPTS` variable with the following options. These options are appended to the +`docker` daemon's run command. -4. Make the daemon listen for connections on `tcp://0.0.0.0:2375` + ``` + DOCKER_OPTS=" --dns 8.8.8.8 --dns 8.8.4.4 -D --tls=false -H tcp://0.0.0.0:2375 " + ``` + +These options : -After saving the file, restart docker using `sudo restart docker`. Verify that the daemon is -running with the options specified by running `ps aux | grep docker | grep -v grep` +- Set `dns` server for all containers +- Enable `-D` (debug) mode +- Set `tls` to false +- Listen for connections on `tcp://0.0.0.0:2375` + +5. Save and close the file. + +6. Restart the `docker` daemon. + + $ sudo restart docker + +7. Verify that the `docker` daemon is running as specified wit the `ps` command. + + $ ps aux | grep docker | grep -v grep From b4988d8d75b4ee915ade6013de76bb1336917528 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 22 Apr 2015 11:44:54 -0700 Subject: [PATCH 573/999] Remove old testing stuff that slipped into master Signed-off-by: Doug Davis --- integration-cli/docker_api_info_test.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/integration-cli/docker_api_info_test.go b/integration-cli/docker_api_info_test.go index 5f7c9515c..67967ab2a 100644 --- a/integration-cli/docker_api_info_test.go +++ b/integration-cli/docker_api_info_test.go @@ -3,15 +3,16 @@ package main import ( "net/http" "strings" - "testing" + + "github.com/go-check/check" ) -func TestInfoApi(t *testing.T) { +func (s *DockerSuite) TestInfoApi(c *check.C) { endpoint := "/info" statusCode, body, err := sockRequest("GET", endpoint, nil) if err != nil || statusCode != http.StatusOK { - t.Fatalf("Expected %d from info request, got %d", http.StatusOK, statusCode) + c.Fatalf("Expected %d from info request, got %d", http.StatusOK, statusCode) } // always shown fields @@ -30,7 +31,7 @@ func TestInfoApi(t *testing.T) { out := string(body) for _, linePrefix := range stringsToCheck { if !strings.Contains(out, linePrefix) { - t.Errorf("couldn't find string %v in output", linePrefix) + c.Errorf("couldn't find string %v in output", linePrefix) } } } From 08150150bbc4477cbcf8286bc15a1cf8b4428e35 Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Mon, 20 Apr 2015 21:47:56 -0700 Subject: [PATCH 574/999] Add integration test for history option Parse the history output to locate the size fields and check whether they are the correct format or not. Use Column name SIZE to mark start and end indices of the size fields Fixes #12578 Signed-off-by: Ankush Agarwal --- integration-cli/docker_cli_history_test.go | 39 ++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go index a3ba18abd..134f7bc7f 100644 --- a/integration-cli/docker_cli_history_test.go +++ b/integration-cli/docker_cli_history_test.go @@ -3,6 +3,8 @@ package main import ( "fmt" "os/exec" + "regexp" + "strconv" "strings" "github.com/go-check/check" @@ -122,3 +124,40 @@ func (s *DockerSuite) TestHistoryImageWithComment(c *check.C) { } } + +func (s *DockerSuite) TestHistoryHumanOptionFalse(c *check.C) { + out, _, _ := runCommandWithOutput(exec.Command(dockerBinary, "history", "--human=false", "busybox")) + lines := strings.Split(out, "\n") + sizeColumnRegex, _ := regexp.Compile("SIZE +") + indices := sizeColumnRegex.FindStringIndex(lines[0]) + startIndex := indices[0] + endIndex := indices[1] + for i := 1; i < len(lines)-1; i++ { + if endIndex > len(lines[i]) { + endIndex = len(lines[i]) + } + sizeString := lines[i][startIndex:endIndex] + if _, err := strconv.Atoi(strings.TrimSpace(sizeString)); err != nil { + c.Fatalf("The size '%s' was not an Integer", sizeString) + } + } +} + +func (s *DockerSuite) TestHistoryHumanOptionTrue(c *check.C) { + out, _, _ := runCommandWithOutput(exec.Command(dockerBinary, "history", "--human=true", "busybox")) + lines := strings.Split(out, "\n") + sizeColumnRegex, _ := regexp.Compile("SIZE +") + humanSizeRegex, _ := regexp.Compile("^\\d+.*B$") // Matches human sizes like 10 MB, 3.2 KB, etc + indices := sizeColumnRegex.FindStringIndex(lines[0]) + startIndex := indices[0] + endIndex := indices[1] + for i := 1; i < len(lines)-1; i++ { + if endIndex > len(lines[i]) { + endIndex = len(lines[i]) + } + sizeString := lines[i][startIndex:endIndex] + if matchSuccess := humanSizeRegex.MatchString(strings.TrimSpace(sizeString)); !matchSuccess { + c.Fatalf("The size '%s' was not in human format", sizeString) + } + } +} From a09ab40f0d980b2a6d6656db638f1fc5d73f5b8b Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 22 Apr 2015 13:19:14 -0700 Subject: [PATCH 575/999] update contrib docs for gocheck Signed-off-by: Jessica Frazelle --- docs/sources/project/test-and-docs.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sources/project/test-and-docs.md b/docs/sources/project/test-and-docs.md index 93f732829..31615abe9 100644 --- a/docs/sources/project/test-and-docs.md +++ b/docs/sources/project/test-and-docs.md @@ -159,15 +159,16 @@ Most test targets require that you build these precursor targets first: ## Running individual or multiple named tests +We use [gocheck](https://labix.org/gocheck) for our integration-cli tests. You can use the `TESTFLAGS` environment variable to run a single test. The flag's value is passed as arguments to the `go test` command. For example, from your local host you can run the `TestBuild` test with this command: - $ TESTFLAGS='-test.run ^TestBuild$' make test + $ TESTFLAGS='-check.f DockerSuite.TestBuild*' make test To run the same test inside your Docker development container, you do this: - root@5f8630b873fe:/go/src/github.com/docker/docker# TESTFLAGS='-run ^TestBuild$' hack/make.sh + root@5f8630b873fe:/go/src/github.com/docker/docker# TESTFLAGS='-check.f TestBuild*' hack/make.sh ## If tests under Boot2Docker fail due to disk space errors From c09765ac431a85f9ad231c52cbe562bb6f0b0b06 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Tue, 21 Apr 2015 21:58:16 -0700 Subject: [PATCH 576/999] Wait until all pushes are done in TestPushInterrupt Background pushes affects other tests Signed-off-by: Alexander Morozov --- integration-cli/docker_cli_push_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 5c74fe8dc..ebf08bae8 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -92,11 +92,9 @@ func (s *DockerSuite) TestPushMultipleTags(c *check.C) { func (s *DockerSuite) TestPushInterrupt(c *check.C) { defer setupRegistry(c)() - repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) // tag the image to upload it tot he private registry - tagCmd := exec.Command(dockerBinary, "tag", "busybox", repoName) - if out, _, err := runCommandWithOutput(tagCmd); err != nil { + if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repoName)); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) } defer deleteImages(repoName) @@ -111,14 +109,17 @@ func (s *DockerSuite) TestPushInterrupt(c *check.C) { if err := pushCmd.Process.Kill(); err != nil { c.Fatalf("Failed to kill push process: %v", err) } - // Try agin - pushCmd = exec.Command(dockerBinary, "push", repoName) - if out, err := pushCmd.CombinedOutput(); err == nil { + if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "push", repoName)); err == nil { str := string(out) if !strings.Contains(str, "already in progress") { c.Fatalf("Push should be continued on daemon side, but seems ok: %v, %s", err, out) } } + // now wait until all this pushes will complete + // if it will fail with timeout - this is some error, so no logic about it + // here + for exec.Command(dockerBinary, "push", repoName).Run() != nil { + } } func (s *DockerSuite) TestPushEmptyLayer(c *check.C) { From 24425021d26f29a475702064181e6c99fb6bd1c5 Mon Sep 17 00:00:00 2001 From: jianbosun Date: Fri, 17 Apr 2015 13:36:23 +0800 Subject: [PATCH 577/999] remove execCreate & execStart from job Also removed the function ExecConfigFromJob Signed-off-by: Sun Jianbo Signed-off-by: Alexander Morozov --- api/client/exec.go | 11 +++++--- api/server/server.go | 62 +++++++++++++++++++------------------------- api/types/types.go | 12 ++++++--- daemon/daemon.go | 2 -- daemon/exec.go | 34 +++++++----------------- runconfig/exec.go | 22 ---------------- 6 files changed, 52 insertions(+), 91 deletions(-) diff --git a/api/client/exec.go b/api/client/exec.go index 23545ae9b..4bf53eaec 100644 --- a/api/client/exec.go +++ b/api/client/exec.go @@ -32,9 +32,6 @@ func (cli *DockerCli) CmdExec(args ...string) error { if err := json.NewDecoder(stream).Decode(&response); err != nil { return err } - for _, warning := range response.Warnings { - fmt.Fprintf(cli.err, "WARNING: %s\n", warning) - } execID := response.ID @@ -43,12 +40,18 @@ func (cli *DockerCli) CmdExec(args ...string) error { return nil } + //Temp struct for execStart so that we don't need to transfer all the execConfig + execStartCheck := &types.ExecStartCheck{ + Detach: execConfig.Detach, + Tty: execConfig.Tty, + } + if !execConfig.Detach { if err := cli.CheckTtyInput(execConfig.AttachStdin, execConfig.Tty); err != nil { return err } } else { - if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execConfig, nil)); err != nil { + if _, _, err := readBody(cli.call("POST", "/exec/"+execID+"/start", execStartCheck, nil)); err != nil { return err } // For now don't print this - wait for when we support exec wait() diff --git a/api/server/server.go b/api/server/server.go index 6e1f35a4b..6deb88f82 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1,8 +1,6 @@ package server import ( - "bufio" - "bytes" "runtime" "time" @@ -1393,35 +1391,27 @@ func (s *Server) postContainerExecCreate(eng *engine.Engine, version version.Ver if err := parseForm(r); err != nil { return nil } - var ( - name = vars["name"] - job = eng.Job("execCreate", name) - stdoutBuffer = bytes.NewBuffer(nil) - outWarnings []string - warnings = bytes.NewBuffer(nil) - ) + name := vars["name"] - if err := job.DecodeEnv(r.Body); err != nil { + execConfig := &runconfig.ExecConfig{} + if err := json.NewDecoder(r.Body).Decode(execConfig); err != nil { return err } + execConfig.Container = name + + if len(execConfig.Cmd) == 0 { + return fmt.Errorf("No exec command specified") + } - job.Stdout.Add(stdoutBuffer) - // Read warnings from stderr - job.Stderr.Add(warnings) // Register an instance of Exec in container. - if err := job.Run(); err != nil { - fmt.Fprintf(os.Stderr, "Error setting up exec command in container %s: %s\n", name, err) + id, err := s.daemon.ContainerExecCreate(execConfig) + if err != nil { + logrus.Errorf("Error setting up exec command in container %s: %s", name, err) return err } - // Parse warnings from stderr - scanner := bufio.NewScanner(warnings) - for scanner.Scan() { - outWarnings = append(outWarnings, scanner.Text()) - } return writeJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{ - ID: engine.Tail(stdoutBuffer, 1), - Warnings: outWarnings, + ID: id, }) } @@ -1431,15 +1421,18 @@ func (s *Server) postContainerExecStart(eng *engine.Engine, version version.Vers return nil } var ( - name = vars["name"] - job = eng.Job("execStart", name) - errOut io.Writer = os.Stderr + execName = vars["name"] + stdin io.ReadCloser + stdout io.Writer + stderr io.Writer ) - if err := job.DecodeEnv(r.Body); err != nil { + execStartCheck := &types.ExecStartCheck{} + if err := json.NewDecoder(r.Body).Decode(execStartCheck); err != nil { return err } - if !job.GetenvBool("Detach") { + + if !execStartCheck.Detach { // Setting up the streaming http interface. inStream, outStream, err := hijackServer(w) if err != nil { @@ -1455,21 +1448,20 @@ func (s *Server) postContainerExecStart(eng *engine.Engine, version version.Vers fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n") } - if !job.GetenvBool("Tty") && version.GreaterThanOrEqualTo("1.6") { + if !execStartCheck.Tty && version.GreaterThanOrEqualTo("1.6") { errStream = stdcopy.NewStdWriter(outStream, stdcopy.Stderr) outStream = stdcopy.NewStdWriter(outStream, stdcopy.Stdout) } else { errStream = outStream } - job.Stdin.Add(inStream) - job.Stdout.Add(outStream) - job.Stderr.Set(errStream) - errOut = outStream + stdin = inStream + stdout = outStream + stderr = errStream } // Now run the user process in container. - job.SetCloseIO(false) - if err := job.Run(); err != nil { - fmt.Fprintf(errOut, "Error starting exec command in container %s: %s\n", name, err) + + if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil { + logrus.Errorf("Error starting exec command in container %s: %s", execName, err) return err } w.WriteHeader(http.StatusNoContent) diff --git a/api/types/types.go b/api/types/types.go index 01b5d38f1..1e8b56bce 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -16,9 +16,6 @@ type ContainerCreateResponse struct { type ContainerExecCreateResponse struct { // ID is the exec ID. ID string `json:"Id"` - - // Warnings are any warnings encountered during the execution of the command. - Warnings []string `json:"Warnings"` } // POST /auth @@ -156,3 +153,12 @@ type Info struct { Name string Labels []string } + +// This struct is a temp struct used by execStart +// Config fields is part of ExecConfig in runconfig package +type ExecStartCheck struct { + // ExecStart will first check if it's detached + Detach bool + // Check if there's a tty + Tty bool +} diff --git a/daemon/daemon.go b/daemon/daemon.go index c643ed65c..ca3aff3e2 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -118,8 +118,6 @@ type Daemon struct { func (daemon *Daemon) Install(eng *engine.Engine) error { for name, method := range map[string]engine.Handler{ "container_inspect": daemon.ContainerInspect, - "execCreate": daemon.ContainerExecCreate, - "execStart": daemon.ContainerExecStart, } { if err := eng.Register(name, method); err != nil { return err diff --git a/daemon/exec.go b/daemon/exec.go index 4787189a7..22872adc4 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -10,7 +10,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/execdriver/lxc" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/broadcastwriter" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/promise" @@ -111,25 +110,15 @@ func (d *Daemon) getActiveContainer(name string) (*Container, error) { return container, nil } -func (d *Daemon) ContainerExecCreate(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s [options] container command [args]", job.Name) - } +func (d *Daemon) ContainerExecCreate(config *runconfig.ExecConfig) (string, error) { if strings.HasPrefix(d.execDriver.Name(), lxc.DriverName) { - return lxc.ErrExec + return "", lxc.ErrExec } - var name = job.Args[0] - - container, err := d.getActiveContainer(name) + container, err := d.getActiveContainer(config.Container) if err != nil { - return err - } - - config, err := runconfig.ExecConfigFromJob(job) - if err != nil { - return err + return "", err } cmd := runconfig.NewCommand(config.Cmd...) @@ -158,20 +147,15 @@ func (d *Daemon) ContainerExecCreate(job *engine.Job) error { d.registerExecCommand(execConfig) - job.Printf("%s\n", execConfig.ID) + return execConfig.ID, nil - return nil } -func (d *Daemon) ContainerExecStart(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("Usage: %s [options] exec", job.Name) - } +func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error { var ( cStdin io.ReadCloser cStdout, cStderr io.Writer - execName = job.Args[0] ) execConfig, err := d.getExecConfig(execName) @@ -201,15 +185,15 @@ func (d *Daemon) ContainerExecStart(job *engine.Job) error { go func() { defer w.Close() defer logrus.Debugf("Closing buffered stdin pipe") - io.Copy(w, job.Stdin) + io.Copy(w, stdin) }() cStdin = r } if execConfig.OpenStdout { - cStdout = job.Stdout + cStdout = stdout } if execConfig.OpenStderr { - cStderr = job.Stderr + cStderr = stderr } execConfig.StreamConfig.stderr = broadcastwriter.New() diff --git a/runconfig/exec.go b/runconfig/exec.go index e634d3081..8fe05be1b 100644 --- a/runconfig/exec.go +++ b/runconfig/exec.go @@ -1,9 +1,6 @@ package runconfig import ( - "fmt" - - "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" ) @@ -19,25 +16,6 @@ type ExecConfig struct { Cmd []string } -func ExecConfigFromJob(job *engine.Job) (*ExecConfig, error) { - execConfig := &ExecConfig{ - User: job.Getenv("User"), - Privileged: job.GetenvBool("Privileged"), - Tty: job.GetenvBool("Tty"), - AttachStdin: job.GetenvBool("AttachStdin"), - AttachStderr: job.GetenvBool("AttachStderr"), - AttachStdout: job.GetenvBool("AttachStdout"), - } - cmd := job.GetenvList("Cmd") - if len(cmd) == 0 { - return nil, fmt.Errorf("No exec command specified") - } - - execConfig.Cmd = cmd - - return execConfig, nil -} - func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) { var ( flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") From fd4f7c4e5c4460bad7baf0854f1edc1bbf93aeef Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Wed, 22 Apr 2015 12:33:46 -0700 Subject: [PATCH 578/999] Correctly format API error on image pull Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) --- api/server/server.go | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 6e1f35a4b..2523e4463 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -740,6 +740,15 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w } } + var ( + opErr error + useJSON = version.GreaterThan("1.0") + ) + + if useJSON { + w.Header().Set("Content-Type", "application/json") + } + if image != "" { //pull if tag == "" { image, tag = parsers.ParseRepositoryTag(image) @@ -756,17 +765,10 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w MetaHeaders: metaHeaders, AuthConfig: authConfig, OutStream: utils.NewWriteFlusher(w), - } - if version.GreaterThan("1.0") { - imagePullConfig.Json = true - w.Header().Set("Content-Type", "application/json") - } else { - imagePullConfig.Json = false + Json: useJSON, } - if err := s.daemon.Repositories().Pull(image, tag, imagePullConfig); err != nil { - return err - } + opErr = s.daemon.Repositories().Pull(image, tag, imagePullConfig) } else { //import if tag == "" { repo, tag = parsers.ParseRepositoryTag(repo) @@ -777,12 +779,7 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w Changes: r.Form["changes"], InConfig: r.Body, OutStream: utils.NewWriteFlusher(w), - } - if version.GreaterThan("1.0") { - imageImportConfig.Json = true - w.Header().Set("Content-Type", "application/json") - } else { - imageImportConfig.Json = false + Json: useJSON, } newConfig, err := builder.BuildFromConfig(s.daemon, &runconfig.Config{}, imageImportConfig.Changes) @@ -791,9 +788,12 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w } imageImportConfig.ContainerConfig = newConfig - if err := s.daemon.Repositories().Import(src, repo, tag, imageImportConfig); err != nil { - return err - } + opErr = s.daemon.Repositories().Import(src, repo, tag, imageImportConfig) + } + + if opErr != nil { + sf := streamformatter.NewStreamFormatter(useJSON) + return fmt.Errorf(string(sf.FormatError(opErr))) } return nil From 7a525c6cb74af854d621f5560868b32694d2553b Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 22 Apr 2015 11:57:23 -0700 Subject: [PATCH 579/999] add integration test for error pull nonexistent Signed-off-by: Jessica Frazelle --- integration-cli/docker_cli_pull_test.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index 8cf09a726..25a93729b 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -98,8 +98,13 @@ func (s *DockerSuite) TestPullImageFromCentralRegistry(c *check.C) { // pulling a non-existing image from the central registry should return a non-zero exit code func (s *DockerSuite) TestPullNonExistingImage(c *check.C) { - pullCmd := exec.Command(dockerBinary, "pull", "fooblahblah1234") - if out, _, err := runCommandWithOutput(pullCmd); err == nil { + testRequires(c, Network) + + name := "sadfsadfasdf" + pullCmd := exec.Command(dockerBinary, "pull", name) + out, _, err := runCommandWithOutput(pullCmd) + + if err == nil || !strings.Contains(out, fmt.Sprintf("Error: image library/%s:latest not found", name)) { c.Fatalf("expected non-zero exit status when pulling non-existing image: %s", out) } } From 28f5541b72a898762575a2ed0995cae7452fa275 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 22 Apr 2015 14:12:46 -0700 Subject: [PATCH 580/999] add regression test for rmi multiple tags without f Signed-off-by: Jessica Frazelle --- integration-cli/docker_cli_rmi_test.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index 786a3e306..234fa22f0 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "os/exec" "strings" @@ -102,6 +103,14 @@ func (s *DockerSuite) TestRmiImgIDForce(c *check.C) { } out, _ = dockerCmd(c, "inspect", "-f", "{{.Id}}", "busybox-test") imgID := strings.TrimSpace(out) + + // first checkout without force it fails + runCmd = exec.Command(dockerBinary, "rmi", imgID) + out, _, err = runCommandWithOutput(runCmd) + if err == nil || !strings.Contains(out, fmt.Sprintf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", imgID)) { + c.Fatalf("rmi tagged in mutiple repos should have failed without force:%s, %v", out, err) + } + dockerCmd(c, "rmi", "-f", imgID) { imagesAfter, _ := dockerCmd(c, "images", "-a") From 71b5a754cec09b2f1bcef986bdd6fd109451b8f2 Mon Sep 17 00:00:00 2001 From: "Daniel, Dao Quang Minh" Date: Wed, 22 Apr 2015 21:53:45 +0000 Subject: [PATCH 581/999] remove unused utils Signed-off-by: Daniel, Dao Quang Minh --- daemon/execdriver/native/utils.go | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 daemon/execdriver/native/utils.go diff --git a/daemon/execdriver/native/utils.go b/daemon/execdriver/native/utils.go deleted file mode 100644 index a70392645..000000000 --- a/daemon/execdriver/native/utils.go +++ /dev/null @@ -1,22 +0,0 @@ -// +build linux - -package native - -//func findUserArgs() []string { -//for i, a := range os.Args { -//if a == "--" { -//return os.Args[i+1:] -//} -//} -//return []string{} -//} - -//// loadConfigFromFd loads a container's config from the sync pipe that is provided by -//// fd 3 when running a process -//func loadConfigFromFd() (*configs.Config, error) { -//var config *libcontainer.Config -//if err := json.NewDecoder(os.NewFile(3, "child")).Decode(&config); err != nil { -//return nil, err -//} -//return config, nil -//} From 4b9fe9c298c8778855c1d14e978c791496dd7c42 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 13 Apr 2015 16:17:14 +0200 Subject: [PATCH 582/999] Remove job from container_inspect Signed-off-by: Antonio Murdaca --- api/client/attach.go | 24 +++---- api/client/logs.go | 11 +-- api/client/utils.go | 27 +++++--- api/common.go | 15 +---- api/server/server.go | 17 +++-- api/server/server_unit_test.go | 35 ---------- api/types/types.go | 48 ++++++++++++- daemon/daemon.go | 7 -- daemon/inspect.go | 115 +++++++++++++++++--------------- docker/docker.go | 6 +- integration-cli/docker_utils.go | 4 +- opts/opts.go | 30 +++++++-- utils/utils.go | 10 --- 13 files changed, 186 insertions(+), 163 deletions(-) diff --git a/api/client/attach.go b/api/client/attach.go index ef2b4ad12..8ab3248ac 100644 --- a/api/client/attach.go +++ b/api/client/attach.go @@ -1,12 +1,13 @@ package client import ( + "encoding/json" "fmt" "io" "net/url" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/signal" ) @@ -30,25 +31,20 @@ func (cli *DockerCli) CmdAttach(args ...string) error { return err } - env := engine.Env{} - if err := env.Decode(stream); err != nil { + var c types.ContainerJSON + if err := json.NewDecoder(stream).Decode(&c); err != nil { return err } - if !env.GetSubEnv("State").GetBool("Running") { + if !c.State.Running { return fmt.Errorf("You cannot attach to a stopped container, start it first") } - var ( - config = env.GetSubEnv("Config") - tty = config.GetBool("Tty") - ) - - if err := cli.CheckTtyInput(!*noStdin, tty); err != nil { + if err := cli.CheckTtyInput(!*noStdin, c.Config.Tty); err != nil { return err } - if tty && cli.isTerminalOut { + if c.Config.Tty && cli.isTerminalOut { if err := cli.monitorTtySize(cmd.Arg(0), false); err != nil { logrus.Debugf("Error monitoring TTY size: %s", err) } @@ -58,7 +54,7 @@ func (cli *DockerCli) CmdAttach(args ...string) error { v := url.Values{} v.Set("stream", "1") - if !*noStdin && config.GetBool("OpenStdin") { + if !*noStdin && c.Config.OpenStdin { v.Set("stdin", "1") in = cli.in } @@ -66,12 +62,12 @@ func (cli *DockerCli) CmdAttach(args ...string) error { v.Set("stdout", "1") v.Set("stderr", "1") - if *proxy && !tty { + if *proxy && !c.Config.Tty { sigc := cli.forwardAllSignals(cmd.Arg(0)) defer signal.StopCatch(sigc) } - if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), tty, in, cli.out, cli.err, nil, nil); err != nil { + if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?"+v.Encode(), c.Config.Tty, in, cli.out, cli.err, nil, nil); err != nil { return err } diff --git a/api/client/logs.go b/api/client/logs.go index 9039ecf09..5e5dd9dd8 100644 --- a/api/client/logs.go +++ b/api/client/logs.go @@ -1,10 +1,11 @@ package client import ( + "encoding/json" "fmt" "net/url" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" ) @@ -29,12 +30,12 @@ func (cli *DockerCli) CmdLogs(args ...string) error { return err } - env := engine.Env{} - if err := env.Decode(stream); err != nil { + var c types.ContainerJSON + if err := json.NewDecoder(stream).Decode(&c); err != nil { return err } - if env.GetSubEnv("HostConfig").GetSubEnv("LogConfig").Get("Type") != "json-file" { + if c.HostConfig.LogConfig.Type != "json-file" { return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver") } @@ -51,5 +52,5 @@ func (cli *DockerCli) CmdLogs(args ...string) error { } v.Set("tail", *tail) - return cli.streamHelper("GET", "/containers/"+name+"/logs?"+v.Encode(), env.GetSubEnv("Config").GetBool("Tty"), nil, cli.out, cli.err, nil) + return cli.streamHelper("GET", "/containers/"+name+"/logs?"+v.Encode(), c.Config.Tty, nil, cli.out, cli.err, nil) } diff --git a/api/client/utils.go b/api/client/utils.go index 8efbd26c9..804dc0c58 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -19,6 +19,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api" + "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/jsonmessage" @@ -238,11 +239,12 @@ func waitForExit(cli *DockerCli, containerID string) (int, error) { return -1, err } - var out engine.Env - if err := out.Decode(stream); err != nil { + var res types.ContainerWaitResponse + if err := json.NewDecoder(stream).Decode(&res); err != nil { return -1, err } - return out.GetInt("StatusCode"), nil + + return res.StatusCode, nil } // getExitCode perform an inspect on the container. It returns @@ -257,13 +259,12 @@ func getExitCode(cli *DockerCli, containerID string) (bool, int, error) { return false, -1, nil } - var result engine.Env - if err := result.Decode(stream); err != nil { + var c types.ContainerJSON + if err := json.NewDecoder(stream).Decode(&c); err != nil { return false, -1, err } - state := result.GetSubEnv("State") - return state.GetBool("Running"), state.GetInt("ExitCode"), nil + return c.State.Running, c.State.ExitCode, nil } // getExecExitCode perform an inspect on the exec command. It returns @@ -278,12 +279,18 @@ func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) { return false, -1, nil } - var result engine.Env - if err := result.Decode(stream); err != nil { + //TODO: Should we reconsider having a type in api/types? + //this is a response to exex/id/json not container + var c struct { + Running bool + ExitCode int + } + + if err := json.NewDecoder(stream).Decode(&c); err != nil { return false, -1, err } - return result.GetBool("Running"), result.GetInt("ExitCode"), nil + return c.Running, c.ExitCode, nil } func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { diff --git a/api/common.go b/api/common.go index 693df3887..cb627824e 100644 --- a/api/common.go +++ b/api/common.go @@ -10,27 +10,16 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/version" "github.com/docker/libtrust" ) // Common constants for daemon and client. const ( - APIVERSION version.Version = "1.19" // Current REST API version - DEFAULTHTTPHOST = "127.0.0.1" // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080 - DEFAULTUNIXSOCKET = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket - DefaultDockerfileName string = "Dockerfile" // Default filename with Docker commands, read by docker build + APIVERSION version.Version = "1.19" // Current REST API version + DefaultDockerfileName string = "Dockerfile" // Default filename with Docker commands, read by docker build ) -func ValidateHost(val string) (string, error) { - host, err := parsers.ParseHost(DEFAULTHTTPHOST, DEFAULTUNIXSOCKET, val) - if err != nil { - return val, err - } - return host, nil -} - type ByPrivatePort []types.Port func (r ByPrivatePort) Len() int { return len(r) } diff --git a/api/server/server.go b/api/server/server.go index 6deb88f82..a9a06fa6e 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1210,12 +1210,21 @@ func (s *Server) getContainersByName(eng *engine.Engine, version version.Version if vars == nil { return fmt.Errorf("Missing parameter") } - var job = eng.Job("container_inspect", vars["name"]) + + name := vars["name"] + if version.LessThan("1.12") { - job.SetenvBool("raw", true) + containerJSONRaw, err := s.daemon.ContainerInspectRaw(name) + if err != nil { + return err + } + return writeJSON(w, http.StatusOK, containerJSONRaw) } - streamJSON(job.Stdout, w, false) - return job.Run() + containerJSON, err := s.daemon.ContainerInspect(name) + if err != nil { + return err + } + return writeJSON(w, http.StatusOK, containerJSON) } func (s *Server) getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index e7a6afcb9..b2a911607 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -32,41 +32,6 @@ func TesthttpError(t *testing.T) { } } -func TestGetContainersByName(t *testing.T) { - eng := engine.New() - name := "container_name" - var called bool - eng.Register("container_inspect", func(job *engine.Job) error { - called = true - if job.Args[0] != name { - t.Errorf("name != '%s': %#v", name, job.Args[0]) - } - if api.APIVERSION.LessThan("1.12") && !job.GetenvBool("dirty") { - t.Errorf("dirty env variable not set") - } else if api.APIVERSION.GreaterThanOrEqualTo("1.12") && job.GetenvBool("dirty") { - t.Errorf("dirty env variable set when it shouldn't") - } - v := &engine.Env{} - v.SetBool("dirty", true) - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil - }) - r := serveRequest("GET", "/containers/"+name+"/json", nil, eng, t) - if !called { - t.Fatal("handler was not called") - } - assertContentType(r, "application/json", t) - var stdoutJson interface{} - if err := json.Unmarshal(r.Body.Bytes(), &stdoutJson); err != nil { - t.Fatalf("%#v", err) - } - if stdoutJson.(map[string]interface{})["dirty"].(float64) != 1 { - t.Fatalf("%#v", stdoutJson) - } -} - func TestGetImagesByName(t *testing.T) { eng := engine.New() name := "image_name" diff --git a/api/types/types.go b/api/types/types.go index 1e8b56bce..656aa1a6e 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -1,6 +1,12 @@ package types -import "github.com/docker/docker/pkg/version" +import ( + "time" + + "github.com/docker/docker/daemon/network" + "github.com/docker/docker/pkg/version" + "github.com/docker/docker/runconfig" +) // ContainerCreateResponse contains the information returned to a client on the // creation of a new container. @@ -162,3 +168,43 @@ type ExecStartCheck struct { // Check if there's a tty Tty bool } + +type ContainerState struct { + Running bool + Paused bool + Restarting bool + OOMKilled bool + Dead bool + Pid int + ExitCode int + Error string + StartedAt time.Time + FinishedAt time.Time +} + +// GET "/containers/{name:.*}/json" +type ContainerJSON struct { + Id string + Created time.Time + Path string + Args []string + Config *runconfig.Config + State *ContainerState + Image string + NetworkSettings *network.Settings + ResolvConfPath string + HostnamePath string + HostsPath string + LogPath string + Name string + RestartCount int + Driver string + ExecDriver string + MountLabel string + ProcessLabel string + Volumes map[string]string + VolumesRW map[string]bool + AppArmorProfile string + ExecIDs []string + HostConfig *runconfig.HostConfig +} diff --git a/daemon/daemon.go b/daemon/daemon.go index ca3aff3e2..8873b4cac 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -116,13 +116,6 @@ type Daemon struct { // Install installs daemon capabilities to eng. func (daemon *Daemon) Install(eng *engine.Engine) error { - for name, method := range map[string]engine.Handler{ - "container_inspect": daemon.ContainerInspect, - } { - if err := eng.Register(name, method); err != nil { - return err - } - } if err := daemon.Repositories().Install(eng); err != nil { return err } diff --git a/daemon/inspect.go b/daemon/inspect.go index 7e25626ef..56db3d059 100644 --- a/daemon/inspect.go +++ b/daemon/inspect.go @@ -1,83 +1,92 @@ package daemon import ( - "encoding/json" "fmt" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" "github.com/docker/docker/runconfig" ) -func (daemon *Daemon) ContainerInspect(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("usage: %s NAME", job.Name) - } - name := job.Args[0] +type ContainerJSONRaw struct { + *Container + HostConfig *runconfig.HostConfig +} + +func (daemon *Daemon) ContainerInspectRaw(name string) (*ContainerJSONRaw, error) { container, err := daemon.Get(name) if err != nil { - return err + return nil, err } container.Lock() defer container.Unlock() - if job.GetenvBool("raw") { - b, err := json.Marshal(&struct { - *Container - HostConfig *runconfig.HostConfig - }{container, container.hostConfig}) - if err != nil { - return err - } - job.Stdout.Write(b) - return nil + + return &ContainerJSONRaw{container, container.hostConfig}, nil +} + +func (daemon *Daemon) ContainerInspect(name string) (*types.ContainerJSON, error) { + container, err := daemon.Get(name) + if err != nil { + return nil, err } - out := &engine.Env{} - out.SetJson("Id", container.ID) - out.SetAuto("Created", container.Created) - out.SetJson("Path", container.Path) - out.SetList("Args", container.Args) - out.SetJson("Config", container.Config) - out.SetJson("State", container.State) - out.Set("Image", container.ImageID) - out.SetJson("NetworkSettings", container.NetworkSettings) - out.Set("ResolvConfPath", container.ResolvConfPath) - out.Set("HostnamePath", container.HostnamePath) - out.Set("HostsPath", container.HostsPath) - out.Set("LogPath", container.LogPath) - out.SetJson("Name", container.Name) - out.SetInt("RestartCount", container.RestartCount) - out.Set("Driver", container.Driver) - out.Set("ExecDriver", container.ExecDriver) - out.Set("MountLabel", container.MountLabel) - out.Set("ProcessLabel", container.ProcessLabel) - out.SetJson("Volumes", container.Volumes) - out.SetJson("VolumesRW", container.VolumesRW) - out.SetJson("AppArmorProfile", container.AppArmorProfile) + container.Lock() + defer container.Unlock() - out.SetList("ExecIDs", container.GetExecIDs()) + // make a copy to play with + hostConfig := *container.hostConfig if children, err := daemon.Children(container.Name); err == nil { for linkAlias, child := range children { - container.hostConfig.Links = append(container.hostConfig.Links, fmt.Sprintf("%s:%s", child.Name, linkAlias)) + hostConfig.Links = append(hostConfig.Links, fmt.Sprintf("%s:%s", child.Name, linkAlias)) } } // we need this trick to preserve empty log driver, so // container will use daemon defaults even if daemon change them - if container.hostConfig.LogConfig.Type == "" { - container.hostConfig.LogConfig = daemon.defaultLogConfig - defer func() { - container.hostConfig.LogConfig = runconfig.LogConfig{} - }() + if hostConfig.LogConfig.Type == "" { + hostConfig.LogConfig = daemon.defaultLogConfig } - out.SetJson("HostConfig", container.hostConfig) - - container.hostConfig.Links = nil - if _, err := out.WriteTo(job.Stdout); err != nil { - return err + containerState := &types.ContainerState{ + Running: container.State.Running, + Paused: container.State.Paused, + Restarting: container.State.Restarting, + OOMKilled: container.State.OOMKilled, + Dead: container.State.Dead, + Pid: container.State.Pid, + ExitCode: container.State.ExitCode, + Error: container.State.Error, + StartedAt: container.State.StartedAt, + FinishedAt: container.State.FinishedAt, } - return nil + + contJSON := &types.ContainerJSON{ + Id: container.ID, + Created: container.Created, + Path: container.Path, + Args: container.Args, + Config: container.Config, + State: containerState, + Image: container.ImageID, + NetworkSettings: container.NetworkSettings, + ResolvConfPath: container.ResolvConfPath, + HostnamePath: container.HostnamePath, + HostsPath: container.HostsPath, + LogPath: container.LogPath, + Name: container.Name, + RestartCount: container.RestartCount, + Driver: container.Driver, + ExecDriver: container.ExecDriver, + MountLabel: container.MountLabel, + ProcessLabel: container.ProcessLabel, + Volumes: container.Volumes, + VolumesRW: container.VolumesRW, + AppArmorProfile: container.AppArmorProfile, + ExecIDs: container.GetExecIDs(), + HostConfig: &hostConfig, + } + + return contJSON, nil } func (daemon *Daemon) ContainerExecInspect(id string) (*execConfig, error) { diff --git a/docker/docker.go b/docker/docker.go index d2d4986ac..1096b840f 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -9,9 +9,9 @@ import ( "strings" "github.com/Sirupsen/logrus" - "github.com/docker/docker/api" "github.com/docker/docker/api/client" "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/term" @@ -63,9 +63,9 @@ func main() { defaultHost := os.Getenv("DOCKER_HOST") if defaultHost == "" || *flDaemon { // If we do not have a host, default to unix socket - defaultHost = fmt.Sprintf("unix://%s", api.DEFAULTUNIXSOCKET) + defaultHost = fmt.Sprintf("unix://%s", opts.DefaultUnixSocket) } - defaultHost, err := api.ValidateHost(defaultHost) + defaultHost, err := opts.ValidateHost(defaultHost) if err != nil { logrus.Fatal(err) } diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 855352973..e427fdf0d 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -20,7 +20,7 @@ import ( "strings" "time" - "github.com/docker/docker/api" + "github.com/docker/docker/opts" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stringutils" "github.com/go-check/check" @@ -274,7 +274,7 @@ func (d *Daemon) LogfileName() string { } func daemonHost() string { - daemonUrlStr := "unix://" + api.DEFAULTUNIXSOCKET + daemonUrlStr := "unix://" + opts.DefaultUnixSocket if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" { daemonUrlStr = daemonHostVar } diff --git a/opts/opts.go b/opts/opts.go index df9decf61..d2c32f13c 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -8,16 +8,16 @@ import ( "regexp" "strings" - "github.com/docker/docker/api" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/ulimit" - "github.com/docker/docker/utils" ) var ( - alphaRegexp = regexp.MustCompile(`[a-zA-Z]`) - domainRegexp = 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*$`) + alphaRegexp = regexp.MustCompile(`[a-zA-Z]`) + domainRegexp = 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*$`) + DefaultHTTPHost = "127.0.0.1" // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080 + DefaultUnixSocket = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket ) func ListVar(values *[]string, names []string, usage string) { @@ -25,7 +25,7 @@ func ListVar(values *[]string, names []string, usage string) { } func HostListVar(values *[]string, names []string, usage string) { - flag.Var(newListOptsRef(values, api.ValidateHost), names, usage) + flag.Var(newListOptsRef(values, ValidateHost), names, usage) } func IPListVar(values *[]string, names []string, usage string) { @@ -174,7 +174,7 @@ func ValidateEnv(val string) (string, error) { if len(arr) > 1 { return val, nil } - if !utils.DoesEnvExist(val) { + if !doesEnvExist(val) { return val, nil } return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil @@ -234,3 +234,21 @@ func ValidateLabel(val string) (string, error) { } return val, nil } + +func ValidateHost(val string) (string, error) { + host, err := parsers.ParseHost(DefaultHTTPHost, DefaultUnixSocket, val) + if err != nil { + return val, err + } + return host, nil +} + +func doesEnvExist(name string) bool { + for _, entry := range os.Environ() { + parts := strings.SplitN(entry, "=", 2) + if parts[0] == name { + return true + } + } + return false +} diff --git a/utils/utils.go b/utils/utils.go index ab5982678..05dfb757a 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -239,16 +239,6 @@ func ReplaceOrAppendEnvValues(defaults, overrides []string) []string { return defaults } -func DoesEnvExist(name string) bool { - for _, entry := range os.Environ() { - parts := strings.SplitN(entry, "=", 2) - if parts[0] == name { - return true - } - } - return false -} - // ValidateContextDirectory checks if all the contents of the directory // can be read and returns an error if some files can't be read // symlinks which point to non-existing files don't trigger an error From f3680e74946d3a773edb118ea3f508b8237e44a8 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 16 Dec 2014 10:06:45 -0500 Subject: [PATCH 583/999] Cleanup daemon/volumes - Mount struct now called volumeMount - Merged volume creation for each volume type (volumes-from, binds, normal volumes) so this only happens in once place - Simplified container copy of volumes (for when `docker cp` is a volume) Signed-off-by: Brian Goff --- daemon/volumes.go | 327 +++++++++-------------- integration-cli/docker_cli_build_test.go | 2 +- 2 files changed, 132 insertions(+), 197 deletions(-) diff --git a/daemon/volumes.go b/daemon/volumes.go index 7c6696d65..4d15023ba 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -14,17 +14,14 @@ import ( "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" - "github.com/docker/docker/volumes" ) -type Mount struct { - MountToPath string - container *Container - volume *volumes.Volume - Writable bool - copyData bool - from *Container - isBind bool +type volumeMount struct { + containerPath string + hostPath string + writable bool + copyData bool + from string } func (container *Container) prepareVolumes() error { @@ -33,9 +30,110 @@ func (container *Container) prepareVolumes() error { container.VolumesRW = make(map[string]bool) } + if len(container.hostConfig.VolumesFrom) > 0 && container.AppliedVolumesFrom == nil { + container.AppliedVolumesFrom = make(map[string]struct{}) + } return container.createVolumes() } +func (container *Container) createVolumes() error { + mounts := make(map[string]*volumeMount) + + // get the normal volumes + for path := range container.Config.Volumes { + path = filepath.Clean(path) + // skip if there is already a volume for this container path + if _, exists := container.Volumes[path]; exists { + continue + } + + realPath, err := container.getResourcePath(path) + if err != nil { + return err + } + if stat, err := os.Stat(realPath); err == nil { + if !stat.IsDir() { + return fmt.Errorf("can't mount to container path, file exists - %s", path) + } + } + + mnt := &volumeMount{ + containerPath: path, + writable: true, + copyData: true, + } + mounts[mnt.containerPath] = mnt + } + + // Get all the bind mounts + // track bind paths separately due to #10618 + bindPaths := make(map[string]struct{}) + for _, spec := range container.hostConfig.Binds { + mnt, err := parseBindMountSpec(spec) + if err != nil { + return err + } + + // #10618 + if _, exists := bindPaths[mnt.containerPath]; exists { + return fmt.Errorf("Duplicate volume mount %s", mnt.containerPath) + } + + bindPaths[mnt.containerPath] = struct{}{} + mounts[mnt.containerPath] = mnt + } + + // Get volumes from + for _, from := range container.hostConfig.VolumesFrom { + cID, mode, err := parseVolumesFromSpec(from) + if err != nil { + return err + } + if _, exists := container.AppliedVolumesFrom[cID]; exists { + // skip since it's already been applied + continue + } + + c, err := container.daemon.Get(cID) + if err != nil { + return fmt.Errorf("container %s not found, impossible to mount its volumes", cID) + } + + for _, mnt := range c.volumeMounts() { + mnt.writable = mnt.writable && (mode == "rw") + mnt.from = cID + mounts[mnt.containerPath] = mnt + } + } + + for _, mnt := range mounts { + containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(container.basefs, mnt.containerPath), container.basefs) + if err != nil { + return err + } + + // Create the actual volume + v, err := container.daemon.volumes.FindOrCreateVolume(mnt.hostPath, mnt.writable) + if err != nil { + return err + } + + container.VolumesRW[mnt.containerPath] = mnt.writable + container.Volumes[mnt.containerPath] = v.Path + v.AddContainer(container.ID) + if mnt.from != "" { + container.AppliedVolumesFrom[mnt.from] = struct{}{} + } + + if mnt.writable && mnt.copyData { + // Copy whatever is in the container at the containerPath to the volume + copyExistingContents(containerMntPath, v.Path) + } + } + + return nil +} + // sortedVolumeMounts returns the list of container volume mount points sorted in lexicographic order func (container *Container) sortedVolumeMounts() []string { var mountPaths []string @@ -47,58 +145,6 @@ func (container *Container) sortedVolumeMounts() []string { return mountPaths } -func (container *Container) createVolumes() error { - mounts, err := container.parseVolumeMountConfig() - if err != nil { - return err - } - - for _, mnt := range mounts { - if err := mnt.initialize(); err != nil { - return err - } - } - - // On every start, this will apply any new `VolumesFrom` entries passed in via HostConfig, which may override volumes set in `create` - return container.applyVolumesFrom() -} - -func (m *Mount) initialize() error { - // No need to initialize anything since it's already been initialized - if hostPath, exists := m.container.Volumes[m.MountToPath]; exists { - // If this is a bind-mount/volumes-from, maybe it was passed in at start instead of create - // We need to make sure bind-mounts/volumes-from passed on start can override existing ones. - if (!m.volume.IsBindMount && !m.isBind) && m.from == nil { - return nil - } - if m.volume.Path == hostPath { - return nil - } - - // Make sure we remove these old volumes we don't actually want now. - // Ignore any errors here since this is just cleanup, maybe someone volumes-from'd this volume - if v := m.container.daemon.volumes.Get(hostPath); v != nil { - v.RemoveContainer(m.container.ID) - m.container.daemon.volumes.Delete(v.Path) - } - } - - // This is the full path to container fs + mntToPath - containerMntPath, err := symlink.FollowSymlinkInScope(filepath.Join(m.container.basefs, m.MountToPath), m.container.basefs) - if err != nil { - return err - } - m.container.VolumesRW[m.MountToPath] = m.Writable - m.container.Volumes[m.MountToPath] = m.volume.Path - m.volume.AddContainer(m.container.ID) - if m.Writable && m.copyData { - // Copy whatever is in the container at the mntToPath to the volume - copyExistingContents(containerMntPath, m.volume.Path) - } - - return nil -} - func (container *Container) VolumePaths() map[string]struct{} { var paths = make(map[string]struct{}) for _, path := range container.Volumes { @@ -139,97 +185,30 @@ func (container *Container) derefVolumes() { } } -func (container *Container) parseVolumeMountConfig() (map[string]*Mount, error) { - var mounts = make(map[string]*Mount) - // Get all the bind mounts - for _, spec := range container.hostConfig.Binds { - path, mountToPath, writable, err := parseBindMountSpec(spec) - if err != nil { - return nil, err - } - // Check if a bind mount has already been specified for the same container path - if m, exists := mounts[mountToPath]; exists { - return nil, fmt.Errorf("Duplicate volume %q: %q already in use, mounted from %q", path, mountToPath, m.volume.Path) - } - // Check if a volume already exists for this and use it - vol, err := container.daemon.volumes.FindOrCreateVolume(path, writable) - if err != nil { - return nil, err - } - mounts[mountToPath] = &Mount{ - container: container, - volume: vol, - MountToPath: mountToPath, - Writable: writable, - isBind: true, // in case the volume itself is a normal volume, but is being mounted in as a bindmount here - } - } - - // Get the rest of the volumes - for path := range container.Config.Volumes { - // Check if this is already added as a bind-mount - path = filepath.Clean(path) - if _, exists := mounts[path]; exists { - continue - } - - // Check if this has already been created - if _, exists := container.Volumes[path]; exists { - continue - } - realPath, err := container.getResourcePath(path) - if err != nil { - return nil, fmt.Errorf("failed to evaluate the absolute path of symlink") - } - if stat, err := os.Stat(realPath); err == nil { - if !stat.IsDir() { - return nil, fmt.Errorf("file exists at %s, can't create volume there", realPath) - } - } - - vol, err := container.daemon.volumes.FindOrCreateVolume("", true) - if err != nil { - return nil, err - } - mounts[path] = &Mount{ - container: container, - MountToPath: path, - volume: vol, - Writable: true, - copyData: true, - } - } - - return mounts, nil -} - -func parseBindMountSpec(spec string) (string, string, bool, error) { - var ( - path, mountToPath string - writable bool - arr = strings.Split(spec, ":") - ) +func parseBindMountSpec(spec string) (*volumeMount, error) { + arr := strings.Split(spec, ":") + mnt := &volumeMount{} switch len(arr) { case 2: - path = arr[0] - mountToPath = arr[1] - writable = true + mnt.hostPath = arr[0] + mnt.containerPath = arr[1] + mnt.writable = true case 3: - path = arr[0] - mountToPath = arr[1] - writable = validMountMode(arr[2]) && arr[2] == "rw" + mnt.hostPath = arr[0] + mnt.containerPath = arr[1] + mnt.writable = validMountMode(arr[2]) && arr[2] == "rw" default: - return "", "", false, fmt.Errorf("Invalid volume specification: %s", spec) + return nil, fmt.Errorf("Invalid volume specification: %s", spec) } - if !filepath.IsAbs(path) { - return "", "", false, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", path) + if !filepath.IsAbs(mnt.hostPath) { + return nil, fmt.Errorf("cannot bind mount volume: %s volume paths must be absolute.", mnt.hostPath) } - path = filepath.Clean(path) - mountToPath = filepath.Clean(mountToPath) - return path, mountToPath, writable, nil + mnt.hostPath = filepath.Clean(mnt.hostPath) + mnt.containerPath = filepath.Clean(mnt.containerPath) + return mnt, nil } func parseVolumesFromSpec(spec string) (string, string, error) { @@ -251,54 +230,6 @@ func parseVolumesFromSpec(spec string) (string, string, error) { return id, mode, nil } -func (container *Container) applyVolumesFrom() error { - volumesFrom := container.hostConfig.VolumesFrom - if len(volumesFrom) > 0 && container.AppliedVolumesFrom == nil { - container.AppliedVolumesFrom = make(map[string]struct{}) - } - - mountGroups := make(map[string][]*Mount) - - for _, spec := range volumesFrom { - id, mode, err := parseVolumesFromSpec(spec) - if err != nil { - return err - } - if _, exists := container.AppliedVolumesFrom[id]; exists { - // Don't try to apply these since they've already been applied - continue - } - - c, err := container.daemon.Get(id) - if err != nil { - return fmt.Errorf("Could not apply volumes of non-existent container %q.", id) - } - - var ( - fromMounts = c.VolumeMounts() - mounts []*Mount - ) - - for _, mnt := range fromMounts { - mnt.Writable = mnt.Writable && (mode == "rw") - mounts = append(mounts, mnt) - } - mountGroups[id] = mounts - } - - for id, mounts := range mountGroups { - for _, mnt := range mounts { - mnt.from = mnt.container - mnt.container = container - if err := mnt.initialize(); err != nil { - return err - } - } - container.AppliedVolumesFrom[id] = struct{}{} - } - return nil -} - func validMountMode(mode string) bool { validModes := map[string]bool{ "rw": true, @@ -344,13 +275,17 @@ func (container *Container) setupMounts() error { return nil } -func (container *Container) VolumeMounts() map[string]*Mount { - mounts := make(map[string]*Mount) +func (container *Container) volumeMounts() map[string]*volumeMount { + mounts := make(map[string]*volumeMount) - for mountToPath, path := range container.Volumes { - if v := container.daemon.volumes.Get(path); v != nil { - mounts[mountToPath] = &Mount{volume: v, container: container, MountToPath: mountToPath, Writable: container.VolumesRW[mountToPath]} + for containerPath, path := range container.Volumes { + v := container.daemon.volumes.Get(path) + if v == nil { + // This should never happen + logrus.Debugf("reference by container %s to non-existent volume path %s", container.ID, path) + continue } + mounts[containerPath] = &volumeMount{hostPath: path, containerPath: containerPath, writable: container.VolumesRW[containerPath]} } return mounts diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index a4ccd18ea..df5621af1 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -4504,7 +4504,7 @@ func (s *DockerSuite) TestBuildExoticShellInterpolation(c *check.C) { _, err := buildImage(name, ` FROM busybox - + ENV SOME_VAR a.b.c RUN [ "$SOME_VAR" = 'a.b.c' ] From b0ef3194aaa8c22b674ed5301c59e8e557a7e85e Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Thu, 23 Apr 2015 09:28:07 +0800 Subject: [PATCH 584/999] fix inspect format result Currently `docker inspect -f` use json.Unmarshal() unmarshal to interface, it will store all JSON numbers in float64, so we use `docker inspect 4f0d73b75a0d | grep Memory` and `docker inspect -f {{.HostConfig.Memory}} 4f0d73b75a0d` will get different values. Signed-off-by: Qiang Huang --- api/client/inspect.go | 9 +++++++-- integration-cli/docker_cli_build_test.go | 16 +++++++--------- integration-cli/docker_cli_inspect_test.go | 20 ++++++++++++++++++++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/api/client/inspect.go b/api/client/inspect.go index f993030f9..75861cdf2 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -57,9 +57,14 @@ func (cli *DockerCli) CmdInspect(args ...string) error { continue } } else { - // Has template, will render var value interface{} - if err := json.Unmarshal(obj, &value); err != nil { + + // Do not use `json.Unmarshal()` because unmarshal JSON into + // an interface value, Unmarshal stores JSON numbers in + // float64, which is different from `json.Indent()` does. + dec := json.NewDecoder(bytes.NewReader(obj)) + dec.UseNumber() + if err := dec.Decode(&value); err != nil { fmt.Fprintf(cli.err, "%s\n", err) status = 1 continue diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index df5621af1..7936b2bc3 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5383,11 +5383,11 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { cID := strings.TrimSpace(out) type hostConfig struct { - Memory float64 // Use float64 here since the json decoder sees it that way - MemorySwap int + Memory int64 + MemorySwap int64 CpusetCpus string CpusetMems string - CpuShares int + CpuShares int64 } cfg, err := inspectFieldJSON(cID, "HostConfig") @@ -5399,10 +5399,9 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { if err := json.Unmarshal([]byte(cfg), &c1); err != nil { c.Fatal(err, cfg) } - mem := int64(c1.Memory) - if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 { + if c1.Memory != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 { c.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", - mem, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares) + c1.Memory, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares) } // Make sure constraints aren't saved to image @@ -5416,10 +5415,9 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { if err := json.Unmarshal([]byte(cfg), &c2); err != nil { c.Fatal(err, cfg) } - mem = int64(c2.Memory) - if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 { + if c2.Memory == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 { c.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", - mem, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares) + c2.Memory, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares) } } diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go index 73eb7c8af..9a5a7b660 100644 --- a/integration-cli/docker_cli_inspect_test.go +++ b/integration-cli/docker_cli_inspect_test.go @@ -21,3 +21,23 @@ func (s *DockerSuite) TestInspectImage(c *check.C) { } } + +func (s *DockerSuite) TestInspectInt64(c *check.C) { + runCmd := exec.Command(dockerBinary, "run", "-d", "-m=300M", "busybox", "true") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + if err != nil { + c.Fatalf("failed to run container: %v, output: %q", err, out) + } + + out = strings.TrimSpace(out) + + inspectCmd := exec.Command(dockerBinary, "inspect", "-f", "{{.HostConfig.Memory}}", out) + inspectOut, _, err := runCommandWithOutput(inspectCmd) + if err != nil { + c.Fatalf("failed to inspect container: %v, output: %q", err, inspectOut) + } + + if strings.TrimSpace(inspectOut) != "314572800" { + c.Fatalf("inspect got wrong value, got: %q, expected: 314572800", inspectOut) + } +} From dde0cc78bdec31be1ecbd7def6a83111224ccc55 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Thu, 23 Apr 2015 10:23:02 +0800 Subject: [PATCH 585/999] Move setHostConfig to daemon file Signed-off-by: Ma Shimiao --- daemon/daemon.go | 18 ++++++++++++++++++ daemon/start.go | 18 ------------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 8873b4cac..bfebd920f 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1248,3 +1248,21 @@ func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]stri return warnings, nil } + +func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.HostConfig) error { + container.Lock() + defer container.Unlock() + if err := parseSecurityOpt(container, hostConfig); err != nil { + return err + } + + // Register any links from the host config before starting the container + if err := daemon.RegisterLinks(container, hostConfig); err != nil { + return err + } + + container.hostConfig = hostConfig + container.toDisk() + + return nil +} diff --git a/daemon/start.go b/daemon/start.go index d3af073a8..09b8b2881 100644 --- a/daemon/start.go +++ b/daemon/start.go @@ -39,21 +39,3 @@ func (daemon *Daemon) ContainerStart(name string, hostConfig *runconfig.HostConf return nil } - -func (daemon *Daemon) setHostConfig(container *Container, hostConfig *runconfig.HostConfig) error { - container.Lock() - defer container.Unlock() - if err := parseSecurityOpt(container, hostConfig); err != nil { - return err - } - - // Register any links from the host config before starting the container - if err := daemon.RegisterLinks(container, hostConfig); err != nil { - return err - } - - container.hostConfig = hostConfig - container.toDisk() - - return nil -} From 05418df539dfed118da099aacfe4250f2f6ad5e0 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Thu, 23 Apr 2015 11:11:23 +0800 Subject: [PATCH 586/999] sysinfo: add IPv4Forwarding check Signed-off-by: Ma Shimiao --- pkg/sysinfo/sysinfo.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 76a61fa95..57e5563a8 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -4,6 +4,8 @@ import ( "io/ioutil" "os" "path" + "strconv" + "strings" "github.com/Sirupsen/logrus" "github.com/docker/libcontainer/cgroups" @@ -52,6 +54,17 @@ func New(quiet bool) *SysInfo { } } + // Checek if ipv4_forward is disabled. + if data, err := ioutil.ReadFile("/proc/sys/net/ipv4/ip_forward"); os.IsNotExist(err) { + sysInfo.IPv4ForwardingDisabled = true + } else { + if enabled, _ := strconv.Atoi(strings.TrimSpace(string(data))); enabled == 0 { + sysInfo.IPv4ForwardingDisabled = true + } else { + sysInfo.IPv4ForwardingDisabled = false + } + } + // Check if AppArmor is supported. if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) { sysInfo.AppArmor = false From 63593267619378520a03e8984c5fcf0ec8957537 Mon Sep 17 00:00:00 2001 From: Rick Wieman Date: Tue, 21 Apr 2015 17:50:09 +0200 Subject: [PATCH 587/999] Makes headings in documentation consistent Fixes #10673. Signed-off-by: Rick Wieman --- docs/mkdocs.yml | 22 +++++----- .../articles/ambassador_pattern_linking.md | 6 +-- docs/sources/articles/b2d_volume_resize.md | 4 +- docs/sources/articles/baseimages.md | 4 +- .../articles/cfengine_process_management.md | 4 +- docs/sources/articles/chef.md | 2 +- .../articles/dockerfile_best-practices.md | 6 +-- docs/sources/articles/host_integration.md | 6 +-- docs/sources/articles/https.md | 6 +-- docs/sources/articles/networking.md | 14 +++---- docs/sources/articles/puppet.md | 2 +- docs/sources/articles/runmetrics.md | 16 ++++---- docs/sources/articles/security.md | 14 +++---- docs/sources/articles/systemd.md | 8 ++-- .../docker-hub-enterprise/install-config.md | 6 +-- docs/sources/docker-hub/accounts.md | 6 +-- docs/sources/docker-hub/builds.md | 6 +-- docs/sources/docker-hub/home.md | 4 +- docs/sources/docker-hub/index.md | 4 +- docs/sources/docker-hub/official_repos.md | 10 ++--- docs/sources/docker-hub/repos.md | 10 ++--- docs/sources/examples.md | 12 +++--- docs/sources/examples/apt-cacher-ng.md | 2 +- docs/sources/examples/couchdb_data_volumes.md | 4 +- docs/sources/examples/nodejs_web_app.md | 4 +- .../sources/examples/running_redis_service.md | 4 +- docs/sources/examples/running_riak_service.md | 2 +- docs/sources/examples/running_ssh_service.md | 2 +- docs/sources/http-routingtable.md | 2 +- docs/sources/index.md | 6 +-- docs/sources/installation/azure.md | 2 +- docs/sources/installation/binaries.md | 8 ++-- docs/sources/installation/cruxlinux.md | 2 +- docs/sources/installation/mac.md | 4 +- docs/sources/installation/oracle.md | 2 +- docs/sources/installation/rhel.md | 4 +- docs/sources/installation/ubuntulinux.md | 4 +- docs/sources/installation/windows.md | 4 +- .../introduction/understanding-docker.md | 6 +-- docs/sources/project/advanced-contributing.md | 2 +- docs/sources/project/coding-style.md | 4 +- docs/sources/project/create-pr.md | 2 +- docs/sources/project/doc-style.md | 2 +- docs/sources/project/review-pr.md | 6 +-- docs/sources/reference/api/docker-io_api.md | 22 +++++----- .../reference/api/docker_io_accounts_api.md | 4 +- .../reference/api/docker_remote_api.md | 40 +++++++++---------- .../reference/api/docker_remote_api_v1.9.md | 6 +-- .../reference/api/hub_registry_spec.md | 6 +-- .../api/registry_api_client_libraries.md | 4 +- .../api/remote_api_client_libraries.md | 4 +- docs/sources/reference/builder.md | 10 ++--- docs/sources/reference/commandline/cli.md | 6 +-- docs/sources/reference/run.md | 4 +- docs/sources/release-notes.md | 12 +++--- docs/sources/terms/container.md | 2 +- docs/sources/terms/filesystem.md | 4 +- docs/sources/terms/image.md | 6 +-- docs/sources/terms/registry.md | 2 +- docs/sources/userguide/dockerhub.md | 4 +- docs/sources/userguide/dockerimages.md | 4 +- docs/sources/userguide/dockerizing.md | 8 ++-- docs/sources/userguide/dockerlinks.md | 8 ++-- docs/sources/userguide/dockerrepos.md | 4 +- docs/sources/userguide/dockervolumes.md | 10 ++--- docs/sources/userguide/index.md | 18 ++++----- docs/sources/userguide/level1.md | 4 +- docs/sources/userguide/level2.md | 4 +- docs/sources/userguide/usingdocker.md | 22 +++++----- 69 files changed, 234 insertions(+), 234 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 1ff27071d..df9d95997 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -25,7 +25,7 @@ pages: # Introduction: - ['index.md', 'About', 'Docker'] -- ['release-notes.md', 'About', 'Release Notes'] +- ['release-notes.md', 'About', 'Release notes'] - ['introduction/index.md', '**HIDDEN**'] - ['introduction/understanding-docker.md', 'About', 'Understanding Docker'] @@ -54,11 +54,11 @@ pages: - ['compose/install.md', 'Installation', 'Docker Compose'] # User Guide: -- ['userguide/index.md', 'User Guide', 'The Docker User Guide' ] -- ['userguide/dockerhub.md', 'User Guide', 'Getting Started with Docker Hub' ] -- ['userguide/dockerizing.md', 'User Guide', 'Dockerizing Applications' ] -- ['userguide/usingdocker.md', 'User Guide', 'Working with Containers' ] -- ['userguide/dockerimages.md', 'User Guide', 'Working with Docker Images' ] +- ['userguide/index.md', 'User Guide', 'The Docker user guide' ] +- ['userguide/dockerhub.md', 'User Guide', 'Getting started with Docker Hub' ] +- ['userguide/dockerizing.md', 'User Guide', 'Dockerizing applications' ] +- ['userguide/usingdocker.md', 'User Guide', 'Working with containers' ] +- ['userguide/dockerimages.md', 'User Guide', 'Working with Docker images' ] - ['userguide/dockerlinks.md', 'User Guide', 'Linking containers together' ] - ['userguide/dockervolumes.md', 'User Guide', 'Managing data in containers' ] - ['userguide/labels-custom-metadata.md', 'User Guide', 'Apply custom metadata' ] @@ -76,7 +76,7 @@ pages: - ['docker-hub/accounts.md', 'Docker Hub', 'Accounts'] - ['docker-hub/repos.md', 'Docker Hub', 'Repositories'] - ['docker-hub/builds.md', 'Docker Hub', 'Automated Builds'] -- ['docker-hub/official_repos.md', 'Docker Hub', 'Official Repo Guidelines'] +- ['docker-hub/official_repos.md', 'Docker Hub', 'Official repo guidelines'] # Docker Hub Enterprise #- ['docker-hub-enterprise/index.md', '**HIDDEN**' ] @@ -125,7 +125,7 @@ pages: - ['reference/commandline/cli.md', 'Reference', 'Docker command line'] - ['reference/builder.md', 'Reference', 'Dockerfile'] - ['faq.md', 'Reference', 'FAQ'] -- ['reference/run.md', 'Reference', 'Run Reference'] +- ['reference/run.md', 'Reference', 'Run reference'] - ['compose/cli.md', 'Reference', 'Compose command line'] - ['compose/yml.md', 'Reference', 'Compose yml'] - ['compose/env.md', 'Reference', 'Compose ENV variables'] @@ -145,7 +145,7 @@ pages: - ['registry/spec/auth/token.md', 'Reference', '    ▪  Authenticate via central service' ] - ['reference/api/hub_registry_spec.md', 'Reference', 'Docker Hub and Registry 1.0'] - ['reference/api/registry_api.md', 'Reference', '    ▪ Docker Registry API v1'] -- ['reference/api/registry_api_client_libraries.md', 'Reference', '    ▪ Docker Registry 1.0 API Client Libraries'] +- ['reference/api/registry_api_client_libraries.md', 'Reference', '    ▪ Docker Registry 1.0 API client libraries'] #- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0'] - ['reference/api/docker-io_api.md', 'Reference', 'Docker Hub API'] #- ['reference/image-spec-v1.md', 'Reference', 'Docker Image Specification v1.0.0'] @@ -169,8 +169,8 @@ pages: - ['reference/api/docker_remote_api_v1.2.md', '**HIDDEN**'] - ['reference/api/docker_remote_api_v1.1.md', '**HIDDEN**'] - ['reference/api/docker_remote_api_v1.0.md', '**HIDDEN**'] -- ['reference/api/remote_api_client_libraries.md', 'Reference', 'Docker Remote API Client Libraries'] -- ['reference/api/docker_io_accounts_api.md', 'Reference', 'Docker Hub Accounts API'] +- ['reference/api/remote_api_client_libraries.md', 'Reference', 'Docker Remote API client libraries'] +- ['reference/api/docker_io_accounts_api.md', 'Reference', 'Docker Hub accounts API'] # Hidden registry files - ['registry/storage-drivers/azure.md', '**HIDDEN**' ] diff --git a/docs/sources/articles/ambassador_pattern_linking.md b/docs/sources/articles/ambassador_pattern_linking.md index 755fa4dc9..2f168262a 100644 --- a/docs/sources/articles/ambassador_pattern_linking.md +++ b/docs/sources/articles/ambassador_pattern_linking.md @@ -1,8 +1,8 @@ -page_title: Link via an Ambassador Container +page_title: Link via an ambassador container page_description: Using the Ambassador pattern to abstract (network) services page_keywords: Examples, Usage, links, docker, documentation, examples, names, name, container naming -# Link via an Ambassador Container +# Link via an ambassador container ## Introduction @@ -30,7 +30,7 @@ different docker host from the consumer. Using the `svendowideit/ambassador` container, the link wiring is controlled entirely from the `docker run` parameters. -## Two host Example +## Two host example Start actual Redis server on one Docker host diff --git a/docs/sources/articles/b2d_volume_resize.md b/docs/sources/articles/b2d_volume_resize.md index 1b39b49ed..65238c669 100644 --- a/docs/sources/articles/b2d_volume_resize.md +++ b/docs/sources/articles/b2d_volume_resize.md @@ -1,5 +1,5 @@ -page_title: Resizing a Boot2Docker Volume -page_description: Resizing a Boot2Docker Volume in VirtualBox with GParted +page_title: Resizing a Boot2Docker volume +page_description: Resizing a Boot2Docker volume in VirtualBox with GParted page_keywords: boot2docker, volume, virtualbox # Getting “no space left on device” errors with Boot2Docker? diff --git a/docs/sources/articles/baseimages.md b/docs/sources/articles/baseimages.md index 701f432ff..a54f5307a 100644 --- a/docs/sources/articles/baseimages.md +++ b/docs/sources/articles/baseimages.md @@ -1,8 +1,8 @@ -page_title: Create a Base Image +page_title: Create a base image page_description: How to create base images page_keywords: Examples, Usage, base image, docker, documentation, examples -# Create a Base Image +# Create a base image So you want to create your own [*Base Image*]( /terms/image/#base-image)? Great! diff --git a/docs/sources/articles/cfengine_process_management.md b/docs/sources/articles/cfengine_process_management.md index a9441a6d3..b0437268b 100644 --- a/docs/sources/articles/cfengine_process_management.md +++ b/docs/sources/articles/cfengine_process_management.md @@ -1,8 +1,8 @@ -page_title: Process Management with CFEngine +page_title: Process management with CFEngine page_description: Managing containerized processes with CFEngine page_keywords: cfengine, process, management, usage, docker, documentation -# Process Management with CFEngine +# Process management with CFEngine Create Docker containers with managed processes. diff --git a/docs/sources/articles/chef.md b/docs/sources/articles/chef.md index 8fe0504ff..84ccdffb2 100644 --- a/docs/sources/articles/chef.md +++ b/docs/sources/articles/chef.md @@ -1,4 +1,4 @@ -page_title: Chef Usage +page_title: Using Chef page_description: Installation and using Docker via Chef page_keywords: chef, installation, usage, docker, documentation diff --git a/docs/sources/articles/dockerfile_best-practices.md b/docs/sources/articles/dockerfile_best-practices.md index 83a77fc74..425eb8658 100644 --- a/docs/sources/articles/dockerfile_best-practices.md +++ b/docs/sources/articles/dockerfile_best-practices.md @@ -1,4 +1,4 @@ -page_title: Best Practices for Writing Dockerfiles +page_title: Best practices for writing Dockerfiles page_description: Hints, tips and guidelines for writing clean, reliable Dockerfiles page_keywords: Examples, Usage, base image, docker, documentation, dockerfile, best practices, hub, official repo @@ -419,7 +419,7 @@ fail catastrophically if the new build's context is missing the resource being added. Adding a separate tag, as recommended above, will help mitigate this by allowing the `Dockerfile` author to make a choice. -## Examples For Official Repositories +## Examples for official repositories These Official Repos have exemplary `Dockerfile`s: @@ -428,7 +428,7 @@ These Official Repos have exemplary `Dockerfile`s: * [Hy](https://registry.hub.docker.com/_/hylang/) * [Rails](https://registry.hub.docker.com/_/rails) -## Additional Resources: +## Additional resources: * [Dockerfile Reference](https://docs.docker.com/reference/builder/#onbuild) * [More about Base Images](https://docs.docker.com/articles/baseimages/) diff --git a/docs/sources/articles/host_integration.md b/docs/sources/articles/host_integration.md index cbcb21a35..e3451764b 100644 --- a/docs/sources/articles/host_integration.md +++ b/docs/sources/articles/host_integration.md @@ -1,8 +1,8 @@ -page_title: Automatically Start Containers +page_title: Automatically start containers page_description: How to generate scripts for upstart, systemd, etc. page_keywords: systemd, upstart, supervisor, docker, documentation, host integration -# Automatically Start Containers +# Automatically start containers As of Docker 1.2, [restart policies](/reference/commandline/cli/#restart-policies) are the @@ -18,7 +18,7 @@ that depend on Docker containers), you can use a process manager like [supervisor](http://supervisord.org/) instead. -## Using a Process Manager +## Using a process manager Docker does not set any restart policies by default, but be aware that they will conflict with most process managers. So don't set restart policies if you are diff --git a/docs/sources/articles/https.md b/docs/sources/articles/https.md index 94d9ca3f2..d6689bbf1 100644 --- a/docs/sources/articles/https.md +++ b/docs/sources/articles/https.md @@ -1,8 +1,8 @@ -page_title: Protecting the Docker daemon Socket with HTTPS +page_title: Protecting the Docker daemon socket with HTTPS page_description: How to setup and run Docker with HTTPS page_keywords: docker, docs, article, example, https, daemon, tls, ca, certificate -# Protecting the Docker daemon Socket with HTTPS +# Protecting the Docker daemon socket with HTTPS By default, Docker runs via a non-networked Unix socket. It can also optionally communicate using a HTTP socket. @@ -193,7 +193,7 @@ location using the environment variable `DOCKER_CERT_PATH`. $ export DOCKER_CERT_PATH=~/.docker/zone1/ $ docker --tlsverify ps -### Connecting to the Secure Docker port using `curl` +### Connecting to the secure Docker port using `curl` To use `curl` to make test API requests, you need to use three extra command line flags: diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index 2ce52ce0e..18529a086 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -1,8 +1,8 @@ -page_title: Network Configuration +page_title: Network configuration page_description: Docker networking page_keywords: network, networking, bridge, docker, documentation -# Network Configuration +# Network configuration ## TL;DR @@ -41,7 +41,7 @@ can use Docker options and — in advanced cases — raw Linux networking commands to tweak, supplement, or entirely replace Docker's default networking configuration. -## Quick Guide to the Options +## Quick guide to the options Here is a quick list of the networking-related Docker command-line options, in case it helps you find the section below that you are @@ -601,9 +601,9 @@ You have to execute the `ip -6 neigh add proxy ...` command for every IPv6 address in your Docker subnet. Unfortunately there is no functionality for adding a whole subnet by executing one command. -### Docker IPv6 Cluster +### Docker IPv6 cluster -#### Switched Network Environment +#### Switched network environment Using routable IPv6 addresses allows you to realize communication between containers on different hosts. Let's have a look at a simple Docker IPv6 cluster example: @@ -649,7 +649,7 @@ the Docker subnet on the host, the container IP addresses and the routes on the containers. The configuration above the line is up to the user and can be adapted to the individual environment. -#### Routed Network Environment +#### Routed network environment In a routed network environment you replace the layer 2 switch with a layer 3 router. Now the hosts just have to know their default gateway (the router) and @@ -993,7 +993,7 @@ of the right to configure their own networks. Using `ip netns exec` is what let us finish up the configuration without having to take the dangerous step of running the container itself with `--privileged=true`. -## Tools and Examples +## Tools and examples Before diving into the following sections on custom network topologies, you might be interested in glancing at a few external tools or examples diff --git a/docs/sources/articles/puppet.md b/docs/sources/articles/puppet.md index 705285fba..50504cd47 100644 --- a/docs/sources/articles/puppet.md +++ b/docs/sources/articles/puppet.md @@ -1,4 +1,4 @@ -page_title: Puppet Usage +page_title: Using Puppet page_description: Installating and using Puppet page_keywords: puppet, installation, usage, docker, documentation diff --git a/docs/sources/articles/runmetrics.md b/docs/sources/articles/runmetrics.md index 327640969..a887d4369 100644 --- a/docs/sources/articles/runmetrics.md +++ b/docs/sources/articles/runmetrics.md @@ -1,8 +1,8 @@ -page_title: Runtime Metrics +page_title: Runtime metrics page_description: Measure the behavior of running containers page_keywords: docker, metrics, CPU, memory, disk, IO, run, runtime -# Runtime Metrics +# Runtime metrics Linux Containers rely on [control groups]( https://www.kernel.org/doc/Documentation/cgroups/cgroups.txt) @@ -11,7 +11,7 @@ CPU, memory, and block I/O usage. You can access those metrics and obtain network usage metrics as well. This is relevant for "pure" LXC containers, as well as for Docker containers. -## Control Groups +## Control groups Control groups are exposed through a pseudo-filesystem. In recent distros, you should find this filesystem under `/sys/fs/cgroup`. Under @@ -28,7 +28,7 @@ To figure out where your control groups are mounted, you can run: $ grep cgroup /proc/mounts -## Enumerating Cgroups +## Enumerating cgroups You can look into `/proc/cgroups` to see the different control group subsystems known to the system, the hierarchy they belong to, and how many groups they contain. @@ -39,7 +39,7 @@ the hierarchy mountpoint; e.g., `/` means “this process has not been assigned a particular group”, while `/lxc/pumpkin` means that the process is likely to be a member of a container named `pumpkin`. -## Finding the Cgroup for a Given Container +## Finding the cgroup for a given container For each container, one cgroup will be created in each hierarchy. On older systems with older versions of the LXC userland tools, the name of @@ -55,12 +55,12 @@ 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//`. -## Metrics from Cgroups: Memory, CPU, Block IO +## Metrics from cgroups: memory, CPU, block I/O For each subsystem (memory, CPU, and block I/O), you will find one or more pseudo-files containing statistics. -### Memory Metrics: `memory.stat` +### Memory metrics: `memory.stat` Memory metrics are found in the "memory" cgroup. Note that the memory control group adds a little overhead, because it does very fine-grained @@ -262,7 +262,7 @@ relevant ones: not perform more I/O, its queue size can increase just because the device load increases because of other devices. -## Network Metrics +## Network metrics Network metrics are not exposed directly by control groups. There is a good explanation for that: network interfaces exist within the context diff --git a/docs/sources/articles/security.md b/docs/sources/articles/security.md index a26f79cf9..39a247c38 100644 --- a/docs/sources/articles/security.md +++ b/docs/sources/articles/security.md @@ -1,8 +1,8 @@ -page_title: Docker Security +page_title: Docker security page_description: Review of the Docker Daemon attack surface page_keywords: Docker, Docker documentation, security -# Docker Security +# Docker security There are three major areas to consider when reviewing Docker security: @@ -14,7 +14,7 @@ There are three major areas to consider when reviewing Docker security: - the "hardening" security features of the kernel and how they interact with containers. -## Kernel Namespaces +## Kernel namespaces Docker containers are very similar to LXC containers, and they have similar security features. When you start a container with `docker @@ -53,7 +53,7 @@ http://en.wikipedia.org/wiki/OpenVZ) in such a way that they could be merged within the mainstream kernel. And OpenVZ was initially released in 2005, so both the design and the implementation are pretty mature. -## Control Groups +## Control groups Control Groups are another key component of Linux Containers. They implement resource accounting and limiting. They provide many @@ -72,7 +72,7 @@ when some applications start to misbehave. Control Groups have been around for a while as well: the code was started in 2006, and initially merged in kernel 2.6.24. -## Docker Daemon Attack Surface +## Docker daemon attack surface Running containers (and applications) with Docker implies running the Docker daemon. This daemon currently requires `root` privileges, and you @@ -132,7 +132,7 @@ containers controlled by Docker. Of course, it is fine to keep your favorite admin tools (probably at least an SSH server), as well as existing monitoring/supervision processes (e.g., NRPE, collectd, etc). -## Linux Kernel Capabilities +## Linux kernel capabilities By default, Docker starts containers with a restricted set of capabilities. What does that mean? @@ -206,7 +206,7 @@ capability removal, or less secure through the addition of capabilities. The best practice for users would be to remove all capabilities except those explicitly required for their processes. -## Other Kernel Security Features +## Other kernel security features Capabilities are just one of the many security features provided by modern Linux kernels. It is also possible to leverage existing, diff --git a/docs/sources/articles/systemd.md b/docs/sources/articles/systemd.md index fddd146b0..c4c0d2c81 100644 --- a/docs/sources/articles/systemd.md +++ b/docs/sources/articles/systemd.md @@ -1,8 +1,8 @@ -page_title: Controlling and configuring Docker using Systemd -page_description: Controlling and configuring Docker using Systemd +page_title: Controlling and configuring Docker using systemd +page_description: Controlling and configuring Docker using systemd page_keywords: docker, daemon, systemd, configuration -# Controlling and configuring Docker using Systemd +# Controlling and configuring Docker using systemd Many Linux distributions use systemd to start the Docker daemon. This document shows a few examples of how to customise Docker's settings. @@ -64,7 +64,7 @@ setting `OPTIONS`: You can also set other environment variables in this file, for example, the `HTTP_PROXY` environment variables described below. -### HTTP Proxy +### HTTP proxy This example overrides the default `docker.service` file. diff --git a/docs/sources/docker-hub-enterprise/install-config.md b/docs/sources/docker-hub-enterprise/install-config.md index 0b7bcfd6f..81fa3041e 100644 --- a/docs/sources/docker-hub-enterprise/install-config.md +++ b/docs/sources/docker-hub-enterprise/install-config.md @@ -1,8 +1,8 @@ -page_title: Using Docker Hub Enterprise Installation -page_description: Docker Hub Enterprise Installation +page_title: Using Docker Hub Enterprise installation +page_description: Docker Hub Enterprise installation page_keywords: docker hub enterprise -# Docker Hub Enterprise Installation +# Docker Hub Enterprise installation Documenation coming soon. diff --git a/docs/sources/docker-hub/accounts.md b/docs/sources/docker-hub/accounts.md index e4623f998..360eb371f 100644 --- a/docs/sources/docker-hub/accounts.md +++ b/docs/sources/docker-hub/accounts.md @@ -4,7 +4,7 @@ page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub # Accounts on Docker Hub -## Docker Hub Accounts +## Docker Hub accounts You can `search` for Docker images and `pull` them from [Docker Hub](https://hub.docker.com) without signing in or even having an @@ -12,7 +12,7 @@ account. However, in order to `push` images, leave comments or to *star* a repository, you are going to need a [Docker Hub](https://hub.docker.com) account. -### Registration for a Docker Hub Account +### Registration for a Docker Hub account You can get a [Docker Hub](https://hub.docker.com) account by [signing up for one here](https://hub.docker.com/account/signup/). A valid @@ -32,7 +32,7 @@ If you can't access your account for some reason, you can reset your password from the [*Password Reset*](https://hub.docker.com/account/forgot-password/) page. -## Organizations & Groups +## Organizations and groups Also available on the Docker Hub are organizations and groups that allow you to collaborate across your organization or team. You can see what diff --git a/docs/sources/docker-hub/builds.md b/docs/sources/docker-hub/builds.md index bd3e3d2cb..541bc1594 100644 --- a/docs/sources/docker-hub/builds.md +++ b/docs/sources/docker-hub/builds.md @@ -83,7 +83,7 @@ You will be able to review and revoke Docker Hub's access by visiting the > using the "Start Build" button on the Hub, or if the webhook on the GitHub repository > still exists, will be triggered by any subsequent commits. -### Auto builds and Limited linked GitHub accounts. +### Auto builds and limited linked GitHub accounts. If you selected to link your GitHub account with only a "Limited" link, then after creating your automated build, you will need to either manually trigger a @@ -101,7 +101,7 @@ section, "Revoke access". You can now re-link your account at any time. -### GitHub Organizations +### GitHub organizations GitHub organizations and private repositories forked from organizations will be made available to auto build using the "Docker Hub Registry" application, which @@ -205,7 +205,7 @@ can be limited to read-only access to just the repositories required to build. -### GitHub Service hooks +### GitHub service hooks The GitHub Service hook allows GitHub to notify the Docker Hub when something has been committed to that git repository. You will need to add the Service Hook manually diff --git a/docs/sources/docker-hub/home.md b/docs/sources/docker-hub/home.md index 15baf7b83..3f81208c1 100644 --- a/docs/sources/docker-hub/home.md +++ b/docs/sources/docker-hub/home.md @@ -1,8 +1,8 @@ -page_title: The Docker Hub Registry Help +page_title: The Docker Hub Registry help page_description: The Docker Registry help documentation home page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, documentation -# The Docker Hub Registry Help +# The Docker Hub Registry help ## Introduction diff --git a/docs/sources/docker-hub/index.md b/docs/sources/docker-hub/index.md index c29a5f787..3651497e2 100644 --- a/docs/sources/docker-hub/index.md +++ b/docs/sources/docker-hub/index.md @@ -1,4 +1,4 @@ -page_title: The Docker Hub Help +page_title: The Docker Hub help page_description: The Docker Help documentation home page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, documentation, accounts, organizations, repositories, groups @@ -16,7 +16,7 @@ account and manage your organizations and groups. Find out how to share your Docker images in [Docker Hub repositories](repos/) and how to store and manage private images. -## [Automated Builds](builds/) +## [Automated builds](builds/) Learn how to automate your build and deploy pipeline with [Automated Builds](builds/) diff --git a/docs/sources/docker-hub/official_repos.md b/docs/sources/docker-hub/official_repos.md index 4ec431238..a101d88c1 100644 --- a/docs/sources/docker-hub/official_repos.md +++ b/docs/sources/docker-hub/official_repos.md @@ -1,8 +1,8 @@ -page_title: Guidelines for Official Repositories on Docker Hub +page_title: Guidelines for official repositories on Docker Hub page_description: Guidelines for Official Repositories on Docker Hub page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, official, image, documentation -# Guidelines for Creating and Documenting Official Repositories +# Guidelines for creating and documenting official repositories ## Introduction @@ -18,7 +18,7 @@ This document consists of two major sections: along with best practices for creating those items * Examples embodying those practices -## Expected Files & Resources +## Expected files and resources ### A Git repository @@ -92,7 +92,7 @@ In terms of content, the long description must include the following sections: * How-to/usage * Issues & contributions -#### Overview & links +#### Overview and links This section should provide: @@ -109,7 +109,7 @@ A section that describes how to run and use the image, including common use cases and example `Dockerfile`s (if applicable). Try to provide clear, step-by- step instructions wherever possible. -##### Issues & contributions +##### Issues and contributions In this section, point users to any resources that can help them contribute to the project. Include contribution guidelines and any specific instructions diff --git a/docs/sources/docker-hub/repos.md b/docs/sources/docker-hub/repos.md index 35cd4f8cc..0a2fa6550 100644 --- a/docs/sources/docker-hub/repos.md +++ b/docs/sources/docker-hub/repos.md @@ -1,8 +1,8 @@ -page_title: Repositories and Images on Docker Hub -page_description: Repositories and Images on Docker Hub +page_title: Repositories and images on Docker Hub +page_description: Repositories and images on Docker Hub page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, webhooks, docs, documentation -# Repositories and Images on Docker Hub +# Repositories and images on Docker Hub ![repositories](/docker-hub/hub-images/repos.png) @@ -51,7 +51,7 @@ private to public. You can also collaborate on Docker Hub with organizations and groups. You can read more about that [here](accounts/). -## Official Repositories +## Official repositories The Docker Hub contains a number of [official repositories](http://registry.hub.docker.com/official). These are @@ -67,7 +67,7 @@ optimized and up-to-date image to power your applications. > organization, product or team you can see more information > [here](https://github.com/docker/stackbrew). -## Private Repositories +## Private repositories Private repositories allow you to have repositories that contain images that you want to keep private, either to your own account or within an diff --git a/docs/sources/examples.md b/docs/sources/examples.md index 9dcd67a64..f4d5b868e 100644 --- a/docs/sources/examples.md +++ b/docs/sources/examples.md @@ -1,9 +1,9 @@ # Examples - - [Dockerizing a Node.js Web App](nodejs_web_app/) - - [Dockerizing a Redis Service](running_redis_service/) - - [Dockerizing an SSH Daemon Service](running_ssh_service/) - - [Dockerizing a CouchDB Service](couchdb_data_volumes/) - - [Dockerizing a PostgreSQL Service](postgresql_service/) + - [Dockerizing a Node.js web app](nodejs_web_app/) + - [Dockerizing a Redis service](running_redis_service/) + - [Dockerizing an SSH daemon service](running_ssh_service/) + - [Dockerizing a CouchDB service](couchdb_data_volumes/) + - [Dockerizing a PostgreSQL service](postgresql_service/) - [Dockerizing MongoDB](mongodb/) - - [Dockerizing a Riak Service](running_riak_service/) + - [Dockerizing a Riak service](running_riak_service/) diff --git a/docs/sources/examples/apt-cacher-ng.md b/docs/sources/examples/apt-cacher-ng.md index 9a3631220..57aa66966 100644 --- a/docs/sources/examples/apt-cacher-ng.md +++ b/docs/sources/examples/apt-cacher-ng.md @@ -2,7 +2,7 @@ page_title: Dockerizing an apt-cacher-ng service page_description: Installing and running an apt-cacher-ng service page_keywords: docker, example, package installation, networking, debian, ubuntu -# Dockerizing an Apt-Cacher-ng Service +# Dockerizing an apt-cacher-ng service > **Note**: > - **If you don't like sudo** then see [*Giving non-root diff --git a/docs/sources/examples/couchdb_data_volumes.md b/docs/sources/examples/couchdb_data_volumes.md index 483168ae2..27bce34a9 100644 --- a/docs/sources/examples/couchdb_data_volumes.md +++ b/docs/sources/examples/couchdb_data_volumes.md @@ -1,8 +1,8 @@ -page_title: Dockerizing a CouchDB Service +page_title: Dockerizing a CouchDB service page_description: Sharing data between 2 couchdb databases page_keywords: docker, example, package installation, networking, couchdb, data volumes -# Dockerizing a CouchDB Service +# Dockerizing a CouchDB service > **Note**: > - **If you don't like sudo** then see [*Giving non-root diff --git a/docs/sources/examples/nodejs_web_app.md b/docs/sources/examples/nodejs_web_app.md index 1db61ae62..ff7179a81 100644 --- a/docs/sources/examples/nodejs_web_app.md +++ b/docs/sources/examples/nodejs_web_app.md @@ -1,8 +1,8 @@ -page_title: Dockerizing a Node.js Web App +page_title: Dockerizing a Node.js web app page_description: Installing and running a Node.js app with Docker page_keywords: docker, example, package installation, node, centos -# Dockerizing a Node.js Web App +# Dockerizing a Node.js web app > **Note**: > - **If you don't like sudo** then see [*Giving non-root diff --git a/docs/sources/examples/running_redis_service.md b/docs/sources/examples/running_redis_service.md index a00db9896..c46bb09c7 100644 --- a/docs/sources/examples/running_redis_service.md +++ b/docs/sources/examples/running_redis_service.md @@ -2,12 +2,12 @@ page_title: Dockerizing a Redis service page_description: Installing and running an redis service page_keywords: docker, example, package installation, networking, redis -# Dockerizing a Redis Service +# Dockerizing a Redis service Very simple, no frills, Redis service attached to a web application using a link. -## Create a docker container for Redis +## Create a Docker container for Redis Firstly, we create a `Dockerfile` for our new Redis image. diff --git a/docs/sources/examples/running_riak_service.md b/docs/sources/examples/running_riak_service.md index 6d49cc87e..1b14c3a41 100644 --- a/docs/sources/examples/running_riak_service.md +++ b/docs/sources/examples/running_riak_service.md @@ -2,7 +2,7 @@ page_title: Dockerizing a Riak service page_description: Build a Docker image with Riak pre-installed page_keywords: docker, example, package installation, networking, riak -# Dockerizing a Riak Service +# Dockerizing a Riak service The goal of this example is to show you how to build a Docker image with Riak pre-installed. diff --git a/docs/sources/examples/running_ssh_service.md b/docs/sources/examples/running_ssh_service.md index e2fc3782d..b1000a04a 100644 --- a/docs/sources/examples/running_ssh_service.md +++ b/docs/sources/examples/running_ssh_service.md @@ -2,7 +2,7 @@ page_title: Dockerizing an SSH service page_description: Installing and running an SSHd service on Docker page_keywords: docker, example, package installation, networking -# Dockerizing an SSH Daemon Service +# Dockerizing an SSH daemon service ## Build an `eg_sshd` image diff --git a/docs/sources/http-routingtable.md b/docs/sources/http-routingtable.md index 07029d2ca..14e1dfcd2 100644 --- a/docs/sources/http-routingtable.md +++ b/docs/sources/http-routingtable.md @@ -1,4 +1,4 @@ -# HTTP Routing Table +# HTTP routing table [**/api**](#cap-/api) | [**/auth**](#cap-/auth) | [**/build**](#cap-/build) | [**/commit**](#cap-/commit) | diff --git a/docs/sources/index.md b/docs/sources/index.md index 993603eb3..ef827acba 100644 --- a/docs/sources/index.md +++ b/docs/sources/index.md @@ -75,18 +75,18 @@ The [Understanding Docker section](introduction/understanding-docker.md) will he - See how Docker compares to virtual machines - See some common use cases. -### Installation Guides +### Installation guides The [installation section](/installation/#installation) will show you how to install Docker on a variety of platforms. -### Docker User Guide +### Docker user guide To learn about Docker in more detail and to answer questions about usage and implementation, check out the [Docker User Guide](/userguide/). -## Release Notes +## Release notes A summary of the changes in each release in the current series can now be found on the separate [Release Notes page](/release-notes/) diff --git a/docs/sources/installation/azure.md b/docs/sources/installation/azure.md index a8e700fea..54910228e 100644 --- a/docs/sources/installation/azure.md +++ b/docs/sources/installation/azure.md @@ -1,4 +1,4 @@ -page_title: Installation on Microsoft Azure Platform +page_title: Installation on Microsoft Azure platform page_description: Instructions for creating a Docker-ready virtual machine on Microsoft Azure cloud platform. page_keywords: Docker, Docker documentation, installation, azure, microsoft diff --git a/docs/sources/installation/binaries.md b/docs/sources/installation/binaries.md index 855d46028..a9a96bec0 100644 --- a/docs/sources/installation/binaries.md +++ b/docs/sources/installation/binaries.md @@ -1,4 +1,4 @@ -page_title: Installation from Binaries +page_title: Installation from binaries page_description: Instructions for installing Docker as a binary. Mostly meant for hackers who want to try out Docker on a variety of environments. page_keywords: binaries, installation, docker, documentation, linux @@ -78,7 +78,7 @@ exhibit unexpected behaviour. > vendor for the system, and might break regulations and security > policies in heavily regulated environments. -## Get the docker binary +## Get the Docker binary You can download either the latest release binary or a specific version. After downloading a binary file, you must set the file's execute bit to run it. @@ -141,7 +141,7 @@ For example: https://get.docker.com/builds/Darwin/x86_64/docker-1.6.0 -### Get the Windows binary +### Get the Windows binary You can only download the Windows client binary for version `1.6.0` onwards. Moreover, the binary is only a client, you cannot use it to run the `docker` daemon. @@ -164,7 +164,7 @@ For example: https://get.docker.com/builds/Windows/x86_64/docker-1.6.0.exe -## Run the docker daemon +## Run the Docker daemon # start the docker in daemon mode from the directory you unpacked $ sudo ./docker -d & diff --git a/docs/sources/installation/cruxlinux.md b/docs/sources/installation/cruxlinux.md index ead4c273c..d474aa52f 100644 --- a/docs/sources/installation/cruxlinux.md +++ b/docs/sources/installation/cruxlinux.md @@ -20,7 +20,7 @@ Assuming you have contrib enabled, update your ports tree and install docker (*a # prt-get depinst docker -## Kernel Requirements +## Kernel requirements To have a working **CRUX+Docker** Host you must ensure your Kernel has the necessary modules enabled for the Docker Daemon to function correctly. diff --git a/docs/sources/installation/mac.md b/docs/sources/installation/mac.md index 9326f5fc4..4b157c168 100644 --- a/docs/sources/installation/mac.md +++ b/docs/sources/installation/mac.md @@ -162,7 +162,7 @@ Initialize and run `boot2docker` from the command line, do the following: $ docker run hello-world -## Basic Boot2Docker Exercises +## Basic Boot2Docker exercises At this point, you should have `boot2docker` running and the `docker` client environment initialized. To verify this, run the following commands: @@ -314,7 +314,7 @@ section. The installer places Boot2Docker in your "Applications" folder. -## Learning more and Acknowledgement +## Learning more and acknowledgement Use `boot2docker help` to list the full command line reference. For more diff --git a/docs/sources/installation/oracle.md b/docs/sources/installation/oracle.md index 6d2f782b4..e05e664c1 100644 --- a/docs/sources/installation/oracle.md +++ b/docs/sources/installation/oracle.md @@ -110,7 +110,7 @@ service. On Oracle Linux 7, you can use a `systemd.mount` definition and modify the Docker `systemd.service` to depend on the btrfs mount defined in systemd. -### SElinux Support on Oracle Linux 7 +### SElinux support on Oracle Linux 7 SElinux must be set to `Permissive` or `Disabled` in `/etc/sysconfig/selinux` to use the btrfs storage engine on Oracle Linux 7. diff --git a/docs/sources/installation/rhel.md b/docs/sources/installation/rhel.md index 58b2316c6..7be8debce 100644 --- a/docs/sources/installation/rhel.md +++ b/docs/sources/installation/rhel.md @@ -16,7 +16,7 @@ running on kernels shipped by the distribution. There are kernel changes which will cause issues if one decides to step outside that box and run non-distribution kernel packages. -## Red Hat Enterprise Linux 7 Installation +## Red Hat Enterprise Linux 7 installation **Red Hat Enterprise Linux 7 (64 bit)** has [shipped with Docker](https://access.redhat.com/site/products/red-hat-enterprise-linux/docker-and-containers). @@ -41,7 +41,7 @@ Portal](https://access.redhat.com/). Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon). -## Red Hat Enterprise Linux 6.5 Installation +## Red Hat Enterprise Linux 6.5 installation You will need **64 bit** [RHEL 6.5](https://access.redhat.com/site/articles/3078#RHEL6) or later, with diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index dbf86f310..75b3c9fb6 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -127,7 +127,7 @@ install Docker using the following: This command downloads a test image and runs it in a container. -## Optional Configurations for Docker on Ubuntu +## Optional configurations for Docker on Ubuntu This section contains optional procedures for configuring your Ubuntu to work better with Docker. @@ -137,7 +137,7 @@ better with Docker. * [Enable UFW forwarding](#enable-ufw-forwarding) * [Configure a DNS server for use by Docker](#configure-a-dns-server-for-docker) -### Create a docker group +### Create a Docker group The `docker` daemon binds to a Unix socket instead of a TCP port. By default that Unix socket is owned by the user `root` and other users can access it with diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index f93f13e60..fd3cc7eb4 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -59,7 +59,7 @@ Let's try the `hello-world` example image. Run This should download the very small `hello-world` image and print a `Hello from Docker.` message. -## Using docker from Windows Command Line Prompt (cmd.exe) +## Using Docker from Windows Command Line Prompt (cmd.exe) Launch a Windows Command Line Prompt (cmd.exe). @@ -77,7 +77,7 @@ to your console window and you are ready to run docker commands such as ![](/installation/images/windows-boot2docker-cmd.png) -## Using docker from PowerShell +## Using Docker from PowerShell Launch a PowerShell window, then you need to add `ssh.exe` to your PATH: diff --git a/docs/sources/introduction/understanding-docker.md b/docs/sources/introduction/understanding-docker.md index 263690217..060428ecc 100644 --- a/docs/sources/introduction/understanding-docker.md +++ b/docs/sources/introduction/understanding-docker.md @@ -109,7 +109,7 @@ Docker containers. Docker provides a simple way to build new images or update ex images, or you can download Docker images that other people have already created. Docker images are the **build** component of Docker. -#### Docker Registries +#### Docker registries Docker registries hold images. These are public or private stores from which you upload or download images. The public Docker registry is called [Docker Hub](http://hub.docker.com). It provides a huge collection of existing @@ -135,7 +135,7 @@ So far, we've learned that: Let's look at how these elements combine together to make Docker work. -### How does a Docker Image work? +### How does a Docker image work? We've already seen that Docker images are read-only templates from which Docker containers are launched. Each image consists of a series of layers. Docker makes use of [union file systems](http://en.wikipedia.org/wiki/UnionFS) to @@ -280,7 +280,7 @@ BSD Jails or Solaris Zones. ### Installing Docker Visit the [installation section](/installation/#installation). -### The Docker User Guide +### The Docker user guide [Learn Docker in depth](/userguide/). diff --git a/docs/sources/project/advanced-contributing.md b/docs/sources/project/advanced-contributing.md index ee958f4b4..f20cbfff9 100644 --- a/docs/sources/project/advanced-contributing.md +++ b/docs/sources/project/advanced-contributing.md @@ -137,7 +137,7 @@ The following provides greater detail on the process: 14. Acceptance and merge! -## About the Advanced process +## About the advanced process Docker is a large project. Our core team gets a great many design proposals. Design proposal discussions can span days, weeks, and longer. The number of comments can reach the 100s. diff --git a/docs/sources/project/coding-style.md b/docs/sources/project/coding-style.md index bf8267e71..57f638936 100644 --- a/docs/sources/project/coding-style.md +++ b/docs/sources/project/coding-style.md @@ -1,8 +1,8 @@ -page_title: Coding Style Checklist +page_title: Coding style checklist page_description: List of guidelines for coding Docker contributions page_keywords: change, commit, squash, request, pull request, test, unit test, integration tests, Go, gofmt, LGTM -# Coding Style Checklist +# Coding style checklist This checklist summarizes the material you experienced working through [make a code contribution](/project/make-a-contribution) and [advanced diff --git a/docs/sources/project/create-pr.md b/docs/sources/project/create-pr.md index f39f0aa98..e9123c463 100644 --- a/docs/sources/project/create-pr.md +++ b/docs/sources/project/create-pr.md @@ -11,7 +11,7 @@ repository into the `docker/docker` repository. You can see the list of active pull requests to Docker on GitHub. -## Check Your Work +## Check your work Before you create a pull request, check your work. diff --git a/docs/sources/project/doc-style.md b/docs/sources/project/doc-style.md index 20e4a9f10..0aa0f419a 100644 --- a/docs/sources/project/doc-style.md +++ b/docs/sources/project/doc-style.md @@ -1,4 +1,4 @@ -page_title: Style Guide for Docker Documentation +page_title: Style guide for Docker documentation page_description: Style guide for Docker documentation describing standards and conventions for contributors page_keywords: style, guide, docker, documentation diff --git a/docs/sources/project/review-pr.md b/docs/sources/project/review-pr.md index 3d77ea406..01cce6fd2 100644 --- a/docs/sources/project/review-pr.md +++ b/docs/sources/project/review-pr.md @@ -1,9 +1,9 @@ -page_title: Participate in the PR Review +page_title: Participate in the PR review page_description: Basic workflow for Docker contributions page_keywords: contribute, pull request, review, workflow, beginner, squash, commit -# Participate in the PR Review +# Participate in the PR review Creating a pull request is nearly the end of the contribution process. At this point, your code is reviewed both by our continuous integration (CI) systems and @@ -45,7 +45,7 @@ So, they value your time and will try to work efficiently with you by keeping their comments specific and brief. If they ask you to make a change, you'll need to update your pull request with additional changes. -## Update an Existing Pull Request +## Update an existing pull request To update your existing pull request: diff --git a/docs/sources/reference/api/docker-io_api.md b/docs/sources/reference/api/docker-io_api.md index a7557bacb..b8da27044 100644 --- a/docs/sources/reference/api/docker-io_api.md +++ b/docs/sources/reference/api/docker-io_api.md @@ -10,7 +10,7 @@ page_keywords: API, Docker, index, REST, documentation, Docker Hub, registry # Repositories -## User Repository +## User repository ### Create a user repository @@ -93,7 +93,7 @@ Status Codes: - **401** – Unauthorized - **403** – Account is not Active -## Library Repository +## Library repository ### Create a library repository @@ -182,9 +182,9 @@ Status Codes: - **401** – Unauthorized - **403** – Account is not Active -# Repository Images +# Repository images -## User Repository Images +## User repository images ### Update user repository images @@ -256,7 +256,7 @@ Status Codes: - **200** – OK - **404** – Not found -## Library Repository Images +## Library repository images ### Update library repository images @@ -326,9 +326,9 @@ Status Codes: - **200** – OK - **404** – Not found -# Repository Authorization +# Repository authorization -## Library Repository +## Library repository ### Authorize a token for a library @@ -361,7 +361,7 @@ Status Codes: - **403** – Permission denied - **404** – Not found -## User Repository +## User repository ### Authorize a token for a user repository @@ -397,7 +397,7 @@ Status Codes: ## Users -### User Login +### User login `GET /v1/users/` @@ -424,7 +424,7 @@ Status Codes: - **401** – Unauthorized - **403** – Account is not Active -### User Register +### User register `POST /v1/users/` @@ -461,7 +461,7 @@ Status Codes: - **201** – User Created - **400** – Errors (invalid json, missing or invalid fields, etc) -### Update User +### Update user `PUT /v1/users/(username)/` diff --git a/docs/sources/reference/api/docker_io_accounts_api.md b/docs/sources/reference/api/docker_io_accounts_api.md index efb86eb33..34f21eb7d 100644 --- a/docs/sources/reference/api/docker_io_accounts_api.md +++ b/docs/sources/reference/api/docker_io_accounts_api.md @@ -1,8 +1,8 @@ -page_title: docker.io Accounts API +page_title: docker.io accounts API page_description: API Documentation for docker.io accounts. page_keywords: API, Docker, accounts, REST, documentation -# docker.io Accounts API +# docker.io accounts API ## Get a single user diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index 7772a651a..d92084f29 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -40,7 +40,7 @@ You can still call an old version of the API using ## v1.19 -### Full Documentation +### Full documentation [*Docker Remote API v1.19*](/reference/api/docker_remote_api_v1.19/) @@ -49,7 +49,7 @@ You can still call an old version of the API using ## v1.18 -### Full Documentation +### Full documentation [*Docker Remote API v1.18*](/reference/api/docker_remote_api_v1.18/) @@ -96,7 +96,7 @@ Add `Warnings` field to response. ## v1.17 -### Full Documentation +### Full documentation [*Docker Remote API v1.17*](/reference/api/docker_remote_api_v1.17/) @@ -154,7 +154,7 @@ This endpoint now returns the labels associated with each image (`Labels`). ## v1.16 -### Full Documentation +### Full documentation [*Docker Remote API v1.16*](/reference/api/docker_remote_api_v1.16/) @@ -182,7 +182,7 @@ You can now copy data which is contained in a volume. ## v1.15 -### Full Documentation +### Full documentation [*Docker Remote API v1.15*](/reference/api/docker_remote_api_v1.15/) @@ -196,7 +196,7 @@ Previously this was only available when starting a container. ## v1.14 -### Full Documentation +### Full documentation [*Docker Remote API v1.14*](/reference/api/docker_remote_api_v1.14/) @@ -222,7 +222,7 @@ the `tag` parameter at the same time will return an error. ## v1.13 -### Full Documentation +### Full documentation [*Docker Remote API v1.13*](/reference/api/docker_remote_api_v1.13/) @@ -250,7 +250,7 @@ Added a `pause` parameter (default `true`) to pause the container during commit ## v1.12 -### Full Documentation +### Full documentation [*Docker Remote API v1.12*](/reference/api/docker_remote_api_v1.12/) @@ -275,7 +275,7 @@ The `insert` endpoint has been removed. ## v1.11 -### Full Documentation +### Full documentation [*Docker Remote API v1.11*](/reference/api/docker_remote_api_v1.11/) @@ -298,7 +298,7 @@ This url is preferred method for getting container logs now. ## v1.10 -### Full Documentation +### Full documentation [*Docker Remote API v1.10*](/reference/api/docker_remote_api_v1.10/) @@ -321,7 +321,7 @@ You can now use the force parameter to force delete a ## v1.9 -### Full Documentation +### Full documentation [*Docker Remote API v1.9*](/reference/api/docker_remote_api_v1.9/) @@ -337,7 +337,7 @@ accepting an AuthConfig object must be updated. ## v1.8 -### Full Documentation +### Full documentation [*Docker Remote API v1.8*](/reference/api/docker_remote_api_v1.8/) @@ -369,7 +369,7 @@ without having to parse the string. ## v1.7 -### Full Documentation +### Full documentation [*Docker Remote API v1.7*](/reference/api/docker_remote_api_v1.7/) @@ -468,7 +468,7 @@ output is now generated in the client, using the ## v1.6 -### Full Documentation +### Full documentation [*Docker Remote API v1.6*](/reference/api/docker_remote_api_v1.6/) @@ -486,7 +486,7 @@ previous API version didn't change. Stdout and stderr are merged. ## v1.5 -### Full Documentation +### Full documentation [*Docker Remote API v1.5*](/reference/api/docker_remote_api_v1.5/) @@ -513,7 +513,7 @@ port mapping. ## v1.4 -### Full Documentation +### Full documentation [*Docker Remote API v1.4*](/reference/api/docker_remote_api_v1.4/) @@ -540,7 +540,7 @@ Image's name added in the events docker v0.5.0 [51f6c4a](https://github.com/docker/docker/commit/51f6c4a7372450d164c61e0054daf0223ddbd909) -### Full Documentation +### Full documentation [*Docker Remote API v1.3*](/reference/api/docker_remote_api_v1.3/) @@ -580,7 +580,7 @@ Start containers (/containers//start): docker v0.4.2 [2e7649b](https://github.com/docker/docker/commit/2e7649beda7c820793bd46766cbc2cfeace7b168) -### Full Documentation +### Full documentation [*Docker Remote API v1.2*](/reference/api/docker_remote_api_v1.2/) @@ -612,7 +612,7 @@ deleted/untagged. docker v0.4.0 [a8ae398](https://github.com/docker/docker/commit/a8ae398bf52e97148ee7bd0d5868de2e15bd297f) -### Full Documentation +### Full documentation [*Docker Remote API v1.1*](/reference/api/docker_remote_api_v1.1/) @@ -639,7 +639,7 @@ Uses json stream instead of HTML hijack, it looks like this: docker v0.3.4 [8d73740](https://github.com/docker/docker/commit/8d73740343778651c09160cde9661f5f387b36f4) -### Full Documentation +### Full documentation [*Docker Remote API v1.0*](/reference/api/docker_remote_api_v1.0/) diff --git a/docs/sources/reference/api/docker_remote_api_v1.9.md b/docs/sources/reference/api/docker_remote_api_v1.9.md index b6675dc4e..bef67a071 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.9.md +++ b/docs/sources/reference/api/docker_remote_api_v1.9.md @@ -675,7 +675,7 @@ Status Codes: ## 2.2 Images -### List Images +### List images `GET /images/json` @@ -1119,7 +1119,7 @@ Status Codes: - **200** – no error - **500** – server error -### Show the docker version information +### Show the Docker version information `GET /version` @@ -1343,7 +1343,7 @@ Here are the steps of `docker run` : In this version of the API, /attach, uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future. -## 3.3 CORS Requests +## 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. diff --git a/docs/sources/reference/api/hub_registry_spec.md b/docs/sources/reference/api/hub_registry_spec.md index 2999f453f..b1481e3a0 100644 --- a/docs/sources/reference/api/hub_registry_spec.md +++ b/docs/sources/reference/api/hub_registry_spec.md @@ -1,4 +1,4 @@ -page_title: Registry Documentation +page_title: Registry documentation page_description: Documentation for docker Registry and Registry API page_keywords: docker, registry, api, hub @@ -679,7 +679,7 @@ On every request, a special header can be returned: On the next request, the client will always pick a server from this list. -## Authentication & Authorization +## Authentication and authorization ### On the Docker Hub @@ -747,7 +747,7 @@ Next request: GET /(...) Cookie: session="wD/J7LqL5ctqw8haL10vgfhrb2Q=?foo=UydiYXInCnAxCi4=×tamp=RjEzNjYzMTQ5NDcuNDc0NjQzCi4=" -## Document Version +## Document version - 1.0 : May 6th 2013 : initial release - 1.1 : June 1st 2013 : Added Delete Repository and way to handle new diff --git a/docs/sources/reference/api/registry_api_client_libraries.md b/docs/sources/reference/api/registry_api_client_libraries.md index 811ac859e..965ba4603 100644 --- a/docs/sources/reference/api/registry_api_client_libraries.md +++ b/docs/sources/reference/api/registry_api_client_libraries.md @@ -1,8 +1,8 @@ -page_title: Registry API Client Libraries +page_title: Registry API client libraries page_description: Various client libraries available to use with the Docker registry API page_keywords: API, Docker, index, registry, REST, documentation, clients, C#, Erlang, Go, Groovy, Java, JavaScript, Perl, PHP, Python, Ruby, Rust, Scala -# Docker Registry 1.0 API Client Libraries +# Docker Registry 1.0 API client libraries These libraries have not been tested by the Docker maintainers for compatibility. Please file issues with the library owners. If you find diff --git a/docs/sources/reference/api/remote_api_client_libraries.md b/docs/sources/reference/api/remote_api_client_libraries.md index 3226d0eee..cbe8f3a32 100644 --- a/docs/sources/reference/api/remote_api_client_libraries.md +++ b/docs/sources/reference/api/remote_api_client_libraries.md @@ -1,8 +1,8 @@ -page_title: Remote API Client Libraries +page_title: Remote API client libraries page_description: Various client libraries available to use with the Docker remote API page_keywords: API, Docker, index, registry, REST, documentation, clients, C#, Erlang, Go, Groovy, Java, JavaScript, Perl, PHP, Python, Ruby, Rust, Scala -# Docker Remote API Client Libraries +# Docker Remote API client libraries These libraries have not been tested by the Docker maintainers for compatibility. Please file issues with the library owners. If you find diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index a4fcbebc1..583698d88 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -1,8 +1,8 @@ -page_title: Dockerfile Reference +page_title: Dockerfile reference page_description: Dockerfiles use a simple DSL which allows you to automate the steps you would normally manually take to create an image. page_keywords: builder, docker, Dockerfile, automation, image creation -# Dockerfile Reference +# Dockerfile reference **Docker can build images automatically** by reading the instructions from a `Dockerfile`. A `Dockerfile` is a text document that contains all @@ -105,7 +105,7 @@ be treated as an argument. This allows statements like: Here is the set of instructions you can use in a `Dockerfile` for building images. -### Environment Replacement +### Environment replacement > **Note**: prior to 1.3, `Dockerfile` environment variables were handled > similarly, in that they would be replaced as described below. However, there @@ -288,7 +288,7 @@ guide](/articles/dockerfile_best-practices/#build-cache) for more information. The cache for `RUN` instructions can be invalidated by `ADD` instructions. See [below](#add) for details. -### Known Issues (RUN) +### Known issues (RUN) - [Issue 783](https://github.com/docker/docker/issues/783) is about file permissions problems that can occur when using the AUFS file system. You @@ -973,7 +973,7 @@ For example you might add something like this: > **Warning**: The `ONBUILD` instruction may not trigger `FROM` or `MAINTAINER` instructions. -## Dockerfile Examples +## Dockerfile examples # Nginx # diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 96aef620c..a87116204 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -24,7 +24,7 @@ the `docker` command, your system administrator can create a Unix group called For more information about installing Docker or `sudo` configuration, refer to the [installation](/installation) instructions for your operating system. -## Environment Variables +## Environment variables For easy reference, the following list of environment variables are supported by the `docker` command line: @@ -48,7 +48,7 @@ These Go environment variables are case-insensitive. See the [Go specification](http://golang.org/pkg/net/http/) for details on these variables. -## Configuration Files +## Configuration files The Docker command line stores its configuration files in a directory called `.docker` within your `HOME` directory. Docker manages most of the files in @@ -2210,7 +2210,7 @@ application change: `--rm` option means that when the container exits, the container's layer is removed. -#### Restart Policies +#### Restart policies Use Docker's `--restart` to specify a container's *restart policy*. A restart policy controls whether the Docker daemon restarts a container after exit. diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index a0d66937f..7218fab64 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -156,7 +156,7 @@ Images using the v2 or later image format have a content-addressable identifier called a digest. As long as the input used to generate the image is unchanged, the digest value is predictable and referenceable. -## PID Settings (--pid) +## PID settings (--pid) --pid="" : Set the PID (Process) Namespace mode for the container, 'host': use the host's PID namespace inside the container @@ -177,7 +177,7 @@ within the container. This command would allow you to use `strace` inside the container on pid 1234 on the host. -## IPC Settings (--ipc) +## IPC settings (--ipc) --ipc="" : Set the IPC mode for the container, 'container:': reuses another container's IPC namespace diff --git a/docs/sources/release-notes.md b/docs/sources/release-notes.md index 87e5c4197..1a32cbb98 100644 --- a/docs/sources/release-notes.md +++ b/docs/sources/release-notes.md @@ -1,8 +1,8 @@ -page_title: Docker 1.x Series Release Notes -page_description: Release Notes for Docker 1.x. +page_title: Docker 1.x series release notes +page_description: Release notes for Docker 1.x. page_keywords: docker, documentation, about, technology, understanding, release -# Release Notes Version 1.6.0 +# Release notes version 1.6.0 (2015-04-16) You can view release notes for earlier version of Docker by selecting the @@ -12,7 +12,7 @@ blog](https://blog.docker.com/2015/04/docker-release-1-6/). -## Docker Engine 1.6.0 Features +## Docker Engine 1.6.0 features For a complete list of engine patches, fixes, and other improvements, see the [merge PR on GitHub](https://github.com/docker/docker/pull/11635). You'll also @@ -30,7 +30,7 @@ repository](https://github.com/docker/docker/blob/master/CHANGELOG.md). | Ulimits | You can now specify the default `ulimit` settings for all containers when configuring the daemon. For example:`docker -d --default-ulimit nproc=1024:2048` See [Default Ulimits](http://docs.docker.com/reference/commandline/cli/#default-ulimits) in this documentation. | | Commit and import Dockerfile | You can now make changes to images on the fly without having to re-build the entire image. The feature `commit --change` and `import --change` allows you to apply standard changes to a new image. These are expressed in the Dockerfile syntax and used to modify the image. For details on how to use these, see the [commit](http://docs.docker.com/reference/commandline/cli/#commit) and [import](http://docs.docker.com/reference/commandline/cli/#import). | -### Known Issues in Engine +### Known issues in Engine This section lists significant known issues present in Docker as of release date. For an exhaustive list of issues, see [the issues list on the project @@ -53,7 +53,7 @@ issues. You might have to flush your cookies if it doesn't work right away. For more information, see the [Docker forum post](https://forums.docker.com/t/new-safari-in-yosemite-issue/300). -## Docker Registry 2.0 Features +## Docker Registry 2.0 features This release includes Registry 2.0. The Docker Registry is a central server for pushing and pulling images. In this release, it was completely rewritten in Go diff --git a/docs/sources/terms/container.md b/docs/sources/terms/container.md index 8b4286878..d0c31c245 100644 --- a/docs/sources/terms/container.md +++ b/docs/sources/terms/container.md @@ -17,7 +17,7 @@ Image*](/terms/image) and some additional information like its unique id, networking configuration, and resource limits is called a **container**. -## Container State +## Container state Containers can change, and so they have state. A container may be **running** or **exited**. diff --git a/docs/sources/terms/filesystem.md b/docs/sources/terms/filesystem.md index 5587e3c83..814246d8b 100644 --- a/docs/sources/terms/filesystem.md +++ b/docs/sources/terms/filesystem.md @@ -1,8 +1,8 @@ -page_title: File Systems +page_title: File system page_description: How Linux organizes its persistent storage page_keywords: containers, files, linux -# File System +# File system ## Introduction diff --git a/docs/sources/terms/image.md b/docs/sources/terms/image.md index e42a6cfa1..0a11d91c9 100644 --- a/docs/sources/terms/image.md +++ b/docs/sources/terms/image.md @@ -1,4 +1,4 @@ -page_title: Images +page_title: Image page_description: Definition of an image page_keywords: containers, lxc, concepts, explanation, image, container @@ -19,7 +19,7 @@ images do not have state. ![](/terms/images/docker-filesystems-debianrw.png) -## Parent Image +## Parent image ![](/terms/images/docker-filesystems-multilayer.png) @@ -27,7 +27,7 @@ Each image may depend on one more image which forms the layer beneath it. We sometimes say that the lower image is the **parent** of the upper image. -## Base Image +## Base image An image that has no parent is a **base image**. diff --git a/docs/sources/terms/registry.md b/docs/sources/terms/registry.md index 68120812c..ad5a81d64 100644 --- a/docs/sources/terms/registry.md +++ b/docs/sources/terms/registry.md @@ -14,7 +14,7 @@ The default registry can be accessed using a browser at [Docker Hub](https://hub.docker.com) or using the `docker search` command. -## Further Reading +## Further reading For more information see [*Working with Repositories*](/userguide/dockerrepos/#working-with-the-repository) diff --git a/docs/sources/userguide/dockerhub.md b/docs/sources/userguide/dockerhub.md index 3d4007d30..2f7170d64 100644 --- a/docs/sources/userguide/dockerhub.md +++ b/docs/sources/userguide/dockerhub.md @@ -2,7 +2,7 @@ page_title: Getting started with Docker Hub page_description: Introductory guide to getting an account on Docker Hub page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, central service, services, how to, container, containers, automation, collaboration, collaborators, registry, repo, repository, technology, github webhooks, trusted builds -# Getting Started with Docker Hub +# Getting started with Docker Hub This section provides a quick introduction to the [Docker Hub](https://hub.docker.com), @@ -21,7 +21,7 @@ most out of Docker. To do this, it provides services such as: In order to use Docker Hub, you will first need to register and create an account. Don't worry, creating an account is simple and free. -## Creating a Docker Hub Account +## Creating a Docker Hub account There are two ways for you to register and create an account: diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md index fc5dbe74a..621946654 100644 --- a/docs/sources/userguide/dockerimages.md +++ b/docs/sources/userguide/dockerimages.md @@ -1,8 +1,8 @@ -page_title: Working with Docker Images +page_title: Working with Docker images page_description: How to work with Docker images. page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, Docker images, Docker image, image management, Docker repos, Docker repositories, docker, docker tag, docker tags, Docker Hub, collaboration -# Working with Docker Images +# Working with Docker images In the [introduction](/introduction/understanding-docker/) we've discovered that Docker images are the basis of containers. In the diff --git a/docs/sources/userguide/dockerizing.md b/docs/sources/userguide/dockerizing.md index 5896dd78e..7124ba6c9 100644 --- a/docs/sources/userguide/dockerizing.md +++ b/docs/sources/userguide/dockerizing.md @@ -1,8 +1,8 @@ -page_title: Dockerizing Applications: A "Hello world" +page_title: Dockerizing applications: A "Hello world" page_description: A simple "Hello world" exercise that introduced you to Docker. page_keywords: docker guide, docker, docker platform, virtualization framework, how to, dockerize, dockerizing apps, dockerizing applications, container, containers -# Dockerizing Applications: A "Hello world" +# Dockerizing applications: A "Hello world" *So what's this Docker thing all about?* @@ -48,7 +48,7 @@ So what happened to our container after that? Well Docker containers only run as long as the command you specify is active. Here, as soon as `Hello world` was echoed, the container stopped. -## An Interactive Container +## An interactive container Let's try the `docker run` command again, this time specifying a new command to run in our container. @@ -90,7 +90,7 @@ use the `exit` command or enter Ctrl-D to finish. As with our previous container, once the Bash shell process has finished, the container is stopped. -## A Daemonized Hello world +## A daemonized Hello world Now a container that runs a command and then exits has some uses but it's not overly helpful. Let's create a container that runs as a daemon, diff --git a/docs/sources/userguide/dockerlinks.md b/docs/sources/userguide/dockerlinks.md index 66dd3d7a4..8a2038846 100644 --- a/docs/sources/userguide/dockerlinks.md +++ b/docs/sources/userguide/dockerlinks.md @@ -1,8 +1,8 @@ -page_title: Linking Containers Together +page_title: Linking containers together page_description: Learn how to connect Docker containers together. page_keywords: Examples, Usage, user guide, links, linking, docker, documentation, examples, names, name, container naming, port, map, network port, network -# Linking Containers Together +# Linking containers together In [the Using Docker section](/userguide/usingdocker), you saw how you can connect to a service running inside a Docker container via a network @@ -11,7 +11,7 @@ applications running inside Docker containers. In this section, we'll briefly re connecting via a network port and then we'll introduce you to another method of access: container linking. -## Connect using Network port mapping +## Connect using network port mapping In [the Using Docker section](/userguide/usingdocker), you created a container that ran a Python Flask application: @@ -175,7 +175,7 @@ recipient container in two ways: * Environment variables, * Updating the `/etc/hosts` file. -### Environment Variables +### Environment variables Docker creates several environment variables when you link containers. Docker automatically creates environment variables in the target container based on diff --git a/docs/sources/userguide/dockerrepos.md b/docs/sources/userguide/dockerrepos.md index a8a1800f5..efa6ca3d0 100644 --- a/docs/sources/userguide/dockerrepos.md +++ b/docs/sources/userguide/dockerrepos.md @@ -101,7 +101,7 @@ information [here](http://docs.docker.com/docker-hub/). * Automated Builds * Webhooks -### Private Repositories +### Private repositories Sometimes you have images you don't want to make public and share with everyone. So Docker Hub allows you to have private repositories. You can @@ -150,7 +150,7 @@ repository. You can create multiple Automated Builds per repository and configure them to point to specific `Dockerfile`'s or Git branches. -#### Build Triggers +#### Build triggers Automated Builds can also be triggered via a URL on Docker Hub. This allows you to rebuild an Automated build image on demand. diff --git a/docs/sources/userguide/dockervolumes.md b/docs/sources/userguide/dockervolumes.md index e80483fc2..c7126d7c3 100644 --- a/docs/sources/userguide/dockervolumes.md +++ b/docs/sources/userguide/dockervolumes.md @@ -1,8 +1,8 @@ -page_title: Managing Data in Containers +page_title: Managing data in containers page_description: How to manage data inside your Docker containers. page_keywords: Examples, Usage, volume, docker, documentation, user guide, data, volumes -# Managing Data in Containers +# Managing data in containers So far we've been introduced to some [basic Docker concepts](/userguide/usingdocker/), seen how to work with [Docker @@ -73,7 +73,7 @@ volumes. The output should look something similar to the following: You will notice in the above 'Volumes' is specifying the location on the host and 'VolumesRW' is specifying that the volume is read/write. -### Mount a Host Directory as a Data Volume +### Mount a host directory as a data volume In addition to creating a volume using the `-v` flag you can also mount a directory from your Docker daemon's host into a container. @@ -116,7 +116,7 @@ read-only. Here we've mounted the same `/src/webapp` directory but we've added the `ro` option to specify that the mount should be read-only. -### Mount a Host File as a Data Volume +### Mount a host file as a data volume The `-v` flag can also be used to mount a single file - instead of *just* directories - from the host machine. @@ -134,7 +134,7 @@ history of the commands typed while in the container. > you want to edit the mounted file, it is often easiest to instead mount the > parent directory. -## Creating and mounting a Data Volume Container +## Creating and mounting a data volume container If you have some persistent data that you want to share between containers, or want to use from non-persistent containers, it's best to diff --git a/docs/sources/userguide/index.md b/docs/sources/userguide/index.md index d0dbdb84e..9cc1c6db3 100644 --- a/docs/sources/userguide/index.md +++ b/docs/sources/userguide/index.md @@ -1,8 +1,8 @@ -page_title: The Docker User Guide -page_description: The Docker User Guide home page +page_title: The Docker user guide +page_description: The Docker user guide home page page_keywords: docker, introduction, documentation, about, technology, docker.io, user, guide, user's, manual, platform, framework, virtualization, home, intro -# Welcome to the Docker User Guide +# Welcome to the Docker user guide In the [Introduction](/) you got a taste of what Docker is and how it works. In this guide we're going to take you through the fundamentals of @@ -19,7 +19,7 @@ We’ll teach you how to use Docker to: We've broken this guide into major sections that take you through the Docker life cycle: -## Getting Started with Docker Hub +## Getting started with Docker Hub *How do I use Docker Hub?* @@ -29,7 +29,7 @@ environment. To learn more: Go to [Using Docker Hub](/userguide/dockerhub). -## Dockerizing Applications: A "Hello world" +## Dockerizing applications: A "Hello world" *How do I run applications inside containers?* @@ -38,7 +38,7 @@ applications. To learn how to Dockerize applications and run them: Go to [Dockerizing Applications](/userguide/dockerizing). -## Working with Containers +## Working with containers *How do I manage my containers?* @@ -48,7 +48,7 @@ about how to inspect, monitor and manage containers: Go to [Working With Containers](/userguide/usingdocker). -## Working with Docker Images +## Working with Docker images *How can I access, share and build my own images?* @@ -57,7 +57,7 @@ learn how to build your own application images with Docker. Go to [Working with Docker Images](/userguide/dockerimages). -## Linking Containers Together +## Linking containers together Until now we've seen how to build individual applications inside Docker containers. Now learn how to build whole application stacks with Docker @@ -65,7 +65,7 @@ by linking together multiple Docker containers. Go to [Linking Containers Together](/userguide/dockerlinks). -## Managing Data in Containers +## Managing data in containers Now we know how to link Docker containers together the next step is learning how to manage data, volumes and mounts inside our containers. diff --git a/docs/sources/userguide/level1.md b/docs/sources/userguide/level1.md index cca77dc36..320fbfee0 100644 --- a/docs/sources/userguide/level1.md +++ b/docs/sources/userguide/level1.md @@ -1,10 +1,10 @@ -page_title: Docker Images Test +page_title: Docker images test page_description: How to work with Docker images. page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, Docker images, Docker image, image management, Docker repos, Docker repositories, docker, docker tag, docker tags, Docker Hub, collaboration Back -# Dockerfile Tutorial +# Dockerfile tutorial ## Test your Dockerfile knowledge - Level 1 diff --git a/docs/sources/userguide/level2.md b/docs/sources/userguide/level2.md index fe6654e71..96e91a1c6 100644 --- a/docs/sources/userguide/level2.md +++ b/docs/sources/userguide/level2.md @@ -1,10 +1,10 @@ -page_title: Docker Images Test +page_title: Docker images test page_description: How to work with Docker images. page_keywords: documentation, docs, the docker guide, docker guide, docker, docker platform, virtualization framework, docker.io, Docker images, Docker image, image management, Docker repos, Docker repositories, docker, docker tag, docker tags, Docker Hub, collaboration Back -#Dockerfile Tutorial +#Dockerfile tutorial ## Test your Dockerfile knowledge - Level 2 diff --git a/docs/sources/userguide/usingdocker.md b/docs/sources/userguide/usingdocker.md index 70996a210..e33ca717d 100644 --- a/docs/sources/userguide/usingdocker.md +++ b/docs/sources/userguide/usingdocker.md @@ -1,8 +1,8 @@ -page_title: Working with Containers +page_title: Working with containers page_description: Learn how to manage and operate Docker containers. page_keywords: docker, the docker guide, documentation, docker.io, monitoring containers, docker top, docker inspect, docker port, ports, docker logs, log, Logs -# Working with Containers +# Working with containers In the [last section of the Docker User Guide](/userguide/dockerizing) we launched our first containers. We launched two containers using the @@ -91,7 +91,7 @@ This will display the help text and all available flags: > You can see a full list of Docker's commands > [here](/reference/commandline/cli/). -## Running a Web Application in Docker +## Running a web application in Docker So now we've learnt a bit more about the `docker` client let's move onto the important stuff: running more containers. So far none of the @@ -121,7 +121,7 @@ Lastly, we've specified a command for our container to run: `python app.py`. Thi > reference](/reference/commandline/cli/#run) and the [Docker Run > Reference](/reference/run/). -## Viewing our Web Application Container +## Viewing our web application container Now let's see our running container using the `docker ps` command. @@ -189,7 +189,7 @@ Our Python application is live! > > In this case you'd browse to http://192.168.59.103:49155 for the above example. -## A Network Port Shortcut +## A network port shortcut Using the `docker ps` command to return the mapped port is a bit clumsy so Docker has a useful shortcut we can use: `docker port`. To use `docker port` we @@ -202,7 +202,7 @@ corresponding public-facing port. In this case we've looked up what port is mapped externally to port 5000 inside the container. -## Viewing the Web Application's Logs +## Viewing the web application's logs Let's also find out a bit more about what's happening with our application and use another of the commands we've learnt, `docker logs`. @@ -217,7 +217,7 @@ logs` command to act like the `tail -f` command and watch the container's standard out. We can see here the logs from Flask showing the application running on port 5000 and the access log entries for it. -## Looking at our Web Application Container's processes +## Looking at our web application container's processes In addition to the container's logs we can also examine the processes running inside it using the `docker top` command. @@ -229,7 +229,7 @@ running inside it using the `docker top` command. Here we can see our `python app.py` command is the only process running inside the container. -## Inspecting our Web Application Container +## Inspecting our web application container Lastly, we can take a low-level dive into our Docker container using the `docker inspect` command. It returns a JSON hash of useful configuration @@ -258,7 +258,7 @@ specific element, for example to return the container's IP address we would: $ docker inspect -f '{{ .NetworkSettings.IPAddress }}' nostalgic_morse 172.17.0.5 -## Stopping our Web Application Container +## Stopping our web application container Okay we've seen web application working. Now let's stop it using the `docker stop` command and the name of our container: `nostalgic_morse`. @@ -271,7 +271,7 @@ been stopped. $ docker ps -l -## Restarting our Web Application Container +## Restarting our web application container Oops! Just after you stopped the container you get a call to say another developer needs the container back. From here you have two choices: you @@ -289,7 +289,7 @@ responds. > Also available is the `docker restart` command that runs a stop and > then start on the container. -## Removing our Web Application Container +## Removing our web application container Your colleague has let you know that they've now finished with the container and won't need it again. So let's remove it using the `docker rm` command. From d6c839cf0f04f30ba95e2b032a4e686dea2eb0d0 Mon Sep 17 00:00:00 2001 From: Tomas Tomecek Date: Thu, 23 Apr 2015 07:40:59 +0200 Subject: [PATCH 588/999] v1 spec: fix typos and formatting Signed-off-by: Tomas Tomecek --- image/spec/v1.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/image/spec/v1.md b/image/spec/v1.md index abed75833..b428cbb20 100644 --- a/image/spec/v1.md +++ b/image/spec/v1.md @@ -31,7 +31,7 @@ This specification uses the following terms: Image JSON

- Each layer has an associated A JSON structure which describes some + Each layer has an associated JSON structure which describes some basic information about the image such as date created, author, and the ID of its parent image as well as execution/runtime configuration like its entry point, default arguments, CPU/memory shares, networking, and @@ -81,7 +81,7 @@ This specification uses the following terms: times of any entries differ. For this reason, image checksums are generated using the TarSum algorithm which produces a cryptographic hash of file contents and selected headers only. Details of this - algorithm are described in the separate [TarSum specification](https://github.com/docker/docker/blob/master/pkg/tarsum/tarsum_spec.md). + algorithm are described in the separate TarSum specification.
Tag @@ -492,9 +492,9 @@ Changeset tar archives. There is also a format for a single archive which contains complete information about an image, including: - - repository names/tags - - all image layer JSON files - - all tar archives of each layer filesystem changesets + - repository names/tags + - all image layer JSON files + - all tar archives of each layer filesystem changesets For example, here's what the full archive of `library/busybox` is (displayed in `tree` format): @@ -523,10 +523,10 @@ For example, here's what the full archive of `library/busybox` is (displayed in There are one or more directories named with the ID for each layer in a full image. Each of these directories contains 3 files: - * `VERSION` - The schema version of the `json` file - * `json` - The JSON metadata for an image layer - * `layer.tar` - The Tar archive of the filesystem changeset for an image - layer. + * `VERSION` - The schema version of the `json` file + * `json` - The JSON metadata for an image layer + * `layer.tar` - The Tar archive of the filesystem changeset for an image + layer. The content of the `VERSION` files is simply the semantic version of the JSON metadata schema: From 1f7ba6f80914cc663b3b38ffc0b2c8e1add2547a Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 23 Apr 2015 09:43:04 +0200 Subject: [PATCH 589/999] Fix the design proposal link in advanced contribution page Signed-off-by: Vincent Demeester --- docs/sources/project/advanced-contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/project/advanced-contributing.md b/docs/sources/project/advanced-contributing.md index f20cbfff9..7ee7a86cb 100644 --- a/docs/sources/project/advanced-contributing.md +++ b/docs/sources/project/advanced-contributing.md @@ -67,7 +67,7 @@ The following provides greater detail on the process: The design proposals are all online in our GitHub pull requests. + 3Akind%2Fproposal" target="_blank">all online in our GitHub pull requests. 3. Talk to the community about your idea. From 62f91b1d34155a58bfeb6ce8d3595149cf5147d7 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Thu, 23 Apr 2015 16:50:41 +0800 Subject: [PATCH 590/999] push test: fix typo Signed-off-by: Ma Shimiao --- integration-cli/docker_cli_push_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index ebf08bae8..3fc1ae3a0 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -72,7 +72,7 @@ func (s *DockerSuite) TestPushMultipleTags(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) repoTag1 := fmt.Sprintf("%v/dockercli/busybox:t1", privateRegistryURL) repoTag2 := fmt.Sprintf("%v/dockercli/busybox:t2", privateRegistryURL) - // tag the image to upload it tot he private registry + // tag the image and upload it to the private registry tagCmd1 := exec.Command(dockerBinary, "tag", "busybox", repoTag1) if out, _, err := runCommandWithOutput(tagCmd1); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) @@ -93,7 +93,7 @@ func (s *DockerSuite) TestPushMultipleTags(c *check.C) { func (s *DockerSuite) TestPushInterrupt(c *check.C) { defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) - // tag the image to upload it tot he private registry + // tag the image and upload it to the private registry if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repoName)); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) } @@ -116,8 +116,8 @@ func (s *DockerSuite) TestPushInterrupt(c *check.C) { } } // now wait until all this pushes will complete - // if it will fail with timeout - this is some error, so no logic about it - // here + // if it failed with timeout - there would be some error, + // so no logic about it here for exec.Command(dockerBinary, "push", repoName).Run() != nil { } } From 47263c6514457d58c1062671077118063189d433 Mon Sep 17 00:00:00 2001 From: Tristan Carel Date: Thu, 23 Apr 2015 12:10:47 +0200 Subject: [PATCH 591/999] Fix typo in builder reference Signed-off-by: Tristan Carel --- docs/sources/reference/builder.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 583698d88..83121e6bd 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -180,7 +180,7 @@ that will be excluded from the context. Globbing is done using Go's > **Note**: > The `.dockerignore` file can even be used to ignore the `Dockerfile` and > `.dockerignore` files. This might be useful if you are copying files from -> the root of the build context into your new containter but do not want to +> the root of the build context into your new container but do not want to > include the `Dockerfile` or `.dockerignore` files (e.g. `ADD . /someDir/`). The following example shows the use of the `.dockerignore` file to exclude the From a8d2fbe7b4593f1e10800a19df14e1bbfb212d41 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Thu, 23 Apr 2015 18:41:30 +0800 Subject: [PATCH 592/999] fix test case name Name like this will never run by go test. And this test case won't get PAAS. Signed-off-by: Qiang Huang --- api/server/server_unit_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go index e7a6afcb9..0daa99bcd 100644 --- a/api/server/server_unit_test.go +++ b/api/server/server_unit_test.go @@ -13,19 +13,20 @@ import ( "github.com/docker/docker/pkg/version" ) -func TesthttpError(t *testing.T) { +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) } + r = httptest.NewRecorder() 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) } + r = httptest.NewRecorder() httpError(r, fmt.Errorf("Some error")) if r.Code != http.StatusInternalServerError { t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code) From 70bb0d8ed7847d7e7850a1a864ff258767f0ad7a Mon Sep 17 00:00:00 2001 From: Simei He Date: Tue, 21 Apr 2015 09:26:15 +0800 Subject: [PATCH 593/999] remove job from load Signed-off-by: Simei He Signed-off-by: He Simei --- api/server/server.go | 12 ++++++++---- graph/load.go | 15 +++++++++++---- graph/service.go | 1 - 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 867593e6a..24b0abd43 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -900,10 +900,14 @@ func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w htt } func (s *Server) postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - job := eng.Job("load") - job.Stdin.Add(r.Body) - job.Stdout.Add(w) - return job.Run() + + imageLoadConfig := &graph.ImageLoadConfig{ + InTar: r.Body, + OutStream: w, + Engine: eng, + } + + return s.daemon.Repositories().Load(imageLoadConfig) } func (s *Server) postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/graph/load.go b/graph/load.go index 5272eb139..f62b82ce4 100644 --- a/graph/load.go +++ b/graph/load.go @@ -4,6 +4,7 @@ package graph import ( "encoding/json" + "io" "io/ioutil" "os" "path" @@ -15,9 +16,15 @@ import ( "github.com/docker/docker/pkg/chrootarchive" ) +type ImageLoadConfig struct { + InTar io.ReadCloser + OutStream io.Writer + Engine *engine.Engine +} + // Loads a set of images into the repository. This is the complementary of ImageExport. // The input stream is an uncompressed tar ball containing images and metadata. -func (s *TagStore) CmdLoad(job *engine.Job) error { +func (s *TagStore) Load(imageLoadConfig *ImageLoadConfig) error { tmpImageDir, err := ioutil.TempDir("", "docker-import-") if err != nil { return err @@ -41,7 +48,7 @@ func (s *TagStore) CmdLoad(job *engine.Job) error { excludes[i] = k i++ } - if err := chrootarchive.Untar(job.Stdin, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil { + if err := chrootarchive.Untar(imageLoadConfig.InTar, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil { return err } @@ -52,7 +59,7 @@ func (s *TagStore) CmdLoad(job *engine.Job) error { for _, d := range dirs { if d.IsDir() { - if err := s.recursiveLoad(job.Eng, d.Name(), tmpImageDir); err != nil { + if err := s.recursiveLoad(imageLoadConfig.Engine, d.Name(), tmpImageDir); err != nil { return err } } @@ -67,7 +74,7 @@ func (s *TagStore) CmdLoad(job *engine.Job) error { for imageName, tagMap := range repositories { for tag, address := range tagMap { - if err := s.SetLoad(imageName, tag, address, true, job.Stdout); err != nil { + if err := s.SetLoad(imageName, tag, address, true, imageLoadConfig.OutStream); err != nil { return err } } diff --git a/graph/service.go b/graph/service.go index 022d5d499..44c1bdef1 100644 --- a/graph/service.go +++ b/graph/service.go @@ -13,7 +13,6 @@ func (s *TagStore) Install(eng *engine.Engine) error { "image_inspect": s.CmdLookup, "image_export": s.CmdImageExport, "viz": s.CmdViz, - "load": s.CmdLoad, "push": s.CmdPush, } { if err := eng.Register(name, handler); err != nil { From ab42a3a23a1276ccad874a13cf8604cb59124e10 Mon Sep 17 00:00:00 2001 From: Zhang Wei Date: Thu, 23 Apr 2015 19:37:47 +0800 Subject: [PATCH 594/999] refactor httpError() and add 404 "not found" mapping When docker pull a non-existent repo, daemon will report "image xxx not found" with an error code 500, which should be 404. This commit add 404 "not found" mapping and refactor httpError function. Signed-off-by: Zhang Wei --- api/server/server.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 867593e6a..178e880e5 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -203,18 +203,19 @@ func httpError(w http.ResponseWriter, err error) { // If we need to differentiate between different possible error types, we should // create appropriate error types with clearly defined meaning. errStr := strings.ToLower(err.Error()) - if strings.Contains(errStr, "no such") { - statusCode = http.StatusNotFound - } else if strings.Contains(errStr, "bad parameter") { - statusCode = http.StatusBadRequest - } else if strings.Contains(errStr, "conflict") { - statusCode = http.StatusConflict - } else if strings.Contains(errStr, "impossible") { - statusCode = http.StatusNotAcceptable - } else if strings.Contains(errStr, "wrong login/password") { - statusCode = http.StatusUnauthorized - } else if strings.Contains(errStr, "hasn't been activated") { - statusCode = http.StatusForbidden + for keyword, status := range map[string]int{ + "not found": http.StatusNotFound, + "no such": http.StatusNotFound, + "bad parameter": http.StatusBadRequest, + "conflict": http.StatusConflict, + "impossible": http.StatusNotAcceptable, + "wrong login/password": http.StatusUnauthorized, + "hasn't been activated": http.StatusForbidden, + } { + if strings.Contains(errStr, keyword) { + statusCode = status + break + } } logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": err}).Error("HTTP Error") From d456401fe1d038ddaf9866bb6ab4ac5744186e2d Mon Sep 17 00:00:00 2001 From: Simei He Date: Tue, 21 Apr 2015 11:16:25 +0800 Subject: [PATCH 595/999] remove job from push Signed-off-by: Simei He Signed-off-by: He Simei --- api/server/server.go | 31 ++++++++++++++++--------------- graph/push.go | 35 +++++++++++++++++------------------ graph/service.go | 1 - 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 24b0abd43..646a8c677 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -858,25 +858,26 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h } } - 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.Stdout, w, true) - } else { - job.Stdout.Add(utils.NewWriteFlusher(w)) + useJSON := version.GreaterThan("1.0") + name := vars["name"] + + imagePushConfig := &graph.ImagePushConfig{ + MetaHeaders: metaHeaders, + AuthConfig: authConfig, + Tag: r.Form.Get("tag"), + OutStream: utils.NewWriteFlusher(w), + Json: useJSON, + } + if useJSON { + w.Header().Set("Content-Type", "application/json") } - if err := job.Run(); err != nil { - if !job.Stdout.Used() { - return err - } - sf := streamformatter.NewStreamFormatter(version.GreaterThan("1.0")) - w.Write(sf.FormatError(err)) + if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil { + sf := streamformatter.NewStreamFormatter(useJSON) + return fmt.Errorf(string(sf.FormatError(err))) } return nil + } func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/graph/push.go b/graph/push.go index fedd29498..34db27b91 100644 --- a/graph/push.go +++ b/graph/push.go @@ -12,7 +12,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" @@ -25,6 +24,14 @@ import ( var ErrV2RegistryUnavailable = errors.New("error v2 registry unavailable") +type ImagePushConfig struct { + MetaHeaders map[string][]string + AuthConfig *registry.AuthConfig + Tag string + Json bool + OutStream io.Writer +} + // Retrieve the all the images to be uploaded in the correct order func (s *TagStore) getImageList(localRepo map[string]string, requestedTag string) ([]string, map[string][]string, error) { var ( @@ -486,15 +493,9 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint * } // FIXME: Allow to interrupt current push when new push of same image is done. -func (s *TagStore) CmdPush(job *engine.Job) error { - if n := len(job.Args); n != 1 { - return fmt.Errorf("Usage: %s IMAGE", job.Name) - } +func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) error { var ( - localName = job.Args[0] - sf = streamformatter.NewStreamFormatter(job.GetenvBool("json")) - authConfig = ®istry.AuthConfig{} - metaHeaders map[string][]string + sf = streamformatter.NewStreamFormatter(imagePushConfig.Json) ) // Resolve the Repository name from fqn to RepositoryInfo @@ -503,10 +504,6 @@ func (s *TagStore) CmdPush(job *engine.Job) error { return err } - tag := job.Getenv("tag") - job.GetenvJson("authConfig", authConfig) - job.GetenvJson("metaHeaders", &metaHeaders) - if _, err := s.poolAdd("push", repoInfo.LocalName); err != nil { return err } @@ -517,16 +514,18 @@ func (s *TagStore) CmdPush(job *engine.Job) error { return err } - r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false) + r, err := registry.NewSession(imagePushConfig.AuthConfig, registry.HTTPRequestFactory(imagePushConfig.MetaHeaders), endpoint, false) if err != nil { return err } reposLen := 1 - if tag == "" { + if imagePushConfig.Tag == "" { reposLen = len(s.Repositories[repoInfo.LocalName]) } - job.Stdout.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen)) + + imagePushConfig.OutStream.Write(sf.FormatStatus("", "The push refers to a repository [%s] (len: %d)", repoInfo.CanonicalName, reposLen)) + // If it fails, try to get the repository localRepo, exists := s.Repositories[repoInfo.LocalName] if !exists { @@ -534,7 +533,7 @@ func (s *TagStore) CmdPush(job *engine.Job) error { } if repoInfo.Index.Official || endpoint.Version == registry.APIVersion2 { - err := s.pushV2Repository(r, localRepo, job.Stdout, repoInfo, tag, sf) + err := s.pushV2Repository(r, localRepo, imagePushConfig.OutStream, repoInfo, imagePushConfig.Tag, sf) if err == nil { s.eventsService.Log("push", repoInfo.LocalName, "") return nil @@ -545,7 +544,7 @@ func (s *TagStore) CmdPush(job *engine.Job) error { } } - if err := s.pushRepository(r, job.Stdout, repoInfo, localRepo, tag, sf); err != nil { + if err := s.pushRepository(r, imagePushConfig.OutStream, repoInfo, localRepo, imagePushConfig.Tag, sf); err != nil { return err } s.eventsService.Log("push", repoInfo.LocalName, "") diff --git a/graph/service.go b/graph/service.go index 44c1bdef1..337eaa3cf 100644 --- a/graph/service.go +++ b/graph/service.go @@ -13,7 +13,6 @@ func (s *TagStore) Install(eng *engine.Engine) error { "image_inspect": s.CmdLookup, "image_export": s.CmdImageExport, "viz": s.CmdViz, - "push": s.CmdPush, } { if err := eng.Register(name, handler); err != nil { return fmt.Errorf("Could not register %q: %v", name, err) From 563708d78d42afe89374d5819fdb671ed72c2dad Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Thu, 23 Apr 2015 09:20:17 -0400 Subject: [PATCH 596/999] Fix race with TestContainerApiCommit Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 4 ++-- integration-cli/docker_utils.go | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index db4093733..7a6468b16 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -625,7 +625,7 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { } id := strings.TrimSpace(string(out)) - name := "testcommit" + name := "testcommit" + stringid.GenerateRandomID() _, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+id, nil) if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { c.Fatal(err) @@ -650,7 +650,7 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { // sanity check, make sure the image is what we think it is out, err = exec.Command(dockerBinary, "run", img.Id, "ls", "/test").CombinedOutput() if err != nil { - c.Fatal(out, err) + c.Fatalf("error checking commited image: %v - %q", err, string(out)) } } diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 855352973..cc5429a57 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -444,8 +444,7 @@ func unpauseAllContainers() error { } func deleteImages(images ...string) error { - args := make([]string, 1, 2) - args[0] = "rmi" + args := []string{"rmi", "-f"} args = append(args, images...) rmiCmd := exec.Command(dockerBinary, args...) exitCode, err := runCommand(rmiCmd) @@ -453,7 +452,6 @@ func deleteImages(images ...string) error { if exitCode != 0 && err == nil { err = fmt.Errorf("failed to remove image: `docker rmi` exit is non-zero") } - return err } From fca4aea077f1960a0cdd0056477730b3f8e0ca38 Mon Sep 17 00:00:00 2001 From: Rajdeep Dua Date: Wed, 22 Apr 2015 04:03:57 -0700 Subject: [PATCH 597/999] TestCase added for Container Create with HostName Signed-off-by: Rajdeep Dua --- integration-cli/docker_api_containers_test.go | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index db4093733..8dfdc16d7 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -681,6 +681,47 @@ func (s *DockerSuite) TestContainerApiCreate(c *check.C) { } } +func (s *DockerSuite) TestContainerApiCreateWithHostName(c *check.C) { + var hostName = "test-host" + config := map[string]interface{}{ + "Image": "busybox", + "Hostname": hostName, + } + + _, b, err := sockRequest("POST", "/containers/create", config) + if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { + c.Fatal(err) + } + type createResp struct { + Id string + } + var container createResp + if err := json.Unmarshal(b, &container); err != nil { + c.Fatal(err) + } + + var id = container.Id + + _, bodyGet, err := sockRequest("GET", "/containers/"+id+"/json", nil) + + type configLocal struct { + Hostname string + } + type getResponse struct { + Id string + Config configLocal + } + + var containerInfo getResponse + if err := json.Unmarshal(bodyGet, &containerInfo); err != nil { + c.Fatal(err) + } + var hostNameActual = containerInfo.Config.Hostname + if hostNameActual != "test-host" { + c.Fatalf("Mismatched Hostname, Expected %v, Actual: %v ", hostName, hostNameActual) + } +} + func (s *DockerSuite) TestContainerApiVerifyHeader(c *check.C) { config := map[string]interface{}{ "Image": "busybox", From cd6cc45d52a135e9e123c2e8c647710a061d38a0 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 23 Apr 2015 16:11:20 +0200 Subject: [PATCH 598/999] Fix TestRenameStoppedContainer race Signed-off-by: Antonio Murdaca --- integration-cli/docker_cli_rename_test.go | 24 +++++++++++------------ 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/integration-cli/docker_cli_rename_test.go b/integration-cli/docker_cli_rename_test.go index fcd87b54b..156ea6eeb 100644 --- a/integration-cli/docker_cli_rename_test.go +++ b/integration-cli/docker_cli_rename_test.go @@ -4,11 +4,11 @@ import ( "os/exec" "strings" + "github.com/docker/docker/pkg/stringid" "github.com/go-check/check" ) func (s *DockerSuite) TestRenameStoppedContainer(c *check.C) { - runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) if err != nil { @@ -25,7 +25,8 @@ func (s *DockerSuite) TestRenameStoppedContainer(c *check.C) { name, err := inspectField(cleanedContainerID, "Name") - runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") + newName := "new_name" + stringid.GenerateRandomID() + runCmd = exec.Command(dockerBinary, "rename", "first_name", newName) out, _, err = runCommandWithOutput(runCmd) if err != nil { c.Fatalf(out, err) @@ -35,22 +36,22 @@ func (s *DockerSuite) TestRenameStoppedContainer(c *check.C) { if err != nil { c.Fatal(err) } - if name != "/new_name" { + if name != "/"+newName { c.Fatal("Failed to rename container ", name) } } func (s *DockerSuite) TestRenameRunningContainer(c *check.C) { - runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) if err != nil { c.Fatalf(out, err) } + newName := "new_name" + stringid.GenerateRandomID() cleanedContainerID := strings.TrimSpace(out) - runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") + runCmd = exec.Command(dockerBinary, "rename", "first_name", newName) out, _, err = runCommandWithOutput(runCmd) if err != nil { c.Fatalf(out, err) @@ -60,31 +61,30 @@ func (s *DockerSuite) TestRenameRunningContainer(c *check.C) { if err != nil { c.Fatal(err) } - if name != "/new_name" { + if name != "/"+newName { c.Fatal("Failed to rename container ") } - } func (s *DockerSuite) TestRenameCheckNames(c *check.C) { - runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) if err != nil { c.Fatalf(out, err) } - runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name") + newName := "new_name" + stringid.GenerateRandomID() + runCmd = exec.Command(dockerBinary, "rename", "first_name", newName) out, _, err = runCommandWithOutput(runCmd) if err != nil { c.Fatalf(out, err) } - name, err := inspectField("new_name", "Name") + name, err := inspectField(newName, "Name") if err != nil { c.Fatal(err) } - if name != "/new_name" { + if name != "/"+newName { c.Fatal("Failed to rename container ") } @@ -92,7 +92,6 @@ func (s *DockerSuite) TestRenameCheckNames(c *check.C) { if err == nil && !strings.Contains(err.Error(), "No such image or container: first_name") { c.Fatal(err) } - } func (s *DockerSuite) TestRenameInvalidName(c *check.C) { @@ -110,5 +109,4 @@ func (s *DockerSuite) TestRenameInvalidName(c *check.C) { if out, _, err := runCommandWithOutput(runCmd); err != nil || !strings.Contains(out, "myname") { c.Fatalf("Output of docker ps should have included 'myname': %s\n%v", out, err) } - } From ee7a7b07e752c56bbe1b941feb1e4313275d68c2 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 23 Apr 2015 16:32:48 +0200 Subject: [PATCH 599/999] Remove deleteAllContainers call in test Signed-off-by: Antonio Murdaca --- integration-cli/docker_api_containers_test.go | 3 --- integration-cli/docker_cli_build_test.go | 1 - integration-cli/docker_cli_create_test.go | 7 ------- integration-cli/docker_cli_ps_test.go | 3 --- integration-cli/docker_cli_run_test.go | 2 -- 5 files changed, 16 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index db4093733..82317340f 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -773,7 +773,6 @@ func (s *DockerSuite) TestContainerApiPostCreateNull(c *check.C) { } func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { - defer deleteAllContainers() config := `{ "Image": "busybox", "Cmd": "ls", @@ -795,8 +794,6 @@ func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { } func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { - defer deleteAllContainers() - out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "busybox")) if err != nil { c.Fatal(err, out) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 7936b2bc3..56a11cdd4 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -2090,7 +2090,6 @@ func (s *DockerSuite) TestBuildRm(c *check.C) { if containerCountBefore == containerCountAfter { c.Fatalf("--rm=false should have left containers behind") } - deleteAllContainers() deleteImages(name) } diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index 5fbd50b6d..10c499982 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -14,7 +14,6 @@ import ( // Make sure we can create a simple container with some args func (s *DockerSuite) TestCreateArgs(c *check.C) { - runCmd := exec.Command(dockerBinary, "create", "busybox", "command", "arg1", "arg2", "arg with space") out, _, _, err := runCommandWithStdoutStderr(runCmd) if err != nil { @@ -256,9 +255,6 @@ func (s *DockerSuite) TestCreateLabels(c *check.C) { if !reflect.DeepEqual(expected, actual) { c.Fatalf("Expected %s got %s", expected, actual) } - - deleteAllContainers() - } func (s *DockerSuite) TestCreateLabelFromImage(c *check.C) { @@ -287,9 +283,6 @@ func (s *DockerSuite) TestCreateLabelFromImage(c *check.C) { if !reflect.DeepEqual(expected, actual) { c.Fatalf("Expected %s got %s", expected, actual) } - - deleteAllContainers() - } func (s *DockerSuite) TestCreateHostnameWithNumber(c *check.C) { diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index d70215350..ca2d67bc9 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -478,9 +478,6 @@ func (s *DockerSuite) TestPsListContainersFilterLabel(c *check.C) { if (!strings.Contains(containerOut, firstID) || !strings.Contains(containerOut, secondID)) || strings.Contains(containerOut, thirdID) { c.Fatalf("Expected ids %s,%s, got %s for exited filter, output: %q", firstID, secondID, containerOut, out) } - - deleteAllContainers() - } func (s *DockerSuite) TestPsListContainersFilterExited(c *check.C) { diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 7e12fc5a9..f23417037 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -1448,7 +1448,6 @@ func (s *DockerSuite) TestRunResolvconfUpdater(c *check.C) { //cleanup defer func() { - deleteAllContainers() if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil { c.Fatal(err) } @@ -2380,7 +2379,6 @@ func (s *DockerSuite) TestRunVolumesNotRecreatedOnStart(c *check.C) { testRequires(c, SameHostDaemon) // Clear out any remnants from other tests - deleteAllContainers() info, err := ioutil.ReadDir(volumesConfigPath) if err != nil { c.Fatal(err) From 05013f1250dc141ed43f987dd8b6a650d0e47ac9 Mon Sep 17 00:00:00 2001 From: Srini Brahmaroutu Date: Mon, 20 Apr 2015 23:21:46 +0000 Subject: [PATCH 600/999] Move https integration tests as unit tests under client Addresses #12255 Signed-off-by: Srini Brahmaroutu --- integration-cli/docker_cli_daemon_test.go | 68 +++++++++++++++ integration-cli/docker_utils.go | 8 ++ .../fixtures/https/ca.pem | 0 .../fixtures/https/client-cert.pem | 0 .../fixtures/https/client-key.pem | 0 .../fixtures/https/client-rogue-cert.pem | 0 .../fixtures/https/client-rogue-key.pem | 0 .../fixtures/https/server-cert.pem | 0 .../fixtures/https/server-key.pem | 0 .../fixtures/https/server-rogue-cert.pem | 0 .../fixtures/https/server-rogue-key.pem | 0 integration/https_test.go | 84 ------------------- integration/runtime_test.go | 58 ------------- 13 files changed, 76 insertions(+), 142 deletions(-) rename {integration => integration-cli}/fixtures/https/ca.pem (100%) rename {integration => integration-cli}/fixtures/https/client-cert.pem (100%) rename {integration => integration-cli}/fixtures/https/client-key.pem (100%) rename {integration => integration-cli}/fixtures/https/client-rogue-cert.pem (100%) rename {integration => integration-cli}/fixtures/https/client-rogue-key.pem (100%) rename {integration => integration-cli}/fixtures/https/server-cert.pem (100%) rename {integration => integration-cli}/fixtures/https/server-key.pem (100%) rename {integration => integration-cli}/fixtures/https/server-rogue-cert.pem (100%) rename {integration => integration-cli}/fixtures/https/server-rogue-key.pem (100%) delete mode 100644 integration/https_test.go diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 81cf0ab0c..2a945827e 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -910,3 +910,71 @@ func (s *DockerSuite) TestDaemonRestartKillWait(c *check.C) { } } + +// TestHttpsInfo connects via two-way authenticated HTTPS to the info endpoint +func (s *DockerSuite) TestHttpsInfo(c *check.C) { + const ( + testDaemonHttpsAddr = "localhost:4271" + ) + + d := NewDaemon(c) + if err := d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem", + "--tlskey", "fixtures/https/server-key.pem", "-H", testDaemonHttpsAddr); err != nil { + c.Fatalf("Could not start daemon with busybox: %v", err) + } + defer d.Stop() + + //force tcp protocol + host := fmt.Sprintf("tcp://%s", testDaemonHttpsAddr) + daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-cert.pem", "--tlskey", "fixtures/https/client-key.pem"} + out, err := d.CmdWithArgs(daemonArgs, "info") + if err != nil { + c.Fatalf("Error Occurred: %s and output: %s", err, out) + } +} + +// 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 (s *DockerSuite) TestHttpsInfoRogueCert(c *check.C) { + const ( + errBadCertificate = "remote error: bad certificate" + testDaemonHttpsAddr = "localhost:4271" + ) + d := NewDaemon(c) + if err := d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem", + "--tlskey", "fixtures/https/server-key.pem", "-H", testDaemonHttpsAddr); err != nil { + c.Fatalf("Could not start daemon with busybox: %v", err) + } + defer d.Stop() + + //force tcp protocol + host := fmt.Sprintf("tcp://%s", testDaemonHttpsAddr) + daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-rogue-cert.pem", "--tlskey", "fixtures/https/client-rogue-key.pem"} + out, err := d.CmdWithArgs(daemonArgs, "info") + if err == nil || !strings.Contains(out, errBadCertificate) { + c.Fatalf("Expected err: %s, got instead: %s and output: %s", errBadCertificate, err, out) + } +} + +// 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 (s *DockerSuite) TestHttpsInfoRogueServerCert(c *check.C) { + const ( + errCaUnknown = "x509: certificate signed by unknown authority" + testDaemonRogueHttpsAddr = "localhost:4272" + ) + d := NewDaemon(c) + if err := d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-rogue-cert.pem", + "--tlskey", "fixtures/https/server-rogue-key.pem", "-H", testDaemonRogueHttpsAddr); err != nil { + c.Fatalf("Could not start daemon with busybox: %v", err) + } + defer d.Stop() + + //force tcp protocol + host := fmt.Sprintf("tcp://%s", testDaemonRogueHttpsAddr) + daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-rogue-cert.pem", "--tlskey", "fixtures/https/client-rogue-key.pem"} + out, err := d.CmdWithArgs(daemonArgs, "info") + if err == nil || !strings.Contains(out, errCaUnknown) { + c.Fatalf("Expected err: %s, got instead: %s and output: %s", errCaUnknown, err, out) + } +} diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 855352973..7c26b11bd 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -269,6 +269,14 @@ func (d *Daemon) Cmd(name string, arg ...string) (string, error) { return string(b), err } +func (d *Daemon) CmdWithArgs(daemonArgs []string, name string, arg ...string) (string, error) { + args := append(daemonArgs, name) + args = append(args, arg...) + c := exec.Command(dockerBinary, args...) + b, err := c.CombinedOutput() + return string(b), err +} + func (d *Daemon) LogfileName() string { return d.logFile.Name() } diff --git a/integration/fixtures/https/ca.pem b/integration-cli/fixtures/https/ca.pem similarity index 100% rename from integration/fixtures/https/ca.pem rename to integration-cli/fixtures/https/ca.pem diff --git a/integration/fixtures/https/client-cert.pem b/integration-cli/fixtures/https/client-cert.pem similarity index 100% rename from integration/fixtures/https/client-cert.pem rename to integration-cli/fixtures/https/client-cert.pem diff --git a/integration/fixtures/https/client-key.pem b/integration-cli/fixtures/https/client-key.pem similarity index 100% rename from integration/fixtures/https/client-key.pem rename to integration-cli/fixtures/https/client-key.pem diff --git a/integration/fixtures/https/client-rogue-cert.pem b/integration-cli/fixtures/https/client-rogue-cert.pem similarity index 100% rename from integration/fixtures/https/client-rogue-cert.pem rename to integration-cli/fixtures/https/client-rogue-cert.pem diff --git a/integration/fixtures/https/client-rogue-key.pem b/integration-cli/fixtures/https/client-rogue-key.pem similarity index 100% rename from integration/fixtures/https/client-rogue-key.pem rename to integration-cli/fixtures/https/client-rogue-key.pem diff --git a/integration/fixtures/https/server-cert.pem b/integration-cli/fixtures/https/server-cert.pem similarity index 100% rename from integration/fixtures/https/server-cert.pem rename to integration-cli/fixtures/https/server-cert.pem diff --git a/integration/fixtures/https/server-key.pem b/integration-cli/fixtures/https/server-key.pem similarity index 100% rename from integration/fixtures/https/server-key.pem rename to integration-cli/fixtures/https/server-key.pem diff --git a/integration/fixtures/https/server-rogue-cert.pem b/integration-cli/fixtures/https/server-rogue-cert.pem similarity index 100% rename from integration/fixtures/https/server-rogue-cert.pem rename to integration-cli/fixtures/https/server-rogue-cert.pem diff --git a/integration/fixtures/https/server-rogue-key.pem b/integration-cli/fixtures/https/server-rogue-key.pem similarity index 100% rename from integration/fixtures/https/server-rogue-key.pem rename to integration-cli/fixtures/https/server-rogue-key.pem diff --git a/integration/https_test.go b/integration/https_test.go deleted file mode 100644 index 17d69345a..000000000 --- a/integration/https_test.go +++ /dev/null @@ -1,84 +0,0 @@ -package docker - -import ( - "crypto/tls" - "crypto/x509" - "io/ioutil" - "strings" - "testing" - "time" - - "github.com/docker/docker/api/client" -) - -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 := 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() { - 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 := 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() { - err := cli.CmdInfo() - if err == nil { - t.Fatal("Expected error but got nil") - } - if !strings.Contains(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 := 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() { - err := cli.CmdInfo() - if err == nil { - t.Fatal("Expected error but got nil") - } - - if !strings.Contains(err.Error(), errCaUnknown) { - t.Fatalf("Expected error: %s, got instead: %s", errCaUnknown, err) - } - - }) -} diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 0c412c862..11df1f5d6 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -120,8 +120,6 @@ func init() { // Create the "global daemon" with a long-running daemons for integration tests spawnGlobalDaemon() - spawnLegitHttpsDaemon() - spawnRogueHttpsDaemon() startFds, startGoroutines = fileutils.GetTotalUsedFds(), runtime.NumGoroutine() } @@ -175,62 +173,6 @@ func spawnGlobalDaemon() { api.AcceptConnections(getDaemon(eng)) } -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 := std_log.New(os.Stderr, "", 0) - root, err := newTestDirectory(unitTestStoreBase) - if err != nil { - t.Fatal(err) - } - // FIXME: here we don't use NewTestEngine because it configures the daemon with Autorestart=false, - // and we want to set it to true. - - eng := newTestEngine(t, true, root) - - serverConfig := &apiserver.ServerConfig{ - Logging: true, - Tls: true, - TlsVerify: true, - TlsCa: cacert, - TlsCert: cert, - TlsKey: key, - } - api := apiserver.New(serverConfig, eng) - // Spawn a Daemon - go func() { - logrus.Debugf("Spawning https daemon for integration tests") - listenURL := &url.URL{ - Scheme: testDaemonHttpsProto, - Host: addr, - } - if err := api.ServeApi([]string{listenURL.String()}); err != nil { - logrus.Fatalf("Unable to spawn the test daemon: %s", err) - } - }() - - // Give some time to ListenAndServer to actually start - time.Sleep(time.Second) - - api.AcceptConnections(getDaemon(eng)) - - return eng -} - // FIXME: test that ImagePull(json=true) send correct json output func GetTestImage(daemon *daemon.Daemon) *image.Image { From bb9da6ba9294a8eab8f4dfaf7cf07c57959fe608 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 22 Apr 2015 05:06:58 -0700 Subject: [PATCH 601/999] Move CLI config processing out from under registry dir No logic changes should be in here, just moving things around. Signed-off-by: Doug Davis --- api/client/cli.go | 9 +- api/client/create.go | 2 +- api/client/login.go | 3 +- api/client/push.go | 2 +- api/client/utils.go | 7 +- api/server/server.go | 24 +-- builder/evaluator.go | 6 +- builder/internals.go | 3 +- builder/job.go | 9 +- cliconfig/config.go | 208 +++++++++++++++++++ {registry => cliconfig}/config_file_test.go | 14 +- graph/pull.go | 3 +- graph/push.go | 3 +- integration/runtime_test.go | 4 +- registry/auth.go | 210 ++------------------ registry/auth_test.go | 43 ++-- registry/registry_test.go | 5 +- registry/service.go | 6 +- registry/session.go | 9 +- 19 files changed, 301 insertions(+), 269 deletions(-) create mode 100644 cliconfig/config.go rename {registry => cliconfig}/config_file_test.go (94%) diff --git a/api/client/cli.go b/api/client/cli.go index 0a1fb2ef8..600d4cc5a 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -15,10 +15,10 @@ import ( "text/template" "time" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" - "github.com/docker/docker/registry" ) // DockerCli represents the docker command line client. @@ -28,8 +28,9 @@ type DockerCli struct { proto string // addr holds the client address. addr string - // configFile holds the configuration file (instance of registry.ConfigFile). - configFile *registry.ConfigFile + + // configFile has the client configuration file + configFile *cliconfig.ConfigFile // in holds the input stream and closer (io.ReadCloser) for the client. in io.ReadCloser // out holds the output stream (io.Writer) for the client. @@ -184,7 +185,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a tr.Dial = (&net.Dialer{Timeout: timeout}).Dial } - configFile, e := registry.LoadConfig(filepath.Join(homedir.Get(), ".docker")) + configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker")) if e != nil { fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) } diff --git a/api/client/create.go b/api/client/create.go index d2987a67e..b0819a05d 100644 --- a/api/client/create.go +++ b/api/client/create.go @@ -38,7 +38,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error { } // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) + authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index) buf, err := json.Marshal(authConfig) if err != nil { return err diff --git a/api/client/login.go b/api/client/login.go index e8e87fc5e..d7da1de2b 100644 --- a/api/client/login.go +++ b/api/client/login.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/docker/docker/api/types" + "github.com/docker/docker/cliconfig" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" "github.com/docker/docker/registry" @@ -56,7 +57,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig, ok := cli.configFile.AuthConfigs[serverAddress] if !ok { - authconfig = registry.AuthConfig{} + authconfig = cliconfig.AuthConfig{} } if username == "" { diff --git a/api/client/push.go b/api/client/push.go index d4fc4c5c9..dc4266cb7 100644 --- a/api/client/push.go +++ b/api/client/push.go @@ -28,7 +28,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { return err } // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) + authConfig := registry.ResolveAuthConfig(cli.configFile, repoInfo.Index) // If we're not using a custom registry, we know the restrictions // applied to repository names and can warn the user in advance. // Custom repositories can have different rules, and we must also diff --git a/api/client/utils.go b/api/client/utils.go index 804dc0c58..7a52ad25f 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -21,6 +21,7 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/engine" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/signal" @@ -119,7 +120,7 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m } func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) { - cmdAttempt := func(authConfig registry.AuthConfig) (io.ReadCloser, int, error) { + cmdAttempt := func(authConfig cliconfig.AuthConfig) (io.ReadCloser, int, error) { buf, err := json.Marshal(authConfig) if err != nil { return nil, -1, err @@ -150,14 +151,14 @@ func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reade } // Resolve the Auth config relevant for this server - authConfig := cli.configFile.ResolveAuthConfig(index) + authConfig := registry.ResolveAuthConfig(cli.configFile, index) body, statusCode, err := cmdAttempt(authConfig) if statusCode == http.StatusUnauthorized { fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName) if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil { return nil, -1, err } - authConfig = cli.configFile.ResolveAuthConfig(index) + authConfig = registry.ResolveAuthConfig(cli.configFile, index) return cmdAttempt(authConfig) } return body, statusCode, err diff --git a/api/server/server.go b/api/server/server.go index 646a8c677..e43ad29ba 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -22,6 +22,7 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/builder" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" "github.com/docker/docker/engine" @@ -34,7 +35,6 @@ import ( "github.com/docker/docker/pkg/stdcopy" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/version" - "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -239,7 +239,7 @@ func streamJSON(out *engine.Output, w http.ResponseWriter, flush bool) { } func (s *Server) postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - var config *registry.AuthConfig + var config *cliconfig.AuthConfig err := json.NewDecoder(r.Body).Decode(&config) r.Body.Close() if err != nil { @@ -728,13 +728,13 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w tag = r.Form.Get("tag") ) authEncoded := r.Header.Get("X-Registry-Auth") - authConfig := ®istry.AuthConfig{} + authConfig := &cliconfig.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 = ®istry.AuthConfig{} + authConfig = &cliconfig.AuthConfig{} } } @@ -802,7 +802,7 @@ func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w return err } var ( - config *registry.AuthConfig + config *cliconfig.AuthConfig authEncoded = r.Header.Get("X-Registry-Auth") headers = map[string][]string{} ) @@ -812,7 +812,7 @@ func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w if err := json.NewDecoder(authJson).Decode(&config); 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 - config = ®istry.AuthConfig{} + config = &cliconfig.AuthConfig{} } } for k, v := range r.Header { @@ -841,7 +841,7 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h if err := parseForm(r); err != nil { return err } - authConfig := ®istry.AuthConfig{} + authConfig := &cliconfig.AuthConfig{} authEncoded := r.Header.Get("X-Registry-Auth") if authEncoded != "" { @@ -849,7 +849,7 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h 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 = ®istry.AuthConfig{} + authConfig = &cliconfig.AuthConfig{} } } else { // the old format is supported for compatibility if there was no authConfig header @@ -1263,9 +1263,9 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R } var ( authEncoded = r.Header.Get("X-Registry-Auth") - authConfig = ®istry.AuthConfig{} + authConfig = &cliconfig.AuthConfig{} configFileEncoded = r.Header.Get("X-Registry-Config") - configFile = ®istry.ConfigFile{} + configFile = &cliconfig.ConfigFile{} buildConfig = builder.NewBuildConfig() ) @@ -1278,7 +1278,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R 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 = ®istry.AuthConfig{} + authConfig = &cliconfig.AuthConfig{} } } @@ -1287,7 +1287,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R 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 = ®istry.ConfigFile{} + configFile = &cliconfig.ConfigFile{} } } diff --git a/builder/evaluator.go b/builder/evaluator.go index 7cbba0351..2f9d4ff85 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -30,13 +30,13 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/builder/command" "github.com/docker/docker/builder/parser" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/tarsum" - "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -99,8 +99,8 @@ type Builder struct { // the final configs of the Dockerfile but dont want the layers disableCommit bool - AuthConfig *registry.AuthConfig - ConfigFile *registry.ConfigFile + AuthConfig *cliconfig.AuthConfig + ConfigFile *cliconfig.ConfigFile // Deprecated, original writer used for ImagePull. To be removed. OutOld io.Writer diff --git a/builder/internals.go b/builder/internals.go index 9574351ca..731ca84fe 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -36,6 +36,7 @@ import ( "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/pkg/urlutil" + "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" ) @@ -443,7 +444,7 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { if err != nil { return nil, err } - resolvedAuth := b.ConfigFile.ResolveAuthConfig(repoInfo.Index) + resolvedAuth := registry.ResolveAuthConfig(b.ConfigFile, repoInfo.Index) pullRegistryAuth = &resolvedAuth } diff --git a/builder/job.go b/builder/job.go index 4c6e55b0a..115d89a4b 100644 --- a/builder/job.go +++ b/builder/job.go @@ -12,6 +12,7 @@ import ( "github.com/docker/docker/api" "github.com/docker/docker/builder/parser" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" @@ -50,8 +51,8 @@ type Config struct { CpuShares int64 CpuSetCpus string CpuSetMems string - AuthConfig *registry.AuthConfig - ConfigFile *registry.ConfigFile + AuthConfig *cliconfig.AuthConfig + ConfigFile *cliconfig.ConfigFile Stdout io.Writer Context io.ReadCloser @@ -76,8 +77,8 @@ func (b *Config) WaitCancelled() <-chan struct{} { func NewBuildConfig() *Config { return &Config{ - AuthConfig: ®istry.AuthConfig{}, - ConfigFile: ®istry.ConfigFile{}, + AuthConfig: &cliconfig.AuthConfig{}, + ConfigFile: &cliconfig.ConfigFile{}, cancelled: make(chan struct{}), } } diff --git a/cliconfig/config.go b/cliconfig/config.go new file mode 100644 index 000000000..19a92fbd8 --- /dev/null +++ b/cliconfig/config.go @@ -0,0 +1,208 @@ +package cliconfig + +import ( + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/docker/docker/pkg/homedir" +) + +const ( + // Where we store the config file + CONFIGFILE = "config.json" + OLD_CONFIGFILE = ".dockercfg" + + // This constant is only used for really old config files when the + // URL wasn't saved as part of the config file and it was just + // assumed to be this value. + DEFAULT_INDEXSERVER = "https://index.docker.io/v1/" +) + +var ( + ErrConfigFileMissing = errors.New("The Auth config file is missing") +) + +// Registry Auth Info +type AuthConfig struct { + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Auth string `json:"auth"` + Email string `json:"email"` + ServerAddress string `json:"serveraddress,omitempty"` +} + +// ~/.docker/config.json file info +type ConfigFile struct { + AuthConfigs map[string]AuthConfig `json:"auths"` + HttpHeaders map[string]string `json:"HttpHeaders,omitempty"` + filename string // Note: not serialized - for internal use only +} + +func NewConfigFile(fn string) *ConfigFile { + return &ConfigFile{ + AuthConfigs: make(map[string]AuthConfig), + HttpHeaders: make(map[string]string), + filename: fn, + } +} + +// load up the auth config information and return values +// FIXME: use the internal golang config parser +func Load(configDir string) (*ConfigFile, error) { + if configDir == "" { + configDir = filepath.Join(homedir.Get(), ".docker") + } + + configFile := ConfigFile{ + AuthConfigs: make(map[string]AuthConfig), + filename: filepath.Join(configDir, CONFIGFILE), + } + + // Try happy path first - latest config file + if _, err := os.Stat(configFile.filename); err == nil { + file, err := os.Open(configFile.filename) + if err != nil { + return &configFile, err + } + defer file.Close() + + if err := json.NewDecoder(file).Decode(&configFile); err != nil { + return &configFile, err + } + + for addr, ac := range configFile.AuthConfigs { + ac.Username, ac.Password, err = DecodeAuth(ac.Auth) + if err != nil { + return &configFile, err + } + ac.Auth = "" + ac.ServerAddress = addr + configFile.AuthConfigs[addr] = ac + } + + return &configFile, nil + } else if !os.IsNotExist(err) { + // if file is there but we can't stat it for any reason other + // than it doesn't exist then stop + return &configFile, err + } + + // Can't find latest config file so check for the old one + confFile := filepath.Join(homedir.Get(), OLD_CONFIGFILE) + + if _, err := os.Stat(confFile); err != nil { + return &configFile, nil //missing file is not an error + } + + b, err := ioutil.ReadFile(confFile) + if err != nil { + return &configFile, err + } + + if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil { + arr := strings.Split(string(b), "\n") + if len(arr) < 2 { + return &configFile, fmt.Errorf("The Auth config file is empty") + } + authConfig := AuthConfig{} + origAuth := strings.Split(arr[0], " = ") + if len(origAuth) != 2 { + return &configFile, fmt.Errorf("Invalid Auth config file") + } + authConfig.Username, authConfig.Password, err = DecodeAuth(origAuth[1]) + if err != nil { + return &configFile, err + } + origEmail := strings.Split(arr[1], " = ") + if len(origEmail) != 2 { + return &configFile, fmt.Errorf("Invalid Auth config file") + } + authConfig.Email = origEmail[1] + authConfig.ServerAddress = DEFAULT_INDEXSERVER + configFile.AuthConfigs[DEFAULT_INDEXSERVER] = authConfig + } else { + for k, authConfig := range configFile.AuthConfigs { + authConfig.Username, authConfig.Password, err = DecodeAuth(authConfig.Auth) + if err != nil { + return &configFile, err + } + authConfig.Auth = "" + authConfig.ServerAddress = k + configFile.AuthConfigs[k] = authConfig + } + } + return &configFile, nil +} + +func (configFile *ConfigFile) Save() error { + // Encode sensitive data into a new/temp struct + tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs)) + for k, authConfig := range configFile.AuthConfigs { + authCopy := authConfig + + authCopy.Auth = EncodeAuth(&authCopy) + authCopy.Username = "" + authCopy.Password = "" + authCopy.ServerAddress = "" + tmpAuthConfigs[k] = authCopy + } + + saveAuthConfigs := configFile.AuthConfigs + configFile.AuthConfigs = tmpAuthConfigs + defer func() { configFile.AuthConfigs = saveAuthConfigs }() + + data, err := json.MarshalIndent(configFile, "", "\t") + if err != nil { + return err + } + + if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil { + return err + } + + err = ioutil.WriteFile(configFile.filename, data, 0600) + if err != nil { + return err + } + + return nil +} + +func (config *ConfigFile) Filename() string { + return config.filename +} + +// create a base64 encoded auth string to store in config +func EncodeAuth(authConfig *AuthConfig) string { + authStr := authConfig.Username + ":" + authConfig.Password + msg := []byte(authStr) + encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) + base64.StdEncoding.Encode(encoded, msg) + return string(encoded) +} + +// decode the auth string +func DecodeAuth(authStr string) (string, string, error) { + decLen := base64.StdEncoding.DecodedLen(len(authStr)) + decoded := make([]byte, decLen) + authByte := []byte(authStr) + n, err := base64.StdEncoding.Decode(decoded, authByte) + if err != nil { + return "", "", err + } + if n > decLen { + return "", "", fmt.Errorf("Something went wrong decoding auth config") + } + arr := strings.SplitN(string(decoded), ":", 2) + if len(arr) != 2 { + return "", "", fmt.Errorf("Invalid auth configuration file") + } + password := strings.Trim(arr[1], "\x00") + return arr[0], password, nil +} diff --git a/registry/config_file_test.go b/cliconfig/config_file_test.go similarity index 94% rename from registry/config_file_test.go rename to cliconfig/config_file_test.go index 6f8bd74f5..6d1125f7b 100644 --- a/registry/config_file_test.go +++ b/cliconfig/config_file_test.go @@ -1,4 +1,4 @@ -package registry +package cliconfig import ( "io/ioutil" @@ -14,7 +14,7 @@ import ( func TestMissingFile(t *testing.T) { tmpHome, _ := ioutil.TempDir("", "config-test") - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on missing file: %q", err) } @@ -36,7 +36,7 @@ func TestSaveFileToDirs(t *testing.T) { tmpHome += "/.docker" - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on missing file: %q", err) } @@ -58,7 +58,7 @@ func TestEmptyFile(t *testing.T) { fn := filepath.Join(tmpHome, CONFIGFILE) ioutil.WriteFile(fn, []byte(""), 0600) - _, err := LoadConfig(tmpHome) + _, err := Load(tmpHome) if err == nil { t.Fatalf("Was supposed to fail") } @@ -69,7 +69,7 @@ func TestEmptyJson(t *testing.T) { fn := filepath.Join(tmpHome, CONFIGFILE) ioutil.WriteFile(fn, []byte("{}"), 0600) - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on empty json file: %q", err) } @@ -104,7 +104,7 @@ func TestOldJson(t *testing.T) { js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}` ioutil.WriteFile(fn, []byte(js), 0600) - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on empty json file: %q", err) } @@ -133,7 +133,7 @@ func TestNewJson(t *testing.T) { js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }` ioutil.WriteFile(fn, []byte(js), 0600) - config, err := LoadConfig(tmpHome) + config, err := Load(tmpHome) if err != nil { t.Fatalf("Failed loading on empty json file: %q", err) } diff --git a/graph/pull.go b/graph/pull.go index b62591ffb..5bfa37316 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -12,6 +12,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" @@ -23,7 +24,7 @@ import ( type ImagePullConfig struct { Parallel bool MetaHeaders map[string][]string - AuthConfig *registry.AuthConfig + AuthConfig *cliconfig.AuthConfig Json bool OutStream io.Writer } diff --git a/graph/push.go b/graph/push.go index 34db27b91..62ff94e0c 100644 --- a/graph/push.go +++ b/graph/push.go @@ -12,6 +12,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/distribution/digest" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/image" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" @@ -26,7 +27,7 @@ var ErrV2RegistryUnavailable = errors.New("error v2 registry unavailable") type ImagePushConfig struct { MetaHeaders map[string][]string - AuthConfig *registry.AuthConfig + AuthConfig *cliconfig.AuthConfig Tag string Json bool OutStream io.Writer diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 0c412c862..587712ffc 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -18,6 +18,7 @@ import ( "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/engine" @@ -28,7 +29,6 @@ import ( "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" "github.com/docker/docker/utils" ) @@ -135,7 +135,7 @@ func setupBaseImage() { imagePullConfig := &graph.ImagePullConfig{ Parallel: true, OutStream: ioutils.NopWriteCloser(os.Stdout), - AuthConfig: ®istry.AuthConfig{}, + AuthConfig: &cliconfig.AuthConfig{}, } d := getDaemon(eng) if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig); err != nil { diff --git a/registry/auth.go b/registry/auth.go index ef4985abc..1ac1ca984 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -1,51 +1,21 @@ package registry import ( - "encoding/base64" "encoding/json" - "errors" "fmt" "io/ioutil" "net/http" - "os" - "path/filepath" "strings" "sync" "time" "github.com/Sirupsen/logrus" - "github.com/docker/docker/pkg/homedir" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/requestdecorator" ) -const ( - // Where we store the config file - CONFIGFILE = "config.json" - OLD_CONFIGFILE = ".dockercfg" -) - -var ( - ErrConfigFileMissing = errors.New("The Auth config file is missing") -) - -// Registry Auth Info -type AuthConfig struct { - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - Auth string `json:"auth"` - Email string `json:"email"` - ServerAddress string `json:"serveraddress,omitempty"` -} - -// ~/.docker/config.json file info -type ConfigFile struct { - AuthConfigs map[string]AuthConfig `json:"auths"` - HttpHeaders map[string]string `json:"HttpHeaders,omitempty"` - filename string // Note: not serialized - for internal use only -} - type RequestAuthorization struct { - authConfig *AuthConfig + authConfig *cliconfig.AuthConfig registryEndpoint *Endpoint resource string scope string @@ -56,7 +26,7 @@ type RequestAuthorization struct { tokenExpiration time.Time } -func NewRequestAuthorization(authConfig *AuthConfig, registryEndpoint *Endpoint, resource, scope string, actions []string) *RequestAuthorization { +func NewRequestAuthorization(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, resource, scope string, actions []string) *RequestAuthorization { return &RequestAuthorization{ authConfig: authConfig, registryEndpoint: registryEndpoint, @@ -121,160 +91,8 @@ func (auth *RequestAuthorization) Authorize(req *http.Request) error { return nil } -// create a base64 encoded auth string to store in config -func encodeAuth(authConfig *AuthConfig) string { - authStr := authConfig.Username + ":" + authConfig.Password - msg := []byte(authStr) - encoded := make([]byte, base64.StdEncoding.EncodedLen(len(msg))) - base64.StdEncoding.Encode(encoded, msg) - return string(encoded) -} - -// decode the auth string -func decodeAuth(authStr string) (string, string, error) { - decLen := base64.StdEncoding.DecodedLen(len(authStr)) - decoded := make([]byte, decLen) - authByte := []byte(authStr) - n, err := base64.StdEncoding.Decode(decoded, authByte) - if err != nil { - return "", "", err - } - if n > decLen { - return "", "", fmt.Errorf("Something went wrong decoding auth config") - } - arr := strings.SplitN(string(decoded), ":", 2) - if len(arr) != 2 { - return "", "", fmt.Errorf("Invalid auth configuration file") - } - password := strings.Trim(arr[1], "\x00") - return arr[0], password, nil -} - -// load up the auth config information and return values -// FIXME: use the internal golang config parser -func LoadConfig(configDir string) (*ConfigFile, error) { - if configDir == "" { - configDir = filepath.Join(homedir.Get(), ".docker") - } - - configFile := ConfigFile{ - AuthConfigs: make(map[string]AuthConfig), - filename: filepath.Join(configDir, CONFIGFILE), - } - - // Try happy path first - latest config file - if _, err := os.Stat(configFile.filename); err == nil { - file, err := os.Open(configFile.filename) - if err != nil { - return &configFile, err - } - defer file.Close() - - if err := json.NewDecoder(file).Decode(&configFile); err != nil { - return &configFile, err - } - - for addr, ac := range configFile.AuthConfigs { - ac.Username, ac.Password, err = decodeAuth(ac.Auth) - if err != nil { - return &configFile, err - } - ac.Auth = "" - ac.ServerAddress = addr - configFile.AuthConfigs[addr] = ac - } - - return &configFile, nil - } else if !os.IsNotExist(err) { - // if file is there but we can't stat it for any reason other - // than it doesn't exist then stop - return &configFile, err - } - - // Can't find latest config file so check for the old one - confFile := filepath.Join(homedir.Get(), OLD_CONFIGFILE) - - if _, err := os.Stat(confFile); err != nil { - return &configFile, nil //missing file is not an error - } - - b, err := ioutil.ReadFile(confFile) - if err != nil { - return &configFile, err - } - - if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil { - arr := strings.Split(string(b), "\n") - if len(arr) < 2 { - return &configFile, fmt.Errorf("The Auth config file is empty") - } - authConfig := AuthConfig{} - origAuth := strings.Split(arr[0], " = ") - if len(origAuth) != 2 { - return &configFile, fmt.Errorf("Invalid Auth config file") - } - authConfig.Username, authConfig.Password, err = decodeAuth(origAuth[1]) - if err != nil { - return &configFile, err - } - origEmail := strings.Split(arr[1], " = ") - if len(origEmail) != 2 { - return &configFile, fmt.Errorf("Invalid Auth config file") - } - authConfig.Email = origEmail[1] - authConfig.ServerAddress = IndexServerAddress() - // *TODO: Switch to using IndexServerName() instead? - configFile.AuthConfigs[IndexServerAddress()] = authConfig - } else { - for k, authConfig := range configFile.AuthConfigs { - authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) - if err != nil { - return &configFile, err - } - authConfig.Auth = "" - authConfig.ServerAddress = k - configFile.AuthConfigs[k] = authConfig - } - } - return &configFile, nil -} - -func (configFile *ConfigFile) Save() error { - // Encode sensitive data into a new/temp struct - tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs)) - for k, authConfig := range configFile.AuthConfigs { - authCopy := authConfig - - authCopy.Auth = encodeAuth(&authCopy) - authCopy.Username = "" - authCopy.Password = "" - authCopy.ServerAddress = "" - tmpAuthConfigs[k] = authCopy - } - - saveAuthConfigs := configFile.AuthConfigs - configFile.AuthConfigs = tmpAuthConfigs - defer func() { configFile.AuthConfigs = saveAuthConfigs }() - - data, err := json.MarshalIndent(configFile, "", "\t") - if err != nil { - return err - } - - if err := os.MkdirAll(filepath.Dir(configFile.filename), 0700); err != nil { - return err - } - - err = ioutil.WriteFile(configFile.filename, data, 0600) - if err != nil { - return err - } - - return nil -} - // Login tries to register/login to the registry server. -func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { +func Login(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { // Separates the v2 registry login logic from the v1 logic. if registryEndpoint.Version == APIVersion2 { return loginV2(authConfig, registryEndpoint, factory) @@ -283,7 +101,7 @@ func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestd } // loginV1 tries to register/login to the v1 registry server. -func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { +func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { var ( status string reqBody []byte @@ -396,7 +214,7 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *reques // now, users should create their account through other means like directly from a web page // served by the v2 registry service provider. Whether this will be supported in the future // is to be determined. -func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { +func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint, factory *requestdecorator.RequestFactory) (string, error) { logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint) var ( err error @@ -429,7 +247,7 @@ func loginV2(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *reques return "", fmt.Errorf("no successful auth challenge for %s - errors: %s", registryEndpoint, allErrors) } -func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { +func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil) if err != nil { return err @@ -450,7 +268,7 @@ func tryV2BasicAuthLogin(authConfig *AuthConfig, params map[string]string, regis return nil } -func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { +func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint, client *http.Client, factory *requestdecorator.RequestFactory) error { token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint, client, factory) if err != nil { return err @@ -477,7 +295,7 @@ func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, regis } // this method matches a auth configuration to a server address or a url -func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { +func ResolveAuthConfig(config *cliconfig.ConfigFile, index *IndexInfo) cliconfig.AuthConfig { configKey := index.GetAuthConfigKey() // First try the happy case if c, found := config.AuthConfigs[configKey]; found || index.Official { @@ -499,16 +317,12 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { // Maybe they have a legacy config file, we will iterate the keys converting // them to the new format and testing - for registry, config := range config.AuthConfigs { + for registry, ac := range config.AuthConfigs { if configKey == convertToHostname(registry) { - return config + return ac } } // When all else fails, return an empty auth config - return AuthConfig{} -} - -func (config *ConfigFile) Filename() string { - return config.filename + return cliconfig.AuthConfig{} } diff --git a/registry/auth_test.go b/registry/auth_test.go index b07aa7dbc..71b963a1f 100644 --- a/registry/auth_test.go +++ b/registry/auth_test.go @@ -5,14 +5,16 @@ import ( "os" "path/filepath" "testing" + + "github.com/docker/docker/cliconfig" ) func TestEncodeAuth(t *testing.T) { - newAuthConfig := &AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} - authStr := encodeAuth(newAuthConfig) - decAuthConfig := &AuthConfig{} + newAuthConfig := &cliconfig.AuthConfig{Username: "ken", Password: "test", Email: "test@example.com"} + authStr := cliconfig.EncodeAuth(newAuthConfig) + decAuthConfig := &cliconfig.AuthConfig{} var err error - decAuthConfig.Username, decAuthConfig.Password, err = decodeAuth(authStr) + decAuthConfig.Username, decAuthConfig.Password, err = cliconfig.DecodeAuth(authStr) if err != nil { t.Fatal(err) } @@ -27,19 +29,16 @@ func TestEncodeAuth(t *testing.T) { } } -func setupTempConfigFile() (*ConfigFile, error) { +func setupTempConfigFile() (*cliconfig.ConfigFile, error) { root, err := ioutil.TempDir("", "docker-test-auth") if err != nil { return nil, err } - root = filepath.Join(root, CONFIGFILE) - configFile := &ConfigFile{ - AuthConfigs: make(map[string]AuthConfig), - filename: root, - } + root = filepath.Join(root, cliconfig.CONFIGFILE) + configFile := cliconfig.NewConfigFile(root) for _, registry := range []string{"testIndex", IndexServerAddress()} { - configFile.AuthConfigs[registry] = AuthConfig{ + configFile.AuthConfigs[registry] = cliconfig.AuthConfig{ Username: "docker-user", Password: "docker-pass", Email: "docker@docker.io", @@ -54,7 +53,7 @@ func TestSameAuthDataPostSave(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.filename) + defer os.RemoveAll(configFile.Filename()) err = configFile.Save() if err != nil { @@ -81,7 +80,7 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.filename) + defer os.RemoveAll(configFile.Filename()) indexConfig := configFile.AuthConfigs[IndexServerAddress()] @@ -92,10 +91,10 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { Official: false, } - resolved := configFile.ResolveAuthConfig(officialIndex) + resolved := ResolveAuthConfig(configFile, officialIndex) assertEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to return IndexServerAddress()") - resolved = configFile.ResolveAuthConfig(privateIndex) + resolved = ResolveAuthConfig(configFile, privateIndex) assertNotEqual(t, resolved, indexConfig, "Expected ResolveAuthConfig to not return IndexServerAddress()") } @@ -104,26 +103,26 @@ func TestResolveAuthConfigFullURL(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.filename) + defer os.RemoveAll(configFile.Filename()) - registryAuth := AuthConfig{ + registryAuth := cliconfig.AuthConfig{ Username: "foo-user", Password: "foo-pass", Email: "foo@example.com", } - localAuth := AuthConfig{ + localAuth := cliconfig.AuthConfig{ Username: "bar-user", Password: "bar-pass", Email: "bar@example.com", } - officialAuth := AuthConfig{ + officialAuth := cliconfig.AuthConfig{ Username: "baz-user", Password: "baz-pass", Email: "baz@example.com", } configFile.AuthConfigs[IndexServerAddress()] = officialAuth - expectedAuths := map[string]AuthConfig{ + expectedAuths := map[string]cliconfig.AuthConfig{ "registry.example.com": registryAuth, "localhost:8000": localAuth, "registry.com": localAuth, @@ -160,12 +159,12 @@ func TestResolveAuthConfigFullURL(t *testing.T) { } for _, registry := range registries { configFile.AuthConfigs[registry] = configured - resolved := configFile.ResolveAuthConfig(index) + resolved := ResolveAuthConfig(configFile, index) if resolved.Email != configured.Email { t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email) } delete(configFile.AuthConfigs, registry) - resolved = configFile.ResolveAuthConfig(index) + resolved = ResolveAuthConfig(configFile, index) if resolved.Email == configured.Email { t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email) } diff --git a/registry/registry_test.go b/registry/registry_test.go index a066de9f8..b4bd4ee72 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -7,6 +7,7 @@ import ( "strings" "testing" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/requestdecorator" ) @@ -20,7 +21,7 @@ const ( ) func spawnTestRegistrySession(t *testing.T) *Session { - authConfig := &AuthConfig{} + authConfig := &cliconfig.AuthConfig{} endpoint, err := NewEndpoint(makeIndex("/v1/")) if err != nil { t.Fatal(err) @@ -33,7 +34,7 @@ func spawnTestRegistrySession(t *testing.T) *Session { } func TestPublicSession(t *testing.T) { - authConfig := &AuthConfig{} + authConfig := &cliconfig.AuthConfig{} getSessionDecorators := func(index *IndexInfo) int { endpoint, err := NewEndpoint(index) diff --git a/registry/service.go b/registry/service.go index cf29732f4..87fc1d076 100644 --- a/registry/service.go +++ b/registry/service.go @@ -1,5 +1,7 @@ package registry +import "github.com/docker/docker/cliconfig" + type Service struct { Config *ServiceConfig } @@ -15,7 +17,7 @@ func NewService(options *Options) *Service { // Auth contacts the public registry with the provided credentials, // and returns OK if authentication was sucessful. // It can be used to verify the validity of a client's credentials. -func (s *Service) Auth(authConfig *AuthConfig) (string, error) { +func (s *Service) Auth(authConfig *cliconfig.AuthConfig) (string, error) { addr := authConfig.ServerAddress if addr == "" { // Use the official registry address if not specified. @@ -35,7 +37,7 @@ func (s *Service) Auth(authConfig *AuthConfig) (string, error) { // Search queries the public registry for images matching the specified // search terms, and returns the results. -func (s *Service) Search(term string, authConfig *AuthConfig, headers map[string][]string) (*SearchResults, error) { +func (s *Service) Search(term string, authConfig *cliconfig.AuthConfig, headers map[string][]string) (*SearchResults, error) { repoInfo, err := s.ResolveRepository(term) if err != nil { return nil, err diff --git a/registry/session.go b/registry/session.go index 940e407e9..dd868a2b3 100644 --- a/registry/session.go +++ b/registry/session.go @@ -18,20 +18,21 @@ import ( "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/requestdecorator" "github.com/docker/docker/pkg/tarsum" ) type Session struct { - authConfig *AuthConfig + authConfig *cliconfig.AuthConfig reqFactory *requestdecorator.RequestFactory indexEndpoint *Endpoint jar *cookiejar.Jar timeout TimeoutType } -func NewSession(authConfig *AuthConfig, factory *requestdecorator.RequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) { +func NewSession(authConfig *cliconfig.AuthConfig, factory *requestdecorator.RequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) { r = &Session{ authConfig: authConfig, indexEndpoint: endpoint, @@ -600,12 +601,12 @@ func (r *Session) SearchRepositories(term string) (*SearchResults, error) { return result, err } -func (r *Session) GetAuthConfig(withPasswd bool) *AuthConfig { +func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig { password := "" if withPasswd { password = r.authConfig.Password } - return &AuthConfig{ + return &cliconfig.AuthConfig{ Username: r.authConfig.Username, Password: password, Email: r.authConfig.Email, From ec51ba01dbbeadb06cc7f3c14e94f8a2ee938a34 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Thu, 23 Apr 2015 10:27:34 -0700 Subject: [PATCH 602/999] Return weird behaviour of returning json errors We need this, so client can get error from stream and not from status code, which is already 200, because write to ResponseWriter was occured. Signed-off-by: Alexander Morozov --- api/server/server.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 646a8c677..3a524aae4 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -861,11 +861,12 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h useJSON := version.GreaterThan("1.0") name := vars["name"] + output := utils.NewWriteFlusher(w) imagePushConfig := &graph.ImagePushConfig{ MetaHeaders: metaHeaders, AuthConfig: authConfig, Tag: r.Form.Get("tag"), - OutStream: utils.NewWriteFlusher(w), + OutStream: output, Json: useJSON, } if useJSON { @@ -873,8 +874,11 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h } if err := s.daemon.Repositories().Push(name, imagePushConfig); err != nil { + if !output.Flushed() { + return err + } sf := streamformatter.NewStreamFormatter(useJSON) - return fmt.Errorf(string(sf.FormatError(err))) + output.Write(sf.FormatError(err)) } return nil From ecccfa82aa22829c52778c4457cacd8d766e3dda Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 22 Apr 2015 11:20:32 -0700 Subject: [PATCH 603/999] Validate we're not using the old testing stuff Signed-off-by: Doug Davis --- Makefile | 2 +- hack/make.sh | 1 + hack/make/validate-test | 35 +++++++++++++++++++++++++++++++++++ integration-cli/check_test.go | 4 +++- 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 hack/make/validate-test diff --git a/Makefile b/Makefile index 7978b632c..b60b2a4d0 100644 --- a/Makefile +++ b/Makefile @@ -77,7 +77,7 @@ test-docker-py: build $(DOCKER_RUN_DOCKER) hack/make.sh binary test-docker-py validate: build - $(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-toml validate-vet + $(DOCKER_RUN_DOCKER) hack/make.sh validate-dco validate-gofmt validate-test validate-toml validate-vet shell: build $(DOCKER_RUN_DOCKER) bash diff --git a/hack/make.sh b/hack/make.sh index eeb26cbce..31e08cd37 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -46,6 +46,7 @@ echo DEFAULT_BUNDLES=( validate-dco validate-gofmt + validate-test validate-toml validate-vet diff --git a/hack/make/validate-test b/hack/make/validate-test new file mode 100644 index 000000000..d9d05f3be --- /dev/null +++ b/hack/make/validate-test @@ -0,0 +1,35 @@ +#!/bin/bash + +# Make sure we're not using gos' Testing package any more in integration-cli + +source "${MAKEDIR}/.validate" + +IFS=$'\n' +files=( $(validate_diff --diff-filter=ACMR --name-only -- 'integration-cli/*.go' || true) ) +unset IFS + +badFiles=() +for f in "${files[@]}"; do + # skip check_test.go since it *does* use the testing package + if [ "$f" = "integration-cli/check_test.go" ]; then + continue + fi + + # we use "git show" here to validate that what's committed is formatted + if git show "$VALIDATE_HEAD:$f" | grep -q testing.T; then + badFiles+=( "$f" ) + fi +done + +if [ ${#badFiles[@]} -eq 0 ]; then + echo 'Congratulations! No testing.T found.' +else + { + echo "These files use the wrong testing infrastructure:" + for f in "${badFiles[@]}"; do + echo " - $f" + done + echo + } >&2 + false +fi diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go index 330bc373b..07bb93159 100644 --- a/integration-cli/check_test.go +++ b/integration-cli/check_test.go @@ -8,7 +8,9 @@ import ( "github.com/go-check/check" ) -func Test(t *testing.T) { check.TestingT(t) } +func Test(t *testing.T) { + check.TestingT(t) +} type TimerSuite struct { start time.Time From 929af4c38d8ca4754d2a3ccf087d359bb67c33f3 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 17 Apr 2015 16:19:34 -0600 Subject: [PATCH 604/999] Fix daemon start/stop logic in hack/make/* scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the Bash manual's `set -e` description: (https://www.gnu.org/software/bash/manual/bashref.html#index-set) > Exit immediately if a pipeline (see Pipelines), which may consist of a > single simple command (see Simple Commands), a list (see Lists), or a > compound command (see Compound Commands) returns a non-zero status. > The shell does not exit if the command that fails is part of the > command list immediately following a while or until keyword, part of > the test in an if statement, part of any command executed in a && or > || list except the command following the final && or ||, any command > in a pipeline but the last, or if the command’s return status is being > inverted with !. If a compound command other than a subshell returns a > non-zero status because a command failed while -e was being ignored, > the shell does not exit. Additionally, further down: > If a compound command or shell function executes in a context where -e > is being ignored, none of the commands executed within the compound > command or function body will be affected by the -e setting, even if > -e is set and a command returns a failure status. If a compound > command or shell function sets -e while executing in a context where > -e is ignored, that setting will not have any effect until the > compound command or the command containing the function call > completes. Thus, the only way to have our `.integration-daemon-stop` script actually run appropriately to clean up our daemon on test/script failure is to use `trap ... EXIT`, which we traditionally avoid because it does not have any stacking capabilities, but in this case is a reasonable compromise because it's going to be the only script using it (for now, at least; we can evaluate more complex solutions in the future if they actually become necessary). The alternatives were much less reasonable. One is to have the entire complex chains in any script wanting to use `.integration-daemon-start` / `.integration-daemon-stop` be chained together with `&&` in an `if` block, which is untenable. The other I could think of was taking the body of these scripts out into separate scripts, essentially meaning we'd need two files for each of these, which further complicates the maintenance. Add to that the fact that our `trap ... EXIT` is scoped to the enclosing subshell (`( ... )`) and we're in even more reasonable territory with this pattern. Signed-off-by: Andrew "Tianon" Page --- hack/make/.integration-daemon-start | 1 + hack/make/.integration-daemon-stop | 2 + hack/make/build-deb | 112 +++++++++++++--------------- hack/make/test-docker-py | 26 ++----- hack/make/test-integration-cli | 18 +---- 5 files changed, 65 insertions(+), 94 deletions(-) diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start index 570c6c7a9..57fd52502 100644 --- a/hack/make/.integration-daemon-start +++ b/hack/make/.integration-daemon-start @@ -25,6 +25,7 @@ if [ -z "$DOCKER_TEST_HOST" ]; then --pidfile "$DEST/docker.pid" \ &> "$DEST/docker.log" ) & + trap "source '${MAKEDIR}/.integration-daemon-stop'" EXIT # make sure that if the script exits unexpectedly, we stop this daemon we just started else export DOCKER_HOST="$DOCKER_TEST_HOST" fi diff --git a/hack/make/.integration-daemon-stop b/hack/make/.integration-daemon-stop index 7e4dc2353..6e1dc844d 100644 --- a/hack/make/.integration-daemon-stop +++ b/hack/make/.integration-daemon-stop @@ -1,5 +1,7 @@ #!/bin/bash +trap - EXIT # reset EXIT trap applied in .integration-daemon-start + for pidFile in $(find "$DEST" -name docker.pid); do pid=$(set -x; cat "$pidFile") ( set -x; kill "$pid" ) diff --git a/hack/make/build-deb b/hack/make/build-deb index 90c4c1693..a5a6d4387 100644 --- a/hack/make/build-deb +++ b/hack/make/build-deb @@ -7,76 +7,64 @@ DEST=$1 ( source "${MAKEDIR}/.integration-daemon-start" - # we need to wrap up everything in between integration-daemon-start and - # integration-daemon-stop to make sure we kill the daemon and don't hang, - # even and especially on test failures - didFail= - if ! { - set -e + # TODO consider using frozen images for the dockercore/builder-deb tags - # TODO consider using frozen images for the dockercore/builder-deb tags + debVersion="${VERSION//-/'~'}" + # if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better + if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then + gitUnix="$(git log -1 --pretty='%at')" + gitDate="$(date --date "@$gitUnix" +'%Y%m%d.%H%M%S')" + gitCommit="$(git log -1 --pretty='%h')" + gitVersion="git${gitDate}.0.${gitCommit}" + # gitVersion is now something like 'git20150128.112847.0.17e840a' + debVersion="$debVersion~$gitVersion" - debVersion="${VERSION//-/'~'}" - # if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better - if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then - gitUnix="$(git log -1 --pretty='%at')" - gitDate="$(date --date "@$gitUnix" +'%Y%m%d.%H%M%S')" - gitCommit="$(git log -1 --pretty='%h')" - gitVersion="git${gitDate}.0.${gitCommit}" - # gitVersion is now something like 'git20150128.112847.0.17e840a' - debVersion="$debVersion~$gitVersion" + # $ dpkg --compare-versions 1.5.0 gt 1.5.0~rc1 && echo true || echo false + # true + # $ dpkg --compare-versions 1.5.0~rc1 gt 1.5.0~git20150128.112847.17e840a && echo true || echo false + # true + # $ dpkg --compare-versions 1.5.0~git20150128.112847.17e840a gt 1.5.0~dev~git20150128.112847.17e840a && echo true || echo false + # true - # $ dpkg --compare-versions 1.5.0 gt 1.5.0~rc1 && echo true || echo false - # true - # $ dpkg --compare-versions 1.5.0~rc1 gt 1.5.0~git20150128.112847.17e840a && echo true || echo false - # true - # $ dpkg --compare-versions 1.5.0~git20150128.112847.17e840a gt 1.5.0~dev~git20150128.112847.17e840a && echo true || echo false - # true + # ie, 1.5.0 > 1.5.0~rc1 > 1.5.0~git20150128.112847.17e840a > 1.5.0~dev~git20150128.112847.17e840a + fi - # ie, 1.5.0 > 1.5.0~rc1 > 1.5.0~git20150128.112847.17e840a > 1.5.0~dev~git20150128.112847.17e840a + debSource="$(awk -F ': ' '$1 == "Source" { print $2; exit }' hack/make/.build-deb/control)" + debMaintainer="$(awk -F ': ' '$1 == "Maintainer" { print $2; exit }' hack/make/.build-deb/control)" + debDate="$(date --rfc-2822)" + + # if go-md2man is available, pre-generate the man pages + ./docs/man/md2man-all.sh -q || true + # TODO decide if it's worth getting go-md2man in _each_ builder environment to avoid this + + # TODO add a configurable knob for _which_ debs to build so we don't have to modify the file or build all of them every time we need to test + for dir in contrib/builder/deb/*/; do + version="$(basename "$dir")" + suite="${version##*-}" + + image="dockercore/builder-deb:$version" + if ! docker inspect "$image" &> /dev/null; then + ( set -x && docker build -t "$image" "$dir" ) fi - debSource="$(awk -F ': ' '$1 == "Source" { print $2; exit }' hack/make/.build-deb/control)" - debMaintainer="$(awk -F ': ' '$1 == "Maintainer" { print $2; exit }' hack/make/.build-deb/control)" - debDate="$(date --rfc-2822)" - - # if go-md2man is available, pre-generate the man pages - ./docs/man/md2man-all.sh -q || true - # TODO decide if it's worth getting go-md2man in _each_ builder environment to avoid this - - # TODO add a configurable knob for _which_ debs to build so we don't have to modify the file or build all of them every time we need to test - for dir in contrib/builder/deb/*/; do - version="$(basename "$dir")" - suite="${version##*-}" - - image="dockercore/builder-deb:$version" - if ! docker inspect "$image" &> /dev/null; then - ( set -x && docker build -t "$image" "$dir" ) - fi - - mkdir -p "$DEST/$version" - cat > "$DEST/$version/Dockerfile.build" <<-EOF - FROM $image - WORKDIR /usr/src/docker - COPY . /usr/src/docker - RUN ln -sfv hack/make/.build-deb debian - RUN { echo '$debSource (${debVersion}-0~${suite}) $suite; urgency=low'; echo; echo ' * Version: $VERSION'; echo; echo " -- $debMaintainer $debDate"; } > debian/changelog && cat >&2 debian/changelog - RUN dpkg-buildpackage -uc -us - EOF - cp -a "$DEST/$version/Dockerfile.build" . # can't use $DEST because it's in .dockerignore... - tempImage="docker-temp/build-deb:$version" - ( set -x && docker build -t "$tempImage" -f Dockerfile.build . ) - docker run --rm "$tempImage" bash -c 'cd .. && tar -c *_*' | tar -xvC "$DEST/$version" - docker rmi "$tempImage" - done - }; then - didFail=1 - fi + mkdir -p "$DEST/$version" + cat > "$DEST/$version/Dockerfile.build" <<-EOF + FROM $image + WORKDIR /usr/src/docker + COPY . /usr/src/docker + RUN ln -sfv hack/make/.build-deb debian + RUN { echo '$debSource (${debVersion}-0~${suite}) $suite; urgency=low'; echo; echo ' * Version: $VERSION'; echo; echo " -- $debMaintainer $debDate"; } > debian/changelog && cat >&2 debian/changelog + RUN dpkg-buildpackage -uc -us + EOF + cp -a "$DEST/$version/Dockerfile.build" . # can't use $DEST because it's in .dockerignore... + tempImage="docker-temp/build-deb:$version" + ( set -x && docker build -t "$tempImage" -f Dockerfile.build . ) + docker run --rm "$tempImage" bash -c 'cd .. && tar -c *_*' | tar -xvC "$DEST/$version" + docker rmi "$tempImage" + done # clean up after ourselves rm -f Dockerfile.build source "${MAKEDIR}/.integration-daemon-stop" - - [ -z "$didFail" ] # "set -e" ftw -) 2>&1 | tee -a $DEST/test.log +) 2>&1 | tee -a "$DEST/test.log" diff --git a/hack/make/test-docker-py b/hack/make/test-docker-py index 409cee0e4..ac5ef3583 100644 --- a/hack/make/test-docker-py +++ b/hack/make/test-docker-py @@ -7,24 +7,14 @@ DEST=$1 ( source "${MAKEDIR}/.integration-daemon-start" - # we need to wrap up everything in between integration-daemon-start and - # integration-daemon-stop to make sure we kill the daemon and don't hang, - # even and especially on test failures - didFail= - if ! { - dockerPy='/docker-py' - [ -d "$dockerPy" ] || { - dockerPy="$DEST/docker-py" - git clone https://github.com/docker/docker-py.git "$dockerPy" - } + dockerPy='/docker-py' + [ -d "$dockerPy" ] || { + dockerPy="$DEST/docker-py" + git clone https://github.com/docker/docker-py.git "$dockerPy" + } - # exporting PYTHONPATH to import "docker" from our local docker-py - test_env PYTHONPATH="$dockerPy" python "$dockerPy/tests/integration_test.py" - }; then - didFail=1 - fi + # exporting PYTHONPATH to import "docker" from our local docker-py + test_env PYTHONPATH="$dockerPy" python "$dockerPy/tests/integration_test.py" source "${MAKEDIR}/.integration-daemon-stop" - - [ -z "$didFail" ] # "set -e" ftw -) 2>&1 | tee -a $DEST/test.log +) 2>&1 | tee -a "$DEST/test.log" diff --git a/hack/make/test-integration-cli b/hack/make/test-integration-cli index 8e9b97570..db1cb298f 100644 --- a/hack/make/test-integration-cli +++ b/hack/make/test-integration-cli @@ -11,21 +11,11 @@ bundle_test_integration_cli() { ( source "${MAKEDIR}/.integration-daemon-start" - # we need to wrap up everything in between integration-daemon-start and - # integration-daemon-stop to make sure we kill the daemon and don't hang, - # even and especially on test failures - didFail= - if ! { - source "${MAKEDIR}/.ensure-frozen-images" - source "${MAKEDIR}/.ensure-httpserver" - source "${MAKEDIR}/.ensure-emptyfs" + source "${MAKEDIR}/.ensure-frozen-images" + source "${MAKEDIR}/.ensure-httpserver" + source "${MAKEDIR}/.ensure-emptyfs" - bundle_test_integration_cli - }; then - didFail=1 - fi + bundle_test_integration_cli source "${MAKEDIR}/.integration-daemon-stop" - - [ -z "$didFail" ] # "set -e" ftw ) 2>&1 | tee -a "$DEST/test.log" From 2a14b7dd35901167d83735a25ff626596391c4ed Mon Sep 17 00:00:00 2001 From: Simei He Date: Tue, 21 Apr 2015 21:10:30 +0800 Subject: [PATCH 605/999] remove job from image_export Signed-off-by: He Simei Signed-off-by: Alexander Morozov --- api/server/server.go | 31 +++++++++++++++++++++++-------- graph/export.go | 25 ++++++++++++++----------- graph/service.go | 1 - 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index 646a8c677..a17c9ee36 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -887,17 +887,32 @@ func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w htt if err := parseForm(r); err != nil { return err } - if version.GreaterThan("1.0") { + + useJSON := version.GreaterThan("1.0") + if useJSON { w.Header().Set("Content-Type", "application/x-tar") } - var job *engine.Job - if name, ok := vars["name"]; ok { - job = eng.Job("image_export", name) - } else { - job = eng.Job("image_export", r.Form["names"]...) + + output := utils.NewWriteFlusher(w) + imageExportConfig := &graph.ImageExportConfig{ + Engine: eng, + Outstream: output, } - job.Stdout.Add(w) - return job.Run() + if name, ok := vars["name"]; ok { + imageExportConfig.Names = []string{name} + } else { + imageExportConfig.Names = r.Form["names"] + } + + if err := s.daemon.Repositories().ImageExport(imageExportConfig); err != nil { + if !output.Flushed() { + return err + } + sf := streamformatter.NewStreamFormatter(useJSON) + output.Write(sf.FormatError(err)) + } + return nil + } func (s *Server) postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/graph/export.go b/graph/export.go index 56b5fba71..00cfa8975 100644 --- a/graph/export.go +++ b/graph/export.go @@ -2,7 +2,6 @@ package graph import ( "encoding/json" - "fmt" "io" "io/ioutil" "os" @@ -20,10 +19,14 @@ import ( // uncompressed tar ball. // name is the set of tags to export. // out is the writer where the images are written to. -func (s *TagStore) CmdImageExport(job *engine.Job) error { - if len(job.Args) < 1 { - return fmt.Errorf("Usage: %s IMAGE [IMAGE...]\n", job.Name) - } +type ImageExportConfig struct { + Names []string + Outstream io.Writer + Engine *engine.Engine +} + +func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { + // get image json tempdir, err := ioutil.TempDir("", "docker-export-") if err != nil { @@ -40,7 +43,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { repo[tag] = id } } - for _, name := range job.Args { + for _, name := range imageExportConfig.Names { name = registry.NormalizeLocalName(name) logrus.Debugf("Serializing %s", name) rootRepo := s.Repositories[name] @@ -48,7 +51,7 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { // this is a base repo name, like 'busybox' for tag, id := range rootRepo { addKey(name, tag, id) - if err := s.exportImage(job.Eng, id, tempdir); err != nil { + if err := s.exportImage(imageExportConfig.Engine, id, tempdir); err != nil { return err } } @@ -67,13 +70,13 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { if len(repoTag) > 0 { addKey(repoName, repoTag, img.ID) } - if err := s.exportImage(job.Eng, img.ID, tempdir); err != nil { + if err := s.exportImage(imageExportConfig.Engine, img.ID, tempdir); err != nil { return err } } else { // this must be an ID that didn't get looked up just right? - if err := s.exportImage(job.Eng, name, tempdir); err != nil { + if err := s.exportImage(imageExportConfig.Engine, name, tempdir); err != nil { return err } } @@ -96,10 +99,10 @@ func (s *TagStore) CmdImageExport(job *engine.Job) error { } defer fs.Close() - if _, err := io.Copy(job.Stdout, fs); err != nil { + if _, err := io.Copy(imageExportConfig.Outstream, fs); err != nil { return err } - logrus.Debugf("End export job: %s", job.Name) + logrus.Debugf("End export image") return nil } diff --git a/graph/service.go b/graph/service.go index 337eaa3cf..ab78c1d05 100644 --- a/graph/service.go +++ b/graph/service.go @@ -11,7 +11,6 @@ import ( func (s *TagStore) Install(eng *engine.Engine) error { for name, handler := range map[string]engine.Handler{ "image_inspect": s.CmdLookup, - "image_export": s.CmdImageExport, "viz": s.CmdViz, } { if err := eng.Register(name, handler); err != nil { From b6569b6b82df4c5e29ee8f5ebd9db7e36919cefd Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Thu, 23 Apr 2015 08:21:39 -0400 Subject: [PATCH 606/999] contrib/init: unshare mount namespace for inits * openrc * sysvinit-debian * upstart Signed-off-by: Vincent Batts --- contrib/init/openrc/docker.initd | 6 ++++-- contrib/init/sysvinit-debian/docker | 7 ++++--- contrib/init/upstart/docker.conf | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/contrib/init/openrc/docker.initd b/contrib/init/openrc/docker.initd index a9d21b170..f251e9af5 100755 --- a/contrib/init/openrc/docker.initd +++ b/contrib/init/openrc/docker.initd @@ -7,6 +7,7 @@ DOCKER_LOGFILE=${DOCKER_LOGFILE:-/var/log/${SVCNAME}.log} DOCKER_PIDFILE=${DOCKER_PIDFILE:-/run/${SVCNAME}.pid} DOCKER_BINARY=${DOCKER_BINARY:-/usr/bin/docker} DOCKER_OPTS=${DOCKER_OPTS:-} +UNSHARE_BINARY=${UNSHARE_BINARY:-/usr/bin/unshare} start() { checkpath -f -m 0644 -o root:docker "$DOCKER_LOGFILE" @@ -16,11 +17,12 @@ start() { ebegin "Starting docker daemon" start-stop-daemon --start --background \ - --exec "$DOCKER_BINARY" \ + --exec "$UNSHARE_BINARY" \ --pidfile "$DOCKER_PIDFILE" \ --stdout "$DOCKER_LOGFILE" \ --stderr "$DOCKER_LOGFILE" \ - -- -d -p "$DOCKER_PIDFILE" \ + -- --mount \ + -- "$DOCKER_BINARY" -d -p "$DOCKER_PIDFILE" \ $DOCKER_OPTS eend $? } diff --git a/contrib/init/sysvinit-debian/docker b/contrib/init/sysvinit-debian/docker index cf33c8377..35fd71f13 100755 --- a/contrib/init/sysvinit-debian/docker +++ b/contrib/init/sysvinit-debian/docker @@ -30,6 +30,7 @@ DOCKER_SSD_PIDFILE=/var/run/$BASE-ssd.pid DOCKER_LOGFILE=/var/log/$BASE.log DOCKER_OPTS= DOCKER_DESC="Docker" +UNSHARE=${UNSHARE:-/usr/bin/unshare} # Get lsb functions . /lib/lsb/init-functions @@ -99,11 +100,11 @@ case "$1" in log_begin_msg "Starting $DOCKER_DESC: $BASE" start-stop-daemon --start --background \ --no-close \ - --exec "$DOCKER" \ + --exec "$UNSHARE" \ --pidfile "$DOCKER_SSD_PIDFILE" \ --make-pidfile \ - -- \ - -d -p "$DOCKER_PIDFILE" \ + -- --mount \ + -- "$DOCKER" -d -p "$DOCKER_PIDFILE" \ $DOCKER_OPTS \ >> "$DOCKER_LOGFILE" 2>&1 log_end_msg $? diff --git a/contrib/init/upstart/docker.conf b/contrib/init/upstart/docker.conf index 4ad6058ed..5e8df6e3c 100644 --- a/contrib/init/upstart/docker.conf +++ b/contrib/init/upstart/docker.conf @@ -37,7 +37,7 @@ script if [ -f /etc/default/$UPSTART_JOB ]; then . /etc/default/$UPSTART_JOB fi - exec "$DOCKER" -d $DOCKER_OPTS + exec unshare -m -- "$DOCKER" -d $DOCKER_OPTS end script # Don't emit "started" event until docker.sock is ready. From 231d362db73310a5243a72dcdb6df2df57c74488 Mon Sep 17 00:00:00 2001 From: Srini Brahmaroutu Date: Thu, 26 Mar 2015 19:43:00 +0000 Subject: [PATCH 607/999] Allow go template to work properly with inspect Closes #11641 Signed-off-by: Srini Brahmaroutu --- api/client/inspect.go | 39 ++++++++----- api/types/types.go | 17 ++++++ integration-cli/docker_api_containers_test.go | 2 +- integration-cli/docker_cli_build_test.go | 29 +++++----- integration-cli/docker_cli_commit_test.go | 2 +- integration-cli/docker_cli_exec_test.go | 2 +- integration-cli/docker_cli_inspect_test.go | 57 +++++++++++++++++++ integration-cli/docker_cli_run_test.go | 4 +- 8 files changed, 120 insertions(+), 32 deletions(-) diff --git a/api/client/inspect.go b/api/client/inspect.go index 75861cdf2..db281795c 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -8,12 +8,14 @@ import ( "strings" "text/template" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" ) // CmdInspect displays low-level information on one or more containers or images. // // Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...] + func (cli *DockerCli) CmdInspect(args ...string) error { cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true) tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template") @@ -34,11 +36,13 @@ func (cli *DockerCli) CmdInspect(args ...string) error { indented := new(bytes.Buffer) indented.WriteByte('[') status := 0 + isImage := false for _, name := range cmd.Args() { obj, _, err := readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil)) if err != nil { obj, _, err = readBody(cli.call("GET", "/images/"+name+"/json", nil, nil)) + isImage = true if err != nil { if strings.Contains(err.Error(), "No such") { fmt.Fprintf(cli.err, "Error: No such image or container: %s\n", name) @@ -57,20 +61,29 @@ func (cli *DockerCli) CmdInspect(args ...string) error { continue } } else { - var value interface{} - - // Do not use `json.Unmarshal()` because unmarshal JSON into - // an interface value, Unmarshal stores JSON numbers in - // float64, which is different from `json.Indent()` does. dec := json.NewDecoder(bytes.NewReader(obj)) - dec.UseNumber() - if err := dec.Decode(&value); err != nil { - fmt.Fprintf(cli.err, "%s\n", err) - status = 1 - continue - } - if err := tmpl.Execute(cli.out, value); err != nil { - return err + + if isImage { + inspPtr := types.ImageInspect{} + if err := dec.Decode(&inspPtr); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + status = 1 + continue + } + if err := tmpl.Execute(cli.out, inspPtr); err != nil { + return err + } + } else { + inspPtr := types.ContainerJSON{} + if err := dec.Decode(&inspPtr); err != nil { + fmt.Fprintf(cli.err, "%s\n", err) + status = 1 + continue + } + if err := tmpl.Execute(cli.out, inspPtr); err != nil { + return err + + } } cli.out.Write([]byte{'\n'}) } diff --git a/api/types/types.go b/api/types/types.go index 656aa1a6e..7c3106546 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -75,6 +75,23 @@ type Image struct { Labels map[string]string } +// GET "/images/{name:.*}/json" +type ImageInspect struct { + Id string + Parent string + Comment string + Created time.Time + Container string + ContainerConfig *runconfig.Config + DockerVersion string + Author string + Config *runconfig.Config + Architecture string + Os string + Size int64 + VirtualSize int64 +} + type LegacyImage struct { ID string `json:"Id"` Repository string diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index db4093733..b4378eb6c 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -644,7 +644,7 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { if err != nil { c.Fatal(err) } - if cmd != "[/bin/sh -c touch /test]" { + if cmd != "{[/bin/sh -c touch /test]}" { c.Fatalf("got wrong Cmd from commit: %q", cmd) } // sanity check, make sure the image is what we think it is diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 7936b2bc3..ac90b2187 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -2350,7 +2350,7 @@ func (s *DockerSuite) TestBuildContextCleanupFailedBuild(c *check.C) { func (s *DockerSuite) TestBuildCmd(c *check.C) { name := "testbuildcmd" - expected := "[/bin/echo Hello World]" + expected := "{[/bin/echo Hello World]}" defer deleteImages(name) _, err := buildImage(name, `FROM scratch @@ -2370,7 +2370,7 @@ func (s *DockerSuite) TestBuildCmd(c *check.C) { func (s *DockerSuite) TestBuildExpose(c *check.C) { name := "testbuildexpose" - expected := "map[2375/tcp:map[]]" + expected := "map[2375/tcp:{}]" defer deleteImages(name) _, err := buildImage(name, `FROM scratch @@ -2467,7 +2467,7 @@ func (s *DockerSuite) TestBuildExposeOrder(c *check.C) { func (s *DockerSuite) TestBuildExposeUpperCaseProto(c *check.C) { name := "testbuildexposeuppercaseproto" - expected := "map[5678/udp:map[]]" + expected := "map[5678/udp:{}]" defer deleteImages(name) _, err := buildImage(name, `FROM scratch @@ -2488,7 +2488,7 @@ func (s *DockerSuite) TestBuildExposeUpperCaseProto(c *check.C) { func (s *DockerSuite) TestBuildExposeHostPort(c *check.C) { // start building docker file with ip:hostPort:containerPort name := "testbuildexpose" - expected := "map[5678/tcp:map[]]" + expected := "map[5678/tcp:{}]" defer deleteImages(name) _, out, err := buildImageWithOut(name, `FROM scratch @@ -2528,7 +2528,7 @@ func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { c.Fatal(err) } - expected := "[/bin/echo]" + expected := "{[/bin/echo]}" if res != expected { c.Fatalf("Entrypoint %s, expected %s", res, expected) } @@ -2545,7 +2545,7 @@ func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { c.Fatal(err) } - expected = "[]" + expected = "{[]}" if res != expected { c.Fatalf("Entrypoint %s, expected %s", res, expected) @@ -2556,7 +2556,7 @@ func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { func (s *DockerSuite) TestBuildEmptyEntrypoint(c *check.C) { name := "testbuildentrypoint" defer deleteImages(name) - expected := "[]" + expected := "{[]}" _, err := buildImage(name, `FROM busybox @@ -2577,7 +2577,7 @@ func (s *DockerSuite) TestBuildEmptyEntrypoint(c *check.C) { func (s *DockerSuite) TestBuildEntrypoint(c *check.C) { name := "testbuildentrypoint" - expected := "[/bin/echo]" + expected := "{[/bin/echo]}" defer deleteImages(name) _, err := buildImage(name, `FROM scratch @@ -3297,8 +3297,8 @@ func (s *DockerSuite) TestBuildEntrypointRunCleanup(c *check.C) { c.Fatal(err) } // Cmd must be cleaned up - if expected := ""; res != expected { - c.Fatalf("Cmd %s, expected %s", res, expected) + if res != "" { + c.Fatalf("Cmd %s, expected nil", res) } } @@ -3371,7 +3371,7 @@ func (s *DockerSuite) TestBuildInheritance(c *check.C) { if err != nil { c.Fatal(err) } - if expected := "[/bin/echo]"; res != expected { + if expected := "{[/bin/echo]}"; res != expected { c.Fatalf("Entrypoint %s, expected %s", res, expected) } ports2, err := inspectField(name, "Config.ExposedPorts") @@ -4242,14 +4242,15 @@ func (s *DockerSuite) TestBuildCleanupCmdOnEntrypoint(c *check.C) { if err != nil { c.Fatal(err) } - if expected := ""; res != expected { - c.Fatalf("Cmd %s, expected %s", res, expected) + if res != "" { + c.Fatalf("Cmd %s, expected nil", res) } + res, err = inspectField(name, "Config.Entrypoint") if err != nil { c.Fatal(err) } - if expected := "[cat]"; res != expected { + if expected := "{[cat]}"; res != expected { c.Fatalf("Entrypoint %s, expected %s", res, expected) } } diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index 9bbff09cc..a75621f3a 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -251,7 +251,7 @@ func (s *DockerSuite) TestCommitChange(c *check.C) { defer deleteImages(imageId) expected := map[string]string{ - "Config.ExposedPorts": "map[8080/tcp:map[]]", + "Config.ExposedPorts": "map[8080/tcp:{}]", "Config.Env": "[DEBUG=true test=1 PATH=/foo]", } diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 7fc3d4f25..0716c486b 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -448,7 +448,7 @@ func (s *DockerSuite) TestInspectExecID(c *check.C) { if err != nil { c.Fatalf("failed to inspect container: %s, %v", out, err) } - if out != "" { + if out != "[]" { c.Fatalf("ExecIDs should be empty, got: %s", out) } diff --git a/integration-cli/docker_cli_inspect_test.go b/integration-cli/docker_cli_inspect_test.go index 9a5a7b660..58c61a9d0 100644 --- a/integration-cli/docker_cli_inspect_test.go +++ b/integration-cli/docker_cli_inspect_test.go @@ -1,7 +1,9 @@ package main import ( + "fmt" "os/exec" + "strconv" "strings" "github.com/go-check/check" @@ -41,3 +43,58 @@ func (s *DockerSuite) TestInspectInt64(c *check.C) { c.Fatalf("inspect got wrong value, got: %q, expected: 314572800", inspectOut) } } + +func (s *DockerSuite) TestInspectImageFilterInt(c *check.C) { + imageTest := "emptyfs" + imagesCmd := exec.Command(dockerBinary, "inspect", "--format='{{.Size}}'", imageTest) + out, exitCode, err := runCommandWithOutput(imagesCmd) + if exitCode != 0 || err != nil { + c.Fatalf("failed to inspect image: %s, %v", out, err) + } + size, err := strconv.Atoi(strings.TrimSuffix(out, "\n")) + if err != nil { + c.Fatalf("failed to inspect size of the image: %s, %v", out, err) + } + + //now see if the size turns out to be the same + formatStr := fmt.Sprintf("--format='{{eq .Size %d}}'", size) + imagesCmd = exec.Command(dockerBinary, "inspect", formatStr, imageTest) + out, exitCode, err = runCommandWithOutput(imagesCmd) + if exitCode != 0 || err != nil { + c.Fatalf("failed to inspect image: %s, %v", out, err) + } + if result, err := strconv.ParseBool(strings.TrimSuffix(out, "\n")); err != nil || !result { + c.Fatalf("Expected size: %d for image: %s but received size: %s", size, imageTest, strings.TrimSuffix(out, "\n")) + } +} + +func (s *DockerSuite) TestInspectContainerFilterInt(c *check.C) { + runCmd := exec.Command("bash", "-c", `echo "blahblah" | docker run -i -a stdin busybox cat`) + out, _, _, err := runCommandWithStdoutStderr(runCmd) + if err != nil { + c.Fatalf("failed to run container: %v, output: %q", err, out) + } + + id := strings.TrimSpace(out) + + runCmd = exec.Command(dockerBinary, "inspect", "--format='{{.State.ExitCode}}'", id) + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("failed to inspect container: %s, %v", out, err) + } + exitCode, err := strconv.Atoi(strings.TrimSuffix(out, "\n")) + if err != nil { + c.Fatalf("failed to inspect exitcode of the container: %s, %v", out, err) + } + + //now get the exit code to verify + formatStr := fmt.Sprintf("--format='{{eq .State.ExitCode %d}}'", exitCode) + runCmd = exec.Command(dockerBinary, "inspect", formatStr, id) + out, _, err = runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("failed to inspect container: %s, %v", out, err) + } + if result, err := strconv.ParseBool(strings.TrimSuffix(out, "\n")); err != nil || !result { + c.Fatalf("Expected exitcode: %d for container: %s", exitCode, id) + } +} diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 7e12fc5a9..65f1770e3 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2443,7 +2443,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) { if err != nil { c.Fatal(err) } - if out != "" { + if out != "" { c.Fatalf("Found unexpected volume entry for '/foo/' in volumes\n%q", out) } @@ -2459,7 +2459,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) { if err != nil { c.Fatal(err) } - if out != "" { + if out != "" { c.Fatalf("Found unexpected volume entry for '/bar/' in volumes\n%q", out) } out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar") From 18f46883851e47387ec2bd116940cdae97ba3c8d Mon Sep 17 00:00:00 2001 From: Josh Hawn Date: Wed, 22 Apr 2015 14:41:24 -0700 Subject: [PATCH 608/999] Validate repo name before image pull Checks for reserved 'scratch' image name. fixes #12281 Docker-DCO-1.1-Signed-off-by: Josh Hawn (github: jlhawn) --- graph/pull.go | 4 ++++ integration-cli/docker_cli_pull_test.go | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/graph/pull.go b/graph/pull.go index b62591ffb..ac814a234 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -39,6 +39,10 @@ func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConf return err } + if err := validateRepoName(repoInfo.LocalName); err != nil { + return err + } + c, err := s.poolAdd("pull", utils.ImageReference(repoInfo.LocalName, tag)) if err != nil { if c != nil { diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index 8cf09a726..a4d4c977b 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -134,3 +134,22 @@ func (s *DockerSuite) TestPullImageOfficialNames(c *check.C) { } } } + +func (s *DockerSuite) TestPullScratchNotAllowed(c *check.C) { + testRequires(c, Network) + + pullCmd := exec.Command(dockerBinary, "pull", "scratch") + out, exitCode, err := runCommandWithOutput(pullCmd) + if err == nil { + c.Fatal("expected pull of scratch to fail, but it didn't") + } + if exitCode != 1 { + c.Fatalf("pulling scratch expected exit code 1, got %d", exitCode) + } + if strings.Contains(out, "Pulling repository scratch") { + c.Fatalf("pulling scratch should not have begun: %s", out) + } + if !strings.Contains(out, "'scratch' is a reserved name") { + c.Fatalf("unexpected output pulling scratch: %s", out) + } +} From c5ef2901d8190b687e847ab17d306dae5af37b42 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 23 Apr 2015 22:27:46 +0200 Subject: [PATCH 609/999] delete "defer deleteContainer" on tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since docker test suite is now using gocheck, ``defer deleteContainer(…)`` is not needed anymore. Fixes #12705 Signed-off-by: Vincent Demeester --- integration-cli/docker_cli_by_digest_test.go | 2 -- integration-cli/docker_cli_cp_test.go | 10 ---------- integration-cli/docker_cli_export_import_test.go | 2 -- integration-cli/docker_cli_history_test.go | 1 - integration-cli/docker_cli_import_test.go | 1 - integration-cli/docker_cli_logs_test.go | 1 - integration-cli/docker_cli_save_load_test.go | 2 -- integration-cli/docker_cli_top_test.go | 1 - 8 files changed, 20 deletions(-) diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index fc8c4600b..bd4518434 100644 --- a/integration-cli/docker_cli_by_digest_test.go +++ b/integration-cli/docker_cli_by_digest_test.go @@ -140,7 +140,6 @@ func (s *DockerSuite) TestCreateByDigest(c *check.C) { if err != nil { c.Fatalf("error creating by digest: %s, %v", out, err) } - defer deleteContainer(containerName) res, err := inspectField(containerName, "Config.Image") if err != nil { @@ -168,7 +167,6 @@ func (s *DockerSuite) TestRunByDigest(c *check.C) { if err != nil { c.Fatalf("error run by digest: %s, %v", out, err) } - defer deleteContainer(containerName) foundRegex := regexp.MustCompile("found=([^\n]+)") matches := foundRegex.FindStringSubmatch(out) diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go index 022b3cc9e..26e778e4f 100644 --- a/integration-cli/docker_cli_cp_test.go +++ b/integration-cli/docker_cli_cp_test.go @@ -32,7 +32,6 @@ func (s *DockerSuite) TestCpGarbagePath(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -90,7 +89,6 @@ func (s *DockerSuite) TestCpRelativePath(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -156,7 +154,6 @@ func (s *DockerSuite) TestCpAbsolutePath(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -216,7 +213,6 @@ func (s *DockerSuite) TestCpAbsoluteSymlink(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -276,7 +272,6 @@ func (s *DockerSuite) TestCpSymlinkComponent(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -337,7 +332,6 @@ func (s *DockerSuite) TestCpUnprivilegedUser(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -379,7 +373,6 @@ func (s *DockerSuite) TestCpSpecialFiles(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -525,7 +518,6 @@ func (s *DockerSuite) TestCpToDot(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { @@ -559,7 +551,6 @@ func (s *DockerSuite) TestCpToStdout(c *check.C) { } cID := strings.TrimSpace(out) - defer deleteContainer(cID) out, _ = dockerCmd(c, "wait", cID) if strings.TrimSpace(out) != "0" { @@ -588,7 +579,6 @@ func (s *DockerSuite) TestCpNameHasColon(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _ = dockerCmd(c, "wait", cleanedContainerID) if strings.TrimSpace(out) != "0" { diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go index 59a510630..bc7b16356 100644 --- a/integration-cli/docker_cli_export_import_test.go +++ b/integration-cli/docker_cli_export_import_test.go @@ -13,7 +13,6 @@ func (s *DockerSuite) TestExportContainerAndImportImage(c *check.C) { containerID := "testexportcontainerandimportimage" defer deleteImages("repo/testexp:v1") - defer deleteContainer(containerID) runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) @@ -53,7 +52,6 @@ func (s *DockerSuite) TestExportContainerWithOutputAndImportImage(c *check.C) { containerID := "testexportcontainerwithoutputandimportimage" defer deleteImages("repo/testexp:v1") - defer deleteContainer(containerID) runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go index 134f7bc7f..8de400831 100644 --- a/integration-cli/docker_cli_history_test.go +++ b/integration-cli/docker_cli_history_test.go @@ -85,7 +85,6 @@ func (s *DockerSuite) TestHistoryNonExistentImage(c *check.C) { func (s *DockerSuite) TestHistoryImageWithComment(c *check.C) { name := "testhistoryimagewithcomment" - defer deleteContainer(name) defer deleteImages(name) // make a image through docker commit [ -m messages ] diff --git a/integration-cli/docker_cli_import_test.go b/integration-cli/docker_cli_import_test.go index dd06ef822..02b857bfd 100644 --- a/integration-cli/docker_cli_import_test.go +++ b/integration-cli/docker_cli_import_test.go @@ -14,7 +14,6 @@ func (s *DockerSuite) TestImportDisplay(c *check.C) { c.Fatal("failed to create a container", out, err) } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) out, _, err = runCommandPipelineWithOutput( exec.Command(dockerBinary, "export", cleanedContainerID), diff --git a/integration-cli/docker_cli_logs_test.go b/integration-cli/docker_cli_logs_test.go index 7f04a328b..a7c891622 100644 --- a/integration-cli/docker_cli_logs_test.go +++ b/integration-cli/docker_cli_logs_test.go @@ -287,7 +287,6 @@ func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) stopSlowRead := make(chan bool) diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go index ead436a92..f74f69fcc 100644 --- a/integration-cli/docker_cli_save_load_test.go +++ b/integration-cli/docker_cli_save_load_test.go @@ -216,7 +216,6 @@ func (s *DockerSuite) TestSaveAndLoadRepoFlags(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) repoName := "foobar-save-load-test" @@ -298,7 +297,6 @@ func (s *DockerSuite) TestSaveRepoWithMultipleImages(c *check.C) { c.Fatalf("failed to create a container: %v %v", out, err) } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, tag) if out, _, err = runCommandWithOutput(commitCmd); err != nil { diff --git a/integration-cli/docker_cli_top_test.go b/integration-cli/docker_cli_top_test.go index 7e75a38d5..f941a42cd 100644 --- a/integration-cli/docker_cli_top_test.go +++ b/integration-cli/docker_cli_top_test.go @@ -15,7 +15,6 @@ func (s *DockerSuite) TestTopMultipleArgs(c *check.C) { } cleanedContainerID := strings.TrimSpace(out) - defer deleteContainer(cleanedContainerID) topCmd := exec.Command(dockerBinary, "top", cleanedContainerID, "-o", "pid") out, _, err = runCommandWithOutput(topCmd) From b6d8b65e5595304ef9b89f1f2ff2cae46c70dae9 Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Thu, 23 Apr 2015 14:04:36 -0700 Subject: [PATCH 610/999] Removing firewalld info Signed-off-by: Mary Anthony --- docs/sources/installation/centos.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/docs/sources/installation/centos.md b/docs/sources/installation/centos.md index 862d50898..7868f11b0 100644 --- a/docs/sources/installation/centos.md +++ b/docs/sources/installation/centos.md @@ -33,17 +33,6 @@ run the following command: Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon). -### FirewallD - -CentOS-7 introduced firewalld, which is a wrapper around iptables and can -conflict with Docker. - -When `firewalld` is started or restarted it will remove the `DOCKER` chain -from iptables, preventing Docker from working properly. - -When using Systemd, `firewalld` is started before Docker, but if you -start or restart `firewalld` after Docker, you will have to restart the Docker daemon. - ## Installing Docker - CentOS-6.5 For CentOS-6.5, the Docker package is part of [Extra Packages From 55d1ac645cc36bb40046b2bcc400f8740ed2e86c Mon Sep 17 00:00:00 2001 From: Florian Weingarten Date: Thu, 23 Apr 2015 21:11:08 +0000 Subject: [PATCH 611/999] [docs] fix formatting issue Signed-off-by: Florian Weingarten --- docs/sources/project/set-up-dev-env.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/project/set-up-dev-env.md b/docs/sources/project/set-up-dev-env.md index 80d4f335b..60a59b615 100644 --- a/docs/sources/project/set-up-dev-env.md +++ b/docs/sources/project/set-up-dev-env.md @@ -209,7 +209,7 @@ build and run a `docker` binary in your container. root@5f8630b873fe:/go/src/github.com/docker/docker# The command creates a container from your `dry-run-test` image. It opens an - interactive terminal (`-ti`) running a `/bin/bash shell`. The + interactive terminal (`-ti`) running a `/bin/bash` shell. The `--privileged` flag gives the container access to kernel features and device access. This flag allows you to run a container in a container. Finally, the `-rm` flag instructs Docker to remove the container when you From fa2c68a89e153cfc82c5af7cbb6d7f15b06e0a8c Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 23 Apr 2015 21:05:21 +0200 Subject: [PATCH 612/999] Remove engine/job from graph Signed-off-by: Antonio Murdaca --- api/server/server.go | 33 ++++++----- api/server/server_unit_test.go | 104 --------------------------------- daemon/daemon.go | 3 - graph/export.go | 23 ++++---- graph/load.go | 19 ++---- graph/load_unsupported.go | 7 +-- graph/service.go | 76 +++++++++++------------- graph/viz.go | 39 ------------- integration/runtime_test.go | 13 +++-- 9 files changed, 81 insertions(+), 236 deletions(-) delete mode 100644 api/server/server_unit_test.go delete mode 100644 graph/viz.go diff --git a/api/server/server.go b/api/server/server.go index 92770e3b4..830e731a7 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -899,10 +899,7 @@ func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w htt } output := utils.NewWriteFlusher(w) - imageExportConfig := &graph.ImageExportConfig{ - Engine: eng, - Outstream: output, - } + imageExportConfig := &graph.ImageExportConfig{Outstream: output} if name, ok := vars["name"]; ok { imageExportConfig.Names = []string{name} } else { @@ -921,14 +918,7 @@ func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w htt } func (s *Server) postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - - imageLoadConfig := &graph.ImageLoadConfig{ - InTar: r.Body, - OutStream: w, - Engine: eng, - } - - return s.daemon.Repositories().Load(imageLoadConfig) + return s.daemon.Repositories().Load(r.Body, w) } func (s *Server) postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -1269,12 +1259,23 @@ func (s *Server) getImagesByName(eng *engine.Engine, version version.Version, w if vars == nil { return fmt.Errorf("Missing parameter") } - var job = eng.Job("image_inspect", vars["name"]) + + name := vars["name"] if version.LessThan("1.12") { - job.SetenvBool("raw", true) + imageInspectRaw, err := s.daemon.Repositories().LookupRaw(name) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, imageInspectRaw) } - streamJSON(job.Stdout, w, false) - return job.Run() + + imageInspect, err := s.daemon.Repositories().Lookup(name) + if err != nil { + return err + } + + return writeJSON(w, http.StatusOK, imageInspect) } func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/api/server/server_unit_test.go b/api/server/server_unit_test.go deleted file mode 100644 index 60cee8224..000000000 --- a/api/server/server_unit_test.go +++ /dev/null @@ -1,104 +0,0 @@ -package server - -import ( - "encoding/json" - "fmt" - "io" - "net/http" - "net/http/httptest" - "testing" - - "github.com/docker/docker/api" - "github.com/docker/docker/engine" - "github.com/docker/docker/pkg/version" -) - -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) - } - - r = httptest.NewRecorder() - 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) - } - - r = httptest.NewRecorder() - httpError(r, fmt.Errorf("Some error")) - if r.Code != http.StatusInternalServerError { - t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code) - } -} - -func TestGetImagesByName(t *testing.T) { - eng := engine.New() - name := "image_name" - var called bool - eng.Register("image_inspect", func(job *engine.Job) error { - called = true - if job.Args[0] != name { - t.Fatalf("name != '%s': %#v", name, job.Args[0]) - } - if api.APIVERSION.LessThan("1.12") && !job.GetenvBool("dirty") { - t.Fatal("dirty env variable not set") - } else if api.APIVERSION.GreaterThanOrEqualTo("1.12") && job.GetenvBool("dirty") { - t.Fatal("dirty env variable set when it shouldn't") - } - v := &engine.Env{} - v.SetBool("dirty", true) - if _, err := v.WriteTo(job.Stdout); err != nil { - return err - } - return nil - }) - r := serveRequest("GET", "/images/"+name+"/json", nil, eng, t) - if !called { - t.Fatal("handler was not called") - } - if r.HeaderMap.Get("Content-Type") != "application/json" { - t.Fatalf("%#v\n", r) - } - var stdoutJson interface{} - if err := json.Unmarshal(r.Body.Bytes(), &stdoutJson); err != nil { - t.Fatalf("%#v", err) - } - if stdoutJson.(map[string]interface{})["dirty"].(float64) != 1 { - t.Fatalf("%#v", stdoutJson) - } -} - -func serveRequest(method, target string, body io.Reader, eng *engine.Engine, t *testing.T) *httptest.ResponseRecorder { - return serveRequestUsingVersion(method, target, api.APIVERSION, body, eng, t) -} - -func serveRequestUsingVersion(method, target string, version version.Version, body io.Reader, eng *engine.Engine, t *testing.T) *httptest.ResponseRecorder { - r := httptest.NewRecorder() - req, err := http.NewRequest(method, target, body) - if err != nil { - t.Fatal(err) - } - ServeRequest(eng, version, r, req) - return r -} - -func readEnv(src io.Reader, t *testing.T) *engine.Env { - out := engine.NewOutput() - v, err := out.AddEnv() - if err != nil { - t.Fatal(err) - } - if _, err := io.Copy(out, src); err != nil { - t.Fatal(err) - } - out.Close() - return v -} - -func assertContentType(recorder *httptest.ResponseRecorder, contentType string, t *testing.T) { - if recorder.HeaderMap.Get("Content-Type") != contentType { - t.Fatalf("%#v\n", recorder) - } -} diff --git a/daemon/daemon.go b/daemon/daemon.go index bfebd920f..d186854ad 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -116,9 +116,6 @@ type Daemon struct { // Install installs daemon capabilities to eng. func (daemon *Daemon) Install(eng *engine.Engine) error { - if err := daemon.Repositories().Install(eng); err != nil { - return err - } // FIXME: this hack is necessary for legacy integration tests to access // the daemon object. eng.HackSetGlobalVar("httpapi.daemon", daemon) diff --git a/graph/export.go b/graph/export.go index 00cfa8975..ee626aae6 100644 --- a/graph/export.go +++ b/graph/export.go @@ -8,7 +8,6 @@ import ( "path" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/registry" @@ -22,7 +21,6 @@ import ( type ImageExportConfig struct { Names []string Outstream io.Writer - Engine *engine.Engine } func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { @@ -51,7 +49,7 @@ func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { // this is a base repo name, like 'busybox' for tag, id := range rootRepo { addKey(name, tag, id) - if err := s.exportImage(imageExportConfig.Engine, id, tempdir); err != nil { + if err := s.exportImage(id, tempdir); err != nil { return err } } @@ -70,13 +68,13 @@ func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { if len(repoTag) > 0 { addKey(repoName, repoTag, img.ID) } - if err := s.exportImage(imageExportConfig.Engine, img.ID, tempdir); err != nil { + if err := s.exportImage(img.ID, tempdir); err != nil { return err } } else { // this must be an ID that didn't get looked up just right? - if err := s.exportImage(imageExportConfig.Engine, name, tempdir); err != nil { + if err := s.exportImage(name, tempdir); err != nil { return err } } @@ -107,7 +105,7 @@ func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { } // FIXME: this should be a top-level function, not a class method -func (s *TagStore) exportImage(eng *engine.Engine, name, tempdir string) error { +func (s *TagStore) exportImage(name, tempdir string) error { for n := name; n != ""; { // temporary directory tmpImageDir := path.Join(tempdir, n) @@ -130,12 +128,17 @@ func (s *TagStore) exportImage(eng *engine.Engine, name, tempdir string) error { if err != nil { return err } - job := eng.Job("image_inspect", n) - job.SetenvBool("raw", true) - job.Stdout.Add(json) - if err := job.Run(); err != nil { + imageInspectRaw, err := s.LookupRaw(n) + if err != nil { return err } + written, err := json.Write(imageInspectRaw) + if err != nil { + return err + } + if written != len(imageInspectRaw) { + logrus.Warnf("%d byes should have been written instead %d have been written", written, len(imageInspectRaw)) + } // serialize filesystem fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar")) diff --git a/graph/load.go b/graph/load.go index f62b82ce4..be968bab5 100644 --- a/graph/load.go +++ b/graph/load.go @@ -10,21 +10,14 @@ import ( "path" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/chrootarchive" ) -type ImageLoadConfig struct { - InTar io.ReadCloser - OutStream io.Writer - Engine *engine.Engine -} - // Loads a set of images into the repository. This is the complementary of ImageExport. // The input stream is an uncompressed tar ball containing images and metadata. -func (s *TagStore) Load(imageLoadConfig *ImageLoadConfig) error { +func (s *TagStore) Load(inTar io.ReadCloser, outStream io.Writer) error { tmpImageDir, err := ioutil.TempDir("", "docker-import-") if err != nil { return err @@ -48,7 +41,7 @@ func (s *TagStore) Load(imageLoadConfig *ImageLoadConfig) error { excludes[i] = k i++ } - if err := chrootarchive.Untar(imageLoadConfig.InTar, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil { + if err := chrootarchive.Untar(inTar, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil { return err } @@ -59,7 +52,7 @@ func (s *TagStore) Load(imageLoadConfig *ImageLoadConfig) error { for _, d := range dirs { if d.IsDir() { - if err := s.recursiveLoad(imageLoadConfig.Engine, d.Name(), tmpImageDir); err != nil { + if err := s.recursiveLoad(d.Name(), tmpImageDir); err != nil { return err } } @@ -74,7 +67,7 @@ func (s *TagStore) Load(imageLoadConfig *ImageLoadConfig) error { for imageName, tagMap := range repositories { for tag, address := range tagMap { - if err := s.SetLoad(imageName, tag, address, true, imageLoadConfig.OutStream); err != nil { + if err := s.SetLoad(imageName, tag, address, true, outStream); err != nil { return err } } @@ -86,7 +79,7 @@ func (s *TagStore) Load(imageLoadConfig *ImageLoadConfig) error { return nil } -func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string) error { +func (s *TagStore) recursiveLoad(address, tmpImageDir string) error { if _, err := s.LookupImage(address); err != nil { logrus.Debugf("Loading %s", address) @@ -126,7 +119,7 @@ func (s *TagStore) recursiveLoad(eng *engine.Engine, address, tmpImageDir string if img.Parent != "" { if !s.graph.Exists(img.Parent) { - if err := s.recursiveLoad(eng, img.Parent, tmpImageDir); err != nil { + if err := s.recursiveLoad(img.Parent, tmpImageDir); err != nil { return err } } diff --git a/graph/load_unsupported.go b/graph/load_unsupported.go index 707534480..7c5155969 100644 --- a/graph/load_unsupported.go +++ b/graph/load_unsupported.go @@ -4,10 +4,9 @@ package graph import ( "fmt" - - "github.com/docker/docker/engine" + "io" ) -func (s *TagStore) CmdLoad(job *engine.Job) error { - return fmt.Errorf("CmdLoad is not supported on this platform") +func (s *TagStore) Load(inTar io.ReadCloser, outStream io.Writer) error { + return fmt.Errorf("Load is not supported on this platform") } diff --git a/graph/service.go b/graph/service.go index ab78c1d05..52dde1d98 100644 --- a/graph/service.go +++ b/graph/service.go @@ -5,57 +5,47 @@ import ( "io" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" ) -func (s *TagStore) Install(eng *engine.Engine) error { - for name, handler := range map[string]engine.Handler{ - "image_inspect": s.CmdLookup, - "viz": s.CmdViz, - } { - if err := eng.Register(name, handler); err != nil { - return fmt.Errorf("Could not register %q: %v", name, err) - } +func (s *TagStore) LookupRaw(name string) ([]byte, error) { + image, err := s.LookupImage(name) + if err != nil || image == nil { + return nil, fmt.Errorf("No such image %s", name) } - return nil + + imageInspectRaw, err := image.RawJson() + if err != nil { + return nil, err + } + + return imageInspectRaw, nil } -// CmdLookup return an image encoded in JSON -func (s *TagStore) CmdLookup(job *engine.Job) error { - if len(job.Args) != 1 { - return fmt.Errorf("usage: %s NAME", job.Name) +// Lookup return an image encoded in JSON +func (s *TagStore) Lookup(name string) (*types.ImageInspect, error) { + image, err := s.LookupImage(name) + if err != nil || image == nil { + return nil, fmt.Errorf("No such image: %s", name) } - name := job.Args[0] - if image, err := s.LookupImage(name); err == nil && image != nil { - if job.GetenvBool("raw") { - b, err := image.RawJson() - if err != nil { - return err - } - job.Stdout.Write(b) - return nil - } - out := &engine.Env{} - out.SetJson("Id", image.ID) - out.SetJson("Parent", image.Parent) - out.SetJson("Comment", image.Comment) - out.SetAuto("Created", image.Created) - out.SetJson("Container", image.Container) - out.SetJson("ContainerConfig", image.ContainerConfig) - out.Set("DockerVersion", image.DockerVersion) - out.SetJson("Author", image.Author) - out.SetJson("Config", image.Config) - out.Set("Architecture", image.Architecture) - out.Set("Os", image.OS) - out.SetInt64("Size", image.Size) - out.SetInt64("VirtualSize", image.GetParentsSize(0)+image.Size) - if _, err = out.WriteTo(job.Stdout); err != nil { - return err - } - return nil + imageInspect := &types.ImageInspect{ + Id: image.ID, + Parent: image.Parent, + Comment: image.Comment, + Created: image.Created, + Container: image.Container, + ContainerConfig: &image.ContainerConfig, + DockerVersion: image.DockerVersion, + Author: image.Author, + Config: image.Config, + Architecture: image.Architecture, + Os: image.OS, + Size: image.Size, + VirtualSize: image.GetParentsSize(0) + image.Size, } - return fmt.Errorf("No such image: %s", name) + + return imageInspect, nil } // ImageTarLayer return the tarLayer of the image diff --git a/graph/viz.go b/graph/viz.go deleted file mode 100644 index 0c45caa9e..000000000 --- a/graph/viz.go +++ /dev/null @@ -1,39 +0,0 @@ -package graph - -import ( - "fmt" - "strings" - - "github.com/docker/docker/engine" - "github.com/docker/docker/image" -) - -func (s *TagStore) CmdViz(job *engine.Job) error { - images, _ := s.graph.Map() - if images == nil { - return nil - } - job.Stdout.Write([]byte("digraph docker {\n")) - - var ( - parentImage *image.Image - err error - ) - for _, image := range images { - parentImage, err = image.GetParent() - if err != nil { - return fmt.Errorf("Error while getting parent image: %v", err) - } - if parentImage != nil { - job.Stdout.Write([]byte(" \"" + parentImage.ID + "\" -> \"" + image.ID + "\"\n")) - } else { - job.Stdout.Write([]byte(" base -> \"" + image.ID + "\" [style=invis]\n")) - } - } - - for id, repos := range s.GetRepoRefs() { - job.Stdout.Write([]byte(" \"" + id + "\" [label=\"" + id + "\\n" + strings.Join(repos, "\\n") + "\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n")) - } - job.Stdout.Write([]byte(" base [style=invisible]\n}\n")) - return nil -} diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 99995016e..82f21b700 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -125,17 +125,22 @@ func init() { func setupBaseImage() { eng := newTestEngine(std_log.New(os.Stderr, "", 0), false, unitTestStoreBase) - job := eng.Job("image_inspect", unitTestImageName) - img, _ := job.Stdout.AddEnv() + d := getDaemon(eng) + + _, err := d.Repositories().Lookup(unitTestImageName) // If the unit test is not found, try to download it. - if err := job.Run(); err != nil || img.Get("Id") != unitTestImageID { + if err != nil { + // seems like we can just ignore the error here... + // there was a check of imgId from job stdout against unittestid but + // if there was an error how could the imgid from the job + // be compared?! it's obvious it's different, am I totally wrong? + // Retrieve the Image imagePullConfig := &graph.ImagePullConfig{ Parallel: true, OutStream: ioutils.NopWriteCloser(os.Stdout), AuthConfig: &cliconfig.AuthConfig{}, } - d := getDaemon(eng) if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig); err != nil { logrus.Fatalf("Unable to pull the test image: %s", err) } From ade8146aa82baa88bacdcf2d9c2559e8f47d71e4 Mon Sep 17 00:00:00 2001 From: "Daniel, Dao Quang Minh" Date: Wed, 22 Apr 2015 23:37:15 +0000 Subject: [PATCH 613/999] reuse same code for setting pipes in run/exec This also moves `exec -i` test to _unix_test.go because it seems to need a pty to reliably reproduce the behavior. Signed-off-by: Daniel, Dao Quang Minh --- daemon/execdriver/native/driver.go | 68 +++++++++++--------- daemon/execdriver/native/exec.go | 25 +------ integration-cli/docker_cli_exec_test.go | 38 ----------- integration-cli/docker_cli_exec_unix_test.go | 47 ++++++++++++++ 4 files changed, 87 insertions(+), 91 deletions(-) create mode 100644 integration-cli/docker_cli_exec_unix_test.go diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index 7bc28f10f..ad13e1c1e 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -87,8 +87,6 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba return execdriver.ExitStatus{ExitCode: -1}, err } - var term execdriver.Terminal - p := &libcontainer.Process{ Args: append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...), Env: c.ProcessConfig.Env, @@ -96,36 +94,9 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba User: c.ProcessConfig.User, } - if c.ProcessConfig.Tty { - rootuid, err := container.HostUID() - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - cons, err := p.NewConsole(rootuid) - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - term, err = NewTtyConsole(cons, pipes, rootuid) - } else { - p.Stdout = pipes.Stdout - p.Stderr = pipes.Stderr - r, w, err := os.Pipe() - if err != nil { - return execdriver.ExitStatus{ExitCode: -1}, err - } - if pipes.Stdin != nil { - go func() { - io.Copy(w, pipes.Stdin) - w.Close() - }() - p.Stdin = r - } - term = &execdriver.StdConsole{} - } - if err != nil { + if err := setupPipes(container, &c.ProcessConfig, p, pipes); err != nil { return execdriver.ExitStatus{ExitCode: -1}, err } - c.ProcessConfig.Terminal = term cont, err := d.factory.Create(c.ID, container) if err != nil { @@ -398,3 +369,40 @@ func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes) error { func (t *TtyConsole) Close() error { return t.console.Close() } + +func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConfig, p *libcontainer.Process, pipes *execdriver.Pipes) error { + var term execdriver.Terminal + var err error + + if processConfig.Tty { + rootuid, err := container.HostUID() + if err != nil { + return err + } + cons, err := p.NewConsole(rootuid) + if err != nil { + return err + } + term, err = NewTtyConsole(cons, pipes, rootuid) + } else { + p.Stdout = pipes.Stdout + p.Stderr = pipes.Stderr + r, w, err := os.Pipe() + if err != nil { + return err + } + if pipes.Stdin != nil { + go func() { + io.Copy(w, pipes.Stdin) + w.Close() + }() + p.Stdin = r + } + term = &execdriver.StdConsole{} + } + if err != nil { + return err + } + processConfig.Terminal = term + return nil +} diff --git a/daemon/execdriver/native/exec.go b/daemon/execdriver/native/exec.go index 04239fdac..dd41c0ad1 100644 --- a/daemon/execdriver/native/exec.go +++ b/daemon/execdriver/native/exec.go @@ -20,9 +20,6 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo return -1, fmt.Errorf("No active container exists with ID %s", c.ID) } - var term execdriver.Terminal - var err error - p := &libcontainer.Process{ Args: append([]string{processConfig.Entrypoint}, processConfig.Arguments...), Env: c.ProcessConfig.Env, @@ -34,29 +31,11 @@ func (d *driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessCo p.Capabilities = execdriver.GetAllCapabilities() } - if processConfig.Tty { - config := active.Config() - rootuid, err := config.HostUID() - if err != nil { - return -1, err - } - cons, err := p.NewConsole(rootuid) - if err != nil { - return -1, err - } - term, err = NewTtyConsole(cons, pipes, rootuid) - } else { - p.Stdout = pipes.Stdout - p.Stderr = pipes.Stderr - p.Stdin = pipes.Stdin - term = &execdriver.StdConsole{} - } - if err != nil { + config := active.Config() + if err := setupPipes(&config, processConfig, p, pipes); err != nil { return -1, err } - processConfig.Terminal = term - if err := active.Start(p); err != nil { return -1, err } diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index 7fc3d4f25..f5909005e 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -38,44 +38,6 @@ func (s *DockerSuite) TestExec(c *check.C) { } -func (s *DockerSuite) TestExecInteractiveStdinClose(c *check.C) { - out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "busybox", "/bin/cat")) - if err != nil { - c.Fatal(err) - } - - contId := strings.TrimSpace(out) - - returnchan := make(chan struct{}) - - go func() { - var err error - cmd := exec.Command(dockerBinary, "exec", "-i", contId, "/bin/ls", "/") - cmd.Stdin = os.Stdin - if err != nil { - c.Fatal(err) - } - - out, err := cmd.CombinedOutput() - if err != nil { - c.Fatal(err, string(out)) - } - - if string(out) == "" { - c.Fatalf("Output was empty, likely blocked by standard input") - } - - returnchan <- struct{}{} - }() - - select { - case <-returnchan: - case <-time.After(10 * time.Second): - c.Fatal("timed out running docker exec") - } - -} - func (s *DockerSuite) TestExecInteractive(c *check.C) { runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "testing", "busybox", "sh", "-c", "echo test > /tmp/file && top") diff --git a/integration-cli/docker_cli_exec_unix_test.go b/integration-cli/docker_cli_exec_unix_test.go new file mode 100644 index 000000000..bee44b990 --- /dev/null +++ b/integration-cli/docker_cli_exec_unix_test.go @@ -0,0 +1,47 @@ +// +build !windows,!test_no_exec + +package main + +import ( + "bytes" + "io" + "os/exec" + "strings" + "time" + + "github.com/go-check/check" + "github.com/kr/pty" +) + +// regression test for #12546 +func (s *DockerSuite) TestExecInteractiveStdinClose(c *check.C) { + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "busybox", "/bin/cat")) + if err != nil { + c.Fatal(err) + } + contId := strings.TrimSpace(out) + + cmd := exec.Command(dockerBinary, "exec", "-i", contId, "echo", "-n", "hello") + p, err := pty.Start(cmd) + if err != nil { + c.Fatal(err) + } + + b := bytes.NewBuffer(nil) + go io.Copy(b, p) + + ch := make(chan error) + go func() { ch <- cmd.Wait() }() + + select { + case err := <-ch: + if err != nil { + c.Errorf("cmd finished with error %v", err) + } + if output := b.String(); strings.TrimSpace(output) != "hello" { + c.Fatalf("Unexpected output %s", output) + } + case <-time.After(1 * time.Second): + c.Fatal("timed out running docker exec") + } +} From c7845e27ee2b0462b01d043caeb771f21d5e4ac7 Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Mon, 20 Apr 2015 14:03:56 -0700 Subject: [PATCH 614/999] Fixing statusCode checks for sockRequest Signed-off-by: Megan Kostick --- integration-cli/docker_api_containers_test.go | 228 ++++++++---------- .../docker_api_exec_resize_test.go | 8 +- integration-cli/docker_api_exec_test.go | 10 +- integration-cli/docker_api_images_test.go | 32 +-- integration-cli/docker_api_info_test.go | 7 +- integration-cli/docker_api_inspect_test.go | 8 +- integration-cli/docker_api_logs_test.go | 16 +- integration-cli/docker_api_resize_test.go | 23 +- integration-cli/docker_api_version_test.go | 9 +- integration-cli/docker_cli_rm_test.go | 9 +- integration-cli/docker_utils.go | 10 +- integration-cli/requirements.go | 4 +- 12 files changed, 165 insertions(+), 199 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 8efc6239d..24efbb7a2 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -28,10 +28,9 @@ func (s *DockerSuite) TestContainerApiGetAll(c *check.C) { c.Fatalf("Error on container creation: %v, output: %q", err, out) } - _, body, err := sockRequest("GET", "/containers/json?all=1", nil) - if err != nil { - c.Fatalf("GET all containers sockRequest failed: %v", err) - } + status, body, err := sockRequest("GET", "/containers/json?all=1", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) var inspectJSON []struct { Names []string @@ -57,10 +56,9 @@ func (s *DockerSuite) TestContainerApiGetExport(c *check.C) { c.Fatalf("Error on container creation: %v, output: %q", err, out) } - _, body, err := sockRequest("GET", "/containers/"+name+"/export", nil) - if err != nil { - c.Fatalf("GET containers/export sockRequest failed: %v", err) - } + status, body, err := sockRequest("GET", "/containers/"+name+"/export", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) found := false for tarReader := tar.NewReader(bytes.NewReader(body)); ; { @@ -90,10 +88,9 @@ func (s *DockerSuite) TestContainerApiGetChanges(c *check.C) { c.Fatalf("Error on container creation: %v, output: %q", err, out) } - _, body, err := sockRequest("GET", "/containers/"+name+"/changes", nil) - if err != nil { - c.Fatalf("GET containers/changes sockRequest failed: %v", err) - } + status, body, err := sockRequest("GET", "/containers/"+name+"/changes", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) changes := []struct { Kind int @@ -122,17 +119,17 @@ func (s *DockerSuite) TestContainerApiStartVolumeBinds(c *check.C) { "Volumes": map[string]struct{}{"/tmp": {}}, } - if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - c.Fatal(err) - } + status, _, err := sockRequest("POST", "/containers/create?name="+name, config) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) bindPath := randomUnixTmpDirPath("test") config = map[string]interface{}{ "Binds": []string{bindPath + ":/tmp"}, } - if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { - c.Fatal(err) - } + status, _, err = sockRequest("POST", "/containers/"+name+"/start", config) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) pth, err := inspectFieldMap(name, "Volumes", "/tmp") if err != nil { @@ -152,9 +149,9 @@ func (s *DockerSuite) TestContainerApiStartDupVolumeBinds(c *check.C) { "Volumes": map[string]struct{}{"/tmp": {}}, } - if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - c.Fatal(err) - } + status, _, err := sockRequest("POST", "/containers/create?name="+name, config) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) bindPath1 := randomUnixTmpDirPath("test1") bindPath2 := randomUnixTmpDirPath("test2") @@ -162,14 +159,15 @@ func (s *DockerSuite) TestContainerApiStartDupVolumeBinds(c *check.C) { config = map[string]interface{}{ "Binds": []string{bindPath1 + ":/tmp", bindPath2 + ":/tmp"}, } - if _, body, err := sockRequest("POST", "/containers/"+name+"/start", config); err == nil { - c.Fatal("expected container start to fail when duplicate volume binds to same container path") - } else { - if !strings.Contains(string(body), "Duplicate volume") { - c.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err) - } + status, body, err := sockRequest("POST", "/containers/"+name+"/start", config) + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) + + if !strings.Contains(string(body), "Duplicate volume") { + c.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err) } } + func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) { volName := "voltst" volPath := "/tmp" @@ -184,16 +182,16 @@ func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) { "Volumes": map[string]struct{}{volPath: {}}, } - if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - c.Fatal(err) - } + status, _, err := sockRequest("POST", "/containers/create?name="+name, config) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) config = map[string]interface{}{ "VolumesFrom": []string{volName}, } - if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { - c.Fatal(err) - } + status, _, err = sockRequest("POST", "/containers/"+name+"/start", config) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) pth, err := inspectFieldMap(name, "Volumes", volPath) if err != nil { @@ -225,18 +223,18 @@ func (s *DockerSuite) TestVolumesFromHasPriority(c *check.C) { "Volumes": map[string]struct{}{volPath: {}}, } - if status, _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && status != http.StatusCreated { - c.Fatal(err) - } + status, _, err := sockRequest("POST", "/containers/create?name="+name, config) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) bindPath := randomUnixTmpDirPath("test") config = map[string]interface{}{ "VolumesFrom": []string{volName}, "Binds": []string{bindPath + ":/tmp"}, } - if status, _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && status != http.StatusNoContent { - c.Fatal(err) - } + status, _, err = sockRequest("POST", "/containers/"+name+"/start", config) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) pth, err := inspectFieldMap(name, "Volumes", volPath) if err != nil { @@ -267,7 +265,9 @@ func (s *DockerSuite) TestGetContainerStats(c *check.C) { } bc := make(chan b, 1) go func() { - _, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) + status, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) bc <- b{body, err} }() @@ -309,10 +309,9 @@ func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) { go func() { // We'll never get return for GET stats from sockRequest as of now, // just send request and see if panic or error would happen on daemon side. - _, _, err := sockRequest("GET", "/containers/"+name+"/stats", nil) - if err != nil { - c.Fatal(err) - } + status, _, err := sockRequest("GET", "/containers/"+name+"/stats", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) }() // allow some time to send request and let daemon deal with it @@ -340,11 +339,10 @@ func (s *DockerSuite) TestBuildApiDockerfilePath(c *check.C) { c.Fatalf("failed to close tar archive: %v", err) } - _, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") - if err == nil { - out, _ := readBody(body) - c.Fatalf("Build was supposed to fail: %s", out) - } + status, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) + out, err := readBody(body) if err != nil { c.Fatal(err) @@ -367,10 +365,10 @@ RUN find /tmp/`, } defer server.Close() - _, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") - if err != nil { - c.Fatalf("Build failed: %s", err) - } + status, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + buf, err := readBody(body) if err != nil { c.Fatal(err) @@ -395,11 +393,10 @@ RUN echo from dockerfile`, } defer git.Close() - _, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") - if err != nil { - buf, _ := readBody(body) - c.Fatalf("Build failed: %s\n%q", err, buf) - } + status, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + buf, err := readBody(body) if err != nil { c.Fatal(err) @@ -424,11 +421,10 @@ RUN echo from Dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - _, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") - if err != nil { - buf, _ := readBody(body) - c.Fatalf("Build failed: %s\n%q", err, buf) - } + status, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + buf, err := readBody(body) if err != nil { c.Fatal(err) @@ -454,10 +450,10 @@ RUN echo from dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - _, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") - if err != nil { - c.Fatalf("Build failed: %s", err) - } + status, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + buf, err := readBody(body) if err != nil { c.Fatal(err) @@ -487,11 +483,10 @@ func (s *DockerSuite) TestBuildApiDockerfileSymlink(c *check.C) { c.Fatalf("failed to close tar archive: %v", err) } - _, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") - if err == nil { - out, _ := readBody(body) - c.Fatalf("Build was supposed to fail: %s", out) - } + status, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) + out, err := readBody(body) if err != nil { c.Fatal(err) @@ -524,9 +519,9 @@ func (s *DockerSuite) TestPostContainerBindNormalVolume(c *check.C) { } bindSpec := map[string][]string{"Binds": {fooDir + ":/foo"}} - if status, _, err := sockRequest("POST", "/containers/two/start", bindSpec); err != nil && status != http.StatusNoContent { - c.Fatal(err) - } + status, _, err := sockRequest("POST", "/containers/two/start", bindSpec) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) fooDir2, err := inspectFieldMap("two", "Volumes", "/foo") if err != nil { @@ -548,9 +543,9 @@ func (s *DockerSuite) TestContainerApiPause(c *check.C) { } ContainerID := strings.TrimSpace(out) - if status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/pause", nil); err != nil && status != http.StatusNoContent { - c.Fatalf("POST a container pause: sockRequest failed: %v", err) - } + status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/pause", nil) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) pausedContainers, err := getSliceOfPausedContainers() @@ -562,9 +557,9 @@ func (s *DockerSuite) TestContainerApiPause(c *check.C) { c.Fatalf("there should be one paused container and not %d", len(pausedContainers)) } - if status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil); err != nil && status != http.StatusNoContent { - c.Fatalf("POST a container pause: sockRequest failed: %v", err) - } + status, _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) pausedContainers, err = getSliceOfPausedContainers() @@ -592,10 +587,10 @@ func (s *DockerSuite) TestContainerApiTop(c *check.C) { Processes [][]string } var top topResp - _, b, err := sockRequest("GET", "/containers/"+id+"/top?ps_args=aux", nil) - if err != nil { - c.Fatal(err) - } + status, b, err := sockRequest("GET", "/containers/"+id+"/top?ps_args=aux", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + if err := json.Unmarshal(b, &top); err != nil { c.Fatal(err) } @@ -626,10 +621,9 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { id := strings.TrimSpace(string(out)) name := "testcommit" + stringid.GenerateRandomID() - _, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+id, nil) - if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { - c.Fatal(err) - } + status, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+id, nil) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) type resp struct { Id string @@ -660,10 +654,10 @@ func (s *DockerSuite) TestContainerApiCreate(c *check.C) { "Cmd": []string{"/bin/sh", "-c", "touch /test && ls /test"}, } - _, b, err := sockRequest("POST", "/containers/create", config) - if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { - c.Fatal(err) - } + status, b, err := sockRequest("POST", "/containers/create", config) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) + type createResp struct { Id string } @@ -736,26 +730,21 @@ func (s *DockerSuite) TestContainerApiVerifyHeader(c *check.C) { } // Try with no content-type - _, body, err := create("") - if err == nil { - b, _ := readBody(body) - c.Fatalf("expected error when content-type is not set: %q", string(b)) - } + status, body, err := create("") + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) body.Close() + // Try with wrong content-type - _, body, err = create("application/xml") - if err == nil { - b, _ := readBody(body) - c.Fatalf("expected error when content-type is not set: %q", string(b)) - } + status, body, err = create("application/xml") + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) body.Close() // now application/json - _, body, err = create("application/json") - if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { - b, _ := readBody(body) - c.Fatalf("%v - %q", err, string(b)) - } + status, body, err = create("application/json") + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) body.Close() } @@ -786,11 +775,9 @@ func (s *DockerSuite) TestContainerApiPostCreateNull(c *check.C) { "NetworkDisabled":false, "OnBuild":null}` - _, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") - if err != nil && !strings.Contains(err.Error(), "200 OK: 201") { - b, _ := readBody(body) - c.Fatal(err, string(b)) - } + status, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) b, err := readBody(body) if err != nil { @@ -822,16 +809,14 @@ func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { "Memory": 524287 }` - _, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") + status, body, _ := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") b, err2 := readBody(body) if err2 != nil { c.Fatal(err2) } - if err == nil || !strings.Contains(string(b), "Minimum memory limit allowed is 4MB") { - c.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") - } - + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(strings.Contains(string(b), "Minimum memory limit allowed is 4MB"), check.Equals, true) } func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { @@ -847,13 +832,12 @@ func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { "Memory": 524287 }` - _, body, err := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json") + status, body, _ := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json") b, err2 := readBody(body) if err2 != nil { c.Fatal(err2) } - if err == nil || !strings.Contains(string(b), "Minimum memory limit allowed is 4MB") { - c.Errorf("Memory limit is smaller than the allowed limit. Container creation should've failed!") - } + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(strings.Contains(string(b), "Minimum memory limit allowed is 4MB"), check.Equals, true) } diff --git a/integration-cli/docker_api_exec_resize_test.go b/integration-cli/docker_api_exec_resize_test.go index 09a13bad2..ab753d8ec 100644 --- a/integration-cli/docker_api_exec_resize_test.go +++ b/integration-cli/docker_api_exec_resize_test.go @@ -18,10 +18,6 @@ func (s *DockerSuite) TestExecResizeApiHeightWidthNoInt(c *check.C) { endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar" status, _, err := sockRequest("POST", endpoint, nil) - if err == nil { - c.Fatal("Expected exec resize Request to fail") - } - if status != http.StatusInternalServerError { - c.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) - } + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) } diff --git a/integration-cli/docker_api_exec_test.go b/integration-cli/docker_api_exec_test.go index 4299f00ec..b7957480f 100644 --- a/integration-cli/docker_api_exec_test.go +++ b/integration-cli/docker_api_exec_test.go @@ -5,6 +5,7 @@ package main import ( "bytes" "fmt" + "net/http" "os/exec" "github.com/go-check/check" @@ -18,8 +19,11 @@ func (s *DockerSuite) TestExecApiCreateNoCmd(c *check.C) { c.Fatal(out, err) } - _, body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}) - if err == nil || !bytes.Contains(body, []byte("No exec command specified")) { - c.Fatalf("Expected error when creating exec command with no Cmd specified: %q", err) + status, body, err := sockRequest("POST", fmt.Sprintf("/containers/%s/exec", name), map[string]interface{}{"Cmd": nil}) + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) + + if !bytes.Contains(body, []byte("No exec command specified")) { + c.Fatalf("Expected message when creating exec command with no Cmd specified") } } diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index e22f67d2b..a0f029d40 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "net/http" "net/url" "os/exec" "strings" @@ -11,10 +12,9 @@ import ( ) func (s *DockerSuite) TestLegacyImages(c *check.C) { - _, body, err := sockRequest("GET", "/v1.6/images/json", nil) - if err != nil { - c.Fatalf("Error on GET: %s", err) - } + status, body, err := sockRequest("GET", "/v1.6/images/json", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) images := []types.LegacyImage{} if err = json.Unmarshal(body, &images); err != nil { @@ -40,10 +40,10 @@ func (s *DockerSuite) TestApiImagesFilter(c *check.C) { getImages := func(filter string) []image { v := url.Values{} v.Set("filter", filter) - _, b, err := sockRequest("GET", "/images/json?"+v.Encode(), nil) - if err != nil { - c.Fatal(err) - } + status, b, err := sockRequest("GET", "/images/json?"+v.Encode(), nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + var images []image if err := json.Unmarshal(b, &images); err != nil { c.Fatal(err) @@ -76,20 +76,20 @@ func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { id := strings.TrimSpace(out) defer deleteImages("saveandload") - _, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") - if err != nil { - c.Fatal(err) - } + status, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + defer body.Close() if out, err := exec.Command(dockerBinary, "rmi", id).CombinedOutput(); err != nil { c.Fatal(err, out) } - _, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar") - if err != nil { - c.Fatal(err) - } + status, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar") + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + defer loadBody.Close() inspectOut, err := exec.Command(dockerBinary, "inspect", "--format='{{ .Id }}'", id).CombinedOutput() diff --git a/integration-cli/docker_api_info_test.go b/integration-cli/docker_api_info_test.go index 67967ab2a..408428910 100644 --- a/integration-cli/docker_api_info_test.go +++ b/integration-cli/docker_api_info_test.go @@ -10,10 +10,9 @@ import ( func (s *DockerSuite) TestInfoApi(c *check.C) { endpoint := "/info" - statusCode, body, err := sockRequest("GET", endpoint, nil) - if err != nil || statusCode != http.StatusOK { - c.Fatalf("Expected %d from info request, got %d", http.StatusOK, statusCode) - } + status, body, err := sockRequest("GET", endpoint, nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) // always shown fields stringsToCheck := []string{ diff --git a/integration-cli/docker_api_inspect_test.go b/integration-cli/docker_api_inspect_test.go index 982962261..b90bdc712 100644 --- a/integration-cli/docker_api_inspect_test.go +++ b/integration-cli/docker_api_inspect_test.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "net/http" "os/exec" "strings" @@ -26,10 +27,9 @@ func (s *DockerSuite) TestInspectApiContainerResponse(c *check.C) { if testVersion != "latest" { endpoint = "/" + testVersion + endpoint } - _, body, err := sockRequest("GET", endpoint, nil) - if err != nil { - c.Fatalf("sockRequest failed for %s version: %v", testVersion, err) - } + status, body, err := sockRequest("GET", endpoint, nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) var inspectJSON map[string]interface{} if err = json.Unmarshal(body, &inspectJSON); err != nil { diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go index bbf9d17cb..bf0e1fbf4 100644 --- a/integration-cli/docker_api_logs_test.go +++ b/integration-cli/docker_api_logs_test.go @@ -17,11 +17,9 @@ func (s *DockerSuite) TestLogsApiWithStdout(c *check.C) { c.Fatal(out, err) } - statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", name), nil) - - if err != nil || statusCode != http.StatusOK { - c.Fatalf("Expected %d from logs request, got %d", http.StatusOK, statusCode) - } + status, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", name), nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) if !bytes.Contains(body, []byte(name)) { c.Fatalf("Expected %s, got %s", name, string(body[:])) @@ -35,11 +33,9 @@ func (s *DockerSuite) TestLogsApiNoStdoutNorStderr(c *check.C) { c.Fatal(out, err) } - statusCode, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs", name), nil) - - if err == nil || statusCode != http.StatusBadRequest { - c.Fatalf("Expected %d from logs request, got %d", http.StatusBadRequest, statusCode) - } + status, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs", name), nil) + c.Assert(status, check.Equals, http.StatusBadRequest) + c.Assert(err, check.IsNil) expected := "Bad parameters: you must choose at least one stream" if !bytes.Contains(body, []byte(expected)) { diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go index 6f5019b6d..6d5528069 100644 --- a/integration-cli/docker_api_resize_test.go +++ b/integration-cli/docker_api_resize_test.go @@ -17,10 +17,9 @@ func (s *DockerSuite) TestResizeApiResponse(c *check.C) { cleanedContainerID := strings.TrimSpace(out) endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" - _, _, err = sockRequest("POST", endpoint, nil) - if err != nil { - c.Fatalf("resize Request failed %v", err) - } + status, _, err := sockRequest("POST", endpoint, nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) } func (s *DockerSuite) TestResizeApiHeightWidthNoInt(c *check.C) { @@ -33,12 +32,8 @@ func (s *DockerSuite) TestResizeApiHeightWidthNoInt(c *check.C) { endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar" status, _, err := sockRequest("POST", endpoint, nil) - if err == nil { - c.Fatal("Expected resize Request to fail") - } - if status != http.StatusInternalServerError { - c.Fatalf("Status expected %d, got %d", http.StatusInternalServerError, status) - } + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) } func (s *DockerSuite) TestResizeApiResponseWhenContainerNotStarted(c *check.C) { @@ -57,10 +52,10 @@ func (s *DockerSuite) TestResizeApiResponseWhenContainerNotStarted(c *check.C) { } endpoint := "/containers/" + cleanedContainerID + "/resize?h=40&w=40" - _, body, err := sockRequest("POST", endpoint, nil) - if err == nil { - c.Fatalf("resize should fail when container is not started") - } + status, body, err := sockRequest("POST", endpoint, nil) + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(err, check.IsNil) + if !strings.Contains(string(body), "Cannot resize container") && !strings.Contains(string(body), cleanedContainerID) { c.Fatalf("resize should fail with message 'Cannot resize container' but instead received %s", string(body)) } diff --git a/integration-cli/docker_api_version_test.go b/integration-cli/docker_api_version_test.go index d1ea6b9f8..b756794c2 100644 --- a/integration-cli/docker_api_version_test.go +++ b/integration-cli/docker_api_version_test.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "net/http" "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" @@ -9,10 +10,10 @@ import ( ) func (s *DockerSuite) TestGetVersion(c *check.C) { - _, body, err := sockRequest("GET", "/version", nil) - if err != nil { - c.Fatal(err) - } + status, body, err := sockRequest("GET", "/version", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + var v types.Version if err := json.Unmarshal(body, &v); err != nil { c.Fatal(err) diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index 8668bc70b..c330bb76a 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -60,13 +60,8 @@ func (s *DockerSuite) TestRmRunningContainerCheckError409(c *check.C) { endpoint := "/containers/foo" status, _, err := sockRequest("DELETE", endpoint, nil) - - if err == nil { - c.Fatalf("Expected error, can't rm a running container") - } else if status != http.StatusConflict { - c.Fatalf("Expected error to contain '409 Conflict' but found %s", err) - } - + c.Assert(status, check.Equals, http.StatusConflict) + c.Assert(err, check.IsNil) } func (s *DockerSuite) TestRmForceRemoveRunningContainer(c *check.C) { diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 5e1540484..20e87244e 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -350,9 +350,6 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io defer client.Close() return resp.Body.Close() }) - if resp.StatusCode != http.StatusOK { - return resp.StatusCode, body, fmt.Errorf("received status != 200 OK: %s", resp.Status) - } return resp.StatusCode, body, err } @@ -1062,10 +1059,9 @@ func daemonTime(c *check.C) time.Time { return time.Now() } - _, body, err := sockRequest("GET", "/info", nil) - if err != nil { - c.Fatalf("daemonTime: failed to get /info: %v", err) - } + status, body, err := sockRequest("GET", "/info", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) type infoJSON struct { SystemTime string diff --git a/integration-cli/requirements.go b/integration-cli/requirements.go index 7499fc506..cc451bd88 100644 --- a/integration-cli/requirements.go +++ b/integration-cli/requirements.go @@ -58,8 +58,8 @@ var ( func() bool { if daemonExecDriver == "" { // get daemon info - _, body, err := sockRequest("GET", "/info", nil) - if err != nil { + status, body, err := sockRequest("GET", "/info", nil) + if err != nil || status != http.StatusOK { log.Fatalf("sockRequest failed for /info: %v", err) } From 47e5acfbaefc45e536b953af6bf8a3993669c816 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Tue, 14 Apr 2015 08:38:34 +0800 Subject: [PATCH 615/999] add devices cgroup check and errors Signed-off-by: Qiang Huang --- pkg/sysinfo/sysinfo.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 76a61fa95..0c1ae8743 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -58,5 +58,11 @@ func New(quiet bool) *SysInfo { } else { sysInfo.AppArmor = true } + + // Check if Devices cgroup is mounted, it is hard requirement for container security. + if _, err := cgroups.FindCgroupMountpoint("devices"); err != nil { + logrus.Fatalf("Error mounting devices cgroup: %v", err) + } + return sysInfo } From 66acef865d7504605572b0d9566d9e250cf19bc2 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Mon, 13 Apr 2015 22:33:52 +0800 Subject: [PATCH 616/999] clean up viz code Signed-off-by: Ma Shimiao --- api/client/images.go | 261 ++++++++++--------------------------------- api/server/server.go | 10 -- 2 files changed, 57 insertions(+), 214 deletions(-) diff --git a/api/client/images.go b/api/client/images.go index 32440d48d..e39c47374 100644 --- a/api/client/images.go +++ b/api/client/images.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net/url" - "strings" "text/tabwriter" "time" @@ -18,74 +17,6 @@ import ( "github.com/docker/docker/utils" ) -// FIXME: --viz and --tree are deprecated. Remove them in a future version. -func (cli *DockerCli) walkTree(noTrunc bool, images []*types.Image, byParent map[string][]*types.Image, prefix string, printNode func(cli *DockerCli, noTrunc bool, image *types.Image, prefix string)) { - length := len(images) - if length > 1 { - for index, image := range images { - if index+1 == length { - printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.ID]; exists { - cli.walkTree(noTrunc, subimages, byParent, prefix+" ", printNode) - } - } else { - printNode(cli, noTrunc, image, prefix+"\u251C─") - if subimages, exists := byParent[image.ID]; exists { - cli.walkTree(noTrunc, subimages, byParent, prefix+"\u2502 ", printNode) - } - } - } - } else { - for _, image := range images { - printNode(cli, noTrunc, image, prefix+"└─") - if subimages, exists := byParent[image.ID]; exists { - cli.walkTree(noTrunc, subimages, byParent, prefix+" ", printNode) - } - } - } -} - -// FIXME: --viz and --tree are deprecated. Remove them in a future version. -func (cli *DockerCli) printVizNode(noTrunc bool, image *types.Image, prefix string) { - var ( - imageID string - parentID string - ) - if noTrunc { - imageID = image.ID - parentID = image.ParentId - } else { - imageID = stringid.TruncateID(image.ID) - parentID = stringid.TruncateID(image.ParentId) - } - if parentID == "" { - fmt.Fprintf(cli.out, " base -> \"%s\" [style=invis]\n", imageID) - } else { - fmt.Fprintf(cli.out, " \"%s\" -> \"%s\"\n", parentID, imageID) - } - if image.RepoTags[0] != ":" { - fmt.Fprintf(cli.out, " \"%s\" [label=\"%s\\n%s\",shape=box,fillcolor=\"paleturquoise\",style=\"filled,rounded\"];\n", - imageID, imageID, strings.Join(image.RepoTags, "\\n")) - } -} - -// FIXME: --viz and --tree are deprecated. Remove them in a future version. -func (cli *DockerCli) printTreeNode(noTrunc bool, image *types.Image, prefix string) { - var imageID string - if noTrunc { - imageID = image.ID - } else { - imageID = stringid.TruncateID(image.ID) - } - - fmt.Fprintf(cli.out, "%s%s Virtual Size: %s", prefix, imageID, units.HumanSize(float64(image.VirtualSize))) - if image.RepoTags[0] != ":" { - fmt.Fprintf(cli.out, " Tags: %s\n", strings.Join(image.RepoTags, ", ")) - } else { - fmt.Fprint(cli.out, "\n") - } -} - // CmdImages lists the images in a specified repository, or all top-level images if no repository is specified. // // Usage: docker images [OPTIONS] [REPOSITORY] @@ -95,9 +26,6 @@ func (cli *DockerCli) CmdImages(args ...string) error { all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)") noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output") showDigests := cmd.Bool([]string{"-digests"}, false, "Show digests") - // 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") flFilter := opts.NewListOpts(nil) cmd.Var(&flFilter, []string{"f", "-filter"}, "Filter output based on conditions provided") @@ -116,158 +44,83 @@ func (cli *DockerCli) CmdImages(args ...string) error { } matchName := cmd.Arg(0) - // FIXME: --viz and --tree are deprecated. Remove them in a future version. - if *flViz || *flTree { - v := url.Values{ - "all": []string{"1"}, - } - if len(imageFilterArgs) > 0 { - filterJSON, err := filters.ToParam(imageFilterArgs) - if err != nil { - return err - } - v.Set("filters", filterJSON) - } - - rdr, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil) + v := url.Values{} + if len(imageFilterArgs) > 0 { + filterJSON, err := filters.ToParam(imageFilterArgs) if err != nil { return err } + v.Set("filters", filterJSON) + } - images := []types.Image{} - err = json.NewDecoder(rdr).Decode(&images) - if err != nil { - return err - } + if cmd.NArg() == 1 { + // FIXME rename this parameter, to not be confused with the filters flag + v.Set("filter", matchName) + } + if *all { + v.Set("all", "1") + } - var ( - printNode func(cli *DockerCli, noTrunc bool, image *types.Image, prefix string) - startImage *types.Image + rdr, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil) + if err != nil { + return err + } - roots = []*types.Image{} - byParent = make(map[string][]*types.Image) - ) + images := []types.Image{} + if err := json.NewDecoder(rdr).Decode(&images); err != nil { + return err + } - for _, image := range images { - if image.ParentId == "" { - roots = append(roots, &image) - } else { - if children, exists := byParent[image.ParentId]; exists { - children = append(children, &image) - } else { - byParent[image.ParentId] = []*types.Image{&image} - } - } - - if matchName != "" { - if matchName == image.ID || matchName == stringid.TruncateID(image.ID) { - startImage = &image - } - - for _, repotag := range image.RepoTags { - if repotag == matchName { - startImage = &image - } - } - } - } - - if *flViz { - fmt.Fprintf(cli.out, "digraph docker {\n") - printNode = (*DockerCli).printVizNode + w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) + if !*quiet { + if *showDigests { + fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE") } else { - printNode = (*DockerCli).printTreeNode + fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") + } + } + + for _, image := range images { + ID := image.ID + if !*noTrunc { + ID = stringid.TruncateID(ID) } - if startImage != nil { - root := []*types.Image{startImage} - cli.walkTree(*noTrunc, root, byParent, "", printNode) - } else if matchName == "" { - cli.walkTree(*noTrunc, roots, byParent, "", printNode) - } - if *flViz { - fmt.Fprintf(cli.out, " base [style=invisible]\n}\n") - } - } else { - v := url.Values{} - if len(imageFilterArgs) > 0 { - filterJSON, err := filters.ToParam(imageFilterArgs) - if err != nil { - return err - } - v.Set("filters", filterJSON) + repoTags := image.RepoTags + repoDigests := image.RepoDigests + + if len(repoTags) == 1 && repoTags[0] == ":" && len(repoDigests) == 1 && repoDigests[0] == "@" { + // dangling image - clear out either repoTags or repoDigsts so we only show it once below + repoDigests = []string{} } - if cmd.NArg() == 1 { - // FIXME rename this parameter, to not be confused with the filters flag - v.Set("filter", matchName) - } - if *all { - v.Set("all", "1") - } - - rdr, _, err := cli.call("GET", "/images/json?"+v.Encode(), nil, nil) - if err != nil { - return err - } - - images := []types.Image{} - err = json.NewDecoder(rdr).Decode(&images) - if err != nil { - return err - } - - w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) - if !*quiet { - if *showDigests { - fmt.Fprintln(w, "REPOSITORY\tTAG\tDIGEST\tIMAGE ID\tCREATED\tVIRTUAL SIZE") + // combine the tags and digests lists + tagsAndDigests := append(repoTags, repoDigests...) + for _, repoAndRef := range tagsAndDigests { + repo, ref := parsers.ParseRepositoryTag(repoAndRef) + // default tag and digest to none - if there's a value, it'll be set below + tag := "" + digest := "" + if utils.DigestReference(ref) { + digest = ref } else { - fmt.Fprintln(w, "REPOSITORY\tTAG\tIMAGE ID\tCREATED\tVIRTUAL SIZE") - } - } - - for _, image := range images { - ID := image.ID - if !*noTrunc { - ID = stringid.TruncateID(ID) + tag = ref } - repoTags := image.RepoTags - repoDigests := image.RepoDigests - - if len(repoTags) == 1 && repoTags[0] == ":" && len(repoDigests) == 1 && repoDigests[0] == "@" { - // dangling image - clear out either repoTags or repoDigsts so we only show it once below - repoDigests = []string{} - } - - // combine the tags and digests lists - tagsAndDigests := append(repoTags, repoDigests...) - for _, repoAndRef := range tagsAndDigests { - repo, ref := parsers.ParseRepositoryTag(repoAndRef) - // default tag and digest to none - if there's a value, it'll be set below - tag := "" - digest := "" - if utils.DigestReference(ref) { - digest = ref + if !*quiet { + if *showDigests { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) } else { - tag = ref - } - - if !*quiet { - if *showDigests { - fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s ago\t%s\n", repo, tag, digest, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) - } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) - } - } else { - fmt.Fprintln(w, ID) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\n", repo, tag, ID, units.HumanDuration(time.Now().UTC().Sub(time.Unix(int64(image.Created), 0))), units.HumanSize(float64(image.VirtualSize))) } + } else { + fmt.Fprintln(w, ID) } } + } - if !*quiet { - w.Flush() - } + if !*quiet { + w.Flush() } return nil } diff --git a/api/server/server.go b/api/server/server.go index 830e731a7..b5f978e84 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -406,15 +406,6 @@ func (s *Server) getImagesJSON(eng *engine.Engine, version version.Version, w ht return writeJSON(w, http.StatusOK, legacyImages) } -func (s *Server) getImagesViz(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - if version.GreaterThan("1.6") { - w.WriteHeader(http.StatusNotFound) - return fmt.Errorf("This is now implemented in the client.") - } - eng.ServeHTTP(w, r) - return nil -} - func (s *Server) getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") @@ -1588,7 +1579,6 @@ func createRouter(s *Server, eng *engine.Engine) *mux.Router { "/info": s.getInfo, "/version": s.getVersion, "/images/json": s.getImagesJSON, - "/images/viz": s.getImagesViz, "/images/search": s.getImagesSearch, "/images/get": s.getImagesGet, "/images/{name:.*}/get": s.getImagesGet, From 667b1e220cf82fb77fd776426a4b712ae5fee0ae Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Wed, 15 Apr 2015 08:16:00 +0800 Subject: [PATCH 617/999] simplify memory limit check If memory cgroup is mounted, memory limit is always supported, no need to check if these files are exist. Signed-off-by: Qiang Huang --- pkg/sysinfo/sysinfo.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index 0c1ae8743..195a03e9a 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -23,20 +23,16 @@ func New(quiet bool) *SysInfo { sysInfo := &SysInfo{} if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil { if !quiet { - logrus.Warnf("%v", err) + logrus.Warnf("Your kernel does not support cgroup memory limit: %v", err) } } else { - _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.limit_in_bytes")) - _, err2 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.soft_limit_in_bytes")) - sysInfo.MemoryLimit = err1 == nil && err2 == nil - if !sysInfo.MemoryLimit && !quiet { - logrus.Warn("Your kernel does not support cgroup memory limit.") - } + // If memory cgroup is mounted, MemoryLimit is always enabled. + sysInfo.MemoryLimit = true - _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) - sysInfo.SwapLimit = err == nil + _, err1 := ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.memsw.limit_in_bytes")) + sysInfo.SwapLimit = err1 == nil if !sysInfo.SwapLimit && !quiet { - logrus.Warn("Your kernel does not support cgroup swap limit.") + logrus.Warn("Your kernel does not support swap memory limit.") } } From 5f4fb8be006c0ffeff2671e5752111e543e07d9f Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Fri, 24 Apr 2015 08:54:08 +0800 Subject: [PATCH 618/999] Add cpu cfs quota to build Signed-off-by: Lei Jitang --- api/client/build.go | 2 ++ api/server/server.go | 1 + builder/evaluator.go | 1 + builder/internals.go | 1 + builder/job.go | 2 ++ contrib/completion/bash/docker | 2 +- docs/man/docker-build.1.md | 1 + integration-cli/docker_cli_build_test.go | 15 ++++++++------- 8 files changed, 17 insertions(+), 8 deletions(-) diff --git a/api/client/build.go b/api/client/build.go index 63cc63bc9..fb022e38d 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -55,6 +55,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit") flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap") flCPUShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") + flCpuQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota") flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") @@ -281,6 +282,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v.Set("cpusetcpus", *flCPUSetCpus) v.Set("cpusetmems", *flCPUSetMems) v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10)) + v.Set("cpuquota", strconv.FormatInt(*flCpuQuota, 10)) v.Set("memory", strconv.FormatInt(memory, 10)) v.Set("memswap", strconv.FormatInt(memorySwap, 10)) diff --git a/api/server/server.go b/api/server/server.go index 830e731a7..cdc6c1815 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1343,6 +1343,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R buildConfig.MemorySwap = int64Value(r, "memswap") buildConfig.Memory = int64Value(r, "memory") buildConfig.CpuShares = int64Value(r, "cpushares") + buildConfig.CpuQuota = int64Value(r, "cpuquota") buildConfig.CpuSetCpus = r.FormValue("cpusetcpus") buildConfig.CpuSetMems = r.FormValue("cpusetmems") diff --git a/builder/evaluator.go b/builder/evaluator.go index 2f9d4ff85..9a2b57a8f 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -124,6 +124,7 @@ type Builder struct { cpuSetCpus string cpuSetMems string cpuShares int64 + cpuQuota int64 memory int64 memorySwap int64 diff --git a/builder/internals.go b/builder/internals.go index 731ca84fe..ba7d45bcb 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -547,6 +547,7 @@ func (b *Builder) create() (*daemon.Container, error) { hostConfig := &runconfig.HostConfig{ CpuShares: b.cpuShares, + CpuQuota: b.cpuQuota, CpusetCpus: b.cpuSetCpus, CpusetMems: b.cpuSetMems, Memory: b.memory, diff --git a/builder/job.go b/builder/job.go index 115d89a4b..acffa8b46 100644 --- a/builder/job.go +++ b/builder/job.go @@ -49,6 +49,7 @@ type Config struct { Memory int64 MemorySwap int64 CpuShares int64 + CpuQuota int64 CpuSetCpus string CpuSetMems string AuthConfig *cliconfig.AuthConfig @@ -169,6 +170,7 @@ func Build(d *daemon.Daemon, buildConfig *Config) error { ConfigFile: buildConfig.ConfigFile, dockerfileName: buildConfig.DockerfileName, cpuShares: buildConfig.CpuShares, + cpuQuota: buildConfig.CpuQuota, cpuSetCpus: buildConfig.CpuSetCpus, cpuSetMems: buildConfig.CpuSetMems, memory: buildConfig.Memory, diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 7f87e50f5..f3b833158 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -279,7 +279,7 @@ _docker_build() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--cpu-shares -c --cpuset-cpus --file -f --force-rm --help --memory -m --memory-swap --no-cache --pull --quiet -q --rm --tag -t" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--cpu-shares -c --cpuset-cpus --cpu-quota --file -f --force-rm --help --memory -m --memory-swap --no-cache --pull --quiet -q --rm --tag -t" -- "$cur" ) ) ;; *) local counter="$(__docker_pos_first_nonflag '--tag|-t')" diff --git a/docs/man/docker-build.1.md b/docs/man/docker-build.1.md index fe6250fc1..4a8eba67d 100644 --- a/docs/man/docker-build.1.md +++ b/docs/man/docker-build.1.md @@ -17,6 +17,7 @@ docker-build - Build a new image from the source code at PATH [**-m**|**--memory**[=*MEMORY*]] [**--memory-swap**[=*MEMORY-SWAP*]] [**-c**|**--cpu-shares**[=*0*]] +[**--cpu-quota**[=*0*]] [**--cpuset-cpus**[=*CPUSET-CPUS*]] PATH | URL | - diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 70e4ba114..72d15c177 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5371,7 +5371,7 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { c.Fatal(err) } - cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpuset-mems=0", "--cpu-shares=100", "-t", name, ".") + cmd := exec.Command(dockerBinary, "build", "--no-cache", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=0", "--cpuset-mems=0", "--cpu-shares=100", "--cpu-quota=8000", "-t", name, ".") cmd.Dir = ctx.Dir out, _, err := runCommandWithOutput(cmd) @@ -5388,6 +5388,7 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { CpusetCpus string CpusetMems string CpuShares int64 + CpuQuota int64 } cfg, err := inspectFieldJSON(cID, "HostConfig") @@ -5399,9 +5400,9 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { if err := json.Unmarshal([]byte(cfg), &c1); err != nil { c.Fatal(err, cfg) } - if c1.Memory != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 { - c.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", - c1.Memory, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares) + if c1.Memory != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "0" || c1.CpusetMems != "0" || c1.CpuShares != 100 || c1.CpuQuota != 8000 { + c.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d, CpuQuota: %d", + c1.Memory, c1.MemorySwap, c1.CpusetCpus, c1.CpusetMems, c1.CpuShares, c1.CpuQuota) } // Make sure constraints aren't saved to image @@ -5415,9 +5416,9 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { if err := json.Unmarshal([]byte(cfg), &c2); err != nil { c.Fatal(err, cfg) } - if c2.Memory == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 { - c.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d", - c2.Memory, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares) + if c2.Memory == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "0" || c2.CpusetMems == "0" || c2.CpuShares == 100 || c2.CpuQuota == 8000 { + c.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpusetMems: %s, CpuShares: %d, CpuQuota: %d", + c2.Memory, c2.MemorySwap, c2.CpusetCpus, c2.CpusetMems, c2.CpuShares, c2.CpuQuota) } } From 493437616d1aabef50768d71ce786595f7295554 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 20 Apr 2015 10:36:52 +1000 Subject: [PATCH 619/999] make space for DHE menu item again Signed-off-by: Sven Dowideit --- docs/mkdocs.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index df9d95997..c62e589f2 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -195,17 +195,17 @@ pages: # Project: - ['project/index.md', '**HIDDEN**'] -- ['project/who-written-for.md', 'Contributor Guide', 'README first'] -- ['project/software-required.md', 'Contributor Guide', 'Get required software'] -- ['project/set-up-git.md', 'Contributor Guide', 'Configure Git for contributing'] -- ['project/set-up-dev-env.md', 'Contributor Guide', 'Work with a development container'] -- ['project/test-and-docs.md', 'Contributor Guide', 'Run tests and test documentation'] -- ['project/make-a-contribution.md', 'Contributor Guide', 'Understand contribution workflow'] -- ['project/find-an-issue.md', 'Contributor Guide', 'Find an issue'] -- ['project/work-issue.md', 'Contributor Guide', 'Work on an issue'] -- ['project/create-pr.md', 'Contributor Guide', 'Create a pull request'] -- ['project/review-pr.md', 'Contributor Guide', 'Participate in the PR review'] -- ['project/advanced-contributing.md', 'Contributor Guide', 'Advanced contributing'] -- ['project/get-help.md', 'Contributor Guide', 'Where to get help'] -- ['project/coding-style.md', 'Contributor Guide', 'Coding style guide'] -- ['project/doc-style.md', 'Contributor Guide', 'Documentation style guide'] +- ['project/who-written-for.md', 'Contribute', 'README first'] +- ['project/software-required.md', 'Contribute', 'Get required software'] +- ['project/set-up-git.md', 'Contribute', 'Configure Git for contributing'] +- ['project/set-up-dev-env.md', 'Contribute', 'Work with a development container'] +- ['project/test-and-docs.md', 'Contribute', 'Run tests and test documentation'] +- ['project/make-a-contribution.md', 'Contribute', 'Understand contribution workflow'] +- ['project/find-an-issue.md', 'Contribute', 'Find an issue'] +- ['project/work-issue.md', 'Contribute', 'Work on an issue'] +- ['project/create-pr.md', 'Contribute', 'Create a pull request'] +- ['project/review-pr.md', 'Contribute', 'Participate in the PR review'] +- ['project/advanced-contributing.md', 'Contribute', 'Advanced contributing'] +- ['project/get-help.md', 'Contribute', 'Where to get help'] +- ['project/coding-style.md', 'Contribute', 'Coding style guide'] +- ['project/doc-style.md', 'Contribute', 'Documentation style guide'] From b0ad95daa84354c4b41679d0182fafbd259e5a69 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 16 Apr 2015 15:04:47 +1000 Subject: [PATCH 620/999] Copy over the DHE documentation for release to docs.docker.com Signed-off-by: Sven Dowideit --- docs/mkdocs.yml | 12 +- .../docker-hub-enterprise/admin-metrics.png | Bin 0 -> 60141 bytes .../admin-settings-http.png | Bin 0 -> 24230 bytes docs/sources/docker-hub-enterprise/admin.png | Bin 0 -> 66488 bytes .../docker-hub-enterprise/adminguide.md | 103 ++++++ .../assets/admin-logs.png | Bin 0 -> 161230 bytes .../assets/admin-metrics.png | Bin 0 -> 74062 bytes .../admin-settings-authentication-basic.png | Bin 0 -> 23600 bytes .../admin-settings-authentication-ldap.png | Bin 0 -> 25353 bytes .../assets/admin-settings-authentication.png | Bin 0 -> 13472 bytes .../assets/admin-settings-http-unlicensed.png | Bin 0 -> 21971 bytes .../assets/admin-settings-http.png | Bin 0 -> 20618 bytes .../assets/admin-settings-license.png | Bin 0 -> 14936 bytes .../assets/admin-settings-security.png | Bin 0 -> 21807 bytes .../assets/admin-settings-storage.png | Bin 0 -> 41579 bytes .../assets/admin-settings.png | Bin 0 -> 26041 bytes .../assets/console-pull.png | Bin 0 -> 35398 bytes .../assets/console-push.png | Bin 0 -> 49729 bytes ...b-org-enterprise-license-CSDE-dropdown.png | Bin 0 -> 28680 bytes .../docker-hub-org-enterprise-license.png | Bin 0 -> 27642 bytes .../assets/jenkins-plugins.png | Bin 0 -> 45860 bytes .../assets/jenkins-ui.png | Bin 0 -> 42805 bytes .../docker-hub-enterprise/configuration.md | 311 +++++++++++++++++ docs/sources/docker-hub-enterprise/index.md | 50 +++ .../docker-hub-enterprise/install-config.md | 8 - docs/sources/docker-hub-enterprise/install.md | 312 ++++++++++++++++++ .../docker-hub-enterprise/quick-start.md | 308 +++++++++++++++++ docs/sources/docker-hub-enterprise/support.md | 14 + docs/sources/docker-hub-enterprise/usage.md | 9 - .../docker-hub-enterprise/userguide.md | 130 ++++++++ 30 files changed, 1236 insertions(+), 21 deletions(-) create mode 100644 docs/sources/docker-hub-enterprise/admin-metrics.png create mode 100644 docs/sources/docker-hub-enterprise/admin-settings-http.png create mode 100644 docs/sources/docker-hub-enterprise/admin.png create mode 100644 docs/sources/docker-hub-enterprise/adminguide.md create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-logs.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-metrics.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-authentication-basic.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-authentication-ldap.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-authentication.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-http-unlicensed.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-http.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-license.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-security.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings-storage.png create mode 100644 docs/sources/docker-hub-enterprise/assets/admin-settings.png create mode 100755 docs/sources/docker-hub-enterprise/assets/console-pull.png create mode 100755 docs/sources/docker-hub-enterprise/assets/console-push.png create mode 100644 docs/sources/docker-hub-enterprise/assets/docker-hub-org-enterprise-license-CSDE-dropdown.png create mode 100644 docs/sources/docker-hub-enterprise/assets/docker-hub-org-enterprise-license.png create mode 100755 docs/sources/docker-hub-enterprise/assets/jenkins-plugins.png create mode 100755 docs/sources/docker-hub-enterprise/assets/jenkins-ui.png create mode 100644 docs/sources/docker-hub-enterprise/configuration.md create mode 100644 docs/sources/docker-hub-enterprise/index.md delete mode 100644 docs/sources/docker-hub-enterprise/install-config.md create mode 100644 docs/sources/docker-hub-enterprise/install.md create mode 100644 docs/sources/docker-hub-enterprise/quick-start.md create mode 100644 docs/sources/docker-hub-enterprise/support.md delete mode 100644 docs/sources/docker-hub-enterprise/usage.md create mode 100644 docs/sources/docker-hub-enterprise/userguide.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index c62e589f2..e425175f6 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -78,10 +78,14 @@ pages: - ['docker-hub/builds.md', 'Docker Hub', 'Automated Builds'] - ['docker-hub/official_repos.md', 'Docker Hub', 'Official repo guidelines'] -# Docker Hub Enterprise -#- ['docker-hub-enterprise/index.md', '**HIDDEN**' ] -#- ['docker-hub-enterprise/install-config.md', 'Docker Hub Enterprise', 'Installation and Configuration' ] -#- ['docker-hub-enterprise/usage.md', 'Docker Hub Enterprise', 'User Guide' ] +# Docker Hub Enterprise: +- ['docker-hub-enterprise/index.md', 'Docker Hub Enterprise', 'Overview' ] +- ['docker-hub-enterprise/quick-start.md', 'Docker Hub Enterprise', 'Quick Start: Basic Workflow' ] +- ['docker-hub-enterprise/userguide.md', 'Docker Hub Enterprise', 'User Guide' ] +- ['docker-hub-enterprise/adminguide.md', 'Docker Hub Enterprise', 'Admin Guide' ] +- ['docker-hub-enterprise/install.md', 'Docker Hub Enterprise', '  Installation' ] +- ['docker-hub-enterprise/configuration.md', 'Docker Hub Enterprise', '  Configuration options' ] +- ['docker-hub-enterprise/support.md', 'Docker Hub Enterprise', 'Support' ] # Examples: - ['examples/index.md', '**HIDDEN**'] diff --git a/docs/sources/docker-hub-enterprise/admin-metrics.png b/docs/sources/docker-hub-enterprise/admin-metrics.png new file mode 100644 index 0000000000000000000000000000000000000000..21a8f74a7cab9ecad0125ba4f795ce03c3837bab GIT binary patch literal 60141 zcmdqI1y>wF+bs$qKnNBBgy0DTcZWeH1_XC^cXyi!A-Dy1cXxMpcLsNN*VDZ3`R=-Z z;GVnIVKLCtR^9bf?ML=5e`!f!^tS|W;o#uVMSlI1g@b#g3^9Bjn^9uHx82IWiX9vrD)!3<9xgudBOKf(IFX;<!W%Lm@@8*K5N8Z?Ds3@?tIS7cEfS|>f3{HaZbf{N&Cj4G~VyNzxn)^ zIVi7&?$cjj+a@#YH^s|_4l|1K%b|Bj5&t*usJL)M#Nc{PO6`)z@&n??{w^z{CQ(-u? z*A?&A5%;>V_{_zoCr283x0&kpt?DnmL76V&(odnjdCkJqGF9Io&I!+1YPM6E5AW0I z(ZuYTIXR;JcTXS8zrL3-G$G~bOm(x%A!}l^&6th&L--vYd!Tle_mP& z0%rEp{4|87kp*AhzG16l%Zu==E-7w_7hixCF(P&^=PMjT8xw=4bZoc*jy@IAQL$1I z2CUD$k%=f~KkxbYj}MacNCH-{WZI`CRMyn&;T7CGm_2@#+I_gbt%bKXhTB3EHAblL zVeQ{PIcwn$E?{r0mrKi2@4B`1Wt?e%*?;_ z^&mbHOwNSh&d#4m7(~5(Smb1yG{mL})fO-z85t}E1qF6a&h$v#t-&}QEje}d!EA|y z*%~WS$sFv0DN$M3_jPr38{6A@tPTr`+VaHT^Kx^w*SmuwV`Ai#lzeCQyUaQDYG5Mb z;$QgrliIwHBN7vd9KoI}_i7gm!Wyk-t{#&ICFu8_2Y!9}>BY_P5(r9?0)|TbZQNNV z)D?(Dx|jhOy!!C^UGvV*jWXh{*HmyHj*4|$ZXuM}J-+IYl8KI}loBHu5s3yDm}!!E zfx}s7@MdCMQfGslaM<3PXTgl$bpuW(J7-DU9A8jYsH9wn5(NR z(-bRf7LHg9qru^PwbGaSM+hw~?H};Lc9Ii`zkd?1WPSOO!9aAGCmWW*M7|& zM8m*faJe^?=y5ftuWx^J(K52F_w;akuuxYqWX~{Yvv+;3QfZ8(R&8#=)l^v-IX*7S zX+GN;Nh9YM9DEoCvft<-h3Ze`D}0`j(bAIg@ZfK7I^N1jfxIOgcQu6uE;&si7Pb$m z)gC?}`&Y;E27T){M`|fqw3nDTapvnZRy0RlMs)a!LW!N9{R)@Z!nx`Bc2zhtE7ST; zgi5=Nh{OtZx_he5%KP zk#4JZYl|(sa#eL5D(!dC3^}?LoH(6(hAf>4Cq*!~$y{u9u<9 zjI-<|73AQDlOGM%4ynLg6L`FkV{E#RHRnuNb$DX{jO@=H3QCt7Pam3&Pj0d}|1BTu zK|T2QM;cg|({8=8JjcbA`5AraZ~x|_J|o*t*$YS`;22XDx;g1+H2qs^L@|ay%C9v;B;_LX6Jh|ju#j#Sfhosm?QJMn z!gwv9a#e&}maji?amC1F2q8*U0gaSF9`Ceo=3AiPKs=_cAh!0JpH4X#Y38$~ zpKrJ+DSbpDD8s_SJUdq5S`QuN#yzlvJ?z0GPT_PM*a5 z>BQfw4!V}gIgl6`)+&OJ5+oj?GhBWM_n@bT-@*%d*YTd%B6@NAg4M}oKr1(9`)YlN zhP;gQ9XG3@zTSW;U|gUv5`_2n9pjbe)rTG4YXFZk9JKn_G2K^a;_i6*ad=R2 z@50euasNrmHxOpkl7!D@SQ*D@IQX$U2)|=A?T5>5Rt((DS|C#_j~yis4-azFa4{Na zKCV=cHIDmw!LD!E5*Avb)dG1qFaoa!GBs<@6l=hlFE=+ywC*v+40QEJ(RCDq0n*L=6oMWA+@{QK*;=2V*%W#wR93q@<{L zc;ZN*4_Lp461doO+W(dog)bI$c;*)rc!lPO$9*?7HBGq1@I~me<@BHi|@E7%k;s&XQaGb@pnPtQ&nd2~TW^jxnzt4#0X3y-$<7XlEqg49g z&s=924=BE^vCKHVLABYGYKJcJKipbD*9!!f9qLpXxGJ0DOUI|4D)$Kq2ma)kO7;I* zX+~&QFvAol=scCk`G|XR-9GL((Ab<7DWvsjWs5?{7forgs=3@#2#5$GG1i;f2k2B^vsj{vOH`clCLm#RtYow+xg839vLa!*Bs z9l0Z=)@Z)u#5vh@5;u86*2LMFqE0dS%*OTN@VxJQrO8D5@CWUvA+Ir-w zcW~w9s?EO5cYAb89@mT7(6ajaddaA<|BR7lXvi*g_I>~d;oHy0-0T{NX9+ODM-!!V zyr*8RW#@<J<0(`}NY219dyRG={m328*Y_jMX*SPe(tE4q9d5+hlDN)$B<2%Ek z=XH)Yy63g@!A4#O964l1%U(Z@C%$+EoyMJ4*)P*E7N&$Wl7Rp;3chkUO7wuQh_ zuD@o7#?$qeJ-N|v8OYf|_6FMftN4vey*aYNU#j2{RLai2(-?g>eNSGvpxiT4kfq|$h zLQ=<8WA`EbALzZmPdJFzlS*7?+o?4Za>PhWMZ<>rDS|>QE(+~66 zGjZ|#Ea5%-I$8!4*^xonEH$rkxHxL?mgi>5tovB24|ZS@T?XjME9*FJ_i4EKYDt_2 zgC3}CdTO9BrMmg&Qu*rmXkx;rfb?Qx{=#;0DvVwG+E=;u)#e#mc`M+iDX`#9^ca%W z#*@20=PhsEoTrBl8H1b}j0?H-FxaMEwdOBP*7v9VU*~zn*58BYa7FB?ihC`af?$W7 zjTmV!F|)-yyALKQNja0bh6b_Obm3~#^-@M@DYHKQf5-J&bDW0&={;=$h)+#PM!{QT zw>JEKJy_)H4^LU@q1W*~mcif8-K>o;yEbf)yKDY@Egfg4N3V)?0Hg0B*fb=yp`t}0 z^_zdrcV!YNi8=GQ{|-5ce5EFjBjS&O)uVap>T%-^iJU^pH*v?2_;G%FVu;xjm)M0V z78Ay6tSvX^Nop36?O>)JlU!OmWzzWV^oj(C>m0-eLaNFd1WqbUnL^InB$Iavd z0?k7vf;+d546e&qc%{`x^oHFQPJc%5(7bCNxg(N<$8OQQcGa4 znBbM(tv!9{2Ccn{&i3pw&Du#cJ54A;w&zodbr-O0x#x0-St8&*oX$6RGxWMX<;I1n z;x;qz2U<<4PRXM1)s@2mcpD^6qLkp$uiI7a#%9{_K0u0_G-76gb^}qh_I*v!XVHCi zs$S}Xs0+yBY^fDjzwWp5I+LTI686$g%9Du)7xiXc+^0@| zZNE97O1K^GE9My{h`rVi-Q8}ThaawKtEWMyHx9)&y2Mrz_eDt`5sQn^dEqb(LW>vLo=)1$?E=F=>7lh)(>!os50mdKRKq6gp{mI zA6a%Gjf%VwVfyTOT6zV~fd>wpetmeHrAnd)8BX2&{<2zv_&+g7qQVmnd{-YYsFA0e z8klF4xLQE(hS|{#s@5xbel1ne)Glp#5=Qp@WmOvk@dSDG>FW;qtC+rK&cK zXYSX&pO|ynv(G=LT(#?5H8TyHfU79hD=NrzUDxGozI#?yEe+o$N!FVr3CYb|8j8{? z#1sGnK^BV1VGFfXpZE$a8@B#na+qAI)Jd}vRmGsRlo&qtG;nc{_@#LPe~0hxpnq|J znYE*o4h6Y$PKBM}U3q{h!Rc;**;564NpUTwc+*I~i0$MDNX?R40my$H# zhFQ#eZbrb*jbaAR_sh>)3w9%i*3Gg3m;yJSZWfLeZDyxH3WRbec)E3 zm&fR(NbYK;$<$yQqBCNHw^G@&rto(2I_*Kgz6G=7D2GKLDt~>rOP|y3;l{@2Xeoqx z#f#yd`zQ$)hYQ_Ao5h6B@$BEf0WEMNl$jk;ljB2$3L<|N43{*V_y z>nZ|}qb)<7Yx)^DBpDy{gJq_U6}E+L>%b#l%!9ve!r2 zfUIR6ZOA$oY54(&eaSS-lvU@?lHToIXlX4hw<`-9qE2J!-<0ZzgO0ePpRP#{S`Hl9 zWAGwGGAebz!2(jSW|g7HR~7gLbM39rr)2DNB%3a8ypNh(CB?btH@Wt=%1SCDXJ?=# z8d{_Kr|R{yMpr|K_3u%HO)5b<2ThO_SMU}Da1JU>CCO?%hIZnjXz@q|*Hh>1dy9<| zdskdD=*OhxP=)-C{>L87jioM6m|SCv3lngP-S&<7aK8v8@3fB!Dw?aR@?xtKMb(4Z zfabKin|nh!XdDa*@SfkDKp9+>Yve z2`y_q(|-7-?}K19R@>(z;BCMwqgJ=tcLCc{0Cp>Bx)>y=Z8qK?HCR9rv&G|Vf7TFu zp`bvN`o^GE9fU2(?Rj%^gP~UbQa>kbdnlK3jnAQq7l^7BL|$J zIjlN(ZEcN&^u&30JPW8VFaz;1XHCBfP*g~`$JyH2A}JSeXmC8#1@e-OjSXh|9qGI4 zW6}jMIy(AGnlhBPBh6)eRB*5MQTUZ2>2@jXrfa0c=#-)6U4p>7ZIl>U87;AmagK4cmLhkKxTPgk{`g~hp;OQEH?*LUEhe2I5Ld z&~D}r<%-{1skk9?=7#NmQ9f^9Ry{HVIlN9Ln~b+~$^b&kSk>nVAifM?HSv`56tL1R zq|=)Cs06WEsZ7nT3bNX~);YWPGBAE^30Jf$P?=p;#@@mu?NefcMk&#;ajtuHKYCH^ zu!EL`BhZqPA5ZXfIk(OCxM0UUZYS;kICtzQb7=Xxg5zCwWwzR#vW0Poayp+R>kPg2 zi5ezr+tGTTFX>y1b?e+Ou%ZPJX97AKr68KpyEnREbsPGK3Nj~YesW&_VA=R;*4Z`?mH4&%vF`N4 zvbckH@X0nwv>WCLA_X_gDUni9P_Q`cO&%$-q(?{z!~xUBM*~Q+<4~Cz-!(LC`+Ax< zF1c(wm=1FIcC^)PCli{x@JegJ00{CmK#hLYe|=~M@)*6MZQF})*Iq36K;2NHXlme z5ihHM7E&dJo^J%^o!`wVsRfI$MU;Es)*E7=5wF_l7SOjTs{3V_^LA8>*RA-N)g7G; zlwOSf_9l3z$AOJ!K>4}9y|mM~`#R7-*Od=U_-``r-I!`Td4S+V_j%vd+a;R$f$(wF zenJFK4*vOmpmd_16f=zI#XIv&9RUJu*ES3p-T-R;xAnCFKmk8rU2`yAsKuD>)_*K9 zJg(QE?yO)T zN3eXI$Lt^hPnn}+zu$Jhj!7c_r@2;M)hm7kB(#+zr=?ZkqqExXjf01`e(5SAAz=Vy z*6NOP20?@~mk=)*85zF2os2{P2;I;6sBk`fkX2Cd?pW2{8BG(9WAz3qBsZnCGIwpP zapLOEtKPr~<$66BKt02zVG*lGYn(mUADfERcH?Bh`Fg*egcxpaln;!^nIZ*)pSW!oW4Fi6 z$6Xaqmg4mEbiY6;?Uh#eM2=f|q|J?YqGT1v7L0vXq{l$0v38 z_$eXVHt0uw9kS4cd$mDBHp2@1z^*@ac*r=o00H_lu!CKj=hU@{JrgsHkp>}B(%hVn zvSL%ZhipAJJB=j%C?eM$6DZFPuNzwF@z*28($8Kiy{m$t%(OfRueGC%QUi3V-_pc3 zg5u%RV`o=)HBV1*49+U(bo(y4`%?4iiG2FN8f z6FIQRSA{ahnpFL|sP^v1-FcvjY?;lOJX_A!$5$C~gBMztxY(iieSVFjN@Fg3BoFl= z>+c+Ia9*BV&EqaHTsEMk>0AKndd{%7x9>GEL}|C;(_K$;(0Me&UaCBrVIF%OgDrPL zQNI{auKb5Q-&Z5X@HW=33{TwTa z5kzSO4IDV^w4D9D17n2LLtpr3SIHF1wmZk&Q1^??8F_i73VlKNm_%Oh3aS)wadGx5 zplwonVZK_zQNn${eERNcL#*nd0Tzx)ZCJC2n9ARIMF7rT!oqoaP1!C zQd!HPDYoiM(!+eWoht+OS-FMzTk9oH15{eiF$}zpXY2h_XubWq@=s~_Q_3%ty2WK) z!5e-OOt=2)v-Nxq_lKz&daWv-zu5(=IJ4N1B>$rY2&lh9dVqi*Wo2I}EzRl4P_ij1 z@hch;*AmK98%I)Ds}ycsr_~XT5})2gjF0KPAF~+qWXX&S*>cdc<RCS!s`?kLZ(-DcIHO%5h($auiq@HAt5UT?F{&Pv8a~Ek!Y*lsSq~<6L*IG48_Fzxk zUHSB4ueS=4N}44TPj3a$8#N;b7FnH*?WV>?2Mv)JToM8$`8^Y|wSr5g4u1?%VpB%s zS&*`z+iR>|x|Rvf`Gp2j2yj-zGU{er-4h=m@C7AteszJ5eU*64-yZLREQ@NW`r&V& z-UbggmzDwfgk|yU0Apwin|+C)Su@=M6Q)aXn9Q2XWX{R{5)0;w-RLaqDLny;;%cZpM3P~WD}-08{w`gs-q4>cr}npaLtFwB!p{>&912;Kd#Z1a)NrtT zsxlx|!q}hh9rYN>7;ri}y?U$3Lmmnw@a82Brz_=$iI1za9j_9B$pZA6qEZkMOZY6N zK-*q^56u4ZQ!u*czbPkzO;EN8bqlG&@*4qg?DtBvIQlONhh&C*Ep^*@RBjaNyP~K4 z^Y51uKmp6owHY(7qGeXMvp6W+*(``${5>b~GGujcRvO0!RjL>NoxKP{cGi`!eAw(y zw9EA@zD!0}C(54RwUQpmYxhJJTWjn7TlCpFzuqApz)j`V$epkM>H!P>Sn_qk@09)D z;{;!E)0*utw~cJ*6jfqqjRGr(!iNSS!oMP3WxNeNU`BzH`7&dwv+;o;uLLr}u#7Xd zV|K4;%;f$fV`QWdG2z$Kxl?BI9sl)1DJ~+n2dKtf?q`3&a>Px}ajnIep?kgQ`TTU~ zqv&bx88Mox2TFC!hp_8zSxxmO-nFaAS`UCCnU_dmGqG+k0hnLzr^S@gmP)^GB}}U& z`(iMQ(1{#7So%r>aw)_L~m>j&O}Lcgi$5sJiJ5_M}`{pQdyTuS%7X>gZHRxqjm*#jK2fWjhJ67DZ@; z$Y)egydjy*5uq6skCpgftN4&Grt)|*CBbzyZ()_J)kODEz%aCLEiS6T8Juw_VOui| z;<@ioc!2hxZ(Q05!JS-X+d^)aB@X)&9wo4H45&^4S|HEfHSTl@DVdSl(8B;BqT+p= z+nRR1x`3g3m1oRM^vQl%+HuomfmOgS2j$!2to2?$f8DMnkmcR6&>c^DEp5rVVzvLS z&4YsnO-(2&RH_8vq|~2oL7LnaAGa|@bpVSJXl_jQx?kpECaB)UslNziJ9%Oo{`T~xw`LNWYjWt0+o$XN8yldZvU6IQH zW2SW-(Fzaw`r@um+n5#hbjy0&a_TfJ>&C;#PvVYLrE5YUI+NCnRn^j*HzkUyAuQ3M zUl!hQMvi16`E;^^@P(q2P%e~FRZd2#2-ahw;z@dsij*T=T9QnBuVE3LP+`tn(W7s3 zUu%9REIbCJvb;Up%=BYp#e=6NHVe$@U=dE#I)9900gf1+0Y_#5h7dww!++*A;R!n{ zd2)6x9zRSPX1K&cLv)Y)fAmV&197OGsv;Sb&*I}q@jR|$f~Ej@sM2(_^#N?)_7G4O z*y-T|H0oh=LW4V1OA_re>10lmtfVS$ha-nBnfjuQQCb*?;jlYV#g@eEFWiQF{&07nd#=^aF> z=d+8n`@I+=khkYAhIv(WW!fl8%33QU#b|8<$zL6ySlZICMuThETij?GgZy-#fInXK zytKT6ts~hEL8s3HW$-&yhbCPf`0CxMN)p3J)wi_&3nZXIgb%HqWqPr$-Mj9k8>oC# z8o^2xsp146g2~DBE};yS!Po}~y#SZYFsLw?L;32kTeA^M*aMeEqz@;NHzDV(nO{|p z>~-OYJD z|3RUhj+=>0?=4hCt6yUsp-3$cgU)Sl5lzGxDAP-MH7xFf=CL(`%aMKldFgN^+;sH) zz`wa$TdBOEW-}}12@8tNEiI|?slUVK=B4uYv~f&kobB?8KsvqX*Oet!WE`+C2?otN z0MX4(1IGPtMC+LYwOLtrCjWD-ZY;defx~Yms&XA+?SFk#Z4AIAUJ2@dT4FUS6~=&xu%NV=_=&-p40-?Gt^_>f_s__SaAZ1T4$?FFjBbCHITDi z!;>GR{2yf7^?Rc%f^zzBR1B-z4S8O_>C0ie4Vo}9H%GIlpJ@eT9aTbMvG1h*bcA$R z3Rl2nK(p*!Q&kiXCO>%kO^@Wo0=|1{rMYXKu%n}6@uC0Pz_^kqQZyX;`8>27(d>e< zox&2wYgHH5c~k4PNQ`{w#o&e?Lx9Js4v!{b%}hoU54D=>!R;7#KDlX2Jo^TZA``vB zt#WL+Bqk$q^CaE08d~6V{+LWa!VO_I+W3+VPj@wKvt6TBYhf-n>T+`P4N&6XVg$wk z(5cOppsZS*Vx)MqRuR~3Z>kZ4F0t9%#Yy>G@Ea0BoxNStR6pkTA9E&z`29v?X0`gr z^r*u)Gx)?2vSU2lvZ0#@uq#JOqG^%sIXF|2T$cjG2|d0*m0FL}?tF(`U|DlWvV_#& zPjq%(c6-#@i@b4%$Pv7cA19ahqPfB(5s==R+-V^cB+cHvEuZ#~Xo=TY;JjB-XLS(m zRYSP+*;YE-O2eKHMV*?r(5r|803MJ4l5>rYDi0xqDH`6 zmqm(&q{ZCPErm>*Ay!;+<15`dEG!_3Xx?rEyY=2^)VD%eohP?~dUZ~xoR(_xf2EWX zN5P)=@x^clXh1dRMf<`7B@a#J&eSM~!}MN$)%ab_d-a(~hI!jOT7E zkp;!pJSw6Qk7p;m-MqZ8ftSRmQ)k@|RaumV6USn;jVdL(F*{nNX5qUfghZeOFJ)wg z(U>E!EdJKqj6>>4`On0A-HU)D$9zteSD%Z^;ya1yM3!V)ZywYw>yK0^gGob&J~Vq( zyxbQ9EUeyYs;HpRF#&}HGi^aT`f`%GUGOx1>e`ZNhT>_=$cPd%gX+oVb+b~ zF@p`|cJ2{`CMVksSoOvpJ0}l58_W>A@8^t&nMyG$u1#c<;`g47HO}Bvcu8SA2C)cB z;~D6byGXU}>FA~l*B=6z)*>gQ$k})arE3$5Ig5bzda1E6%#C1+iOsHGe2e5c6J^Ja zElM?sZDL&8qw{B4^t0$8c<+Ip$nU}`H^Lu-9YP#B<=ScQhW(PiB8S}k&Wdbbzbtos zMACFSPr)gKeU%k`OO#sgSBplWmFS32M)F&_hEA!WI-e0_A3l}Qf3%eeNk8w z1&8GSo{EdGBv`$JS&bH5Pe+(=2=Z7w5oUUo7Z1rDnFLLi zYs35Rf7cuv+#24t12wavngT%l4um0OFLjf@%(1Wk(~)nyL|WhfpV_hY#-Ij~enJ9`i2ENPsT&6E(+fO5oO{upOZx_gOwY(&a))=w0|Ee` zNOlj{$F6(tzuX0MkZGfg%}a1C{!~>Z7>HpqI$mlT zjAQdv((CFuYQ<3 zl&^iI%?l_pZe=G}rl+N)adC?MEw$g3J!*M|jE|3(&Y35=?&S?8aE0#enUvohcl$7ByF+KYwEf|%RM*vC9$pF+Z|I=hjiWH#s0)+Uc zc0@o=khmSN0Hn@Ht0%(k#e|g0nm<+V;NTZPIC$Lh{FGYFHh#xtwKND+==*@(3=F{q z#r63^)iuRNxrYeOhgCDZ0|VqNEMb7uq5=>Ydga?$DK|Q^*li7d=I19)80t0wAk`?v z<3l3p(fPu{e42@BLJzW}8GqC5Y0A?D6+oi=?O0vu#Fo-utaoU;wX8`{v#j%ujKq0) zDr_mfv8~JV?U8Mux4KrXnE|Eu(^*W*MQbuvk>FkMziaW2hkhu^cH6@Sk9U`a%4L6W z=@d&1hX_bKF2*avsOJ6~8) zSQwO^P6h$^>+|!J=foEov*kkXf!|+WWXVmS`xt878)M_jZ&bNm24GnFd zAFpfZaxxAPZUCL{i`JKhhDN79l4ikmzX(}jdx=@6{q<4PwPv;X+xgrNPW#{JR_Tr3^WnPruN|d9Pst`!C zYK5Uh!X?H>=F}lGz@{0%CHX}~7S{`g6%MuNt0B9?#m}u!x9cMX6&01wH8hh~@TS|t zNp0JJ47PvO+FrRC>)G|Yib`Dl?N)+f|G9?%d*HjO14~b?N}auxbzb5sh`^VsFn*5{mrh0k>cu1o-BbhGqD?#p2w_gANra#AvYsvbZr^w+A6jLkQ?#f@j8-66yS~US@dT9!9 zCh)-8iM9!?qJg(n&BKyx;X{V|W##Z!M~ef1402cs{Mgyb|#Vy z4i4%AI^wo%z=EIY=nB*f&_6Q&26&YfK=gJ0NS*aX{Za-PQtMCm$2qB~pSTZ)d5<>$ z+Est9+(0~@9mTf;{wN`>x><84=OpgHC8{Y@AwFh6M~U zG%nxonU^E6EM9;Zw-oZ|3ak;CtuT`E?b#i(HBbPc8vrJqt^nLNlQ4l1ew!|BfY=3w zAf|xTxN|;07j8=w4jfd0Hw8d=?c#rr;70m7!iPmSwnsRtJk8{ zEOh``zB3=mbdg$55I&o$^FFBM$;Sr?VDSC?{nKh|W9KT2oRk%F4|!$Pt=kakRV&}b z3_O~W=>SsV+zi_@Y59v}zR8A;~1xO3(lu{u&hwg?p* zWBacG(VTus{c@BOo-*fO+_le#SvE*y<>VAxT-ZCnENWItu^`4IBVM@8y`RoZM7ajAlNk)zX4t9IzNc%fP@O zue-Z@rIKsO`3K<1X&M%byMqaHnjg*vRvH1xvpNMOW%X{sgau*iEbgR|>mwB=y%2wz;CI~8_RE8M*far1PUmx?M%iAC=)$N4#AZ{P87xO+V`^sRvg(c3p3I7! zp0fx@aU!Fl$^h4ORKL&2Sho?rR2=U26~N&yVpd9D-;mE8Is!`vMg;EZC0* z2-%I_VN*$VuAf>1x`p#z^3V&%sGqOTl2OR}Xugjb5f1`?xrmE$-gv(gCgl-);oJVd zTE19^m-gQ<8d%%*k_NSFzb}1h?lT%C{Qrp^hAj21hkEaW8D(EuPZvhll+!bI@j<5l zy)g;lmnr#7iUg2wI|Nj@w{=Z}93l;RY)3-z19rP2wwBv*va!qmLUejYmmTKEs zDXpgB*P#oyKfTGysVQJOu4z`US^9MUg9t;^p6yap&uip8t3JmoZvj-`OO2(#o$vLCE&YS*Rz@zTs`BNgy?CM&Q;z#Mr z3QwGeQEG2n&eY>MZ>w9U+T~}u{OtT8+bScB{nDT<7h;b$ysj3)6K<-thr7XNZf0O2 z(V|dv1r1Lp;A~9@PE|`bo*4y3RLAK@a4hKh!U^s72W;QN_~$m)J)BC&W7~RnjUi7m z=5nV}X)Wv$jpNYT{rTs0>~Hsq6bT4hm1xGOkzr<6XgY;Dg>e91MT=Aw!DZcVL0n9_Xa zF`yR8_~j`Bm;2}mA%1ae_pIX(dC>5HYw6npZ=m1v4VAglQ@Vj>?4_e&%iqNwtp0A@ zE}MIzHL&V8pdsysy_lr=YUzsr+r*J)l7UXSA58Dk7_$%BevY`XNXJMiy?-1PGe!~V zHs6N--sD@9LqR_qbhsDS`Rrzr+NcG7^TBMVy`3l~X|A}q5g99V^^L>sq@xyu+~yb} z-2RY`v5!^W*z6i4JiAo7%=1|jZ;$trIle|=ip$_nb*L+m$2G=@iwuW?Gaf{;BWcDl ze^C1JAooaP9&ehO$Vp(*Hc^xRJhgeh!=srDVn{ZbRdD}cJWf=;#^Ie4>Pxq?4qw1A zVsxw%XFdK|!|3Aq8t14+GyAGt&!qN|V354qTVsj!RG#3dG5Rq^yz_pY47e#yqEX9{ zQY!3tx^m%*ut|j{(X?zH#3(3^O@xUKqZPfCGC57 zMpzn@@U!DtQ+UzAC@qd**0M2UV$)YI;RzfY$`j(#U?}Z$80VS&yyc=Z4U2nxZ%Hal z3xgao#Fit=JqMn@v_&5wxqnve;7a;4SL`AY?_}^E*+D7!`=UaG4DRME^cL!44)|Zn z#P5%VfqBnex3RW9+6`A*{|3By8F?!-G+Z#rh&#ge#__kxdL~W^OeT|YLB{r(B_!)% zf7PtaR)GaEQDakgw@OSMr!Aq{B>eD0hM0BYN-YJT`$ycXd#7%5@!hz|WqXp#3dU|* zbJ%h?BXq|1)?M&n5O^dhcTyq~Rb;%P8B?q|9qiJJ{3hd55sPe&XFwIO)h` z-{|lV8X828W0FLvMM6iHZ9B~Ln5a~BNxtJJ+=)_|HkauNrgmT&?A-T{UPrCot#>Go zH0R1)!ksWYV{DqXx^CUV0ezwq=S-UnZ9(}ud8@SQ4#9BUwUpOj7zRJW?{Cl~F@h6w zX<~qjf9$vg$T(EhYR1D@OR)FRYqZ)A*MAwPjnr|MO40Ru-B&h<)0l8uHjyp|9RE1> zdgGAyyy<uBMAfNc4(3solYCk zG*T#RIcRCX0X0kNs_ku8WTH=gktG|0I@R?sJ)X$=v1J>l9_Llcvd_Gh*Lj!l$zyG9 z$ELzmb99fTQ9{cK@U}Zy4Sz-+DR^13vYqk-j-ZWyJf;_&TW*Q&(QDQr+Z>U+*8JYl z$s!BOEuhrz-9)z8kjK-Q12mozw*RLC6J#e~hZDu70*dky6wSEc6?YfQ-c7Qpu0$x> zl}bxnBJX z7h_)E2HeE?OUEQX00t-iSS+OM{xvni&8|0G@@Nsq;VJ6oErdIB|H4XBY8|2Fl|%>Y zE_?bhB6krBvkp&A2;**;0WP)zDP1YDhCY=nG-OMIj}#W{q@8jLjTH{bImL9U zX>_-uxx8MOJr#sYqHgJ^KW6AkM_VX z+}m?|cl0sj27kBH(dnA|xS*#tt-j0(BOfBVx?F}oBWCCCwhbGO!Ou@dK7B+S>QUr$ zJ>~p#5@%dguza~HpoIly!`id`73$yd_j^XK)mxu}un*v&rg0(F2lJCSweQh=p?RT+ zTnA!0CB#Ept$gNRuI8@w_>TixwS;`7FCIs931U&VzG5gKpMJg`etz9k)Y36o0tN;^ z*|IBm2{8p}yI|f_9KqLz=_k#CRnwote66*o900DJ}ZWrH|l%3_O zgA&Q8W%!Yp=WYlLA(qv4bnSiVNOQ@=e(;wCfe`s>rza|I!)0F@j;3AO)_Yi#)ub{{ z*k1Y^ac;M3>=O37Gq)=rAIRBC<`IZkcRn`XtO3QqKe~=`{mFd z(YU*{L6sUwE|~Lg?9hbs#f+*O8t(R3EHjVXk?pG>EF;a{mlx-w;j(SIZINNY9$Qu? zC2C{4tUspCwVPW~cW3XWnx4UKPNYibkI$G7SXe_<5(0K*JSZAt-hpUoesz+c4STmt zVLj@w*wCsn1G_TztE%V^5u;H*K8Do|rr(5B8;89U#ZO|CDp@8K*megc+3ndA(Yh^I z)mSFZ++VsjrIcLXYi!TmUxJRMa|{Y{2H*vxlU^0>jobw^x7gn$=Y9I15poMY#Mhpg z3@><2O7VGD${J0VgIY3^iX1%M%d0FAs^NA=RQSg|a!Ev3e@K}qNYZkONYm9Sn z^G#1HN+TvJSggb@b(5f&{)0w$QAxaOFk!d=7$KiPYqk1VX4!+4VEFl!5GA^F{39I2 zcE}G!Zx*wFa}@{#-k^A*2sv3ZCk&=zzN}W)ln>sgyL%eHbrTY!XF7||Wtg9)5tK2} zh@GMF%pQ@W4KI{;(Woqvv&)tX?yOjd9{W^e##ZU9&{5>Z`etHEP1@Z)rQho6r$Lh+ zvZw&6W{3RZo@jmR#Q^EaO_HaArP*WC&|N>v{k6uK#=A zZLRPBzO`-tvpo}W-`9Oz=Xo6avG4nFoIX)0+Y{QR92|0E7EVgvh zWL3Uh3?ZKW7u5?~1!UJgHa4Dk2&x1PtZtAM{2RV)a?moJNN zo3P@q)m-c%eE5sO{IVkc^n{7ZWBjQX+Y4xSXHb-uL21 z_bt3uqV{$+dH>$!m+}AI7A1o6uXbD9=TTFG&+|_4 zg})}X@%PpuUJ#+qBVU$qOPpa2P?*1E%NFJ};>@>h-J-8MnVN7&5`4oljZoZ~sARh( zw^aB`!BJhmcJ01KR76A&!~#e~Cg2U|<0q?6#GY)QT+bJCH?d-~uBWHxp+krE1+Cml z`U9A`wo~Z^Fo}spw=!?LsR~J#Fd6du!w;EG)R*a92 zKYsSC^4?yDWMOGMkQilp&Vs(#nQ4krQmPcnTC?jUv#4=lu$WClab2CJc_dmdMGW&e z44*qGo;dL^^H*5_`-eP7VGRw9Ymt#5E4S(uzj!h42LDm{XjzY&V^yLzMT<`5{oHMkYJ9tPvGeouKhDmsKo9VC=OL4<4^^s;K7M}YXx__l>gNs)32AR@yXeET zs-&uFDaEpSb-+3)r}bzbk!Rc^DQWiVugz8c+w`<5U3+RbW4W#u6yL-z#i+%ErCu2Ma@ZYo{k2yoxcfZO(X0E|^Is>}ymzw23VPVvXI8uSeBsv`zTEvYJ?>a;!P})b&G~ zsVP>)<1gmh`a^Z>bei~vdO4@ZOWLy|DA}^+H zU?4&+0K!c9?H%UiMIl?&?p}g8LXm)hw{i?;6pee1Q&f%GTV@r?Ea)FS=Fw_ycj+M3Gll3RQ3qFuq zVnG5@)P@DhNY;7Y^n_r~*$D21M zs&AyGcyXWkj#__OCvr46IM~F(!UwvT2|f*V-*LnAmsdCDNgL*iMyX!n+q6l+LjSm7 z-uV}pInws+o8J<4{`*jj1Vlv-qSpfQ>Ld5l-f19D=Sh2T7Z`{tSy>6s-{_3k`0`ug zftKcoC2F1H^B5N0w{6bOarwco8YR7iI;5tigkewH&$aCfvE|-<>LTmLgC=MjqAn_h z@?cST?%TJI0-f%UHQKkf6~gG%r%!j6m6hd>Ml0i%;Mp-yr$6Pe_BUl|ejJDHvRe8e zR$cG0KF_!!=V3moEvwO}E~h{>Qj#nisx92u^97#!JPM?1{p9k4Ad`n*Eas&qT6f=; zM;e$%fs5vD&dbT6D6+^R$F(AQc4?Q7Mh650G-VSmx@rf_Nfx;&`9R2#$G*&~FGFi= zN8HbG`zab27>Kb-7b7Ijc0i~uQR4Mj(B{RID7IS8Q@Xb?J@mr|-L-O_YcbxT6pwOu z!#d=X%U7-pe~b3wlau2G?>~VLNtP*f5&J`V1|Ml1_1Nv_nUHEvLY&t;I=<_BOTn65 zmhVW6up4ZVdD&$z?fHu4Q`@p?||Pd)R|r(@MNqcUVlp_vnRdYL1ROsyLP`+0a=T+6j5o zFaGraq$aHvJCUMowmZ7*Wuq!HHuCW$9}3aGGKP=Zfo8j^^6Egf`_LZNlBJ1nv=0ni zMFX(*hw6uiZ`@pRE55vcsYVh9I*sFHxZYeWM!g3wH$unNM zvUo)Ywk)@t$4N^|KZu;B$e(*$M+Ii5`nDlrwuk8$?e|;0?tPJ$SJ=Z;@swWO#mrn6 zi;MS7Oiy?A(o+)kpNUE$4MHe3L-?p#a6~8X-U=+IRjC!9U+lbzoM_sT@ACNR(=s4} z6sfh?cT}(!0cR>?bOm$tpfN#aDimN!vDw`4$!$*`uNc5*_A1=Y9GsDjtfIMpC z-W&-t2{V2?+mjHTEWI$206<0OBsP(@9*4(9UNeqFJ;Nv>BS*gj$Zm7{w)OM!el! zKhf94@4pif!9WoZ*KjaeHbptw!yxbc5^Rctn8^nF!~0|{;n7|?laZ0px%21Uzs@dW z-3UllXno?c+)sjvh6W28TZzPLy z;Aov-yY!G|Zj1=DX9%17p$uat0IVS~bxst?YXA&(W4&4&92{-Nb5pBGpjaXIizQ@@ zgm!adn6OS;XXh0x*^&<*xX^!E+)*B63IwTc%e0JvZcFYGsF7D=h78kb}!RXw;r@dr` zqhyGh{#=4;WC{B4gJH~w&B@^c+y*S0M=(O<^!u@|$@fljKq~(l1!NxD=mVSo_gF1qE;Ag1Q8?PWV+^ z)w;6Iw(=m^DV;hcfL73TyU(^53})aZw7MM4%=q#P3Rrn~makc}reK=>#bqrMp3`1> zjv6cfmMsj_)L7roPd~$M-}cA8HYjvI-nr&sd_QzwUz(6&Uqfp9;9w{||3*AD%ogZw z$`VEb^Tnv8l(Dd=sH-G~;7JUPjLfIPB>FO^)s7z3^6aVl^vR$zoPYoR{SydkH4hK7 zlWaabJdCvGm!7^2H^3Wx!hi?!C^IuNioyyxATK{3s__bJVH4OdTq|WR3>Z+w5We4I zv%vMl0&G#K+g(uAZ|;w6>>*}49j z*g;9x87?#Qn4SE_wcUimTq(zvLSzT(VstbwUfLIMId=b*MWl#<96OH!=1b8tHg+Fg zDCyYijc*CF2lwXnJ;<3J|7aQgqOOh|)|lS~)0t0uM2rg!;#7eTDax`sO7Yye?QHDq zrZzSyI|AzI*m^(>%X9Mb2zWz%;eYjN#pQ)eZ3xy7ZX>6s-~=$WwGF`|h($f5Ti_~2 zwZV&O>b;~gJ;f&5_E1D{o|1<_DEO1*X-mL$^>$^UbDrC6NzCk zfC$dDB1}*hRru=!RSjv`i&8|&?+PF7^CjLRBsvZV@2jY`*V~H9@?CzgtXUI; zRN^zQtEy_H#BHM*Gf-BBtA#wjLXSJLiYi9B9kVuedh3Zij#r|Rgsk1mR$M2TNzCDlE)?N2+BV|4Gx;EVN701*A z0Q!8sjT^6HhzM2}Lbh<@cEMgG{7w`Ms4MaJ9{VqhRAQM9JqI^6h?K-)Vw`|b_@hHA zDr8kq)Y<%DUu6&58R3CFV%GJH@$vCF4&ScYFFWqSS)SOJ75f?wnQy*zo5N$O4SNxn zcrxz7qAGM@Ke1dPJNbn@tRF~1OZMdcI)}%jGe369%F2E~*{YhM)ir6r>oI!0Dt4_< z;QmHkG23kzN`}dnEWEtw8AsEN3Ru>z4aV}en%UHWd-mA0bWuS^L&FvdA7Jt9>@1h$ z`NT{W{W*jq9XEptI0(Z6Lena>8*P-r$-`ezU#vJl!Rq2WG;~fuQIU`rRYi)YP(ond zUccb*w*+~GQUDt~Tv+O={WCnuU{1;DVhV-;X};(i1`)3aw+U0%%0Ur zGgyL``~}yfz}Brk5fN*!G?wV=>t}grbHt`KUKOnb7si3}Xo&CpUzJ$B`_;0i$S>Bd7%Y&Vo)ga|Ov>q5}xVvWK z+CAs)*Log3dK7^7fi}rdx9{GS)qF$yu};>VD=I1q=BF2^2|;%RBai@V$#3o9kGXqg zNrP9|&6_5e#h7!xhfVL(Yjbs2s5pjM)7Q4{K{7z)ZWZZ&nim zkpeW7%YV7yn3(##X>yA`e_lr+5GQ7V7WA)}v)OgxCCfcIFCK(3jayw`zO=o(zL9I^ ziI=++RI^^-Aput|Md?l$N}Y5gMR#{;QhegpXOBA(WrO1q zWFS(`V#wEfq$YwT3JVLJQL{L#`w>)&v?-#W&3tjZd6sYnlRv(hT3Rl~q62LT)-bNb zQ$|{Pz4tz6=bTKDojchOHo{ue#W;*4=--g5sEc6FRstPv-?3vqp;F-t4O<|yb?dT~ zD_1_w$k>18Oq!mY*W8Q*$UuOoO9W%oI_QlX2)sKw`6IS3SN&<&s}lrMqljAsa`zV| z5m6U`p~%!6ln4O*W%#4@-rHXe*BxZl#H7?)+l-ehDJgwuYBGa0^ySN!$CBLo8n(W< zD|W=?ZJKVz{K?5l?rD10bn&&yTh^j!FQ(FGrhhU}xL!ZtO>KwW)zu*W>f<9+qh&*Y zC53NX2HBW=T1`d88<@GIwA2*jQCrWtn;<>f2%{UN8ruIUmwg4VEUh_#Wj@nNk&84T zEkQMK5?~#Wo3Dc0-ECd}(xmgznWp#wjm= zH_LVK;K2_CZW16~f0PWE4CN)sqUxwbslkezd7;H+F|cN%ZaR_*;=jQ1wRX1Fv6F;swtxi~_nJ0(&pLK6_jt~^4a<3DnJGe(Q=^=A8zvY@lmNYP5p-#-~n(50S@?FzkV&AyC>s3L>YUGaZ<$}KOVPr z!<;Jwo0`X_2ZFhF%!iNk0F0I(ij&5knNr(#?!5f`xzx^`JBOwRi(qhP#SEk?_zsA% zsAlJ?4o6LoukRw6D6$drE(~4;BWX79DVG$rC>?3N?@)SDL%@{xK}R0PG*{NOYXMxu zY*t>ZsBx>{X##aWKcO37y`&^Js?-ufuu<41@~}zkO@e@k9)8*L6+>W!8aoJK z_2!Mr>5rKUi|cH}!SoYuFX;jvO~_2oh{urh)vfe>mtnHg1!)NW8W)jVuL{C>M$-O^ ze??x{m=@hb$KrWVP|&qQn>sL7iX9q9$EREedC_p4oapc0PlL@?AmW0>l|{=4ue(;p zl@@nKMJ2e&6R=1agf7CiPM?&Fj3J0_BpMA;eg*P&Tu~ct*mApU35tGY;j+!+XZTLklInVq|m2ijTe!ZmLT=mf#&~rpkm>KzW z$+(Wb@A=R@?VzR3!_FS7;W;s6N=W*MhzRtIU@*ImZhGdR%+c1xoaN>PDU%Y(DD9?yKf*93x^a#JWxVS3GyxN2!82j0f7Y~5# zG&47E2L%fMDg&_mWz@@G?DuA$`MeaX8h9#9N*F~*)tqpHz;6UIHc+cS)C0?t0Cq<=X61Q8`ZX&e=%&Cvg$gS}>cA9}(5v#0xhhaWkk$rgXJ?AKeEBj( zf$*a>q8^S#fW3;W)3+OXmO8z$d2_4 z{<{KZ9)&Wcq_J@oQlnNxwvbfUF;02E^5m`rN$VT+^)yVn?!o$3TruV$UqVbL8yYlhH~5*V>bRx7Qxhl(8j}3q4Amb=<&jZ=UfjDM zz17XlYrqR>v@F?+YU<+LXJwsD3q!cYE+-`kA~0S7>y^^4KO8ZHq5?DOoVysbIhlUF z))3K){5RE{hiT{<=N7OuH5ukg2KmmcxH2lrv-cFbvWO%JtP?kWWdubK`3jv2Om|e` zvj#E4xE(<=$G&T+kxLh6fjPFyb3!RfIep&;m-qY>f@heqpsEMD8x=DIXQr=V`zb75dDKrKRaLjFq5 zMs!oy_>5K6j!jdE>nd+WgCi`8k7&_5*hp#Fj(Wit`zMoy8HR-Sz2#Me={I&bc zh4beX4jp0)4sq1yPEhk|nmyC-cXfw`9OM2@gsTco{Pi-X%tcS z_Snrw5)8BN_epEi)|&gWC_SPnE~dy)CqZ=`s%>)&+p=zcs3C}%cq^z0bXyP_G;&Jo zo+dTX9UFRXsBM2(e_*F!m_uwocdB(R|J1C3*7s6KyOZ6FKIl$Jk&ZXTgvhGO-2xc6 zr7-EdSn(bL2<0o4O>-DtuDu$=${AOzsKlt$_voT}ik2m8SgqNRTmotU16_X_ryI#& zz~DL{seM>eMC$@qb6+z-mGF!xEaM&Qb83VDy3Uo!xZ_E6_R_2*T35t17H2sVH#3Qz*M<ZxR7YEqZPUNso39q_6`!c->6#IL zFT!#rHxEYypbq1~y7R%-AsiJhc(+XhGxZ*y2Ak3B{r&yhQQsLo0S&3yccgKU;l9&a zeOK}PD~gMB&VBgs zVLck5pgp<0U{$v*?)y3^awy0AkC}fK7*m+8T{KZ^N;`jpF3x08yp-QvKE(F3%T8zWmXSQ z1gzsSgS-~0RFAR!`H*Ek3zT|K=REKKL2 zwo7_$cT^ThNlA?&d2ZgknYxJYw-fCOzx60JwH^Ell5b)b-7)ZJ<1EL>j~^NCllDZ! zZfIDETn)tUE9%Ga?`pCP)&77e3E~vWy4Z1*o@3WJgia}navJ!Q^a*m3Lr!k)e$jAq zJTL41pM$NTWR2o(Zxo?1pGrUL-zcKuCa zr(5!mr%wbh2f$&azHHUHbs3z4E!W`c z#fwxhh+A=SaqvUfk1`YmJgIe6J$ja-nbQ@s{HYwrzFQ45zb3y$Qx{Q`3N-o(7A|7O zVQJQVId^wkT?Qqi0G6_!2q&izQzppz@sAPp*EsPCJ%ZwU!io9#6>drdj+wv1bL=INof7p|4b zYF-Tu4c2v0M1zD<^fY^TbW{P_Cm~mHhEfklgqf*a^g*?4Y$R&Zj|Dzgvsjf7p723? z{M7*B$?ff?^8t9CW*g_)Zw9CdIr;YP1Dy0SG&))anriH-R`e`#wjb_EtW{k+(n|UL z{Z|Mpj~~>^O9$o9Np>nA5utqV?`J@L*@o(|6vW3E&*JM3xRw}dGs6kVdi@<@K4{ke ztJcT=JnKrk0u5QXDDQmFWmsy=&~^C%BY6={qNfXOu4LL-PkUMP`!k39-iP6L?*@_r z1Zq1}DqS2n1toVoicvyC=9R{VbFja+G=~L|h4uQG-iFj5+$EA>K!$S3xb7w`xJ*n; zxJ9%J&UAh;u`!?QjkODZKN<~fZEezVIiG<+s|ve#DdG?kX>moxA|4(d9Ac$q<25~Q zOWHOOmAY%=m@tU+YIk?{(w}#bZV+TPAQVzWhs}BZytKBK4Ve?c@R_m)k{@J{;>ya! zGH&Dbt~#6c?p=)f# zS=kIoo=-|j|Ad7_05&#w{%v529zME&8&QKjl3~|*@Oo5~D7aEZdHKs=Pi{4M@Pd{Y z?Qhn(Cv`y;<4>%ulfeRVXe7}P)pb!OsAKGcF$9F9(To$X*eoIgQPe2MrLxFFCuRa) zO6*r*BM>WKM0~I^A$!h7!)f~a$0y|&T&{o&41IqkO4$y;N3-7gB&}r48rj!p>bgEy zX&gsDq>?YJ(lTV0Ar_FevGCJnp#3=X<*Qff zw=j~Tn)B*&aWeusIBmAQ7bEnjI)5}+^ zScSyJhPf40nTVl*NC#o!N>|siqKwt)*|>fCcVnfv-wB@3O*V22k-H1;!7@vmy4JT+#_$^T%e<)ljVx_ zR)Va3GaWc`36^%`INXdEA*A92x8GdVE#sVX`)&2ok--kBs%i^j(bieX;d09mdmwEv zQ4{DP-w^(qcy<8J+F*MCv$`f~ufrk~KaEMoh9vZW2&+I{6_s=~?YxYWR>%t||M0#Z zJa+7w@+2zmw%*=A@P@thp&QId$9`2G%EbK`Z5alG;cAG@T32J^i5f!-`dsL-CRHy> zqwrl-l}2H}tub_LccAF1YQ)TeQ_yweX2PK$K!DQCFk1+QMl1U>3=(N+X+nMHvGQr- z4EqvhsC~$O+f9lo2y62wu){lW0u)tDDmlw)9%>)>03xRY$9LDKi~joci`aRQ^Ge>n zU3TP_$bJHd*2r8$$aSEn1nz7gmmu*U>_a z`s*amA42fb$>p`J96bKcP3yJIuHR`h4v2)6gTosm*o9vsB-xF2t7qk*%mx0^E`*qI z5E6sxG?HH#(VbD@w*8v?Q8j|=-pJ4Yb@jpfX}A*jaEp=DP(nV)Z#IS8iBhh#x_U)= z{>W0v3j?R$&j>+Y=Y=2vTZTMVOof`3HZ1|z5GpUtCN^g}e&|tUTIQYv=wu+%??aV_ zd-e8C3+1qBT+Dt#%tm~(YcW1ZY*W@tfX=X-G0~a{(Xh* z|0%)GU_%q;9rpD*yDUj5+9Pui3TW^eiRCD0FFk+$yh{ASg$t*fo-P4&bp4qg0+Jpx zp|9P!L#`3KY5n%?EFeb{H6n3~OOYvHIe|w(3A#|>%(y2QU04bIZ51j87BI?H zJx@j;F^E8S+ep037trw#c7MGBOf(6Ah!lja9fWNe0F$OaYR;7J&-2062NZft z2Lk9|%|UKtYiVggU8Jph{CEHWO8^OXACxRW{0l=ZQZzD(Qg*?=;K^XaJn*`B^)*IP=bzJ(adw@2@;Kc0g^jX*#UVQ!>`ASQS6pjj`Y&!tj344bv=nhvgecd~`m48}Bn1iDcOaIf| zAO8vhEokeB*4^Nk)t^I(H;<{&OVE*1gJvvBNH@ypNM?ackx1up7-@dZ%v}Eia;R<9 zAFSDbH=b;}4(YVCrKK>hbfWTiaJ-^*{@a7^;t0~@*we|8F63BH^UJ`-ks>S034Iqs zg7C?K3S@}}@TnZg5JP&3sIY51j0hqg&!$#ZONcbu zf~gs9Mn*jD9v;vV=G)lV{F9O?B*V{H9(T4iEJO(vEkpoV-VZ_~A>kc-SJIf~#~iU5 zu@)HF5ONt%93fF%0+9(ao?Zd6P!*fU&l76f{0U zLOJT_a*$4nFk&bg7~IQz_wVsX7|)HWj@oBuZ9CCHV1|(>X0RxCprRx10GJa1ARdGZ z)3N$=x0~%OU=nz478E_4$YOlL!bgBzGpH%@no~B{#~SFzJS|GDAeW4MLlb`T>=`UycSo{BI~x?jok0;`5hiQPVo`xViOU=k zcfDH31S!0RjUN=h8=I=%>cBa zH{pE06tGwOwfq<;;x7hqa1j<9l?d@=K*$K*%#;b%3~=lUD4s$1BjS))#=V=mKb=(r zd*+`{BT4sQ3i0=U`sB$IcHC19;Ua8~CJxh&S=ttTz|(@p;l?-OQj3tXgAgW?>vNs@ z?>5Pb*#W$hdu~FT)6~@!fPN(eLQ9b34+w>NrXGe^{eyw=jE+{CVRkke>iPeGHZ~~H z3A8Cv?xc(Rf=dCxj1 zT*Pn_pKC5L#6k?R?tQHMF}1?A+(iNyrCC(-3UzT1!(vH0q=M+`1dNSNGhx$0RLBN;(dU_QJdm_L5}_*euR1hIdCNrazOA_n6G zvV9;88IK$}LV$G!2G9+|hNI&waz5ZlYhhN1Qlp?eT4)2(hIwS&b)j<~#716%>W@?l zc3)mzS2j;RbsD26e-0SJCo2rvfmZcnWW@W%jT`Vw33cV&yHX{4J-OO0*)|v>ZSroQ zsnAs%t&vr7LGlXpP!a?Gsrz(~;vsx_!UU4Q@BjXnZT{BV9UOl2E`M=9*-hHw=UIPU z{8x-Kl!=h^uFEGD9hp1-8xudSudgRRwufgb!Q^+P8&<$hj$MQ~6@K#KrY3GwR;9qo z=Ri#>Pv=5vgehPF%spsjTS&Cd^XH!`NB+yi-a*<1o?5Zri;S#+z=9x0I)+(3j+cp8hr?4b65 z&e{FVl_*TkVRHn*u@(T2>pxog{_Z6d{@#g%8H3bf1{)C{>=+p76^QP@9X1Uj7LlY) zu&r%9^ibi@^9-@GSyTOFJkCnL z$aAI)Y?%T8HsO{heiqGbEu8$iCKXl=#w3~$sYXL)y#D4u8m;DF(GW4p*M&_Z09g=p zF3BAmNZ(V2^RQV43QqVdVnycgP_&xx?MglYfkU5)yo|{|z{~-7w?N1WVXml&S^p+; zHm@{%*N%hb*8}2-UEPe>3l{?>I>9E)iK?|~=AYOe!M@gf7fnDo&COr`24gzT?`8#m zi#;Y5t%;{kpRRAAIsU#Jc!!tv5l0xJqhYEv`bZI@lr~4f0gyru`U6ntTo;aavHxY+ zlagsG&j^A-7y~KO9p});fzC?Tp95O~AZbls|0i=&H@lfFK)DuuuTcG207P5G zAyp*aba8b}rvHI5dSM+q&l{88(w{#oQD(wwzYIVh@v99>l{0&nxOfF#2=(|w4dNuj zHj4q*ELpi7rpK^GMBdBb(}f2=-eSLVR0~m^?WJo}SyB2Y{_&V%F-9J_p$I^4+@%Nb_iV z_1%2r)_Q*aMe_bjn}LyHAsBNuyn?;{_94=gLc}-a<1pXU_lnszaR7eQnsOvZ#iftf z1dwG5!&xzS0Mxu#I_0p^W~D_7*?#hmZ?n4952YdJYm4KOr|3tFT zs^EK#YcK{xBYroH_A(>h1Yu0ub50P!;5s4CKf@U+BwFZeDNne^+s?qB`SAWe8z?|KR)^J6{=TrH^XXESiauJA6-LSBxwHdoLYhlIhOkx zUWASJWZYJwzgo!HV%29*76vwN$_N5!_+Q~pmKwvYZ{NQMW92RFLvHQFQn2p&L*As> z2csn&lxmv26zL_n2o#ky0-8E8UsOQgB4C&Dab4Zp@22mF+j2X7|8R-4t1Mn@j@hPD z3ma}dd_NBBXjDf zDw&@nmLI=r+*vu$aO1DQ&?GBs>AS1f-+eCl<+6ak0AJAd(^ZA{T6sMpxla1td69D8 zy(N9swn&F}GT%NqC`~vIMjqJWm8lZQPqpxZe)`GO<@k$nx40q|qj^>1mRfJo~h9iR&oR334h z5b+4}F}t4@$X255>k_fznwYEyfF+f3+!RPAQe+Y@BPd9c42h6U`lhYf+1O%se*;OA zT;P>vB9Ni$!fWIdJ)S8r(tgW-cB zqnn-*uKVOy_UA(pZbim9~p0C+T2gXd01agcOv7nkf)I5pb4y9KZuA7sn&^B*L(cxM-v zCFJtZsX%(*FM-qAaFlk~1y0_Ht-`Ee4RwAmXVPY`O7@G;5$zGRF*pESYt# zi-y=p$LIC3i9h2#0>}@tvK|heM-;b1X3W25kFc2+gHGc6i;Lm+aFH^u-~A7pDlQTX zxM|0t-;+pw|0>-SoY`OE8li_B8A7!_my@3e^_Ke*K>Pi|GI1~{S-UNLI1jH>vrB>h z#)HR?S-AlO&Ai|2{zvkG$iO9 z0<>}FpKyiS#JAG)Dp+F)lS~M3Aov%sLK^h{LQm3F6s%c}xUZm8wqU68JQT;Qq-vIy zk-?iLIW_mbOVdib@#+K!(~=m)p!ZoeIw8#%588py4QoMe-Bkay z@#h>ORArxDjYR`MatWl68C)=gjjWidb3BT7A*Bhr1s8+X2Ya!P*!#0$xLFhwXqI~= zC41==H8twLp#ppbpU`_w{vjR%qC!I_6l{WwpijO?GDK-YB&w?0fS)UY^H$=`iMnit zV%7{Cn%=kD*RHJx6D@-6PROM4RMw@NdFm;w`jDR zSU*G#NI{Omh!4&0&Ck@(y%Ypnw{>Jho@0cZSD{%N9Rc(%Nn!O3{U4tw5u+gEgXUQs zi2*?EG9W)VEi%zA^P3eylLQQL+WoTKPxwja(F9uX%dn}5O=FM4*I@8=oQs#QO>}BK ze1R-2N8O|I8z94LHG!=;NZL7vT~QfYH>bk5-gg_-J=~l+eQUqHjo5-dheR4kQ+|Gx z+xIgD!NVe&tzccXDuEV-U5e(xziV@qDCy%kg7&DzG~PK7l#O+yK3x)<6;A0#8MYQ+ z`+iq=sP2@z;5i_Gmv84zjXxhN4Xs`ck9AylM~~k?$sOhd--#K_*z~JPd7Yc(`C=^J z0NT)RMM}G93{Sy%V%-eGmr9SLo!t)NIsj`{3gQqR2l)AjE-D#{^?e`gow5a*$M>H; z{iwngX73@WpQHgsxeH@NFwflk#C zg7PXwylF^G^WS^SiG}oi`Qn{kIJFcil;qnZ(PFFNt5B9g9e@E8y}MUhS<;%>Vb6HW z%*=eu$ml90qr_CCFuw;6M&`n7PoJhWef;oYpJ=$S5jYK-(>fnVx?Yo6BZU4zygmS( z@R)%CGs0A=18Fy%W+>`)PeDs52b2gu)Tk7mi@>&Rm0okx#$(w!Q-0B=O=ewJKqq1>03dW)wip*eET%mS88y z*POP{jzIR*pm8P6w#$C6-qTYLoIgLc(nLM~0QggxSYNa%#CF!sD47xa4*L6n;Y^V` zNL@)XAj)^eHHJTp>vB*sO`sDM>iK2VRK$P^0#nJ^c@Ol(_*$bzv3XDZ3|!9}nDWI) zK7#y2z@%8iAX$vzLfESUyTPuK;$NKse?E=x)x=SQqjZ&(Xh!5=g-485)~mU=r$g zLQh<*i*`q#Qlu4{c2(-XUAM#yxE$5L=%#KX6kyl%U z$gaK)Q&$YI2bZCd^`MyjJodcsi(#u+kt041>Zf57&buL2Z1=OSf^dNY9dz+x@ z#!MOCn77F3tbhddXJADX`q>6wHyI)X9_gZiDCry4dN)hz3LwJ3$}Kb=^M45|YVul9 z0)gg0BP}N~&rp?cx&U1b!wb+SjkZqIu!PPct`tS6X|qE3K#+cCbJy+bF|U!SW##-4qlM zzzS(#6}UBYoEZl5$mqe7tdTH7TmVF;1<|LaKL<&|A5xoqU{lm`^^pH&By(MR=bcB{3HwHArU)n95v4B77QlC?glXITM~_tE(h%j*UU9=S!M}GN zP*jZM0A}ec!ns9|Gzf9`$*%AT$UkwXNcJ6un@{21_a>0#OzpsF5+_-s|9HM z0tOmXk6OJ_yLWTs=H>#z9TSqnK$5!>XO~-Ae%q26s!+}S`;Fd#FC%Rx*l;x{$cRFQ z*W62p6~&0TU*F_V1uZvn>lY*J656Vv-oGGq2P97hio|X>{sqT~;7{n8ICtPTE-~wy z9a$L}!{(n3#8|HQ9S0;}aSGe{Cld)7RhE?zCuW=>Xdu#VMHOQUor5HgT9%@4H?HEI zlIcevZc$J#9ICb`B^3h3qNtD7!H9bJXF&JyN`OjQB^G|tP56VD@i_w5 zw6zUE5lig@%}Yc%(kJ=5TZ#-gpe{BVxh(^JT11iWqtj+5%okx58~HvHbV|4L^=-m7 z(APg88lHk9BJANZlpl5liHcQDfa7q{+{_9xukE7O{098`WuQXR)r@$cJhN8TeS-Dp zVWERiy9nB#6c0{RR8;Ns zfI>ANc|MlKaBbpeiA5VGCLC|Tgqn_oF<8N3PFb3mTtt9cN7^~I<7nwPSwadDmg!!> z{E1B$_&inv;@PU9UAZ2x7QNN)lSJXHMy5R`{k#fnAyK_uX34q*?bKy~V82EQ3kgq_9-~R>d{}<|n|NsB$CjPu$z>U$6Pb{=#3ldGM)xJ;YICx(Zh-5P-cSI`FD_6%ebTC_2p%SXwB3@vS70t7Le--m zmJ1wclZ%p~;u07VhGBMFnrk2mEwdcghVJ8Mq~{GErw?}4TFi+&k$AHe^(fN9(Z2C8 zoSqRtWKhgF3j$Ui4+zoc-A*E=DLmL@EM7wP)PhHCZ6+c8yXhbb)iCf(J&9NzHd=VSlIm(T19Yt?NrMLgBnm*}FZh~ZHHW0BC9k6u_Du%RGXp|n zm^f}7C(?jc)4_1Dq^2T%LTf*LN{h?Kzaagb`aIjdC#;AFyH!5>^TjMzDKFnZAp^+ZYFx!HHg6p?$>t4At-`Cd{cSi&b#4)r&v;vR{*3Rlk zBW|V~LN%E}2vbI*mC@hDnc!e!I&YdmZVWBH?{2rf@m&r;OTvpmHYV+nWCj7yan#EX zV?OG(Y8UQbdOsrb+5|%3}N>h~MnfNf`z4dBh2IDWLG^ew}NLjq_A(4F}HqVUobp>NjtQ zJ4!h>D@zPC-p#6q1~*_Q-mqoMVra$6UC5lSUVqghsUy0`qc3;}#fZ-Ir6q=ysjksY zHpcUHnh`ae}VUFet;LYCd_HH~h=96NI|F9S?pk`qtUnOkW~!1yzx zl}v7Cl%32K1VL{?`idkZ8r?kaj#sy|@PM3$7JpC?tBDg8tQ46Jhm0Kk}k+d%K%{VhV+KQBe_Wd9{7)K3mkHI8e6;O>Zs#h4N=3 z29WTBqb2>=&_CF5LGs){>KNQ6AJ`c&O2Z#fx-O?xODna`aVyBCjp)J< z$Ytjq*Y1w}C73+ZVO(q>=@Sw1&2=w*k6 zM*7lWUxh<;DN!eP>KHaGq@YWZBLhjNCq-(QHn<}8L%XUUaC1B#$!oP9V*-=Dc6U!G zWM7vDYf5qs-hl41oOsqr_esYXBqS&W{y?NG;NOTxpL7>>&~=PC0YmDOs;kWV>RQqC zTt-~;u&`B3_BBPv#>R5tT&6v+i^Ge{0#`f-ytjdX<(450o8{z;iEp0}Pbeh((L0xc zW+Ti|+lFmZKkz(T%=P9Z{WsyLmYX1GLd?v+N5}1je}D=80n)RSYJ0Y|kg$jk>!i=G z*4Ou%N;9R;!owttwz&uO{Nm!;(QOgWsn>1rWITW&e{V&Mkmm5QjBgq&rJS4P> zTz}V0kQL&$GfoD4nvKAI8SGtX7F2ffI_N_Me%M9EK|!A;J8#&-GcLp*?w2}e~`uL5Fg<)^v7 zss!vJ)&z?P6pR9*Z)HxyclEGV6r?XoB9-)E$KM{yH%FS|l8IlK7}{~hVAdEyjIuE- z=<3$+q8nt{WME!Zmbp!zn&EVBceAmRQ+9Xrv-CYkBUv-ndaJ;X0CFYQ7XASy0&kCm z|2%Z`o6=m{Z$$eD4fuF@V$MWWI1fFYz+$+;r#_v8jnMivN{ggF=)?&E60GCy4~J9G z&Z7iuO_;YI;?!f{ll-v;eRL0BYMKIjNJgGO10$6on%gok!Ao%l9bxrp1Z!E>M;DIv zY=cZm4)6jHs!33bww|VEi{`x_lhL>_Su>Z^?+p{y-K{9%iCP84(;p-h4z~gyBV?H> zG*>_j1ko@E0>(eAPeab>za2JRXW?!;HQp}_)-z>YLNb)Y_Rh|Yz?zOc-vGp8z*+`2 zf_vg_ln_VB7JREu?$}qQ zxef=CLmwjsnS$gw^)WLpMy1(fCk%a8fZUYt0N`lc5tTUCc;Z*`Uw{3zJ8H~%A~+$r z+5$^4dZ;64TMU};B||&Ax)$adq&dTc`M_?gb!}^`Mx!w{D9L0P9L)%RxS>hOv5%Am z(5=Hf$*e_$OM$SSDV~}Oo}=50NLxxNs*ZoKt5Md^mVklP$H?gPEjvB6R%{E?nn^a$ zK~7zreYQ1i?CW?k8Otzw>>WAAv0o2dL(#z*ndIb|S6%jGv%(VAD42grf7L%@h~F6X zj_4q($2X;_KL7%e36`-12=#zDq`8dT?ayCv?I>vW!ypIz4w972JTnFQRXg!Z$q_wB zMAf|K=yK{)jva879|jBJ%3!D_Av9a6sQ%t3aLg(sIDJJ{YGRs4C-0I&dyFGx(Sq- zMAhTckDg7IibXp=eT);`x_}71S)vrNX`wvoKys%p!W=}beU~C5BWrW=3YA9h@Cp(& z!PU3^>mb*r7_Z^K<bL0 zcO~~PA(gzM1?O$IQORt>qvcm>M+k3PQ2X!!YlZ965{bfh+pX@;O$wmycV_;9DZxs+ zw@R;UHB?FKw_>5&?8@)YsA$z1>~{{h=AU@A$w@2=^eAo6HYDj|(ShHjVqo~f0Bd8T z!^cCi|Bn^p|9GwcUw?Jek{#(qn`|HQ{ID{$vJMWm@-Q{EHn5cnnzOM?ii@O0#ff?T z^_xC!NqjwTTS_8_TW8&HTDW0WKfmio)3ff9nSsii*F-!d1fv2?=}|!56|2am`(XE) zrWP$)bi3gdk4?;gbjIG*?rX-&DqL@I(Z?5njrL^wF;(jRmi5v1zTkG%?CraDmPKE1 z7@DowQ}&WqAZzUDnPASscirZVYke#@KdcMC>QdQ3n|f+<`tpkBS(Un>4KGDRUJUg0 zOpMaz2UZDjSVjm%J0`E?ac|Z&@k!qmWmCgtHMv>W{wx3GZLO(Jv-B~ELk;tpo@e=m zJDO9)U2hhXKU?1Nrv8Iv@UMAiPOLIJ=eP318ZWgt&qIDI{i{Wf>G9hBSovcrwx*X+ zWUw>%x@h)etNLR*s`;0N3?2SC&eeN@;dw>8Kx$%fZoTxQtI8#pB31|XkA2>@BV8`| za>=mivvKFf*UMAitm$wvVr@{^Vrn>ggUK%|fUtwsZ{z0;?1{J=RIM60pUQ8qWnMr@v0P0s?6iCsJmCzlO7 z@@{c&IN-Y?w6e6Vd3)I7p=&{Ld&lz|Sa*dQl)i|#b1rLlU$}qBNmQsSK6SZmWbOEJNn9%~KMELOZFHlsSn?hD?jJ%u@+v4$C}Lk}M$^Gv4>N zbI-l!dG0yqx%YX_>;CopvG;DXT5J7&zwh@mecywoSMhWD%YUuYwkrzn>GtaJ>}YiF>}P9V>7sx4 z`VRXov}Y&O3&xIewwH?>DePE(@5f_qRUV!v7X4qsKHAZi=t==sKWlLRw63p5uJ{(I zLhhP2=@Nf&w=L0+*XE|gS+&nwEF@LGdh)H{UQ>Kl-4B&}A;y0))RmgQPi(r}sTpfI z;;OcxQL#(iI`hYCyEhi=<42q9yB%!{0{&ba(~}jfb5rPJ8h78>L}xxc(y|v$x0)Tr zI9vtH_J@YtKA=eJzP4+?`laza&F0|c^LtqimBen1PF*muO!<3PGxf5YFw63s44O+@ z*J_FPIcZB}GAy4`JKx1F)svEb<#200QmD?kGgM;p&Q@w;;Ug~jL(Xe5M|@A|ZM~;O z^<~>9vHN>yykaM9^v~6zvW{%~&M&)XtasP+T}#+1M-C^MgO>UQ7OWTstYU-@q&>H; zn4;6Ed4pYz%67T;5`~m$s($RlR-B3kyj^P+rn7GOGN#x#T434MK<$@x%do#&yP!ux z+EcJM-g~3r&t}Pz`PI}K%5}j%9;e&YTW2P(WVMMNG70fNzw!Yd4*mVbQ(L_|M>^xa z*m6@0f)5oTB&FKeY79j1)mao~7$~V*U6hepU%k%n0#CnH8HvUGORsv`zurY#OA)pK zZ)5EW$`*k_-5A62YiD2Xwu!%FXJ?+2W@uL7cJLLWZe#Qcqhki+hb&Z2__3?+7x}Hf zec)l(7W?1DgMTE9FoKg^71~91stc*rh#yuN@v+W0mGM(~nSEaU@<#Jg?9rpk(|s;w z=S!LMWgA7i!c_a2OGFV{=&FXM4f{G>Su%eta}<QAwuQl^iy{A4o#5r8~ zTElNZ*uA4=U5AIvjnC8bcZZrQPsC;S1_yO#C-1nIAo!MX#LZ)=EsxZjN7LmtXX@j( z*8R|8Q`VYLDNpUz;}Ja6w8%I@_08wIcI1Btb+X^Y_2;RGG%PN8)BZdA?AW#wJlwBw zPdfCZieKB`y`iD!){kf^64?sF>3pg@4Aez_LsBAjhHkiE1LqCNot*1Ku zJ|%gj$3c~0nQs{ecDb1eK@T=;cp9H%@Z2^p?uSGSB3NzeRi=Nz+k>aM+n-!7&9-^^ zyfEI~m78C4U`Xop@*^=e1+Vg7(BCVCwWo3LY}i-yx%XSDw2aaVR~;<_qpPGOP3 z+lRowrOIttr^8KsGUaS}rM&H;eXx=J`#Q&UFHgljt$M1tZ#XhDUnBQ{{EBlKyLFiT zr=D(1oH3}a%iBus<#bKv(OlsPV}Z}>KJ<(?3fDZ)yTBBbANY7PjW79(nK`>t_o5}| zQs2Bf)5#c2kDb$=apqNVf493eSwJsL>IfDH_dw(7+wSjDzqN}!YLwU-x6-pK*MGj? z^R2J$6Zh|F#@EHhj(B{n*%w)+Z=YNndrRmJI|upnFD~}C4+Szj&ufs&B{}vcza!#j zM@Cf1jgHpt54#$x}Z zd^%yhqF{fZ?tr`vbBL&#g7*UafZ zTc^USr=O^)34D-qSe*33>1wtk8~>r5ugUjfelVjC#I(|;ihjH4=5ap=HHKSb)Z^s7 z4H`$BRaHF%R{x#x3RUEi>%B;`zYkNJ+(>|FlohI~X)kEYr{=rF!&l@!y54fXNy|g* zgjR>XRB^76%MweQrO16G2Wz>?7fesD@Ob}SB_xJBlqnqvrrJ|CTACxC z21?gh<_<4-S~s$C*v&$6ul=ck44WClRab`Ojb;RR{F*ji?G35doof=i+}^S3wp%%0 zTNX8Q_;55P18WbNO?$N#o7VMj zi~5?yFDGvqgz93^qIXHF&P!><$bPmKUX=K=r!;40^2(jG__>Xx-T0D=ItdkHEb@ky zsX^XFs;nE%`c_}MR1={Z?L*c(BC&3BzTCQ~6!|~ir~k_O z%=~XpHFU#yw&y=S^1QvglKb>gGq*8k+T#TWQ)0G?6)w;>{t+*2(kEljNH?9K9O?K~ zR*e~0AvvN-j-SW@GAB&}!BbVLyu>XSoFj$lj$9ag5OvXB|dY`!C=n$hJ zUz*SL`~Lwx*1uHK#`lU%wzl%kk`31d_6G;t{`h3QqqNjS znxBd@ZxorIC=M6!KFzuMm+4>v+h6+za@+sm;pycSNU(CNQN4Eh2gSdWc0N3xp^b>P z$l;;jPEnDvS8q<_c}kw_ySQ}R%9O;vk3%N6d^p%wiqxQ7$V{Ctl?$HR7#@Ds%y|8% zgUwRPlE18S$6k;7PgR}$mtehEX6-fT&r3|n+=cyjP=Sr@#8AT`O_^4O#&&jO{nXDF zwz(LpXh|@0^Yi2V@V39kbR;NjMi-La9Ml9Hm#;YkwgHu+7r|$?B5YB8o)brj?gI)S0$%8n*px z(SMziwsg_JwEp|S-`)6(I(d`#y5z>j?l%-LVDTyo(R3S9G%RyR3X9is@l#FRRm3%6 zF}2g-hZQzUk+Vr>FbD9sW9JvPIGMi?s@U8YzhaP zcN%$~5;Bmxusv?&xax_Z^1nut!h4C&xkYW%2j=pamVTo%t7n2k;;4gG9o#p3rnlhp z_r{HtT%2vMeVcXIZ(O0tB``9*IOpp@M)ms1-h7fVd|hX4M*kR%BZoz+wE92Cr)1rW zyrJE-=~`FHa@2tAQDgV4YzwWt4nk1yWA@nEKVI$l?tWHq+c6g(QwfO}7v8Dt*OhNq z&bk2yiZEr)Z>5&i>q-5l zJ+5(3!~o5W@JOolo!b+Gi7)em95)YIvs#&@A3SK{94uk*;*0qb&wL3FwMuW%$px~R zk-rck_p3mzgj8Fn7Y!yRdUZ{hxAS*}GLb8s8tk+=yatkAUpD?S{5e8UFvE^gpmRW_^&!$6LHv_cgs{ z>dtResV=>9(UQQnl*YVU6);}NVKFqWACjyzo+-B^X1x@M@DwlaXp_%`7v84 zobD6z{L4XmQB^-QNEVgg_*3oASl>x{su4W?<8__&^vDbQJ1eSqKZZ_Ti1|_DUY;sH zb+@jSmUoVKSnJP6Dbr7Eaznmw3`flkHm?r+Gvm<48{MUACWWLq3RetzuTSt!n)05& z)!jc9d(0=SD`wNN+zf98O^LACu9)oe>SNoQ3p2_RjK*8}9{P4)H4G~M+d7=jY9@iy z`2rU9!ojl-BCd_G*t^kx&Wx&la~nHADMAC%K02FVx;*J?_nl=N^=WnuE8Q1ge3NI| zG(0VqUG0=oExLKJ?d0@|@&xwQ!elP_gAX3p$$z$9hLS6O!sBaeHTK%jW=&68V}Q}7 z4-+Fl6`R}Q){_-y^)e}Qje%Iz9uy95%;ue9-!;5lc|Dy~(mpW2I7x5(F}GMHk8p5D zxrl~Us3CRPa?Nf}`} zpK1xo^)JVR`*JqtWCX4cvCh-6NiMwIIvdaVt9FfUdSv&0AkOJ{%BVqX!i0tvqqTj( zKBwiu6+92OM4vivoci_Lx&*;)onRe}yS*8aza@l!f1<`q*&hE<<|DdTl#&Cfh9 zrPKO6zSiwJR&3CJ@K>$C^EU-MbH{gK-hoDCdL^CfwS$G+{ozF4yuj>!+kF^6qeO?4}3imsbsN zUhfD=G@QSU7N;y`3 zFu8TDIB(3Fjk4Ist+*|ADZa;t=1TVYz9FBW#U=aQcIdzVf}1t@!*&}6BuOqt|FB>X zkrVHKvZ^{GOW4yZ@yBJq>7Qs6>RM|?9lg@8XDjO)k|6j>XYlO#GHLGa&SmHcQ>7(J z_xm^MzAxKw`(&SIWfa#hloU_Z{_`9t3Rg;`>|p0$PZgizE1l=NwyHY(P{}@_Z^GKs z*(U008_sVc)6g{l6%QMn1Z=~A4p?y1cl4a4iY%i`W5m@bOe9J|?tF+6V zV9)h^Z(r_TP-}*Vr$g1gy6Q=2>g%SZq)S4EdRjNs?Z+0n(p51tJwz+&w${(F`l;iK z*|v^O?p%a2W{+AoXGq-GMfTNNae|!3dPpO=ZQK9imHY}de?*S<9JUL!zmy$dy`1c2 zIXd^__>@oo?zR6(4$42BD5`H_JbQxU)T{BGHs%`Xo;QBc5E^v-wdryTpSn5ZS@iSR zmCf^E;-TI(DN{@-Y7C=(4h~|BU%1H(Mn%m%)WU;t0>jcBbJBa4PNJ?mGRE_FS`!q| z|Jh0AfB)7057Trq5~m6Vz5^YJsst3eUuI8QlKv&ps7y+Rdw158J6dF>-6~ zPqCTzm*Fh%%vyBBzgU7dm5+-Jnn|k|YuQ2;;m{7tSmWA!z2MUv@23)8|mp1Ko=&(C&tGY zK>&Xt@GwNNmskyh4pf%~Pr9oo1g}8>5-uTx|JvXQJU&su#h)*F(ut1`IL=`BK~)qiqH`f!4aeI+f)Jh?esu<*4c{EV zbcqErr0}4#FF^Q1FC#I~2M9+1A9u#m@+Oo5;^pLFy6W2&o~`03MG;_)Y@pK$~S z_KxCJD_yIav%09-^W3?`%*ui}j0`v<=uH{6j$u}piXVT_i2&hM4GR_H~bj4t5)|mAlZRaMdD7k z|MFz2@@A-wzXnpz2k6XAssD9Gc}o&dcOeGZ(w!FMv(MQm#D-eZ)QIXa@ix1S?K(5KEUX1-+#N2BrxwXK~(0 zJQ1l>Dlujy3pa=fw8CPr9C*M>a+{ct{D}i$cmiD`_XreDV%$>zmy)CT8QpWBwr#^o z!&&reNQe;;$NW;jT#4R3j;D!yZ+9ZCpw9JM`2{Qe4)*~f6iBZ6^V?PJd9G`KhWqOS zB~GSTV~#lFBH{*b<6w6NPZ+*3!~nyjVbSBuF0|_)5gs;ZQy&gK>p!Rey{q z9Ds%!-;fOfGJL*}(GCHDrO+UWaqRx|X)R6MC~qg0cnK8VkEpBPCwz9xwv1#z6lv<7 ztp$;Y=7;bJ0ddMy>-f%ef6hz)^tOK5q}G{(0b0Aiy_OMV?N6x)F58lJ*Bh1;}Mdj``n z6v`uQ{Zj)=z3MXHy7k`X8?#isDI;e>4-5AVgyk9ITA}`h@?~{EZoDZi{Y4%4dO0Z} zA*Y~qs!^L}&p}VlEtrn3m{x4#k++*~d~P1;>*WjrEkXT5sBbG}3FRql27TRdMMNUv z(j)w9^}U4g=gCQeZHdz|5OlgUKyUH}5)eR`YlLLQs@~|7q7d zh(9rNWOWm*c3>+&mRSh^pU1N~=8JM0TOdP0ZsnpOK0ulM`6qftcZ$9H71qY+JBF?i zidQ_fJN(O;KZcunb;HBMg|B$l_UiWikdIGsup47Go@&dn0V!^1sh!Q_%US)na93g0 z5CEpI7uP{d4W_-uz?ke@7?EI+3e6$ri6Xa2EeIk(>SG5O2wY}>(|!VQj3-llUnvMq zM*ub^YvpSzJrSJE%I#wl&)DE_zlOkYkud=MgAP^74-QGZ^RNu`;-7Z{HW*W*xKx)@ zi%PwTJ9ZPGDB*I54jus|t<uCWA9tZUc)k&50tqUV|OKH|23^(5*gz%4kS)TKC9Ru>NYONHDLnW zb~JeJM8q0Ic3@6ZsGGUDZxZ-CuAjj0ft)v1koS?z(hAc(=s5m|smBlO!&b`4c~HM?%DO*S z(A0*6ChLdkIYUaVjrW+hT2EdIwvq{a0qK^H-@b|7@$9j?)$#rjUBFPUiF=4{Qg;HZuya=FrAlkhKydw;{@PND#3G-yr%Ru$N z2C;>}*q`Uc&dR-$(1qy4S}ZhuUd7 ze){wKQes;+56}5fLGwRfO?u2|+Z-I+5%4&B?huGj<>ZsK2vy&y;*k=}@dW z1O9LQzf1N1b3xm2L}hsPQzaJtPW`!GTP$H_k9NWp5IIrNfe}J-5x99DP=2Z94VCQ~ z?{43mU>xcD3z2e>tO|Jb5PuaS$AuTlrWI^1YxZ~YjGx_FuVfxYaqchG2=_xsj zw=_Np%%pPg73ai($3--ufydr`>AMLjle{F$lr_6@8k^p9e+|4DxzOHZ2V~1FA0Jg*8l3K zPt6Z>xS!H-{xm<_qN|xwSD3o>EO;{)t-8)_jDHM}Etm`NguM+#4US{bnJnl1p}-{*$c{EMfS#rY}CauX091^Q+A3A$If___}u$ z6)FVshS)zwR3%Y({TLr_1dA{wDM>WF8v8_wfEiI)pz>&h<|!O-PCl5Zi8@3hUUgs_ zEL*zteb?OhzEJtZgak>8>W$7|;s7G!#(W-k0mgVTezUMx(nKVOq^C3JM$Bk``STCz zdlDByw5W+dkni1FiE#0?WW8|%DlWq>^AN{Lz~@*uCY_G1aR3L$yaPBqYXG~a0Oq3U zMq0F7h!UU_$?Q8)l+%t}HHL4T0Nx46y0s8Qe(5%5Z3oH;x|BK)C8QvQl0F0S>}aJ# z4QT0VBrKq?kc3Id$Q**@1!NTd;bs<40d zkruSHv>0DUDqs@`xMx)ja*8I1L64ZqvK6tTqAZkkd+jNqD>MV35o+Qg?>?Ti^&N1e zVHHxy0wNg~p?DMxg6EfBJhcs|#lG|cCmAXaX#qbxsgV&>pneDm4OZVEo3t0sXqKpp zpEbG~%7oyBBThhCf?vec=moKhKK|3DnuaEiNLl>Me){w^7>QYDT_ervuY`o=9QKfc z*0i(2%)0!U%{{GyO&7$3gx(Qv2B<`t9N){I?M=z;um@K9VHBK)4VV!9d^i?i3|Met zeKT^Ao$U7_wqu}O+(9#83hn^?Zkv53ubG+IF4MHOfnClC2?+*F$uD5E%-n&tba<3n z#QrlN6dR|>*1YJGuTWV9Z`;=Pd-~pEV{f__QBRO(fs1?< zQ`s}dhblVmUfD{`+VlW(p z(U%eWNX^5CuL4jm2@BKEX)IR;D4IbJ9y}-2T}({heMP?Knq6R7H-S?1`P_c~ce0hD zO2B4np{S()5YPMmCsbK*7vl&l6t8o5G2#xvGNdZeo-bd7_wCbxYf#-9HCgqz9FAA% zc^)Hd6y12ob=6G8rmW~+W?~X>_poXnZ~^1474-dd*CB}T?17d^qF)cV ziV|S$`GL;WjXyiJm?v1J_#G%rND%I>@tjKsuHFI9ju!xYWx5?rWmxp#VETo-eLuu0 zzsUCR5WNqHIfI>;-W?4?56rEiliuijQYl}h=Jyyi}|M-h9EzN9M4dhO{FM+n$BChr8 zo0=7N;|h24xUa-}4tk6I{$Kfk|L`kS_+9t$5tfvEnRbJPQlwfoz#1Y!1p~aWaawy6 zN#)d<%;mZ#(e*K~DH@uK@n!aH&E(JpHac9+-NlrXjY4VnZkIOca4g%@n&X@ZO)zS& z4CXHH!8~d}qjdeo4I5Z$g+u8D!PYb+`+`kvw$|4?m$?~N{7wW6J`gn}DoV=AL?k7t z=vky`K>R~b?hLeZYZ1Mx`VlNq2WnitOw6#kU~@S+d5OTu@hx_BJC7S08KppRR+HE> zKtIj;Q8z{n4D#7NKGyG_9(A5;!Gp^gvHiwkAPydK1NN*G(h3Kpn?Uf=5w+p=!4N7I zoYNeGL(tbDa|Z}X5QUh6f&%(@Ur$dAsPY4leMSWY{nUDJcY>h^qXaf!9RzS1h-gPl z%=3fDmF=uViKn+xyRqmDUGU|d|k>2!V6+%u?x7>!xH00BZ3 z0|}D2z_K5O44cYSVQpAopc6wHD~ypnuuL1RBA@%WJ)7Vj0rYbU-4PkCX~EjP!#Ytt;9gY)Z+kJfZqaM!eK@U?3w@sO4OA*kUNA99BAn& zNYA@4+}?~_2u+7Lu!Qx&k3jIM!wjRRzdsS&I!mZ8GfJaCEQXRDW+DT=Tl-KTlh{N( zefl}j)nPcTq3G7wv4!x`K|Iv{xq%ydr~gmAyK(h$u0bzX$^)gc<-%Oa$D3 z`!v325La`MAO$yXj^ZgB#f*jdz#{TKv~|eiLt~WPUMS1YO7XJkZe&Ts?9mOh04g*k zfZ8`qi*Q;#gfWiG0Ua*LoM7;$6oAmlY_LG_3_f!uR{|EHFjzR4gCQdCWora8um`Ri z=pNERNloFm!X${%x(O+fpD`JF-_g;Le%A_8hd+jg-=nFJ`s3|AnJR1#$q4<$qDnk4 z{sa&E31ph}=Ao-?Eo#bLs@0Y~FO@g~7gs%G^B+VBk}~rNvenBZBFwc>r&yR0r0awQWuIzyu6C$!f*WCWN1ZPnK*YJ#--aO(Qx14WOedX;ZVJ=0Khj$aSVK@r!ot{~`~X_x zD*?~I5wd~Qu&|Jjx{}f@Pzht;E07cP+#d`SQp^#b5A@^qtzN9j@)wv4aM+vFh3po5SaZoL5wd6c@{VGoqeVVRtq7gsbpE-#DG!{ z?d82Q+z7}SKbcF>1Bhknzd|muwB1*=4BbEx?fT!g2LG^U_!s^{me-xqRE*X3YhRM# zCMp&xh)ZM&j-rzZx-{SA?}v{nDn9H{P*5;rPEStm1X&8TTO-Um4F(TXK%TUrd4e&B zJv3nDNGRr~`*SF<1b7ke7&KB3Dv*xw2}w zq!M-yX(mvPB%)v4iQyVj3HBKxW+P_3)=5L`N6|ZnLyl3}w)rvUIYs0>C|P$AAVhDIVDor!aTPJMzVCRZRsZ^`XQfykC#g;BB?g>ufxF;QU<2Ooj_`Cy1R zuh1zBsjaQm%iM;qG<_~n>^0JWQXRO7DbF8)>V!5xEnqDlpGQ?Up47henagxRL%Gkt zaKQq}cn?*P6R7_3WA_|LGh0F_swz&-SfWq27Er=I$c}Zv;RDP-4j~0*wX$4%SS6^0 zsGgu*<63ok5%O_FyY)h{L@)z{MSGj1d1aVe6RkoxkVc~+2S^;HE&MS!{QTABK5F~0|t}3%&>;4-X*g(Dsaj@19Pnp9Z=3t!SqY) zW{j*7A&UaOefhh0@3D0yL6loGf#U*kC30WE?xkU38w_b-G$`R14XJD(x9fc3go)Dn zdDe5FDl4n0(dY)M4(DkV>z)*UtVQV%6cC`%J&RyVh<`suFLYe#nMJSLF5Avz58;Oe zfDLHydeBfr&tx%Y?2SAQYujirNxL48oIZ)$+q5I<@>66i;)WxG6ALuAU_~aObdZ}J zt!KREqENE3vpUEI`OVZRQG#^Rsx1GCvhF{uy8fng%r-1q#=#*~2!4M;M^}d3LvnX0 ziTLq(l1N}Ut3Kd{bdN{QbLI$&MDvdsEdA(awV{c37sZYd#zOCrw-eEo(9r3PhPY+qbPC&%4rl*gD zcDu}uSPW{J-TsF{_{pjxjW0W{kN3}s38fpmnyfHo>9z<0nNW};s}@`85jAx3W*3SgchbAvhY zIgV=A6ea}lc)-9XY6Ymo5N1i7#Pf?F8VV@yR05!mQkjEG#5dE+b+PlagN3&hY)yZR zjXC%AXBN&T&y7UQX9XF8+S=(ke+ANlWj#|}^K%#dAYmRr1?86H5RhM`#`i(u1P5^^ z5TJL>l22-dW5nrQ=+kTDCL%~^z*M#m<%Oe_#qg&RQp^tK^zXWYlOXehq1BsE{gm11 zkR=9^Tr^ZeQTeH0U?_9$gBKBvcvz)vO2n;`!4QS>s~d7n5R4npMw1#7B_nwuzT~X+ z7^)D`hY=w}um|J$NlAeu#z^r*>N3vScHff=`kUHigXr_@e=CXlL(_1gq)t^Kv)62yVHTB%_!obSinR$-{nkX7m*X7 z%z#u3~6(10>JBxoK^8cjEyKq{sN-N-Bo9ex?tRJct#f=WM^R`MnD%UaFC@ zKYg7LgB#EHbBXqS@@1gFfFNkLo(_#)>!6oWU@AaKNnNs(FNz)SrE&2sc~H%`FE+ zO9mdH!`<>43T4?rr6Y%_dKEI9Phnqn8VXz4SVB~^L>L-i+gU?~Rp&~H!j<-aT2CcH zcqI|NKMs)}lIMZeyaVeo#1G-f;E{gRs{|S;!nbc%Ak~QR{8UqaXBT*l&(Ufap=Ah{ zzv4!pMiK1x>DCS=BNk;G*4=2Gig+JJ1a44UXFyylbZ=fP@?rmNFXsoOIJ(&397^1M zF=C7d2^iZC*B%IVtaU+g`7%gz)S{B6LlA&$94H{YXtB)!HQ5>NrcK;;nV^m;2aw*& zZ}Xwf7sIIaa~yNm`+fS}?PkPI zp)_pL_qKoInxPzkPQrZd;%|tsO-GB)OJG@%J`XJ!kNZx<)R+6O{hF>;|DU7Qe_`45 z-*1JL{%;4_|Lm5C2H(3^+pIwGI@XIx3gw(L3`xqXtF_T7@C%_|1Y+Y9PKjxH6Lm%1 z(Da936zVH7Z)UbDd=-#ScJJJ7Z(>9VWaP*V&2-**EeM&h50Tj z$tfhp&uEDu`V@~)x62G(cTaFojlaRze?#PYqp?!~g?Ynkp`;^noD2+8a25*To^47< zeGT9NBLXF%i!`Ql^d5SH#)q^;s3oC!MZ^195rTf>osw@;8f?zA$vbD)H8ZqRL%r*v2*wYO#1wgup2E|@vTrrH*UUY%zYM9j?8PaE*J0pi^aqw{Tve8;YpH?8wc;w4}-_I4X(;<$s}F zyG~--AiQf=BgS)PnQc|_NUpIM#6tm34JU8p*d96H;3^sprG z5WCMO<9oEi-XgyZdPK<>DSd{I6cQ?U9&fw43J;quJU5#rPLVu=3Sb#fF+e=wQxmR} zu_`8s`?9Wp{67nK%`Z^6w;yNya&PqqG-mEFSSW|)7os<#RUT7u zBp4|ukx3XRtdV_f*C+QAdM(8du^FDm7Z25<& zvkRAd^`ZLEhFY;f3WjkfkypO-qIn1{q6VT?;)3i5T*H%GzUD&xf*h;-Y}w7Ov^R6{ zBaHodBp1h3=v>5EEK0kNd+#PzMWc#Mt^M4(q$4}hGw5)-mlu7&ur%BrdiRMliI2tAOUQc?`Feu!8Yk)NY}AU`+u z_<0mkCrOPuVACS&#^O4KckC$3<3mk|a7v6e5mF)eK?OPnU(tmztp9U$eNJU`#vEoR z&Nyo!x3yI|9;RDi7?(LivE{(V6$sfUaEyhQBsS?nW!PSR4elZy0^(ACj-3)~mW%Bw zR#gL-Myy3?iajAgff6NDWfEGKi_qR|SQ{bEGd3}yLoB`WEngxglUP)-@SqX8*($X`Fhg2mQoejlgdhMr3cOpW+2VSZ4{qE3QlFf;=UT|;+w4c*;y zHox;f>-})f*R$Srj?2Bavl;fY_jAW{-`90L27i;6#D7Bm1Oo#DU+U{;B@7Hq_50%y z4sfNxBw-dfJ#-M0Qh5aY@qF|n2>ATi_N#^i2F6pO`{MydQVJ;s#%m0z&!1FWQ+DRu zJXOZ#E)EA$>9}INXy3fsd1DYG@$e<_`P;par`6xBvo*6>6`)>)kxDMm@gmmB(8h7i z3DsF>T;DGV_ML|y(b0+P}Qz=z13;YZ7hy=0U^$1XFSEtjF4{!@z9KajHYV zh?=rAqyICpM{oQMWJPo^-T?m&$_#&~et+c|P7H?3{kcW|h5qCFACbypGT)yc`APZE z-JcEEAD7&p-{8?di2Lt0&F`9C-2XCr!vB|UqTybPA(`Y2Ie0!$e`dPeOa8Y~1A6eG zvwL$u6n(&~3aw-ad5`z6zJ&ck{dPHwnmIUV)t^ZI5lFYj-k31%GbM|z$8P1~%2c`O zxxQ3elq_Z;?8wcIAxF{s3ia3HQ{_XAZo_u%vc$q+ClU3JMwJAdVsTi9940S}nBTRl zlg91nTe%ApaS>e(`Mfmj9p-n>5=`(xsXuYn^nUCzwsbKZ_xe*%8r=g`5_9RLNQEyM zM&Lr5N-?aM$2RV~IXX~xm``9Z=9Vx7_$zH!URvNw2v@`SZ2`A;g3zC*vi~luPDo?P z157l8m6ezQ<^HIapv@`^HeDP*wXHg5)R1#r)>Z5a3RkJr)ZG~Lfxp}t33#*3&WyA_ z2h2$zWZwKDb=;XU8d>bnw36GsYwTr=#nip+ipWc$!qvtYt@gUPfQkv%F6U;Jx&P}F zqkJ`t?cx}9u1xw#v*a_pJwYx@sBo1IX+Jdx5(XJRnz7*zcFB!=%T<+ac@T%4tf8qo z<3~q)KNg#L^A-+%)4ure<)g4Kn6Zo{9fj^N{dyl9Z4PEXMhz)hw_=}YK@v!IwaupU zD_vgD*0v}kY&Dey#mp$wG>=1=gp*jxA=@SW!$g;UoltsP$*r40(w`X@H_rM-hg*T4Ys$5wSwQy!g-o+E4Q&t^dcuY!) ztceM|A1)z14-b!_h%d(}i+j8gYtxat*C=IP_=WFIP}h%tu!aY6SZZj`UYPsOrP!Q| z-d7bNBJU%1Uq5{#2O{5XQK$faE0g{M)cA#-Mg{l-|F z49+?mrbbw|eyGZ2qd-H<`|O0Ny?()Z<#G&hxS4_M1my;LWmCV3A3qo+M7ZwPDa!WA z@2QZHdqGSf4N;MyOk#1#6-GA9e~~Lo@WxvfJc3knhv5(lBTb;td*0yPduDYdG4&rAF9V!cRMAjBD%WBJ^e zvNF!`LXFlb({wpi)%9Am7l`I~eZX|RKlM#dC9a_3pPZ_=X!&+vj_=F_Xid z_iy#knpXC&iG7Rbm0B=!a_Y!H{=N|FA)&w1tNNT779YNqmIlOMr_*?T{h~>pt03?^ z6xHcELvKuf@6jMe*u%x1=&h0ms+BXyu6^Q?$__G~0c-?R0CkHQr^n-)m2hzS*GB_A z)g&ZwO;B(X^?tE|ZpO{fxF=YUVZMWbB;3Ht{hFvA_L2*vLsK`*84Z|sy;rMsnQTR% z)aJscqB5P0*@8ry$L3i57_o2*)3F^>RaAUlZaT0Q&Nu&>gTwvy%s*Quz6^4E6=V*) z?By1;IDy0H3mn28kQuuSEp9MGMo=k^2&T$|Zcmh1Yd79RWcu)-#fq29c}E?{ zQ&L~Q22FoQn2*q70H4TsEL2LMMZh5V6ub}pim`ABN`7>`=;x=kmvMCIe%9X+PT?21 zvb>xi?8d2~p}|i{fH4c^1M=5>iUsncOTs`#q0P{7L=DE%>;o;sl4|mxqA3#XoMaX= z>7n2Agd)7pW!kA_!*MZ>wKA|VIp!jzzcy1O`_#k8EmwRO{qdwnzL`}U5)Cy1I8Vtsj70-4G9V)dNYigt5q1}E1G$3>K za|Ozm(I(QWFO123JDcIx0dzuQ!+S37ZjKT?Y@^638i00$mr8H;tE^}~AYk{4}USE0G5`ypw6uhixV#8Gwo zP(d<8Xs_CD8-`&0F$IJnDVGVJcpw2PJ6;!a@pwRVeJ$B0#~XczTi*wh6`W4gNB-cn z0~WUE7$U)lVuf_j&4#4tGVn#y378JXOA?a6hw5`E`x^C1i|=i_$oBTA^z?xyU#!cG z%*ed_{0tZ@!g{8P!)mf5=+ZK&<79JWbD~%;zT0Uzn1R!zH{rK&Vq)SC6O-tqr0(+t zZ^EWH7Oez!1F<&MuD!U7qOGm1#TE0BoT#)k>azJI+eKUc&hBo*n~MV@1wyC$pqCA3 zN_z6}b8S}`m@`w>B-Z4KFg<(XdwQaTUhBIgO!DkO9-@NH(^``+=DwboEQTH7UX0VV z;VsrP<|&SEU^iVC#r5%`cc;akH5bXOe3s|_<7z~zwbL$$JFk&>eYM@#!gr7n`f7_^ z-!8v|(b4uipGM-iN}1!U4o-3}UkpAr0bVQHF&&Bz6bU!|gg}_5u3Q~AUuFv7!Cnux zjB4iyb)4FeXjjIWz>ikD#Qg?D!4|ins~u5Zgi^Ln@5n_Vo`}VQN(ylGn+xNj*yR`n zkbt(tb~I2Wz^}`!t#>9%n~}uCLx@zN%)Gou=>rq_yED7a^m0irJ*i|k4 zUe{;Vz$m=n+3Me=j=(Aw5dS7+yh!)-^z^xs^Uk-4`*r1q&zQUe(|6?Kq~VADPc37= zd?n8;eE+^FkrzWxT8}#y*X6EjL$o#`H_ao$T1WC0eMf`7J5HJdYB zwy(4y+lvtKID1Irk|R;*^ta60`viQpS5X#DDZpuwq?q{yn`VEG!p+c#R`Uv@tX!*K zTn}4R5#{^k|}Qg{V@CYgoP)(8JT+`6OyF2%`Gvui`#BfXz>UHf_k6GWD0HHM2_PW z-^c`U9QU6n%F;~MFw%~zR^7Cujyis)^T*lhD~huAJHM5;8NF%6FW~ELo((Nw_-wIj zHeP#*%!|XwWXK6~HrC(XB&(a>Kz=n<`TiZ7#`~Zd*I>kP)MC%^OXEj6mLf~#LX6bf z;c};>q@**oH#AmcEr~$ZGd4DEUaYFBikqI+3wn^+fp2n(8r<@$mu+>BN zNj;5VMi!`ff6>SDoVBxMp=-@oEIR$;RP~5_R~tWu3;w=&h{dlh)wzJw8XJSgi?~m# z-(hEZ%zujFP;HV8|MHkL6`%*5QsRPC+7DC&wa$i$RSpzvPIDhEa856VgVNZh=o@2_ zDyg0lvB+6l@26nE8P+x}=3eB)KDnNVT7$v9w)9OA%ULnNI>D(OyUrB40k$uQh!Q!B za9RTi358x&0qLaDa^h$Yv9F}APRbi8uCwrOoyy77Bz*vR*Ihv1vHhW-fSr*_%|frv zPRi{4wt#&yk$8%+LyfjZ^JNn`f7N@Om$~fgcK7r!vau23e1tTn+caDTw6(SU z221dt0Ny;WNTP2&qtW8zu!ot>MtbuwSMj3(Z*p*@X4mb$D5l@=4Etrh8JHwL{pN9n zD!!-OgSa*4pGb+l7Wg-7Nj!7%SMV4SUBdV|R|n`smGA)*KV(*27jk1zs0hQX&}2#I zqvtLbs$fpSF`;Nk@fvU{KBJ--E2Ijk#gu6U3an zk4w# z#CA`^G&+U!^0RRT%7gG=Ga?SU*rFrC$1K20>dDLq4JX&Hq@GNR9|pOSr@kV=D_Pqw zFjTj9MsUw@@DKG*#7D08dKa5$6%he7V93D4@mfWcwgB@{)9FoYoR@ahImWuvp|hwY z1!9hRhZ-oyBKfgg)R13`bP&82K4b3s*G;~=>&8N=bPNo*jR!qFJ!I_q9|_~%e~U1C zpz7wwRp_tTS#3ijBR{qLy_pHf@XEaULW{bjjt{oal$fkI6-Rib@6=eb zrnhV@P4QU7!t==dswXnv7dgH0e{pJH{Nl?Ly(6Wt`;JoA#+UPr$B~vCaS}e`KOw~( z_d>8wsPQ!lK1(qt@3ugiI{dD+bg)JA5%Il`d2C$u&&i$~H>RsC;d!gZ!KUG;o9L%X z_iU*NJ37-c^r(i)x?IQl*QSZlx492YbdZ{AQl~j|R_?p#(n5XW0TJPeJo1Qwiut}j z={Ao*qFfBW9+T}V!~z+9c{^ujrLGpN(8jd<@}Rpmj>$&8%fg#e1mXQeZwC*L+RJ$i z3?@x9p8VsF^%oMsdBkn>aroOXpm_{tWD;TNa)M%eep}L4G4|yoPv>|;CF_U&@L;J;S0L} zRU1j5Zyvu*IXIJ+PRijfN#Ldyb^AJbGC{acfk)mrrQE8FO{E}mxDi6dY;Dmz?6WOd zA(}YP{x(!y=JgqA@@jz!Jw_P1?Dqs;ReB6`N{+Vt zCl}Pav^)eij##o@3pGy8Kmw^7&oey#aG(D{kmEi+Az8T96z4qTdY2WY^X*#`g-zYi z(pBT_u1)hRX1Ni5^vy9M79Ex46t8TGb4*AB66UXlobS!0*8V&J>eA+^L&9J84Lof3 z!lA_dbnACM8NtMAa=Q1O+bGkko^H+l$4j+ncO$Q-MpUUR>XK1QtpOaD{zU3$3*NSi zWkQ46y0Y})WdT^+SHc-a~&VUI*RLuiknBJ4P|M> zD5p9d75PKoK{q#DAudt=Pi9(|jHSK3J*!1IQ?vV*&J>ns6Asstlo2wa;v@Qfcka6C z<;ifZxx2OLC9l?qVrduk8t>tcds;85McO^I&Pq+ZMO?xMmJ09kSKDUTEq$dFo=)_mh_c9P+Bh|BXw>gVN>stq)Ot`4e7%eG`J;`3Bp_Jb*|FLPjN0w+SIzR zkFmJ4?*bHYJfbu)eO($-Kib<$8K9khz(P>o`|c1@4Qn&iIiE^O1-x zRlnHSSbdVwBUF#TVH=T@tn5w+9TNbal^P+HzJ0@Z_;YM{Sm{eRxdebrm6n#i&o;BN z%BrhNxre@#lC0Bftznn6cg4c7=Xch1{BJ0%L!KJ-DwH*-7T4* z<9nbU#vfM@7sqOEZ-1jRv;-cU)!Nn~mBcL6?KglQ#?a^uL~Gg5fgE1<6R~CgYlw4C z4|I;n{6L&wH*Y~#)sU2ezKK$NYyvqA6;Ln2MXOq}G4Ci{y-fZ^qA(CT>4kGJOy*WN z>GIlDw1tl^{&;Qeh+t$rx9wbSyjuy@uCfw?vc50`tVLQd& zf2q#{J)6k%(RyXou{&|uK6%E)IglzGcOL0dnhG4 zWWLZA2cP`2xj7>S9UWb4Y=sRKSS#W&5&bs+NURgpJ^pSdVtjG1WHnt8T%-m6Qn%>< zl$6RjvI1drPt6D_-f|@O$tJMB*{PjZz0dXrBpKt52nwpc{npVdg>#_D?9Rfj#q^qQ z(pGE85rEyye}w@)B5dR@?(|{M?qi0-GFja-cm~h9UO0l;+Q89SB9Qh9ckQboKS2BE4@`6uq&Rf}Ycg zbv$qi$T3fG=obj15p7Ubqsh(6V)wc_*?YxN0CaYV8RCIoWMhD4_ZNw7&>UnV*Vo*6 z-fi884|8f-R^;p|- ze`J(}&}dM^FK^D!u&{9bPJa8l{|6=tx$LAI&%^!9@fY_CbAF4n%*)woTms3bx)OO9 z$|K+XCDet%XLv%?F>H?dnViW|Q&j}%3&@7W>}AcxZpre*+JG+w1<)y#ieEGk^=20) zRV5pl4ndS>3WJ_(>cqg^w1sR^V#V^glS0B4xEQTadBfx^Q4c| zxr~Ycpx+Px`bqHiW-;vWFLs0dYTO(}?`liKilqxCl~%I4QSF81C&87`>6AT;x6w7s za0Qa{28pu17Pa^YERIcX(ukj1Np1uj5R% z(&6Mat1l~y7b$W^x}N62O5=UQ0Ra3!2e)L)PYNU*0OMU*4rK$q1;;{4Wa)`WbY0)v zGXRsNep_1>vtXCe%Xn@%qvf{XFiL^wd*I&V985sT@6&?JCJ7o#W&ZjLApIAAyM-6~8t8!}1qa|2ev(u2<5))1* zYf$uG6eq}xd=ch1C5vF?eT1hpaoR+wVXel851DwW&l^aUSGalCgmF%QNg+2egaK{; z*)?lFmfo|{QOrHNMtaibcr(>{fRbxVjtT7|m+>;9x+dQmk~2|aa^~3cLw5BN^x6>< z5s8Z#z5rmo#lrkt&OC4L7evb*?QIZ%$s#U7Xc0=P6+8>Td(LxWH4#zxp*CzllOiUtRVmbsf}nGf!&k*MXGxa9PX7H5vSa_1)h0>m zxduzf#7WFW+_T06sk(DNPfyGCFAqYC^YUOOo86eK0xsmH3L=Ts)#^JO9KIYI5ua`O z{_>4KSJwDxy=!QtrK)d{*Q{C>chyC**hBeKl=lHQv0z03qpSVV(8};2mEC*gO`e~i^c~iDuedBP%yp~LpPTv<@ z@~_KCaXXS~j^C74)cD>fsdpD2*?7gK<+8JS{oRuEcWPhWO)D3)^!|OtP79!qM5qZp zy3Y@9w<JA_(506lwTUlvo&PT_z!lC7MAU0|7*lP2YDJNrOI1xo)s(85 zLa|Ccmd;kAaP2&!hGLK^_(GX;XeD~cy3|)7D>h=y$;h5Z-$K}D^CLFB+TTC2aAct4 zwmFC`2^mbC?9Wz2uovKP-T#;cGzNEFLknulx+kab7 zBY!5gD;mWVv5HAh_HbG1X>CVt?()P>G7HQ9loeKUG`J2cYrTS|ezg4H{1x$hx-2Ygh;YS5%NEVYi%ny0l}Xp>tXO_i^SB?5)t^8#s{rd9Zxc`lap8b z94cjG;mqmKv1Y4^ekp!L0+`VKdrUyzEAar9@UWAO7%6#556aNH^02E|tAmH&QZX^% zB-Eg6p|WC?Zh{;wf@_?Ie7CnsF45Z@g3Ba9mt;4J<1Y zCINfa)LNx6ks(a3s4(F#x$TPD+sn0Z@_vFtPWMS+2y++{ot&3*4Fsu3OIc8XFW1_72jOsAuX!}P#_7fgguZ8po ztYmcs779msR@6rincK-yBuM`p>m4Qlf2(UK^WCI@z1c$*OL^~VfpyeBXR@8t59+Fa z%^Vom!A_RCUE#}WJYzYB!11y9y#(Q`%x`xqZKNQl6l=`$KNZy86kFd;7Qc)voNWF( z5pU>W&f(^**FiJB6HzP51EvA^k=;0;M6RAI19)Xw_jTMIHWrh?ZJEoSfHLRnm6Oe0 zjk;gGqF#Y#En+~2PIkO@zClGPTB{TWIrez91KT^}1?J!cB5CzhRrL!Fl8P^YC0X^G zfH`}CdDtKO+SjaAGUs;6Plj1cz!a|rUc1myyudjKlAoO&P{0rWxC?di-!E z!d&SIH<3xquRA7m-^LE;<|c>2W2cI5pm>TH4+N%&>DyP^U9K-2OkM>;o$SwWp_dCp zI7e3kv>2|I4g#XL`)kgZb5$ilBlb$JN2?hsg7w+(_K}Hdiw03(r&7LgCo!GV6e%D17Spi~`i=F_^JyMd( zG~HwQD$}RhfsRj!A@q2AG z7?t3_wDWOdB@SwlGXb;Embfq_ldOFFjf3364{ZR2?o3$dq;-CcZ11WwYP!}@x%`3 zHecL&d?1$VXP#5&Kft4j>tT$ckFG=s$<{Fb zHM3UPLmjSP^(2mJjM?At!}F#EW04e{T-y5Cm@ZjW3CwsJryNr}LT;y13+J8HP?Le; z`97+ZE8O&C?(k$u<3q7zST#@)*U*cW&uteIj{I~0v^Z}DBy-z`7Ej??5N%D$8Os@5 zP#^eF%(;|?)XVRD&KKl9QTcX-jV3P?Qlmrrtd?7Mi7CqN-myRjg!0|(Tn9KLog4c*>=;H)iUOX0j~%X=jhH(jxp;KLziy z)yS4=Z-fd1QL9vk_f1UfG_zc4cpWMgqmUB%*3u9wdK z8$kGc)5E&o&RgX4udJ663%Q}fZuFD<<^q6fY<@e4pag?Hf~xg|OMd+}JAEaQlc?C_ zQzEw`cV50kXrx4W0>|a8x+mB$#YvsQvSeo=y#5DK#MOsoB_&4wbKW%8!Z-8O3aRCo z%sH^|MhoczPj`I~QzQewx`?4mwvv98{wJf!GI-~a%5g7Iq}}YnO@y8U>5bhjwexsQ zNqq(X&XuUm`<1;z-|-^Hz&|3V;DC3(oFDI*+rm9`BzcC$zExR2vuEEiAyP;Yj`b~O zd(0{m#!-H@71J3K8hoK?Sq&BSEx*oXXK(K3!bZ5-9=%S}_%2r4+HG9JQlyBB_|K;& zp3~?*nDv?|vSO);T)kvl`TDgDb@rFLD1YgzfmmxHqoFK02YvWz1@{#p)t5aDt}e1@ zq!X0<@EYrZVk&ava!yo0 zy-1}gL*2<%h$xIL`ywEwrRt?M)afYG&9d4^n*d-FQywthl;@>fGY~wHI**h?1?IP< zo`<$;-BXN+$I9oU2q`n3=i6igjGonAqHxNITh%rX1=3DAG|OtY0oe^?j8zvTBD$K# zX-ILu;^$_*ZIYIfUPbRU_$BY%(8c7W^^bDN=a!aou$O3Kl~09@k&D|ay_F&mB0x~} zfh9NfM_zJS%D6ch33OZRsmO$g`RZkO>K<^*;XmKOd3S|zoukTBZt`zG&ZcX&EcSRH zp<4$&FJ^Dp(yWU7d0pB$A@7A&;MPYq2<~0EGdXlrJrpFACvOutR$VDcsL{72 zcP+?#f~b;_fk5+Q#Ou(rfd^uOndDAHa(m8V zm&d@8cH{mu8y`&hiYRkrxyC`8fG@(T=C8hrCS|$~cieTHm1+O-ThH3%wg#!2oXsaq z{p{xCjY;f2$?G=Dr7-~&mONbl^Xj@zY+|(cmH8CffiM#pxo+wJmZY^M+8jUr%9uU) z$HKyDI4HFA+qe0qdcAntCMyg1(W5}j)F!k%wz4%hz9G`0bq|)gpHnO~uiKQ*AivDl zSeM*;aJE!&m%_JBk`Gpn9cnhGEfa(x!n|6m5BdQ|_2)mLbb6p# zUa&=;cDa&%;dv(}58CDKM{K-}R*pGzuiL1NP%7x#*$7b&O(cY?9?ph{a3{$v?GuuI zSAH7b6((^`q*fkSp<^jEjrdi!)1d9uj!JRcVr)aUlUG9TN7yiqn8tuk9sL}uyU1-U zvEOy?Z@hRbFL29!g<8dU55mtikA!0t(`gTnIA3EGOp1FQ1SQ z^P&qjPA=J1mnkduEzgxVz5;}h%ZDqanvqEcHvf&$q)4OBn0??{UpQ3~C0c)|@MPTP z9UV(Jk%B^MNwZb2FE;3VZC-z^V zC(VpYTuDY=&~hqbQcY3nyEQ(3xO@sLL+z8^qtut{O$Klxnr)y23eL)UH#|H-#%=rh zwydnPyYn>*T}@vCEAZT6l+cK&|5L5K{OvIpjqiVAY<|-JzYDkjZ#s07%}vY-UdN5W zG}fQ&lK==_d#fox9Racz4jNx|eO5%(dpJm8F3mVL@(VD6833;-kbENTH7P%JeD2Tx zBP;aVy&L>*QM@m|&o-a_R0MRFLtc`fea41Jd_6x=CZFxi^_))VDX6Nxaz>=)E9V|{ zF{D$fqy=893(g`>hHf+f)_*-G8Em-NL18}CV`L2oK}zhz^KTwVQjtL036gkJ`Tuw z31J?9{O>&@BjBvMU$9$43qc^vqRN=XvqZ)>9ofSD@-^9yLm;NVnpK}WN%)PMf`k!H-+ zW>rJ1^Cq(-I0Gb?1u%Z|y2tt6cL0MHRzWb-n(fcmZ^dbW6{n`AJ}@#$=>SG3sHjk3 z%pL{idlX;L1Qk78)0`i7XKT1F>Iw>^1t=pHkDL8)p*)u357Ub^ zD?hBB3J3^noj0QEr)!-o1FxN!Z>XM7c@;@qN04&<2>QUxj0ajMF%bW%8`V!E=AX$p zw#H!1mSwJq(3`i87osF6fI|KH4p`$ufn5VB@}`r*Z$%=*!`oL5F?{uIYhgy5i3iuW zfhM>TaDai0WJvdnrh#1e0{-~GH!ih@6%hvb9I!#*k!q3Vuhyl%fMo!CCfw_lfV_|f zFzP_u6ZngE{G-Cv5y+X|>gAxazGhN`t`|SfB0b|iQO~nYrx8@ z_rfK1IY7e~h!RqT6>g2>5(1i6QD9p_v+?cqx#>zrI5#)<;N)ab59$ey+Oq~QK~+Op zS=nT%k>r7!ot<6h)!_>A_+I^{soAZpAkq#b3_=T!x3xe50$$>&Te@cMuTCK8vcfuMIi8Inu{w79mnt6edrnnkmK4qw}4&Z!v%FriN(njf$yY7(GNP8@-4 zFyd7sBgjt`yMV9%PDuEwtxYN9vKMwiM+HVlyo%R#XI$_+#s@sDyIVTV^GK>Al7>cG z)Z@$wTz{&{Yd!6cTrmJ%;0;m&NcG9Q)=#>)X3JpJTLsW+AO!*pS7u>hn8#iX!w+E1 z(^D`oFqkw_q$D3|pf%}BY6m3jeQ6>dljWug2#(nQoZYGV>Mh|H$#$#y*-gg+0f)7` z-d@>1iHZ4Zy@{ECuLrt)cHcX{{x6UN2x(weyvc%l)hnaGauGI3Y~TazGx(h9zLno? zQl1a;y2>NtGRaF7atX*vTU5028SML~S=14h0MY#eVwg;e-g_c^?D> zYGja~)FSe$1RZ20XYO*-Aq7Xr$A88wzGLCwePgRT5DQ#^we0FubDpdzi%G7yf-V-Z zoPctGDY-8P;2g&QL3Tlv)zk~^g~`c02>N;fdUNAxTs}3rF_byxv0qQ3jb4pWz}coB zE{mWJ)KXPe&V(Grq~`&lJeI3KbJ~PO_#Qe&u8jurgANcXz-k8r^z*q57t5j4G=xsv z;msD;xhg07k8(%yfhDm>Nl8iN%a;K6jf}w9F$;WHq~qPQw<)xScyZ5kPJuleO4{1J z0E0a>BJmt&vzN4R-eRuSN!zZS7PNqmr;P=Uq+Kn$>7$onK;(aTRtUnex`w zECZlx=EjS5Huu{1%cN&ez1xtZBYPn6-CMSL<6o$I?bo*rAAnPiK}eckj6b3?)KI84j zN;Jz~zZE3stC3T9#g%+grTw;1{;inXd3&u@r;b3%XDlGa7Bh>wZD{YSa7SbtC(Q>X z5@dvSdHOrKCAw!{D+$Vy{FvB&7c$!S-K8=9AdO37{ixMU7_Ri*6zeKY=R(!f@4CM6 zuSJ2_@K~BaVXJTwz?Hw**{Gyq5gmQItDvyDpnPh}UkTz>al)P_URZR$x7mFnOjGAutS<%cAp=iEUEBrlaXh#* z5ly}|vH0^PW1#nID2;@S?Vl{hqux8+F^kb{OlGsoMKfpIoIAhZL|(rOQ7_rn6sDo` zIAMp1JZ>fT z85&6_bv5ArVqWue@1@!oPo3Uh3n%`uC32wE{e0?-B~i8 z&FtY?%69U`0M;vMXW6@im!!FU>f}`ZBKl%M>*uR@jHJgim7)ww>7ie5Z0RCA(ms|8 z38s2yxi?vLKvq)U)zv4oMM9dKQ3{&l5|d!lEjmLg$u5deVkDJ=6!06tXw8t_f^=t# zg_a?vZaWZKia0(v3ofGw&CW>i}}ldtV;>1I9e-6{V8c=S>y8o z0Rvrajd^Rpf~0CDMm%CwNl3xwHj@K6u1$wUGO*zydWR zMiTjGMr%OB>r7jw^wJu&n(N5uwbx9F%C7Eiw%GN;06cNLiBPAP6)2nuyIsh&YrN#F zB%xMlrBXAZ=3q5+?`cyuuihpn(B^!;e;#T#d-}~b^>#Ylh?+a>N{co7E>4Dnun!68 zEBYwIB)yy1DrjZxfL<})nV2Kc?9nno+&Ryp@4WK@1g+v1e2iy*kl(FqZox#J_E|MM zucW5-i@umCOg8}SJtW_2h^V#f$K$@`D-JE0>mQZL5{!t0-zI5spzw0;7}k}&d$QH( z)5lR>+m2M98D(~6Ne+6iah=rC#OA&$D-xq7y{}}%&&q|EiR3dNXYHftRZrR5Kz+no zT*>BJs`%8YVptvSP{fj%@h0B!q!jm!Cr+j5FG6m?VXeqRv$xjp%CGA_U)>xlJ@0sW z>~02V6C@QrmQS_jY;M&Ql&=>DtuJhYTVtmpuaf&SfdSMb2ya<8k7__I|1-M;?Ldr- z2-*(07rq>#;&>3JTS>^mLH9%Bm@TQw+uDg$l+{ zqsYpT=5qJbd@^rQaC?em>87=Hdthmrmtt=BgGH@F{O&QI)869;>?phz z&O;SpGz7~h;kdZv@<#ilY%Z@>Go={h>$FuNzfL37ThU`%fAQ=p?S+aC3oM@6S_3>d zB!YoK&3^y?0!#$&ekq9m>O{$6vn@b^yH)W|SUq>nSh}al?)vsZ6++QK@Gmy|E|+9k zs~!*KoI$>2dwY4VPA|vvLli-)&PI%?)^+z!aA19$%sOpHX5CnOMEzMPfbfh9bMdN` zcH_@=?FsotJ<^pss><>+wGNBkinzE_WDe!-tmoSNXMWSD1!NeCGn#=Z8gPKN1hMbdL)<4MGNVSrI2HgFYfszGV+&wtj-yRp1JIHw*bpU6ZdD)pD-anel zb~l`AFyUl^h}xo4qLS>HiA|e=$s-Cb)fIXE0J9Z%jFN242~~aNaA!AZihkm-_qD=5 zt4XXLWfA8$HL&+6KVF26irR80qQ~rdZ0!8?Fo#u}>E{J_uFPig>9~<_($^WDw~??o z1)lL0_gewiTY*9DUMz>;nyuWnyR~!fSr-TR5x1czCUSfaP;Vp29B;L0HjjxXDtE&rcpo zV5D|7wwCQPwWf$X^R~OTV^M@tNc!4Vi!F&d_PCx96gR{4N9q z8PqJSX>EjRI1@Czkg65C%qdEiW+#huXDQ8zP)4ZtM(Mb8#R}&%#OYKd28gPw~XqMC%xwO*8j?J z1lt^T{@=`AUdlBuT4c2=9OqR;y%MGHT8n$#vx*#*xx)8QE}4ihanPsceE}>OMk4zm zO>CL1@6YZ`Iq-Y29bOZB$31*=-j60l?xF+p74&4it_&M*A$3ndk6~(+V+l9};VHpN z)eD~3Gv7tqZzJQI*70FSo+tDXXgB8Hf$bq~UqPkl2_?PM?nbOMw!3hzeyoJDt4z9g z293LXsq zC4d7}-AJGt>#rk^YFQ6N?tYT}@CnxU`2=|>6ZGgw58-nriSYh39X{)P-l;BVkVU=Ckg2xQu4Q@|6%Q{KXza3Vc1bV(0pDEG zTHxNcy&lp-k9xnZ_Sj~ynPa{cNg?CXfBpG%{M=QF;fU*R?h1wDjLxRZCTf$*(N#$! zu^|)C1k`HTmp~-o=Tl?+3Tx|XN9cL2^o!&ZD%I=hU5xY%*T*HfqT7+Dw_`>7iAG`B zLVCvwm8L!-6O6DsbmM?eg|RfM`K;&Hw#Sg)7Y`KPc(V7|tmQ!SG@P$U3lbB@5SO!A z%QSLkzY@H^|7b3;#_VpamF(nFXvAf8gYxDyVCQx@S7GBqs%+oG#iZ#pUFsCAdo$fY zqY>@3fGEE`1u!U+0`$bclYDB=ozhBb+3lQgfrAkmXZ1RC;DVf%r529YUjGR&A-H<3 zpliaX!{GhKr`P*B383#-oEzTrsld4mjc{?iu0}c(79Sp-)&DnCADVUyeG;>(FLSnV zy{mloDQG1e1xFw7ksSm#b~QELx@)@Zb>4ZP^IN@$7#z|hr#)sCOqy~V6AB3Devj+0 z83PT~AIN^fgh6WDpGN6ZI3!j@CFgQow5tKpFobG|IByW1cS{{?Y*ze#GR6!#b;hCN zhS0Rh``YvBq*&T=CDdHw>?>r(&tcCZh2i*^Nnpq8$~&&^45V{2r_%50alI{atKnU^ zUkvshpfqMKUiVlIH?&$9r!*_-^lxC<+&Yd8d~4a-91^xWyS5g7!+7?ReBd(;aWMCR$B zq$)B>k)fbcicCSmBx6JfVG2_qOc~DF{<;79+;i?<|AkGS?EQWFoxXRyYaKd&E!dE5 zzu|+*g~mM;1}jqGp&S`bR^Jh7R32&GyQ?qA?qg=ZYN%keKCIk(WHL8KWvyVGF;ab# zRXTnkD|}AtTcs9`FE^y=(yW@fb|b~rzts8zvXI>1`6IpM@rZ-*wg~GeYMYKcJdw4S zp6_>fuFmF4pkydgl~?K=z+5h|U;1v1@1ee`WIONfz4wYpZT8p8&;CaSFF|}{ruv?O{D#&R<0$bfBnlxr0?UOtt9QRbz4E&sGUb5eeyY! zc&@JbtFIjk-4#?SzQ5VN+Gho+@6)9x=*Fc?d_y8hX>ULK>i_lA|Hmiz%da^KM%16> zx3;wj!uiWCXoS>aG!4NBU!@wYTP^G;QH zs;?pWmCky4d3jm(ULLMP&-N~e+}mqZT^`&TvHM%>@l~XS+{ZMo+is@<&0;@U%iu|06$z+D5^)KwacsX*l~Na-;y=9lShM``ZW*S}cg#Z}yS zlv6rNH_z(aNlL%t5*oAIRpb?;NyAB>c!vE=#5>--dpDI$bgTeVko)A|)s&!dD~?H% zYBrJQPlB^*1jctmg0W7AbFg!kZ33@iBE#uHrYxfB+WOw(!j~PTi2@60QH^@Io?^y8 z!RDlf?K*Pg2w;rHvc9<-X|=2LAo|v&R=McH4RI|e8#1H!C8}8l-777%aNyWAqA!>d zT)Et9O%B3>HtC;?N4wYqLJ}k0#lE1B#G*S#(O7u4$|zhit{=UMlpOC8`j08_j9+3s z;?5@KUJco>b@&sJMARK1D(V2iC3R$G?)vrXj1e2xx>ft8e$h-hJ#xtTqhqwOt-O?H zr4U>Rz-V}INp5k)+~dXOXyzTXV}l)CXtX$j0)TgCAJ;8gOjCs7|Gsn!2}>?y4Gm~0 zLNMa}>hbgkB%NH^$L-|#o)|y{CYb1U%5(dsZ@F*&Y}XS{@Z$M~#a^DCwaa{kytSjl zLo(i3B3~LacCWirzS!u?g^J4z$Yd1$A~%>lWLawsVy zEgT&x8o9B#41ul_*!;^&ks8atPe%Acv%@%eqDLxlmAe!6D3>jJbpPhgH~FoF6Y zKt;ADYmm}~VeW2PwQ-fwckO%_HI)s`4*`@`xS;(9Kp98KvoR2DJV{x2t~}Ghl69cT zV^iSQ{{qlZVAo_vg`|if8*^=vE}~PG%eWu0IHuyrn$cmgbD49GQd|`M%B=VkJ|$Xc zuLh}yy!k~U;7f;0#c0NwgRxRuDeJBj+%)D6=k?+l#q#;e%82#}Ci2sJXv=-n%sU0 z5Ya*nl?}F8e*M$KJo$q^-0SbAbbW4i?cF88X6L~FVh@7UF`Op_s$s`wpcyy5kw)cJ zvJ^cGaijAD`hrciZ0T5~+$%9D$v&ikw10lCOC@_d1T5Se6HSN>-DKdBEKFnV18z{V zJW*!s)t;?cizaU&5Im)e^x@ek=_K@I&?SD7J0He%gLh2)y5e=41nuPpU^s<8RbK}? z;7Pq`i>hkC^N%(Mc1h9{i)rGk28^$$KR7$5tkr+|Wedl?3Cr{cXNXf!Kx1+dD_@0) z*0tXTB7k2??g?x;vIs~lalTwg_Jm0i??ysL7db`KJY>pknRWcciI%ReL{QO{?5|R0 zsnsq0=PuIsz=x^4kw53F)~guZs~2c2zpk07Heec}vu)$x;J}m=rU^6@(;5MDZ8Y4+ zTur}fCCR<~D~Ul;my{X}mX%jWP9q;kmJSt!ol*y*Aku>K?6rjF z7Q76PgLAO;h|UaGX*pwYro|}BL6BX@JrqpF`8~-N$Jr5WghNPO#o;*MCYHPDf0Da?XUbmoIa^tLMa?8|&aa zc(8MovU)>~b6LUpwucPtTcdE_e2HeQ^}dC*h9^g_PyO*7o+Sii&+e=IGGe zxQpiNS%6_c37z)MW+)da!ZNnYrZp57eJW)VJ=cnInOAdb>z!C#+K@xq(vR%GkODHv zZzfE@Ssgt;I6GKmbJ?`V??B`#f9={_mTc@KMASlIV9_PA+i5dpVy-1}c#wUz}AHm_4L(!_QIAKXOR*sAO6PiP~EUv7^c z1XkWqN*^l#@CcBFf}K{e47vaSAXb?LF#hql=8&sCJ1a-KwB90$F?$3{ycLyfu>~ejKYr(0zsbXAkrk1VG+T0&Wk@h#F zk}U!FHYD}UW+`MgKdn)ZOensrA$=35yo$u+%+-bG4JWj>w=)J*`G?r$18S3IjhQP*AGWK$ zgh)tG)j<)VLdq&NctDh5X^qEjcrR$|SRj-6wv#myz*RS&-;38(8Hakv;+W zM3;&BpF_&+Hj#aYUuA^WpXg%~bqMD|LvAr3Spi{RV?pI`4gsM|cKY<`rTNJ;1eqa- znc29z*JwNfuy!~iA_Ct^|6KD@SgOG*g8<^niz{nH-5n^=no`MxS_0tAwAvUQ z4#r%lE=l!v!O77yV&A|px_a=I@lLQ-{%OM)t2Wc?Xe9O zdR`YUG_*c&u+&o}hXuAjKG88C;{#+i_j|Md<8^<#1w$p4jo}XSjQo|H)UlPXZeTz{ zce`~|1LP8oF`i?WZ|FbxavTzMZxFmX zD~G5@ai(bkGHua2E5lVG3QQkG_=yuI*iT&5t)XPJk8sks_oy*;=lFP^PEd4-v^K%)nHd2GN6xu0 zrmK|-sj!jSC^gHdl1U=Z;To}tGf8}DR+SeP(Fk*~Ki8l|7g|LTV-|#Ppgy2*{mb63 z^G>Dc4|sPLXhCCw@?b!_mYZT}Kl<4&zw>JlZs4Xd#pQzCp%{iRhN+!oE(${V%YAXd z72*_VY(x@gYjx*s=e@lf`+g5Egxg6m%x2F{Mr;!0+9UR%&LKi9cB$ZWO%&o4{pqX; z4s}H2!bB75Ybt7EI8FPABhCUPh=XFx%@H+#s2)DwlMQt}Ra2h^_&4V8*u=4Igm=G2 zja3^NAA}Xr%ZW=c;{?lKA5MLGq;0a9VUv~E7Qayl((oEojC+f za<&~l8lSa=eaC&Fdu$Mg@y7ITE*oJ@N*ic!H%vU}P|D@IQzI^%5T6FLC?ft&}brbi!h5WCD cj4M`d3!57Fn*DnWAwvoaUaSZFOQ*sB%EJ|RUYvIg8jVczOgp55)vTm?&Qv8RW4=cH z_pv3I^o`WVxY)z<+sBFlN*VShslUJBj$s>rBwX{nw^`B?Q+-~Z?4#MkOC`*)Nva569ZPUH9b zD8OF$uk0(jm;IX-4l<(<&DU zydbG+SSv=0Y8!tBk<;f)uJxbf!et*Abr&PJu#m&lbUnUYc=m=9Ed9lH_!t9Ml=Z;R zJ3uf+;tP+@uLu!}ubeN(DWS@9H{mcH)20syFW(h3ObOcLq@{LH4bJ8!n56xKqA~DL ztVig8S3u0Ccqr5E_!W_dzAcc)(%0KU=J!X@>u9*50M?v06uFs9<*ZXRe>40`H}>Pg zT74|8P3#4}3zl%a{Cy*xQJFbk+jfMTAYo^q?_WP6zl(`^2W=t11qQ9rUxmdv7773D z(xLcu{SRN;4~=~h+qQz&_vTB^?&bx~_cxPgB;oFd$WJx3)lF68z-S)0DFu9)w>bt& zF8g(m+WARGMS*ZH>1c|!Af3#$M-B*zho?@{YF4L&zx2X~cO&UgEA%9>7dylv^Oypr z^h4#ts&+0M=kb#WM{fhX&Sy7;tXuECE~jxv7vF5>cI?B7&4i@nIJQ*)U7BsnMk>N) z_46Zx98=8_p(uLVM~b=V^?Jgg*V{`{#yBbceHeNF%W~;;?D`R6^6lM&!#C)&u?(V} z%~vDv0IBqTi#efjPv18#szjM!Q-@x^aEz_I;*OyeXe^VL7w&!`VZQyXq99{l#E&WW zNQj()CYUnBgu>Z2#Gi|&@bA?wO(7o`jxMQpQu(aGLRPnXi$kU9=055DlbXsvttQ7b zsepwmQP<=Kmyy`;yDjokd%0jK0X^?qyqiR_<#$a?Op0}y;wL7Q z_V)KH)afXws95yd;lv{d#k95e`6K^uuhdO{63~SDRSH_mE*P2av=N{%CMK{#kAM!OQaK88^V z=ZJmS>~tqd*>Z^u=j5hRm2ZuST^UWz&^ENSl{u=)QdZwM-3(!4V29Sb+vpHbZRecM zO#QiV6mJ{*sy~xi!rN$0#LzZw33f;gS?9)da#cB9#nj^AL+e^mO|b8R?w=BL7^Rt4 zoorA2!9wWpQ(C-yq)gMPMQFnUM;bTnY&OaOc|)qmAB(lH7#YSVbkJ&LlP~&dVufDOuTr zXx{3rJ~Ch9xk?kPy6 zB|OyE4<)%)Nx{(lFG5uY>Gyc~ElOi9DlS|!{L2^K&-ag*1H=8RHvn-`We$Zn*AJzgQQ$_Q*PoMAAfhxS-06k?f9bTPQ)BD23<$CKHf{2O-$gnVfXli=;6gd6O zX1-efQ9oRix<^gVr)Q=_%ZOp$vEc@gJJtms>& zL9J)|i}TWY4XPaX%S;__tIuWS+GtaG^4-3+CpBgL@}j)3s~H|HL+$!e6c%E7w*So| z{3z}x5*=gN#+(U{yO#))zNX%EyPOlpNm)92R6V2*&juB>6qp+tY_#>{50b@oGf9ea zzaL#ylotBi{VKX0?Oh_7r%G5^9b|-_*oIwVjLNQWRAFQ(2ydc37=?uicf^TJ)|-m5 zt6_|8mxqhKx4|1EYzD@5AV9#{M5~0zu6=dlxzzMAPQq|9>$&j>Hm^jCm%0*u+;@?o zfhO&sFCg0Y>3%f;6Yej!3(W^z_fNOCw{X^r^?&MCz{$@~*KKe(A3mIi2t4?Qkb34X zoDo6_K}BOOsaWLVOwK!i5(D~{IyfjD&!9tiCtO-sXb32I#$(_%Wywf*K#lcWrQ&>2 z?srAS_OT2RDFuZ-%bIn^2H_fJ7Z(@&;Esd2s)n2OFe4<7pr9aiIy_){6#;|!rKF_3 zC0jyEw2%dXZ{!k@XlykgRnBN<8q1mYB#!WS(pcra1};!+y%t(lNV)oWY-Xz( zS@?}{@fOcG(bem|I$=~KLvgsc*kDF}6bT#L+F-s6c!Jp6j4}H@sw@Kk+S+A! z&vlgIzVxV~6?^dWaMsk!jKk8_B<9bg;w;A`t2edNjam>rV62maOfs%9$=H5-QK7xj zGIf-H3ruhLf703!1gzm#Yg}?3Y}erc1?Y-jIz0Pk_5GZ23iZtEe6N7q{s7nhLaKa{ zs$(S7!-AK|Lci(yWs;jCE8Fa=Qy>u%CVI@JS>z|R8IF;I-fSL>0}+OM zdibI!4>Y`g?Ler76R_~&GE4m-QVu)3c!!?ygM5!lbHemAmL%-+iTM_?!|BTBN&ofj zWV`KOw54y7**!1J#iK|es~rIzYe662ZXV7@8;%VltU4hep6mVR@I7`^{xf6;AY_;n+egp>78pV*IAK zKwr>!2M=xO#{~UXIm}i{&c9z9F&a(E+Is!OsnO&l*h=lp@nZ30snp3$bypHPBa+pd zELhO7M>}sF&bMy+rcmD!T6DH)`4OqLw3Z)5@khz5 zexVU^;cstX^UXsr>IHt=3wEET*zME*>5Srm(B8badUv*L7C+7$E_n*i^yG5|*wDT0%lXAqdF_ z2M4MhAQ_{+Nc4RfHFb4!Ptwm88v7tMwGSo(wJt6%6N@C0dI{^aQca8C2D8wgPQo0l z+Heg#dW|2nS!7Su)~pF16gtN71)6?PrF#U+E&P&QNt=t-x`>(Iup!a+sjKq12`0iA zig3aC%f-!c@pS)2L|}M%`C;g6#=#>OQB4mI#C)6jB}0=lEcdkYRK&wm8azC*>7==7 zaW^*WLnYsDbyVG)FUAComqGQkH9LF+<^hZEFsr2ApbxtEo^9T@XQd-bdEJGn<{C!p zu63mZN8zbFZo#^BdE&(;|0=!Q4ot5`rYa-wzkS|jesLEq4D=EFTf-}@BOzF}1UO^N)ZXg@ENA+P$Gf(|1PnR@N zOKx^LxkL?{5amzkYSfwMR>HKnMm+_g7d1TQHqZMfR*A5P2_ot>Ti^S>Q-lbq3~ z#z`{xM+DDG?uT~*__I|nTw-G()Ym_ZbO_*?9^Rc~AU?5e0qz%aYe|@tnBUc|8KgThdliRAcKs)rWM2d%)e ztgnxLlJVxWlYWrUw3HsMQ1j*&0Q>WG%Q!kZs#kq>_IWYl zF+}y`T3m-7xw;=KWN*R3U0?r|tJ8HaSZ_Wb*XZY6wCuo>CH2YOQTf7`h#~43`Q>eCnqOrwtAVLsizu?W32{>|MOF{L>M$pzVXK&Ng(bz6s;(|+6}Hom{2DcNMRE=%F5vWngY{z`L25d< zPO=4$8Ndditc@RH-HqhcDp5TZVTk)}V(J=qZ908MtNXW>KPR^w z4?5u5a&#`^u}%Z>{^MCc%__f4@=?KS53Sc89fYM*5Za%U^H3D#5mWLaH@lgGW&cXe z@dwf*?hRV>;!x@b)_Vj7_2zIvOUmUb*;t7G_CjQdAx3SI4%%T%58`C5!@Lw#bQ1BS zut_bM^!3T^%y!f2=OsQ43O0Tm?_tJ*Py+p$gIRvo5|Ds|ymsI|EbpwpSjuApW#j6BN z2QIIVBCq~vRv3y6!xfqIT|GT&MGd*;O9NS`&ssuU+<2K@o9)PVCbEiG#g&!Y2D-zf z)2%GUsJ_R?!R#T z<)Ftm##QB5<-kdJazp1AanOkMPM_k4XyEyAf3tW-65{O_2V~QY0hU0*{hg=R_iE0J zMEbtvdU>OIQr^m`Rp}Ry({Nn0nuJ^jMD!Sb8so&TG{FC6Re680i_OhYq*}6vrFR3M z3K1S5IzvJNRaIG01a$&6#5PO&m$gYN|Iy>s!_Ct0Rd-eTXSASPG)0tL+6 z|5&@;fMbeQS%WOS<%|f$Z+uN&AD<}GRLf6ZsZ6?lU)y(X?9#%H?b|Ta*{=yNd!Iuva$1ZB-s}2YPP4|^<|h+a9(YdJzK~<>9Yc(c_OxHyXBh+^c#v>=?-6 zNh*9n0KVl#bp<=R?dVf(t}^&A1-H;3%f#((O=HT^&EKLrDOQ~`v*bSKXVM7&;^vQv?s!q*e zV@Er3MCudOr$|2~0P{-JQ|f9WayuW0{}phh@q;SO{j<~dF#IYo@otqi9jyb=s_DsU z2awst(`(%vRE@gdAJ&PmF_D}C$4ctz{bC<q1`EE1-^VM^ofZHVL3&C zhZ6(|QV)uOfdQKt0N?8azykbuI*nJa1E$y44aFmgrGT8^^z`&=j=8z{zvAL>NHT}A zipta$0x6?*{qM6gM*u^^1)dHnQVrtEV-gVXZM!U3X!YR%l9D{Tl~y!T-oHRRtav*G zg;tpi#h2&qNMf2F6#%134zG#{bDq7u1*tJV2NOvS??vtRpMNbY${0D$nO@D8MwCdY zoBLT-^!d2W*VH&VXyxIK(z@11U~1k;&&~XnwJ8o3MMhY2i?$ks9b9P+O&dFZqo=3W zyT3m)uig5QJ`EOScb~9l7qv5bcGd`7L@gq!U)$dy=XF!D%ZuQGMQz1iCsa7Qk-V)3 zk9hLJ2k1pv@O?fN-<&oRFeAjq-g6kV|HVXY#tTHXThP zahThk%scNTva9AfbQkn54Yo70Vg;$k#Dva;RM`XgrRStbC5=>w9HEdC2FYU)rR7CG)q zL1aVJ9DhmIfsD*SSAo{fpfsR7RME;<)d zu*RFJ-aajP1-=mc7#l2p0za0tCSgTPB!hbM!{UahPk(~dxeZgMJ<2o?mcbXRbb|m- zQv`q@dFiK9ETgN#dtuTL)2KeVxIB2eT79M*HvWk|Qwb-^_b``nvmRokRp`j00*>YLmw@($l+)L}e)JlhfVTqqMQ~de==0QO{TDsn}j)!w7#MrW*rP z>ESm;t)aOVvg!P(T6QfpA{Hwiu+)bGGl8&1Spc~3eL7FayyJNjf=Na?gPG6ADz_1< zEN`$_?{L_7&q+@oQJ|dX${zNO#OGoh&`r)g=H1=h6qgwdPGTJaG})L*1%VbFqf)L^ zd3m10YDLJxvZtm8suRg?Hubk(7h0MsZa+RSbQJEba)Q#PWC@w?SOt(I5MqTfZw54~ z;Ot!Ws>6%*Eq|>#He%5DO~W$S&FqHB;MBb)a{LW%UT#_#niAK*h9+?Fs_)>-S<|1yy;5$=*n% zS)&;C~&v@okhv8)Q$f&y5bJH~jyx1wlnRjUFRqHmgK)M~B zp^?H>?nBbew%S3C?6{QA|KKBsqK6Br%$OI;SqKrE+##{c`pA}MbeHW21|MS_`BbSi z#g&Z^bxmQa1zKi<&zC3{;j^Yiid^K3X1)j1ua#>*wdMvs@oue zxCXRYDPm&obCPdFhtl%&+Q7}X+sQAV;Z&Z4%*@Z;{A4Yjmxc$k6)CTpvkeTg#kvV8vAS6E*yJlUwE=)P1IV)K&VWIV#1FkuH$5s0%oUTgSJ*3rS2LFCshf`3#l)0!^c1kYaPzMs zpa~uiwIpfwbNlXNva1>L>Z(yYt%H<94UkeUH#`4x|^7p(V+O7z8L7cT>ALNoY3gw0I&Cb;Q{P8kze17vs>QoAv)Z2T4~LHK(y{t>%-VG#;UVGs2GvRbQd^H< zg?@#4xn_xaql4i0mvSa6k01iw2mbV^yJDfK+$}$5Nm*I(I`wNebz{Ih~YDfBIlfM9I)lov2Akd;`G| zbRskyVI*jgglCzijAwPC@uM7SesuUxaL$>v>;o6CVj~S-%gLIKYEpyD)9RL4 zFp_DZf`mq+bD5F%Xx6MtIA>T?UnDXR<%)!$^Or(7u6YEyTtuJv>6( z+;69$1tmqOa&bXeC7gpja3> zxqM%R0anYC2ZuwC)prMnj(>8KO;x|a&$ z&(g^(tMXQj8*(!6Dee{sM5o>y=%m0C7n4UUCoIexSzoVD=4{67zW0sLGPLMxXR9IU z6?)j%eo=*;hR{+r%c&Xq=XdF^linU{PU9b&BkiEOsPDOA*SX-1u&o%;&b>~OziTsF zBL(AJhNOoJSiSMT2|QuY)MV0VX^pQxEDC+zl{wIg&!A(USneXm|@BbK=qt8I0>40vbCu&IbWIREy!hG(n z65(;M@>igOHYdRcFbMw31weWI>SCn-&9VnfQgj#3`iR@QRP%)O-@)lutCL!F`c;YZ zDI?Aplx!0NifqH#jQ}G_Y6J*O$PJpsaUis!9C?H>6HO4=U2e(;ITkr@WLj4~F}vuH zloaaeK@RabnaYBK3<~JPf%^|oI+BVh#XtTnwe9$+K>2$LwJR~uk)pak4-=vsl~YP6 z146+oOZFrGq;hV0>II}?AMiX)Ovf2HI3GkoF6df7AeC82*u)pveRH!)i5@V2ha}H` zWiHRN#)(137Co#>5Yk<;EBq57c~R^u1|AJ+0NXRtwQ@eIBX}kina>p zs~K_{5+YUV_~onUBm7sArKw<)C%h=kiXHC8(vWMc$#y0j{&Y;Ql+?{^(P(UJ9=;^6 z2v)pPmcb6?GO=c0DIu=lUjgyaI_~gP#B2paXV?&lWLo^V zKkWsNlTwu$>SMhKztlieESFtd6wNp2ExNanM6C?j9b{lSfMt=}L8jlEa+&>ntL52p zM&jnvhJslCfW+agBiT#4^5^cXZjsdv0W1>$=fy`_Ze5R_^#Q4 zrqRQ}j}qbRBV;1f;w?|)KzZ54ljud@tq8S9v{WoHT^K}=V3)A`znt%;XZxfZ7Jt86 z$$-^UBeoRdnPNy8`{n(ZmU;yE6NZa~=9K(c@)mxKewOxZ$UnTUEtG%?Vwi2t@%lA> z+(I%@SxfE$sn_|z*uE?=#7Gx^D_Iu^-1w!fDuZ ziCx?>P(9>gV2tRCB9#pq>ciD5T`Tw^R$Q8I1}JA$B%x%^!syG%p17U|PuWc~yQLeK zPc13zPj1WVj_=pin~UPDp6eo)M>rWs=UwR+0*fi0Y8wNS`^0^7p?8Hy3^&%eqa+-G zEJ()XK#5U?Uk|d1L)8ak6NvTPUKEz!P3SGoLW*9zf!oWlIBjk$4KE5dtCPOg+aHvS zta3Eeo#(d^dGkqh)U|y^UxSW2L7oTGYuqvg9Tbt;#ddPn4(;gR^MM~BP8r$uZR_WG zzN4Zl&>{X4hX#(ZtYAeq!B>R(%_~FIt#ZUhvT`%DY&IDmf5Rt*t*rQxSx^sbZ)OFk z#zMbuoH}XSo(w$=`xDU3>GaL;9hGkZDvQb@0dqW`<*nS<=#Pxrm~rqSh~Be~+pZs` zC?!pkOInf!7slj8LSX)6wag4Gp;qFXzmLlQr0;EF_HCa>v3)6-v5Uqf>jU}-oTTV5 zU8`SINfWSrqrkX8>XNR@Yw(q^gl_wU1MhUQR$_SN6%q3&JN~j6_^Y${X=YK z0#wlz6bL$FWPWwV^U#rhs+NseAstUg2G9QBc=x2JqtL$UAEZ_9Cj9KP91Ki+X@`F3 z<#5611F*lL*!^=Se2umi#Ns3>EvQ@Pb+qU~!IkqTh&VvjYrWJQ8gvdbGWYB@6LO1H zdJ9_dD=ZN6@abSBM^vS`5w$F`H5r?T8Yo%r9a4C%(7MxM^KhNRuq9pKxX^{NmJ%g) zIeD@5_<&S;Ob|6nX4;sA(MJG2PDl$`+#kUf#xct~0|FH5QVWtB)&_3jd5NuTMfK^i zZEiU732b$jIngv;N+1M=bwRd>pLz{}Qao1<`tq3Lxr`;JmttADOWtN~Wpguw^}L7I z7SZa@VG5)?P7jN%k@8(YZj?&~TNm&I_s!^lTj5)Pe~g(W{m?gB))?L6wkRgO(v>g0 z4}I84@%%vX#cz7TP;o5YKO$HRp{NMMOpYL3bFGo3bhB|o!6b(-Ih|egM~)L#{Dhw$ zJSpSg209|xW~Pvh9aYmM6<$WbSlI$NGboLD({xGI8v0C4aN?aLi zS_z!AXTxU@#yKWL@rRb&vx)8P$2LJ7ePLkCc`IkuU z&g1O5+!-IMhg)s`V;L@j$m(#|z2T;ND1GNgKSodGTVJP}itQ325voA{ed*28px^O_ zQ*8_3RHLsAwk&RPTm7mTQdJrqSnV92!X6_~TtYeiN3x`dxY)DrPLGYD@Il<3m$r;! zN`ueRDMv{kz-Nv6J30DKRZpjm$+pT35N*n;$R0eViWVas(XKt2So;pDR=J@Iy8z~iX-_TV}5$hmgy&AjirrefA{7!LZ)U}-D3x6 zgWM@rY4s|zS@(npGA=x|T6kK)=O8~(==!_vgd`Yda@L9Mo1^A=jp4q_g5#?T8jdfD z`lYU1UC`Sz%C9~GF*+|dC(OH$*;olOS>!PgoOLWKp>b{ zl*0Vg75Zg1x*$yrPvs-f&&w{NzNL>Vnhb=|17r!du$~Rn&Jh}xT0n0z&A7k5Dz1(- z2D>dlw7rH%WK$e@mE}3pf`J-CpCdXQ9xA&QaRK!}HYW)u2?lEjf{CAwvbK`)@Q~G@ zBD+kJr4r8DS8unj{p%dee=4$ktl*jDn9;?JzUOsbOa`D`wzw@uax4cDu!M zV{68CEl1ps;-ft7*VAO#aG4yj$ePPF2K)$4{#Ktrz9@(V*>tshhmLNk>!pX2RT6bp zS}@F`9=`KK+1t_go;(|Wb>D&AvIrIcF6TH42@X77`_Bv!_qFS)dgtvjhu%J?-UisJ zrTH7zHl?>YIN+RoN00n-qS+QOAOqd7pnVCGv$=Sh+S^xONs@|k39;2^X7dJ_6v4js z`eTA&63g9|NNAK3H+l%k0o{#`j%q$)$9sp1tmon*sh*bI5(q9KW(kL@8RS|~sM8a2 zMrZD;A@tgu6fqAfRC;MV!7JW%p)24vf)J3ICfM%DQ3?K8ccsd*qP6YB{;9DsP^)!L z(4hr64`iky{<$t3vXO)Z;lK$WmRzl>>a)LofGo0>y=%+hWabI-B0Pq(#?+a-f!u(# zIlM@sNZIm1C5iu|>zDavSV;!smAJ$8kMiOXM172QcesQdA(C~(5&(HNL&eA>MqD^0 z?u*Ig;-wsx7oF#%FvRyO)0-zdHKv+`UipLB15Vx=XTzDPtm+s2qsZX}6~Quf`fRT# zRE{Z(?%yg&b1<=TB+D>1`(a1%1$PI!SGj^-NWXl;LPYv(w*~{uY0pC|>dl6@W+_hW zeZ;=RK4)ieK?_?LMLkN=e=O_H2N^+=wwcjYx z^ycT9v5DD+J@>c^i4G<^`n*eork37k ze~)Or(tr&y%yBcN+->n3=b;VT+%!%(J>}f6=CsBG(-dtSUN)V&^Qu4UTBmUz9BCG( zc@n2-zJIWdCU(6uHe+_ZVd@R%5BGmW{+%2X0ime@y}T)7=l9!kO&}dssttoI{8HCZ z?~k`xm;>nwnaHtSqBCgQeh7XWd6pWz+P!Q48~>Ps{!377EDke(EeSq9T?`C-0)o=; z>Zz7H=NI<%x}r-z(tnBBI$t{7UjwfQd}pZl|4Tae*aW~&YT`ldG)`u>*}G_T~VIe;)~$^k8s-8Kjv7> z^FuxWPS*oj)Njk4lbcnCcu`tLrr~Um7Kj2n*6f)~266arH)DLHoqUmrY;4Mk&K#JK zmG3?IMQM_+^_tvF*-(@N_(W=l&;_nWm4kKLSvMjoxqzDh(jX8nKnTliZsx5n9)*#!u*B#zxqbtx6c_>z_5d7;fP}VM=KDxH_7J8v zSzWHz2A~1`E*))ir`3H|f6lM?z9YhMbBjB#Qs;=Sv&I!_k1&_i?Ov zS1H^RSlt7hL2dgEL}O#)-_g;Xx3F}w+GF~20*hxjUGS|55*ohWq*_vF@__hL-s-YY?r2&kis zF1f8hfI3T8ZeC7~!@!q>lR?_Dz+9ilOQ>uL=bzzW+0y#;w?_>yQh+`O0MT~p_4c2} zv{^D@-xlS#Zzr1l<#A&GYH>#ECh^fo;y()jkkvr0Vq2F0Ja^SoNn8m@7q*&t80AW^t?|IonRw5&CMx5 zslpLx%dv7=lZX~fB*uITzrZ>(PEBTf@aT^OsR6)qF4k^{2AI@9LF%B~Wr6#H7&MX1 z3a?15%=C1$xc>O@b=I*>|ABOp9_bPH^|ku(CXoKe}w6Y92t>g-@9EZ zHuJH6<*Yzs3l?qb3JVG>E|+cVLgMMaepRGZEq>F&NJFFacz0>V;(G?Y(o_3NPY;6X z`L+x~ARuaL>b(gWMo>>rk2G}-yUlzypfGvOx@Ahz1^Z=fx-F-@M8Ji+tHYu+uOluH zP(7r9NRkBsn0Wy}8V<6l^L>6Y*jG_e`I@7$Lu_>myt%Fcb9`j?%GTG8jg5_lk&$to zUu<4LFfKMWa2X1P(&kOhncne%s?UWAu1NY?dXn&p4glhR_Drb`k!Kq~SuMZJN)0ny zslKdXHy`_01DqMV2ehWJBLF!Bk^pE`Huba(uGe zp=(TPsvN+U?PkqvQIL`8+P{3&p;r-X@Nb_R9UYzWU@N1?w$9DsuF3Uq{j`q^2AH7X z(PD#OxhV|bZNj2u7z}Ps*64H^aq=byfbn^85w}+hb^sX{HfNS@vNu&ASUzBO93|}S z?cII;@!cCyZK|33`1vHLqi69UBQS1FM~z`QG6_l`5Taj?VFwViNYhSS&GQ@zzTfrU z*>SKQtyVuzm8Y7Xp3XR5Idt7{<`y~sZ=YQ-0CHwwVPR)0IZ)*VX!04ij>Cb87)g|! zDJjIY)^qPY+bkw2U&x;Y)xfX}0hW2&$w>oHZ+pDE^Nfy( zk&=|u2aIp{@3$;S_2f}v@W}gNI{BqH0v}W#@ zX5vX~Pz1AyqFSc-{FCa7$5-FNEjrL1VCc+v45xwP~sU^+fQ51dG^ReN%?5#=^? z6=ZJF^9wh{>!_h5DOGgzOP}lkZNUZ(aw^b^H1M4T_;Fv)%}urY)4`0E9bne6fFv9i zZ0Z~UZ|>?AFN*eJ5yeZX@SldWEeIP+&jGs)anZ*$^d(}_hr_0nZQs9i9N~h!%#zOk z^L`+PaV|Kr{EP#{fQ>P%SZ{%V=6~1Xyh_qbI^mb+{|xY-`aiV{aQfhNUAR0OkJNt} z!oX%PR#UK>NU9H?eZ9AYJMiEBmsWb$?OI4De4H4U=v@nGP(EqhhhwzLP{9x`feZ7HjoX|ntsLhWRS1C|0t$>^9D(YKA<>v zbr3n*@|U;FKQCqie_+{TdEHouICX3hsS(WeA*{9Md}Kv&I?iBdI)yVE!kV;QbNR{I zYNQiV&m5yv ziM2;j=AXng;blD>YLWnFQdj7*+ee2ydGb?6GG*UMG5nmKRC$G;Eo`Yoo@I2zEXN%l zlRm7&jU~xWU%LINpIXhnxgKUe9qltohmF6FDUalPe~-(uK%$R=HLxr+L)eBGg!i`e zt*5#@B42Z;VuQ|W_TrAqypU6$D~w1h+ru-P+T!VQQvSql0bj{MU$s9Mz1r&|^&bRTa8P=Uh_)(!>>vWZn05W>1Zvz2z& z^+bcoi(!6>HWo7aPbjP;_gfs%7sm}0zV-O9)HVqx&V^KPwPU+shXaJ3qJ=AJGYZ@Z z9v@AHiOt!14SumC=#wNWLWe;xe?P1lMn6}<-S@PaLtDiRV-waquY`)RBO{sx!<%C$^{(30N z=wS9On5?kz{mSBKe%ocGYYqW*y3JOLh>1a1m&d*c1h)GHdN^6bKM$kbHlq~R<<{H| ziO&5bVAbPM%yNV~)5^j`pSbu{7|e< z(d!gP#e*PHZ_!{7>>BLy@NEjh8Pb@Z?!WBz?rNwss$RSLypr#J`j64Oed5TtgsbxK zM9Ig9$$Tzq$A9DTX`YdN?`@js#hQ>rWwNqvQR$xHVTV!r1Z<>`qo4J%5@n#n7O+^$ zlkPWcz9LK)Xqx;|xKlYP>66aHnw3xNsM_$TY5MK$eb)?kY&vPZ;5*RBGucq=?s2V?}C0%C+e`91#avdNk7$|Zuwdl@#9f=m;>KE zocI8SiVb>cf^U?~V8NlhKvKQ(Xy4<52tH4kVhvlp-rs57GLnS|Rfh3FPSyEZjPlFrc)6n&&eX zE~HsM%bf4{<@(M}%b$1u_>_MhpJe&Y1~Qn;baAvBb*x~0I6^v58}4?Pmdodxu7A3C z^F#)#X=pB7TzZ(_czchSXJ%{|YmM20iDM^n95hnktS!CCHzOAR2 zw)%*psY(|8bi1WXzzmC>^fJT#x{T+b>s3osqMaG%P+Wbft5Gsoos~3#3;J5nRr^1= z06PA@r`PqmDY$@4JSuRAgN=Ia$~U6~Wu#H$S(A=U=Mo+d!pj}!G^Vy2@b;U-YxGF? zkj6(0Sop~S9U#$cl~UmAGYYNMVVT%5e^L=4Ir8O2cy>o(MVEQ@UT1hhJiUJSbJtY1 zuaabXAaSxdkL9^?#v2f+x1Z(HY6vzp+{~dZ+d{5v2`E}q6?k{Bxr}2hMRhIzi)Z8W zgG}p6*#erix?GnJ$B$ftRA=H-H~!u%-H?_Ox4^q8pUWX65x*0zJ+pb1GReLKY z*awF?hpH9YFznh|I^2qzyPuB;c%SdE(J>bT3(G`gBq?sYdNOYBE7MEd`OIC-oa|GV zo2Eb04~dQL5`jx=-u>T#S^Jy*$MwL?Y!nDABa;iUO{x@bm4CI*paUu_Hj%tiEdY_c zT@2a(z?!P%fiITqziO756RG>Zq+!yQhK>9tNZddW6H$%47(U4}C!!oleWQ*QS;G^q zC8V0@DqFt{7jo3iZ2USh@!F8*v2&F%>+$Q;a^CnoEZ_@%NTJWo?|W0<@N=~`k>k{e zWgeowvZEdjSj?C=NI7WP*Zx8@w)6Z>W|r{@F%7B<*T%>?bU(h&Y3IxOH+VYOL=pb9 z&5EJSHD?9s_`~s9{VxASN~^gPHK*zFwnjSmd&Uq%c{G{Y^w6b z&E^!}?PG|PZGmRRhCJmfs%K^u}WMZ zThgDi21Y2OId7e9GZ7y-%Ph%i?+Io5e+4;znHU@`^%UXV(C%8r5oZWX+!@$A!H6KLI`RpDNyJ0KhGP03r9OQl#MzqU}%c#i* z9><{A3`zGBv5OSmR||Ek7FfS)c^=>J+C3sd)+p8zeY3by+;x32mKGwuXcF{EFFekN zar(M2hWg&)yaEHa)ePAN{)|fqraJER*jVabxh59hVgSz57q4``$W&WlCFJ}{gOj0! zG^>z!l&DNU*;{Fr`RU8Kl=qfMxjNt+sE?p}zy7KgpEJ*ZUH0&+9WeR^WxTGMsyz3& zY;#An5xA4uo=2Bc`@}$(zHrawjFq9Ee`YfOdzF;`ufFoHs;X&nV*H%pmTke7vZluT z6|ZFy0fTvxjAk2ig0%y@8>`BqqN|262y2eu@AgRvy*ZW&T4cCiKBiAVe ze;HxR4a!#=ZcM)0Ch9Hs1mSda8TDylzp$~smiGK*>(NMG(eoSJcV0?cD4RSaGMF!F zt8A`q+jn{NCU{`U`V#MTn`F)~PeiL&%H$v7fXM2TR!K7;UCb)vwUO(@JuNw9rXe1% zNs3`_U1G4CMq%)HOVfXStaLw=COO?wpJD8C!X_I55#2`IN6h%@PMtmDlJj(1s6a=N zN(6OW1Iy1R%W~{02WEHn<8Ldpz5!y^-yz2q2j)L5qsj~{MdOvXJNw5`dPffRxo#2q zqmE4Irv4>uQVSOjfPLw3G}OL7?6QJ01CzeA1{Zt_Eq0y_d_n>sNZ?M9xp_N$RJN~a zpD8|n&O|0uz<|9xL`(Z_cptQyeA>45isuhORod>29T z8aN(*49t5I`u|QN{`Yn7|D7l2`@6flw|8}ARdoFm*Pow-HwTjS&~)$}N|W2MI6xzP zi;8MsWrdn38w`HDTLS>Gkh(fIqYx=kl?}*bclT>x`KS4GAufP*y#UU<0LoroS!tR5 zCN%#WKEO6LLe40xug3?_nLdC{??RgJ_piE<-ZUe(oC$i#w|mOWUZeiKz<>Y*0D|%Z z{$axdsi^2Q+tjg{$o!U9S3uD~i9P@fpyer~2WMrGFZ*2UyhcXu8W=#y$;pwFloT{J zrf@x8Ui!aidke6vwykaW5d$Qokw!p4LJ^TJDFH!{M!E!~yF=;5LQqNp>6GpU1q5lN zL%O@+A8Vg;{_lM6+3$P(|8;%ezI5+RJkPV%Tyu^&#(m%8oA28 z+z=EJ(lGZk7N3yN_eF22$lYkeKi}VkQ!| z5CwtnB3ceBkdwlfi|o zFXI&0mTH4lgL}Mr?FSi~56UlPnO$ai@PH^%U@NG8*X-C}u<7Sd9B3esgr;`R5<%!m zIRQTk&S7I8iJi&&4#bA@o$)UFW6tb=?OF`wi{w~<144|jw-6DgM7SFas(QZO1s#t~ ziQ-d}&ba!fU_#r~Va^;+@}oz2Syg7|&~ahjgoA?T($=&DGb&qQJV^p;Ww;m%3_z4e zybm_?WfKI*0MRC&`FQ_1rg3Ln2KxsXAu50wdr8|nSjqA&zsk%1#Uw#Bz7jtyB~hC485*^85%Z4Q1QbA2zcE- zOUK40PbuV~j=-p*4DW*dv96n04)*pcAx0^5?Cc5vi}zLh_3)1h6F+=lFuMP7VWPHn z_$(%dw7cBig!bK!rsMTmHb5mMTwTkh zLxSrZS%LSt*PG~Q#y@uD-@kvygJHg&EE$=r5;xqIwdcGx`pg3!AssL8GjO2@?M;&p zg$5zod4C9@dPk*xk zEpfJ)Zq^yci+uJ~LI%_9edv$Egj*Lnn7lY!!&Y-33iq-J3=BrO)?MkiBZ6#rWc|1K zy-vzRW}$6Bbwro=96tI%%Qx^pK!&$3Ly5EHo6(QAZV;BN&{M6*xFJST%|;fQ+e6k$wkL9^KsB-01o9sr?CWq|JvNNXGB8TYSZd z@$um>Ueg7w)Qw`ef+>}|#6d<{+6ypUTA&ZCm|96kC$~5o8XJn2B7@Q&_ZKVf z?d_Qz7Zw&?KqVw3kS{yJ99-o=Ny*Ejqjna$#kP2gUkv7IhkM@9D7u$kbwtyovMCIS z5^9YhvHu(>(u(?mp`jtum3F*cPwVuY3W52|6l!74FosyYjQjWRBk>5fkzdvB&a$5# zT&+DnbI&2nMm$r>T(=ouHok%w(>O9h0oQSlom~W0#CT<+riR9V390xFDyzcPu0Pg9 zi4OZ9`&~i78X@&jn~I9|koL1@8QWzcAt7%2&x4L?xJS(uBNNFV6-wnRtBg^|Xd36q z^nY6c(gBN`o4fcMboU>jONytw!b79rwz`DECcXDCr{ni;vW$!j=wa|nOw& znam>V32JC5=W$+(8_pqhVCUqNkdU|xpVX(C@UoKS@qS2F77MK10%( zJsx3`RNKIBqXYNWjni1#Bmw?MPI=K zB1iVW;$p{e^QcUM6llkLkzH#y`xx~QM)m!%sHi9kB@=zOzY^eq3XcGU|rf9XLtPR;KVV^i;Tk;c2@_IBZWemL$A+(l?-f!;;yc|faJe< z`_>0`9NBn2;<~ZW8!F`$6=*1E+%yT_SIy9vi{~)8MnFLDR6`@Atc(YyC^`h0JhjoI zBZee&NuY1whX_Y100u+I4uYBtWvfZIZEV||X>Ejj;dp<&b+p_b`IL|lOc?D+;qyaW zIyyQ%GqX%5kpl|rstf4o=y3wB1TYmJiHl#d8msV!%@#;`gVqQt&b2SfyT*dvXC84v zp5#CZgU41g&~Wdq7xvryt`bg8PQC=+6f*1TT@qyX0v=vjRW<4JXViRGqNKVyDZ+k?APtJqDElhUwybEz@p>|(k51$3$M8nD9Hs_gDk7IH-OexJ}|H+p} z>xtkd0nQPMhIAVcund4}sNLEpWON*2&$XVbvOxrXy9lTw-t?Sol~>3{?IOp)+Mlw4O_5TU%ZefpC8@4%eeGKO3zZP+D5rlJ~@Q`zJT2mnC8kvjOqW5N?6TW)cN*#RXz&IkC+Iv-M^Z2B_NR}-*ww~Wule_ zjEUNo8%M{+@QH~0V6)bGTSLB7{Y9vDe|alTX}jl->R;8wr%DEA91Q10mH9}Bi;Lf6XHbX#h!Yt8_h0Vj^D|u^8>9(r z?m1nkIpaB#s&MKuyr)sMpRg}2Ucp{ZN0*sgSbo{%G>_DScZjh3o$rRqXGWdlWxCE; zlJQ=irfmKFB4N6o$g3py3RIr9bY$D;-|savUUiyFby1x2lfUwPqdC|p?&qDCl0*3E z<2X-H8yNgP_T~OhF<$wqFGE%S##)kwI2~fJ!;|5z}`&B7U&Ljt$ zSyIV%H3k=Weu?3fHPOs;DCQrnCZ_AzMxpo#?e+D|!p5Y_Q7<=&1O)Dy-x>RZCph`n zXDS#gkPWNHX04hVT1Ipvjbw@DIXgEm*)O(z>!0u0baPfi)qS#8E11@2D;v@L^7Shj z$z|vIyv^TcmUpl=eX_#+$4itK&78~J%_(Io+%8h@4K zjq;~HwOKrTPDV>j^&4qrlvX7l+B|uiXH7?@ofLQ8I%|5k@LU*t{09&3;JD2(+p7s3 z_1Z4$(%1kEy3Mfz^pKHKWO!?V1Y5;WKOQ7?V{nG#+Xw;Y9n z+vucpT-_t^nYO1Ue)+Wo?Ph5sTwCO1~27Nk7?-o zeXYGKrM^-HOa0SU%9S_D2fgC(K{Qv&49^N~uE@1D=WJn~ym7!uq?Tzw_Z*9Ats1YU z8i-IXM6Uc3yusiN4l0J)3>o$ zd!JcLp-ax5y{I15voP{AagLL)^SWA5RMg`0DuM5~ci;oF=jt_Q>u|EE0#YZ&dh&s8 z{B^3ZMw@+A$Jk4BJ>R7;Xm@CwtVuo1QqFHA8H;-K7d0FT1C!!>Q2v-{j7(?fX%d=rzg7lG`hC6%^6Ro&#U0lAGP5 z&Mj&MUj};f`pa;Kkl#rN(bJPb_{PqVjn-SztXN|FKudX{ zOYG+AzD4rkfqw;w_@8&`mc5%N+v^j)$5Z;{p$TrbOt1%aGTkc+4{$N`L_QwY(t_F% zy|)Gi3HHuIQ-_K_r+rZ|oY)2f_l#t*n_gdwAT6EuFMc(? zXeKY1nA@wzkkY4n%A9Okuoa(Ws=mo=nkAk&j5p~vlen}J9XRtl-o!RXJeOpiJ56!2 z8E5%q$w}c9!yF{Dyj&AGM#%f=Cr1b?CRp{sXR= za=Yam8dcXZY5V4sqH=5a@&rSM_*uNwu-f&iT-feowxdwd)nhm$K(6V@&udxWFI z{cVy*E<72l?rn`%6_~SViZ^#_)oc#TsIP@>N%r`ONU^cz*mkU?Xknl4VygV$&?e%geQOKj>utkE>@V0K|FE$)!?+f{=KH2hpfsKRPj~ zuMHe9&hnuqOZ4{TgCsHvli2Y7TUoc|+;=W0o(=j>j; zpYIWLU4SzCS5FW2#3g4H7G3=KfYN=P2AZzD1}S;QJ2@!f$b}2#qBmSLi70EZT4mfj zm%|rVSWfY$=P+2542Lvhrnf~Pv1oHFHvHi4Qs3W_Bxk6kQoQuMRDd#3l0pC3_c7b0 z(8j(Y3^M=c-)?WGVPjbV+lW;7a+2D7x|IfMPit>?n8cEbnD;R_7BXTpQ46j~4%`=fSPE zV_VSchK=IUjHP!-=N1igqle$rO|0@M@Q#ygGWE`JO!{W64{;*QSLSy=MH zy~a2xD|cadM7`nX06{Jn350_>UA5e)<1adGh8!VnHwsG?t=#$4XUvNKsQl|r)$(KM zQ>n_rL|t#uacmdN1_w^!J6&qtdK}kL<&P8T$X!NbMymB!4pU>fYUW{PV`6akN~Ttk z#S61A1J|#1{8{N*=I*eWlFKiv@bV1P)HWH#7U_D2;gk(>4k?goF?zT z-I95&#@t$oUtl)7YI(=jrbu`|=X+grf9|ou?w>slo|uHd9?Z+RQQtQYgN+U{wC4MU z+p1b}@I(GY<#TY0U~v$pj50TCo|{H0sqq(=U&%@leU-nhHZ?ocFv-($VoS|F7+u_%ryk1?>F<5a z22SIb3R?_RC$&peDm%lramZK@NtLX_V#NQFuZsk~gW zW53DjQql{?ZLD+_N|mdiS+|l5?Uc+#fzmS>_Vp2?-j!b zUppa?Fc?Zsf$LAdx{&4Y18hnjJkSYSI(60i?p2guPZ)D0@r+yDc=gJ}?tb zygLGJBS`)D__zyb)NQ7G9&jgB(riBYHYKp#!EpeE+M5KOZH91gK|%0vjxK@5`IjhE zsBeYdj|?IslKxdCKX^OSw^vC}F`qsq!8OEyf^yvJ*pgMZM(Y_^!$p9n@QoWcP!}#- zKycT#_H`W|4PXeYYYwjfp!Ns~LOc?Z)PhK?>v#3)US3cK0}a%}FHiMA!H0o~DY`yh z&2G|3NpyXpTB@h9@h>SSh?A!l7oW`+sO0o44wt&^n(eHPT=(+wLQvj6cCO)kpO&ck zov@+Z+thk4e#E@MjdFb@kmKAXlQ5z&QG?3LPI&qv?jF7Lvzfs zVdTwb1_l$f?zV#T9!*%7w2#dDUs?d+OP4O`{rtqKF)tD2?|-=^f-1`+JpBj&%X>~M zfN`snQntUQ6?^^u{Uy}*&dz|eG)7RYUl^5pABats#2x`HEZ`q_8Lo6=cefc(z}L~y zCL11ujwj0eYkR7YVuh95S@LGNNM|4Pu24YS=;`RtfVkis7J$N9$9^0T>;sY7874&h&ABI+5N7aY;!-0Jno8 zA_m;Db6tD5TTwO(zi7cs__Bh60z9UMD%Wl7yMx?=!vHMg=QF`V8DA4Cd-gj zke8Ao?f`IgITaDn&FPt$sF8w#0y-WZ^>Qi#0y+aj!{U;2u(1s}J3S5sNIWwN(X@hP zB6F#|y?ryFqy0;%X=%&hx+B8chS@r~d^G@86>lzP@&id#0N5{JBwsk>XdSv>N`3fY z1UAQ#<=R?WtN=j%H4sM$jY~Vl*C{Er3R94-IC;nJ;X9F zA^`a#P052g?oSYG-2fbCKq?8(iUH^W)29N^|P?%Z15cLo&Y51I0(u=E$%?GDJOD+*fE!elGTkS@S^oyV#VqEYSy{y8$W+#i;EeklE>>nGQ4rLoF|6yDx8Ab0Qw$^BW}Uc zhprR^*Go!HE^t^>^z-xkoRV@0aGab1W|dihyAI$rNGLiv8QI6a53~mVqd|{B=M$(i z`G3;YAHj2)T=(9;)&wrJA`oA<_|l;zi-(_|r5$wEq456*n_=h<1XJ@+fuX*&btJTP zD3wq2_Y)%?X2XCla+1*B+1JXz!NS4<_no@7r9CP)dwX*)BTYvSpsLJfdcVTU)ywVR z91b9U>p=MolP+UZU3)vlI$~L7LTfqXNw59qJV6r^lj6;kRI{_UT9z=_L4YpmgVY^Q z^t1c^n#MaK?E-ZTjU+(Shstc}0d&5=&(H6$K29Zkb^vo(Ku`b6;cz@qnT8AA<<)0qXN#MeF^h_c;ggUA00qg^jsTCyn*QF_CJIkl z=y-(#yop4goq@($|Lo)tnsX4ZZj!rN=>sL9p;%tfx^V*1WwSx9>YW~$13>HFync-; z*NPY#QU{AH?X3bwxG;Tv{o8l%KGM+82se*L>4709gsw->egkh51VMqkZ!e6DVbAKP$Yb_|rA+sN z^T(xji1@HOkqWv!xT{^^2T?9-4Bfo#J<|q6@$YZw+3o&F!7?XPN|jakP`f*zxv{<7 z2zYVO>(_YUX}fXp@deM@NlqYCB14Troa1*XuTBrsgTwvL6 z?C-bAuvPoj)zy)=0(6gpJ){l}cj$&S0<7Qf&n1C46amuj80rTHugiR3tq0_|h&szW zVdtl<=VGk6cEOW49{l7-;fgXLJH^}(JIl(;k$w+Sc7sb-aH+MHV_=#f(-5IVo!7@5 zsWqQI#lytJ>?6pAX^@jHuD*2>R6iej*a$@(0BqPNk)e>Hes6GZ3w_1mcF zrjCa_^JS|n+(z|w=3bN5cN<=baxtU5I8a>VKj3UZsiB|jz0cF&@6T9M^3>AFsC$1J zjnwU0qPWvg0w`pqu^H1<{2`m2k)<%@@DR91`=nd;Qo7_$M zyLlM%E&Gyv?H3L>4l6XuY1s)C2xapL2oHUSfAO^LQ_r8&KR2`R3v0L!g<3Myx((Fj z$Hltlo!ZSIfz^{%*8U$p7^@SFUaoC1$?Df7nZB$>@lzM=8jAbztW80Urd5$k-j=Sm zMaBEz=d?PeCWf!}O2A)PO0C#V%zz<@Ax2L{_(>_u7TxU^E(o z@aZRKW_;`F9$Ji)Jjubq#l6d|r4Qxzgm#M-d(+C3e3xFM;R{9v;$YHeIgUH5Fl4DAhcj@EXtKhORDL2RHA!fmY?HcG~Lm*Wlhz#(heKN>A)A-e2`v2xn|H_$w${ksjm>?~xKHCYG zpIFvVs$MF%@ay*@%lX8m7AUL%8!YyOf5nhdFqL}KEQVgp;QnQ9c(Wp#KScOl9#gxb zRn+H^qnUf!-*6dZ_Udo#+~~n<>l2F;3PXrwSk_|EfZ>q9_PB z@Hy)*WmNzdsSmpwq_U#0=0MMo#u^KqKdG`GeSk&*>o?-=?rzjtE(s(8nK_hypKR#n z7~W5zdSW{=GGf>ignzJ@miWGvO4y6Crltl+kf{1o7kBr@&dzJCku+*R>kP3Ejf^z5 zw`X`olBOD>mz7Yg@3fxv5}wcO}r%ubQfgGj3 z&Ct-$T(D$mCtPH3S$;y@p*q*GpR*`@xKp_Qr%5Ye^JLC>2-YB`cyN`XR+;LJE7qrK z9@HOG1m>E@=9`+${gbkj6j$Urh;vzRncF`kJjNIE4kln9w2n?}zk`?T9Iy1juv^&8 zFsQTq*02W+t!m(3;fC#6@3u2kD;=d9Hw%7w^B-U?~h!hGXUs4h@^8m3XcG!q$d zGf47m{k;_1ZQ{ktUkw)66wGFiU>%PDqEp}7`s0W6;ZR!}Cg^wCR)LMxKiryCFR>s4 zozMoP{PeePOMy8=S7IxuO2UDA`!hdL^Q*6^DP@42tOwTb*y!lw@^Yx~`3a%C(BVxw zdU|%yW@zklz;=je4B&Jefck-zMNW!X=9Cj#Dlac*qH$jMb>a8#PkGg4ZoA9~`t~pa)bknAItVV&uey-f>eN=G~m2h^- z+Jhz2J>EhvQ(4@6?8(OzNwzozze@R_xYb8s2>aLw{!5jR9F)E+9KXho{ym|9;QW6@f?@ZMC_qSWXarZ^A zF)_Ug568*Q$w7(<$62Ywqmtf26M3kl4L}ov^bjvW+lN9jLWe_S$APpW)7c}SPZK(g zp{{)M51ohs2-C<%+@OHpfI^fGBp7z(lu+%&2zgcv)PM>v0@!$v>(Jl1^JIooy5h)d zDI=|~?K-Ra)2xCQZq*AEzPW>m?;kX$%0nmy<>kN-nT})ep^P_(F$@-deQ6jO(L{84 z(;!l5`vz~;4HsEF)7aS93{y-Z`mS~p=u?^ka443Fd{kncidIJ|o%2ZV>Ag(P&d#(hHO%^*pFR5+!)=|g(4Q3zg0)EXYB$H6mej@FBx~#n z9q;~rlglzn7DvPBt0#o`Z;HbFo*rfkk8x<0SU?Ry4h`15Lth?XLAhTQO$4%2iYI4u z`^O3*A{W44HxLp{Zl~Ko$o8RP^u`R~x9)LqwIG0iiOGD+wLr+b$Wi|gtV(A;^A359 zBw|bk{~dYz^F5?tyUdWkB?a{UA;`{7va8yfzg(p4iuXZgpWa53$cgdAnWCcD*s_tB zrZ)~T`a350-PEr?#?Y6egxNj^zQakXtY8R}s!o@wb8-rcHUn)y89>1tnF zgplh5=yXs%;3{shQtq1aDVs6QMbvG-y6cQ@3-&y&Y_+p&6r~YR zqF_D`^W_hzE$W;d=-2az4HT3yHDW&M_0Ejp57j)6Z_UU%FRL;Dj+1)og=_j#41v*Z z{H(yk+pRhhBL^tG+20Nxj`zuaH_t9C8ijI39(;B;rwHF9h4;?8$Zu$?jLA%d1{h=h z%`&WxCt)}tY1xXeoEr=`fnVte3%}P}OFu^$!tGKMOilDCbU*VnZk%mx&=$X6%Cq4^Loh*#;rg|%RkFW!`EFr4C6hHyOT()K(&JWFBcdqU z!=EAtDYe)f^wUe6a-OG=!;U0MZsoM6s}ECZI7wG6SU2jY4#-zcDQHk9p0j*(kB(=C z|L9=@3mJTTcQNdRb(>=x^&l>iQ(EZY*9f>aPr8siVxXT@W$KJvkL;Z}()V5p*9ebJ z=Ux7`b{kTl;m@9Z`-(3Y;?M*(N63cQa0C0losw?!N}rG?+IJl>y8QyF+i|$R#sA| zV(pcxR(eSQd-dR(T-)!8(PGt^TV}Zt&qw`&-{8xJh1m$u*VZ3pbS?e*Zwr(0cW1%W;!gl*E-dCn=nr5) z>_XZ%cYm-TPfa{*FR)`A(^Gq8)7$npGBIF!s@u(npX|S>QOEljCLhVz(A|UGE?Qv# zD0a>%z~v&PyR8>cXr=P{WE8-b%$T|!??gAp_Znr6JdeVRpZT@ZF2(tyXV6=D?@k9V zQ6vZN0vzK0VbL5$?E(NLyt6zh5>{YcvAnefXT%w3tqGra>)Cw+Kr%)|$rLKNLIM;B0TR$)!wXtkrf-y;$fTUQAl?iyh~JfN~eIfv6L zh^C^4EW%D_1ay|yLjG{0BVPh7B?h$@`EhQ=rMe$&l%dIr_iQI_QMwjHwiq@lZEra0 znFK$|@z%d)bHo4RtolJ&O;SVi<4jLDU9lI#VM!~Jo4^}bxI&1LI^OGKAv11LW`xY)tnc; z5PwgvU3!!?>*-xv`Q_OwhV=9Tl{2hww`G2P{mKcgJXQJ2l*7a7 zdL`bBIoDIn$aS>^M(gpbxgUw<;<*%LaR3X-&6D7IY7~P{9lAyl(bzGBq2yjZ^|H-> zEnILy97n&g89Qv9|`Ax~pvP{6spR?$k; z5>XXgRl9!F(kz1(gbFgg{ebUDmG`~eg2u8uy>8*RCC*%F56kj+SzpA8Kao~d_y|>^ z8vMZQTsC@z7;<*NR0SHYg@z7dMmgTqVtIP}EbGiZ&0JGW(DFD_pp;(2gD@(oFk-gH z6Upw&@8-=Ypemn*Gn0IF zAJM%4LLlK}%NsHqH58rX#X^jbh9QqdfZT$t*~L9?ubp5gJgb1M{)QRmr%@(R?J$yc zf5@vp;4Wr5UEt!xiC@Y7WwL!0rsch!-m6&XTNTU|-j-!CuK-8j+^g-$xnoQj)LiLB z#a^1oKYlf`Sf6+sn9umqh@!3gqFe|0zz{9onDakq^W#^LbYV~}h3CmJ z&{vjG3c|h4;nO%+TV6yBCAwWiL_kQX47Em;*L_)+^mx17^riDmBgVUVq=-}kln0QD z;5s`rE$IuHT0T$ZxTd{ZwEG-^YHXiAflms=Rq(~h%fjZU|lQ;#4( zxYt^j!$n|%dS)C6+$X!9cb#r&7A0Oy`4S{~hzzEF=4R3eGS1RBVY34z*B6LFNP)Dt zqS*~ zy#XHTq0-Uz^qT!aSuMI3SvUC}N$PjDtcO{|_-@DDYVx|7v%?K&|JZcUP_JzYk(3LY zoF0Fy83;-7{kngJ@u*TY_b?UJsDB0Wnb7}2hc}V-Mb@}|I#LYnZNJRXz(!yFnFuO& zBy6$kSmIDI;E&ZKLNs^1{~9#cb6Jn&A2MPO*%5HJGrzx{nCDqU0Wo08Uwi)7Knq&o zDgV^q0cG{+LRMM^o67dlkBzf0JO|c%7f>!=vN-0hlnpu`h~rYy)B2gN+<^KaXpIpW z>HlwH(EnK~`al2lN#FWaUK>qMQdm)3P~|AgAd1x`efcfUofCEf5}Ex*`&Inx+8Ekr zBZYL+`KvzyZ$Mq}@{xSegWZWC_Qjsj>95CFe=S44Sk0Ge=T$)<(<&3XMd~d)AL#qS z^=+{?1jM6CzT2f)1UacGOf4xg_My?A*#331pOUXT#W#X9EM{Ru$I$g}7n9YwC898e zY-c9x-H-2g?@G!uYG%7Op^s^f^r^qVFAS&$nGSz4UHhlQDy zRW`G1s|b4lKdvYV4mZ-s15O}Y4`&3+N3-LR3A%%-069Z)qUpUD_E z_H5A{JUoWAOd|R9vncJomiEgsK9zN&O$P~0mBPDM#`#GDK}|OaiUeeH+jzfaU^F22(l>n_W~ zXl^hpOm2T({Z;L!Czxuwq8!17elLY)lT*@7ZDv<2M3eA52`ll9BFB~n;8BR_v(MS4ZkfC?icM|AF9*~Z5kDdR! z;E5zUxjN@-%T`#@{qHL|??*uFRHmVx>J%muT6y(T@yDx=nTf9K#w#daagHicIc8Tz z=ie<${-?(2$6pFVl%9c+k&U}z5{m123kin2~}_)J=+vscWW65VJz@+Ah0`2^slKrfOf#2dnq5ASIoU2g zdea$`LYYUqT1|l;n&tl-MmPXqxy`rxV_N|c&K)%K`1aKpTWup*8ZE`@tI5YSe=Ykh z$8XIQtrb)641=KtK#M$eBNtiKWNx3|eOEtHaQTEg{^fZp;le=XWr%Y!J^Qf1mi^X9 zIu4jwC}t_!M#tZ<<%2-r0z`kd`|jG>mi38;2UP#=Y@p{`RGE z-pr_T#?ORTEO;|^brk=4l+pt!j62K*37a$NpGexwY3 z-mTx-m?Yrg^!A`4#2=gZq~$X^HTC6W9vT~=jRQIFJ)BDQ-CGr{2(`0vzY={({qpG> zXy@zLPK*?1jwFvJ0tQ_ts(%grkTl!}3izikw7H>fK#ON^aPUy6H4QMwh>c&%wG3(3 z*+dxtG6EI~JYNgj-Xqy@mRvZ@db>}b8Ynf+0Nu;JpQRbpRWwR0ki((q86Is@1wl(H z9LM?U^XD5#?g5{qF>B0@x3Qidq?XD${A#ajSWqtBCC`etxAGxC5)Fy&fG7rEk`!m_Ul*G ztltk=^LYNKKl6Qe>6BSy*1FP|?yk%1Vb;P30N#)k;3;01iGvf?|RO?$2)hhW0-=kCD$ zn6sV(4vR=upp}Xi*^iXliZX&)Fa*xTt!r++e11G}{(csEX&c@% zDdAC3h39CN((&;PPnMX=wAGvp4^1~3N=ubJODgbq;lM}NV?AA0SpE9I%=z?F;KY&|Oy zD6W_}3$7m(inphx)W+kiZKUn^m}XT-iRsV<-tF&}qZ_Wl@Io!Vm( zW2*7%YaZ4*92)Msbc+~s)(Lj<(N)%{k^Al}#TB)}n|LHA?w1o2%FrZblvuHr!ltM+ z$wr;!2)xL4xN@dhKE2ouzMakc!267zqnWeuzrXT?`Po@&_?J#ru74X+9e6j_<`*~V(tCDG-j*!?%=^q-+yVD$P@peWik&> zgGrO&?b*eKDn0;fPKTDX$lOg(<^1njqRMlV_0GnmvaRY@-XhanEw|RhZ+_{&AS!NJ zgrg}&XV0moN3cspm?S+%PE4_x`7yfuG2Qh~{N#gLm$iZ_)5|$(Xc=xgKlm1eX?rz- z?CX~|53!uYu*HIY_SCta+oU`6%BXNZQ9toaXl2yi&9d)Yk5lThNzJSv2lW7t+sG$2HPI1dta#eE^!bdoW$cOZ@i6`eN`!zt3c=3HvrPGBaaBkN-=>3_rOv zU(qiQEi72CTU%H(!Q#E=u<%q_P0jv1vbqr*Mn5YvcMlI^qOPd1A1tkPw6|k{ndw<`cp0Qq*TNrNgg>C6f=N<% zxH+|KEhU8kj|-3VU?I@+;>C+^fOptAiK-#vFor386%=Yn{~}%49a>LDKfE4k-0_45 zKl-+*=cX+!EmuIp#LCJVR$f_I2rj1upntO6gpN}#ki0@ulIUOn2})KtBXkIxW<(#o8iuC`hek#l=roZ)wkEo-&k zNj$tlRZfNu>^)akS879*J5Eh6P!ur+3VK()_Ec1DZ!VokH=^I4BNL#1ZkAZymAc1S zQc^IhOI)bnzCBcSddOOkA17GA9~+veY&w(|Ns?dhZWklpGPvTigkE?mBQI|PCxhN2 ztxZj3`0)C5o$=34&B$LMIn|Zk9nP0m+#gRJo{!aq{^$}=b#mFy$*Hk3l5B) zY;%~$Zwd}J4Nky;p|Dl7fLU%Mso~1r>PW}WpW&XcT8&4H!jQ3Hhy56Q85sUKmD+eKw#k4`u@|AAc3q($bPo>tRc$DVaQODn0oqtL>bd;YU=lj|#Pkw^#W_A0pz^%Sv66FUE^v5J_ zd9f^2dmB+pg268Y<{RT~%Q*>hUnkU$noCK!9d3ktsCjwR|3wNDk2YsPM?OsgV-a*U zVrwbRuEqy?9RiLxcPz2rB(cA)-LQ~P6F-x_Nl@S2jgR=G_cZP|{hD3!o|H}D4iMa2 z82Qm7oqQwyK}cw9g+@wkm;N!Y#npt6)j{T8!<`$SjxViXd<=MXk@9kL?8q>Fv|5N# zvl*(+VE^h6fdH}ZPTM;Cm}-zkq5s#V)aZ+-inkI{+)0;73Sv1q81#Q)FV|lEO|!E% z;#)0PTk}}Ji-${#RU&)cNS2JZFO{!TMW8PH7WC&^dmi37+u3y<+VP3~>|!Uvdv-R- zsO2t(hJst3*wpo(J&-?7oG40k&%5TI*X6UFFFA4dtEQjd$^8WqTCj9qnAuqTRm|ZR zR}=8Y{TqIiBUZmKr>074;FmQQx1k#Xf);pA{%7Q^R%%!giDmXQGyxTcJ@0(k7k|_r ztY7yICv;w;h+0?I!WZ@8EvlpQ!=nhN;lq1jFJNOWaqqDg&C9D|=u^90*3VP|&tzOB z=mU~6d(nO>RvqaVig>h1&1}87LcX!XXL#Vb4T^T~I6T{Pgr@W5KG%W$LBjr#cfs#Pe1Z{RLc-uQC%qDeNk749wlen`Kw9 zIpN&Ds{T$p%pK=R+2Aif&fl0-b3z67gjZUy$%6QW1y&5YuF`r?S6==gb#q!cJpTSa?hW4J=9d|m8tdhD(4Jr`hBCu?O)GtFBkD}b%lnm$IrZCYpZRI>7(@X z^1!iAq%u@)m3f!wvtEEE;Qa?voIXI$_s_HX&4b@o8!k&~8~i#LwKM)MRy%q53I>NG zmEP~ciY_d#+?s$uv_dk^z1y_)+!Pm7kI?0fR0XQJT36-eAyJVkml2 zQql%kRO~rucxrf`i47$lKf<6*v0jS8Vg+Hboeyl&h&MkkaS{W@T6cG^t)scvsTg4Bqn$tbagX7~FP-voF?(I2&9l%58s0I+c z!Qr33%)HMo7`>75gFXW~&>=&qQ&4W5YL(+K4zA)}9iKR{3<^lWfZ^Ao1H-Z#c&ga$ zuW6%RK1!~KZ2WV#DM~=YrRrch)Oe&w+_c)mzMk7Se*QCyXYaZ7{F$+l5h9XQGc6GA0#1kckcZBW2=}!1djcvm!Ro0oUD6=R6i&pBBC2NZ#Jdw`46;nUw?VX&cTVU zy{Oq5g*?&ZEDewrL0qi^f%%Q7$KVHK-8T;ML!__Z9izP6ajsT5nzglc7`33rQ{6)h zUT@Y}vqehI{`2vBkxc?MBi;_~uU`vgn|PZKgSbDoyQZb(5trkVvXv%t)VH1%YsLTI!F$-<3U_G)-QHJkG+?cOUhhf)s83W?Ra-0v2L`@DHS7Bgq}MY)Cq{*3 zJXa_O*L9_(gGJ7FX8-)j=QJO<*Fx$Jw&1_vm7r2?Cp8Sq(W(A4J2&?!dqVNs>P-dB<+Abg{_@o5-D1JcO$Va&}!!ULd8k(-o1VNUvywu@=bJjm6 z1@E(7+bio@$4H7|xKi#0v#5g+JqUdL>Jiy>>FUx_+t?lo1z7~h>yjdk zJK*nul!xFb^0}Y@A8~RLrz@ziG+&yH#j4wh`55cFwlp$SAbh^D0}Uv@`udEvc%blr za&!jd&?rM}D34+|Od=+0w(#xF)Z0|P^JeD#a}pjkp)G^!12T0>1;L;qU76XcGAXHH z65RqO9jGPYDa3<+6buS@HoUC&0tviUnau@Aj*;p5>=_xje&9sd+AJKIA3<{H*V2-x zdkqxb<_=+A=gQ1cziAez1YGVR{z8<{mIJmyt*~Gb0}2pfi{2#A|8--;i%tw#Vi#hl zIqdC!_1FA1^nxy(fW|=FnON(ZSJ1|D1=nkb!J?)H^W$LdT&r`0Q|`a5qO6=89MBIT z5_mT^KG3WFZ2p^E&VCc=MgoNta}?(8?k-p?U2&9;xpx7gR1=s-SZr9p@#REZ7DK47 zidW<1(7`+fr1F*fKmR=;Wk^JS+dlit^a1>VcYr$cfu`=il0Z@n6b3f7ICLre?9zRv z#5SQ7V`z1)Vz?CiD;y6G%l`MQuwiw6IqS9SDszo}pYpl80reu)Gr!lZI`R|AE7>!y zUtxlLgGF1|pQTm^cJZW?y?uR6kX8*rUk>tD9qhBr&CI5!`QjtM5-j}i_?QEln)bRS zX~_B}zNDrGoX&CA>cfIJ4r7PJsReS$2KXnYvXVcR+nO5QGw#Q?4_osic~5T-fpk9x zVl@^frU7VIx!qJvCTct_pdjjKZ4Imjom_T#`EQF@FpEd>GKw_tZQ+2FZVwL1_-LW~ zokLx88D?Y6{9C>*5w+tF2??FX9Wna-9M-9E{LX-W9~VLdulFRrpXYI4H_MNNLB=@+ zDc!5IDZIXSt_#-vCbk8M(#68MexNOq%z;tA5d z!H9^67%F=4?l&6=;xO*)-K+u{l>=F`_mcEL#OCT~g8+-dk$hJ4P+Sx$jmYmjm)9<8Vfz}z`;f-omGt9UL;s-Zm}k1kZH1Wb z8U_XzINFc|1{qanhv~!`e5Z;1 z+aGo5HtTQ;!1WVCvUY`I+v{Nk#bp%zx}2$VAIS!T!bMR$Q13MRN3!XR>IY;`40 zmf^&A?R)p{2Ze%#H$Z& zz93lIr6IL_k_c?eq zO!rzCnwjNZows1&XyEM(&w5Alux@#HXQ%F6JxIr#>HqS;~9Stnl=>%~a$u=X?bEo2ciUWiuC67>TSn-JxsG?&sb%{$$5ok| z-+syxp8!XXDj!w;QCd!1_Es2RQQ#7r&zOtVJJb|pzvrjB9cJ1bEW7G69Wvif!)|Z^ zgiFy}mufNF?$b}t{_(^s!nk^LL=+&;vb5wH1SV87=T6+s~; znzpY{=3MvoUQPNxZeJUC8WEy9k?kG5G=tX+DnM%ZCtBhQksJ0SO;X+X=!H zS?{-APTs9|rL=qYk6~Lg-gyQ--3M7Sq#)b9yY$=FuaSxMUsdcrmhmtO+fNDL!lXnV z=Z-!-E!me;gJS}m!F{C6CRW1X5V>8kcMu5hpyfz?h-?fS%6XIv)MkWrqEuaJIT{~-0 zwOnDp!&Oi3S-8ILg(3^h1Znv5qL(0l5z_%fJQ9DP%86=lf$3s?8V*b(aWDWeEiM6@dvxqh%ahEE=IJ=VKs}zS;X-a(d6>bt8x_YHc2}rA$8xut(u`5 z>t$=Tn56e+c(=;l?^x#3+6E%~_U+r>06-(G`pQaKXp#$`q=gv8?-dp6tn`K39J&)& z$g%I)vlJlD|o?|UDsiiO1Q+vO!BTn=&jmk6t?ey77NH$5a5SYl*u&Q~oPUV((T&t?j9p5ziT z1w(91w1XC#Xu1;(8jjl8312<7{FVh7KEfc^10tmr7+MiK(dA(>SIqXK<)K(D-DHz@U|cUr1mTzNi2~hIl(xvt$D))wHy< zuF7+^ek~CmWghAMLYv2c8Ow@w>zt7i!)w=L)+WQTHu1sfFd5|nNf&75m?6EFZM$ON z)0z)QM(1TLZz00heOX&NC9i0a&un%;^&EZ_P5~2h*?8Tiks~Fj!zB0z1_#B^R-g|< zHSIE5p)SdYShn`Vyw7+5{WglMU_4CJ+DMHtVXC_y z(qSkK>BR=vPsTve?Y2B+sO8cviOp|OVC}O0uJyrB5YDcCS5s3{E0P~gm`}Z9kGBh3ge$@Km?^}$^D=VMk60xk2hL*Og;~q`Nxu0K@ zCPtvsJqAH#9k7|WToJhoC6cMz?H_sKU){og%N(&xXL&|8w|=r5RwZfdHa9LJ3)3kW z{d#` zq!m5XX!N5JI1Q~h`68Yd9gEN!v^9CuthR4*;ciOYC|nQEcRhR^1v-Mfp%EZ444qJd zTo~#8EZ{J60CmMidN+9Zy1*;6=D7y6jqul;A11RzRaG@7H}_C@1!K9{Uvkfe>6k%W zI!qTAqThTL8p_1n5R{h2jI#kq4H_v}MI2RD_J1;vV$@7k8KL0FtC4pB7}ITu&12X& zD3zdbB@hP&S%z1qlG7zu9rNCUp=FCbX&)}1HtNt)+`LL5&Dvyq@DkudlFwj9i4+>w zC^9eR!RwHe@z#hTW?B|WlW8C_7diLr-c6*ds8KnUl2)J;x||;)`XkM>8#qidK}KJc zcNS)oa2>_+G{kX;xESEbN|j$;UfzgZcJhh(EaV1aMHq&C?0kImI8A&kHYk|^i8Vs{ zL{#xe+n9b|Iwgm_l9Ho$6GXPr(|R< zCWkn=Qxg(=02Qq-hcke% zqmsC|_(QRvU7weu&wp*FtEF`TZZy|D5ydWnB+%`&PUQj~c6Y<2*kqPp$4uL@R zvMjOZlEEW1BP4}? z!PKTFsmuhy>d+0ZxPtO!@H<12Sr>i1W%JUpi3wm%S?~dlkB?7lp^$10h05EOLSHh2 zSHt2>wC<5lUa@@7HyQl){%EuI4(^L0LSAkw(0pX$kMV+3k_JbGcSS|f+$p0lwNZoNWieItsE;FnCyabZ4@+rzY1YXu_{ zQ?lM!TK?$)nJ}4uU9n`F-dVC%6EeDWI3c8@g=oCHa+{>tP|ZlE)b2$aZpUm3Y^Us;o;)Pk1xSnk(A`? zyAIoCgDS2Boe7f?dA-TEFzAyOK=_Tmg)7&u-yxu1XzR|nub_L66bs@B-aJ=SX_TwM z%_R=!Q)}>vX#Mw?aK;x_^#uqePOkcK*GU9MPfiSGAkVh!icH*iMB|31m21L4y6x{@u=&6YZDK|#`G9ZZ+Zn^Slmb@g4)T`9M?w%Ni6Yn5P ztYBR_QAkGJ0Ml|{l+pjVVTuY5w>raa|2p5o790{MAgf5?h)}vR&Y78_Z?m%rmxH*- zhs#E;VotmyLcklC-YQJdtHDJGb$SyTG4LcVK;LiPjDk; zVbfKl4`RI|adDv5*4Ain4`X=mF_QTI-X$f{kVVz#o1*?izy8(&{ic&DT`q6Eo@2aa zE-Aj#$SR-z%}C+i0q*H;>A7a^TkLRBTG5nIho-@gb8`nb6OWP50I*e zbAU?}vDDVrdm`gVgq*?4$Ii{Y4DY&|udlE8R_{Zc^u&@IKn4!O9<7~-WeD~C-@oU) zb)r3e=FHo_H!BPI1*`m(O&$T=@`CUxI*_`l=Xn+3fNCQ z*nR2Nd)~4RQ{aWG0rYHMwRjn`zy%rS5WcazPjv#Ozz>C3C;ytwWIc}kD=}&B4tVtF7Dno@ zm}f)NA}A;*TT#OA*_s#ByyOJLv!BBS#U>gm1lvDWjQ17@oG&PQBw~8GtM0p^JDO=} zFm>e7p=r3s0v`+3v#_w}t^Q^D^4bhrM|WW+@0jqH=}B@AP)KvQGs!9|>+!b1d7H2p z5GL@gpzSvw=~l>N(D<2@@*zK`U$sj8wqqFY$GL{dt;hMs4Tktn$;%}5`aOa}j2gog zC1!hgsh`X5Z?J8yerW&UOn~zG%%~624u|LJT-u|a%!?@u0)|gMCCv1MynM;=!GHfc z*bimHLYnQ4gyHOr9k)Gtn=~vNhVhn@#OCX0hOg-8EUjULy~?N+mIY`5Mo$<8T7k9Bsd{$`A_?CLSOW`F}p8p_Jb2vZS^%K`Sd86i29-`qErce-8R0sFJr zkp~N&bPavEZxnY=Q!X!hf7|nuogHg85KP*CmNh-32dffx-9LUQAqo>ObD)>9XwCu9 zD4=pWCuc4cmzek(nPHdfVfBPa7^dA!pXdzl#u4dW?l86>F|comC-C~REAe&a@H;W^ zd_ED@aFk5I1T%1?V;#ecR=6HMny+B9sPK%Hq@G1(#ds@IepIVAN2R|>8SAIDz>vll z0ph~8<)xStVh(z0`x+MykCv|<9v;A^7>HL6md@(dC4G81QT5lb8$3R16^Qk^qz8aJ zJq^3k>t?WZ0M;46FU2EeH-Ws0-d?Hhud@@Xl7)-cu3S0qPa=&YN>WDwRVLeJEp6tb z9*mbpKJQ|iZqq1YHZ{u_&-+w)fGan}M1LYAK&1(dnChjXwGn_l$mxQ~eI9(jKLf?X zHr=))OZ2sm5xzmHV5Fs^yLil#Ikxjw9>yo1fh>^0#$&j{r5d-~%B?H$-=B{)C<=;- zOo=EiT(@lA9Ha31axD4ZjT;M5j^yNmGOLJuHq_sL0TbtL6o9g-Sy%M+FQf1w1Cb&> zLGPZ1%!Vh-KQABI>oem2ht>~O4mQPyHgSR4a%&+z2EM%{Sj;B#-|OnAn%Yn^z5+F0s2gBmXIBc)8c) zfl`8*O>Ad#cmrk^e&Huj;f+R*`4s^C_0Ep8cHZIW)S*7@^exA1JmbSstR+P!SIu)rGc@(@_o3?COgb6t60vG@~%7(WT z#Kp&}VR;M&F=Uklo@X+HssjEZGCOAC3n!vIF1oYfUx5FlnLX%q3>kVW!#bRQ{GF9X zR9f=T^P!nyxgFyZFUmS%utFP6jl||v%mRjQbZ1%D{lNP{!MuQ--$|w&s|JUL$QmBF z&Qvr$_(vy@B#(qjmMJ`Z=!%Ag*f;FkckWaziySjxpNNc!{(s?Du{TlpBkWTe(U2kv zZX}k26cq6^ix+pCnZ|@)z@(K9l^TUOg8T#Ja@%hYk454La>7sG8cNg`6<4_PBT5gSL{83 zwrV>M&oNk(fyQvh+gl@c*2^hiDnuIq|4B^p9-stD>;cgWpD(%R?%h>L0B`jqL(VuR zq1SLpO-^1PACy@lQ&&2`g1YuIqWDrz-cheUhO+=lszDdTBtLz6q?Ro90bX^BZ=gw>>nUg%pE1M^V2Z?B@a}?Z?WTr&#u`9$VeWaQ}F&Fm>>mFgp7SR)B@5 z2EYYy90MWwV|BF)Nb(d4Nur(RhK4aa`hLP|eD~L?_cvw{_Yy;xAHipi1w(vil=tAh zFTvXc^Eb1wnc``4bMrhPWV;|c@XkL8*EAH(b8|cQXkBMajW%KFlf%qV;JgFY#kz1% zldT9UDYcp>AE1~qbTc|FL|#&omLLlV*p5Qu69QzO4g8*0^S;L+gJ;}^0Q`+);~ZZ=j{Y{J4UXoA2LK)|Om^Fr_OZI94%#E2kFiBRBT zZ%sbdqUd$U?*cQKFb^(V*hty9Yu5^VQQ3Z`;2xlTg1-Uw6Y^iS#A!b6vXX7q^+nh_ zMu>@kSg~fQ@zn%9*ZCWmbT)&ZEFY(JN7~FZ##E)hX}`gkypRFPLi9h{-yPPBO)#un zsrE4xIWuegyWEcfavT<6;CXM_x|NC;V`5I@2rJFfMg|0NKqsUG1?ZQL&MxTzR%w6= zlPnrV95^pRQHu72ba6l)c43Vcq5i{ksO>8;6@7ZdtslmV=hN&kcG!g}7brc9ShNH* zc3Wd_ufCasv|U$;QYs=S&_F8M2pm{(j{E1oa0g68!t>ukr0nM8bwA7=tn8!J?uQIC1R zvvX&2Pq5ThaSRb~{nQvp5Q`oJ(YuX)6Pu{>zGqk*N7lQozh#wcd-_?gc_~L=>i@Ty z)4zV_e?4kCU=D!G8KtZ@&&hr9(fpk|twf^c`X8^wIS#r6uEds}r9E)o&rEcVbimsIlEe`#sL9Fzr;Ff&!uRe>%bYuxg-W8fSK2_f zPda%;?OcnYYx*ndwRc@}>TWbAF@IyN)%Ddac1`>TQ%;+AyTiYcI;C3nI}-gBZs46< z0I-o{IlxZYh_(aq&Iy}Y8kspg4ryX~jS^}b7{tUz4zvMo$XS+Q^c*Ws2QnA(Y;aUm z@ss*rr%SHy%~3Jb)Yf|B<=*?vFzE8+8{g2d*!Bb4CIY!UTr)6Xrw4vSHsTr7C-~9k zSpUe1k*b*+fE;34BVg9K22`gAj=;b`FZi;u@7zfX;l~m1X&Nnm&4^6OdK{dbIS=<} zfV4R+&u!2EtYPATLx#s~Vhxh1jp~+#|Q&XW=Kd(C}c5lp(gkp;L8a<*d{! zl4lk&r9m!5bP+&a1xfvua;o`5VHJsh%5Q4gQbL3XMVJGC;Rh-e4F+2Ex{?n@^7N(Jo%RlG*oo2$hkOn3~KCO!U$O7ssNdOn}4#vn(OU zX))vW4`-JBNLn-nVL?IHK%!wPS3G+*TTdtcd@E*;3ASwqgi55Hf9cE)2|g0XW#%5ICcC5*nLC|T7y zpVb08+oTd8$rw_sWJa0H>ynCT#(fKOb9z6wEim>x0@f2UGTjL6`z*#Q@r+SXQCl?< z^}oCYbIWjYM6bN6Y5|3cj^TpghrfbiMaiZwUcP*bB~J4w?!2hPvOj&ADq>}`I-gzo zfbs^L@ovbe+O0)I>^oDRGoux^7#Zs4h{p{-mQ6%t1L&Pfx*(DuRenpr9yO1Ga2XEA z60C__$IQyF?{_E-1hyAx7$D{91qsogo^ad2CP=e>fgKPvG(1l4JtKJ4-5Q2nf`)D z?Mf1B1g{P6kXHP85b3;27GOT1wU-gUFpj}uza^f>4j(4UKHM<;Lhz(nzC7_kAAG)W zjF*m>wXkVsFyC;$mhmfBO(_E56P;Q%%DB_Y${rvj)c7Ut`^|Xhk#K#@0qG)F5pvb5 zw@ST%5O4s-a!dz;^L_bMQ=fIs1u{O28pKUa_rg~Tl#6AE4~}lQTHxQr=@AegxGQ`d zeSLjim@9pF%)gwB6_805=3`A0-h9*@NKLMU#<x*b-c~{F%g3% zRKXwxG2jqWB?^GI=)PQJj6&QiRUD?xP#>p$zec7)#N#{SeEbqT^!%E59Zbj6{&nL^ z)uzS<2VVCZ>KYeIVB(op;+==7hjKOep`E!3vs?Ql$x+n_RohR9pCm z9J!xCysg1kR>?R%K{xlUI(T*M9z1ErXdjWZtmyL!bGR|qhFpHxs0 z_KXYi3~>ghV1)%4)8NM77djv;AyD26n^Ge{Z;H|~t^=Y2=p`}OmJ zaZ8RZ)S0slnax2_9&Y<}MX9Z_l-+B*JzQ-}2DBBpW@O|&KkX2FkT%Oc*jr(g?^CWF z+h6vW!T+w~%GB2bouT{#cEQC@xJ5AH>*?tsI9XOw?QT!dkwF&A0-efeX3FYWNC>dH zO(<_5DT_Z{3;rZwNhp6QvAY7wF_^{!@MhyK@ei{%1=aAT|9%U0hyX=IL{KQTsgHEO z{^z`B(kcqO907RYgAu?JLN7-nw7OeM_GIvONB=ma?Mdg(Vb`*0Eqcel!(~gq{-wFu zh;|OKmF%Gdf%=x4+nq;`I0SrgUY?+Q#*Ag`d)Y}Nq}E`KG2_z2uA^IwWz3TL9dfo$ zCQ{mK80s@GR_9MHS3DJafM)RO4$J8w#TWL~?t>QdV>=e{M-+}*4J5R8OTV*KA`%1+ zU+Sg~HOGF|b&`ulm+lg+OmNo#2E(nA=7M5J4cPIEu&_h=`rH7<@A&!YmXG|NC7n`e zqWO~5zXt%lT(n9qimTh+-V5T-7v*+;Y5PrAC?!Sb!>Mo`%lgNxqjUxC*dtKwo3BP*^v3=IuEG==0sbqNsTPA;yN1OOYzJxzMYGSkk4eM^u8 zclWEUpjoSZ|Eit#{i_or?W;d1jkX!pR*%SyJc)I}oan61<4+?`{M(8ul^Y^7yhqko zUg|W_^v{1&X%rD24vKVTU7c=R56Z$b(y5U^VGcp#eS>p8mf$(HEuJV)| z0jre#_x(f>2V0BxNNQB83J!mt?*&xiF`|-jAguGRLs{gQgw2sz1n45bn;!aqgw(7a zGw$eF1+5bk9QL|7CeG0m3d4pkyG>0EcL}tHWu2r@sFoV;ta;+bM3^gIy_tT8jMCx^@gTl|`{(QMrNBOASQY z=vp#`U-n(&T&QP0u~X?aga5nmlCnOjYi;nJ?^$ z)NcNp(`$e9gK(PMJz3|z?{^$(BOV?eOnk+|E5t(1tQQsqo_7s_@JEjGE?K3psm5ki z{dkvN-FOq*`I;J|uF1fdvDb=x6~m{hZZ<8TZj?JocQDsWT7IUgR=J>PRfbXbt-tf4 z*^_YXtJVL}Dvd;L@hwaqlHm5;w0rkmw|yWnpnqWyGTr_%JUlXE{w00f=^r`y=Rn15 z+)f^)B_bOnqM#O#eXWQ$Nl8lF$MAMS|4C3Qn1u!pqB6(w$%nw6ndJk z(|i(0dt|jb+d^|w=a8ahk==D^GrR9RB~4!I?pqFK@@)LxtaCf%z$FtETVSr?^;T^x z81DkMqS5y@=)EDIVJnDX13iz5JBR{~inm63U;kqS^i>^fY%;LF+7{@r0Wa_Q@9MP@>$<$#8KhI1eghUve{I7IJ^EDG!DAM#d@{7$ z0K7K=At7SqZ*HSCV>Be-$@cGD`i~at_qnt*-*hdStdMWiH&KmNET!{HPi&4cmflZq?J zN&ju)r1-lAhMR4y^V6^MpHl%m+&-=aJ!7M~i?%*%Q5QSUJtM4m zt@T8Ug-Z0*!WlQiI(tu@+CzONKZ&5Qr*}Se>eN-#znd`h#(cgyUdJ~J8)f&AmXBDP zLb3A{vnv<@1L-*eQnQ89Z<9%6v1@byn$&x~zMIf}VUhFEOu2XKsIUHuqIc$_0Jfa| zy<=OLI|l95leoAw_zNTlW8nkHeO#?IS=reO008CV>?YZP#+*mo%8@AyBW^SAkGhB< zvqIX-Et@q(S|`fY{-{i>7N}i1>$3Prec$D$jrQs1h6BSbuQEwqZZ$UWpmUnXBCR_Mz%#hzM`qgTjmhBMtl<3N2n9M|pK8!*lIO zsvYff_?nP8opH^zlxK-&Doc{qip+C5N;9d}*=bTO<*XmcxF+^Au=dtse>c$K)Zixp zK24ItY(^F@xOvz$%q;LxOL4aHBWxu4^Oy|YKnRCGgo)vc$jEr1Z5h^k8UK|&9S3cV zDB#-jRkt!Z+f9y;Q~v-X5*6!?EocFj+!_Fo%Uf^`wD1q-R52_Ez6$mz7#U;pz{kXF ze3wwvPQjYHdsrpy=3hO@o^BL`BhI|@O`M}||B>-g*@`|sb(&Fyz3SUv+Fzk(j<&DX zT@kpuG->KvlVfB-X!KK7o2^iGf~G+>2xD{58Q(6D@{b@vftch-g#8Fv7$loVYoY}J z!>(>@_NQ*#8dBvf>q(J~WpFf#{6l{Gy#G64q`5RKkP;0mW7~LRA z?L84F;sG+j!vlF|JSXO+YRTJ!1b@XeWrwLLDw9+6`l6OJUqo5CEy@*dyXG&?PtaLw z`9eqB%5IaaU#gyry`Ja%bE5l#J3}u=6rJ8!pz*YEOs98s%+^$fQN~Ax(LsxKUadu1 zRBzi#=e~aT3JZHnEi+M@p9&TqZsl8;a$cx-d5&mQz_8L8r1xI&OS%)4E6?q^tvaL7oQ(j%s=Sgfcm~>BfzHekEg^bD(TIvzI>F-g@<>k&DgglE;}HKD)W~ z{6&VsbBu-OM#F&E0saZO>gq6C&7Z2; z$uEgCEDU9no*W22rINT_>ymPw4-j;_HMBOnc?ZtTU)j=EaYad}{+rAW5jC0RnIHWp zmE-rrJ(qK1^)V+>jyl-d)XI-0*jmr&oFw=^I_xyFNDE5YKl^dGRkVNloC8DK%tvaQ zIa-{lW5!g1j&;^HHs!gQVeBWl`=W2~0dLxltql+~T-lNMGD$xF+|KFQPSy8UH0cJf zr&^926bU%~KC5qbv=1lb^^WC0^Z$Vfes2CPd$4Le(?9xz{FRdu)6~m196orE0zr@Z z?)+a#>_Yzw#yTN^(EB|W{LzB$(Fz5~O`vJ$e1F$JYe@~84c}VckTx`YpC{_q;hZ`* z%WOs8x`w3hKNJc~T<<67nKV>4E=rnAa zbvZp^Fz;hLZ<@pVk)6kWC8!&WRD<&r1jQGmbjJ{6G1e#EEy2w~b2)Fla>c9ifZG#T z1Y?B3Ke)Z#f}qy_+64ZVNBA3-yX;Dftk+fm+nBkXda;?{R{y`E+8@CXVOA+F> zW84I&WVNrIRd=R=%&P)cCJ%L_Deu7jNvvr(w^*lg^uk&B_Eb$7-K2reDfw%D~14i)C(8JAcw_Z zrWLF)Zv6V$t5?`IlNA12AhVIEQ>L~dazPasAy#&Yhd`lDA}E5zt7|WY`lkJ@1bzQkGY#&IiChZZqC8M;Ja|fZ+k`rux`UYh=q^b zVIM;aSNW`-+QieUv+XaNz=BR1`wyUM^26q@Ux`B679A$ZwqOgz7&RZI9Q@hX{$!La ztf*b9{E#{$2_*SW?|ADgO4fu(I5=CII`ra2RGh|<8!uz?$*+gq!)p^gIyPs9Ya8XQq`O7$ zxMa(_jqI!DJsg?NHhgsbX{U_Unq3Vg=hMIWrI)-8Ob@3y8EDeE=FY?U)Iwh!E>9ey zjP-VMO;yb%-+1iycK`P>IX>YYEoauti!M9}{<5j$wJ>TVX#C!O`SL|em3Gf95y1hE zTq6Dj)QDwG@0RHSxy8IaJ2cRK2{kn}_)`ZXWhHwc1kMBXVB?L0m{4Nmg>H>)Kficu zNg!DuM!5wq-|KeChWXll)(rUU*m288=*=1$z+YJ zSzpJ(#J-@QTD$8-8V zAWIXPB<8p{!6=0#^vul6ViFUN1&Y{V#}&K(kt^}w%0pHd48bMEw77twu&6p^a&j`e zriKwZK>)FFaSrfQuYZ)w%ANGUujTcS-wVp-*^t58T07*sw!K9-Qr<3|n+tXw&`52;}0O#WF2${>^uM*z`N@IwxiWsB`4NuHHmNo#f%xrY&MXKGs7# z!pX&jooR2r-g5uCes2A@(*SXpJ$!hH#4oPZh8_`Y4bwsG=pGTf@q}%f#c*>vh|En8CFELlJd#u473g}ia7sfm2*VgHW%-qT zfK|wY_D_B2##+X=}rAE(0Wp1?)(cJ>_cLv=Cvh@DpD%HX( zHr2^5WjFvgvtfN4;A6{{`*L97?X-#NY5Ou^Fj@8XBZG1MrkkF9+VbRmeuv5sN?!r1 z!Dn%O7*7-DKQM}KgZ+r&ggAyk=OYGv69$6OtD6A;k&z(iH)bP=0scF-Z$Ahlu;-v> zM`uhNzanZixEYuKF2f`(@h%E!69NvKVavo5(!Q9*G$tvoJnX4MnK7(BT$9RG35acgSn01iN zbmW@@Q|n?jh)`}wVNi6osJEg9YZR;g-l8mkYsZ%G9<{b56tLRJ`VuJG+%S{D#H8R^ za>A9_4fpro^c!^vYPMlMgmY4xVV4TRscRey6Vp!E_@Mx0=ioRFA%lb-SQhNqy;lPV zxi$Rybst^ZIy%Be+Yl#LVlyWAHcl(V@O_VWH)Ljti`<-Wa&~qDrz9*IQ$YM)2M_S%10M&lP}r5HhhC4YQ!} zVIkVH0KzbGnr~^?5fc)!$j2vT9UZ2t7d@Jrn&yd$irUUjkAg?S2!t%gxaGL_#C>01 z2>Hc3LC!q|9p}Tnsv6@34_`yaOCcNhSQ2VgUVSJo&O?lH0sjfMWD6)*Tq9K=cft>} zwq;$VKC$Lcn#p4!9G5`b!NoHpiJ4gtruSveih#}Sks#cyG>@#WEiD191wdg zM)6{soR0&-OKA~0_5eyF-3k`_HO!-sv1)MermNHrre~pn?Cq`Z7yzU3RsJK?1Y~s^ zY7huL-(ZsR%pP-9c%hZriDBF!q$;bcq2ZX4B2@kP^E?Xl%u>a2#eOXqc!(=0F^f!p zKPw<0Kv>FG;?EBVKqOJF;ERXr-|P{;fTarW93gUmBzb+se6rTp%j?+Dqc=Y#IQxh5 z8n@Q??C5KdJ)UvQdOkGrhc7+{eX_YLIx~+fqQbvRPPRd{k8r#Z(+99?(5fPt@3CP9 z+XRLm8mNV!{NYP(AR8l*y`Y)Ix={8VI~L@nAv&5n8)W@2V;-uvA?t$6#i&8Hr- zrj|e0pKgc@2{{7D#}(bFe`7@XoMDW5JF+q#$Y^BL4#`=+?x9b(nJSCl^pe7o&Vz$;ilI-dm?VXm{dy@#l{t zz~6GniXu>vcfj-^Cqb`Ps3`xGe%{x*Ob1o+KH+J)Kv+mN^*%0-GSI~b!W?e^yF1DE zm~Ezh3s=JMh(RHlb8(uGPX#H8w2eWwOaMmHhW2Q`b>S3t2SWEJ+CLWkt7jEgE~u0+8{Q8k)H>s)qWy&HQgCED+{ZcYe)83a4Bja3;J6E(IxCByuU>G z25LIaWv6m2X64P`by2QD4}~36%&4s>8`hk|8{50fCxdt1JA3IO54D0c3DLQn+=+Da zXZEPEz$3(k2B~J+lW+Z-v@v3dckeAYD`eL)LVj>uoXizBaPQ#u;EJ2n48)KD;~*d< z;U-gXpMXAn34f-h6m3AKwzT8}jj2?pMQ-0a^iMqC396+lDK& z_Tuk~k<`+?;$%aHEUI|1;7{)g!k<4sD)ngZAKUb;tdirD6%}_vEAoL+vN#Qsa=;yU zM>4Y$kV!w=)#{{{kjG?K9k0vtGiMR46v|BnWctNEh z8%+?V$gVT%TCjWu$R0IF`A`#4DClDSMHW!e-L_A7{aW14E?q(6{X^fsCXW+)7d8>e<)6mddjf~`?5Z{sWk+Pj~^71c+7Jc~9?|3KdIAXeh$Vyttg25xB= zu(4UhZUPFp!$NMCxy3csKduT^@3dY&xJ#!4>Nw27{q26&ftd-}LjjE!o$ULi90XjC zf3I$u*(t8^sKAfmd9j<2I?D9#D+tpE^9#hBKPG{xU~;w#CX_^{jQzd`|7 zKMmmE;#!LP1oo-2|B{tvo6_Fy{I?Tqn&KA zC8{DgpQHzCEGfPobt354*A!uE68Arte$81-NWQ4lA->K=p}-a2v0wb-jBX*wnuOy1 zgID`BHuki+4Qvlk-H<*2DFCC%;Gm#GZDSZ$z=(nY_un^9HcZApAiX`}B>-FpJ$oHxheb+W zUY`DA6U`w|9mIi4A@=ka?2LwPtLoX9URCU#36*d(?P!ZmXmUMz^au(IOBq?&NBiWG zySBIOyOUi$s$fH?Pq5*dM?pGZ5U~{iJB)H*`yh;RICXmcBaJLb_IxMRm)6b6T>2*$ zU}zZ*8!`DnyNqF4;j?Dqtv)?D+ML>#k&SN`3+U%_4{58wJtT%lc9_GUsT;#~N>fi! zupwb<;D+P~E|N6WI`^%Z~K8X#HfWLJ4-c=R)S;^zo8BchmQrBX{& zuwT{TWoH{Sd(b72&Wdssec@9S)dCR0Q8pshq78V}rr$jpQo9nn3VkrNAyjn;`$~g~ z%F4F<0}*1XXx)SxL8D?VFPA8U$SskiSquRO^B&F9Idc34YXpl z)SfNd&gT1Q{I$*4k>O|7-5o#J-+vX4g*eblJ?2{qQ6W~@ht$Ik<4&rlvC3+}4I_52 z3Aq8HJBl2_s)ga?G0gN3eGQx5F6xMQOTGNg= zmi^lMc-O$>=idqJS=pYwt-JUVV(ad^8@L2j6*M%kqK6~y0uc4GT5(@Bj&>F7dp&|e ziQv9BZrq?y&@hmH@0i(nu)fY?I>U6ovX$LkmlxY=Q5RYECb!)HReR199vDaY^G{ki zNc80QXqGNr3wC0+VM8H^BiwmjtUMqp<~YobI=I1Au24TZI(i)#ro=dZ&O9GY4M@3N z%nkLxDsE(EW@g#we;?NV553XkZ@L<~`eqH?R4xsTL}6Lg-n?F?JzMNG-P2{oXvhLy zvP0riU0uXGB@eyFgO3;<1(VN;B7Q4uHFKc=0J!k&+cz4#^hVPYX5Ov(x*n-aa~K`F zmrHxqQ80>Z>F>mvcPc=X0Hy~VMjI-^K5D$RAx+7KAP8v4o^Bv@ot-l6yd=9H{_cWu zUjF=ftiBDZZMY|0@O6tAZZG@)2C5T&_XDbwF#a@xh?cY zJ5KYH?DtMe`Sdp;#oD;BG1g1+@N+f%_>0dIG(&I#alLRDp@JO-(LzG?UC|MGCd||m zVvv90bK6=$$ymwE><*m^uu!l(k4&tm{G-_cP5Q3-UVi@OXou;Yhausn3W+Ncd}%HuFrz znGV(H1inFn^=3T)4X|$rPK=HnaD8Vx#f^vL1i93~iO<-Uj+UEh60#``kQ&Gi1IW2A zXouOTWx*!kf_rzjE}^Hxb>$F*RFpR$8D8Px;v)Npi^$C&RZu%`42s*BlA@xe=y+g( z#@~^Y@VltvQ-d189p#`qvtVfiuI0c~Cdc8!Uf00nHCUzW=>-|DPNC|9`*Qz7pUCcpb|AHx+8y^oy0gF>C1hMJZ*Yj38hc zs`K{P=uKuUT~Q~lA|UP_p-z1Aq>=a1U{`5Mua}0MD0~?1-MdHT3vWOy+AwaZ++h^e z6}@NWdZ z26S#Fp7iglFo$u3d<&HL1!6Rgyg0+KrP!sOLqkeTLMnj3kVqV0NwEa{IPVEiY}diV zjkvbyH)QDbkBtaE_7>M1k_)dO1D&&Hy}ONS61q}NtP53)Rqyf`t;!SuQKR_E*!4{OihN#&I0wy6Pl>>NrX&&It z9R3DdM~Jh&`didz;WvpCB`GQC0vu41Fm`~>gF+FV8QX+6g7;Fv8hnZ2K>ZqH4WhM2 zuZn(%id-*&xAvY0Ptbgg$tEail>5h>uqpv_J_(%!0A7?`@)z_PGFVt``{yC=kt+c- zya@;)*d6VroOT36$1wi_phVKc*4t0&={962hkBv3h!}sbuua^~y(lb_G?F`ahG*B& z72TJbd;M41fFa)qcPZk|Ng?}C!Xk&g2JUW=1Xbwj_)yydO2*Wnq$6*G-Ka7FK~c|G z!VPoYf$h&I#0iOV)nTfcm2wpXr$d^VvCS=anKYW-IvGND4KE6_!86CZ9)&!A{uKJu z2Kz}aO7$tY3W6;O;woUQ4XeDE8EZu&eODJXb$8QIfMEztLIbn}UKrYw<86Wb44RPsP_B}Z*0x0cdduu*wuI;l z3yEyF9)OKNp)fHqg?)j46{R2Y+h)91#QsXiV~eIg-LTaIL9>LYiz2jY-N8se7f)$= zX@5+_)CpIlfoD{jt^kD)kS==U{$WoG5K&Q-vcYczA5}`r9ndmnW7TQHij{$BGz)qK{{RyxP0>;oYGz5A;O3^=a$jJAW`*C7q}QQ3sPSMKOdl1m0a}rsdjJ zJG4e27A@biO}y)PD+xr0Pu>6u(ga(jTm$)$NiVt;%up@0T1?^wwMDj+U0V-1}}&iYdEtgb3P z^sgr4Xs(sK(r zXwS!OK&OxgZ(wpud;`|0+Rm-2OSiFb&cGiaOAO%teGMZ&V(ND}*A<3v_&vbdZkVx6 z4R|^1-O&aos3r)9NOwfUwq#QtK*v5)7OaW!BtkK~LIPL_1{OBqLckB{!9B&D|IR2w zXplrH8#y{fbFbDJDbk+Oz}^`rB(oEleFnvuuZ+GQU7H|1eB_eUW?3p>$CJC=O!M#V zTeX6z@!HF3i4OxCZg`cfQ|o!ywRL!W^I5kovl-XhoQK5+`m$H-D_u&!!)*=%C5Dp}d02!nv#te^H5E|B^k+u zWrABN>D0Dj(V2neufL|jM4hA)NuKnD6j(02{6nw-bptjU0568BoalLw|AdsC1#i*$wZt!_ zF|o)?{4_5`<2`5V%r1KdKi!Cd`Y|;GDW3Ze$~DWJn3fxF;Fk$4$x|Ik91i{1)UK0F zE9=EHS1*BEJC8Ck+7~@!pm^lS@?t|`QH26!-n`{A8!^*J7jJ<5ZF|lRF0Oq$i!nZ% zcQ7Tby0ldNOz}U`#wrvG8u^RHj(`zg|6t1&7B?#KNPv-1r+`A^2g<& z{rX=V6U^;yea~4xCCO@?Hiuk<#mmFz&8IkXVuElB0C1Pi?q!sA&cFyk$cSvNlJ#>g z8VAuJAEq(=paZW)v_5wFG{C}DLLQ$VX-+->YO%GYvi?UBk`Y1@@-MVA&cjlY!!XQ~ zc^6Orpml6o-a;ntaN~!*>$g|!W?p2`xnijK{;c~YwQHBzJ`NVjP0`QVW|ql^jBbd# zU%^MA)UoJFca}|~a-XcAPsyzy#(|RBZm;1<;AqAQN$uMaU5VUo5?}^-zQ&x^Fs~kDn@n=4pKKk!`HqUfwjUosZAtzUk zbXF986SyhDyKP(Bf66p+eM0{+N$u$Gg z0jm4GSnk9IO8=L5MR}W6p%Y&sjvDNgT$m(uL3vE#1+~z8%87e*hdy}88Fo~bXz6~M zF01(*O5?9*qvkof(RPi_V(|1 zJHr3UAkA>W;(Xdur>mYM`rpeUWs=+b=>@Yorl?uc;NCP=WMJ>;B|4K7US_3byuss5 z{3Dxy5sb%eE(MLmV}51HCk0UiT9y5hFqHuvkV9V z{l8rBA2Uk(Y=QU4WKG~oR&Im$cb*m?_o4JF<^y(gua4P*1AJN?6J| zEz)pq7j8xd+AZi+{I&h@S+^BqoLy8Rg{4npo^Lq0zgj&uaKHM3146bZRl<>y7``-Y zV0vz;cK5M&M$6XD)8v!AlQNmP?-9)ZGDhzu4~GVO?C$pUws#etmF0_Fj;N+Am&rY< zP9BkM28~XMlCN;_wuk4ZR2ya%Zd25`INMOduG9PaUB`0Pn-Bm?8 z3lX+?ZORW)u0DTR`E9@@WfK|7T%J%3%2w0Sb8WXbslR_+&@TCP5 zhU!p$O5;)K^lqcL%y#whF!M#dOBnB)w~O}A**y}I;aB7fuvi&U$N)_|KET9UvxLll%tVK5n9Cllw0#l#o`rr zj9?}VmJ^WJ`4j?&U^xI5#O$%wt-YRKP_P6T0wh#7ot>!zus`OQ&rov$yva$V_tHzl z5CQI4i%?a?#KvC3&K+!7R?~}|46J}9I58a_Lldcn;Wsfyh6kaL9mZC~e;%E2x=DM4 zmYv_w7WOj01f8v*C4h*_i|IU>>`_k1%HD)^1+kriqv1Zr;Nj&)7?^f;b)5$CP*d#6*im#*x7sg%tzWND$G5VPib4GKB(~OQh(` zf@8vxf{6ZLG?p4xUjF^s}!6YQsy(@;*&@sMu7h?TK0H#<7Y*zW1h z(`UX;;&EeRA7*I0;-7ckfOcSn8!9 z$d@6N_Wn>gj+j0piG5Jh%%h4nAqgb{93&}Io&7$@uJib z1;?hcx2<}Dgv;X)GyeMLn(ZehOqnukG!1|OWLCz(hNu(RCD8XB1Wut`fG?If0a2rb43knZf@TBMqI_`9HZ_D&`uGE>X{2EjZBTjmXbY4yn)1W ze>f7H+bu?;Gzb{c-dWtJBvE0Kn@Yc-6PyjO&}B?ASn|O-9dIMXLlO)VLXKLI(LcnE zYKfLAu~?-2j_-D%J%{A1VKAO~>PWD==)wqyF-`FR3e_ z4qDnVSaUfs4t8ZTo6!TP0*Oj>=`+eA3x87Q*^kjQEaR;ag$QuHzdTL8 zU?jlY(0ZtstfwQ%8&_IeB7pZfP#-?@>Ki~?~N#*Jv5MWz$AM<+E z*-Q^mAe~XIIXxLdW8Uz?JAM>B;PDMqw?+bYEM23cquj{dJ2>Hd}ZuAnV(1#{3*x@PR-}j=mgNcC@k3OXy9^`)K@ifI92^{XueBKMfwzrAzr_Hxb>e_tg=Y)N z@1cnhl0dw7;v4om3( z=}7d03!e=w40&R;TFYZ!adNp)c#(n9x}&Ns#S%G3vFEHBXxii#gR4Q{$j97d!FS1F zB9+K~xko>(KP*r?;9CJV`YsJQSO%VFf-HHxEB-cI*~bVS{6F0f|KUboJ@Xqs&pZCJ SCu(1X*OJA{w6zh2U4H>>>~!M* literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/adminguide.md b/docs/sources/docker-hub-enterprise/adminguide.md new file mode 100644 index 000000000..d47104167 --- /dev/null +++ b/docs/sources/docker-hub-enterprise/adminguide.md @@ -0,0 +1,103 @@ +page_title: Docker Hub Enterprise: Admin guide +page_description: Documentation describing administration of Docker Hub Enterprise +page_keywords: docker, documentation, about, technology, hub, enterprise + +# Docker Hub Enterprise Administrator's Guide + +This guide covers tasks and functions an administrator of Docker Hub Enterprise +(DHE) will need to know about, such as reporting, logging, system management, +performance metrics, etc. +For tasks DHE users need to accomplish, such as using DHE to push and pull +images, please visit the [User's Guide](./userguide). + +## Reporting + +### System Health + +![System Health page](../assets/admin-metrics.png) + +The "System Health" tab displays resource utilization metrics for the DHE host +as well as for each of its contained services. The CPU and RAM usage meters at +the top indicate overall resource usage for the host, while detailed time-series +charts are provided below for each service. You can mouse-over the charts or +meters to see detailed data points. + +Clicking on a service name (i.e., "load_balancer", "admin_server", etc.) will +display the network, CPU, and memory (RAM) utilization data for the specified +service. See below for a +[detailed explanation of the available services](#services). + +### Logs + +![System Logs page](../assets/admin-logs.png) + +Click the "Logs" tab to view all logs related to your DHE instance. You will see +log sections on this page for each service in your DHE instance. Older or newer +logs can be loaded by scrolling up or down. See below for a +[detailed explanation of the available services](#services). + +DHE's log files can be found on the host in `/usr/local/etc/dhe/logs/`. The +files are limited to a maximum size of 64mb. They are rotated every two weeks, +when the aggregator sends logs to the collection server, or they are rotated if +a logfile would exceed 64mb without rotation. Log files are named `-`, where the "component name" is the service it +provides (`manager`, `admin-server`, etc.). + +### Usage statistics and crash reports + +During normal use, DHE generates usage statistics and crash reports. This +information is collected by Docker, Inc. to help us prioritize features, fix +bugs, and improve our products. Specifically, Docker, Inc. collects the +following information: + +* Error logs +* Crash logs + +## Emergency access to the DHE admin web interface + +If your authenticated or public access to the DHE web interface has stopped +working, but your DHE admin container is still running, you can add an +[ambassador container](https://docs.docker.com/articles/ambassador_pattern_linking/) +to get temporary unsecure access to it by running: + + $ docker run --rm -it --link docker_hub_enterprise_admin_server:admin -p 9999:80 svendowideit/ambassador + +> **Note:** This guide assumes you can run Docker commands from a machine where +> you are a member of the `docker` group, or have root privileges. Otherwise, +> you may need to add `sudo` to the example command above. + +This will give you access on port `9999` on your DHE server - `http://:9999/admin/`. + +## Services + +DHE runs several Docker services which are essential to its reliability and +usability. The following services are included; you can see their details by +running queries on the [System Health](#system-health) and [Logs](#logs) pages: + +* `admin_server`: Used for displaying system health, performing upgrades, +configuring settings, and viewing logs. +* `load_balancer`: Used for maintaining high availability by distributing load +to each image storage service (`image_storage_X`). +* `log_aggregator`: A microservice used for aggregating logs from each of the +other services. Handles log persistence and rotation on disk. +* `image_storage_X`: Stores Docker images using the [Docker Registry HTTP API V2](https://github.com/docker/distribution/blob/master/doc/SPEC.md). Typically, +multiple image storage services are used in order to provide greater uptime and +faster, more efficient resource utilization. + +## DHE system management + +The `dockerhubenterprise/manager` image is used to control the DHE system. This +image uses the Docker socket to orchestrate the multiple services that comprise +DHE. + + $ sudo bash -c "$(sudo docker run dockerhubenterprise/manager [COMMAND])" + +Supported commands are: `install`, `start`, `stop`, `restart`, `status`, and +`upgrade`. + +> **Note**: `sudo` is needed for `dockerhubenterprise/manager` commands to +> ensure that the Bash script is run with full access to the Docker host. + +## Next Steps + +For information on installing DHE, take a look at the [Installation instructions](./install.md). diff --git a/docs/sources/docker-hub-enterprise/assets/admin-logs.png b/docs/sources/docker-hub-enterprise/assets/admin-logs.png new file mode 100644 index 0000000000000000000000000000000000000000..76f0d19a80ce96bf2e9995a6076a0c559d7235bf GIT binary patch literal 161230 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 G#Qy*sNpWNV literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/admin-metrics.png b/docs/sources/docker-hub-enterprise/assets/admin-metrics.png new file mode 100644 index 0000000000000000000000000000000000000000..ccec72a31a19954e2271d801599c558a8c302974 GIT binary patch literal 74062 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 literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/admin-settings-authentication-basic.png b/docs/sources/docker-hub-enterprise/assets/admin-settings-authentication-basic.png new file mode 100644 index 0000000000000000000000000000000000000000..ef9dfe3513154d6a2490759c6d664337a6ab9fa1 GIT binary patch literal 23600 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 z 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 U_CsBk)|u7*y$8d&p=qD}15D}J)c^nh literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/admin-settings-http.png b/docs/sources/docker-hub-enterprise/assets/admin-settings-http.png new file mode 100644 index 0000000000000000000000000000000000000000..d860c5088d003af2034cc24e23a885afaf1d2c12 GIT binary patch literal 20618 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 literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/admin-settings-security.png b/docs/sources/docker-hub-enterprise/assets/admin-settings-security.png new file mode 100644 index 0000000000000000000000000000000000000000..81d375040e0cdb381340c38ae89105a1ba1abf2f GIT binary patch literal 21807 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%0A$|SnVdBCcZ^>KYpZ`H*(3`n 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 OV!jjfwrES#C;tK@B42(0 literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/console-pull.png b/docs/sources/docker-hub-enterprise/assets/console-pull.png new file mode 100755 index 0000000000000000000000000000000000000000..57f264f4ead39d4ce2ee921cccaeead0ab1d82da GIT binary patch literal 35398 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 literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/docker-hub-org-enterprise-license-CSDE-dropdown.png b/docs/sources/docker-hub-enterprise/assets/docker-hub-org-enterprise-license-CSDE-dropdown.png new file mode 100644 index 0000000000000000000000000000000000000000..49dbfab8745d2b7d7d80aa5f8de8efcf9169e5cf GIT binary patch literal 28680 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 z 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 Lijqa*24DUQNu1i5 literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/docker-hub-org-enterprise-license.png b/docs/sources/docker-hub-enterprise/assets/docker-hub-org-enterprise-license.png new file mode 100644 index 0000000000000000000000000000000000000000..3c70b747c6bc32ccbfea457f7686d8d0d5764671 GIT binary patch literal 27642 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 P2c)W`dB5bIMbQ5O!JPrS literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/assets/jenkins-ui.png b/docs/sources/docker-hub-enterprise/assets/jenkins-ui.png new file mode 100755 index 0000000000000000000000000000000000000000..6c8bd5f722bef0ad9347a2849d1a63a10f8953d2 GIT binary patch literal 42805 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 Om$Jf(r-&!uH~$0qGi2=m literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub-enterprise/configuration.md b/docs/sources/docker-hub-enterprise/configuration.md new file mode 100644 index 000000000..6050da401 --- /dev/null +++ b/docs/sources/docker-hub-enterprise/configuration.md @@ -0,0 +1,311 @@ +page_title: Docker Hub Enterprise: Configuration options +page_description: Configuration instructions for Docker Hub Enterprise +page_keywords: docker, documentation, about, technology, understanding, enterprise, hub, registry + +# Configuration options + +This page will help you properly configure Docker Hub Enterprise (DHE) so it can +run in your environment. + +Start with DHE loaded in your browser and click the "Settings" tab to view +configuration options. You'll see options for configuring: + +* Domains and ports +* Security settings +* Storage settings +* Authentication settings +* Your DHE license + +## Domains and Ports + +![Domain and Ports page](../assets/admin-settings-http.png) + +* *Domain Name*: **required**; defaults to an empty string, the fully qualified domain name assigned to the DHE host. +* *Load Balancer HTTP Port*: defaults to 80, used as the entry point for the image storage service. To see load balancer status, you can query +http://<dhe-host>/load_balancer_status. +* *Load Balancer HTTPS Port*: defaults to 443, used as the secure entry point +for the image storage service. +* *HTTP_PROXY*: defaults to an empty string, proxy server for HTTP requests. +* *HTTPS_PROXY*: defaults to an empty string, proxy server for HTTPS requests. +* *NO_PROXY*: defaults to an empty string, proxy bypass for HTTP and HTTPS requests. + + +> **Note**: If you need DHE to re-generate a self-signed certificate at some +> point, you'll need to first delete `/usr/local/etc/dhe/ssl/server.pem`, and +> then restart the DHE containers, either by changing and saving the "Domain Name", +> or using `bash -c "$(docker run dockerhubenterprise/manager restart)"`. + + +## Security + +![Security settings page](../assets/admin-settings-security.png) + +* *SSL Certificate*: Used to enter the hash (string) from the SSL Certificate. +This cert must be accompanied by its private key, entered below. +* *Private Key*: The hash from the private key associated with the provided +SSL Certificate (as a standard x509 key pair). + +In order to run, DHE requires encrypted communications via HTTPS/SSL between (a) the DHE registry and your Docker Engine(s), and (b) between your web browser and the DHE admin server. There are a few options for setting this up: + +1. You can use the self-signed certificate DHE generates by default. +2. You can generate your own certificates using a public service or your enterprise's infrastructure. See the [Generating SSL certificates](#generating-ssl-certificates) section for the options available. + +If you are generating your own certificates, you can install them by following the instructions for +[Adding your own registry certificates to DHE](#adding-your-own-registry-certificates-to-dhe). + +On the other hand, if you choose to use the DHE-generated certificates, or the +certificates you generate yourself are not trusted by your client Docker hosts, +you will need to do one of the following: + +* [Install a registry certificate on all of your client Docker daemons](#installing-registry-certificates-on-client-docker-daemons), + +* Set your [client Docker daemons to run with an unconfirmed connection to the registry](#if-you-cant-install-the-certificates). + +### Generating SSL certificates + +There are three basic approaches to generating certificates: + +1. Most enterprises will have private key infrastructure (PKI) in place to +generate keys. Consult with your security team or whomever manages your private +key infrastructure. If you have this resource available, Docker recommends you +use it. + +2. If your enterprise can't provide keys, you can use a public Certificate +Authority (CA) like "InstantSSL.com" or "RapidSSL.com" to generate a +certificate. If your certificates are generated using a globally trusted +Certificate Authority, you won't need to install them on all of your +client Docker daemons. + +3. Use the self-signed registry certificate generated by DHE, and install it +onto the client Docker daemon hosts as shown below. + +### Adding your own Registry certificates to DHE + +Whichever method you use to generate certificates, once you have them +you can set up your DHE server to use them by navigating to the "Settings" page, +going to "Security," and putting the SSL Certificate text (including all +intermediate Certificates, starting with the host) into the +"SSL Certificate" edit box, and the previously generated Private key into +the "SSL Private Key" edit box. + +Click the "Save" button, and then wait for the DHE Admin site to restart and +reload. It should now be using the new certificate. + +Once the "Security" page has reloaded, it will show `#` hashes instead of the +certificate text you pasted in. + +If your certificate is signed by a chain of Certificate Authorities that are +already trusted by your Docker daemon servers, you can skip the "Installing +registry certificates" step below. + +### Installing Registry certificates on client Docker daemons + +If your certificates do not have a trusted Certificate Authority, you will need +to install them on each client Docker daemon host. + +The procedure for installing the DHE certificates on each Linux distribution has +slightly different steps, as shown below. + +You can test this certificate using `curl`: + +``` +$ curl https://dhe.yourdomain.com/v2/ +curl: (60) SSL certificate problem: self signed certificate +More details here: http://curl.haxx.se/docs/sslcerts.html + +curl performs SSL certificate verification by default, using a "bundle" + of Certificate Authority (CA) public keys (CA certs). If the default + bundle file isn't adequate, you can specify an alternate file + using the --cacert option. +If this HTTPS server uses a certificate signed by a CA represented in + the bundle, the certificate verification probably failed due to a + problem with the certificate (it might be expired, or the name might + not match the domain name in the URL). +If you'd like to turn off curl's verification of the certificate, use + the -k (or --insecure) option. + +$ curl --cacert /usr/local/etc/dhe/ssl/server.pem https://dhe.yourdomain.com/v2/ +{"errors":[{"code":"UNAUTHORIZED","message":"access to the requested resource is not authorized","detail":null}]} +``` + +Continue by following the steps corresponding to your chosen OS. + +#### Ubuntu/Debian + +``` + $ export DOMAIN_NAME=dhe.yourdomain.com + $ openssl s_client -connect $DOMAIN_NAME:443 -showcerts /dev/null | openssl x509 -outform PEM | tee /usr/local/share/ca-certificates/$DOMAIN_NAME.crt + $ update-ca-certificates + Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done. + Running hooks in /etc/ca-certificates/update.d....done. + $ service docker restart + docker stop/waiting + docker start/running, process 29291 +``` + +#### RHEL + +``` + $ export DOMAIN_NAME=dhe.yourdomain.com + $ openssl s_client -connect $DOMAIN_NAME:443 -showcerts /dev/null | openssl x509 -outform PEM | tee /etc/pki/ca-trust/source/anchors/$DOMAIN_NAME.crt + $ update-ca-trust + $ /bin/systemctl restart docker.service +``` + +#### Boot2Docker 1.6.0 + +Install the CA cert (or the auto-generated cert) by adding the following to +your `/var/lib/boot2docker/bootsync.sh`: + +``` +#!/bin/sh + +cat /var/lib/boot2docker/server.pem >> /etc/ssl/certs/ca-certificates.crt +``` + + +Then get the certificate from the new DHE server using: + +``` +$ openssl s_client -connect dhe.yourdomain.com:443 -showcerts /dev/null | openssl x509 -outform PEM | sudo tee -a /var/lib/boot2docker/server.pem +``` + +If your certificate chain is complicated, you may want to use the changes in +[Pull request 807](https://github.com/boot2docker/boot2docker/pull/807/files) + +Now you can either reboot your Boot2Docker virtual machine, or run the following to +install the server certificate, and then restart the Docker daemon. + +``` +$ sudo chmod 755 /var/lib/boot2docker/bootsync.sh +$ sudo /var/lib/boot2docker/bootsync.sh +$ sudo /etc/init.d/docker restart`. +``` + +### If you can't install the certificates + +If for some reason you can't install the certificate chain on a client Docker host, +or your certificates do not have a global CA, you can configure your Docker daemon to run in "insecure" mode. This is done by adding an extra flag, +`--insecure-registry host-ip|domain-name`, to your client Docker daemon startup flags. +You'll need to restart the Docker daemon for the change to take effect. + +This flag means that the communications between your Docker client and the DHE +Registry server are still encrypted, but the client Docker daemon is not +confirming that the Registry connection is not being hijacked or diverted. + +> **Note**: If you enter a "Domain Name" into the "Security" settings, it needs +> to be DNS resolvable on any client Docker daemons that are running in +> "insecure-registry" mode. + +To set the flag, follow the directions below for your operating system. + +#### Ubuntu + +On Ubuntu 14.04 LTS, you customize the Docker daemon configuration with the +`/etc/defaults/docker` file. + +Open or create the `/etc/defaults/docker` file, and add the +`--insecure-registry` flag to the `DOCKER_OPTS` setting (which may need to be +added or uncommented) as follows: + +``` +DOCKER_OPTS="--insecure-registry dhe.yourdomain.com" +``` + +Then restart the Docker daemon with `sudo service docker restart`. + +#### RHEL + +On RHEL, you customize the Docker daemon configuration with the +`/etc/sysconfig/docker` file. + +Open or create the `/etc/sysconfig/docker` file, and add the +`--insecure-registry` flag to the `OPTIONS` setting (which may need to be +added or uncommented) as follows: + +``` +OPTIONS="--insecure-registry dhe.yourdomain.com" +``` + +Then restart the Docker daemon with `sudo service docker restart`. + +### Boot2Docker + +On Boot2Docker, you customize the Docker daemon configuration with the +`/var/lib/boot2docker/profile` file. + +Open or create the `/var/lib/boot2docker/profile` file, and add an `EXTRA_ARGS` +setting as follows: + +``` +EXTRA_ARGS="--insecure-registry dhe.yourdomain.com" +``` + +Then restart the Docker daemon with `sudo /etc/init.d/docker restart`. + +## Image Storage Configuration + +DHE offers multiple methods for image storage, which are defined using specific +storage drivers. Image storage can be local, remote, or on a cloud service such +as S3. Storage drivers can be added or customized via the DHE storage driver +API. + +![Storage settings page](../assets/admin-settings-storage.png) + +* *Yaml configuration file*: This file (`/usr/local/etc/dhe/storage.yml`) is +used to configure the image storage services. The editable text of the file is +displayed in the dialog box. The schema of this file is identical to that used +by the [Registry 2.0](http://docs.docker.com/registry/configuration/). +* If you are using the file system driver to provide local image storage, you will need to specify a root directory which will get mounted as a sub-path of +`/var/local/dhe/image-storage`. The default value of this root directory is +`/local`, so the full path to it is `/var/local/dhe/image-storage/local`. + +> **Note:** +> Saving changes you've made to settings will restart the Docker Hub Enterprise +> instance. The restart may cause a brief interruption for users of the image +> storage system. + +## Authentication + +The current authentication methods are `None`, `Basic` and `LDAP`. + +The `Basic` setting includes: + +![Basic authentication settings page](../assets/admin-settings-authentication-basic.png) + +* A button to add one user, or to upload a CSV file containing username, +password pairs +* A DHE website Administrator Filter, allowing you to either +* * 'Allow all authenticated users' to log into the DHE admin web interface, or +* * 'Whitelist usernames', which allows you to restrict access to the web +interface to the listed set of users. + +The `LDAP` setting includes: + +![LDAP authentication settings page](../assets/admin-settings-authentication-ldap.png) + +* *Use StartTLS*: defaults to unchecked, check to enable StartTLS +* *LDAP Server URL*: **required**; defaults to null, LDAP server URL (e.g., - ldap://example.com) +* *User Base DN*: **required**; defaults to null, user base DN in the form +(e.g., - dc=example,dc=com) +* *User Login Attribute*: **required**; defaults to null, user login attribute +(e.g., - uid or sAMAccountName) +* *Search User DN*:** required**; defaults to null, search user DN +(e.g., - domain\username) +* *Search User Password*: **required**; defaults to null, search user password +* A *DHE Registry User filter*, allowing you to either +* * 'Allow all authenticated users' to push or pull any images, or +* * 'Filter LDAP search results', which allows you to restrict DHE registry pull +and push to users matching the LDAP filter, +* * 'Whitelist usernames', which allows you to restrict DHE registry pull and +push to the listed set of users. +* A *DHE website Administrator filter*, allowing you to either +* * 'Allow all authenticated users' to log into the DHE admin web interface, or +* * 'Filter LDAP search results', which allows you to restrict DHE admin web access to users matching the LDAP filter, +* * 'Whitelist usernames', which allows you to restrict access to the web interface to the listed set of users. + +## Next Steps + +For information on getting support for DHE, take a look at the +[Support information](./support.md). + diff --git a/docs/sources/docker-hub-enterprise/index.md b/docs/sources/docker-hub-enterprise/index.md new file mode 100644 index 000000000..c14bf9280 --- /dev/null +++ b/docs/sources/docker-hub-enterprise/index.md @@ -0,0 +1,50 @@ +page_title: Docker Hub Enterprise: Overview +page_description: Docker Hub Enterprise +page_keywords: docker, documentation, about, technology, understanding, enterprise, hub, registry + +# Overview + +Docker Hub Enterprise (DHE) lets you run and manage your own Docker image +storage service, securely on your own infrastructure behind your company +firewall. This allows you to securely store, push, and pull the images used by +your enterprise to build, ship, and run applications. DHE also provides +monitoring and usage information to help you understand the workloads being +placed on it. + +Specifically, DHE provides: + +* An image registry to store, manage, and collaborate on Docker images +* Pluggable storage drivers +* Configuration options to let you run DHE in your particular enterprise +environment. +* Easy, transparent upgrades +* Logging, usage and system health metrics + +DHE is perfect for: + +* Providing a secure, on-premise development environment +* Creating a streamlined build pipeline +* Building a consistent, high-performance test/QA environment +* Managing image deployment + +DHE is built on [version 2 of the Docker registry](https://github.com/docker/distribution). + +## Documentation + +The following documentation for DHE is available: + +* **Overview** This page. +* [**Quick Start: Basic User Workflow**](./quick-start.md) Go here to learn the +fundamentals of how DHE works and how you can set up a simple, but useful +workflow. +* [**User Guide**](./userguide.md) Go here to learn about using DHE from day to +day. +* [**Administrator Guide**](./adminguide.md) Go here if you are an administrator +responsible for running and maintaining DHE. +* [**Installation**](install.md) Go here for the steps you'll need to install +DHE and get it working. +* [**Configuration**](./configuration.md) Go here to find out details about +setting up and configuring DHE for your particular environment. +* [**Support**](./support.md) Go here for information on getting support for +DHE. + diff --git a/docs/sources/docker-hub-enterprise/install-config.md b/docs/sources/docker-hub-enterprise/install-config.md deleted file mode 100644 index 81fa3041e..000000000 --- a/docs/sources/docker-hub-enterprise/install-config.md +++ /dev/null @@ -1,8 +0,0 @@ -page_title: Using Docker Hub Enterprise installation -page_description: Docker Hub Enterprise installation -page_keywords: docker hub enterprise - -# Docker Hub Enterprise installation - -Documenation coming soon. - diff --git a/docs/sources/docker-hub-enterprise/install.md b/docs/sources/docker-hub-enterprise/install.md new file mode 100644 index 000000000..84f9a321b --- /dev/null +++ b/docs/sources/docker-hub-enterprise/install.md @@ -0,0 +1,312 @@ +page_title: Docker Hub Enterprise: Install +page_description: Installation instructions for Docker Hub Enterprise +page_keywords: docker, documentation, about, technology, understanding, enterprise, hub, registry + +# Install + +## Overview + +This document describes the process of obtaining, installing, and securing +Docker Hub Enterprise (DHE). DHE is installed from Docker containers. Once +installed, you will need to select a method of securing it. This doc will +explain the options you have for security and help you find the resources needed +to configure it according to your chosen method. More configuration details can +be found in the [DHE Configuration page](./configuration.md). + +Specifically, installation requires completion of these steps, in order: + +1. Acquire a license by purchasing DHE or requesting a trial license. +2. Install the commercially supported Docker Engine. +3. Install DHE +4. Add your license to your DHE instance + +## Licensing + +In order to run DHE, you will need to acquire a license, either by purchasing +DHE or requesting a trial license. The license will be associated with your +Docker Hub account or Docker Hub organization (so if you don't have an account, +you'll need to set one up, which can be done at the same time as your license +request). To get your license or start your trial, please contact our +[sales department](mailto:sales@docker.com). Upon completion of your purchase or +request, you will receive an email with further instructions for licensing your +copy of DHE. + +## Prerequisites + +DHE requires the following: + +* Commercially supported Docker Engine 1.6.0 or later running on an +Ubuntu 14.04 LTS, RHEL 7.1 or RHEL 7.0 host. (See below for instructions on how +to install the commercially supported Docker Engine.) + +> **Note:** In order to remain in compliance with your DHE support agreement, +> you must use the current version of commercially supported Docker Engine. +> Running the regular, open source version of Engine is **not** supported. + +* Your Docker daemon needs to be listening to the Unix socket (the default) so +that it can be bind-mounted into the DHE management containers, allowing +DHE to manage itself and its updates. For this reason, your DHE host will also +need internet connectivity so it can access the updates. + +* Your host also needs to have TCP ports `80` and `443` available for the DHE +container port mapping. + +* You will also need the Docker Hub user-name and password used when obtaining +the DHE license (or the user-name of an administrator of the Hub organization +that obtained an Enterprise license). + +## Installing the Commercially Supported Docker Engine + +Since DHE is installed using Docker, the commercially supported Docker Engine +must be installed first. This is done with an RPM or DEB repository, which you +set up using a Bash script downloaded from the [Docker Hub](https://hub.docker.com). + +### Download the commercially supported Docker Engine installation script + +To download the commercially supported Docker Engine Bash installation script, +log in to the [Docker Hub](https://hub.docker.com) with the user-name used to +obtain your license . Once you're logged in, go to the +["Enterprise Licenses"](https://registry.hub.docker.com/account/licenses/) page +in your Hub account's "Settings" section. + +Select your intended host operating system from the "Download CS Engine" drop- +down at the top right of the page and then, once the Bash setup script is +downloaded, follow the steps below appropriate for your chosen OS. + +![Docker Hub Docker engine install dropdown](../assets/docker-hub-org-enterprise-license-CSDE-dropdown.png) + +### RHEL 7.0/7.1 installation + +First, copy the downloaded Bash setup script to your RHEL host. Next, run the +following to install commercially supported Docker Engine and its dependencies, +and then start the Docker daemon: + +``` +$ sudo yum update && sudo yum upgrade +$ chmod 755 docker-cs-engine-rpm.sh +$ sudo ./docker-cs-engine-rpm.sh +$ sudo yum install docker-engine-cs +$ sudo systemctl enable docker.service +$ sudo systemctl start docker.service +``` + +In order to simplify using Docker, you can get non-sudo access to the Docker +socket by adding your user to the `docker` group, then logging out and back in +again: + +``` +$ sudo usermod -a -G docker $USER +$ exit +``` + +> **Note**: you may need to reboot your server to update its RHEL kernel. + +### Ubuntu 14.04 LTS installation + +First, copy the downloaded Bash setup script to your Ubuntu host. Next, run the +following to install commercially supported Docker Engine and its dependencies: + +``` +$ sudo apt-get update && sudo apt-get upgrade +$ chmod 755 docker-cs-engine-deb.sh +$ sudo ./docker-cs-engine-deb.sh +$ sudo apt-get install docker-engine-cs +``` + +In order to simplify using Docker, you can get non-sudo access to the Docker +socket by adding your user to the `docker` group, then logging out and back in +again: + +``` +$ sudo usermod -a -G docker $USER +$ exit +``` + +> **Note**: you may need to reboot your server to update its LTS kernel. + +## Installing Docker Hub Enterprise + +Once the commercially supported Docker Engine is installed, you can install DHE +itself. DHE is a self-installing application built and distributed using Docker +and the [Docker Hub](https://registry.hub.docker.com/). It is able to restart +and reconfigure itself using the Docker socket that is bind-mounted to its +container. + + +Start installing DHE by running the "dockerhubenterprise/manager" container: + +``` + $ sudo bash -c "$(sudo docker run dockerhubenterprise/manager install)" +``` + +> **Note**: `sudo` is needed for `dockerhubenterprise/manager` commands to +> ensure that the Bash script is run with full access to the Docker host. + +You can also find this command on the "Enterprise Licenses" section of your Hub +user profile. The command will execute a shell script that creates the needed +directories and then runs Docker to pull DHE's images and run its containers. + +Depending on your internet connection, this process may take several minutes to +complete. + +A successful installation will pull a large number of Docker images and should +display output similar to: + +``` +$ sudo bash -c "$(sudo docker run dockerhubenterprise/manager install)" +Unable to find image 'dockerhubenterprise/manager:latest' locally +Pulling repository dockerhubenterprise/manager +c46d58daad7d: Pulling image (latest) from dockerhubenterprise/manager +c46d58daad7d: Pulling image (latest) from dockerhubenterprise/manager +c46d58daad7d: Pulling dependent layers +511136ea3c5a: Download complete +fa4fd76b09ce: Pulling metadata +fa4fd76b09ce: Pulling fs layer +ff2996b1faed: Download complete +... +fd7612809d57: Pulling metadata +fd7612809d57: Pulling fs layer +fd7612809d57: Download complete +c46d58daad7d: Pulling metadata +c46d58daad7d: Pulling fs layer +c46d58daad7d: Download complete +c46d58daad7d: Download complete +Status: Downloaded newer image for dockerhubenterprise/manager:latest +Unable to find image 'dockerhubenterprise/manager:1.0.0_8ce62a61e058' locally +Pulling repository dockerhubenterprise/manager +c46d58daad7d: Download complete +511136ea3c5a: Download complete +fa4fd76b09ce: Download complete +1c8294cc5160: Download complete +117ee323aaa9: Download complete +2d24f826cb16: Download complete +33bfc1956932: Download complete +48f0dd6c9414: Download complete +65c30f72ecb2: Download complete +d4b29764d0d3: Download complete +5654f4fe5384: Download complete +9b9faa6ecd11: Download complete +0c275f56ca5c: Download complete +ff2996b1faed: Download complete +fd7612809d57: Download complete +Status: Image is up to date for dockerhubenterprise/manager:1.0.0_8ce62a61e058 +INFO [1.0.0_8ce62a61e058] Attempting to connect to docker engine dockerHost="unix:///var/run/docker.sock" +INFO [1.0.0_8ce62a61e058] Running install command +<...output truncated...> +Creating container docker_hub_enterprise_load_balancer with docker daemon unix:///var/run/docker.sock +Starting container docker_hub_enterprise_load_balancer with docker daemon unix:///var/run/docker.sock +Bringing up docker_hub_enterprise_log_aggregator. +Creating container docker_hub_enterprise_log_aggregator with docker daemon unix:///var/run/docker.sock +Starting container docker_hub_enterprise_log_aggregator with docker daemon unix:///var/run/docker.sock +$ docker ps +CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +0168f37b6221 dockerhubenterprise/log-aggregator:1.0.0_8ce62a61e058 "log-aggregator" 4 seconds ago Up 4 seconds docker_hub_enterprise_log_aggregator +b51c73bebe8b dockerhubenterprise/nginx:1.0.0_8ce62a61e058 "nginxWatcher" 4 seconds ago Up 4 seconds 0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp docker_hub_enterprise_load_balancer +e8327864356b dockerhubenterprise/admin-server:1.0.0_8ce62a61e058 "server" 5 seconds ago Up 5 seconds 80/tcp docker_hub_enterprise_admin_server +52885a6e830a dockerhubenterprise/auth_server:alpha-a5a2af8a555e "garant --authorizat 6 seconds ago Up 5 seconds 8080/tcp +``` + +Once this process completes, you should be able to manage and configure your DHE +instance by pointing your browser to `https:///`. + +Your browser will warn you that this is an unsafe site, with a self-signed, +untrusted certificate. This is normal and expected; allow this connection +temporarily. + +### Setting the DHE Domain Name + +The DHE Administrator site will also warn that the "Domain Name" is not set. Go +to the "Settings" tab, and set the "Domain Name" to the full host-name of your +DHE server. +Hitting the "Save and Restart DHE Server" button will generate a new certificate, which will be used +by both the DHE Administrator web interface and the DHE Registry server. + +After the server restarts, you will again need to allow the connection to the untrusted DHE web admin site. + +![http settings page](../assets/admin-settings-http-unlicensed.png) + +Lastly, you will see a warning notifying you that this instance of DHE is +unlicensed. You'll correct this in the next step. + +### Add your license + +The DHE registry services will not start until you add your license. +To do that, you'll first download your license from the Docker Hub and then +upload it to your DHE web admin server. Follow these steps: + +1. If needed, log back into the [Docker Hub](https://hub.docker.com) + using the user-name you used when obtaining your license. Go to "Settings" (in + the menu under your user-name, top right) to get to your account settings, and + then click on "Enterprise Licenses" in the side bar at left. + +2. You'll see a list of available licenses. Click on the download button to + obtain the license file you'd like to use. + ![Download DHE license](../assets/docker-hub-org-enterprise-license.png) + +3. Next, go to your DHE instance in your browser and click on the Settings tab + and then the "License" tab. Click on the "Upload license file" button, which + will open a standard file browser. Locate and select the license file you + downloaded in step 2, above. Approve the selection to close the dialog. + ![http settings page](../assets/admin-settings-license.png) + +4. Click the "Save and Restart DHE" button, which will quit DHE and then restart it, registering + the new license. + +5. Verify the acceptance of the license by confirming that the "unlicensed copy" +warning is no longer present. + +### Securing DHE + +Securing DHE is **required**. You will not be able to push or pull from DHE until you secure it. + +There are several options and methods for securing DHE. For more information, +see the [configuration documentation](./configuration.md#security) + +### Using DHE to push and pull images + +Now that you have DHE configured with a "Domain Name" and have your client +Docker daemons configured with the required security settings, you can test your +setup by following the instructions for +[Using DHE to Push and pull images](./userguide.md#using-dhe-to-push-and-pull-images). + +### DHE web interface and registry authentication + +By default, there is no authentication set on either the DHE web admin +interface or the DHE registry. You can restrict access using an in-DHE +configured set of users (and passwords), or you can configure DHE to use LDAP- +based authentication. + +See [DHE Authentication settings](./configuration.md#authentication) for more +details. + +# Upgrading + +DHE has been designed to allow on-the-fly software upgrades. Start by +clicking on the "System Health" tab. In the upper, right-hand side of the +dashboard, below the navigation bar, you'll see the currently installed version +(e.g., `Current Version: 0.1.12345`). + +If your DHE instance is the latest available, you will also see the message: +"System Up to Date." + +If there is an upgrade available, you will see the message "System Update +Available!" alongside a button labeled "Update to Version X.XX". To upgrade, DHE +will pull new DHE container images from the Docker Hub. If you have not already +connected to Docker Hub, DHE will prompt you to log in. + +The upgrade process requires a small amount of downtime to complete. To complete +the upgrade, DHE will: +* Connect to the Docker Hub to pull new container images with the new version of +DHE. +* Deploy those containers +* Shut down the old containers +* Resolve any necessary links/urls. + +Assuming you have a decent internet connection, the entire upgrade process +should complete within a few minutes. + +## Next Steps + +For information on configuring DHE for your environment, take a look at the +[Configuration instructions](./configuration.md). + diff --git a/docs/sources/docker-hub-enterprise/quick-start.md b/docs/sources/docker-hub-enterprise/quick-start.md new file mode 100644 index 000000000..a813deb07 --- /dev/null +++ b/docs/sources/docker-hub-enterprise/quick-start.md @@ -0,0 +1,308 @@ +page_title: Docker Hub Enterprise: Quick-start: Basic Workflow +page_description: Brief tutorial on the basics of Docker Hub Enterprise user workflow +page_keywords: docker, documentation, about, technology, understanding, enterprise, hub, registry, image, repository + + +# Docker Hub Enterprise Quick Start: Basic User Workflow + +## Overview + +This Quick Start Guide will give you a hands-on look at the basics of using +Docker Hub Enterprise (DHE), Docker’s on-premise image storage application. +This guide will walk you through using DHE to complete a typical, and critical, +part of building a development pipeline: setting up a Jenkins instance. Once you +complete the task, you should have a good idea of how DHE works and how it might +be useful to you. + +Specifically, this guide demonstrates the process of retrieving the +[official Docker image for Jenkins](https://registry.hub.docker.com/_/jenkins/), +customizing it to suit your needs, and then hosting it on your private instance +of DHE located inside your enterprise’s firewalled environment. Your developers +will then be able to retrieve the custom Jenkins image in order to use it to +build CI/CD infrastructure for their projects, no matter the platform they’re +working from, be it a laptop, a VM, or a cloud provider. + +The guide will walk you through the following steps: + +1. Pulling the official Jenkins image from the public Docker Hub +2. Customizing the Jenkins image to suit your needs +3. Pushing the customized image to DHE +4. Pulling the customized image from DHE +4. Launching a container from the custom image +5. Using the new Jenkins container + +You should be able to complete this guide in about thirty minutes. + +> **Note:** This guide assumes you have installed a working instance of DHE +> reachable at dhe.yourdomain.com. If you need help installing and configuring +> DHE, please consult the +[installation instructions](./install.md). + + +## Pulling the official Jenkins image + +> **Note:** This guide assumes you are familiar with basic Docker concepts such +> as images, containers, and registries. If you need to learn more about Docker +> fundamentals, please consult the +> [Docker user guide](http://docs.docker.com/userguide/). + +First, you will retrieve a copy of the official Jenkins image from the Docker Hub. From the CLI of a machine running the Docker Engine on your network, use +the +[`docker pull`](https://docs.docker.com/reference/commandline/cli/#pull) +command to pull the public Jenkins image. + + $ docker pull jenkins + +> **Note:** This guide assumes you can run Docker commands from a machine where +> you are a member of the `docker` group, or have root privileges. Otherwise, you may +> need to add `sudo` to the example commands below. + +Docker will start the process of pulling the image from the Hub. Once it has completed, the Jenkins image should be visible in the output of a [`docker images`](https://docs.docker.com/reference/commandline/cli/#images) command: + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + jenkins latest 1a7cc22b0ee9 6 days ago 662 MB + +> **Note:** Because the `pull` command did not specify any tags, it will pull +> the latest version of the public Jenkins image. If your enterprise environment +> requires you to use a specific version, add the tag for the version you need +> (e.g., `jenkins:1.565`). + +## Customizing the Jenkins image + +Now that you have a local copy of the Jenkins image, you’ll customize it so that +the containers it builds will integrate with your infrastructure. To do this, +you’ll create a custom Docker image that adds a Jenkins plugin that provides +fine grained user management. You’ll also configure Jenkins to be more secure by +disabling HTTP access and forcing it to use HTTPS. +You’ll do this by using a `Dockerfile` and the `docker build` command. + +> **Note:** These are obviously just a couple of examples of the many ways you +> can modify and configure Jenkins. Feel free to add or substitute whatever +> customization is necessary to run Jenkins in your environment. + +### Creating a `build` context + +In order to add the new plugin and configure HTTPS access to the custom Jenkins +image, you need to: + +1. Create text file that defines the new plugin +2. Create copies of the private key and certificate + +All of the above files need to be in the same directory as the Dockerfile you +will create in the next step. + +1. Create a build directory called `build`, and change to that new directory: + + $ mkdir build && cd build + +In this directory, create a new file called `plugins` and add the following +line: + + role-strategy:2.2.0 + +(The plugin version used above was the latest version at the time of writing.) + +2. You will also need to make copies of the server’s private key and certificate. Give the copies the following names — `https.key` and `https.pem`. + +> **Note:** Because creating new keys varies widely by platform and +> implementation, this guide won’t cover key generation. We assume you have +> access to existing keys. If you don’t have access, or can’t generate keys +> yourself, feel free to skip the steps involving them and HTTPS config. The +> guide will still walk you through building a custom Jenkins image and pushing +> and pulling that image using DHE. + +### Creating a Dockerfile + +In the same directory as the `plugins` file and the private key and certificate, +create a new [`Dockerfile`](https://docs.docker.com/reference/builder/) with the +following contents: + + FROM jenkins + + #New plugins must be placed in the plugins file + COPY plugins /usr/share/jenkins/plugins + + #The plugins.sh script will install new plugins + RUN /usr/local/bin/plugins.sh /usr/share/jenkins/plugins + + #Copy private key and cert to image + COPY https.pem /var/lib/jenkins/cert + COPY https.key /var/lib/jenkins/pk + + #Configure HTTP off and HTTPS on, using port 1973 + ENV JENKINS_OPTS --httpPort=-1 --httpsPort=1973 --httpsCertificate=/var/lib/jenkins/cert --httpsPrivateKey=/var/lib/jenkins/pk + +The first `COPY` instruction in the above will copy the `plugin` file created +earlier into the `/usr/share/jenkins` directory within the custom image you are +defining with the `Dockerfile`. + +The `RUN` instruction will execute the `/usr/local/bin/plugins.sh` script with +the newly copied `plugins` file, which will install the listed plugin. + +The next two `COPY` instructions copy the server’s private key and certificate +into the required directories within the new image. + +The `ENV` instruction creates an environment variable called `JENKINS_OPT` in +the image you are about to create. This environment variable will be present in +any containers launched form the image and contains the required settings to +tell Jenkins to disable HTTP and operate over HTTPS. + +> **Note:** You can specify any valid port number as part of the `JENKINS_OPT` +> environment variable declared above. The value `1973` used in the example is +> arbitrary. + +The `Dockerfile`, the `plugins` file, as well as the private key and +certificate, must all be in the same directory because the `docker build` +command uses the directory that contains the `Dockerfile` as its “build +context”. Only files contained within that “build context” will be included in +the image being built. + +### Building your custom image + +Now that the `Dockerfile`, the `plugins` file, and the files required for HTTPS +operation are created in your current working directory, you can build your +custom image using the +[`docker build` command](https://docs.docker.com/reference/commandline/cli/#build): + + docker build -t dhe.yourdomain.com/ci-infrastructure/jnkns-img . + +> **Note:** Don’t miss the period (`.`) at the end of the command above. This +> tells the `docker build` command to use the current working directory as the +> "build context". + +This command will build a new Docker image called `jnkns-img` which is based on +the public Jenkins image you pulled earlier, but contains all of your +customization. + +Please note the use of the `-t` flag in the `docker build` command above. The +`-t` flag lets you tag an image so it can be pushed to a custom repository. In +the example above, the new image is tagged so it can be pushed to the +`ci-infrastructure` Repository within the `dhe.yourdomain.com` registry (your +local DHE instance). This will be important when you need to `push` the +customized image to DHE later. + +A `docker images` command will now show the custom image alongside the Jenkins +image pulled earlier: + + $ sudo docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + dhe.yourdomain.com/ci-infrastructure/jnkns-img latest fc0ab3008d40 2 minutes ago 674.5 MB + jenkins latest 1a7cc22b0ee9 6 days ago 662 MB + +## Pushing to Docker Hub Enterprise + +Now that you’ve create the custom image, it can be pushed to DHE using the +[`docker push`command](https://docs.docker.com/reference/commandline/cli/#push): + + $ docker push dhe.yourdomain.com/ci-infrastructure/jnkns-img + 511136ea3c5a: Image successfully pushed + 848d84b4b2ab: Image successfully pushed + 71d9d77ae89e: Image already exists + + 492ed3875e3e: Image successfully pushed + fc0ab3008d40: Image successfully pushed + +You can view the traffic throughput while the custom image is being pushed from +the `System Health` tab in DHE: + +![DHE console push throughput](../assets/console-push.png) + +Once the image is successfully pushed, it can be downloaded, or pulled, by any +Docker host that has access to DHE. + +## Pulling from Docker Hub Enterprise +To pull the `jnkns-img` image from DHE, run the +[`docker pull`](https://docs.docker.com/reference/commandline/cli/#pull) +command from any Docker Host that has access to your DHE instance: + + $ docker pull dhe.yourdomain.com/ci-infrastructure/jnkns-img + latest: Pulling from dhe.yourdomain.com/ci-infrastructure/jnkns-img + 511136ea3c5a: Pull complete + 848d84b4b2ab: Pull complete + 71d9d77ae89e: Pull complete + + 492ed3875e3e: Pull complete + fc0ab3008d40: Pull complete + dhe.yourdomain.com/ci-infrastructure/jnkns-img:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security. + Status: Downloaded newer image for dhe.yourdomain.com/ci-infrastructure/jnkns-img:latest + +You can view the traffic throughput while the custom image is being pulled from +the `System Health` tab in DHE: + +![DHE console pull throughput](../assets/console-pull.png) + +Now that the `jnkns-img` image has been pulled locally from DHE, you can view it +in the output of the `docker images` command: + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + dhe.yourdomain.com/ci-infrastructure/jnkns-img latest fc0ab3008d40 8 minutes ago 674.5 MB + +## Launching a custom Jenkins container + +Now that you’ve successfully pulled the customized Jenkins image from DHE, you +can create a container from it with the +[`docker run` command](https://docs.docker.com/reference/commandline/cli/#run): + + + $ docker run -p 1973:1973 --name jenkins01 dhe.yourdomain.com/ci-infrastructure/jnkns-img + /usr/share/jenkins/ref/init.groovy.d/tcp-slave-angent-port.groovy + /usr/share/jenkins/ref/init.groovy.d/tcp-slave-angent-port.groovy -> init.groovy.d/tcp-slave-angent-port.groovy + copy init.groovy.d/tcp-slave-angent-port.groovy to JENKINS_HOME + /usr/share/jenkins/ref/plugins/role-strategy.hpi + /usr/share/jenkins/ref/plugins/role-strategy.hpi -> plugins/role-strategy.hpi + copy plugins/role-strategy.hpi to JENKINS_HOME + /usr/share/jenkins/ref/plugins/dockerhub.hpi + /usr/share/jenkins/ref/plugins/dockerhub.hpi -> plugins/dockerhub.hpi + copy plugins/dockerhub.hpi to JENKINS_HOME + + INFO: Jenkins is fully up and running + +> **Note:** The `docker run` command above maps port 1973 in the container +> through to port 1973 on the host. This is the HTTPS port you specified in the +> Dockerfile earlier. If you specified a different HTTPS port in your +> Dockerfile, you will need to substitute this with the correct port numbers for +> your environment. + +You can view the newly launched a container, called `jenkins01`, using the +[`docker ps` command](https://docs.docker.com/reference/commandline/cli/#ps): + + $ docker ps + CONTAINER ID IMAGE COMMAND CREATED STATUS ...PORTS NAMES + 2e5d2f068504 dhe.yourdomain.com/ci-infrastructure/jnkns-img:latest "/usr/local/bin/jenk About a minute ago Up About a minute 50000/tcp, 0.0.0.0:1973->1973/tcp jenkins01 + + +## Accessing the new Jenkins container + +The previous `docker run` command mapped port `1973` on the container to port +`1973` on the Docker host, so the Jenkins Web UI can be accessed at +`https://:1973` (Don’t forget the `s` at the end of `https`.) + +> **Note:** If you are using a self-signed certificate, you may get a security +> warning from your browser telling you that the certificate is self-signed and +> not trusted. You may wish to add the certificate to the trusted store in order +> to prevent further warnings in the future. + +![Jenkins landing page](../assets/jenkins-ui.png) + +From within the Jenkins Web UI, navigate to `Manage Jenkins` (on the left-hand +pane) > `Manage Plugins` > `Installed`. The `Role-based Authorization Strategy` +plugin should be present with the `Uninstall` button available to the right. + +![Jenkins plugin manager](../assets/jenkins-plugins.png) + +In another browser session, try to access Jenkins via the default HTTP port 8080 +— `http://:8080`. This should result in a “connection timeout,” +showing that Jenkins is not available on its default port 8080 over HTTP. + +This demonstration shows your Jenkins image has been configured correctly for +HTTPS access, your new plugin was added and is ready for use, and HTTP access +has been disabled. At this point, any member of your team can use `docker pull` +to access the image from your DHE instance, allowing them to access a +configured, secured Jenkins instance that can run on any infrastructure. + +## Next Steps + +For more information on using DHE, take a look at the +[User's Guide](./userguide.md). diff --git a/docs/sources/docker-hub-enterprise/support.md b/docs/sources/docker-hub-enterprise/support.md new file mode 100644 index 000000000..ed60748a3 --- /dev/null +++ b/docs/sources/docker-hub-enterprise/support.md @@ -0,0 +1,14 @@ +page_title: Docker Hub Enterprise: Support +page_description: Commercial Support +page_keywords: docker, documentation, about, technology, understanding, enterprise, hub, registry, support + +# Commercial Support + +Purchasing a DHE License or Commercial Support subscription means your questions +and issues about DHE will receive prioritized support. +You can file a ticket through [email](mailto:support@docker.com) from your +company email address, or visit our [support site](https://support.docker.com). +In either case, you'll need to verify your email address, and then you can +communicate with the support team either by email or web interface. + +**The availability of support depends on your [support subscription](https://www.docker.com/enterprise/support/)** diff --git a/docs/sources/docker-hub-enterprise/usage.md b/docs/sources/docker-hub-enterprise/usage.md deleted file mode 100644 index 252223ef7..000000000 --- a/docs/sources/docker-hub-enterprise/usage.md +++ /dev/null @@ -1,9 +0,0 @@ -page_title: Using Docker Hub Enterprise -page_description: Docker Hub Enterprise -page_keywords: docker hub enterprise - -# Docker Hub Enterprise - -Documenation coming soon. - - diff --git a/docs/sources/docker-hub-enterprise/userguide.md b/docs/sources/docker-hub-enterprise/userguide.md new file mode 100644 index 000000000..6d329722d --- /dev/null +++ b/docs/sources/docker-hub-enterprise/userguide.md @@ -0,0 +1,130 @@ +page_title: Docker Hub Enterprise: User guide +page_description: Documentation describing basic use of Docker Hub Enterprise +page_keywords: docker, documentation, about, technology, hub, enterprise + + +# Docker Hub Enterprise User's Guide + +This guide covers tasks and functions a user of Docker Hub Enterprise (DHE) will +need to know about, such as pushing or pulling images, etc. For tasks DHE +administrators need to accomplish, such as configuring or monitoring DHE, please +visit the [Administrator's Guide](./adminguide.md). + +## Using DHE to push and pull images + +The primary use case for DHE users is to push and pull images to and from the +DHE image storage service. The following instructions describe these procedures. + +> **Note**: If your DHE instance has authentication enabled, you will need to +>use your command line to `docker login ` (e.g., `docker login +> dhe.yourdomain.com`). +> +> Failures due to unauthenticated `docker push` and `docker pull` commands will +> look like : +> +> $ docker pull dhe.yourdomain.com/hello-world +> Pulling repository dhe.yourdomain.com/hello-world +> FATA[0001] Error: image hello-world:latest not found +> +> $ docker push dhe.yourdomain.com/hello-world +> The push refers to a repository [dhe.yourdomain.com/hello-world] (len: 1) +> e45a5af57b00: Image push failed +> FATA[0001] Error pushing to registry: token auth attempt for registry https://dhe.yourdomain.com/v2/: https://> dhe.yourdomain.com/auth/v2/token/?scope=repository%3Ahello-world%3Apull%2Cpush&service=dhe.yourdomain.com > request failed with status: 401 Unauthorized + + +1. Pull the `hello-world` official image from the Docker Hub. By default, if +Docker can't find an image locally, it will attempt to pull the image from the +Docker Hub. + + `$ docker pull hello-world` + +2. List your available images. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + hello-world latest e45a5af57b00 3 months ago 910 B + + Your list should include the `hello-world` image from the earlier run. + +3. Re-tag the `hello-world` image so that it refers to your DHE server. + + `$ docker tag hello-world:latest dhe.yourdomain.com/demouser/hello-mine:latest` + + The command labels a `hello-world:latest` image using a new tag in the + `[REGISTRYHOST/][USERNAME/]NAME[:TAG]` format. The `REGISTRYHOST` in this + case is the DHE server, `dhe.yourdomain.com`, and the `USERNAME` is + `demouser`. + +4. List your new image. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + hello-world latest e45a5af57b00 3 months ago 910 B + dhe.yourdomain.com/demouser/hello-mine latest e45a5af57b00 3 months ago 910 B + + You should see your new image label in the listing, with the same `IMAGE ID` + as the Official image. + +5. Push this new image to your DHE server. + + `$ docker push dhe.yourdomain.com/demouser/hello-mine:latest` + +6. Set up a test of DHE by removing all images from your local environment: + + `$ docker rmi -f $(docker images -q -a)` + + This command is for illustrative purposes only: removing the image forces + any subsequent `run` to pull from a remote registry (such as DHE) rather + than from a local cache. If you run `docker images` after this you should + not see any instance of `hello-world` or `hello-mine` in your images list. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + +7. Try running `hello-mine`. + + $ docker run hello-mine + Unable to find image 'hello-mine:latest' locally + Pulling repository hello-mine + FATA[0007] Error: image library/hello-mine:latest not found + + The `run` command fails because your new image doesn't exist on the Docker Hub. + +8. Run `hello-mine` again, this time pointing it to pull from DHE: + + $ docker run dhe.yourdomain.com/demouser/hello-mine + latest: Pulling from dhe.yourdomain.com/demouser/hello-mine + 511136ea3c5a: Pull complete + 31cbccb51277: Pull complete + e45a5af57b00: Already exists + Digest: sha256:45f0de377f861694517a1440c74aa32eecc3295ea803261d62f950b1b757bed1 + Status: Downloaded newer image for dhe.yourdomain.com/demouser/hello-mine:latest + + If you run `docker images` after this you'll see a `hello-mine` image. + + $ docker images + REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE + dhe.yourdomain.com/demouser/hello-mine latest e45a5af57b00 3 months ago 910 B + +> **Note**: If the Docker daemon on which you are running `docker push` doesn't +> have the right certificates set up, you will get an error similar to: +> +> $ docker push dhe.yourdomain.com/demouser/hello-world +> FATA[0000] Error response from daemon: v1 ping attempt failed with error: Get https://dhe.yourdomain.com/v1/_ping: x509: certificate signed by unknown authority. If this private registry supports only HTTP or HTTPS with an unknown CA certificate, please add `--insecure-registry dhe.yourdomain.com` to the daemon's arguments. In the case of HTTPS, if you have access to the registry's CA certificate, no need for the flag; simply place the CA certificate at /etc/docker/certs.d/dhe.yourdomain.com/ca.crt + +9. You have now successfully created a custom image, `hello-mine`, tagged it, + and pushed it to the image storage provided by your DHE instance. You then + pulled that image back down from DHE and onto your machine, where you can + use it to create a container containing the "Hello World" application.. + +## Next Steps + +For information on administering DHE, take a look at the [Administrator's Guide](./adminguide.md). + + + From 4377ebd6a758278c1766006c7eb8b777fa175719 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Fri, 6 Mar 2015 12:53:06 +1100 Subject: [PATCH 621/999] *: expose getResourcePath and getRootResourcePath wrappers Due to the importance of path safety, the internal sanitisation wrappers for volumes and containers should be exposed so other parts of Docker can benefit from proper path sanitisation. Signed-off-by: Aleksa Sarai (github: cyphar) --- daemon/container.go | 47 ++++++++++++++++++++++++++++++++++----------- daemon/volumes.go | 10 +++++----- volumes/volume.go | 36 ++++++++++++++++++++++++++++------ 3 files changed, 71 insertions(+), 22 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index bdfcbf447..8eb35c9f5 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -211,12 +211,37 @@ func (container *Container) LogEvent(action string) { ) } -func (container *Container) getResourcePath(path string) (string, error) { +// Evaluates `path` in the scope of the container's basefs, with proper path +// sanitisation. Symlinks are all scoped to the basefs of the container, as +// though the container's basefs was `/`. +// +// The basefs of a container is the host-facing path which is bind-mounted as +// `/` inside the container. This method is essentially used to access a +// particular path inside the container as though you were a process in that +// container. +// +// NOTE: The returned path is *only* safely scoped inside the container's basefs +// if no component of the returned path changes (such as a component +// symlinking to a different path) between using this method and using the +// path. See symlink.FollowSymlinkInScope for more details. +func (container *Container) GetResourcePath(path string) (string, error) { cleanPath := filepath.Join("/", path) return symlink.FollowSymlinkInScope(filepath.Join(container.basefs, cleanPath), container.basefs) } -func (container *Container) getRootResourcePath(path string) (string, error) { +// Evaluates `path` in the scope of the container's root, with proper path +// sanitisation. Symlinks are all scoped to the root of the container, as +// though the container's root was `/`. +// +// The root of a container is the host-facing configuration metadata directory. +// Only use this method to safely access the container's `container.json` or +// other metadata files. If in doubt, use container.GetResourcePath. +// +// NOTE: The returned path is *only* safely scoped inside the container's root +// if no component of the returned path changes (such as a component +// symlinking to a different path) between using this method and using the +// path. See symlink.FollowSymlinkInScope for more details. +func (container *Container) GetRootResourcePath(path string) (string, error) { cleanPath := filepath.Join("/", path) return symlink.FollowSymlinkInScope(filepath.Join(container.root, cleanPath), container.root) } @@ -515,7 +540,7 @@ func (streamConfig *StreamConfig) StderrLogPipe() io.ReadCloser { } func (container *Container) buildHostnameFile() error { - hostnamePath, err := container.getRootResourcePath("hostname") + hostnamePath, err := container.GetRootResourcePath("hostname") if err != nil { return err } @@ -529,7 +554,7 @@ func (container *Container) buildHostnameFile() error { func (container *Container) buildHostsFiles(IP string) error { - hostsPath, err := container.getRootResourcePath("hosts") + hostsPath, err := container.GetRootResourcePath("hosts") if err != nil { return err } @@ -895,7 +920,7 @@ func (container *Container) Unmount() error { } func (container *Container) logPath(name string) (string, error) { - return container.getRootResourcePath(fmt.Sprintf("%s-%s.log", container.ID, name)) + return container.GetRootResourcePath(fmt.Sprintf("%s-%s.log", container.ID, name)) } func (container *Container) ReadLog(name string) (io.Reader, error) { @@ -907,11 +932,11 @@ func (container *Container) ReadLog(name string) (io.Reader, error) { } func (container *Container) hostConfigPath() (string, error) { - return container.getRootResourcePath("hostconfig.json") + return container.GetRootResourcePath("hostconfig.json") } func (container *Container) jsonPath() (string, error) { - return container.getRootResourcePath("config.json") + return container.GetRootResourcePath("config.json") } // This method must be exported to be used from the lxc template @@ -981,7 +1006,7 @@ func (container *Container) Copy(resource string) (io.ReadCloser, error) { } }() - basePath, err := container.getResourcePath(resource) + basePath, err := container.GetResourcePath(resource) if err != nil { return nil, err } @@ -1083,7 +1108,7 @@ func (container *Container) setupContainerDns() error { if err != nil { return err } - container.ResolvConfPath, err = container.getRootResourcePath("resolv.conf") + container.ResolvConfPath, err = container.GetRootResourcePath("resolv.conf") if err != nil { return err } @@ -1244,7 +1269,7 @@ func (container *Container) initializeNetworking() error { return err } - hostsPath, err := container.getRootResourcePath("hosts") + hostsPath, err := container.GetRootResourcePath("hosts") if err != nil { return err } @@ -1375,7 +1400,7 @@ func (container *Container) setupWorkingDirectory() error { if container.Config.WorkingDir != "" { container.Config.WorkingDir = path.Clean(container.Config.WorkingDir) - pth, err := container.getResourcePath(container.Config.WorkingDir) + pth, err := container.GetResourcePath(container.Config.WorkingDir) if err != nil { return err } diff --git a/daemon/volumes.go b/daemon/volumes.go index 4d15023ba..79d83504a 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -47,7 +47,7 @@ func (container *Container) createVolumes() error { continue } - realPath, err := container.getResourcePath(path) + realPath, err := container.GetResourcePath(path) if err != nil { return err } @@ -336,7 +336,7 @@ func (container *Container) mountVolumes() error { return fmt.Errorf("could not find volume for %s:%s, impossible to mount", source, dest) } - destPath, err := container.getResourcePath(dest) + destPath, err := container.GetResourcePath(dest) if err != nil { return err } @@ -347,7 +347,7 @@ func (container *Container) mountVolumes() error { } for _, mnt := range container.specialMounts() { - destPath, err := container.getResourcePath(mnt.Destination) + destPath, err := container.GetResourcePath(mnt.Destination) if err != nil { return err } @@ -360,7 +360,7 @@ func (container *Container) mountVolumes() error { func (container *Container) unmountVolumes() { for dest := range container.Volumes { - destPath, err := container.getResourcePath(dest) + destPath, err := container.GetResourcePath(dest) if err != nil { logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) continue @@ -372,7 +372,7 @@ func (container *Container) unmountVolumes() { } for _, mnt := range container.specialMounts() { - destPath, err := container.getResourcePath(mnt.Destination) + destPath, err := container.GetResourcePath(mnt.Destination) if err != nil { logrus.Errorf("error while unmounting volumes %s: %v", destPath, err) continue diff --git a/volumes/volume.go b/volumes/volume.go index 87aa4ad25..283bc9bca 100644 --- a/volumes/volume.go +++ b/volumes/volume.go @@ -114,14 +114,38 @@ func (v *Volume) FromDisk() error { } func (v *Volume) jsonPath() (string, error) { - return v.getRootResourcePath("config.json") -} -func (v *Volume) getRootResourcePath(path string) (string, error) { - cleanPath := filepath.Join("/", path) - return symlink.FollowSymlinkInScope(filepath.Join(v.configPath, cleanPath), v.configPath) + return v.GetRootResourcePath("config.json") } -func (v *Volume) getResourcePath(path string) (string, error) { +// Evalutes `path` in the scope of the volume's root path, with proper path +// sanitisation. Symlinks are all scoped to the root of the volume, as +// though the volume's root was `/`. +// +// The volume's root path is the host-facing path of the root of the volume's +// mountpoint inside a container. +// +// NOTE: The returned path is *only* safely scoped inside the volume's root +// if no component of the returned path changes (such as a component +// symlinking to a different path) between using this method and using the +// path. See symlink.FollowSymlinkInScope for more details. +func (v *Volume) GetResourcePath(path string) (string, error) { cleanPath := filepath.Join("/", path) return symlink.FollowSymlinkInScope(filepath.Join(v.Path, cleanPath), v.Path) } + +// Evalutes `path` in the scope of the volume's config path, with proper path +// sanitisation. Symlinks are all scoped to the root of the config path, as +// though the config path was `/`. +// +// The config path of a volume is not exposed to the container and is just used +// to store volume configuration options and other internal information. If in +// doubt, you probably want to just use v.GetResourcePath. +// +// NOTE: The returned path is *only* safely scoped inside the volume's config +// path if no component of the returned path changes (such as a component +// symlinking to a different path) between using this method and using the +// path. See symlink.FollowSymlinkInScope for more details. +func (v *Volume) GetRootResourcePath(path string) (string, error) { + cleanPath := filepath.Join("/", path) + return symlink.FollowSymlinkInScope(filepath.Join(v.configPath, cleanPath), v.configPath) +} From b7c3c0cb6988c9a7864649bfe458217336c5fc51 Mon Sep 17 00:00:00 2001 From: Aleksa Sarai Date: Fri, 20 Mar 2015 19:59:15 +1100 Subject: [PATCH 622/999] *: switch to Get(Root)?ResourcePath where appropriate Several parts of the codebase didn't use the correct path sanitisation wrappers. Now that the wrappers have been exposed, use those. Signed-off-by: Aleksa Sarai (github: cyphar) --- builder/internals.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/builder/internals.go b/builder/internals.go index ba7d45bcb..2fe7ca8ba 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -32,7 +32,6 @@ import ( "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/pkg/symlink" "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/tarsum" "github.com/docker/docker/pkg/urlutil" @@ -646,14 +645,12 @@ func (b *Builder) addContext(container *daemon.Container, orig, dest string, dec err error destExists = true origPath = path.Join(b.contextPath, orig) - destPath = path.Join(container.RootfsPath(), dest) + destPath string ) - if destPath != container.RootfsPath() { - destPath, err = symlink.FollowSymlinkInScope(destPath, container.RootfsPath()) - if err != nil { - return err - } + destPath, err = container.GetResourcePath(dest) + if err != nil { + return err } // Preserve the trailing '/' From c21d408ad24cf8e2b5bd761d562fae7e3ae1bc54 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Fri, 24 Apr 2015 17:03:33 +0200 Subject: [PATCH 623/999] Add coverage on pkg/archive Add tests on: - changes.go - archive.go - wrap.go Should fix #11603 as the coverage is now 81.2% on the ``pkg/archive`` package. There is still room for improvement though :). Signed-off-by: Vincent Demeester --- pkg/archive/archive_test.go | 309 ++++++++++++++++++++++++++++++++++++ pkg/archive/changes_test.go | 152 ++++++++++++++++++ pkg/archive/wrap_test.go | 98 ++++++++++++ 3 files changed, 559 insertions(+) create mode 100644 pkg/archive/wrap_test.go diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index dabb0d504..ae9b5a8cd 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -207,6 +207,315 @@ func TestCmdStreamGood(t *testing.T) { } } +func TestUntarPathWithInvalidDest(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempFolder) + invalidDestFolder := path.Join(tempFolder, "invalidDest") + // Create a src file + srcFile := path.Join(tempFolder, "src") + _, err = os.Create(srcFile) + if err != nil { + t.Fatalf("Fail to create the source file") + } + err = UntarPath(srcFile, invalidDestFolder) + if err == nil { + t.Fatalf("UntarPath with invalid destination path should throw an error.") + } +} + +func TestUntarPathWithInvalidSrc(t *testing.T) { + dest, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatalf("Fail to create the destination file") + } + defer os.RemoveAll(dest) + err = UntarPath("/invalid/path", dest) + if err == nil { + t.Fatalf("UntarPath with invalid src path should throw an error.") + } +} + +func TestUntarPath(t *testing.T) { + tmpFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpFolder) + srcFile := path.Join(tmpFolder, "src") + tarFile := path.Join(tmpFolder, "src.tar") + os.Create(path.Join(tmpFolder, "src")) + cmd := exec.Command("/bin/sh", "-c", "tar cf "+tarFile+" "+srcFile) + _, err = cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + destFolder := path.Join(tmpFolder, "dest") + err = os.MkdirAll(destFolder, 0740) + if err != nil { + t.Fatalf("Fail to create the destination file") + } + err = UntarPath(tarFile, destFolder) + if err != nil { + t.Fatalf("UntarPath shouldn't throw an error, %s.", err) + } + expectedFile := path.Join(destFolder, srcFile) + _, err = os.Stat(expectedFile) + if err != nil { + t.Fatalf("Destination folder should contain the source file but did not.") + } +} + +// Do the same test as above but with the destination as file, it should fail +func TestUntarPathWithDestinationFile(t *testing.T) { + tmpFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpFolder) + srcFile := path.Join(tmpFolder, "src") + tarFile := path.Join(tmpFolder, "src.tar") + os.Create(path.Join(tmpFolder, "src")) + cmd := exec.Command("/bin/sh", "-c", "tar cf "+tarFile+" "+srcFile) + _, err = cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + destFile := path.Join(tmpFolder, "dest") + _, err = os.Create(destFile) + if err != nil { + t.Fatalf("Fail to create the destination file") + } + err = UntarPath(tarFile, destFile) + if err == nil { + t.Fatalf("UntarPath should throw an error if the destination if a file") + } +} + +// Do the same test as above but with the destination folder already exists +// and the destination file is a directory +// It's working, see https://github.com/docker/docker/issues/10040 +func TestUntarPathWithDestinationSrcFileAsFolder(t *testing.T) { + tmpFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpFolder) + srcFile := path.Join(tmpFolder, "src") + tarFile := path.Join(tmpFolder, "src.tar") + os.Create(srcFile) + cmd := exec.Command("/bin/sh", "-c", "tar cf "+tarFile+" "+srcFile) + _, err = cmd.CombinedOutput() + if err != nil { + t.Fatal(err) + } + destFolder := path.Join(tmpFolder, "dest") + err = os.MkdirAll(destFolder, 0740) + if err != nil { + t.Fatalf("Fail to create the destination folder") + } + // Let's create a folder that will has the same path as the extracted file (from tar) + destSrcFileAsFolder := path.Join(destFolder, srcFile) + err = os.MkdirAll(destSrcFileAsFolder, 0740) + if err != nil { + t.Fatal(err) + } + err = UntarPath(tarFile, destFolder) + if err != nil { + t.Fatalf("UntarPath should throw not throw an error if the extracted file already exists and is a folder") + } +} + +func TestCopyWithTarInvalidSrc(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(nil) + } + destFolder := path.Join(tempFolder, "dest") + invalidSrc := path.Join(tempFolder, "doesnotexists") + err = os.MkdirAll(destFolder, 0740) + if err != nil { + t.Fatal(err) + } + err = CopyWithTar(invalidSrc, destFolder) + if err == nil { + t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.") + } +} + +func TestCopyWithTarInexistentDestWillCreateIt(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(nil) + } + srcFolder := path.Join(tempFolder, "src") + inexistentDestFolder := path.Join(tempFolder, "doesnotexists") + err = os.MkdirAll(srcFolder, 0740) + if err != nil { + t.Fatal(err) + } + err = CopyWithTar(srcFolder, inexistentDestFolder) + if err != nil { + t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.") + } + _, err = os.Stat(inexistentDestFolder) + if err != nil { + t.Fatalf("CopyWithTar with an inexistent folder should create it.") + } +} + +// Test CopyWithTar with a file as src +func TestCopyWithTarSrcFile(t *testing.T) { + folder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(folder) + dest := path.Join(folder, "dest") + srcFolder := path.Join(folder, "src") + src := path.Join(folder, path.Join("src", "src")) + err = os.MkdirAll(srcFolder, 0740) + if err != nil { + t.Fatal(err) + } + err = os.MkdirAll(dest, 0740) + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(src, []byte("content"), 0777) + err = CopyWithTar(src, dest) + if err != nil { + t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err) + } + _, err = os.Stat(dest) + // FIXME Check the content + if err != nil { + t.Fatalf("Destination file should be the same as the source.") + } +} + +// Test CopyWithTar with a folder as src +func TestCopyWithTarSrcFolder(t *testing.T) { + folder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(folder) + dest := path.Join(folder, "dest") + src := path.Join(folder, path.Join("src", "folder")) + err = os.MkdirAll(src, 0740) + if err != nil { + t.Fatal(err) + } + err = os.MkdirAll(dest, 0740) + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(path.Join(src, "file"), []byte("content"), 0777) + err = CopyWithTar(src, dest) + if err != nil { + t.Fatalf("archiver.CopyWithTar shouldn't throw an error, %s.", err) + } + _, err = os.Stat(dest) + // FIXME Check the content (the file inside) + if err != nil { + t.Fatalf("Destination folder should contain the source file but did not.") + } +} + +func TestCopyFileWithTarInvalidSrc(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tempFolder) + destFolder := path.Join(tempFolder, "dest") + err = os.MkdirAll(destFolder, 0740) + if err != nil { + t.Fatal(err) + } + invalidFile := path.Join(tempFolder, "doesnotexists") + err = CopyFileWithTar(invalidFile, destFolder) + if err == nil { + t.Fatalf("archiver.CopyWithTar with invalid src path should throw an error.") + } +} + +func TestCopyFileWithTarInexistentDestWillCreateIt(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(nil) + } + defer os.RemoveAll(tempFolder) + srcFile := path.Join(tempFolder, "src") + inexistentDestFolder := path.Join(tempFolder, "doesnotexists") + _, err = os.Create(srcFile) + if err != nil { + t.Fatal(err) + } + err = CopyFileWithTar(srcFile, inexistentDestFolder) + if err != nil { + t.Fatalf("CopyWithTar with an inexistent folder shouldn't fail.") + } + _, err = os.Stat(inexistentDestFolder) + if err != nil { + t.Fatalf("CopyWithTar with an inexistent folder should create it.") + } + // FIXME Test the src file and content +} + +func TestCopyFileWithTarSrcFolder(t *testing.T) { + folder, err := ioutil.TempDir("", "docker-archive-copyfilewithtar-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(folder) + dest := path.Join(folder, "dest") + src := path.Join(folder, "srcfolder") + err = os.MkdirAll(src, 0740) + if err != nil { + t.Fatal(err) + } + err = os.MkdirAll(dest, 0740) + if err != nil { + t.Fatal(err) + } + err = CopyFileWithTar(src, dest) + if err == nil { + t.Fatalf("CopyFileWithTar should throw an error with a folder.") + } +} + +func TestCopyFileWithTarSrcFile(t *testing.T) { + folder, err := ioutil.TempDir("", "docker-archive-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(folder) + dest := path.Join(folder, "dest") + srcFolder := path.Join(folder, "src") + src := path.Join(folder, path.Join("src", "src")) + err = os.MkdirAll(srcFolder, 0740) + if err != nil { + t.Fatal(err) + } + err = os.MkdirAll(dest, 0740) + if err != nil { + t.Fatal(err) + } + ioutil.WriteFile(src, []byte("content"), 0777) + err = CopyWithTar(src, dest+"/") + if err != nil { + t.Fatalf("archiver.CopyFileWithTar shouldn't throw an error, %s.", err) + } + _, err = os.Stat(dest) + if err != nil { + t.Fatalf("Destination folder should contain the source file but did not.") + } +} + func TestTarFiles(t *testing.T) { // try without hardlinks if err := checkNoChanges(1000, false); err != nil { diff --git a/pkg/archive/changes_test.go b/pkg/archive/changes_test.go index 53ec575b6..290b2dd40 100644 --- a/pkg/archive/changes_test.go +++ b/pkg/archive/changes_test.go @@ -6,6 +6,7 @@ import ( "os/exec" "path" "sort" + "syscall" "testing" "time" ) @@ -91,17 +92,130 @@ func createSampleDir(t *testing.T, root string) { } } +func TestChangeString(t *testing.T) { + modifiyChange := Change{"change", ChangeModify} + toString := modifiyChange.String() + if toString != "C change" { + t.Fatalf("String() of a change with ChangeModifiy Kind should have been %s but was %s", "C change", toString) + } + addChange := Change{"change", ChangeAdd} + toString = addChange.String() + if toString != "A change" { + t.Fatalf("String() of a change with ChangeAdd Kind should have been %s but was %s", "A change", toString) + } + deleteChange := Change{"change", ChangeDelete} + toString = deleteChange.String() + if toString != "D change" { + t.Fatalf("String() of a change with ChangeDelete Kind should have been %s but was %s", "D change", toString) + } +} + +func TestChangesWithNoChanges(t *testing.T) { + rwLayer, err := ioutil.TempDir("", "docker-changes-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(rwLayer) + layer, err := ioutil.TempDir("", "docker-changes-test-layer") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(layer) + createSampleDir(t, layer) + changes, err := Changes([]string{layer}, rwLayer) + if err != nil { + t.Fatal(err) + } + if len(changes) != 0 { + t.Fatalf("Changes with no difference should have detect no changes, but detected %d", len(changes)) + } +} + +func TestChangesWithChanges(t *testing.T) { + rwLayer, err := ioutil.TempDir("", "docker-changes-test") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(rwLayer) + // Create a folder + dir1 := path.Join(rwLayer, "dir1") + os.MkdirAll(dir1, 0740) + deletedFile := path.Join(dir1, ".wh.file1-2") + ioutil.WriteFile(deletedFile, []byte{}, 0600) + modifiedFile := path.Join(dir1, "file1-1") + ioutil.WriteFile(modifiedFile, []byte{0x00}, 01444) + // Let's add a subfolder for a newFile + subfolder := path.Join(dir1, "subfolder") + os.MkdirAll(subfolder, 0740) + newFile := path.Join(subfolder, "newFile") + ioutil.WriteFile(newFile, []byte{}, 0740) + // Let's create folders that with have the role of layers with the same data + layer, err := ioutil.TempDir("", "docker-changes-test-layer") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(layer) + createSampleDir(t, layer) + os.MkdirAll(path.Join(layer, "dir1/subfolder"), 0740) + + // Let's modify modtime for dir1 to be sure it's the same for the two layer (to not having false positive) + fi, err := os.Stat(dir1) + if err != nil { + return + } + mtime := fi.ModTime() + stat := fi.Sys().(*syscall.Stat_t) + atime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)) + + layerDir1 := path.Join(layer, "dir1") + os.Chtimes(layerDir1, atime, mtime) + + changes, err := Changes([]string{layer}, rwLayer) + if err != nil { + t.Fatal(err) + } + + sort.Sort(changesByPath(changes)) + + expectedChanges := []Change{ + {"/dir1/file1-1", ChangeModify}, + {"/dir1/file1-2", ChangeDelete}, + {"/dir1/subfolder", ChangeModify}, + {"/dir1/subfolder/newFile", ChangeAdd}, + } + + for i := 0; i < max(len(changes), len(expectedChanges)); i++ { + if i >= len(expectedChanges) { + t.Fatalf("unexpected change %s\n", changes[i].String()) + } + if i >= len(changes) { + t.Fatalf("no change for expected change %s\n", expectedChanges[i].String()) + } + if changes[i].Path == expectedChanges[i].Path { + if changes[i] != expectedChanges[i] { + t.Fatalf("Wrong change for %s, expected %s, got %s\n", changes[i].Path, changes[i].String(), expectedChanges[i].String()) + } + } else if changes[i].Path < expectedChanges[i].Path { + t.Fatalf("unexpected change %s\n", changes[i].String()) + } else { + t.Fatalf("no change for expected change %s != %s\n", expectedChanges[i].String(), changes[i].String()) + } + } +} + // Create an directory, copy it, make sure we report no changes between the two func TestChangesDirsEmpty(t *testing.T) { src, err := ioutil.TempDir("", "docker-changes-test") if err != nil { t.Fatal(err) } + defer os.RemoveAll(src) createSampleDir(t, src) dst := src + "-copy" if err := copyDir(src, dst); err != nil { t.Fatal(err) } + defer os.RemoveAll(dst) changes, err := ChangesDirs(dst, src) if err != nil { t.Fatal(err) @@ -291,3 +405,41 @@ func TestApplyLayer(t *testing.T) { t.Fatalf("Unexpected differences after reapplying mutation: %v", changes2) } } + +func TestChangesSizeWithNoChanges(t *testing.T) { + size := ChangesSize("/tmp", nil) + if size != 0 { + t.Fatalf("ChangesSizes with no changes should be 0, was %d", size) + } +} + +func TestChangesSizeWithOnlyDeleteChanges(t *testing.T) { + changes := []Change{ + {Path: "deletedPath", Kind: ChangeDelete}, + } + size := ChangesSize("/tmp", changes) + if size != 0 { + t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size) + } +} + +func TestChangesSize(t *testing.T) { + parentPath, err := ioutil.TempDir("", "docker-changes-test") + defer os.RemoveAll(parentPath) + addition := path.Join(parentPath, "addition") + if err := ioutil.WriteFile(addition, []byte{0x01, 0x01, 0x01}, 0744); err != nil { + t.Fatal(err) + } + modification := path.Join(parentPath, "modification") + if err = ioutil.WriteFile(modification, []byte{0x01, 0x01, 0x01}, 0744); err != nil { + t.Fatal(err) + } + changes := []Change{ + {Path: "addition", Kind: ChangeAdd}, + {Path: "modification", Kind: ChangeModify}, + } + size := ChangesSize(parentPath, changes) + if size != 6 { + t.Fatalf("ChangesSizes with only delete changes should be 0, was %d", size) + } +} diff --git a/pkg/archive/wrap_test.go b/pkg/archive/wrap_test.go new file mode 100644 index 000000000..46ab36697 --- /dev/null +++ b/pkg/archive/wrap_test.go @@ -0,0 +1,98 @@ +package archive + +import ( + "archive/tar" + "bytes" + "io" + "testing" +) + +func TestGenerateEmptyFile(t *testing.T) { + archive, err := Generate("emptyFile") + if err != nil { + t.Fatal(err) + } + if archive == nil { + t.Fatal("The generated archive should not be nil.") + } + + expectedFiles := [][]string{ + {"emptyFile", ""}, + } + + tr := tar.NewReader(archive) + actualFiles := make([][]string, 0, 10) + i := 0 + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatal(err) + } + buf := new(bytes.Buffer) + buf.ReadFrom(tr) + content := buf.String() + actualFiles = append(actualFiles, []string{hdr.Name, content}) + i++ + } + if len(actualFiles) != len(expectedFiles) { + t.Fatalf("Number of expected file %d, got %d.", len(expectedFiles), len(actualFiles)) + } + for i := 0; i < len(expectedFiles); i++ { + actual := actualFiles[i] + expected := expectedFiles[i] + if actual[0] != expected[0] { + t.Fatalf("Expected name '%s', Actual name '%s'", expected[0], actual[0]) + } + if actual[1] != expected[1] { + t.Fatalf("Expected content '%s', Actual content '%s'", expected[1], actual[1]) + } + } +} + +func TestGenerateWithContent(t *testing.T) { + archive, err := Generate("file", "content") + if err != nil { + t.Fatal(err) + } + if archive == nil { + t.Fatal("The generated archive should not be nil.") + } + + expectedFiles := [][]string{ + {"file", "content"}, + } + + tr := tar.NewReader(archive) + actualFiles := make([][]string, 0, 10) + i := 0 + for { + hdr, err := tr.Next() + if err == io.EOF { + break + } + if err != nil { + t.Fatal(err) + } + buf := new(bytes.Buffer) + buf.ReadFrom(tr) + content := buf.String() + actualFiles = append(actualFiles, []string{hdr.Name, content}) + i++ + } + if len(actualFiles) != len(expectedFiles) { + t.Fatalf("Number of expected file %d, got %d.", len(expectedFiles), len(actualFiles)) + } + for i := 0; i < len(expectedFiles); i++ { + actual := actualFiles[i] + expected := expectedFiles[i] + if actual[0] != expected[0] { + t.Fatalf("Expected name '%s', Actual name '%s'", expected[0], actual[0]) + } + if actual[1] != expected[1] { + t.Fatalf("Expected content '%s', Actual content '%s'", expected[1], actual[1]) + } + } +} From 36fbf4b86469ca6fe3677d47c9a1976bcdd111e4 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 17 Apr 2015 15:30:22 -0700 Subject: [PATCH 624/999] Shallow clone using git to build images. Signed-off-by: David Calavera --- builder/job.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/builder/job.go b/builder/job.go index 115d89a4b..7ea1ae30b 100644 --- a/builder/job.go +++ b/builder/job.go @@ -114,7 +114,7 @@ func Build(d *daemon.Daemon, buildConfig *Config) error { } defer os.RemoveAll(root) - if output, err := exec.Command("git", "clone", "--recursive", buildConfig.RemoteURL, root).CombinedOutput(); err != nil { + if output, err := exec.Command("git", "clone", "--depth", "1", "--recursive", buildConfig.RemoteURL, root).CombinedOutput(); err != nil { return fmt.Errorf("Error trying to use git: %s (%s)", err, output) } From 9fb7204a41804131c2492f9d50d7451e123a05e5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 21 Apr 2015 10:58:38 -0700 Subject: [PATCH 625/999] Do not try to shallow git history when the protocol doesn't allow it. This only happens with the old git http dumb protocol, but that's what we use in our integration tests. We check the Content-Type header advertised in http requests to make sure the http transport is the git smart transport: See this commit as a reference: https://github.com/git/git/commit/4656bf47fca857df51b5d6f4b7b052192b3b2317 Signed-off-by: David Calavera --- builder/job.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/builder/job.go b/builder/job.go index 7ea1ae30b..8c031a8bd 100644 --- a/builder/job.go +++ b/builder/job.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "io/ioutil" + "net/http" "os" "os/exec" "strings" @@ -114,7 +115,9 @@ func Build(d *daemon.Daemon, buildConfig *Config) error { } defer os.RemoveAll(root) - if output, err := exec.Command("git", "clone", "--depth", "1", "--recursive", buildConfig.RemoteURL, root).CombinedOutput(); err != nil { + clone := cloneArgs(buildConfig.RemoteURL, root) + + if output, err := exec.Command("git", clone...).CombinedOutput(); err != nil { return fmt.Errorf("Error trying to use git: %s (%s)", err, output) } @@ -239,3 +242,21 @@ func Commit(d *daemon.Daemon, name string, c *daemon.ContainerCommitConfig) (str return img.ID, nil } + +func cloneArgs(remoteURL, root string) []string { + args := []string{"clone", "--recursive"} + shallow := true + + if strings.HasPrefix(remoteURL, "http") { + res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL)) + if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" { + shallow = false + } + } + + if shallow { + args = append(args, "--depth", "1") + } + + return append(args, remoteURL, root) +} From 3117bf3ef562bc43ef007d8afe3815c58aac6e85 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Tue, 21 Apr 2015 14:24:29 -0700 Subject: [PATCH 626/999] Test that we set the right arguments for git cloning depending on the transport. Signed-off-by: David Calavera --- builder/job_test.go | 56 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 builder/job_test.go diff --git a/builder/job_test.go b/builder/job_test.go new file mode 100644 index 000000000..79421a068 --- /dev/null +++ b/builder/job_test.go @@ -0,0 +1,56 @@ +package builder + +import ( + "fmt" + "net/http" + "net/http/httptest" + "net/url" + "reflect" + "testing" +) + +func TestCloneArgsSmartHttp(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + serverURL, _ := url.Parse(server.URL) + + serverURL.Path = "/repo.git" + gitURL := serverURL.String() + + mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) { + q := r.URL.Query().Get("service") + w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-advertisement", q)) + }) + + args := cloneArgs(gitURL, "/tmp") + exp := []string{"clone", "--recursive", "--depth", "1", gitURL, "/tmp"} + if !reflect.DeepEqual(args, exp) { + t.Fatalf("Expected %v, got %v", exp, args) + } +} + +func TestCloneArgsDumbHttp(t *testing.T) { + mux := http.NewServeMux() + server := httptest.NewServer(mux) + serverURL, _ := url.Parse(server.URL) + + serverURL.Path = "/repo.git" + gitURL := serverURL.String() + + mux.HandleFunc("/repo.git/info/refs", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + }) + + args := cloneArgs(gitURL, "/tmp") + exp := []string{"clone", "--recursive", gitURL, "/tmp"} + if !reflect.DeepEqual(args, exp) { + t.Fatalf("Expected %v, got %v", exp, args) + } +} +func TestCloneArgsGit(t *testing.T) { + args := cloneArgs("git://github.com/docker/docker", "/tmp") + exp := []string{"clone", "--recursive", "--depth", "1", "git://github.com/docker/docker", "/tmp"} + if !reflect.DeepEqual(args, exp) { + t.Fatalf("Expected %v, got %v", exp, args) + } +} From 8bd5a95e1e39541dfc5dc635c5c8e7604fe10028 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 23 Apr 2015 09:35:19 -0700 Subject: [PATCH 627/999] Document the extra `depth` argument in git contexts. Signed-off-by: David Calavera --- docs/sources/reference/commandline/cli.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index a87116204..26659c8ff 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -636,12 +636,13 @@ refer to any of the files in the context. For example, your build can use an [*ADD*](/reference/builder/#add) instruction to reference a file in the context. -The `URL` parameter can specify the location of a Git repository; in this -case, the repository is the context. The Git repository is recursively -cloned with its submodules. The system does a fresh `git clone -recursive` -in a temporary directory on your local host. Then, this clone is sent to -the Docker daemon as the context. Local clones give you the ability to -access private repositories using local user credentials, VPN's, and so forth. +The `URL` parameter can specify the location of a Git repository; +the repository acts as the build context. The system recursively clones the repository +and its submodules using a `git clone --depth 1 --recursive` command. +This command runs in a temporary directory on your local host. +After the command succeeds, the directory is sent to the Docker daemon as the context. +Local clones give you the ability to access private repositories using +local user credentials, VPN's, and so forth. Instead of specifying a context, you can pass a single Dockerfile in the `URL` or pipe the file in via `STDIN`. To pipe a Dockerfile from `STDIN`: From 1cfb307d70558110e8e88a7391ae0e6cf6ebf174 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Thu, 23 Apr 2015 10:19:34 -0700 Subject: [PATCH 628/999] Remove duplicated git clone logic. Signed-off-by: David Calavera --- api/client/build.go | 11 +----- builder/job.go | 32 +--------------- utils/git.go | 47 ++++++++++++++++++++++++ builder/job_test.go => utils/git_test.go | 2 +- 4 files changed, 51 insertions(+), 41 deletions(-) create mode 100644 utils/git.go rename builder/job_test.go => utils/git_test.go (98%) diff --git a/api/client/build.go b/api/client/build.go index 63cc63bc9..eb39058b6 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -95,20 +95,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } else { root := cmd.Arg(0) if urlutil.IsGitURL(root) { - remoteURL := cmd.Arg(0) - if !urlutil.IsGitTransport(remoteURL) { - remoteURL = "https://" + remoteURL - } - - root, err = ioutil.TempDir("", "docker-build-git") + root, err = utils.GitClone(root) 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 diff --git a/builder/job.go b/builder/job.go index 8c031a8bd..7991cba21 100644 --- a/builder/job.go +++ b/builder/job.go @@ -5,9 +5,7 @@ import ( "fmt" "io" "io/ioutil" - "net/http" "os" - "os/exec" "strings" "sync" @@ -23,6 +21,7 @@ import ( "github.com/docker/docker/pkg/urlutil" "github.com/docker/docker/registry" "github.com/docker/docker/runconfig" + "github.com/docker/docker/utils" ) // whitelist of commands allowed for a commit/import @@ -106,21 +105,12 @@ func Build(d *daemon.Daemon, buildConfig *Config) error { if buildConfig.RemoteURL == "" { context = ioutil.NopCloser(buildConfig.Context) } else if urlutil.IsGitURL(buildConfig.RemoteURL) { - if !urlutil.IsGitTransport(buildConfig.RemoteURL) { - buildConfig.RemoteURL = "https://" + buildConfig.RemoteURL - } - root, err := ioutil.TempDir("", "docker-build-git") + root, err := utils.GitClone(buildConfig.RemoteURL) if err != nil { return err } defer os.RemoveAll(root) - clone := cloneArgs(buildConfig.RemoteURL, root) - - if output, err := exec.Command("git", clone...).CombinedOutput(); err != nil { - return fmt.Errorf("Error trying to use git: %s (%s)", err, output) - } - c, err := archive.Tar(root, archive.Uncompressed) if err != nil { return err @@ -242,21 +232,3 @@ func Commit(d *daemon.Daemon, name string, c *daemon.ContainerCommitConfig) (str return img.ID, nil } - -func cloneArgs(remoteURL, root string) []string { - args := []string{"clone", "--recursive"} - shallow := true - - if strings.HasPrefix(remoteURL, "http") { - res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL)) - if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" { - shallow = false - } - } - - if shallow { - args = append(args, "--depth", "1") - } - - return append(args, remoteURL, root) -} diff --git a/utils/git.go b/utils/git.go new file mode 100644 index 000000000..18e002d18 --- /dev/null +++ b/utils/git.go @@ -0,0 +1,47 @@ +package utils + +import ( + "fmt" + "io/ioutil" + "net/http" + "os/exec" + "strings" + + "github.com/docker/docker/pkg/urlutil" +) + +func GitClone(remoteURL string) (string, error) { + if !urlutil.IsGitTransport(remoteURL) { + remoteURL = "https://" + remoteURL + } + root, err := ioutil.TempDir("", "docker-build-git") + if err != nil { + return "", err + } + + clone := cloneArgs(remoteURL, root) + + if output, err := exec.Command("git", clone...).CombinedOutput(); err != nil { + return "", fmt.Errorf("Error trying to use git: %s (%s)", err, output) + } + + return root, nil +} + +func cloneArgs(remoteURL, root string) []string { + args := []string{"clone", "--recursive"} + shallow := true + + if strings.HasPrefix(remoteURL, "http") { + res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL)) + if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" { + shallow = false + } + } + + if shallow { + args = append(args, "--depth", "1") + } + + return append(args, remoteURL, root) +} diff --git a/builder/job_test.go b/utils/git_test.go similarity index 98% rename from builder/job_test.go rename to utils/git_test.go index 79421a068..a82841ae1 100644 --- a/builder/job_test.go +++ b/utils/git_test.go @@ -1,4 +1,4 @@ -package builder +package utils import ( "fmt" From a9688cdca5577d6db65d76f38bcbe4c1e6f5994f Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Thu, 23 Apr 2015 14:39:31 -0700 Subject: [PATCH 629/999] Implement teardown removeAllImages Signed-off-by: Alexander Morozov --- integration-cli/check_test.go | 1 + integration-cli/docker_api_containers_test.go | 1 - integration-cli/docker_api_images_test.go | 2 - integration-cli/docker_cli_build_test.go | 186 ------------------ integration-cli/docker_cli_by_digest_test.go | 8 - integration-cli/docker_cli_commit_test.go | 16 -- integration-cli/docker_cli_create_test.go | 1 - integration-cli/docker_cli_events_test.go | 3 - .../docker_cli_export_import_test.go | 4 - integration-cli/docker_cli_history_test.go | 2 - integration-cli/docker_cli_images_test.go | 9 - integration-cli/docker_cli_import_test.go | 1 - integration-cli/docker_cli_ps_test.go | 1 - integration-cli/docker_cli_pull_test.go | 5 - integration-cli/docker_cli_push_test.go | 4 - integration-cli/docker_cli_rm_test.go | 1 - integration-cli/docker_cli_run_test.go | 8 - integration-cli/docker_cli_save_load_test.go | 7 - integration-cli/docker_cli_tag_test.go | 10 - integration-cli/docker_utils.go | 49 +++++ 20 files changed, 50 insertions(+), 269 deletions(-) diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go index 07bb93159..f6dbdb8a9 100644 --- a/integration-cli/check_test.go +++ b/integration-cli/check_test.go @@ -30,6 +30,7 @@ type DockerSuite struct { func (s *DockerSuite) TearDownTest(c *check.C) { deleteAllContainers() + deleteAllImages() s.TimerSuite.TearDownTest(c) } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 24efbb7a2..f14bd91c1 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -632,7 +632,6 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { if err := json.Unmarshal(b, &img); err != nil { c.Fatal(err) } - defer deleteImages(img.Id) cmd, err := inspectField(img.Id, "Config.Cmd") if err != nil { diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index a0f029d40..ac3ad55d7 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -30,7 +30,6 @@ func (s *DockerSuite) TestApiImagesFilter(c *check.C) { name := "utest:tag1" name2 := "utest/docker:tag2" name3 := "utest:5000/docker:tag3" - defer deleteImages(name, name2, name3) for _, n := range []string{name, name2, name3} { if out, err := exec.Command(dockerBinary, "tag", "busybox", n).CombinedOutput(); err != nil { c.Fatal(err, out) @@ -74,7 +73,6 @@ func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { c.Fatal(err) } id := strings.TrimSpace(out) - defer deleteImages("saveandload") status, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") c.Assert(status, check.Equals, http.StatusOK) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 72d15c177..6d6805aef 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -27,7 +27,6 @@ import ( func (s *DockerSuite) TestBuildJSONEmptyRun(c *check.C) { name := "testbuildjsonemptyrun" - defer deleteImages(name) _, err := buildImage( name, @@ -45,7 +44,6 @@ func (s *DockerSuite) TestBuildJSONEmptyRun(c *check.C) { func (s *DockerSuite) TestBuildEmptyWhitespace(c *check.C) { name := "testbuildemptywhitespace" - defer deleteImages(name) _, err := buildImage( name, @@ -65,7 +63,6 @@ func (s *DockerSuite) TestBuildEmptyWhitespace(c *check.C) { func (s *DockerSuite) TestBuildShCmdJSONEntrypoint(c *check.C) { name := "testbuildshcmdjsonentrypoint" - defer deleteImages(name) _, err := buildImage( name, @@ -99,7 +96,6 @@ func (s *DockerSuite) TestBuildShCmdJSONEntrypoint(c *check.C) { func (s *DockerSuite) TestBuildEnvironmentReplacementUser(c *check.C) { name := "testbuildenvironmentreplacement" - defer deleteImages(name) _, err := buildImage(name, ` FROM scratch @@ -123,7 +119,6 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementUser(c *check.C) { func (s *DockerSuite) TestBuildEnvironmentReplacementVolume(c *check.C) { name := "testbuildenvironmentreplacement" - defer deleteImages(name) _, err := buildImage(name, ` FROM scratch @@ -153,7 +148,6 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementVolume(c *check.C) { func (s *DockerSuite) TestBuildEnvironmentReplacementExpose(c *check.C) { name := "testbuildenvironmentreplacement" - defer deleteImages(name) _, err := buildImage(name, ` FROM scratch @@ -183,7 +177,6 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementExpose(c *check.C) { func (s *DockerSuite) TestBuildEnvironmentReplacementWorkdir(c *check.C) { name := "testbuildenvironmentreplacement" - defer deleteImages(name) _, err := buildImage(name, ` FROM busybox @@ -200,7 +193,6 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementWorkdir(c *check.C) { func (s *DockerSuite) TestBuildEnvironmentReplacementAddCopy(c *check.C) { name := "testbuildenvironmentreplacement" - defer deleteImages(name) ctx, err := fakeContext(` FROM scratch @@ -236,8 +228,6 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementAddCopy(c *check.C) { func (s *DockerSuite) TestBuildEnvironmentReplacementEnv(c *check.C) { name := "testbuildenvironmentreplacement" - defer deleteImages(name) - _, err := buildImage(name, ` FROM busybox @@ -305,8 +295,6 @@ func (s *DockerSuite) TestBuildEnvironmentReplacementEnv(c *check.C) { func (s *DockerSuite) TestBuildHandleEscapes(c *check.C) { name := "testbuildhandleescapes" - defer deleteImages(name) - _, err := buildImage(name, ` FROM scratch @@ -395,8 +383,6 @@ func (s *DockerSuite) TestBuildOnBuildLowercase(c *check.C) { name := "testbuildonbuildlowercase" name2 := "testbuildonbuildlowercase2" - defer deleteImages(name, name2) - _, err := buildImage(name, ` FROM busybox @@ -427,7 +413,6 @@ func (s *DockerSuite) TestBuildOnBuildLowercase(c *check.C) { func (s *DockerSuite) TestBuildEnvEscapes(c *check.C) { name := "testbuildenvescapes" - defer deleteImages(name) _, err := buildImage(name, ` FROM busybox @@ -450,7 +435,6 @@ func (s *DockerSuite) TestBuildEnvEscapes(c *check.C) { func (s *DockerSuite) TestBuildEnvOverwrite(c *check.C) { name := "testbuildenvoverwrite" - defer deleteImages(name) _, err := buildImage(name, ` @@ -478,8 +462,6 @@ func (s *DockerSuite) TestBuildEnvOverwrite(c *check.C) { func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainerInSourceImage(c *check.C) { name := "testbuildonbuildforbiddenmaintainerinsourceimage" - defer deleteImages("onbuild") - defer deleteImages(name) createCmd := exec.Command(dockerBinary, "create", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(createCmd) @@ -510,8 +492,6 @@ func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainerInSourceImage(c *check. func (s *DockerSuite) TestBuildOnBuildForbiddenFromInSourceImage(c *check.C) { name := "testbuildonbuildforbiddenfrominsourceimage" - defer deleteImages("onbuild") - defer deleteImages(name) createCmd := exec.Command(dockerBinary, "create", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(createCmd) @@ -542,8 +522,6 @@ func (s *DockerSuite) TestBuildOnBuildForbiddenFromInSourceImage(c *check.C) { func (s *DockerSuite) TestBuildOnBuildForbiddenChainedInSourceImage(c *check.C) { name := "testbuildonbuildforbiddenchainedinsourceimage" - defer deleteImages("onbuild") - defer deleteImages(name) createCmd := exec.Command(dockerBinary, "create", "busybox", "true") out, _, _, err := runCommandWithStdoutStderr(createCmd) @@ -576,9 +554,6 @@ func (s *DockerSuite) TestBuildOnBuildCmdEntrypointJSON(c *check.C) { name1 := "onbuildcmd" name2 := "onbuildgenerated" - defer deleteImages(name2) - defer deleteImages(name1) - _, err := buildImage(name1, ` FROM busybox ONBUILD CMD ["hello world"] @@ -611,9 +586,6 @@ func (s *DockerSuite) TestBuildOnBuildEntrypointJSON(c *check.C) { name1 := "onbuildcmd" name2 := "onbuildgenerated" - defer deleteImages(name2) - defer deleteImages(name1) - _, err := buildImage(name1, ` FROM busybox ONBUILD ENTRYPOINT ["echo"]`, @@ -642,7 +614,6 @@ ONBUILD ENTRYPOINT ["echo"]`, func (s *DockerSuite) TestBuildCacheADD(c *check.C) { name := "testbuildtwoimageswithadd" - defer deleteImages(name) server, err := fakeStorage(map[string]string{ "robots.txt": "hello", "index.html": "world", @@ -677,7 +648,6 @@ func (s *DockerSuite) TestBuildCacheADD(c *check.C) { func (s *DockerSuite) TestBuildLastModified(c *check.C) { name := "testbuildlastmodified" - defer deleteImages(name) server, err := fakeStorage(map[string]string{ "file": "hello", @@ -743,7 +713,6 @@ RUN ls -le /file` func (s *DockerSuite) TestBuildSixtySteps(c *check.C) { name := "foobuildsixtysteps" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\n"+strings.Repeat("ADD foo /\n", 60), map[string]string{ "foo": "test1", @@ -760,7 +729,6 @@ func (s *DockerSuite) TestBuildSixtySteps(c *check.C) { func (s *DockerSuite) TestBuildAddSingleFileToRoot(c *check.C) { name := "testaddimg" - defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -786,7 +754,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte // Issue #3960: "ADD src ." hangs func (s *DockerSuite) TestBuildAddSingleFileToWorkdir(c *check.C) { name := "testaddsinglefiletoworkdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox ADD test_file .`, map[string]string{ @@ -813,7 +780,6 @@ ADD test_file .`, func (s *DockerSuite) TestBuildAddSingleFileToExistDir(c *check.C) { name := "testaddsinglefiletoexistdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -847,7 +813,6 @@ func (s *DockerSuite) TestBuildCopyAddMultipleFiles(c *check.C) { defer server.Close() name := "testcopymultiplefilestofile" - defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -884,7 +849,6 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' func (s *DockerSuite) TestBuildAddMultipleFilesToFile(c *check.C) { name := "testaddmultiplefilestofile" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD file1.txt file2.txt test `, @@ -906,7 +870,6 @@ func (s *DockerSuite) TestBuildAddMultipleFilesToFile(c *check.C) { func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFile(c *check.C) { name := "testjsonaddmultiplefilestofile" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD ["file1.txt", "file2.txt", "test"] `, @@ -928,7 +891,6 @@ func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFile(c *check.C) { func (s *DockerSuite) TestBuildAddMultipleFilesToFileWild(c *check.C) { name := "testaddmultiplefilestofilewild" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD file*.txt test `, @@ -950,7 +912,6 @@ func (s *DockerSuite) TestBuildAddMultipleFilesToFileWild(c *check.C) { func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFileWild(c *check.C) { name := "testjsonaddmultiplefilestofilewild" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD ["file*.txt", "test"] `, @@ -972,7 +933,6 @@ func (s *DockerSuite) TestBuildJSONAddMultipleFilesToFileWild(c *check.C) { func (s *DockerSuite) TestBuildCopyMultipleFilesToFile(c *check.C) { name := "testcopymultiplefilestofile" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch COPY file1.txt file2.txt test `, @@ -994,7 +954,6 @@ func (s *DockerSuite) TestBuildCopyMultipleFilesToFile(c *check.C) { func (s *DockerSuite) TestBuildJSONCopyMultipleFilesToFile(c *check.C) { name := "testjsoncopymultiplefilestofile" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch COPY ["file1.txt", "file2.txt", "test"] `, @@ -1016,7 +975,6 @@ func (s *DockerSuite) TestBuildJSONCopyMultipleFilesToFile(c *check.C) { func (s *DockerSuite) TestBuildAddFileWithWhitespace(c *check.C) { name := "testaddfilewithwhitespace" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN mkdir "/test dir" RUN mkdir "/test_dir" @@ -1052,7 +1010,6 @@ RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, func (s *DockerSuite) TestBuildCopyFileWithWhitespace(c *check.C) { name := "testcopyfilewithwhitespace" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN mkdir "/test dir" RUN mkdir "/test_dir" @@ -1088,7 +1045,6 @@ RUN [ $(cat "/test dir/test_file6") = 'test6' ]`, func (s *DockerSuite) TestBuildAddMultipleFilesToFileWithWhitespace(c *check.C) { name := "testaddmultiplefilestofilewithwhitespace" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox ADD [ "test file1", "test file2", "test" ] `, @@ -1110,7 +1066,6 @@ func (s *DockerSuite) TestBuildAddMultipleFilesToFileWithWhitespace(c *check.C) func (s *DockerSuite) TestBuildCopyMultipleFilesToFileWithWhitespace(c *check.C) { name := "testcopymultiplefilestofilewithwhitespace" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox COPY [ "test file1", "test file2", "test" ] `, @@ -1132,7 +1087,6 @@ func (s *DockerSuite) TestBuildCopyMultipleFilesToFileWithWhitespace(c *check.C) func (s *DockerSuite) TestBuildCopyWildcard(c *check.C) { name := "testcopywildcard" - defer deleteImages(name) server, err := fakeStorage(map[string]string{ "robots.txt": "hello", "index.html": "world", @@ -1183,7 +1137,6 @@ func (s *DockerSuite) TestBuildCopyWildcard(c *check.C) { func (s *DockerSuite) TestBuildCopyWildcardNoFind(c *check.C) { name := "testcopywildcardnofind" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox COPY file*.txt /tmp/ `, nil) @@ -1204,7 +1157,6 @@ func (s *DockerSuite) TestBuildCopyWildcardNoFind(c *check.C) { func (s *DockerSuite) TestBuildCopyWildcardCache(c *check.C) { name := "testcopywildcardcache" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox COPY file1.txt /tmp/`, map[string]string{ @@ -1238,7 +1190,6 @@ func (s *DockerSuite) TestBuildCopyWildcardCache(c *check.C) { func (s *DockerSuite) TestBuildAddSingleFileToNonExistingDir(c *check.C) { name := "testaddsinglefiletononexistingdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1264,7 +1215,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, func (s *DockerSuite) TestBuildAddDirContentToRoot(c *check.C) { name := "testadddircontenttoroot" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1288,7 +1238,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, func (s *DockerSuite) TestBuildAddDirContentToExistingDir(c *check.C) { name := "testadddircontenttoexistingdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1314,7 +1263,6 @@ RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`, func (s *DockerSuite) TestBuildAddWholeDirToRoot(c *check.C) { name := "testaddwholedirtoroot" - defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1342,7 +1290,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte // Testing #5941 func (s *DockerSuite) TestBuildAddEtcToRoot(c *check.C) { name := "testaddetctoroot" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD . /`, map[string]string{ @@ -1361,7 +1308,6 @@ ADD . /`, // Testing #9401 func (s *DockerSuite) TestBuildAddPreservesFilesSpecialBits(c *check.C) { name := "testaddpreservesfilesspecialbits" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox ADD suidbin /usr/bin/suidbin RUN chmod 4755 /usr/bin/suidbin @@ -1384,7 +1330,6 @@ RUN [ $(ls -l /usr/bin/suidbin | awk '{print $1}') = '-rwsr-xr-x' ]`, func (s *DockerSuite) TestBuildCopySingleFileToRoot(c *check.C) { name := "testcopysinglefiletoroot" - defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1410,7 +1355,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte // Issue #3960: "ADD src ." hangs - adapted for COPY func (s *DockerSuite) TestBuildCopySingleFileToWorkdir(c *check.C) { name := "testcopysinglefiletoworkdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox COPY test_file .`, map[string]string{ @@ -1437,7 +1381,6 @@ COPY test_file .`, func (s *DockerSuite) TestBuildCopySingleFileToExistDir(c *check.C) { name := "testcopysinglefiletoexistdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1463,7 +1406,6 @@ RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' func (s *DockerSuite) TestBuildCopySingleFileToNonExistDir(c *check.C) { name := "testcopysinglefiletononexistdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1488,7 +1430,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, func (s *DockerSuite) TestBuildCopyDirContentToRoot(c *check.C) { name := "testcopydircontenttoroot" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1512,7 +1453,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, func (s *DockerSuite) TestBuildCopyDirContentToExistDir(c *check.C) { name := "testcopydircontenttoexistdir" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1538,7 +1478,6 @@ RUN [ $(ls -l /exists/test_file | awk '{print $3":"$4}') = 'root:root' ]`, func (s *DockerSuite) TestBuildCopyWholeDirToRoot(c *check.C) { name := "testcopywholedirtoroot" - defer deleteImages(name) ctx, err := fakeContext(fmt.Sprintf(`FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd RUN echo 'dockerio:x:1001:' >> /etc/group @@ -1565,7 +1504,6 @@ RUN [ $(ls -l /exists | awk '{print $3":"$4}') = 'dockerio:dockerio' ]`, expecte func (s *DockerSuite) TestBuildCopyEtcToRoot(c *check.C) { name := "testcopyetctoroot" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch COPY . /`, map[string]string{ @@ -1583,7 +1521,6 @@ COPY . /`, func (s *DockerSuite) TestBuildCopyDisallowRemote(c *check.C) { name := "testcopydisallowremote" - defer deleteImages(name) _, out, err := buildImageWithOut(name, `FROM scratch COPY https://index.docker.io/robots.txt /`, true) @@ -1604,7 +1541,6 @@ func (s *DockerSuite) TestBuildAddBadLinks(c *check.C) { var ( name = "test-link-absolute" ) - defer deleteImages(name) ctx, err := fakeContext(dockerfile, nil) if err != nil { c.Fatal(err) @@ -1692,7 +1628,6 @@ func (s *DockerSuite) TestBuildAddBadLinksVolume(c *check.C) { name = "test-link-absolute-volume" dockerfile = "" ) - defer deleteImages(name) tempDir, err := ioutil.TempDir("", "test-link-absolute-volume-temp-") if err != nil { @@ -1737,7 +1672,6 @@ func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) { { name := "testbuildinaccessiblefiles" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{"fileWithoutReadAccess": "foo"}) if err != nil { c.Fatal(err) @@ -1770,7 +1704,6 @@ func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) { } { name := "testbuildinaccessibledirectory" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{"directoryWeCantStat/bar": "foo"}) if err != nil { c.Fatal(err) @@ -1809,7 +1742,6 @@ func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) { } { name := "testlinksok" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", nil) if err != nil { c.Fatal(err) @@ -1829,7 +1761,6 @@ func (s *DockerSuite) TestBuildWithInaccessibleFilesInContext(c *check.C) { } { name := "testbuildignoredinaccessible" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD . /foo/", map[string]string{ "directoryWeCantStat/bar": "foo", @@ -1867,7 +1798,6 @@ func (s *DockerSuite) TestBuildForceRm(c *check.C) { c.Fatalf("failed to get the container count: %s", err) } name := "testbuildforcerm" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nRUN true\nRUN thiswillfail", nil) if err != nil { c.Fatal(err) @@ -1903,7 +1833,6 @@ func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { defer wg.Wait() name := "testbuildcancelation" - defer deleteImages(name) // (Note: one year, will never finish) ctx, err := fakeContext("FROM busybox\nRUN sleep 31536000", nil) @@ -2018,7 +1947,6 @@ func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { func (s *DockerSuite) TestBuildRm(c *check.C) { name := "testbuildrm" - defer deleteImages(name) ctx, err := fakeContext("FROM scratch\nADD foo /\nADD foo /", map[string]string{"foo": "bar"}) if err != nil { c.Fatal(err) @@ -2112,7 +2040,6 @@ func (s *DockerSuite) TestBuildWithVolumes(c *check.C) { "/test8]": emptyMap, } ) - defer deleteImages(name) _, err := buildImage(name, `FROM scratch VOLUME /test1 @@ -2146,7 +2073,6 @@ func (s *DockerSuite) TestBuildWithVolumes(c *check.C) { func (s *DockerSuite) TestBuildMaintainer(c *check.C) { name := "testbuildmaintainer" expected := "dockerio" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch MAINTAINER dockerio`, @@ -2166,7 +2092,6 @@ func (s *DockerSuite) TestBuildMaintainer(c *check.C) { func (s *DockerSuite) TestBuildUser(c *check.C) { name := "testbuilduser" expected := "dockerio" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd @@ -2188,7 +2113,6 @@ func (s *DockerSuite) TestBuildUser(c *check.C) { func (s *DockerSuite) TestBuildRelativeWorkdir(c *check.C) { name := "testbuildrelativeworkdir" expected := "/test2/test3" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN [ "$PWD" = '/' ] @@ -2214,7 +2138,6 @@ func (s *DockerSuite) TestBuildRelativeWorkdir(c *check.C) { func (s *DockerSuite) TestBuildWorkdirWithEnvVariables(c *check.C) { name := "testbuildworkdirwithenvvariables" expected := "/test1/test2" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ENV DIRPATH /test1 @@ -2236,7 +2159,6 @@ func (s *DockerSuite) TestBuildWorkdirWithEnvVariables(c *check.C) { func (s *DockerSuite) TestBuildRelativeCopy(c *check.C) { name := "testbuildrelativecopy" - defer deleteImages(name) dockerfile := ` FROM busybox WORKDIR /test1 @@ -2276,7 +2198,6 @@ func (s *DockerSuite) TestBuildRelativeCopy(c *check.C) { func (s *DockerSuite) TestBuildEnv(c *check.C) { name := "testbuildenv" expected := "[PATH=/test:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PORT=2375]" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ENV PATH /test:$PATH @@ -2299,7 +2220,6 @@ func (s *DockerSuite) TestBuildContextCleanup(c *check.C) { testRequires(c, SameHostDaemon) name := "testbuildcontextcleanup" - defer deleteImages(name) entries, err := ioutil.ReadDir("/var/lib/docker/tmp") if err != nil { c.Fatalf("failed to list contents of tmp dir: %s", err) @@ -2325,7 +2245,6 @@ func (s *DockerSuite) TestBuildContextCleanupFailedBuild(c *check.C) { testRequires(c, SameHostDaemon) name := "testbuildcontextcleanup" - defer deleteImages(name) entries, err := ioutil.ReadDir("/var/lib/docker/tmp") if err != nil { c.Fatalf("failed to list contents of tmp dir: %s", err) @@ -2350,7 +2269,6 @@ func (s *DockerSuite) TestBuildContextCleanupFailedBuild(c *check.C) { func (s *DockerSuite) TestBuildCmd(c *check.C) { name := "testbuildcmd" expected := "{[/bin/echo Hello World]}" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch CMD ["/bin/echo", "Hello World"]`, @@ -2370,7 +2288,6 @@ func (s *DockerSuite) TestBuildCmd(c *check.C) { func (s *DockerSuite) TestBuildExpose(c *check.C) { name := "testbuildexpose" expected := "map[2375/tcp:{}]" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch EXPOSE 2375`, @@ -2413,7 +2330,6 @@ func (s *DockerSuite) TestBuildExposeMorePorts(c *check.C) { tmpl.Execute(buf, portList) name := "testbuildexpose" - defer deleteImages(name) _, err := buildImage(name, buf.String(), true) if err != nil { c.Fatal(err) @@ -2458,7 +2374,6 @@ func (s *DockerSuite) TestBuildExposeOrder(c *check.C) { id1 := buildID("testbuildexpose1", "80 2375") id2 := buildID("testbuildexpose2", "2375 80") - defer deleteImages("testbuildexpose1", "testbuildexpose2") if id1 != id2 { c.Errorf("EXPOSE should invalidate the cache only when ports actually changed") } @@ -2467,7 +2382,6 @@ func (s *DockerSuite) TestBuildExposeOrder(c *check.C) { func (s *DockerSuite) TestBuildExposeUpperCaseProto(c *check.C) { name := "testbuildexposeuppercaseproto" expected := "map[5678/udp:{}]" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch EXPOSE 5678/UDP`, @@ -2488,7 +2402,6 @@ func (s *DockerSuite) TestBuildExposeHostPort(c *check.C) { // start building docker file with ip:hostPort:containerPort name := "testbuildexpose" expected := "map[5678/tcp:{}]" - defer deleteImages(name) _, out, err := buildImageWithOut(name, `FROM scratch EXPOSE 192.168.1.2:2375:5678`, @@ -2513,7 +2426,6 @@ func (s *DockerSuite) TestBuildExposeHostPort(c *check.C) { func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { name := "testbuildentrypointinheritance" name2 := "testbuildentrypointinheritance2" - defer deleteImages(name, name2) _, err := buildImage(name, `FROM busybox @@ -2554,7 +2466,6 @@ func (s *DockerSuite) TestBuildEmptyEntrypointInheritance(c *check.C) { func (s *DockerSuite) TestBuildEmptyEntrypoint(c *check.C) { name := "testbuildentrypoint" - defer deleteImages(name) expected := "{[]}" _, err := buildImage(name, @@ -2577,7 +2488,6 @@ func (s *DockerSuite) TestBuildEmptyEntrypoint(c *check.C) { func (s *DockerSuite) TestBuildEntrypoint(c *check.C) { name := "testbuildentrypoint" expected := "{[/bin/echo]}" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch ENTRYPOINT ["/bin/echo"]`, @@ -2617,7 +2527,6 @@ func (s *DockerSuite) TestBuildOnBuildLimitedInheritence(c *check.C) { if err != nil { c.Fatalf("build failed to complete: %s, %v", out1, err) } - defer deleteImages(name1) } { name2 := "testonbuildtrigger2" @@ -2634,7 +2543,6 @@ func (s *DockerSuite) TestBuildOnBuildLimitedInheritence(c *check.C) { if err != nil { c.Fatalf("build failed to complete: %s, %v", out2, err) } - defer deleteImages(name2) } { name3 := "testonbuildtrigger3" @@ -2652,7 +2560,6 @@ func (s *DockerSuite) TestBuildOnBuildLimitedInheritence(c *check.C) { c.Fatalf("build failed to complete: %s, %v", out3, err) } - defer deleteImages(name3) } // ONBUILD should be run in second build. @@ -2669,7 +2576,6 @@ func (s *DockerSuite) TestBuildOnBuildLimitedInheritence(c *check.C) { func (s *DockerSuite) TestBuildWithCache(c *check.C) { name := "testbuildwithcache" - defer deleteImages(name) id1, err := buildImage(name, `FROM scratch MAINTAINER dockerio @@ -2696,7 +2602,6 @@ func (s *DockerSuite) TestBuildWithCache(c *check.C) { func (s *DockerSuite) TestBuildWithoutCache(c *check.C) { name := "testbuildwithoutcache" name2 := "testbuildwithoutcache2" - defer deleteImages(name, name2) id1, err := buildImage(name, `FROM scratch MAINTAINER dockerio @@ -2723,8 +2628,6 @@ func (s *DockerSuite) TestBuildWithoutCache(c *check.C) { func (s *DockerSuite) TestBuildConditionalCache(c *check.C) { name := "testbuildconditionalcache" - name2 := "testbuildconditionalcache2" - defer deleteImages(name, name2) dockerfile := ` FROM busybox @@ -2761,13 +2664,11 @@ func (s *DockerSuite) TestBuildConditionalCache(c *check.C) { if id3 != id2 { c.Fatal("Should have used the cache") } - } func (s *DockerSuite) TestBuildADDLocalFileWithCache(c *check.C) { name := "testbuildaddlocalfilewithcache" name2 := "testbuildaddlocalfilewithcache2" - defer deleteImages(name, name2) dockerfile := ` FROM busybox MAINTAINER dockerio @@ -2796,7 +2697,6 @@ func (s *DockerSuite) TestBuildADDLocalFileWithCache(c *check.C) { func (s *DockerSuite) TestBuildADDMultipleLocalFileWithCache(c *check.C) { name := "testbuildaddmultiplelocalfilewithcache" name2 := "testbuildaddmultiplelocalfilewithcache2" - defer deleteImages(name, name2) dockerfile := ` FROM busybox MAINTAINER dockerio @@ -2825,7 +2725,6 @@ func (s *DockerSuite) TestBuildADDMultipleLocalFileWithCache(c *check.C) { func (s *DockerSuite) TestBuildADDLocalFileWithoutCache(c *check.C) { name := "testbuildaddlocalfilewithoutcache" name2 := "testbuildaddlocalfilewithoutcache2" - defer deleteImages(name, name2) dockerfile := ` FROM busybox MAINTAINER dockerio @@ -2854,7 +2753,6 @@ func (s *DockerSuite) TestBuildADDLocalFileWithoutCache(c *check.C) { func (s *DockerSuite) TestBuildCopyDirButNotFile(c *check.C) { name := "testbuildcopydirbutnotfile" name2 := "testbuildcopydirbutnotfile2" - defer deleteImages(name, name2) dockerfile := ` FROM scratch COPY dir /tmp/` @@ -2888,7 +2786,6 @@ func (s *DockerSuite) TestBuildADDCurrentDirWithCache(c *check.C) { name3 := name + "3" name4 := name + "4" name5 := name + "5" - defer deleteImages(name, name2, name3, name4, name5) dockerfile := ` FROM scratch MAINTAINER dockerio @@ -2950,7 +2847,6 @@ func (s *DockerSuite) TestBuildADDCurrentDirWithCache(c *check.C) { func (s *DockerSuite) TestBuildADDCurrentDirWithoutCache(c *check.C) { name := "testbuildaddcurrentdirwithoutcache" name2 := "testbuildaddcurrentdirwithoutcache2" - defer deleteImages(name, name2) dockerfile := ` FROM scratch MAINTAINER dockerio @@ -2977,7 +2873,6 @@ func (s *DockerSuite) TestBuildADDCurrentDirWithoutCache(c *check.C) { func (s *DockerSuite) TestBuildADDRemoteFileWithCache(c *check.C) { name := "testbuildaddremotefilewithcache" - defer deleteImages(name) server, err := fakeStorage(map[string]string{ "baz": "hello", }) @@ -3010,7 +2905,6 @@ func (s *DockerSuite) TestBuildADDRemoteFileWithCache(c *check.C) { func (s *DockerSuite) TestBuildADDRemoteFileWithoutCache(c *check.C) { name := "testbuildaddremotefilewithoutcache" name2 := "testbuildaddremotefilewithoutcache2" - defer deleteImages(name, name2) server, err := fakeStorage(map[string]string{ "baz": "hello", }) @@ -3046,8 +2940,6 @@ func (s *DockerSuite) TestBuildADDRemoteFileMTime(c *check.C) { name3 := name + "3" name4 := name + "4" - defer deleteImages(name, name2, name3, name4) - files := map[string]string{"baz": "hello"} server, err := fakeStorage(files) if err != nil { @@ -3115,7 +3007,6 @@ func (s *DockerSuite) TestBuildADDRemoteFileMTime(c *check.C) { func (s *DockerSuite) TestBuildADDLocalAndRemoteFilesWithCache(c *check.C) { name := "testbuildaddlocalandremotefilewithcache" - defer deleteImages(name) server, err := fakeStorage(map[string]string{ "baz": "hello", }) @@ -3167,7 +3058,6 @@ CMD ["cat", "/foo"]`, } name := "contexttar" buildCmd := exec.Command(dockerBinary, "build", "-t", name, "-") - defer deleteImages(name) buildCmd.Stdin = context if out, _, err := runCommandWithOutput(buildCmd); err != nil { @@ -3194,15 +3084,12 @@ func (s *DockerSuite) TestBuildNoContext(c *check.C) { if out, _ := dockerCmd(c, "run", "--rm", "nocontext"); out != "ok\n" { c.Fatalf("run produced invalid output: %q, expected %q", out, "ok") } - - deleteImages("nocontext") } // TODO: TestCaching func (s *DockerSuite) TestBuildADDLocalAndRemoteFilesWithoutCache(c *check.C) { name := "testbuildaddlocalandremotefilewithoutcache" name2 := "testbuildaddlocalandremotefilewithoutcache2" - defer deleteImages(name, name2) server, err := fakeStorage(map[string]string{ "baz": "hello", }) @@ -3237,7 +3124,6 @@ func (s *DockerSuite) TestBuildADDLocalAndRemoteFilesWithoutCache(c *check.C) { func (s *DockerSuite) TestBuildWithVolumeOwnership(c *check.C) { name := "testbuildimg" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox:latest @@ -3269,7 +3155,6 @@ func (s *DockerSuite) TestBuildWithVolumeOwnership(c *check.C) { // utilizing cache func (s *DockerSuite) TestBuildEntrypointRunCleanup(c *check.C) { name := "testbuildcmdcleanup" - defer deleteImages(name) if _, err := buildImage(name, `FROM busybox RUN echo "hello"`, @@ -3303,7 +3188,6 @@ func (s *DockerSuite) TestBuildEntrypointRunCleanup(c *check.C) { func (s *DockerSuite) TestBuildForbiddenContextPath(c *check.C) { name := "testbuildforbidpath" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD ../../ test/ `, @@ -3325,7 +3209,6 @@ func (s *DockerSuite) TestBuildForbiddenContextPath(c *check.C) { func (s *DockerSuite) TestBuildADDFileNotFound(c *check.C) { name := "testbuildaddnotfound" - defer deleteImages(name) ctx, err := fakeContext(`FROM scratch ADD foo /usr/local/bar`, map[string]string{"bar": "hello"}) @@ -3344,7 +3227,6 @@ func (s *DockerSuite) TestBuildADDFileNotFound(c *check.C) { func (s *DockerSuite) TestBuildInheritance(c *check.C) { name := "testbuildinheritance" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch @@ -3384,7 +3266,6 @@ func (s *DockerSuite) TestBuildInheritance(c *check.C) { func (s *DockerSuite) TestBuildFails(c *check.C) { name := "testbuildfails" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN sh -c "exit 23"`, @@ -3400,7 +3281,6 @@ func (s *DockerSuite) TestBuildFails(c *check.C) { func (s *DockerSuite) TestBuildFailsDockerfileEmpty(c *check.C) { name := "testbuildfails" - defer deleteImages(name) _, err := buildImage(name, ``, true) if err != nil { if !strings.Contains(err.Error(), "The Dockerfile (Dockerfile) cannot be empty") { @@ -3413,7 +3293,6 @@ func (s *DockerSuite) TestBuildFailsDockerfileEmpty(c *check.C) { func (s *DockerSuite) TestBuildOnBuild(c *check.C) { name := "testbuildonbuild" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ONBUILD RUN touch foobar`, @@ -3432,7 +3311,6 @@ func (s *DockerSuite) TestBuildOnBuild(c *check.C) { func (s *DockerSuite) TestBuildOnBuildForbiddenChained(c *check.C) { name := "testbuildonbuildforbiddenchained" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ONBUILD ONBUILD RUN touch foobar`, @@ -3448,7 +3326,6 @@ func (s *DockerSuite) TestBuildOnBuildForbiddenChained(c *check.C) { func (s *DockerSuite) TestBuildOnBuildForbiddenFrom(c *check.C) { name := "testbuildonbuildforbiddenfrom" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ONBUILD FROM scratch`, @@ -3464,7 +3341,6 @@ func (s *DockerSuite) TestBuildOnBuildForbiddenFrom(c *check.C) { func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainer(c *check.C) { name := "testbuildonbuildforbiddenmaintainer" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ONBUILD MAINTAINER docker.io`, @@ -3481,7 +3357,6 @@ func (s *DockerSuite) TestBuildOnBuildForbiddenMaintainer(c *check.C) { // gh #2446 func (s *DockerSuite) TestBuildAddToSymlinkDest(c *check.C) { name := "testbuildaddtosymlinkdest" - defer deleteImages(name) ctx, err := fakeContext(`FROM busybox RUN mkdir /foo RUN ln -s /foo /bar @@ -3502,7 +3377,6 @@ func (s *DockerSuite) TestBuildAddToSymlinkDest(c *check.C) { func (s *DockerSuite) TestBuildEscapeWhitespace(c *check.C) { name := "testbuildescaping" - defer deleteImages(name) _, err := buildImage(name, ` FROM busybox @@ -3526,7 +3400,6 @@ docker.com>" func (s *DockerSuite) TestBuildVerifyIntString(c *check.C) { // Verify that strings that look like ints are still passed as strings name := "testbuildstringing" - defer deleteImages(name) _, err := buildImage(name, ` FROM busybox @@ -3546,7 +3419,6 @@ func (s *DockerSuite) TestBuildVerifyIntString(c *check.C) { func (s *DockerSuite) TestBuildDockerignore(c *check.C) { name := "testbuilddockerignore" - defer deleteImages(name) dockerfile := ` FROM busybox ADD . /bla @@ -3576,7 +3448,6 @@ func (s *DockerSuite) TestBuildDockerignore(c *check.C) { func (s *DockerSuite) TestBuildDockerignoreCleanPaths(c *check.C) { name := "testbuilddockerignorecleanpaths" - defer deleteImages(name) dockerfile := ` FROM busybox ADD . /tmp/ @@ -3598,7 +3469,6 @@ func (s *DockerSuite) TestBuildDockerignoreCleanPaths(c *check.C) { func (s *DockerSuite) TestBuildDockerignoringDockerfile(c *check.C) { name := "testbuilddockerignoredockerfile" - defer deleteImages(name) dockerfile := ` FROM busybox ADD . /tmp/ @@ -3627,7 +3497,6 @@ func (s *DockerSuite) TestBuildDockerignoringDockerfile(c *check.C) { func (s *DockerSuite) TestBuildDockerignoringRenamedDockerfile(c *check.C) { name := "testbuilddockerignoredockerfile" - defer deleteImages(name) dockerfile := ` FROM busybox ADD . /tmp/ @@ -3658,7 +3527,6 @@ func (s *DockerSuite) TestBuildDockerignoringRenamedDockerfile(c *check.C) { func (s *DockerSuite) TestBuildDockerignoringDockerignore(c *check.C) { name := "testbuilddockerignoredockerignore" - defer deleteImages(name) dockerfile := ` FROM busybox ADD . /tmp/ @@ -3682,7 +3550,6 @@ func (s *DockerSuite) TestBuildDockerignoreTouchDockerfile(c *check.C) { var id2 string name := "testbuilddockerignoretouchdockerfile" - defer deleteImages(name) dockerfile := ` FROM busybox ADD . /tmp/` @@ -3732,7 +3599,6 @@ func (s *DockerSuite) TestBuildDockerignoreTouchDockerfile(c *check.C) { func (s *DockerSuite) TestBuildDockerignoringWholeDir(c *check.C) { name := "testbuilddockerignorewholedir" - defer deleteImages(name) dockerfile := ` FROM busybox COPY . / @@ -3754,7 +3620,6 @@ func (s *DockerSuite) TestBuildDockerignoringWholeDir(c *check.C) { func (s *DockerSuite) TestBuildLineBreak(c *check.C) { name := "testbuildlinebreak" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN sh -c 'echo root:testpass \ @@ -3770,7 +3635,6 @@ RUN [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]`, func (s *DockerSuite) TestBuildEOLInLine(c *check.C) { name := "testbuildeolinline" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN sh -c 'echo root:testpass > /tmp/passwd' @@ -3786,7 +3650,6 @@ RUN [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]`, func (s *DockerSuite) TestBuildCommentsShebangs(c *check.C) { name := "testbuildcomments" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox # This is an ordinary comment. @@ -3805,7 +3668,6 @@ RUN [ "$(/hello.sh)" = "hello world" ]`, func (s *DockerSuite) TestBuildUsersAndGroups(c *check.C) { name := "testbuildusers" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox @@ -3868,7 +3730,6 @@ RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1042:1043/10 func (s *DockerSuite) TestBuildEnvUsage(c *check.C) { name := "testbuildenvusage" - defer deleteImages(name) dockerfile := `FROM busybox ENV HOME /root ENV PATH $HOME/bin:$PATH @@ -3904,7 +3765,6 @@ RUN [ "$ghi" = "def" ] func (s *DockerSuite) TestBuildEnvUsage2(c *check.C) { name := "testbuildenvusage2" - defer deleteImages(name) dockerfile := `FROM busybox ENV abc=def RUN [ "$abc" = "def" ] @@ -4007,7 +3867,6 @@ RUN [ "$eee1,$eee2,$eee3,$eee4" = 'foo,foo,foo,foo' ] func (s *DockerSuite) TestBuildAddScript(c *check.C) { name := "testbuildaddscript" - defer deleteImages(name) dockerfile := ` FROM busybox ADD test /test @@ -4030,7 +3889,6 @@ RUN [ "$(cat /testfile)" = 'test!' ]` func (s *DockerSuite) TestBuildAddTar(c *check.C) { name := "testbuildaddtar" - defer deleteImages(name) ctx := func() *FakeContext { dockerfile := ` @@ -4085,7 +3943,6 @@ RUN cat /existing-directory-trailing-slash/test/foo | grep Hi` func (s *DockerSuite) TestBuildAddTarXz(c *check.C) { name := "testbuildaddtarxz" - defer deleteImages(name) ctx := func() *FakeContext { dockerfile := ` @@ -4136,7 +3993,6 @@ func (s *DockerSuite) TestBuildAddTarXz(c *check.C) { func (s *DockerSuite) TestBuildAddTarXzGz(c *check.C) { name := "testbuildaddtarxzgz" - defer deleteImages(name) ctx := func() *FakeContext { dockerfile := ` @@ -4195,7 +4051,6 @@ func (s *DockerSuite) TestBuildAddTarXzGz(c *check.C) { func (s *DockerSuite) TestBuildFromGIT(c *check.C) { name := "testbuildfromgit" - defer deleteImages(name) git, err := fakeGIT("repo", map[string]string{ "Dockerfile": `FROM busybox ADD first /first @@ -4223,7 +4078,6 @@ func (s *DockerSuite) TestBuildFromGIT(c *check.C) { func (s *DockerSuite) TestBuildCleanupCmdOnEntrypoint(c *check.C) { name := "testbuildcmdcleanuponentrypoint" - defer deleteImages(name) if _, err := buildImage(name, `FROM scratch CMD ["test"] @@ -4256,7 +4110,6 @@ func (s *DockerSuite) TestBuildCleanupCmdOnEntrypoint(c *check.C) { func (s *DockerSuite) TestBuildClearCmd(c *check.C) { name := "testbuildclearcmd" - defer deleteImages(name) _, err := buildImage(name, `From scratch ENTRYPOINT ["/bin/bash"] @@ -4276,7 +4129,6 @@ func (s *DockerSuite) TestBuildClearCmd(c *check.C) { func (s *DockerSuite) TestBuildEmptyCmd(c *check.C) { name := "testbuildemptycmd" - defer deleteImages(name) if _, err := buildImage(name, "FROM scratch\nMAINTAINER quux\n", true); err != nil { c.Fatal(err) } @@ -4291,14 +4143,10 @@ func (s *DockerSuite) TestBuildEmptyCmd(c *check.C) { func (s *DockerSuite) TestBuildOnBuildOutput(c *check.C) { name := "testbuildonbuildparent" - defer deleteImages(name) if _, err := buildImage(name, "FROM busybox\nONBUILD RUN echo foo\n", true); err != nil { c.Fatal(err) } - childname := "testbuildonbuildchild" - defer deleteImages(childname) - _, out, err := buildImageWithOut(name, "FROM "+name+"\nMAINTAINER quux\n", true) if err != nil { c.Fatal(err) @@ -4312,7 +4160,6 @@ func (s *DockerSuite) TestBuildOnBuildOutput(c *check.C) { func (s *DockerSuite) TestBuildInvalidTag(c *check.C) { name := "abcd:" + stringutils.GenerateRandomAlphaOnlyString(200) - defer deleteImages(name) _, out, err := buildImageWithOut(name, "FROM scratch\nMAINTAINER quux\n", true) // if the error doesnt check for illegal tag name, or the image is built // then this should fail @@ -4323,7 +4170,6 @@ func (s *DockerSuite) TestBuildInvalidTag(c *check.C) { func (s *DockerSuite) TestBuildCmdShDashC(c *check.C) { name := "testbuildcmdshc" - defer deleteImages(name) if _, err := buildImage(name, "FROM busybox\nCMD echo cmd\n", true); err != nil { c.Fatal(err) } @@ -4346,7 +4192,6 @@ func (s *DockerSuite) TestBuildCmdSpaces(c *check.C) { // the arg separator to make sure ["echo","hi"] and ["echo hi"] don't // look the same name := "testbuildcmdspaces" - defer deleteImages(name) var id1 string var id2 string var err error @@ -4380,7 +4225,6 @@ func (s *DockerSuite) TestBuildCmdSpaces(c *check.C) { func (s *DockerSuite) TestBuildCmdJSONNoShDashC(c *check.C) { name := "testbuildcmdjson" - defer deleteImages(name) if _, err := buildImage(name, "FROM busybox\nCMD [\"echo\", \"cmd\"]", true); err != nil { c.Fatal(err) } @@ -4400,7 +4244,6 @@ func (s *DockerSuite) TestBuildCmdJSONNoShDashC(c *check.C) { func (s *DockerSuite) TestBuildErrorInvalidInstruction(c *check.C) { name := "testbuildignoreinvalidinstruction" - defer deleteImages(name) out, _, err := buildImageWithOut(name, "FROM busybox\nfoo bar", true) if err == nil { @@ -4410,7 +4253,6 @@ func (s *DockerSuite) TestBuildErrorInvalidInstruction(c *check.C) { } func (s *DockerSuite) TestBuildEntrypointInheritance(c *check.C) { - defer deleteImages("parent", "child") if _, err := buildImage("parent", ` FROM busybox @@ -4447,8 +4289,6 @@ func (s *DockerSuite) TestBuildEntrypointInheritanceInspect(c *check.C) { expected = `["/bin/sh","-c","echo quux"]` ) - defer deleteImages(name, name2) - if _, err := buildImage(name, "FROM busybox\nENTRYPOINT /foo/bar", true); err != nil { c.Fatal(err) } @@ -4481,7 +4321,6 @@ func (s *DockerSuite) TestBuildEntrypointInheritanceInspect(c *check.C) { func (s *DockerSuite) TestBuildRunShEntrypoint(c *check.C) { name := "testbuildentrypoint" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox ENTRYPOINT /bin/echo`, @@ -4500,7 +4339,6 @@ func (s *DockerSuite) TestBuildRunShEntrypoint(c *check.C) { func (s *DockerSuite) TestBuildExoticShellInterpolation(c *check.C) { name := "testbuildexoticshellinterpolation" - defer deleteImages(name) _, err := buildImage(name, ` FROM busybox @@ -4534,7 +4372,6 @@ func (s *DockerSuite) TestBuildVerifySingleQuoteFails(c *check.C) { // as a "string" insead of "JSON array" and pass it on to "sh -c" and // it should barf on it. name := "testbuildsinglequotefails" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox @@ -4550,7 +4387,6 @@ func (s *DockerSuite) TestBuildVerifySingleQuoteFails(c *check.C) { func (s *DockerSuite) TestBuildVerboseOut(c *check.C) { name := "testbuildverboseout" - defer deleteImages(name) _, out, err := buildImageWithOut(name, `FROM busybox @@ -4568,7 +4404,6 @@ RUN echo 123`, func (s *DockerSuite) TestBuildWithTabs(c *check.C) { name := "testbuildwithtabs" - defer deleteImages(name) _, err := buildImage(name, "FROM busybox\nRUN echo\tone\t\ttwo", true) if err != nil { @@ -4588,7 +4423,6 @@ func (s *DockerSuite) TestBuildWithTabs(c *check.C) { func (s *DockerSuite) TestBuildLabels(c *check.C) { name := "testbuildlabel" expected := `{"License":"GPL","Vendor":"Acme"}` - defer deleteImages(name) _, err := buildImage(name, `FROM busybox LABEL Vendor=Acme @@ -4608,7 +4442,6 @@ func (s *DockerSuite) TestBuildLabels(c *check.C) { func (s *DockerSuite) TestBuildLabelsCache(c *check.C) { name := "testbuildlabelcache" - defer deleteImages(name) id1, err := buildImage(name, `FROM busybox @@ -4659,7 +4492,6 @@ func (s *DockerSuite) TestBuildStderr(c *check.C) { // This test just makes sure that no non-error output goes // to stderr name := "testbuildstderr" - defer deleteImages(name) _, _, stderr, err := buildImageWithStdoutStderr(name, "FROM busybox\nRUN echo one", true) if err != nil { @@ -4685,7 +4517,6 @@ func (s *DockerSuite) TestBuildChownSingleFile(c *check.C) { testRequires(c, UnixCli) // test uses chown: not available on windows name := "testbuildchownsinglefile" - defer deleteImages(name) ctx, err := fakeContext(` FROM busybox @@ -4765,7 +4596,6 @@ func (s *DockerSuite) TestBuildSymlinkBreakout(c *check.C) { func (s *DockerSuite) TestBuildXZHost(c *check.C) { name := "testbuildxzhost" - defer deleteImages(name) ctx, err := fakeContext(` FROM busybox @@ -4796,7 +4626,6 @@ func (s *DockerSuite) TestBuildVolumesRetainContents(c *check.C) { name = "testbuildvolumescontent" expected = "some text" ) - defer deleteImages(name) ctx, err := fakeContext(` FROM busybox COPY content /foo/file @@ -4929,7 +4758,6 @@ func (s *DockerSuite) TestBuildRenamedDockerfile(c *check.C) { func (s *DockerSuite) TestBuildFromMixedcaseDockerfile(c *check.C) { testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows - defer deleteImages("test1") ctx, err := fakeContext(`FROM busybox RUN echo from dockerfile`, @@ -4954,7 +4782,6 @@ func (s *DockerSuite) TestBuildFromMixedcaseDockerfile(c *check.C) { func (s *DockerSuite) TestBuildWithTwoDockerfiles(c *check.C) { testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows - defer deleteImages("test1") ctx, err := fakeContext(`FROM busybox RUN echo from Dockerfile`, @@ -4978,7 +4805,6 @@ RUN echo from Dockerfile`, } func (s *DockerSuite) TestBuildFromURLWithF(c *check.C) { - defer deleteImages("test1") server, err := fakeStorage(map[string]string{"baz": `FROM busybox RUN echo from baz @@ -5013,7 +4839,6 @@ RUN echo from Dockerfile`, } func (s *DockerSuite) TestBuildFromStdinWithF(c *check.C) { - defer deleteImages("test1") ctx, err := fakeContext(`FROM busybox RUN echo from Dockerfile`, @@ -5121,8 +4946,6 @@ func (s *DockerSuite) TestBuildDockerfileOutsideContext(c *check.C) { if err == nil { c.Fatalf("Expected error. Out: %s", out) } - deleteImages(name) - } func (s *DockerSuite) TestBuildSpaces(c *check.C) { @@ -5134,7 +4957,6 @@ func (s *DockerSuite) TestBuildSpaces(c *check.C) { ) name := "testspaces" - defer deleteImages(name) ctx, err := fakeContext("FROM busybox\nCOPY\n", map[string]string{ "Dockerfile": "FROM busybox\nCOPY\n", @@ -5199,7 +5021,6 @@ func (s *DockerSuite) TestBuildSpaces(c *check.C) { func (s *DockerSuite) TestBuildSpacesWithQuotes(c *check.C) { // Test to make sure that spaces in quotes aren't lost name := "testspacesquotes" - defer deleteImages(name) dockerfile := `FROM busybox RUN echo " \ @@ -5275,7 +5096,6 @@ func (s *DockerSuite) TestBuildMissingArgs(c *check.C) { } func (s *DockerSuite) TestBuildEmptyScratch(c *check.C) { - defer deleteImages("sc") _, out, err := buildImageWithOut("sc", "FROM scratch", true) if err == nil { c.Fatalf("Build was supposed to fail") @@ -5286,7 +5106,6 @@ func (s *DockerSuite) TestBuildEmptyScratch(c *check.C) { } func (s *DockerSuite) TestBuildDotDotFile(c *check.C) { - defer deleteImages("sc") ctx, err := fakeContext("FROM busybox\n", map[string]string{ "..gitme": "", @@ -5302,7 +5121,6 @@ func (s *DockerSuite) TestBuildDotDotFile(c *check.C) { } func (s *DockerSuite) TestBuildNotVerbose(c *check.C) { - defer deleteImages("verbose") ctx, err := fakeContext("FROM busybox\nENV abc=hi\nRUN echo $abc there", map[string]string{}) if err != nil { @@ -5337,8 +5155,6 @@ func (s *DockerSuite) TestBuildNotVerbose(c *check.C) { func (s *DockerSuite) TestBuildRUNoneJSON(c *check.C) { name := "testbuildrunonejson" - defer deleteImages(name, "hello-world") - ctx, err := fakeContext(`FROM hello-world:frozen RUN [ "/hello" ]`, map[string]string{}) if err != nil { @@ -5361,7 +5177,6 @@ RUN [ "/hello" ]`, map[string]string{}) func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { name := "testbuildresourceconstraints" - defer deleteImages(name, "hello-world") ctx, err := fakeContext(` FROM hello-world:frozen @@ -5425,7 +5240,6 @@ func (s *DockerSuite) TestBuildResourceConstraintsAreUsed(c *check.C) { func (s *DockerSuite) TestBuildEmptyStringVolume(c *check.C) { name := "testbuildemptystringvolume" - defer deleteImages(name) _, err := buildImage(name, ` FROM busybox diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index bd4518434..6470d974c 100644 --- a/integration-cli/docker_cli_by_digest_test.go +++ b/integration-cli/docker_cli_by_digest_test.go @@ -33,7 +33,6 @@ func setupImageWithTag(tag string) (string, error) { if out, _, err := runCommandWithOutput(cmd); err != nil { return "", fmt.Errorf("image tagging failed: %s, %v", out, err) } - defer deleteImages(repoAndTag) // delete the container as we don't need it any more if err := deleteContainer(containerName); err != nil { @@ -77,7 +76,6 @@ func (s *DockerSuite) TestPullByTagDisplaysDigest(c *check.C) { if err != nil { c.Fatalf("error pulling by tag: %s, %v", out, err) } - defer deleteImages(repoName) // the pull output includes "Digest: ", so find that matches := digestRegex.FindStringSubmatch(out) @@ -108,7 +106,6 @@ func (s *DockerSuite) TestPullByDigest(c *check.C) { if err != nil { c.Fatalf("error pulling by digest: %s, %v", out, err) } - defer deleteImages(imageReference) // the pull output includes "Digest: ", so find that matches := digestRegex.FindStringSubmatch(out) @@ -248,7 +245,6 @@ func (s *DockerSuite) TestBuildByDigest(c *check.C) { // do the build name := "buildbydigest" - defer deleteImages(name) _, err = buildImage(name, fmt.Sprintf( `FROM %s CMD ["/bin/echo", "Hello World"]`, imageReference), @@ -340,7 +336,6 @@ func (s *DockerSuite) TestListImagesWithoutDigests(c *check.C) { func (s *DockerSuite) TestListImagesWithDigests(c *check.C) { defer setupRegistry(c)() - defer deleteImages(repoName+":tag1", repoName+":tag2") // setup image1 digest1, err := setupImageWithTag("tag1") @@ -348,7 +343,6 @@ func (s *DockerSuite) TestListImagesWithDigests(c *check.C) { c.Fatalf("error setting up image: %v", err) } imageReference1 := fmt.Sprintf("%s@%s", repoName, digest1) - defer deleteImages(imageReference1) c.Logf("imageReference1 = %s", imageReference1) // pull image1 by digest @@ -377,7 +371,6 @@ func (s *DockerSuite) TestListImagesWithDigests(c *check.C) { c.Fatalf("error setting up image: %v", err) } imageReference2 := fmt.Sprintf("%s@%s", repoName, digest2) - defer deleteImages(imageReference2) c.Logf("imageReference2 = %s", imageReference2) // pull image1 by digest @@ -508,7 +501,6 @@ func (s *DockerSuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) { c.Fatalf("error pulling by digest: %s, %v", out, err) } // just in case... - defer deleteImages(imageReference) imageID, err := inspectField(imageReference, ".Id") if err != nil { diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index a75621f3a..1544b3aac 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -33,10 +33,6 @@ func (s *DockerSuite) TestCommitAfterContainerIsDone(c *check.C) { if out, _, err = runCommandWithOutput(inspectCmd); err != nil { c.Fatalf("failed to inspect image: %s, %v", out, err) } - - deleteContainer(cleanedContainerID) - deleteImages(cleanedImageID) - } func (s *DockerSuite) TestCommitWithoutPause(c *check.C) { @@ -65,10 +61,6 @@ func (s *DockerSuite) TestCommitWithoutPause(c *check.C) { if out, _, err = runCommandWithOutput(inspectCmd); err != nil { c.Fatalf("failed to inspect image: %s, %v", out, err) } - - deleteContainer(cleanedContainerID) - deleteImages(cleanedImageID) - } //test commit a paused container should not unpause it after commit @@ -92,8 +84,6 @@ func (s *DockerSuite) TestCommitPausedContainer(c *check.C) { if err != nil { c.Fatalf("failed to commit container to image: %s, %v", out, err) } - cleanedImageID := strings.TrimSpace(out) - defer deleteImages(cleanedImageID) cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.State.Paused}}", cleanedContainerID) out, _, _, err = runCommandWithStdoutStderr(cmd) @@ -120,7 +110,6 @@ func (s *DockerSuite) TestCommitNewFile(c *check.C) { c.Fatal(err) } imageID = strings.Trim(imageID, "\r\n") - defer deleteImages(imageID) cmd = exec.Command(dockerBinary, "run", imageID, "cat", "/foo") @@ -161,7 +150,6 @@ func (s *DockerSuite) TestCommitHardlink(c *check.C) { c.Fatal(imageID, err) } imageID = strings.Trim(imageID, "\r\n") - defer deleteImages(imageID) cmd = exec.Command(dockerBinary, "run", "-t", "hardlinks", "ls", "-di", "file1", "file2") secondOuput, _, err := runCommandWithOutput(cmd) @@ -185,7 +173,6 @@ func (s *DockerSuite) TestCommitHardlink(c *check.C) { } func (s *DockerSuite) TestCommitTTY(c *check.C) { - defer deleteImages("ttytest") cmd := exec.Command(dockerBinary, "run", "-t", "--name", "tty", "busybox", "/bin/ls") if _, err := runCommand(cmd); err != nil { @@ -220,7 +207,6 @@ func (s *DockerSuite) TestCommitWithHostBindMount(c *check.C) { } imageID = strings.Trim(imageID, "\r\n") - defer deleteImages(imageID) cmd = exec.Command(dockerBinary, "run", "bindtest", "true") @@ -248,7 +234,6 @@ func (s *DockerSuite) TestCommitChange(c *check.C) { c.Fatal(imageId, err) } imageId = strings.Trim(imageId, "\r\n") - defer deleteImages(imageId) expected := map[string]string{ "Config.ExposedPorts": "map[8080/tcp:{}]", @@ -274,7 +259,6 @@ func (s *DockerSuite) TestCommitMergeConfigRun(c *check.C) { id := strings.TrimSpace(out) dockerCmd(c, "commit", `--run={"Cmd": ["cat", "/tmp/foo"]}`, id, "commit-test") - defer deleteImages("commit-test") out, _ = dockerCmd(c, "run", "--name", name, "commit-test") if strings.TrimSpace(out) != "testing" { diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go index 10c499982..646a8eafe 100644 --- a/integration-cli/docker_cli_create_test.go +++ b/integration-cli/docker_cli_create_test.go @@ -259,7 +259,6 @@ func (s *DockerSuite) TestCreateLabels(c *check.C) { func (s *DockerSuite) TestCreateLabelFromImage(c *check.C) { imageName := "testcreatebuildlabel" - defer deleteImages(imageName) _, err := buildImage(imageName, `FROM busybox LABEL k1=v1 k2=v2`, diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 35a9c0a21..dfe35b14c 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -144,7 +144,6 @@ func (s *DockerSuite) TestEventsContainerEventsSinceUnixEpoch(c *check.C) { func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) { name := "testimageevents" - defer deleteImages(name) _, err := buildImage(name, `FROM scratch MAINTAINER "docker"`, @@ -180,8 +179,6 @@ func (s *DockerSuite) TestEventsImagePull(c *check.C) { since := daemonTime(c).Unix() testRequires(c, Network) - defer deleteImages("hello-world") - pullCmd := exec.Command(dockerBinary, "pull", "hello-world") if out, _, err := runCommandWithOutput(pullCmd); err != nil { c.Fatalf("pulling the hello-world image from has failed: %s, %v", out, err) diff --git a/integration-cli/docker_cli_export_import_test.go b/integration-cli/docker_cli_export_import_test.go index bc7b16356..3370a9676 100644 --- a/integration-cli/docker_cli_export_import_test.go +++ b/integration-cli/docker_cli_export_import_test.go @@ -12,8 +12,6 @@ import ( func (s *DockerSuite) TestExportContainerAndImportImage(c *check.C) { containerID := "testexportcontainerandimportimage" - defer deleteImages("repo/testexp:v1") - runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { @@ -51,8 +49,6 @@ func (s *DockerSuite) TestExportContainerAndImportImage(c *check.C) { func (s *DockerSuite) TestExportContainerWithOutputAndImportImage(c *check.C) { containerID := "testexportcontainerwithoutputandimportimage" - defer deleteImages("repo/testexp:v1") - runCmd := exec.Command(dockerBinary, "run", "-d", "--name", containerID, "busybox", "true") out, _, err := runCommandWithOutput(runCmd) if err != nil { diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go index 8de400831..d229f1a8c 100644 --- a/integration-cli/docker_cli_history_test.go +++ b/integration-cli/docker_cli_history_test.go @@ -14,7 +14,6 @@ import ( // sort is not predictable it doesn't always fail. func (s *DockerSuite) TestBuildHistory(c *check.C) { name := "testbuildhistory" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN echo "A" RUN echo "B" @@ -85,7 +84,6 @@ func (s *DockerSuite) TestHistoryNonExistentImage(c *check.C) { func (s *DockerSuite) TestHistoryImageWithComment(c *check.C) { name := "testhistoryimagewithcomment" - defer deleteImages(name) // make a image through docker commit [ -m messages ] //runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "echo", "foo") diff --git a/integration-cli/docker_cli_images_test.go b/integration-cli/docker_cli_images_test.go index cf219e88b..0ab646250 100644 --- a/integration-cli/docker_cli_images_test.go +++ b/integration-cli/docker_cli_images_test.go @@ -26,9 +26,6 @@ func (s *DockerSuite) TestImagesEnsureImageIsListed(c *check.C) { } func (s *DockerSuite) TestImagesOrderedByCreationDate(c *check.C) { - defer deleteImages("order:test_a") - defer deleteImages("order:test_c") - defer deleteImages("order:test_b") id1, err := buildImage("order:test_a", `FROM scratch MAINTAINER dockerio1`, true) @@ -80,9 +77,6 @@ func (s *DockerSuite) TestImagesFilterLabel(c *check.C) { imageName1 := "images_filter_test1" imageName2 := "images_filter_test2" imageName3 := "images_filter_test3" - defer deleteImages(imageName1) - defer deleteImages(imageName2) - defer deleteImages(imageName3) image1ID, err := buildImage(imageName1, `FROM scratch LABEL match me`, true) @@ -130,7 +124,6 @@ func (s *DockerSuite) TestImagesFilterLabel(c *check.C) { func (s *DockerSuite) TestImagesFilterSpaceTrimCase(c *check.C) { imageName := "images_filter_test" - defer deleteImages(imageName) buildImage(imageName, `FROM scratch RUN touch /test/foo @@ -189,7 +182,6 @@ func (s *DockerSuite) TestImagesEnsureDanglingImageOnlyListedOnce(c *check.C) { c.Fatalf("error tagging foobox: %s", err) } imageId := stringid.TruncateID(strings.TrimSpace(out)) - defer deleteImages(imageId) // overwrite the tag, making the previous image dangling cmd = exec.Command(dockerBinary, "tag", "-f", "busybox", "foobox") @@ -197,7 +189,6 @@ func (s *DockerSuite) TestImagesEnsureDanglingImageOnlyListedOnce(c *check.C) { if err != nil { c.Fatalf("error tagging foobox: %s", err) } - defer deleteImages("foobox") cmd = exec.Command(dockerBinary, "images", "-q", "-f", "dangling=true") out, _, err = runCommandWithOutput(cmd) diff --git a/integration-cli/docker_cli_import_test.go b/integration-cli/docker_cli_import_test.go index 02b857bfd..201dbaa58 100644 --- a/integration-cli/docker_cli_import_test.go +++ b/integration-cli/docker_cli_import_test.go @@ -27,7 +27,6 @@ func (s *DockerSuite) TestImportDisplay(c *check.C) { c.Fatalf("display is messed up: %d '\\n' instead of 1:\n%s", n, out) } image := strings.TrimSpace(out) - defer deleteImages(image) runCmd = exec.Command(dockerBinary, "run", "--rm", image, "true") out, _, err = runCommandWithOutput(runCmd) diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index ca2d67bc9..881f02d4f 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -560,7 +560,6 @@ func (s *DockerSuite) TestPsListContainersFilterExited(c *check.C) { func (s *DockerSuite) TestPsRightTagName(c *check.C) { tag := "asybox:shmatest" - defer deleteImages(tag) if out, err := exec.Command(dockerBinary, "tag", "busybox", tag).CombinedOutput(); err != nil { c.Fatalf("Failed to tag image: %s, out: %q", err, out) } diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index 9dc9920ca..bdb55f35d 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -13,7 +13,6 @@ func (s *DockerSuite) TestPullImageWithAliases(c *check.C) { defer setupRegistry(c)() repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) - defer deleteImages(repoName) repos := []string{} for _, tag := range []string{"recent", "fresh"} { @@ -25,7 +24,6 @@ func (s *DockerSuite) TestPullImageWithAliases(c *check.C) { if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repo)); err != nil { c.Fatalf("Failed to tag image %v: error %v, output %q", repos, err, out) } - defer deleteImages(repo) if out, err := exec.Command(dockerBinary, "push", repo).CombinedOutput(); err != nil { c.Fatalf("Failed to push image %v: error %v, output %q", repo, err, string(out)) } @@ -61,7 +59,6 @@ func (s *DockerSuite) TestPullVerified(c *check.C) { // unless keychain is manually updated to contain the daemon's sign key. verifiedName := "hello-world" - defer deleteImages(verifiedName) // pull it expected := "The image you are pulling has been verified" @@ -88,8 +85,6 @@ func (s *DockerSuite) TestPullVerified(c *check.C) { func (s *DockerSuite) TestPullImageFromCentralRegistry(c *check.C) { testRequires(c, Network) - defer deleteImages("hello-world") - pullCmd := exec.Command(dockerBinary, "pull", "hello-world") if out, _, err := runCommandWithOutput(pullCmd); err != nil { c.Fatalf("pulling the hello-world image from the registry has failed: %s, %v", out, err) diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 3fc1ae3a0..44d34dd63 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -22,7 +22,6 @@ func (s *DockerSuite) TestPushBusyboxImage(c *check.C) { if out, _, err := runCommandWithOutput(tagCmd); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) } - defer deleteImages(repoName) pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err != nil { @@ -77,12 +76,10 @@ func (s *DockerSuite) TestPushMultipleTags(c *check.C) { if out, _, err := runCommandWithOutput(tagCmd1); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) } - defer deleteImages(repoTag1) tagCmd2 := exec.Command(dockerBinary, "tag", "busybox", repoTag2) if out, _, err := runCommandWithOutput(tagCmd2); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) } - defer deleteImages(repoTag2) pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err != nil { @@ -97,7 +94,6 @@ func (s *DockerSuite) TestPushInterrupt(c *check.C) { if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repoName)); err != nil { c.Fatalf("image tagging failed: %s, %v", out, err) } - defer deleteImages(repoName) pushCmd := exec.Command(dockerBinary, "push", repoName) if err := pushCmd.Start(); err != nil { diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index c330bb76a..b8d1b843d 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -87,7 +87,6 @@ func (s *DockerSuite) TestRmContainerOrphaning(c *check.C) { // build first dockerfile img1, err := buildImage(img, dockerfile1, true) - defer deleteImages(img1) if err != nil { c.Fatalf("Could not build image %s: %v", img, err) } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 8da6f3b87..342137c47 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -447,7 +447,6 @@ func (s *DockerSuite) TestRunCreateVolumesInSymlinkDir(c *check.C) { if _, err := buildImage(name, dockerFile, false); err != nil { c.Fatal(err) } - defer deleteImages(name) out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-v", "/test/test", name)) if err != nil { @@ -604,7 +603,6 @@ func (s *DockerSuite) TestRunCreateVolume(c *check.C) { // Note that this bug happens only with symlinks with a target that starts with '/'. func (s *DockerSuite) TestRunCreateVolumeWithSymlink(c *check.C) { image := "docker-test-createvolumewithsymlink" - defer deleteImages(image) buildCmd := exec.Command(dockerBinary, "build", "-t", image, "-") buildCmd.Stdin = strings.NewReader(`FROM busybox @@ -644,7 +642,6 @@ func (s *DockerSuite) TestRunCreateVolumeWithSymlink(c *check.C) { // Tests that a volume path that has a symlink exists in a container mounting it with `--volumes-from`. func (s *DockerSuite) TestRunVolumesFromSymlinkPath(c *check.C) { name := "docker-test-volumesfromsymlinkpath" - defer deleteImages(name) buildCmd := exec.Command(dockerBinary, "build", "-t", name, "-") buildCmd.Stdin = strings.NewReader(`FROM busybox @@ -1746,7 +1743,6 @@ func (s *DockerSuite) TestRunState(c *check.C) { // Test for #1737 func (s *DockerSuite) TestRunCopyVolumeUidGid(c *check.C) { name := "testrunvolumesuidgid" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd @@ -1772,7 +1768,6 @@ func (s *DockerSuite) TestRunCopyVolumeUidGid(c *check.C) { // Test for #1582 func (s *DockerSuite) TestRunCopyVolumeContent(c *check.C) { name := "testruncopyvolumecontent" - defer deleteImages(name) _, err := buildImage(name, `FROM busybox RUN mkdir -p /hello/local && echo hello > /hello/local/world`, @@ -1794,7 +1789,6 @@ func (s *DockerSuite) TestRunCopyVolumeContent(c *check.C) { func (s *DockerSuite) TestRunCleanupCmdOnEntrypoint(c *check.C) { name := "testrunmdcleanuponentrypoint" - defer deleteImages(name) if _, err := buildImage(name, `FROM busybox ENTRYPOINT ["echo"] @@ -2349,7 +2343,6 @@ func (s *DockerSuite) TestRunCreateVolumeEtc(c *check.C) { } func (s *DockerSuite) TestVolumesNoCopyData(c *check.C) { - defer deleteImages("dataimage") if _, err := buildImage("dataimage", `FROM busybox RUN mkdir -p /foo @@ -2430,7 +2423,6 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) { true); err != nil { c.Fatal(err) } - defer deleteImages("run_volumes_clean_paths") cmd := exec.Command(dockerBinary, "run", "-v", "/foo", "-v", "/bar/", "--name", "dark_helmet", "run_volumes_clean_paths") if out, _, err := runCommandWithOutput(cmd); err != nil { diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go index f74f69fcc..fe6bf2bfc 100644 --- a/integration-cli/docker_cli_save_load_test.go +++ b/integration-cli/docker_cli_save_load_test.go @@ -131,7 +131,6 @@ func (s *DockerSuite) TestSaveSingleTag(c *check.C) { repoName := "foobar-save-single-tag-test" tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", fmt.Sprintf("%v:latest", repoName)) - defer deleteImages(repoName) if out, _, err := runCommandWithOutput(tagCmd); err != nil { c.Fatalf("failed to tag repo: %s, %v", out, err) } @@ -157,7 +156,6 @@ func (s *DockerSuite) TestSaveImageId(c *check.C) { repoName := "foobar-save-image-id-test" tagCmd := exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v:latest", repoName)) - defer deleteImages(repoName) if out, _, err := runCommandWithOutput(tagCmd); err != nil { c.Fatalf("failed to tag repo: %s, %v", out, err) } @@ -264,7 +262,6 @@ func (s *DockerSuite) TestSaveMultipleNames(c *check.C) { if out, _, err := runCommandWithOutput(tagCmd); err != nil { c.Fatalf("failed to tag repo: %s, %v", out, err) } - defer deleteImages(repoName + "-one") // Make two images tagCmd = exec.Command(dockerBinary, "tag", "emptyfs:latest", fmt.Sprintf("%v-two:latest", repoName)) @@ -272,7 +269,6 @@ func (s *DockerSuite) TestSaveMultipleNames(c *check.C) { if err != nil { c.Fatalf("failed to tag repo: %s, %v", out, err) } - defer deleteImages(repoName + "-two") out, _, err = runCommandPipelineWithOutput( exec.Command(dockerBinary, "save", fmt.Sprintf("%v-one", repoName), fmt.Sprintf("%v-two:latest", repoName)), @@ -311,9 +307,7 @@ func (s *DockerSuite) TestSaveRepoWithMultipleImages(c *check.C) { tagBar := repoName + ":bar" idFoo := makeImage("busybox:latest", tagFoo) - defer deleteImages(idFoo) idBar := makeImage("busybox:latest", tagBar) - defer deleteImages(idBar) deleteImages(repoName) @@ -358,7 +352,6 @@ func (s *DockerSuite) TestSaveDirectoryPermissions(c *check.C) { os.Mkdir(extractionDirectory, 0777) defer os.RemoveAll(tmpDir) - defer deleteImages(name) _, err = buildImage(name, `FROM busybox RUN adduser -D user && mkdir -p /opt/a/b && chown -R user:user /opt/a diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go index 1b4d36b7b..35225f9c1 100644 --- a/integration-cli/docker_cli_tag_test.go +++ b/integration-cli/docker_cli_tag_test.go @@ -18,9 +18,6 @@ func (s *DockerSuite) TestTagUnprefixedRepoByName(c *check.C) { if out, _, err := runCommandWithOutput(tagCmd); err != nil { c.Fatal(out, err) } - - deleteImages("testfoobarbaz") - } // tagging an image by ID in a new unprefixed repo should work @@ -36,9 +33,6 @@ func (s *DockerSuite) TestTagUnprefixedRepoByID(c *check.C) { if out, _, err = runCommandWithOutput(tagCmd); err != nil { c.Fatal(out, err) } - - deleteImages("testfoobarbaz") - } // ensure we don't allow the use of invalid repository names; these tag operations should fail @@ -104,8 +98,6 @@ func (s *DockerSuite) TestTagExistedNameWithoutForce(c *check.C) { if err == nil || !strings.Contains(out, "Conflict: Tag test is already set to image") { c.Fatal("tag busybox busybox:test should have failed,because busybox:test is existed") } - deleteImages("busybox:test") - } // tag an image with an existed tag name with -f option should work @@ -122,8 +114,6 @@ func (s *DockerSuite) TestTagExistedNameWithForce(c *check.C) { if out, _, err := runCommandWithOutput(tagCmd); err != nil { c.Fatal(out, err) } - deleteImages("busybox:test") - } // ensure tagging using official names works diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 20e87244e..688de68a1 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -396,6 +396,55 @@ func deleteAllContainers() error { return nil } +var protectedImages = map[string]struct{}{} + +func init() { + out, err := exec.Command(dockerBinary, "images").CombinedOutput() + if err != nil { + panic(err) + } + lines := strings.Split(string(out), "\n")[1:] + for _, l := range lines { + if l == "" { + continue + } + fields := strings.Fields(l) + imgTag := fields[0] + ":" + fields[1] + protectedImages[imgTag] = struct{}{} + } +} + +func deleteAllImages() error { + out, err := exec.Command(dockerBinary, "images").CombinedOutput() + if err != nil { + return err + } + lines := strings.Split(string(out), "\n")[1:] + var imgs []string + for _, l := range lines { + if l == "" { + continue + } + fields := strings.Fields(l) + imgTag := fields[0] + ":" + fields[1] + if _, ok := protectedImages[imgTag]; !ok { + if fields[0] == "" { + imgs = append(imgs, fields[2]) + continue + } + imgs = append(imgs, imgTag) + } + } + if len(imgs) == 0 { + return nil + } + args := append([]string{"rmi", "-f"}, imgs...) + if err := exec.Command(dockerBinary, args...).Run(); err != nil { + return err + } + return nil +} + func getPausedContainers() (string, error) { getPausedContainersCmd := exec.Command(dockerBinary, "ps", "-f", "status=paused", "-q", "-a") out, exitCode, err := runCommandWithOutput(getPausedContainersCmd) From 07795c3f5db3aa8ff592b0606ab76d32db4c6d25 Mon Sep 17 00:00:00 2001 From: Daniel Antlinger Date: Thu, 23 Apr 2015 11:43:08 -0700 Subject: [PATCH 630/999] fix runtime issue for TestEventsFilterContainer Signed-off-by: Daniel Antlinger Signed-off-by: Alexander Morozov --- integration-cli/docker_cli_events_test.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index b8e24260a..16e03bfe3 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -322,12 +322,15 @@ func (s *DockerSuite) TestEventsFilterContainer(c *check.C) { nameID := make(map[string]string) for _, name := range []string{"container_1", "container_2"} { - out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "true")) + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--name", name, "busybox", "true")) + if err != nil { + c.Fatalf("Error: %v, Output: %s", err, out) + } + id, err := inspectField(name, "Id") if err != nil { c.Fatal(err) } - nameID[name] = strings.TrimSpace(out) - waitInspect(name, "{{.State.Runing }}", "false", 5) + nameID[name] = id } until := fmt.Sprintf("%d", daemonTime(c).Unix()) From 11a5f1af01842acf9a25ccebb59f36472ae51170 Mon Sep 17 00:00:00 2001 From: Anton Tiurin Date: Fri, 24 Apr 2015 00:39:05 +0300 Subject: [PATCH 631/999] statsCollector: fix data race in run() statsCollector.publishers must be protected to prevent modifications during the iteration in run(). Being locked for a long time is bad, so pairs of containers & publishers (pointers) are copied to release the lock fast. Signed-off-by: Anton Tiurin --- daemon/stats_collector.go | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/daemon/stats_collector.go b/daemon/stats_collector.go index 5677a8634..22239743a 100644 --- a/daemon/stats_collector.go +++ b/daemon/stats_collector.go @@ -76,22 +76,42 @@ func (s *statsCollector) unsubscribe(c *Container, ch chan interface{}) { } func (s *statsCollector) run() { + type publishersPair struct { + container *Container + publisher *pubsub.Publisher + } + // we cannot determine the capacity here. + // it will grow enough in first iteration + var pairs []publishersPair + for range time.Tick(s.interval) { + systemUsage, err := s.getSystemCpuUsage() + if err != nil { + logrus.Errorf("collecting system cpu usage: %v", err) + continue + } + + // it does not make sense in the first iteration, + // but saves allocations in further iterations + pairs = pairs[:0] + + s.m.Lock() for container, publisher := range s.publishers { - systemUsage, err := s.getSystemCpuUsage() - if err != nil { - logrus.Errorf("collecting system cpu usage for %s: %v", container.ID, err) - continue - } - stats, err := container.Stats() + // copy pointers here to release the lock ASAP + pairs = append(pairs, publishersPair{container, publisher}) + } + s.m.Unlock() + + for _, pair := range pairs { + stats, err := pair.container.Stats() if err != nil { if err != execdriver.ErrNotRunning { - logrus.Errorf("collecting stats for %s: %v", container.ID, err) + logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err) } continue } stats.SystemUsage = systemUsage - publisher.Publish(stats) + pair.publisher.Publish(stats) } } } From 887ad57cfa46df542001a1bb4e3ab052ce5f5ed9 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 24 Apr 2015 11:05:17 -0700 Subject: [PATCH 632/999] Use -f for rm instead of kill before Signed-off-by: Alexander Morozov --- integration-cli/docker_utils.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 20e87244e..907be173c 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -361,9 +361,7 @@ func readBody(b io.ReadCloser) ([]byte, error) { func deleteContainer(container string) error { container = strings.TrimSpace(strings.Replace(container, "\n", " ", -1)) - killArgs := strings.Split(fmt.Sprintf("kill %v", container), " ") - runCommand(exec.Command(dockerBinary, killArgs...)) - rmArgs := strings.Split(fmt.Sprintf("rm -v %v", container), " ") + rmArgs := strings.Split(fmt.Sprintf("rm -fv %v", container), " ") exitCode, err := runCommand(exec.Command(dockerBinary, rmArgs...)) // set error manually if not set if exitCode != 0 && err == nil { From 366ee6bdfa9d2c87d006a162fc951ecc0f68051a Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Fri, 24 Apr 2015 12:23:54 -0700 Subject: [PATCH 633/999] Expose ParseRestartPolicy ParseRestartPolicy is useful function for third party go programs to use so that they can parse the restart policy in the same way that Docker does Signed-off-by: Darren Shepherd --- runconfig/parse.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runconfig/parse.go b/runconfig/parse.go index 2cdb2d331..47feac866 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -277,7 +277,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe return nil, nil, cmd, fmt.Errorf("--net: invalid net mode: %v", err) } - restartPolicy, err := parseRestartPolicy(*flRestartPolicy) + restartPolicy, err := ParseRestartPolicy(*flRestartPolicy) if err != nil { return nil, nil, cmd, err } @@ -374,8 +374,8 @@ func convertKVStringsToMap(values []string) map[string]string { return result } -// parseRestartPolicy returns the parsed policy or an error indicating what is incorrect -func parseRestartPolicy(policy string) (RestartPolicy, error) { +// ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect +func ParseRestartPolicy(policy string) (RestartPolicy, error) { p := RestartPolicy{} if policy == "" { From 9bea123bddb9d2ce8b2aa517c2a2a4b269ed39bc Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 24 Apr 2015 13:16:51 -0700 Subject: [PATCH 634/999] Not protect dangling images for integration-cli Signed-off-by: Alexander Morozov --- integration-cli/docker_utils.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 929426b4f..51ecacb33 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -408,7 +408,10 @@ func init() { } fields := strings.Fields(l) imgTag := fields[0] + ":" + fields[1] - protectedImages[imgTag] = struct{}{} + // just for case if we have dangling images in tested daemon + if imgTag != ":" { + protectedImages[imgTag] = struct{}{} + } } } From f696b1071a296bee1f4ac7cafa807ce337fb9f2c Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 24 Apr 2015 14:16:56 -0700 Subject: [PATCH 635/999] Implement DockerRegistrySuite in integration-cli To avoid manually creating and destroying registrys in tests. Signed-off-by: Alexander Morozov --- integration-cli/check_test.go | 25 +++++++++- integration-cli/docker_cli_by_digest_test.go | 48 ++++---------------- integration-cli/docker_cli_pull_test.go | 5 +- integration-cli/docker_cli_push_test.go | 22 +++------ integration-cli/docker_utils.go | 5 +- 5 files changed, 43 insertions(+), 62 deletions(-) diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go index f6dbdb8a9..533ac127c 100644 --- a/integration-cli/check_test.go +++ b/integration-cli/check_test.go @@ -24,6 +24,10 @@ func (s *TimerSuite) TearDownTest(c *check.C) { fmt.Printf("%-60s%.2f\n", c.TestName(), time.Since(s.start).Seconds()) } +func init() { + check.Suite(&DockerSuite{}) +} + type DockerSuite struct { TimerSuite } @@ -34,4 +38,23 @@ func (s *DockerSuite) TearDownTest(c *check.C) { s.TimerSuite.TearDownTest(c) } -var _ = check.Suite(&DockerSuite{}) +func init() { + check.Suite(&DockerRegistrySuite{ + ds: &DockerSuite{}, + }) +} + +type DockerRegistrySuite struct { + ds *DockerSuite + reg *testRegistryV2 +} + +func (s *DockerRegistrySuite) SetUpTest(c *check.C) { + s.reg = setupRegistry(c) + s.ds.SetUpTest(c) +} + +func (s *DockerRegistrySuite) TearDownTest(c *check.C) { + s.reg.Close() + s.ds.TearDownTest(c) +} diff --git a/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go index 6470d974c..b9b319cf9 100644 --- a/integration-cli/docker_cli_by_digest_test.go +++ b/integration-cli/docker_cli_by_digest_test.go @@ -62,9 +62,7 @@ func setupImageWithTag(tag string) (string, error) { return pushDigest, nil } -func (s *DockerSuite) TestPullByTagDisplaysDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.C) { pushDigest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -88,12 +86,9 @@ func (s *DockerSuite) TestPullByTagDisplaysDigest(c *check.C) { if pushDigest != pullDigest { c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) } - } -func (s *DockerSuite) TestPullByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPullByDigest(c *check.C) { pushDigest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -118,12 +113,9 @@ func (s *DockerSuite) TestPullByDigest(c *check.C) { if pushDigest != pullDigest { c.Fatalf("push digest %q didn't match pull digest %q", pushDigest, pullDigest) } - } -func (s *DockerSuite) TestCreateByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestCreateByDigest(c *check.C) { pushDigest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -145,12 +137,9 @@ func (s *DockerSuite) TestCreateByDigest(c *check.C) { if res != imageReference { c.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference) } - } -func (s *DockerSuite) TestRunByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestRunByDigest(c *check.C) { pushDigest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -181,12 +170,9 @@ func (s *DockerSuite) TestRunByDigest(c *check.C) { if res != imageReference { c.Fatalf("unexpected Config.Image: %s (expected %s)", res, imageReference) } - } -func (s *DockerSuite) TestRemoveImageByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestRemoveImageByDigest(c *check.C) { digest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -217,12 +203,9 @@ func (s *DockerSuite) TestRemoveImageByDigest(c *check.C) { } else if !strings.Contains(err.Error(), "No such image") { c.Fatalf("expected 'No such image' output, got %v", err) } - } -func (s *DockerSuite) TestBuildByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestBuildByDigest(c *check.C) { digest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -262,12 +245,9 @@ func (s *DockerSuite) TestBuildByDigest(c *check.C) { if res != imageID { c.Fatalf("Image %s, expected %s", res, imageID) } - } -func (s *DockerSuite) TestTagByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestTagByDigest(c *check.C) { digest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -302,12 +282,9 @@ func (s *DockerSuite) TestTagByDigest(c *check.C) { if tagID != expectedID { c.Fatalf("expected image id %q, got %q", expectedID, tagID) } - } -func (s *DockerSuite) TestListImagesWithoutDigests(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestListImagesWithoutDigests(c *check.C) { digest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -334,8 +311,7 @@ func (s *DockerSuite) TestListImagesWithoutDigests(c *check.C) { } -func (s *DockerSuite) TestListImagesWithDigests(c *check.C) { - defer setupRegistry(c)() +func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) { // setup image1 digest1, err := setupImageWithTag("tag1") @@ -482,12 +458,9 @@ func (s *DockerSuite) TestListImagesWithDigests(c *check.C) { if !busyboxRe.MatchString(out) { c.Fatalf("expected %q: %s", busyboxRe.String(), out) } - } -func (s *DockerSuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) { pushDigest, err := setupImage() if err != nil { c.Fatalf("error setting up image: %v", err) @@ -511,5 +484,4 @@ func (s *DockerSuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.C) { if _, err := runCommand(cmd); err != nil { c.Fatalf("error deleting image by id: %v", err) } - } diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go index bdb55f35d..a3ded8f03 100644 --- a/integration-cli/docker_cli_pull_test.go +++ b/integration-cli/docker_cli_pull_test.go @@ -9,9 +9,7 @@ import ( ) // See issue docker/docker#8141 -func (s *DockerSuite) TestPullImageWithAliases(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) repos := []string{} @@ -48,7 +46,6 @@ func (s *DockerSuite) TestPullImageWithAliases(c *check.C) { c.Fatalf("Image %v shouldn't have been pulled down", repo) } } - } // pulling library/hello-world should show verified message diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 44d34dd63..8f7ee3158 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -13,9 +13,7 @@ import ( ) // pulling an image from the central registry should work -func (s *DockerSuite) TestPushBusyboxImage(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPushBusyboxImage(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) // tag the image to upload it to the private registry tagCmd := exec.Command(dockerBinary, "tag", "busybox", repoName) @@ -37,9 +35,7 @@ func (s *DockerSuite) TestPushUnprefixedRepo(c *check.C) { } } -func (s *DockerSuite) TestPushUntagged(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPushUntagged(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) expected := "Repository does not exist" @@ -51,9 +47,7 @@ func (s *DockerSuite) TestPushUntagged(c *check.C) { } } -func (s *DockerSuite) TestPushBadTag(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPushBadTag(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox:latest", privateRegistryURL) expected := "does not exist" @@ -65,9 +59,7 @@ func (s *DockerSuite) TestPushBadTag(c *check.C) { } } -func (s *DockerSuite) TestPushMultipleTags(c *check.C) { - defer setupRegistry(c)() - +func (s *DockerRegistrySuite) TestPushMultipleTags(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) repoTag1 := fmt.Sprintf("%v/dockercli/busybox:t1", privateRegistryURL) repoTag2 := fmt.Sprintf("%v/dockercli/busybox:t2", privateRegistryURL) @@ -87,8 +79,7 @@ func (s *DockerSuite) TestPushMultipleTags(c *check.C) { } } -func (s *DockerSuite) TestPushInterrupt(c *check.C) { - defer setupRegistry(c)() +func (s *DockerRegistrySuite) TestPushInterrupt(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL) // tag the image and upload it to the private registry if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "tag", "busybox", repoName)); err != nil { @@ -118,8 +109,7 @@ func (s *DockerSuite) TestPushInterrupt(c *check.C) { } } -func (s *DockerSuite) TestPushEmptyLayer(c *check.C) { - defer setupRegistry(c)() +func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.C) { repoName := fmt.Sprintf("%v/dockercli/emptylayer", privateRegistryURL) emptyTarball, err := ioutil.TempFile("", "empty_tarball") if err != nil { diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 929426b4f..931f3e269 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -1125,7 +1125,7 @@ func daemonTime(c *check.C) time.Time { return dt } -func setupRegistry(c *check.C) func() { +func setupRegistry(c *check.C) *testRegistryV2 { testRequires(c, RegistryHosting) reg, err := newTestRegistryV2(c) if err != nil { @@ -1143,8 +1143,7 @@ func setupRegistry(c *check.C) func() { if err != nil { c.Fatal("Timeout waiting for test registry to become available") } - - return func() { reg.Close() } + return reg } // appendBaseEnv appends the minimum set of environment variables to exec the From 29a3bbf2b375111b9494df1ce5ee457508f7acad Mon Sep 17 00:00:00 2001 From: Ankush Agarwal Date: Mon, 20 Apr 2015 13:05:48 -0700 Subject: [PATCH 636/999] Eliminate json.Marshal from graph/export.go and volumes/volume.go Fixes #12531 Fixes #12530 Signed-off-by: Ankush Agarwal --- graph/export.go | 11 +++++++++-- volumes/volume.go | 13 +++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/graph/export.go b/graph/export.go index 00cfa8975..91382c60e 100644 --- a/graph/export.go +++ b/graph/export.go @@ -85,8 +85,15 @@ func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { } // write repositories, if there is something to write if len(rootRepoMap) > 0 { - rootRepoJson, _ := json.Marshal(rootRepoMap) - if err := ioutil.WriteFile(path.Join(tempdir, "repositories"), rootRepoJson, os.FileMode(0644)); err != nil { + f, err := os.OpenFile(path.Join(tempdir, "repositories"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + f.Close() + return err + } + if err := json.NewEncoder(f).Encode(rootRepoMap); err != nil { + return err + } + if err := f.Close(); err != nil { return err } } else { diff --git a/volumes/volume.go b/volumes/volume.go index 87aa4ad25..164469265 100644 --- a/volumes/volume.go +++ b/volumes/volume.go @@ -2,7 +2,6 @@ package volumes import ( "encoding/json" - "io/ioutil" "os" "path/filepath" "sync" @@ -81,17 +80,19 @@ func (v *Volume) ToDisk() error { } func (v *Volume) toDisk() error { - data, err := json.Marshal(v) + jsonPath, err := v.jsonPath() if err != nil { return err } - - pth, err := v.jsonPath() + f, err := os.OpenFile(jsonPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { return err } - - return ioutil.WriteFile(pth, data, 0666) + if err := json.NewEncoder(f).Encode(v); err != nil { + f.Close() + return err + } + return f.Close() } func (v *Volume) FromDisk() error { From ae9905ef9c5e8fe793e6a6269bb720618f4fcaed Mon Sep 17 00:00:00 2001 From: John Howard Date: Fri, 24 Apr 2015 16:52:32 -0700 Subject: [PATCH 637/999] Fixed typo 'configuring' Signed-off-by: John Howard --- daemon/networkdriver/bridge/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index f1397dc32..04e3737dc 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -233,7 +233,7 @@ func InitDriver(config *Config) error { // Configure iptables for link support if config.EnableIptables { if err := setupIPTables(addrv4, config.InterContainerCommunication, config.EnableIpMasq); err != nil { - logrus.Errorf("Error configuing iptables: %s", err) + logrus.Errorf("Error configuring iptables: %s", err) return err } // call this on Firewalld reload From daad696e091c931c8f0bf210c65d90db27b0af74 Mon Sep 17 00:00:00 2001 From: He Simei Date: Sat, 25 Apr 2015 09:10:32 +0800 Subject: [PATCH 638/999] Fix error prompt for pull & import handler postImagesCreate. Signed-off-by: He Simei --- api/server/server.go | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index cdc6c1815..65cfd8a96 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -740,8 +740,9 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w } var ( - opErr error + err error useJSON = version.GreaterThan("1.0") + output = utils.NewWriteFlusher(w) ) if useJSON { @@ -763,11 +764,12 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w Parallel: version.GreaterThan("1.3"), MetaHeaders: metaHeaders, AuthConfig: authConfig, - OutStream: utils.NewWriteFlusher(w), + OutStream: output, Json: useJSON, } - opErr = s.daemon.Repositories().Pull(image, tag, imagePullConfig) + err = s.daemon.Repositories().Pull(image, tag, imagePullConfig) + } else { //import if tag == "" { repo, tag = parsers.ParseRepositoryTag(repo) @@ -777,7 +779,7 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w imageImportConfig := &graph.ImageImportConfig{ Changes: r.Form["changes"], InConfig: r.Body, - OutStream: utils.NewWriteFlusher(w), + OutStream: output, Json: useJSON, } @@ -787,15 +789,19 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w } imageImportConfig.ContainerConfig = newConfig - opErr = s.daemon.Repositories().Import(src, repo, tag, imageImportConfig) - } + err = s.daemon.Repositories().Import(src, repo, tag, imageImportConfig) - if opErr != nil { + } + if err != nil { + if !output.Flushed() { + return err + } sf := streamformatter.NewStreamFormatter(useJSON) - return fmt.Errorf(string(sf.FormatError(opErr))) + output.Write(sf.FormatError(err)) } return nil + } func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { From 2188c8d2f41f4905411a40c30f4c703d7f4e0fcf Mon Sep 17 00:00:00 2001 From: Sergey Alekseev Date: Sat, 25 Apr 2015 11:28:39 +0300 Subject: [PATCH 639/999] Fix documentation typo in articles/basics.md Signed-off-by: Sergey Alekseev --- docs/sources/articles/basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/articles/basics.md b/docs/sources/articles/basics.md index 94264ece6..7d7c15409 100644 --- a/docs/sources/articles/basics.md +++ b/docs/sources/articles/basics.md @@ -172,7 +172,7 @@ will be stored (as a diff). See which images you already have using the # Commit your container to a new named image $ docker commit - # List your containers + # List your images $ docker images You now have an image state from which you can create new instances. From 8f752ffeafd2f8c08035a5e39220fe17c9309fd7 Mon Sep 17 00:00:00 2001 From: Hu Keping Date: Fri, 24 Apr 2015 19:57:04 +0800 Subject: [PATCH 640/999] Add test for REST API container rename Signed-off-by: Hu Keping --- integration-cli/docker_api_containers_test.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 24efbb7a2..172e3e993 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -841,3 +841,22 @@ func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { c.Assert(status, check.Equals, http.StatusInternalServerError) c.Assert(strings.Contains(string(b), "Minimum memory limit allowed is 4MB"), check.Equals, true) } + +func (s *DockerSuite) TestContainerApiRename(c *check.C) { + runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + + containerID := strings.TrimSpace(out) + newName := "new_name" + stringid.GenerateRandomID() + statusCode, _, err := sockRequest("POST", "/containers/"+containerID+"/rename?name="+newName, nil) + + // 204 No Content is expected, not 200 + c.Assert(statusCode, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) + + name, err := inspectField(containerID, "Name") + if name != "/"+newName { + c.Fatalf("Failed to rename container, expected %v, got %v. Container rename API failed", newName, name) + } +} From bc149be69c5ad22f82269427dd4b4aed9df4fa40 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Sat, 25 Apr 2015 04:42:43 -0700 Subject: [PATCH 641/999] A fix for = in env values in linked containers Closes: #12763 Signed-off-by: Doug Davis --- integration-cli/docker_cli_links_test.go | 21 +++++++++++++++++++++ links/links.go | 4 ++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go index 95b340be2..6bb173c10 100644 --- a/integration-cli/docker_cli_links_test.go +++ b/integration-cli/docker_cli_links_test.go @@ -310,3 +310,24 @@ func (s *DockerSuite) TestLinksUpdateOnRestart(c *check.C) { c.Fatalf("For 'onetwo' alias expected IP: %s, got: %s", realIP, ip) } } + +func (s *DockerSuite) TestLinksEnvs(c *check.C) { + runCmd := exec.Command(dockerBinary, "run", "-d", "-e", "e1=", "-e", "e2=v2", "-e", "e3=v3=v3", "--name=first", "busybox", "top") + out, _, _, err := runCommandWithStdoutStderr(runCmd) + if err != nil { + c.Fatalf("Run of first failed: %s\n%s", out, err) + } + + runCmd = exec.Command(dockerBinary, "run", "--name=second", "--link=first:first", "busybox", "env") + + out, stde, rc, err := runCommandWithStdoutStderr(runCmd) + if err != nil || rc != 0 { + c.Fatalf("run of 2nd failed: rc: %d, out: %s\n err: %s", rc, out, stde) + } + + if !strings.Contains(out, "FIRST_ENV_e1=\n") || + !strings.Contains(out, "FIRST_ENV_e2=v2") || + !strings.Contains(out, "FIRST_ENV_e3=v3=v3") { + c.Fatalf("Incorrect output: %s", out) + } +} diff --git a/links/links.go b/links/links.go index 8bbacdd3d..935bff4ae 100644 --- a/links/links.go +++ b/links/links.go @@ -107,8 +107,8 @@ func (l *Link) ToEnv() []string { if l.ChildEnvironment != nil { for _, v := range l.ChildEnvironment { - parts := strings.Split(v, "=") - if len(parts) != 2 { + parts := strings.SplitN(v, "=", 2) + if len(parts) < 2 { continue } // Ignore a few variables that are added during docker build (and not really relevant to linked containers) From cd4f507b42e800d148b211ca2c780d01192a9041 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Sat, 25 Apr 2015 05:46:47 -0700 Subject: [PATCH 642/999] Fix race condition in API commit test Signed-off-by: Doug Davis --- integration-cli/docker_api_containers_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index f14bd91c1..50958a6cd 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -614,14 +614,14 @@ func (s *DockerSuite) TestContainerApiTop(c *check.C) { } func (s *DockerSuite) TestContainerApiCommit(c *check.C) { - out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "touch /test").CombinedOutput() + cName := "testapicommit" + out, err := exec.Command(dockerBinary, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test").CombinedOutput() if err != nil { c.Fatal(err, out) } - id := strings.TrimSpace(string(out)) name := "testcommit" + stringid.GenerateRandomID() - status, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+id, nil) + status, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+cName, nil) c.Assert(status, check.Equals, http.StatusCreated) c.Assert(err, check.IsNil) From fa9299f4c03cf097887db26dadcc63c711c925f1 Mon Sep 17 00:00:00 2001 From: Ed Costello Date: Sat, 25 Apr 2015 14:57:01 -0400 Subject: [PATCH 643/999] Copy edits for typos Signed-off-by: Ed Costello --- docs/sources/articles/b2d_volume_resize.md | 2 +- docs/sources/articles/networking.md | 2 +- docs/sources/articles/puppet.md | 2 +- docs/sources/articles/security.md | 2 +- docs/sources/installation/SUSE.md | 2 +- docs/sources/project/create-pr.md | 2 +- docs/sources/project/review-pr.md | 2 +- docs/sources/project/test-and-docs.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.10.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.11.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.12.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.13.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.14.md | 2 +- docs/sources/reference/api/docker_remote_api_v1.15.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.16.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.17.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.18.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.19.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.9.md | 2 +- docs/sources/reference/builder.md | 8 ++++---- docs/sources/reference/run.md | 2 +- 21 files changed, 29 insertions(+), 29 deletions(-) diff --git a/docs/sources/articles/b2d_volume_resize.md b/docs/sources/articles/b2d_volume_resize.md index 65238c669..53c859095 100644 --- a/docs/sources/articles/b2d_volume_resize.md +++ b/docs/sources/articles/b2d_volume_resize.md @@ -60,7 +60,7 @@ You might need to create the bus before you can add the ISO. ## 5. Add the new VDI image In the settings for the Boot2Docker image in VirtualBox, remove the VMDK image -from the SATA contoller and add the VDI image. +from the SATA controller and add the VDI image. diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index 18529a086..823b450c7 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -576,7 +576,7 @@ As soon as the router wants to send an IPv6 packet to the first container it will transmit a neighbor solicitation request, asking, who has `2001:db8::c009`? But it will get no answer because noone on this subnet has this address. The container with this address is hidden behind the Docker host. -The Docker host has to listen to neighbor solication requests for the container +The Docker host has to listen to neighbor solicitation requests for the container address and send a response that itself is the device that is responsible for the address. This is done by a Kernel feature called `NDP Proxy`. You can enable it by executing diff --git a/docs/sources/articles/puppet.md b/docs/sources/articles/puppet.md index 50504cd47..a1b3d273a 100644 --- a/docs/sources/articles/puppet.md +++ b/docs/sources/articles/puppet.md @@ -1,5 +1,5 @@ page_title: Using Puppet -page_description: Installating and using Puppet +page_description: Installing and using Puppet page_keywords: puppet, installation, usage, docker, documentation # Using Puppet diff --git a/docs/sources/articles/security.md b/docs/sources/articles/security.md index 39a247c38..42d15e88c 100644 --- a/docs/sources/articles/security.md +++ b/docs/sources/articles/security.md @@ -249,7 +249,7 @@ may still be utilized by Docker containers on supported kernels, by directly using the clone syscall, or utilizing the 'unshare' utility. Using this, some users may find it possible to drop more capabilities from their process as user namespaces provide -an artifical capabilities set. Likewise, however, this artifical +an artificial capabilities set. Likewise, however, this artificial capabilities set may require use of 'capsh' to restrict the user-namespace capabilities set when using 'unshare'. diff --git a/docs/sources/installation/SUSE.md b/docs/sources/installation/SUSE.md index 2a0aa91d9..756ed6b5c 100644 --- a/docs/sources/installation/SUSE.md +++ b/docs/sources/installation/SUSE.md @@ -8,7 +8,7 @@ Docker is available in **openSUSE 12.3 and later**. Please note that due to its current limitations Docker is able to run only **64 bit** architecture. Docker is not part of the official repositories of openSUSE 12.3 and -openSUSE 13.1. Hence it is neccessary to add the [Virtualization +openSUSE 13.1. Hence it is necessary to add the [Virtualization repository](https://build.opensuse.org/project/show/Virtualization) from [OBS](https://build.opensuse.org/) to install the `docker` package. diff --git a/docs/sources/project/create-pr.md b/docs/sources/project/create-pr.md index e9123c463..613ab6911 100644 --- a/docs/sources/project/create-pr.md +++ b/docs/sources/project/create-pr.md @@ -77,7 +77,7 @@ Always rebase and squash your commits before making a pull request. `git commit -s` - Make sure your message includes Date: Thu, 26 Mar 2015 23:14:31 +0000 Subject: [PATCH 644/999] Happy birthday Docker! cgroup-parent option for docker build. Thanks to Michael, Nathan and Jessie for their support! #42 Signed-off-by: Julien Barbier --- api/client/build.go | 2 ++ api/server/server.go | 1 + builder/evaluator.go | 13 +++++++------ builder/internals.go | 13 +++++++------ builder/job.go | 2 ++ 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/api/client/build.go b/api/client/build.go index 800e04ac9..107a1995f 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -58,6 +58,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { flCpuQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota") flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") + flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container") cmd.Require(flag.Exact, 1) cmd.ParseFlags(args, true) @@ -276,6 +277,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v.Set("cpuquota", strconv.FormatInt(*flCpuQuota, 10)) v.Set("memory", strconv.FormatInt(memory, 10)) v.Set("memswap", strconv.FormatInt(memorySwap, 10)) + v.Set("cgroupparent", *flCgroupParent) v.Set("dockerfile", *dockerfileName) diff --git a/api/server/server.go b/api/server/server.go index cdc6c1815..08932b90e 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1346,6 +1346,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R buildConfig.CpuQuota = int64Value(r, "cpuquota") buildConfig.CpuSetCpus = r.FormValue("cpusetcpus") buildConfig.CpuSetMems = r.FormValue("cpusetmems") + buildConfig.CgroupParent = r.FormValue("cgroupparent") // Job cancellation. Note: not all job types support this. if closeNotifier, ok := w.(http.CloseNotifier); ok { diff --git a/builder/evaluator.go b/builder/evaluator.go index 9a2b57a8f..214499bc0 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -121,12 +121,13 @@ type Builder struct { noBaseImage bool // indicates that this build does not start from any base image, but is being built from an empty file system. // Set resource restrictions for build containers - cpuSetCpus string - cpuSetMems string - cpuShares int64 - cpuQuota int64 - memory int64 - memorySwap int64 + cpuSetCpus string + cpuSetMems string + cpuShares int64 + cpuQuota int64 + cgroupParent string + memory int64 + memorySwap int64 cancelled <-chan struct{} // When closed, job was cancelled. } diff --git a/builder/internals.go b/builder/internals.go index ba7d45bcb..7c3b924e7 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -546,12 +546,13 @@ func (b *Builder) create() (*daemon.Container, error) { b.Config.Image = b.image hostConfig := &runconfig.HostConfig{ - CpuShares: b.cpuShares, - CpuQuota: b.cpuQuota, - CpusetCpus: b.cpuSetCpus, - CpusetMems: b.cpuSetMems, - Memory: b.memory, - MemorySwap: b.memorySwap, + CpuShares: b.cpuShares, + CpuQuota: b.cpuQuota, + CpusetCpus: b.cpuSetCpus, + CpusetMems: b.cpuSetMems, + CgroupParent: b.cgroupParent, + Memory: b.memory, + MemorySwap: b.memorySwap, } config := *b.Config diff --git a/builder/job.go b/builder/job.go index 0ad488aae..a64375c96 100644 --- a/builder/job.go +++ b/builder/job.go @@ -52,6 +52,7 @@ type Config struct { CpuQuota int64 CpuSetCpus string CpuSetMems string + CgroupParent string AuthConfig *cliconfig.AuthConfig ConfigFile *cliconfig.ConfigFile @@ -166,6 +167,7 @@ func Build(d *daemon.Daemon, buildConfig *Config) error { cpuQuota: buildConfig.CpuQuota, cpuSetCpus: buildConfig.CpuSetCpus, cpuSetMems: buildConfig.CpuSetMems, + cgroupParent: buildConfig.CgroupParent, memory: buildConfig.Memory, memorySwap: buildConfig.MemorySwap, cancelled: buildConfig.WaitCancelled(), From 81897adcee8813efdff8374a1f46125b7e094847 Mon Sep 17 00:00:00 2001 From: Julien Barbier Date: Thu, 26 Mar 2015 23:45:17 +0000 Subject: [PATCH 645/999] Adding doc Signed-off-by: Julien Barbier --- docs/sources/reference/commandline/cli.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 26659c8ff..072d7b40c 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -627,8 +627,9 @@ is returned by the `docker attach` command to its caller too: -m, --memory="" Memory limit for all build containers --memory-swap="" Total memory (memory + swap), `-1` to disable swap -c, --cpu-shares CPU Shares (relative weight) - --cpuset-cpus="" CPUs in which to allow execution, e.g. `0-3`, `0,1` --cpuset-mems="" MEMs in which to allow execution, e.g. `0-3`, `0,1` + --cpuset-cpus="" CPUs in which to allow exection, e.g. `0-3`, `0,1` + --cgroup-parent="" Optional parent cgroup for the container Builds Docker images from a Dockerfile and a "context". A build's context is the files located in the specified `PATH` or `URL`. The build process can @@ -847,6 +848,9 @@ you refer to it on the command line. > children) for security reasons, and to ensure repeatable builds on remote > Docker hosts. This is also the reason why `ADD ../file` will not work. +`docker build` has a `--cgroup-parent` option that causes the containers used +in the build to be run with this option. + ## commit Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]] From f40dd69c97a5a3797f07d52fe5f76e296ef629dc Mon Sep 17 00:00:00 2001 From: Marianna Date: Fri, 27 Mar 2015 19:18:24 -0700 Subject: [PATCH 646/999] Add test for cgroup parent flag for build Signed-off-by: Marianna --- integration-cli/docker_cli_build_test.go | 27 ++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 6d6805aef..fc5f13539 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5251,3 +5251,30 @@ func (s *DockerSuite) TestBuildEmptyStringVolume(c *check.C) { } } + +func TestBuildContainerWithCgroupParent(t *testing.T) { + testRequires(t, NativeExecDriver) + defer deleteImages() + + cgroupParent := "test" + data, err := ioutil.ReadFile("/proc/self/cgroup") + if err != nil { + t.Fatalf("failed to read '/proc/self/cgroup - %v", err) + } + selfCgroupPaths := parseCgroupPaths(string(data)) + _, found := selfCgroupPaths["memory"] + if !found { + t.Fatalf("unable to find self cpu cgroup path. CgroupsPath: %v", selfCgroupPaths) + } + cmd := exec.Command(dockerBinary, "build", "--cgroup-parent", cgroupParent , "-") + cmd.Stdin = strings.NewReader(` +FROM busybox +RUN cat /proc/self/cgroup +`) + + out, _, err := runCommandWithOutput(cmd) + if err != nil { + t.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err) + } + logDone("build - cgroup parent") +} From f039c699a40397b71f05838e9a9c67100ae474c6 Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Tue, 31 Mar 2015 18:27:53 -0700 Subject: [PATCH 647/999] Fix gofmt Mischevious comma is mischevious Signed-off-by: Nathan LeClaire --- integration-cli/docker_cli_build_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index fc5f13539..35ea20f56 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5266,7 +5266,7 @@ func TestBuildContainerWithCgroupParent(t *testing.T) { if !found { t.Fatalf("unable to find self cpu cgroup path. CgroupsPath: %v", selfCgroupPaths) } - cmd := exec.Command(dockerBinary, "build", "--cgroup-parent", cgroupParent , "-") + cmd := exec.Command(dockerBinary, "build", "--cgroup-parent", cgroupParent, "-") cmd.Stdin = strings.NewReader(` FROM busybox RUN cat /proc/self/cgroup From 65aba0c9d6a72fc89f87c328e9ca21e20bf3cf7a Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Fri, 17 Apr 2015 15:35:56 -0700 Subject: [PATCH 648/999] Add dep to test Signed-off-by: Nathan LeClaire --- integration-cli/docker_cli_build_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 35ea20f56..93affbc5f 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5254,6 +5254,7 @@ func (s *DockerSuite) TestBuildEmptyStringVolume(c *check.C) { func TestBuildContainerWithCgroupParent(t *testing.T) { testRequires(t, NativeExecDriver) + testRequires(t, SameHostDaemon) defer deleteImages() cgroupParent := "test" From a8dfafc98642455ed83f9422f3174fa828dde4df Mon Sep 17 00:00:00 2001 From: Marianna Date: Fri, 17 Apr 2015 19:01:16 -0700 Subject: [PATCH 649/999] Make the docs for --cgroup-parent better Signed-off-by: Marianna --- docs/sources/reference/commandline/cli.md | 6 ++++-- docs/sources/reference/run.md | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 072d7b40c..3ffa5bd5c 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -848,8 +848,10 @@ you refer to it on the command line. > children) for security reasons, and to ensure repeatable builds on remote > Docker hosts. This is also the reason why `ADD ../file` will not work. -`docker build` has a `--cgroup-parent` option that causes the containers used -in the build to be run with this option. +When `docker build` is run with the `--cgroup-parent` option the containers used +in the build will be run with the [corresponding `docker run` +flag](/reference/run/#specifying-custom-cgroups). + ## commit diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 7218fab64..0cd43eaac 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -465,6 +465,13 @@ Note: You would have to write policy defining a `svirt_apache_t` type. +## Specifying custom cgroups + +Using the `--cgroup-parent` flag, you can pass a specific cgroup to run a +container in. This allows you to create and manage cgroups on their own. You can +define custom resources for those cgroups and put containers under a common +parent group. + ## Runtime constraints on resources The operator can also adjust the performance parameters of the From 9dbe12b792a46538c01862930c85ace18dbe14f3 Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Sat, 25 Apr 2015 16:42:30 -0400 Subject: [PATCH 650/999] Migrate integration test to new method Signed-off-by: Nathan LeClaire --- integration-cli/docker_cli_build_test.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 93affbc5f..dd8635f95 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5252,20 +5252,20 @@ func (s *DockerSuite) TestBuildEmptyStringVolume(c *check.C) { } -func TestBuildContainerWithCgroupParent(t *testing.T) { - testRequires(t, NativeExecDriver) - testRequires(t, SameHostDaemon) +func (s *DockerSuite) TestBuildContainerWithCgroupParent(c *check.C) { + testRequires(c, NativeExecDriver) + testRequires(c, SameHostDaemon) defer deleteImages() cgroupParent := "test" data, err := ioutil.ReadFile("/proc/self/cgroup") if err != nil { - t.Fatalf("failed to read '/proc/self/cgroup - %v", err) + c.Fatalf("failed to read '/proc/self/cgroup - %v", err) } selfCgroupPaths := parseCgroupPaths(string(data)) _, found := selfCgroupPaths["memory"] if !found { - t.Fatalf("unable to find self cpu cgroup path. CgroupsPath: %v", selfCgroupPaths) + c.Fatalf("unable to find self cpu cgroup path. CgroupsPath: %v", selfCgroupPaths) } cmd := exec.Command(dockerBinary, "build", "--cgroup-parent", cgroupParent, "-") cmd.Stdin = strings.NewReader(` @@ -5275,7 +5275,6 @@ RUN cat /proc/self/cgroup out, _, err := runCommandWithOutput(cmd) if err != nil { - t.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err) + c.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err) } - logDone("build - cgroup parent") } From 26543e03095eaa45d9afc12c029f07539323fee5 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 22 Apr 2015 00:47:51 +0200 Subject: [PATCH 651/999] Replace json.Unmarshal with json.Decoder().Decode() Signed-off-by: Antonio Murdaca --- builder/parser/line_parsers.go | 2 +- daemon/execdriver/lxc/init.go | 8 ++++---- graph/load.go | 26 +++++++++++++++----------- graph/tags.go | 5 +++-- pkg/parsers/filters/parse.go | 3 +-- 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/builder/parser/line_parsers.go b/builder/parser/line_parsers.go index 5f65a8762..8db360ca3 100644 --- a/builder/parser/line_parsers.go +++ b/builder/parser/line_parsers.go @@ -233,7 +233,7 @@ func parseString(rest string) (*Node, map[string]bool, error) { // parseJSON converts JSON arrays to an AST. func parseJSON(rest string) (*Node, map[string]bool, error) { var myJson []interface{} - if err := json.Unmarshal([]byte(rest), &myJson); err != nil { + if err := json.NewDecoder(strings.NewReader(rest)).Decode(&myJson); err != nil { return nil, nil, err } diff --git a/daemon/execdriver/lxc/init.go b/daemon/execdriver/lxc/init.go index 6cdbf775e..eca1c02e2 100644 --- a/daemon/execdriver/lxc/init.go +++ b/daemon/execdriver/lxc/init.go @@ -4,7 +4,6 @@ import ( "encoding/json" "flag" "fmt" - "io/ioutil" "log" "os" "os/exec" @@ -107,12 +106,13 @@ func getArgs() *InitArgs { func setupEnv(args *InitArgs) error { // Get env var env []string - content, err := ioutil.ReadFile(".dockerenv") + dockerenv, err := os.Open(".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) + defer dockerenv.Close() + if err := json.NewDecoder(dockerenv).Decode(&env); err != nil { + return fmt.Errorf("Unable to decode environment variables: %v", err) } // Propagate the plugin-specific container env variable env = append(env, "container="+os.Getenv("container")) diff --git a/graph/load.go b/graph/load.go index be968bab5..d978b1ee8 100644 --- a/graph/load.go +++ b/graph/load.go @@ -58,22 +58,26 @@ func (s *TagStore) Load(inTar io.ReadCloser, outStream io.Writer) error { } } - repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories")) - if err == nil { - repositories := map[string]Repository{} - if err := json.Unmarshal(repositoriesJson, &repositories); err != nil { + reposJSONFile, err := os.Open(path.Join(tmpImageDir, "repo", "repositories")) + if err != nil { + if !os.IsNotExist(err) { return err } + return nil + } + defer reposJSONFile.Close() - for imageName, tagMap := range repositories { - for tag, address := range tagMap { - if err := s.SetLoad(imageName, tag, address, true, outStream); err != nil { - return err - } + repositories := map[string]Repository{} + if err := json.NewDecoder(reposJSONFile).Decode(&repositories); err != nil { + return err + } + + for imageName, tagMap := range repositories { + for tag, address := range tagMap { + if err := s.SetLoad(imageName, tag, address, true, outStream); err != nil { + return err } } - } else if !os.IsNotExist(err) { - return err } return nil diff --git a/graph/tags.go b/graph/tags.go index 39f0ffc29..abffe2f56 100644 --- a/graph/tags.go +++ b/graph/tags.go @@ -115,11 +115,12 @@ func (store *TagStore) save() error { } func (store *TagStore) reload() error { - jsonData, err := ioutil.ReadFile(store.path) + f, err := os.Open(store.path) if err != nil { return err } - if err := json.Unmarshal(jsonData, store); err != nil { + defer f.Close() + if err := json.NewDecoder(f).Decode(&store); err != nil { return err } return nil diff --git a/pkg/parsers/filters/parse.go b/pkg/parsers/filters/parse.go index 9c056bb3c..df5486d51 100644 --- a/pkg/parsers/filters/parse.go +++ b/pkg/parsers/filters/parse.go @@ -58,8 +58,7 @@ func FromParam(p string) (Args, error) { if len(p) == 0 { return args, nil } - err := json.Unmarshal([]byte(p), &args) - if err != nil { + if err := json.NewDecoder(strings.NewReader(p)).Decode(&args); err != nil { return nil, err } return args, nil From ba79f0ca1f389e3c87c19b2c326b3b6afbebc8ef Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Sun, 26 Apr 2015 10:32:43 -0700 Subject: [PATCH 652/999] Adding James and theJeztah to the list Signed-off-by: Mary Anthony --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index b708af774..5c02fa671 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -409,6 +409,8 @@ made through a pull request. "fredlf", "james", "moxiegirl", + "thaJeztah", + "jamtur01", "spf13", "sven" ] From cfd0f53b03d26c370e37abf651d7f481ee8c6912 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Sun, 26 Apr 2015 15:36:01 -0400 Subject: [PATCH 653/999] Add missing API docs about filtering by label. Signed-off-by: Daniel Nephin --- docs/sources/reference/api/docker_remote_api_v1.18.md | 2 ++ docs/sources/reference/api/docker_remote_api_v1.19.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index c97fe9c61..a91ca8417 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -91,6 +91,7 @@ Query Parameters: - **filters** - a json encoded value of the filters (a map[string][]string) to process on the containers list. Available filters: - exited=<int> -- containers with exit code of <int> - status=(restarting|running|paused|exited) + - label=`key` or `key=value` of a container label Status Codes: @@ -1191,6 +1192,7 @@ Query Parameters: - **all** – 1/True/true or 0/False/false, default false - **filters** – a json encoded value of the filters (a map[string][]string) to process on the images list. Available filters: - dangling=true + - label=`key` or `key=value` of an image label ### Build image from a Dockerfile diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 8c27de159..cede2e107 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -91,6 +91,7 @@ Query Parameters: - **filters** - a json encoded value of the filters (a map[string][]string) to process on the containers list. Available filters: - exited=<int> -- containers with exit code of <int> - status=(restarting|running|paused|exited) + - label=`key` or `key=value` of a container label Status Codes: @@ -1194,6 +1195,7 @@ Query Parameters: - **all** – 1/True/true or 0/False/false, default false - **filters** – a json encoded value of the filters (a map[string][]string) to process on the images list. Available filters: - dangling=true + - label=`key` or `key=value` of an image label ### Build image from a Dockerfile From f930c86cb1ea94525ecc8a0331277afad99d4c1a Mon Sep 17 00:00:00 2001 From: Jinsoo Park Date: Thu, 26 Mar 2015 14:14:51 -0400 Subject: [PATCH 654/999] add library files for socat in mkimage-unittest.sh in https://docs.docker.com/articles/ambassador_pattern_linking/ svendowideit/ambassador images is from docker-ut built using this script and uses socat but socat complains as follows socat: error while loading shared libraries: libreadline.so.5: cannot open shared object file: No such file or directory socat: error while loading shared libraries: libssl.so.1.0.0: cannot open shared object file: No such file or directory socat: error while loading shared libraries: libcrypto.so.1.0.0: cannot open shared object file: No such file or directory socat: error while loading shared libraries: libtinfo.so.5: cannot open shared object file: No such file or directory /usr/lib/x86_64-linux-gnu/lib{crypto,ssl}.so* lib are symlinks so removing -P option from cp adding libreadline.so and libtinfo.so Signed-off-by: Jinsoo Park update libssl.so path Signed-off-by: Jinsoo Park Remove mkimage-unittest.sh Signed-off-by: Jinsoo Park --- contrib/mkimage-unittest.sh | 49 ------------------------------------- 1 file changed, 49 deletions(-) delete mode 100755 contrib/mkimage-unittest.sh diff --git a/contrib/mkimage-unittest.sh b/contrib/mkimage-unittest.sh deleted file mode 100755 index feebb17b0..000000000 --- a/contrib/mkimage-unittest.sh +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env bash -# Generate a very minimal filesystem based on busybox-static, -# and load it into the local docker under the name "docker-ut". - -missing_pkg() { - echo "Sorry, I could not locate $1" - echo "Try 'apt-get install ${2:-$1}'?" - exit 1 -} - -BUSYBOX=$(which busybox) -[ "$BUSYBOX" ] || missing_pkg busybox busybox-static -SOCAT=$(which socat) -[ "$SOCAT" ] || missing_pkg socat - -shopt -s extglob -set -ex -ROOTFS=`mktemp -d ${TMPDIR:-/var/tmp}/rootfs-busybox.XXXXXXXXXX` -trap "rm -rf $ROOTFS" INT QUIT TERM -cd $ROOTFS - -mkdir bin etc dev dev/pts lib proc sys tmp -touch etc/resolv.conf -cp /etc/nsswitch.conf etc/nsswitch.conf -echo root:x:0:0:root:/:/bin/sh > etc/passwd -echo daemon:x:1:1:daemon:/usr/sbin:/bin/sh >> etc/passwd -echo root:x:0: > etc/group -echo daemon:x:1: >> etc/group -ln -s lib lib64 -ln -s bin sbin -cp $BUSYBOX $SOCAT bin -for X in $(busybox --list) -do - ln -s busybox bin/$X -done -rm bin/init -ln bin/busybox bin/init -cp -P /lib/x86_64-linux-gnu/lib{pthread*,c*(-*),dl*(-*),nsl*(-*),nss_*,util*(-*),wrap,z}.so* lib -cp /lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 lib -cp -P /usr/lib/x86_64-linux-gnu/lib{crypto,ssl}.so* lib -for X in console null ptmx random stdin stdout stderr tty urandom zero -do - cp -a /dev/$X dev -done - -chmod 0755 $ROOTFS # See #486 -tar --numeric-owner -cf- . | docker import - docker-ut -docker run -i -u root docker-ut /bin/echo Success. -rm -rf $ROOTFS From 6d9439c627e36349679249d991400da45eb75587 Mon Sep 17 00:00:00 2001 From: He Simei Date: Mon, 27 Apr 2015 12:41:03 +0800 Subject: [PATCH 655/999] remove useless http call from export Signed-off-by: He Simei --- api/client/export.go | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/api/client/export.go b/api/client/export.go index 8f1642f60..1ff46f9b5 100644 --- a/api/client/export.go +++ b/api/client/export.go @@ -3,7 +3,6 @@ package client import ( "errors" "io" - "net/url" "os" flag "github.com/docker/docker/pkg/mflag" @@ -34,19 +33,9 @@ func (cli *DockerCli) CmdExport(args ...string) error { return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.") } - if len(cmd.Args()) == 1 { - image := cmd.Arg(0) - if err := cli.stream("GET", "/containers/"+image+"/export", nil, output, nil); err != nil { - return err - } - } else { - v := url.Values{} - for _, arg := range cmd.Args() { - v.Add("names", arg) - } - if err := cli.stream("GET", "/containers/get?"+v.Encode(), nil, output, nil); err != nil { - return err - } + image := cmd.Arg(0) + if err := cli.stream("GET", "/containers/"+image+"/export", nil, output, nil); err != nil { + return err } return nil From accbbfeae4c2f3ceb68060e08e46e382cdefc975 Mon Sep 17 00:00:00 2001 From: jmzwcn Date: Mon, 27 Apr 2015 15:50:47 +0800 Subject: [PATCH 656/999] Remove dead code from daemon/daemon.go fix #12492 Signed-off-by: Daniel Zhang --- daemon/daemon.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index d186854ad..99f5ae636 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -225,7 +225,6 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err if container.IsRunning() { logrus.Debugf("killing old running container %s", container.ID) - existingPid := container.Pid container.SetStopped(&execdriver.ExitStatus{ExitCode: 0}) // We only have to handle this for lxc because the other drivers will ensure that @@ -237,11 +236,6 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err cmd := &execdriver.Command{ ID: container.ID, } - var err error - cmd.ProcessConfig.Process, err = os.FindProcess(existingPid) - if err != nil { - logrus.Debugf("cannot find existing process for %d", existingPid) - } daemon.execDriver.Terminate(cmd) } From c7b2632dc8550c8aa80ebd8b229809ac37fa53e1 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 27 Apr 2015 13:56:55 +0200 Subject: [PATCH 657/999] Remove c.Fatal from goroutine in TestGetContainersAttachWebsocket Signed-off-by: Antonio Murdaca --- integration-cli/docker_api_attach_test.go | 38 ++++++++++++++++------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/integration-cli/docker_api_attach_test.go b/integration-cli/docker_api_attach_test.go index ce7f85d30..c784d5c36 100644 --- a/integration-cli/docker_api_attach_test.go +++ b/integration-cli/docker_api_attach_test.go @@ -40,24 +40,38 @@ func (s *DockerSuite) TestGetContainersAttachWebsocket(c *check.C) { expected := []byte("hello") actual := make([]byte, len(expected)) - outChan := make(chan string) + + outChan := make(chan error) go func() { - if _, err := ws.Read(actual); err != nil { - c.Fatal(err) - } - outChan <- "done" + _, err := ws.Read(actual) + outChan <- err + close(outChan) }() - inChan := make(chan string) + inChan := make(chan error) go func() { - if _, err := ws.Write(expected); err != nil { - c.Fatal(err) - } - inChan <- "done" + _, err := ws.Write(expected) + inChan <- err + close(inChan) }() - <-inChan - <-outChan + select { + case err := <-inChan: + if err != nil { + c.Fatal(err) + } + case <-time.After(5 * time.Second): + c.Fatal("Timeout writing to ws") + } + + select { + case err := <-outChan: + if err != nil { + c.Fatal(err) + } + case <-time.After(5 * time.Second): + c.Fatal("Timeout reading from ws") + } if !bytes.Equal(expected, actual) { c.Fatal("Expected output on websocket to match input") From f30d1c1835618eadea5d0a68d1301dffd9f09b22 Mon Sep 17 00:00:00 2001 From: Aidan Hobson Sayers Date: Mon, 27 Apr 2015 01:07:30 +0100 Subject: [PATCH 658/999] Prevent deadlock on attempt to use own net Signed-off-by: Aidan Hobson Sayers --- daemon/container.go | 3 +++ integration-cli/docker_cli_run_test.go | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/daemon/container.go b/daemon/container.go index bdfcbf447..01eef4d31 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1515,6 +1515,9 @@ func (container *Container) getNetworkedContainer() (*Container, error) { if err != nil { return nil, err } + if container == nc { + return nil, fmt.Errorf("cannot join own network") + } if !nc.IsRunning() { return nil, fmt.Errorf("cannot join network of a non running container: %s", parts[1]) } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 342137c47..b7961126c 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2657,6 +2657,14 @@ func (s *DockerSuite) TestContainerNetworkMode(c *check.C) { } } +func (s *DockerSuite) TestContainerNetworkModeToSelf(c *check.C) { + cmd := exec.Command(dockerBinary, "run", "--name=me", "--net=container:me", "busybox", "true") + out, _, err := runCommandWithOutput(cmd) + if err == nil || !strings.Contains(out, "cannot join own network") { + c.Fatalf("using container net mode to self should result in an error") + } +} + func (s *DockerSuite) TestRunModePidHost(c *check.C) { testRequires(c, NativeExecDriver, SameHostDaemon) From 9e0ffae864d6679084e84e2e27cbb9d350f6dbae Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Tue, 21 Apr 2015 21:51:41 -0400 Subject: [PATCH 659/999] remove some uneeded sleeps in tests Signed-off-by: Brian Goff --- integration-cli/docker_api_logs_test.go | 38 +++++++++++---- integration-cli/docker_cli_wait_test.go | 65 ++++++++++++++++--------- 2 files changed, 71 insertions(+), 32 deletions(-) diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go index bf0e1fbf4..a1723ef21 100644 --- a/integration-cli/docker_api_logs_test.go +++ b/integration-cli/docker_api_logs_test.go @@ -1,28 +1,46 @@ package main import ( + "bufio" "bytes" "fmt" "net/http" "os/exec" + "strings" + "time" "github.com/go-check/check" ) func (s *DockerSuite) TestLogsApiWithStdout(c *check.C) { - name := "logs_test" - - runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "bin/sh", "-c", "sleep 10 && echo "+name) - if out, _, err := runCommandWithOutput(runCmd); err != nil { - c.Fatal(out, err) + out, _ := dockerCmd(c, "run", "-d", "-t", "busybox", "/bin/sh", "-c", "while true; do echo hello; sleep 1; done") + id := strings.TrimSpace(out) + if err := waitRun(id); err != nil { + c.Fatal(err) } - status, body, err := sockRequest("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", name), nil) - c.Assert(status, check.Equals, http.StatusOK) - c.Assert(err, check.IsNil) + type logOut struct { + out string + status int + err error + } + chLog := make(chan logOut) - if !bytes.Contains(body, []byte(name)) { - c.Fatalf("Expected %s, got %s", name, string(body[:])) + go func() { + statusCode, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", id), nil, "") + out, _ := bufio.NewReader(body).ReadString('\n') + chLog <- logOut{strings.TrimSpace(out), statusCode, err} + }() + + select { + case l := <-chLog: + c.Assert(l.status, check.Equals, http.StatusOK) + c.Assert(l.err, check.IsNil) + if !strings.HasSuffix(l.out, "hello") { + c.Fatalf("expected log output to container 'hello', but it does not") + } + case <-time.After(2 * time.Second): + c.Fatal("timeout waiting for logs to exit") } } diff --git a/integration-cli/docker_cli_wait_test.go b/integration-cli/docker_cli_wait_test.go index 09d0272bc..21f04faf0 100644 --- a/integration-cli/docker_cli_wait_test.go +++ b/integration-cli/docker_cli_wait_test.go @@ -44,19 +44,29 @@ func (s *DockerSuite) TestWaitNonBlockedExitZero(c *check.C) { // blocking wait with 0 exit code func (s *DockerSuite) TestWaitBlockedExitZero(c *check.C) { - - runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10") - out, _, err := runCommandWithOutput(runCmd) - if err != nil { - c.Fatal(out, err) - } + out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "trap 'exit 0' SIGTERM; while true; do sleep 0.01; done") containerID := strings.TrimSpace(out) - runCmd = exec.Command(dockerBinary, "wait", containerID) - out, _, err = runCommandWithOutput(runCmd) + if err := waitRun(containerID); err != nil { + c.Fatal(err) + } - if err != nil || strings.TrimSpace(out) != "0" { - c.Fatal("failed to set up container", out, err) + chWait := make(chan string) + go func() { + out, _, _ := runCommandWithOutput(exec.Command(dockerBinary, "wait", containerID)) + chWait <- out + }() + + time.Sleep(100 * time.Millisecond) + dockerCmd(c, "stop", containerID) + + select { + case status := <-chWait: + if strings.TrimSpace(status) != "0" { + c.Fatalf("expected exit 0, got %s", status) + } + case <-time.After(2 * time.Second): + c.Fatal("timeout waiting for `docker wait` to exit") } } @@ -97,19 +107,30 @@ func (s *DockerSuite) TestWaitNonBlockedExitRandom(c *check.C) { // blocking wait with random exit code func (s *DockerSuite) TestWaitBlockedExitRandom(c *check.C) { - - runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10; exit 99") - out, _, err := runCommandWithOutput(runCmd) - if err != nil { - c.Fatal(out, err) - } + out, _ := dockerCmd(c, "run", "-d", "busybox", "sh", "-c", "trap 'exit 99' SIGTERM; while true; do sleep 0.01; done") containerID := strings.TrimSpace(out) - - runCmd = exec.Command(dockerBinary, "wait", containerID) - out, _, err = runCommandWithOutput(runCmd) - - if err != nil || strings.TrimSpace(out) != "99" { - c.Fatal("failed to set up container", out, err) + if err := waitRun(containerID); err != nil { + c.Fatal(err) + } + if err := waitRun(containerID); err != nil { + c.Fatal(err) } + chWait := make(chan string) + go func() { + out, _, _ := runCommandWithOutput(exec.Command(dockerBinary, "wait", containerID)) + chWait <- out + }() + + time.Sleep(100 * time.Millisecond) + dockerCmd(c, "stop", containerID) + + select { + case status := <-chWait: + if strings.TrimSpace(status) != "99" { + c.Fatalf("expected exit 99, got %s", status) + } + case <-time.After(2 * time.Second): + c.Fatal("timeout waiting for `docker wait` to exit") + } } From 29f379ea6e2a68d8ac4bf41d3559a52066a275a0 Mon Sep 17 00:00:00 2001 From: Zhang Wei Date: Mon, 27 Apr 2015 20:54:51 +0800 Subject: [PATCH 660/999] improve docker man page fix issue #12708: "docker man page is misleading about format of commands" Signed-off-by: Zhang Wei --- docs/man/docker.1.md | 114 ++++++++++++++++++++++++++++--------------- 1 file changed, 76 insertions(+), 38 deletions(-) diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index 0196b6364..75b9b80cc 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -128,120 +128,158 @@ unix://[/path/to/socket] to use. Enable selinux support. Default is false. SELinux does not presently support the BTRFS storage driver. # COMMANDS -**docker-attach(1)** +**attach** Attach to a running container + See **docker-attach(1)** for full documentation on the **attach** command. -**docker-build(1)** +**build** Build an image from a Dockerfile + See **docker-build(1)** for full documentation on the **build** command. -**docker-commit(1)** +**commit** Create a new image from a container's changes + See **docker-commit(1)** for full documentation on the **commit** command. -**docker-cp(1)** +**cp** Copy files/folders from a container's filesystem to the host + See **docker-cp(1)** for full documentation on the **cp** command. -**docker-create(1)** +**create** Create a new container + See **docker-create(1)** for full documentation on the **create** command. -**docker-diff(1)** +**diff** Inspect changes on a container's filesystem + See **docker-diff(1)** for full documentation on the **diff** command. -**docker-events(1)** +**events** Get real time events from the server + See **docker-events(1)** for full documentation on the **events** command. -**docker-exec(1)** +**exec** Run a command in a running container + See **docker-exec(1)** for full documentation on the **exec** command. -**docker-export(1)** +**export** Stream the contents of a container as a tar archive + See **docker-export(1)** for full documentation on the **export** command. -**docker-history(1)** +**history** Show the history of an image + See **docker-history(1)** for full documentation on the **history** command. -**docker-images(1)** +**images** List images + See **docker-images(1)** for full documentation on the **images** command. -**docker-import(1)** +**import** Create a new filesystem image from the contents of a tarball + See **docker-import(1)** for full documentation on the **import** command. -**docker-info(1)** +**info** Display system-wide information + See **docker-info(1)** for full documentation on the **info** command. -**docker-inspect(1)** +**inspect** Return low-level information on a container or image + See **docker-inspect(1)** for full documentation on the **inspect** command. -**docker-kill(1)** +**kill** Kill a running container (which includes the wrapper process and everything inside it) + See **docker-kill(1)** for full documentation on the **kill** command. -**docker-load(1)** +**load** Load an image from a tar archive + See **docker-load(1)** for full documentation on the **load** command. -**docker-login(1)** +**login** Register or login to a Docker Registry + See **docker-login(1)** for full documentation on the **login** command. -**docker-logout(1)** +**logout** Log the user out of a Docker Registry + See **docker-logout(1)** for full documentation on the **logout** command. -**docker-logs(1)** +**logs** Fetch the logs of a container + See **docker-logs(1)** for full documentation on the **logs** command. -**docker-pause(1)** +**pause** Pause all processes within a container + See **docker-pause(1)** for full documentation on the **pause** command. -**docker-port(1)** +**port** Lookup the public-facing port which is NAT-ed to PRIVATE_PORT + See **docker-port(1)** for full documentation on the **port** command. -**docker-ps(1)** +**ps** List containers + See **docker-ps(1)** for full documentation on the **ps** command. -**docker-pull(1)** +**pull** Pull an image or a repository from a Docker Registry + See **docker-pull(1)** for full documentation on the **pull** command. -**docker-push(1)** +**push** Push an image or a repository to a Docker Registry + See **docker-push(1)** for full documentation on the **push** command. -**docker-restart(1)** +**restart** Restart a running container + See **docker-restart(1)** for full documentation on the **restart** command. -**docker-rm(1)** +**rm** Remove one or more containers + See **docker-rm(1)** for full documentation on the **rm** command. -**docker-rmi(1)** +**rmi** Remove one or more images + See **docker-rmi(1)** for full documentation on the **rmi** command. -**docker-run(1)** +**run** Run a command in a new container + See **docker-run(1)** for full documentation on the **run** command. -**docker-save(1)** +**save** Save an image to a tar archive + See **docker-save(1)** for full documentation on the **save** command. -**docker-search(1)** +**search** Search for an image in the Docker index + See **docker-search(1)** for full documentation on the **search** command. -**docker-start(1)** +**start** Start a stopped container + See **docker-start(1)** for full documentation on the **start** command. -**docker-stats(1)** +**stats** Display a live stream of one or more containers' resource usage statistics + See **docker-stats(1)** for full documentation on the **stats** command. -**docker-stop(1)** +**stop** Stop a running container + See **docker-stop(1)** for full documentation on the **stop** command. -**docker-tag(1)** +**tag** Tag an image into a repository + See **docker-tag(1)** for full documentation on the **tag** command. -**docker-top(1)** +**top** Lookup the running processes of a container + See **docker-top(1)** for full documentation on the **top** command. -**docker-unpause(1)** +**unpause** Unpause all processes within a container + See **docker-unpause(1)** for full documentation on the **unpause** command. -**docker-version(1)** +**version** Show the Docker version information + See **docker-version(1)** for full documentation on the **version** command. -**docker-wait(1)** +**wait** Block until a container stops, then print its exit code + See **docker-wait(1)** for full documentation on the **wait** command. # STORAGE DRIVER OPTIONS From ab97303caed2a7be474e2c4ff7a55af9d6c8c3cc Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 27 Apr 2015 08:38:01 -0700 Subject: [PATCH 661/999] Windows: Info no containerized check Signed-off-by: John Howard --- daemon/info.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/daemon/info.go b/daemon/info.go index 270abda59..df1c0530c 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -33,11 +33,15 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { if s, err := operatingsystem.GetOperatingSystem(); err == nil { operatingSystem = s } - if inContainer, err := operatingsystem.IsContainerized(); err != nil { - logrus.Errorf("Could not determine if daemon is containerized: %v", err) - operatingSystem += " (error determining if containerized)" - } else if inContainer { - operatingSystem += " (containerized)" + + // Don't do containerized check on Windows + if runtime.GOOS != "windows" { + if inContainer, err := operatingsystem.IsContainerized(); err != nil { + logrus.Errorf("Could not determine if daemon is containerized: %v", err) + operatingSystem += " (error determining if containerized)" + } else if inContainer { + operatingSystem += " (containerized)" + } } meminfo, err := system.ReadMemInfo() From ba1725a94ee75603d3a21e0580be3954fc689137 Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 27 Apr 2015 09:25:38 -0700 Subject: [PATCH 662/999] Windows: Refactor volumes Signed-off-by: John Howard --- daemon/volumes.go | 16 ---------------- daemon/volumes_linux.go | 24 ++++++++++++++++++++++++ daemon/volumes_windows.go | 8 ++++++++ 3 files changed, 32 insertions(+), 16 deletions(-) create mode 100644 daemon/volumes_linux.go create mode 100644 daemon/volumes_windows.go diff --git a/daemon/volumes.go b/daemon/volumes.go index 4d15023ba..ede1eef73 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -13,7 +13,6 @@ import ( "github.com/docker/docker/pkg/chrootarchive" "github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/symlink" - "github.com/docker/docker/pkg/system" ) type volumeMount struct { @@ -314,21 +313,6 @@ func copyExistingContents(source, destination string) error { return copyOwnership(source, destination) } -// copyOwnership copies the permissions and uid:gid of the source file -// into the destination file -func copyOwnership(source, destination string) error { - stat, err := system.Stat(source) - if err != nil { - return err - } - - if err := os.Chown(destination, int(stat.Uid()), int(stat.Gid())); err != nil { - return err - } - - return os.Chmod(destination, os.FileMode(stat.Mode())) -} - func (container *Container) mountVolumes() error { for dest, source := range container.Volumes { v := container.daemon.volumes.Get(source) diff --git a/daemon/volumes_linux.go b/daemon/volumes_linux.go new file mode 100644 index 000000000..93fea8165 --- /dev/null +++ b/daemon/volumes_linux.go @@ -0,0 +1,24 @@ +// +build !windows + +package daemon + +import ( + "os" + + "github.com/docker/docker/pkg/system" +) + +// copyOwnership copies the permissions and uid:gid of the source file +// into the destination file +func copyOwnership(source, destination string) error { + stat, err := system.Stat(source) + if err != nil { + return err + } + + if err := os.Chown(destination, int(stat.Uid()), int(stat.Gid())); err != nil { + return err + } + + return os.Chmod(destination, os.FileMode(stat.Mode())) +} diff --git a/daemon/volumes_windows.go b/daemon/volumes_windows.go new file mode 100644 index 000000000..ca1199a54 --- /dev/null +++ b/daemon/volumes_windows.go @@ -0,0 +1,8 @@ +// +build windows + +package daemon + +// Not supported on Windows +func copyOwnership(source, destination string) error { + return nil +} From bb1c576eb3024f7fb4242d76dc835b7c4dd85c5b Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 27 Apr 2015 18:33:08 +0200 Subject: [PATCH 663/999] Expose whole Response struct in sockRequestRaw Signed-off-by: Antonio Murdaca --- integration-cli/docker_api_containers_test.go | 50 +++++++++---------- integration-cli/docker_api_images_test.go | 8 +-- integration-cli/docker_api_logs_test.go | 12 ++--- integration-cli/docker_utils.go | 16 +++--- 4 files changed, 43 insertions(+), 43 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index e7ce0f3f5..cd04c38dc 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -339,8 +339,8 @@ func (s *DockerSuite) TestBuildApiDockerfilePath(c *check.C) { c.Fatalf("failed to close tar archive: %v", err) } - status, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") - c.Assert(status, check.Equals, http.StatusInternalServerError) + res, body, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar") + c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError) c.Assert(err, check.IsNil) out, err := readBody(body) @@ -365,8 +365,8 @@ RUN find /tmp/`, } defer server.Close() - status, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") - c.Assert(status, check.Equals, http.StatusOK) + res, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json") + c.Assert(res.StatusCode, check.Equals, http.StatusOK) c.Assert(err, check.IsNil) buf, err := readBody(body) @@ -393,8 +393,8 @@ RUN echo from dockerfile`, } defer git.Close() - status, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") - c.Assert(status, check.Equals, http.StatusOK) + res, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + c.Assert(res.StatusCode, check.Equals, http.StatusOK) c.Assert(err, check.IsNil) buf, err := readBody(body) @@ -421,8 +421,8 @@ RUN echo from Dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - status, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") - c.Assert(status, check.Equals, http.StatusOK) + res, body, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json") + c.Assert(res.StatusCode, check.Equals, http.StatusOK) c.Assert(err, check.IsNil) buf, err := readBody(body) @@ -450,8 +450,8 @@ RUN echo from dockerfile`, defer git.Close() // Make sure it tries to 'dockerfile' query param value - status, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") - c.Assert(status, check.Equals, http.StatusOK) + res, body, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json") + c.Assert(res.StatusCode, check.Equals, http.StatusOK) c.Assert(err, check.IsNil) buf, err := readBody(body) @@ -483,8 +483,8 @@ func (s *DockerSuite) TestBuildApiDockerfileSymlink(c *check.C) { c.Fatalf("failed to close tar archive: %v", err) } - status, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") - c.Assert(status, check.Equals, http.StatusInternalServerError) + res, body, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar") + c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError) c.Assert(err, check.IsNil) out, err := readBody(body) @@ -720,7 +720,7 @@ func (s *DockerSuite) TestContainerApiVerifyHeader(c *check.C) { "Image": "busybox", } - create := func(ct string) (int, io.ReadCloser, error) { + create := func(ct string) (*http.Response, io.ReadCloser, error) { jsonData := bytes.NewBuffer(nil) if err := json.NewEncoder(jsonData).Encode(config); err != nil { c.Fatal(err) @@ -729,21 +729,21 @@ func (s *DockerSuite) TestContainerApiVerifyHeader(c *check.C) { } // Try with no content-type - status, body, err := create("") - c.Assert(status, check.Equals, http.StatusInternalServerError) + res, body, err := create("") c.Assert(err, check.IsNil) + c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError) body.Close() // Try with wrong content-type - status, body, err = create("application/xml") - c.Assert(status, check.Equals, http.StatusInternalServerError) + res, body, err = create("application/xml") c.Assert(err, check.IsNil) + c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError) body.Close() // now application/json - status, body, err = create("application/json") - c.Assert(status, check.Equals, http.StatusCreated) + res, body, err = create("application/json") c.Assert(err, check.IsNil) + c.Assert(res.StatusCode, check.Equals, http.StatusCreated) body.Close() } @@ -774,8 +774,8 @@ func (s *DockerSuite) TestContainerApiPostCreateNull(c *check.C) { "NetworkDisabled":false, "OnBuild":null}` - status, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") - c.Assert(status, check.Equals, http.StatusCreated) + res, body, err := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") + c.Assert(res.StatusCode, check.Equals, http.StatusCreated) c.Assert(err, check.IsNil) b, err := readBody(body) @@ -808,13 +808,13 @@ func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) { "Memory": 524287 }` - status, body, _ := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") + res, body, _ := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json") b, err2 := readBody(body) if err2 != nil { c.Fatal(err2) } - c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError) c.Assert(strings.Contains(string(b), "Minimum memory limit allowed is 4MB"), check.Equals, true) } @@ -831,13 +831,13 @@ func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { "Memory": 524287 }` - status, body, _ := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json") + res, body, _ := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json") b, err2 := readBody(body) if err2 != nil { c.Fatal(err2) } - c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError) c.Assert(strings.Contains(string(b), "Minimum memory limit allowed is 4MB"), check.Equals, true) } diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index ac3ad55d7..e88fbaeaa 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -74,9 +74,9 @@ func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { } id := strings.TrimSpace(out) - status, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") - c.Assert(status, check.Equals, http.StatusOK) + res, body, err := sockRequestRaw("GET", "/images/"+id+"/get", nil, "") c.Assert(err, check.IsNil) + c.Assert(res.StatusCode, check.Equals, http.StatusOK) defer body.Close() @@ -84,9 +84,9 @@ func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { c.Fatal(err, out) } - status, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar") - c.Assert(status, check.Equals, http.StatusOK) + res, loadBody, err := sockRequestRaw("POST", "/images/load", body, "application/x-tar") c.Assert(err, check.IsNil) + c.Assert(res.StatusCode, check.Equals, http.StatusOK) defer loadBody.Close() diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go index a1723ef21..f9284494d 100644 --- a/integration-cli/docker_api_logs_test.go +++ b/integration-cli/docker_api_logs_test.go @@ -20,22 +20,22 @@ func (s *DockerSuite) TestLogsApiWithStdout(c *check.C) { } type logOut struct { - out string - status int - err error + out string + res *http.Response + err error } chLog := make(chan logOut) go func() { - statusCode, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", id), nil, "") + res, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1×tamps=1", id), nil, "") out, _ := bufio.NewReader(body).ReadString('\n') - chLog <- logOut{strings.TrimSpace(out), statusCode, err} + chLog <- logOut{strings.TrimSpace(out), res, err} }() select { case l := <-chLog: - c.Assert(l.status, check.Equals, http.StatusOK) c.Assert(l.err, check.IsNil) + c.Assert(l.res.StatusCode, check.Equals, http.StatusOK) if !strings.HasSuffix(l.out, "hello") { c.Fatalf("expected log output to container 'hello', but it does not") } diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 5d2a537e1..8386bb59f 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -313,20 +313,20 @@ func sockRequest(method, endpoint string, data interface{}) (int, []byte, error) return -1, nil, err } - status, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json") + res, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json") if err != nil { b, _ := ioutil.ReadAll(body) - return status, b, err + return -1, b, err } var b []byte b, err = readBody(body) - return status, b, err + return res.StatusCode, b, err } -func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io.ReadCloser, error) { +func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (*http.Response, io.ReadCloser, error) { c, err := sockConn(time.Duration(10 * time.Second)) if err != nil { - return -1, nil, fmt.Errorf("could not dial docker daemon: %v", err) + return nil, nil, fmt.Errorf("could not dial docker daemon: %v", err) } client := httputil.NewClientConn(c, nil) @@ -334,7 +334,7 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io req, err := http.NewRequest(method, endpoint, data) if err != nil { client.Close() - return -1, nil, fmt.Errorf("could not create new request: %v", err) + return nil, nil, fmt.Errorf("could not create new request: %v", err) } if ct != "" { @@ -344,14 +344,14 @@ func sockRequestRaw(method, endpoint string, data io.Reader, ct string) (int, io resp, err := client.Do(req) if err != nil { client.Close() - return -1, nil, fmt.Errorf("could not perform request: %v", err) + return nil, nil, fmt.Errorf("could not perform request: %v", err) } body := ioutils.NewReadCloserWrapper(resp.Body, func() error { defer client.Close() return resp.Body.Close() }) - return resp.StatusCode, body, err + return resp, body, nil } func readBody(b io.ReadCloser) ([]byte, error) { From b6cfe7ca07722cee22345602595fc8fb928dbe79 Mon Sep 17 00:00:00 2001 From: Matt McCormick Date: Sun, 26 Apr 2015 20:58:31 -0400 Subject: [PATCH 664/999] Do Debian installation verification with hello-world image. The hello-world image is recommended as a verification image and it is smaller than Ubuntu. Signed-off-by: Matt McCormick --- docs/sources/installation/debian.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index aeee1ecb1..da9e5f59b 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -28,9 +28,10 @@ To install the latest Debian package (may not be the latest Docker release): To verify that everything has worked as expected: - $ sudo docker run -i -t ubuntu /bin/bash + $ sudo docker run --rm hello-world -Which should download the `ubuntu` image, and then start `bash` in a container. +This command downloads and runs the `hello-world` image in a container. When the +container runs, it prints an informational message. Then, it exits. > **Note**: > If you want to enable memory and swap accounting see From 40779b28bb04dca6178ba17eaeb93265e2974ee1 Mon Sep 17 00:00:00 2001 From: Lorenzo Fontana Date: Mon, 27 Apr 2015 21:12:23 +0200 Subject: [PATCH 665/999] Parallelize TestEventsLimit Signed-off-by: Lorenzo Fontana --- integration-cli/docker_cli_events_test.go | 25 +++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index 5f3616b21..b1cc26b9a 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -7,6 +7,7 @@ import ( "regexp" "strconv" "strings" + "sync" "time" "github.com/go-check/check" @@ -65,9 +66,29 @@ func (s *DockerSuite) TestEventsContainerFailStartDie(c *check.C) { } func (s *DockerSuite) TestEventsLimit(c *check.C) { - for i := 0; i < 30; i++ { - dockerCmd(c, "run", "busybox", "echo", strconv.Itoa(i)) + + var waitGroup sync.WaitGroup + errChan := make(chan error, 17) + + args := []string{"run", "--rm", "busybox", "true"} + for i := 0; i < 17; i++ { + waitGroup.Add(1) + go func() { + defer waitGroup.Done() + err := exec.Command(dockerBinary, args...).Run() + errChan <- err + }() } + + waitGroup.Wait() + close(errChan) + + for err := range errChan { + if err != nil { + c.Fatalf("%q failed with error: %v", strings.Join(args, " "), err) + } + } + eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(c).Unix())) out, _, _ := runCommandWithOutput(eventsCmd) events := strings.Split(out, "\n") From 844538142d95c1b7dda1bb2903179510105fe9b5 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 26 Apr 2015 18:50:25 +0200 Subject: [PATCH 666/999] Small if err cleaning Signed-off-by: Antonio Murdaca --- api/client/build.go | 2 +- api/client/diff.go | 3 +-- api/client/history.go | 3 +-- api/client/ps.go | 3 +-- api/client/rmi.go | 3 +-- api/client/search.go | 3 +-- api/client/top.go | 3 +-- api/common.go | 3 +-- api/server/server.go | 7 +++---- builder/internals.go | 11 +++++++++-- cliconfig/config.go | 3 +-- daemon/container.go | 3 +-- daemon/daemon.go | 3 +-- daemon/exec.go | 3 +-- daemon/graphdriver/devmapper/deviceset.go | 13 ++++++------- graph/graph.go | 5 ++--- graph/pull.go | 6 ++---- graph/push.go | 3 +-- image/image.go | 3 +-- integration-cli/docker_api_containers_test.go | 8 ++++---- integration-cli/utils.go | 3 +-- pkg/jsonlog/jsonlog_marshalling.go | 3 +-- pkg/mflag/flag.go | 3 +-- pkg/system/lstat.go | 3 +-- pkg/system/stat_linux.go | 3 +-- pkg/timeoutconn/timeoutconn.go | 3 +-- registry/session.go | 3 +-- registry/session_v2.go | 4 +--- trust/trusts.go | 9 +++------ 29 files changed, 51 insertions(+), 74 deletions(-) diff --git a/api/client/build.go b/api/client/build.go index 800e04ac9..e83de976b 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -173,7 +173,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { includes = append(includes, ".dockerignore", *dockerfileName) } - if err = utils.ValidateContextDirectory(root, excludes); err != nil { + if err := utils.ValidateContextDirectory(root, excludes); err != nil { return fmt.Errorf("Error checking context is accessible: '%s'. Please check permissions and try again.", err) } options := &archive.TarOptions{ diff --git a/api/client/diff.go b/api/client/diff.go index a22734d04..6000c6b38 100644 --- a/api/client/diff.go +++ b/api/client/diff.go @@ -31,8 +31,7 @@ func (cli *DockerCli) CmdDiff(args ...string) error { } changes := []types.ContainerChange{} - err = json.NewDecoder(rdr).Decode(&changes) - if err != nil { + if err := json.NewDecoder(rdr).Decode(&changes); err != nil { return err } diff --git a/api/client/history.go b/api/client/history.go index 79c6f3f7a..31b853503 100644 --- a/api/client/history.go +++ b/api/client/history.go @@ -30,8 +30,7 @@ func (cli *DockerCli) CmdHistory(args ...string) error { } history := []types.ImageHistory{} - err = json.NewDecoder(rdr).Decode(&history) - if err != nil { + if err := json.NewDecoder(rdr).Decode(&history); err != nil { return err } diff --git a/api/client/ps.go b/api/client/ps.go index 44f5ff0d2..6c40c6867 100644 --- a/api/client/ps.go +++ b/api/client/ps.go @@ -92,8 +92,7 @@ func (cli *DockerCli) CmdPs(args ...string) error { } containers := []types.Container{} - err = json.NewDecoder(rdr).Decode(&containers) - if err != nil { + if err := json.NewDecoder(rdr).Decode(&containers); err != nil { return err } diff --git a/api/client/rmi.go b/api/client/rmi.go index 11c9ff32d..a8590dc82 100644 --- a/api/client/rmi.go +++ b/api/client/rmi.go @@ -37,8 +37,7 @@ func (cli *DockerCli) CmdRmi(args ...string) error { encounteredError = fmt.Errorf("Error: failed to remove one or more images") } else { dels := []types.ImageDelete{} - err = json.NewDecoder(rdr).Decode(&dels) - if err != nil { + if err := json.NewDecoder(rdr).Decode(&dels); err != nil { fmt.Fprintf(cli.err, "%s\n", err) encounteredError = fmt.Errorf("Error: failed to remove one or more images") continue diff --git a/api/client/search.go b/api/client/search.go index 4e493b234..e606d479f 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -51,8 +51,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error { } results := ByStars{} - err = json.NewDecoder(rdr).Decode(&results) - if err != nil { + if err := json.NewDecoder(rdr).Decode(&results); err != nil { return err } diff --git a/api/client/top.go b/api/client/top.go index 4975f4759..ee16fdbf6 100644 --- a/api/client/top.go +++ b/api/client/top.go @@ -31,8 +31,7 @@ func (cli *DockerCli) CmdTop(args ...string) error { } procList := types.ContainerProcessList{} - err = json.NewDecoder(stream).Decode(&procList) - if err != nil { + if err := json.NewDecoder(stream).Decode(&procList); err != nil { return err } diff --git a/api/common.go b/api/common.go index cb627824e..4a9523cd4 100644 --- a/api/common.go +++ b/api/common.go @@ -107,8 +107,7 @@ func MatchesContentType(contentType, expectedType string) bool { // LoadOrCreateTrustKey attempts to load the libtrust key at the given path, // otherwise generates a new one func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) { - err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700) - if err != nil { + if err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700); err != nil { return nil, err } trustKey, err := libtrust.LoadKeyFile(trustKeyPath) diff --git a/api/server/server.go b/api/server/server.go index 1e951d36c..61e816265 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -277,8 +277,7 @@ func (s *Server) postContainersKill(eng *engine.Engine, version version.Version, if vars == nil { return fmt.Errorf("Missing parameter") } - err := parseForm(r) - if err != nil { + if err := parseForm(r); err != nil { return err } @@ -289,7 +288,7 @@ func (s *Server) postContainersKill(eng *engine.Engine, version version.Version, if sigStr := vars["signal"]; sigStr != "" { // 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(sigStr, 10, 5) + sig, err := strconv.ParseUint(sigStr, 10, 5) if err != nil { // The signal is not a number, treat it as a string (either like // "KILL" or like "SIGKILL") @@ -301,7 +300,7 @@ func (s *Server) postContainersKill(eng *engine.Engine, version version.Version, } } - if err = s.daemon.ContainerKill(name, sig); err != nil { + if err := s.daemon.ContainerKill(name, sig); err != nil { return err } diff --git a/builder/internals.go b/builder/internals.go index ba7d45bcb..53542a669 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -148,8 +148,15 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp // do the copy (e.g. hash value if cached). Don't actually do // the copy until we've looked at all src files for _, orig := range args[0 : len(args)-1] { - err := calcCopyInfo(b, cmdName, ©Infos, orig, dest, allowRemote, allowDecompression) - if err != nil { + if err := calcCopyInfo( + b, + cmdName, + ©Infos, + orig, + dest, + allowRemote, + allowDecompression, + ); err != nil { return err } } diff --git a/cliconfig/config.go b/cliconfig/config.go index 19a92fbd8..2a27589d2 100644 --- a/cliconfig/config.go +++ b/cliconfig/config.go @@ -166,8 +166,7 @@ func (configFile *ConfigFile) Save() error { return err } - err = ioutil.WriteFile(configFile.filename, data, 0600) - if err != nil { + if err := ioutil.WriteFile(configFile.filename, data, 0600); err != nil { return err } diff --git a/daemon/container.go b/daemon/container.go index 01eef4d31..10d6b4cd8 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -149,8 +149,7 @@ func (container *Container) toDisk() error { return err } - err = ioutil.WriteFile(pth, data, 0666) - if err != nil { + if err := ioutil.WriteFile(pth, data, 0666); err != nil { return err } diff --git a/daemon/daemon.go b/daemon/daemon.go index 99f5ae636..f12ed2111 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1181,8 +1181,7 @@ func tempDir(rootDir string) (string, error) { if tmpDir = os.Getenv("DOCKER_TMPDIR"); tmpDir == "" { tmpDir = filepath.Join(rootDir, "tmp") } - err := os.MkdirAll(tmpDir, 0700) - return tmpDir, err + return tmpDir, os.MkdirAll(tmpDir, 0700) } func checkKernel() error { diff --git a/daemon/exec.go b/daemon/exec.go index 22872adc4..9aa102690 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -214,8 +214,7 @@ func (d *Daemon) ContainerExecStart(execName string, stdin io.ReadCloser, stdout // the exitStatus) even after the cmd is done running. go func() { - err := container.Exec(execConfig) - if err != nil { + if err := container.Exec(execConfig); err != nil { execErr <- fmt.Errorf("Cannot run exec command %s in container %s: %s", execName, container.ID, err) } }() diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index b5d67fa11..42b9d76be 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -218,7 +218,7 @@ func (devices *DeviceSet) ensureImage(name string, size int64) (string, error) { } defer file.Close() - if err = file.Truncate(size); err != nil { + if err := file.Truncate(size); err != nil { return "", err } } @@ -697,7 +697,7 @@ func (devices *DeviceSet) setupBaseImage() error { logrus.Debugf("Creating filesystem on base device-mapper thin volume") - if err = devices.activateDeviceIfNeeded(info); err != nil { + if err := devices.activateDeviceIfNeeded(info); err != nil { return err } @@ -706,7 +706,7 @@ func (devices *DeviceSet) setupBaseImage() error { } info.Initialized = true - if err = devices.saveMetadata(info); err != nil { + if err := devices.saveMetadata(info); err != nil { info.Initialized = false return err } @@ -1099,14 +1099,14 @@ func (devices *DeviceSet) initDevmapper(doInit bool) error { // If we didn't just create the data or metadata image, we need to // load the transaction id and migrate old metadata if !createdLoopback { - if err = devices.initMetaData(); err != nil { + if err := devices.initMetaData(); err != nil { return err } } // Right now this loads only NextDeviceId. If there is more metadata // down the line, we might have to move it earlier. - if err = devices.loadDeviceSetMetaData(); err != nil { + if err := devices.loadDeviceSetMetaData(); err != nil { return err } @@ -1528,8 +1528,7 @@ func (devices *DeviceSet) MetadataDevicePath() string { func (devices *DeviceSet) getUnderlyingAvailableSpace(loopFile string) (uint64, error) { buf := new(syscall.Statfs_t) - err := syscall.Statfs(loopFile, buf) - if err != nil { + if err := syscall.Statfs(loopFile, buf); err != nil { logrus.Warnf("Couldn't stat loopfile filesystem %v: %v", loopFile, err) return 0, err } diff --git a/graph/graph.go b/graph/graph.go index 5159a9322..9b2d7c2ee 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -348,9 +348,8 @@ func (graph *Graph) Delete(name string) error { tmp, err := graph.Mktemp("") graph.idIndex.Delete(id) if err == nil { - err = os.Rename(graph.ImageRoot(id), tmp) - // On err make tmp point to old dir and cleanup unused tmp dir - if err != nil { + if err := os.Rename(graph.ImageRoot(id), tmp); err != nil { + // On err make tmp point to old dir and cleanup unused tmp dir os.RemoveAll(tmp) tmp = graph.ImageRoot(id) } diff --git a/graph/pull.go b/graph/pull.go index 0ebf75abb..c3c064fc5 100644 --- a/graph/pull.go +++ b/graph/pull.go @@ -537,8 +537,7 @@ func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *regis di.err <- downloadFunc(di) }(&downloads[i]) } else { - err := downloadFunc(&downloads[i]) - if err != nil { + if err := downloadFunc(&downloads[i]); err != nil { return false, err } } @@ -548,8 +547,7 @@ func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *regis for i := len(downloads) - 1; i >= 0; i-- { d := &downloads[i] if d.err != nil { - err := <-d.err - if err != nil { + if err := <-d.err; err != nil { return false, err } } diff --git a/graph/push.go b/graph/push.go index 62ff94e0c..1b33288d8 100644 --- a/graph/push.go +++ b/graph/push.go @@ -367,8 +367,7 @@ func (s *TagStore) pushV2Repository(r *registry.Session, localRepo Repository, o logrus.Debugf("Pushing layer: %s", layer.ID) if layer.Config != nil && metadata.Image != layer.ID { - err = runconfig.Merge(&metadata, layer.Config) - if err != nil { + if err := runconfig.Merge(&metadata, layer.Config); err != nil { return err } } diff --git a/image/image.go b/image/image.go index 90714d6db..a34d2b940 100644 --- a/image/image.go +++ b/image/image.go @@ -268,8 +268,7 @@ func NewImgJSON(src []byte) (*Image, error) { func ValidateID(id string) error { validHex := regexp.MustCompile(`^([a-f0-9]{64})$`) if ok := validHex.MatchString(id); !ok { - err := fmt.Errorf("image ID '%s' is invalid", id) - return err + return fmt.Errorf("image ID '%s' is invalid", id) } return nil } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index e7ce0f3f5..a26afb162 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -176,7 +176,7 @@ func (s *DockerSuite) TestContainerApiStartVolumesFrom(c *check.C) { c.Fatal(out, err) } - name := "testing" + name := "TestContainerApiStartDupVolumeBinds" config := map[string]interface{}{ "Image": "busybox", "Volumes": map[string]struct{}{volPath: {}}, @@ -620,7 +620,7 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { c.Fatal(err, out) } - name := "testcommit" + stringid.GenerateRandomID() + name := "TestContainerApiCommit" status, b, err := sockRequest("POST", "/commit?repo="+name+"&testtag=tag&container="+cName, nil) c.Assert(status, check.Equals, http.StatusCreated) c.Assert(err, check.IsNil) @@ -842,12 +842,12 @@ func (s *DockerSuite) TestStartWithTooLowMemoryLimit(c *check.C) { } func (s *DockerSuite) TestContainerApiRename(c *check.C) { - runCmd := exec.Command(dockerBinary, "run", "--name", "first_name", "-d", "busybox", "sh") + runCmd := exec.Command(dockerBinary, "run", "--name", "TestContainerApiRename", "-d", "busybox", "sh") out, _, err := runCommandWithOutput(runCmd) c.Assert(err, check.IsNil) containerID := strings.TrimSpace(out) - newName := "new_name" + stringid.GenerateRandomID() + newName := "TestContainerApiRenameNew" statusCode, _, err := sockRequest("POST", "/containers/"+containerID+"/rename?name="+newName, nil) // 204 No Content is expected, not 200 diff --git a/integration-cli/utils.go b/integration-cli/utils.go index 4ca7158ae..f0de79ea8 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -169,8 +169,7 @@ func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in } func unmarshalJSON(data []byte, result interface{}) error { - err := json.Unmarshal(data, result) - if err != nil { + if err := json.Unmarshal(data, result); err != nil { return err } diff --git a/pkg/jsonlog/jsonlog_marshalling.go b/pkg/jsonlog/jsonlog_marshalling.go index 6244eb01a..abaa8a73b 100644 --- a/pkg/jsonlog/jsonlog_marshalling.go +++ b/pkg/jsonlog/jsonlog_marshalling.go @@ -65,8 +65,7 @@ import ( func (mj *JSONLog) MarshalJSON() ([]byte, error) { var buf bytes.Buffer buf.Grow(1024) - err := mj.MarshalJSONBuf(&buf) - if err != nil { + if err := mj.MarshalJSONBuf(&buf); err != nil { return nil, err } return buf.Bytes(), nil diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go index f2da1cd1b..f0d20d99b 100644 --- a/pkg/mflag/flag.go +++ b/pkg/mflag/flag.go @@ -486,8 +486,7 @@ func (f *FlagSet) Set(name, value string) error { if !ok { return fmt.Errorf("no such flag -%v", name) } - err := flag.Value.Set(value) - if err != nil { + if err := flag.Value.Set(value); err != nil { return err } if f.actual == nil { diff --git a/pkg/system/lstat.go b/pkg/system/lstat.go index a966cd488..d0e43b370 100644 --- a/pkg/system/lstat.go +++ b/pkg/system/lstat.go @@ -12,8 +12,7 @@ import ( // Throws an error if the file does not exist func Lstat(path string) (*Stat_t, error) { s := &syscall.Stat_t{} - err := syscall.Lstat(path, s) - if err != nil { + if err := syscall.Lstat(path, s); err != nil { return nil, err } return fromStatT(s) diff --git a/pkg/system/stat_linux.go b/pkg/system/stat_linux.go index 928ba89e6..3899b3e0e 100644 --- a/pkg/system/stat_linux.go +++ b/pkg/system/stat_linux.go @@ -20,8 +20,7 @@ func fromStatT(s *syscall.Stat_t) (*Stat_t, error) { // Throws an error if the file does not exist func Stat(path string) (*Stat_t, error) { s := &syscall.Stat_t{} - err := syscall.Stat(path, s) - if err != nil { + if err := syscall.Stat(path, s); err != nil { return nil, err } return fromStatT(s) diff --git a/pkg/timeoutconn/timeoutconn.go b/pkg/timeoutconn/timeoutconn.go index 3a554559a..d9534b5da 100644 --- a/pkg/timeoutconn/timeoutconn.go +++ b/pkg/timeoutconn/timeoutconn.go @@ -17,8 +17,7 @@ type conn struct { func (c *conn) Read(b []byte) (int, error) { if c.timeout > 0 { - err := c.Conn.SetReadDeadline(time.Now().Add(c.timeout)) - if err != nil { + if err := c.Conn.SetReadDeadline(time.Now().Add(c.timeout)); err != nil { return 0, err } } diff --git a/registry/session.go b/registry/session.go index f7358bc10..e65f82cd6 100644 --- a/registry/session.go +++ b/registry/session.go @@ -597,8 +597,7 @@ func (r *Session) SearchRepositories(term string) (*SearchResults, error) { return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Unexpected status code %d", res.StatusCode), res) } result := new(SearchResults) - err = json.NewDecoder(res.Body).Decode(result) - return result, err + return result, json.NewDecoder(res.Body).Decode(result) } func (r *Session) GetAuthConfig(withPasswd bool) *cliconfig.AuthConfig { diff --git a/registry/session_v2.go b/registry/session_v2.go index a14e434ac..4188e505b 100644 --- a/registry/session_v2.go +++ b/registry/session_v2.go @@ -387,10 +387,8 @@ func (r *Session) GetV2RemoteTags(ep *Endpoint, imageName string, auth *RequestA return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to fetch for %s", res.StatusCode, imageName), res) } - decoder := json.NewDecoder(res.Body) var remote remoteTags - err = decoder.Decode(&remote) - if err != nil { + if err := json.NewDecoder(res.Body).Decode(&remote); err != nil { return nil, fmt.Errorf("Error while decoding the http response: %s", err) } return remote.Tags, nil diff --git a/trust/trusts.go b/trust/trusts.go index c4a2f4158..885127ee5 100644 --- a/trust/trusts.go +++ b/trust/trusts.go @@ -62,8 +62,7 @@ func NewTrustStore(path string) (*TrustStore, error) { baseEndpoints: endpoints, } - err = t.reload() - if err != nil { + if err := t.reload(); err != nil { return nil, err } @@ -170,8 +169,7 @@ func (t *TrustStore) fetch() { continue } // TODO check if value differs - err = ioutil.WriteFile(path.Join(t.path, bg+".json"), b, 0600) - if err != nil { + if err := ioutil.WriteFile(path.Join(t.path, bg+".json"), b, 0600); err != nil { logrus.Infof("Error writing trust graph statement: %s", err) } fetchCount++ @@ -180,8 +178,7 @@ func (t *TrustStore) fetch() { if fetchCount > 0 { go func() { - err := t.reload() - if err != nil { + if err := t.reload(); err != nil { logrus.Infof("Reload of trust graph failed: %s", err) } }() From 50868b2c575c26492f4c49a79d2f3b51ec68a229 Mon Sep 17 00:00:00 2001 From: Daniel Antlinger Date: Thu, 23 Apr 2015 16:35:13 -0700 Subject: [PATCH 667/999] fixed TestDiffEnsureDockerinitFilesAreIgnored is too long #12672 Signed-off-by: Daniel Antlinger --- integration-cli/docker_cli_diff_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/integration-cli/docker_cli_diff_test.go b/integration-cli/docker_cli_diff_test.go index 332b128ed..725b76286 100644 --- a/integration-cli/docker_cli_diff_test.go +++ b/integration-cli/docker_cli_diff_test.go @@ -40,12 +40,14 @@ func (s *DockerSuite) TestDiffFilenameShownInOutput(c *check.C) { func (s *DockerSuite) TestDiffEnsureDockerinitFilesAreIgnored(c *check.C) { // 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"} + containerCount := 5 // we might not run into this problem from the first run, so start a few containers - for i := 0; i < 20; i++ { + for i := 0; i < containerCount; i++ { containerCmd := `echo foo > /root/bar` runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", containerCmd) out, _, err := runCommandWithOutput(runCmd) + if err != nil { c.Fatal(out, err) } From 3941623fbc3fa724d61f53121513ffd87d03b61c Mon Sep 17 00:00:00 2001 From: David Mackey Date: Mon, 27 Apr 2015 13:33:30 -0700 Subject: [PATCH 668/999] trivial: typo cleanup Signed-off-by: David Mackey --- builder/internals.go | 2 +- contrib/docker-device-tool/device_tool.go | 2 +- daemon/daemon.go | 2 +- daemon/networkdriver/ipallocator/allocator_test.go | 2 +- engine/streams_test.go | 2 +- integration-cli/docker_api_containers_test.go | 2 +- integration-cli/docker_cli_attach_test.go | 4 ++-- integration-cli/docker_cli_attach_unix_test.go | 8 ++++---- integration-cli/docker_cli_build_test.go | 4 ++-- integration-cli/docker_cli_commit_test.go | 2 +- integration-cli/docker_cli_daemon_test.go | 2 +- integration-cli/docker_cli_ps_test.go | 2 +- integration-cli/docker_cli_push_test.go | 4 ++-- integration-cli/docker_cli_rmi_test.go | 2 +- integration-cli/docker_cli_run_test.go | 4 ++-- integration-cli/docker_cli_run_unix_test.go | 4 ++-- integration-cli/docker_cli_save_load_test.go | 2 +- integration-cli/docker_cli_start_test.go | 2 +- integration/api_test.go | 4 ++-- integration/container_test.go | 2 +- integration/runtime_test.go | 2 +- integration/utils.go | 2 +- pkg/archive/archive_windows_test.go | 2 +- pkg/graphdb/graphdb_test.go | 4 ++-- pkg/term/winconsole/console_windows_test.go | 2 +- registry/registry_test.go | 2 +- runconfig/config_test.go | 2 +- runconfig/merge.go | 2 +- volumes/repository.go | 2 +- 29 files changed, 39 insertions(+), 39 deletions(-) diff --git a/builder/internals.go b/builder/internals.go index ba7d45bcb..d373de6c1 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -483,7 +483,7 @@ func (b *Builder) processImageFrom(img *imagepkg.Image) error { fmt.Fprintf(b.ErrStream, "# Executing %d build triggers\n", nTriggers) } - // Copy the ONBUILD triggers, and remove them from the config, since the config will be commited. + // Copy the ONBUILD triggers, and remove them from the config, since the config will be committed. onBuildTriggers := b.Config.OnBuild b.Config.OnBuild = []string{} diff --git a/contrib/docker-device-tool/device_tool.go b/contrib/docker-device-tool/device_tool.go index 9ad094a34..0a0b0803d 100644 --- a/contrib/docker-device-tool/device_tool.go +++ b/contrib/docker-device-tool/device_tool.go @@ -125,7 +125,7 @@ func main() { err = devices.ResizePool(size) if err != nil { - fmt.Println("Error resizeing pool: ", err) + fmt.Println("Error resizing pool: ", err) os.Exit(1) } diff --git a/daemon/daemon.go b/daemon/daemon.go index 99f5ae636..45a5af28f 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -823,7 +823,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService // Load storage driver driver, err := graphdriver.New(config.Root, config.GraphOptions) if err != nil { - return nil, fmt.Errorf("error intializing graphdriver: %v", err) + return nil, fmt.Errorf("error initializing graphdriver: %v", err) } logrus.Debugf("Using graph driver %s", driver) // register cleanup for graph driver diff --git a/daemon/networkdriver/ipallocator/allocator_test.go b/daemon/networkdriver/ipallocator/allocator_test.go index fffe6e338..6c5c0e4db 100644 --- a/daemon/networkdriver/ipallocator/allocator_test.go +++ b/daemon/networkdriver/ipallocator/allocator_test.go @@ -601,7 +601,7 @@ func TestRegisterBadTwice(t *testing.T) { Mask: []byte{255, 255, 255, 248}, } if err := a.RegisterSubnet(network, subnet); err != ErrNetworkAlreadyRegistered { - t.Fatalf("Expecteded ErrNetworkAlreadyRegistered error, got %v", err) + t.Fatalf("Expected ErrNetworkAlreadyRegistered error, got %v", err) } } diff --git a/engine/streams_test.go b/engine/streams_test.go index 476a721ba..c22338a32 100644 --- a/engine/streams_test.go +++ b/engine/streams_test.go @@ -182,7 +182,7 @@ func TestInputAddEmpty(t *testing.T) { t.Fatal(err) } if len(data) > 0 { - t.Fatalf("Read from empty input shoul yield no data") + t.Fatalf("Read from empty input should yield no data") } } diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index e7ce0f3f5..9a2300270 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -643,7 +643,7 @@ func (s *DockerSuite) TestContainerApiCommit(c *check.C) { // sanity check, make sure the image is what we think it is out, err = exec.Command(dockerBinary, "run", img.Id, "ls", "/test").CombinedOutput() if err != nil { - c.Fatalf("error checking commited image: %v - %q", err, string(out)) + c.Fatalf("error checking committed image: %v - %q", err, string(out)) } } diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go index 11ae1584a..4dd45ddff 100644 --- a/integration-cli/docker_cli_attach_test.go +++ b/integration-cli/docker_cli_attach_test.go @@ -161,7 +161,7 @@ func (s *DockerSuite) TestAttachDisconnect(c *check.C) { c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - c.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("expected 'hello', got %q", out) } if err := stdin.Close(); err != nil { @@ -174,7 +174,7 @@ func (s *DockerSuite) TestAttachDisconnect(c *check.C) { c.Fatal(err) } if running != "true" { - c.Fatal("exepected container to still be running") + c.Fatal("expected container to still be running") } } diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index 5567c92a0..bae83d994 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -172,7 +172,7 @@ func (s *DockerSuite) TestAttachDetach(c *check.C) { c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - c.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("expected 'hello', got %q", out) } // escape sequence @@ -195,7 +195,7 @@ func (s *DockerSuite) TestAttachDetach(c *check.C) { c.Fatal(err) } if running != "true" { - c.Fatal("exepected container to still be running") + c.Fatal("expected container to still be running") } go func() { @@ -243,7 +243,7 @@ func (s *DockerSuite) TestAttachDetachTruncatedID(c *check.C) { c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - c.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("expected 'hello', got %q", out) } // escape sequence @@ -266,7 +266,7 @@ func (s *DockerSuite) TestAttachDetachTruncatedID(c *check.C) { c.Fatal(err) } if running != "true" { - c.Fatal("exepected container to still be running") + c.Fatal("expected container to still be running") } go func() { diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 6d6805aef..695e4cd6e 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -3408,7 +3408,7 @@ func (s *DockerSuite) TestBuildVerifyIntString(c *check.C) { out, rc, err := runCommandWithOutput(exec.Command(dockerBinary, "inspect", name)) if rc != 0 || err != nil { - c.Fatalf("Unexcepted error from inspect: rc: %v err: %v", rc, err) + c.Fatalf("Unexpected error from inspect: rc: %v err: %v", rc, err) } if !strings.Contains(out, "\"123\"") { @@ -5033,7 +5033,7 @@ RUN echo " \ expecting := "\n foo \n" if !strings.Contains(out, expecting) { - c.Fatalf("Bad output: %q expecting to contian %q", out, expecting) + c.Fatalf("Bad output: %q expecting to contain %q", out, expecting) } } diff --git a/integration-cli/docker_cli_commit_test.go b/integration-cli/docker_cli_commit_test.go index 1544b3aac..391cd4ebc 100644 --- a/integration-cli/docker_cli_commit_test.go +++ b/integration-cli/docker_cli_commit_test.go @@ -262,7 +262,7 @@ func (s *DockerSuite) TestCommitMergeConfigRun(c *check.C) { out, _ = dockerCmd(c, "run", "--name", name, "commit-test") if strings.TrimSpace(out) != "testing" { - c.Fatal("run config in commited container was not merged") + c.Fatal("run config in committed container was not merged") } type cfg struct { diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 2a945827e..034c17ebb 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -860,7 +860,7 @@ func (s *DockerSuite) TestDaemonwithwrongkey(c *check.C) { if err := d1.Start(); err == nil { d1.Stop() - c.Fatalf("It should not be succssful to start daemon with wrong key: %v", err) + c.Fatalf("It should not be successful to start daemon with wrong key: %v", err) } content, _ := ioutil.ReadFile(d1.logFile.Name()) diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index 881f02d4f..271051815 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -547,7 +547,7 @@ func (s *DockerSuite) TestPsListContainersFilterExited(c *check.C) { } ids = strings.Split(strings.TrimSpace(out), "\n") if len(ids) != 2 { - c.Fatalf("Should be 2 zero exited containerst got %d", len(ids)) + c.Fatalf("Should be 2 zero exited containers got %d", len(ids)) } if ids[0] != secondNonZero { c.Fatalf("First in list should be %q, got %q", secondNonZero, ids[0]) diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 8f7ee3158..69a05ed82 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -41,7 +41,7 @@ func (s *DockerRegistrySuite) TestPushUntagged(c *check.C) { expected := "Repository does not exist" pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err == nil { - c.Fatalf("pushing the image to the private registry should have failed: outuput %q", out) + c.Fatalf("pushing the image to the private registry should have failed: output %q", out) } else if !strings.Contains(out, expected) { c.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out) } @@ -53,7 +53,7 @@ func (s *DockerRegistrySuite) TestPushBadTag(c *check.C) { expected := "does not exist" pushCmd := exec.Command(dockerBinary, "push", repoName) if out, _, err := runCommandWithOutput(pushCmd); err == nil { - c.Fatalf("pushing the image to the private registry should have failed: outuput %q", out) + c.Fatalf("pushing the image to the private registry should have failed: output %q", out) } else if !strings.Contains(out, expected) { c.Fatalf("pushing the image failed with an unexpected message: expected %q, got %q", expected, out) } diff --git a/integration-cli/docker_cli_rmi_test.go b/integration-cli/docker_cli_rmi_test.go index 234fa22f0..9dc2ee297 100644 --- a/integration-cli/docker_cli_rmi_test.go +++ b/integration-cli/docker_cli_rmi_test.go @@ -108,7 +108,7 @@ func (s *DockerSuite) TestRmiImgIDForce(c *check.C) { runCmd = exec.Command(dockerBinary, "rmi", imgID) out, _, err = runCommandWithOutput(runCmd) if err == nil || !strings.Contains(out, fmt.Sprintf("Conflict, cannot delete image %s because it is tagged in multiple repositories, use -f to force", imgID)) { - c.Fatalf("rmi tagged in mutiple repos should have failed without force:%s, %v", out, err) + c.Fatalf("rmi tagged in multiple repos should have failed without force:%s, %v", out, err) } dockerCmd(c, "rmi", "-f", imgID) diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index b7961126c..c3b25558d 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -329,7 +329,7 @@ func (s *DockerSuite) TestRunLinksContainerWithContainerId(c *check.C) { cmd = exec.Command(dockerBinary, "inspect", "-f", "{{.NetworkSettings.IPAddress}}", cID) ip, _, _, err := runCommandWithStdoutStderr(cmd) if err != nil { - c.Fatalf("faild to inspect container: %v, output: %q", err, ip) + c.Fatalf("failed to inspect container: %v, output: %q", err, ip) } ip = strings.TrimSpace(ip) cmd = exec.Command(dockerBinary, "run", "--link", cID+":test", "busybox", "/bin/cat", "/etc/hosts") @@ -2067,7 +2067,7 @@ func (s *DockerSuite) TestRunCidFileCleanupIfEmpty(c *check.C) { if err == nil { c.Fatalf("Run without command must fail. out=%s", out) } else if !strings.Contains(out, "No command specified") { - c.Fatalf("Run without command failed with wrong outpuc. out=%s\nerr=%v", out, err) + c.Fatalf("Run without command failed with wrong output. out=%s\nerr=%v", out, err) } if _, err := os.Stat(tmpCidFile); err == nil { diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 74fae1735..43fa82150 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -213,7 +213,7 @@ func (s *DockerSuite) TestRunAttachDetach(c *check.C) { c.Fatal(err) } if strings.TrimSpace(out) != "hello" { - c.Fatalf("exepected 'hello', got %q", out) + c.Fatalf("expected 'hello', got %q", out) } // escape sequence @@ -236,7 +236,7 @@ func (s *DockerSuite) TestRunAttachDetach(c *check.C) { c.Fatal(err) } if running != "true" { - c.Fatal("exepected container to still be running") + c.Fatal("expected container to still be running") } go func() { diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go index fe6bf2bfc..f83f6645a 100644 --- a/integration-cli/docker_cli_save_load_test.go +++ b/integration-cli/docker_cli_save_load_test.go @@ -333,7 +333,7 @@ func (s *DockerSuite) TestSaveRepoWithMultipleImages(c *check.C) { sort.Strings(actual) sort.Strings(expected) if !reflect.DeepEqual(expected, actual) { - c.Fatalf("achive does not contains the right layers: got %v, expected %v", actual, expected) + c.Fatalf("archive does not contains the right layers: got %v, expected %v", actual, expected) } } diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index 52afac1af..13ecedd57 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -205,7 +205,7 @@ func (s *DockerSuite) TestStartMultipleContainers(c *check.C) { c.Fatal("Container should be stopped") } - // start all the three containers, container `child_first` start first which should be faild + // start all the three containers, container `child_first` start first which should be failed // container 'parent' start second and then start container 'child_second' cmd = exec.Command(dockerBinary, "start", "child_first", "parent", "child_second") out, _, err = runCommandWithOutput(cmd) diff --git a/integration/api_test.go b/integration/api_test.go index 614966f6e..e45fa97e8 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -434,7 +434,7 @@ func TestGetEnabledCors(t *testing.T) { t.Errorf("Expected header Access-Control-Allow-Headers to be \"Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth\", %s found.", allowHeaders) } if allowMethods != "GET, POST, DELETE, PUT, OPTIONS" { - t.Errorf("Expected hearder Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods) + t.Errorf("Expected header Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods) } } @@ -648,7 +648,7 @@ func TestConstainersStartChunkedEncodingHostConfig(t *testing.T) { } if c.HostConfig.Binds[0] != "/tmp:/foo" { - t.Fatal("Chunked encoding not properly handled, execpted binds to be /tmp:/foo, got:", c.HostConfig.Binds[0]) + t.Fatal("Chunked encoding not properly handled, expected binds to be /tmp:/foo, got:", c.HostConfig.Binds[0]) } } diff --git a/integration/container_test.go b/integration/container_test.go index 01078734c..9256e9997 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -213,7 +213,7 @@ func BenchmarkRunParallel(b *testing.B) { return } // if string(output) != "foo" { - // complete <- fmt.Errorf("Unexecpted output: %v", string(output)) + // complete <- fmt.Errorf("Unexpected output: %v", string(output)) // } if err := daemon.Rm(container); err != nil { complete <- err diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 82f21b700..a2f22072c 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -837,7 +837,7 @@ func TestDestroyWithInitLayer(t *testing.T) { // Make sure that the container does not exist in the driver if _, err := driver.Get(container.ID, ""); err == nil { - t.Fatal("Conttainer should not exist in the driver") + t.Fatal("Container should not exist in the driver") } // Make sure that the init layer is removed from the driver diff --git a/integration/utils.go b/integration/utils.go index 1d27cd6e4..62e02e9bb 100644 --- a/integration/utils.go +++ b/integration/utils.go @@ -41,7 +41,7 @@ func waitContainerStart(t *testing.T, timeout time.Duration) *daemon.Container { }) if container == nil { - t.Fatal("An error occured while waiting for the container to start") + t.Fatal("An error occurred while waiting for the container to start") } return container diff --git a/pkg/archive/archive_windows_test.go b/pkg/archive/archive_windows_test.go index b33e0fb00..72bc71e06 100644 --- a/pkg/archive/archive_windows_test.go +++ b/pkg/archive/archive_windows_test.go @@ -20,7 +20,7 @@ func TestCanonicalTarNameForPath(t *testing.T) { if out, err := CanonicalTarNameForPath(v.in); err != nil && !v.shouldFail { t.Fatalf("cannot get canonical name for path: %s: %v", v.in, err) } else if v.shouldFail && err == nil { - t.Fatalf("canonical path call should have pailed with error. in=%s out=%s", v.in, out) + t.Fatalf("canonical path call should have failed with error. in=%s out=%s", v.in, out) } else if !v.shouldFail && out != v.expected { t.Fatalf("wrong canonical tar name. expected:%s got:%s", v.expected, out) } diff --git a/pkg/graphdb/graphdb_test.go b/pkg/graphdb/graphdb_test.go index 12dd524ed..1cd223bd9 100644 --- a/pkg/graphdb/graphdb_test.go +++ b/pkg/graphdb/graphdb_test.go @@ -52,7 +52,7 @@ func TestGetRootEntity(t *testing.T) { t.Fatal("Entity should not be nil") } if e.ID() != "0" { - t.Fatalf("Enity id should be 0, got %s", e.ID()) + t.Fatalf("Entity id should be 0, got %s", e.ID()) } } @@ -74,7 +74,7 @@ func TestSetDuplicateEntity(t *testing.T) { t.Fatal(err) } if _, err := db.Set("/foo", "43"); err == nil { - t.Fatalf("Creating an entry with a duplciate path did not cause an error") + t.Fatalf("Creating an entry with a duplicate path did not cause an error") } } diff --git a/pkg/term/winconsole/console_windows_test.go b/pkg/term/winconsole/console_windows_test.go index ee9d96834..edb5d6f66 100644 --- a/pkg/term/winconsole/console_windows_test.go +++ b/pkg/term/winconsole/console_windows_test.go @@ -18,7 +18,7 @@ func helpsTestParseInt16OrDefault(t *testing.T, expectedValue int16, shouldFail t.Errorf(format, args) } if expectedValue != value { - t.Errorf("The value returned does not macth expected\n\tExpected:%v\n\t:Actual%v", expectedValue, value) + t.Errorf("The value returned does not match expected\n\tExpected:%v\n\t:Actual%v", expectedValue, value) t.Errorf(format, args) } } diff --git a/registry/registry_test.go b/registry/registry_test.go index b4bd4ee72..3f63eb6e2 100644 --- a/registry/registry_test.go +++ b/registry/registry_test.go @@ -736,7 +736,7 @@ func TestSearchRepositories(t *testing.T) { } 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") + assertEqual(t, results.Results[0].StarCount, 42, "Expected 'fakeimage' to have 42 stars") } func TestValidRemoteName(t *testing.T) { diff --git a/runconfig/config_test.go b/runconfig/config_test.go index e36dacbf4..87fc6c6aa 100644 --- a/runconfig/config_test.go +++ b/runconfig/config_test.go @@ -104,7 +104,7 @@ func TestParseRunVolumes(t *testing.T) { 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) + t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %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) } diff --git a/runconfig/merge.go b/runconfig/merge.go index ce6697dbf..9c9a3b436 100644 --- a/runconfig/merge.go +++ b/runconfig/merge.go @@ -41,7 +41,7 @@ func Merge(userConf, imageConf *Config) error { } if len(imageConf.PortSpecs) > 0 { // FIXME: I think we can safely remove this. Leaving it for now for the sake of reverse-compat paranoia. - logrus.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", ")) + logrus.Debugf("Migrating image port specs to container: %s", strings.Join(imageConf.PortSpecs, ", ")) if userConf.ExposedPorts == nil { userConf.ExposedPorts = make(nat.PortSet) } diff --git a/volumes/repository.go b/volumes/repository.go index 0dac3753d..71d6c0ad6 100644 --- a/volumes/repository.go +++ b/volumes/repository.go @@ -58,7 +58,7 @@ func (r *Repository) newVolume(path string, writable bool) (*Volume, error) { path = filepath.Clean(path) // Ignore the error here since the path may not exist - // Really just want to make sure the path we are using is real(or non-existant) + // Really just want to make sure the path we are using is real(or nonexistent) if cleanPath, err := filepath.EvalSymlinks(path); err == nil { path = cleanPath } From c3c08f76bec023218b632e4c688ff9fcda11fcef Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 27 Apr 2015 16:10:59 -0400 Subject: [PATCH 669/999] Fix undead containers When a container has errors on removal, it gets flagged as dead. If you `docker rm -f` a dead container the container is dereffed from the daemon and doesn't show up on `docker ps` anymore... except that the container JSON file may still be lingering around and becomes undead when you restart the daemon. Signed-off-by: Brian Goff --- daemon/delete.go | 1 + 1 file changed, 1 insertion(+) diff --git a/daemon/delete.go b/daemon/delete.go index d398741d7..464193b28 100644 --- a/daemon/delete.go +++ b/daemon/delete.go @@ -129,6 +129,7 @@ func (daemon *Daemon) commonRm(container *Container, forceRemove bool) (err erro if err != nil && forceRemove { daemon.idIndex.Delete(container.ID) daemon.containers.Delete(container.ID) + os.RemoveAll(container.root) } }() From 57464c32b99b36a2963fb37da86b9870c9a56145 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Sat, 25 Apr 2015 19:47:42 -0700 Subject: [PATCH 670/999] Implement daemon suite for integration-cli For creating and stopping test daemons automatically. Signed-off-by: Alexander Morozov --- integration-cli/check_test.go | 21 ++ integration-cli/docker_cli_daemon_test.go | 403 +++++++++------------- integration-cli/docker_cli_exec_test.go | 15 +- integration-cli/docker_cli_proxy_test.go | 5 +- 4 files changed, 186 insertions(+), 258 deletions(-) diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go index 533ac127c..202799cbf 100644 --- a/integration-cli/check_test.go +++ b/integration-cli/check_test.go @@ -58,3 +58,24 @@ func (s *DockerRegistrySuite) TearDownTest(c *check.C) { s.reg.Close() s.ds.TearDownTest(c) } + +func init() { + check.Suite(&DockerDaemonSuite{ + ds: &DockerSuite{}, + }) +} + +type DockerDaemonSuite struct { + ds *DockerSuite + d *Daemon +} + +func (s *DockerDaemonSuite) SetUpTest(c *check.C) { + s.d = NewDaemon(c) + s.ds.SetUpTest(c) +} + +func (s *DockerDaemonSuite) TearDownTest(c *check.C) { + s.d.Stop() + s.ds.TearDownTest(c) +} diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 034c17ebb..e099995ad 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -16,25 +16,23 @@ import ( "github.com/go-check/check" ) -func (s *DockerSuite) TestDaemonRestartWithRunningContainersPorts(c *check.C) { - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonRestartWithRunningContainersPorts(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() - if out, err := d.Cmd("run", "-d", "--name", "top1", "-p", "1234:80", "--restart", "always", "busybox:latest", "top"); err != nil { + if out, err := s.d.Cmd("run", "-d", "--name", "top1", "-p", "1234:80", "--restart", "always", "busybox:latest", "top"); err != nil { c.Fatalf("Could not run top1: err=%v\n%s", err, out) } // --restart=no by default - if out, err := d.Cmd("run", "-d", "--name", "top2", "-p", "80", "busybox:latest", "top"); err != nil { + if out, err := s.d.Cmd("run", "-d", "--name", "top2", "-p", "80", "busybox:latest", "top"); err != nil { c.Fatalf("Could not run top2: err=%v\n%s", err, out) } testRun := func(m map[string]bool, prefix string) { var format string for cont, shouldRun := range m { - out, err := d.Cmd("ps") + out, err := s.d.Cmd("ps") if err != nil { c.Fatalf("Could not run ps: err=%v\n%q", err, out) } @@ -51,34 +49,30 @@ func (s *DockerSuite) TestDaemonRestartWithRunningContainersPorts(c *check.C) { testRun(map[string]bool{"top1": true, "top2": true}, "") - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatalf("Could not restart daemon: %v", err) } - testRun(map[string]bool{"top1": true, "top2": false}, "After daemon restart: ") - } -func (s *DockerSuite) TestDaemonRestartWithVolumesRefs(c *check.C) { - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonRestartWithVolumesRefs(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatal(err) } - defer d.Stop() - if out, err := d.Cmd("run", "-d", "--name", "volrestarttest1", "-v", "/foo", "busybox"); err != nil { + if out, err := s.d.Cmd("run", "-d", "--name", "volrestarttest1", "-v", "/foo", "busybox"); err != nil { c.Fatal(err, out) } - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatal(err) } - if _, err := d.Cmd("run", "-d", "--volumes-from", "volrestarttest1", "--name", "volrestarttest2", "busybox", "top"); err != nil { + if _, err := s.d.Cmd("run", "-d", "--volumes-from", "volrestarttest1", "--name", "volrestarttest2", "busybox", "top"); err != nil { c.Fatal(err) } - if out, err := d.Cmd("rm", "-fv", "volrestarttest2"); err != nil { + if out, err := s.d.Cmd("rm", "-fv", "volrestarttest2"); err != nil { c.Fatal(err, out) } - v, err := d.Cmd("inspect", "--format", "{{ json .Volumes }}", "volrestarttest1") + v, err := s.d.Cmd("inspect", "--format", "{{ json .Volumes }}", "volrestarttest1") if err != nil { c.Fatal(err) } @@ -87,30 +81,25 @@ func (s *DockerSuite) TestDaemonRestartWithVolumesRefs(c *check.C) { if _, err := os.Stat(volumes["/foo"]); err != nil { c.Fatalf("Expected volume to exist: %s - %s", volumes["/foo"], err) } - } -func (s *DockerSuite) TestDaemonStartIptablesFalse(c *check.C) { - d := NewDaemon(c) - if err := d.Start("--iptables=false"); err != nil { +func (s *DockerDaemonSuite) TestDaemonStartIptablesFalse(c *check.C) { + if err := s.d.Start("--iptables=false"); err != nil { c.Fatalf("we should have been able to start the daemon with passing iptables=false: %v", err) } - d.Stop() - } // Issue #8444: If docker0 bridge is modified (intentionally or unintentionally) and // no longer has an IP associated, we should gracefully handle that case and associate // an IP with it rather than fail daemon start -func (s *DockerSuite) TestDaemonStartBridgeWithoutIPAssociation(c *check.C) { - d := NewDaemon(c) +func (s *DockerDaemonSuite) TestDaemonStartBridgeWithoutIPAssociation(c *check.C) { // rather than depending on brctl commands to verify docker0 is created and up // let's start the daemon and stop it, and then make a modification to run the // actual test - if err := d.Start(); err != nil { + if err := s.d.Start(); err != nil { c.Fatalf("Could not start daemon: %v", err) } - if err := d.Stop(); err != nil { + if err := s.d.Stop(); err != nil { c.Fatalf("Could not stop daemon: %v", err) } @@ -121,27 +110,18 @@ func (s *DockerSuite) TestDaemonStartBridgeWithoutIPAssociation(c *check.C) { c.Fatalf("failed to remove docker0 IP association: %v, stdout: %q, stderr: %q", err, stdout, stderr) } - if err := d.Start(); err != nil { + if err := s.d.Start(); err != nil { warning := "**WARNING: Docker bridge network in bad state--delete docker0 bridge interface to fix" c.Fatalf("Could not start daemon when docker0 has no IP address: %v\n%s", err, warning) } - - // cleanup - stop the daemon if test passed - if err := d.Stop(); err != nil { - c.Fatalf("Could not stop daemon: %v", err) - } - } -func (s *DockerSuite) TestDaemonIptablesClean(c *check.C) { - - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonIptablesClean(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() - if out, err := d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { + if out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { c.Fatalf("Could not run top: %s, %v", out, err) } @@ -157,7 +137,7 @@ func (s *DockerSuite) TestDaemonIptablesClean(c *check.C) { c.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out) } - if err := d.Stop(); err != nil { + if err := s.d.Stop(); err != nil { c.Fatalf("Could not stop daemon: %v", err) } @@ -171,18 +151,14 @@ func (s *DockerSuite) TestDaemonIptablesClean(c *check.C) { if strings.Contains(out, ipTablesSearchString) { c.Fatalf("iptables output should not have contained %q, but was %q", ipTablesSearchString, out) } - } -func (s *DockerSuite) TestDaemonIptablesCreate(c *check.C) { - - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonIptablesCreate(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() - if out, err := d.Cmd("run", "-d", "--name", "top", "--restart=always", "-p", "80", "busybox:latest", "top"); err != nil { + if out, err := s.d.Cmd("run", "-d", "--name", "top", "--restart=always", "-p", "80", "busybox:latest", "top"); err != nil { c.Fatalf("Could not run top: %s, %v", out, err) } @@ -198,12 +174,12 @@ func (s *DockerSuite) TestDaemonIptablesCreate(c *check.C) { c.Fatalf("iptables output should have contained %q, but was %q", ipTablesSearchString, out) } - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatalf("Could not restart daemon: %v", err) } // make sure the container is not running - runningOut, err := d.Cmd("inspect", "--format='{{.State.Running}}'", "top") + runningOut, err := s.d.Cmd("inspect", "--format='{{.State.Running}}'", "top") if err != nil { c.Fatalf("Could not inspect on container: %s, %v", out, err) } @@ -221,69 +197,64 @@ func (s *DockerSuite) TestDaemonIptablesCreate(c *check.C) { if !strings.Contains(out, ipTablesSearchString) { c.Fatalf("iptables output after restart should have contained %q, but was %q", ipTablesSearchString, out) } - } -func (s *DockerSuite) TestDaemonLoggingLevel(c *check.C) { - d := NewDaemon(c) +func (s *DockerDaemonSuite) TestDaemonLogLevelWrong(c *check.C) { + c.Assert(s.d.Start("--log-level=bogus"), check.NotNil, check.Commentf("Daemon shouldn't start with wrong log level")) +} - if err := d.Start("--log-level=bogus"); err == nil { - c.Fatal("Daemon should not have been able to start") - } - - d = NewDaemon(c) - if err := d.Start("--log-level=debug"); err != nil { +func (s *DockerDaemonSuite) TestDaemonLogLevelDebug(c *check.C) { + if err := s.d.Start("--log-level=debug"); err != nil { c.Fatal(err) } - d.Stop() - content, _ := ioutil.ReadFile(d.logFile.Name()) + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { c.Fatalf(`Missing level="debug" in log file:\n%s`, string(content)) } +} - d = NewDaemon(c) - if err := d.Start("--log-level=fatal"); err != nil { +func (s *DockerDaemonSuite) TestDaemonLogLevelFatal(c *check.C) { + // we creating new daemons to create new logFile + if err := s.d.Start("--log-level=fatal"); err != nil { c.Fatal(err) } - d.Stop() - content, _ = ioutil.ReadFile(d.logFile.Name()) + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if strings.Contains(string(content), `level=debug`) { c.Fatalf(`Should not have level="debug" in log file:\n%s`, string(content)) } +} - d = NewDaemon(c) - if err := d.Start("-D"); err != nil { +func (s *DockerDaemonSuite) TestDaemonFlagD(c *check.C) { + if err := s.d.Start("-D"); err != nil { c.Fatal(err) } - d.Stop() - content, _ = ioutil.ReadFile(d.logFile.Name()) + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { c.Fatalf(`Missing level="debug" in log file using -D:\n%s`, string(content)) } +} - d = NewDaemon(c) - if err := d.Start("--debug"); err != nil { +func (s *DockerDaemonSuite) TestDaemonFlagDebug(c *check.C) { + if err := s.d.Start("--debug"); err != nil { c.Fatal(err) } - d.Stop() - content, _ = ioutil.ReadFile(d.logFile.Name()) + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { c.Fatalf(`Missing level="debug" in log file using --debug:\n%s`, string(content)) } +} - d = NewDaemon(c) - if err := d.Start("--debug", "--log-level=fatal"); err != nil { +func (s *DockerDaemonSuite) TestDaemonFlagDebugLogLevelFatal(c *check.C) { + if err := s.d.Start("--debug", "--log-level=fatal"); err != nil { c.Fatal(err) } - d.Stop() - content, _ = ioutil.ReadFile(d.logFile.Name()) + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if !strings.Contains(string(content), `level=debug`) { c.Fatalf(`Missing level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content)) } - } -func (s *DockerSuite) TestDaemonAllocatesListeningPort(c *check.C) { +func (s *DockerDaemonSuite) TestDaemonAllocatesListeningPort(c *check.C) { listeningPorts := [][]string{ {"0.0.0.0", "0.0.0.0", "5678"}, {"127.0.0.1", "127.0.0.1", "1234"}, @@ -295,31 +266,25 @@ func (s *DockerSuite) TestDaemonAllocatesListeningPort(c *check.C) { cmdArgs = append(cmdArgs, "--host", fmt.Sprintf("tcp://%s:%s", hostDirective[0], hostDirective[2])) } - d := NewDaemon(c) - if err := d.StartWithBusybox(cmdArgs...); err != nil { + if err := s.d.StartWithBusybox(cmdArgs...); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() for _, hostDirective := range listeningPorts { - output, err := d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", hostDirective[1], hostDirective[2]), "busybox", "true") + output, err := s.d.Cmd("run", "-p", fmt.Sprintf("%s:%s:80", hostDirective[1], hostDirective[2]), "busybox", "true") if err == nil { c.Fatalf("Container should not start, expected port already allocated error: %q", output) } else if !strings.Contains(output, "port is already allocated") { c.Fatalf("Expected port is already allocated error: %q", output) } } - } // #9629 -func (s *DockerSuite) TestDaemonVolumesBindsRefs(c *check.C) { - d := NewDaemon(c) - - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonVolumesBindsRefs(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatal(err) } - defer d.Stop() tmp, err := ioutil.TempDir(os.TempDir(), "") if err != nil { @@ -331,28 +296,26 @@ func (s *DockerSuite) TestDaemonVolumesBindsRefs(c *check.C) { c.Fatal(err) } - if out, err := d.Cmd("create", "-v", tmp+":/foo", "--name=voltest", "busybox"); err != nil { + if out, err := s.d.Cmd("create", "-v", tmp+":/foo", "--name=voltest", "busybox"); err != nil { c.Fatal(err, out) } - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatal(err) } - if out, err := d.Cmd("run", "--volumes-from=voltest", "--name=consumer", "busybox", "/bin/sh", "-c", "[ -f /foo/test ]"); err != nil { + if out, err := s.d.Cmd("run", "--volumes-from=voltest", "--name=consumer", "busybox", "/bin/sh", "-c", "[ -f /foo/test ]"); err != nil { c.Fatal(err, out) } - } -func (s *DockerSuite) TestDaemonKeyGeneration(c *check.C) { +func (s *DockerDaemonSuite) TestDaemonKeyGeneration(c *check.C) { // TODO: skip or update for Windows daemon os.Remove("/etc/docker/key.json") - d := NewDaemon(c) - if err := d.Start(); err != nil { + if err := s.d.Start(); err != nil { c.Fatalf("Could not start daemon: %v", err) } - d.Stop() + s.d.Stop() k, err := libtrust.LoadKeyFile("/etc/docker/key.json") if err != nil { @@ -363,10 +326,9 @@ func (s *DockerSuite) TestDaemonKeyGeneration(c *check.C) { if len(kid) != 59 { c.Fatalf("Bad key ID: %s", kid) } - } -func (s *DockerSuite) TestDaemonKeyMigration(c *check.C) { +func (s *DockerDaemonSuite) TestDaemonKeyMigration(c *check.C) { // TODO: skip or update for Windows daemon os.Remove("/etc/docker/key.json") k1, err := libtrust.GenerateECP256PrivateKey() @@ -380,11 +342,10 @@ func (s *DockerSuite) TestDaemonKeyMigration(c *check.C) { c.Fatalf("Error saving private key: %s", err) } - d := NewDaemon(c) - if err := d.Start(); err != nil { + if err := s.d.Start(); err != nil { c.Fatalf("Could not start daemon: %v", err) } - d.Stop() + s.d.Stop() k2, err := libtrust.LoadKeyFile("/etc/docker/key.json") if err != nil { @@ -393,29 +354,25 @@ func (s *DockerSuite) TestDaemonKeyMigration(c *check.C) { if k1.KeyID() != k2.KeyID() { c.Fatalf("Key not migrated") } - } // Simulate an older daemon (pre 1.3) coming up with volumes specified in containers // without corresponding volume json -func (s *DockerSuite) TestDaemonUpgradeWithVolumes(c *check.C) { - d := NewDaemon(c) - +func (s *DockerDaemonSuite) TestDaemonUpgradeWithVolumes(c *check.C) { graphDir := filepath.Join(os.TempDir(), "docker-test") defer os.RemoveAll(graphDir) - if err := d.StartWithBusybox("-g", graphDir); err != nil { + if err := s.d.StartWithBusybox("-g", graphDir); err != nil { c.Fatal(err) } - defer d.Stop() tmpDir := filepath.Join(os.TempDir(), "test") defer os.RemoveAll(tmpDir) - if out, err := d.Cmd("create", "-v", tmpDir+":/foo", "--name=test", "busybox"); err != nil { + if out, err := s.d.Cmd("create", "-v", tmpDir+":/foo", "--name=test", "busybox"); err != nil { c.Fatal(err, out) } - if err := d.Stop(); err != nil { + if err := s.d.Stop(); err != nil { c.Fatal(err) } @@ -430,7 +387,7 @@ func (s *DockerSuite) TestDaemonUpgradeWithVolumes(c *check.C) { c.Fatal(err) } - if err := d.Start("-g", graphDir); err != nil { + if err := s.d.Start("-g", graphDir); err != nil { c.Fatal(err) } @@ -447,7 +404,7 @@ func (s *DockerSuite) TestDaemonUpgradeWithVolumes(c *check.C) { } // Now with just removing the volume config and not the volume data - if err := d.Stop(); err != nil { + if err := s.d.Stop(); err != nil { c.Fatal(err) } @@ -455,7 +412,7 @@ func (s *DockerSuite) TestDaemonUpgradeWithVolumes(c *check.C) { c.Fatal(err) } - if err := d.Start("-g", graphDir); err != nil { + if err := s.d.Start("-g", graphDir); err != nil { c.Fatal(err) } @@ -467,45 +424,37 @@ func (s *DockerSuite) TestDaemonUpgradeWithVolumes(c *check.C) { if len(dir) == 0 { c.Fatalf("expected volumes config dir to contain data for new volume") } - } // GH#11320 - verify that the daemon exits on failure properly // Note that this explicitly tests the conflict of {-b,--bridge} and {--bip} options as the means // to get a daemon init failure; no other tests for -b/--bip conflict are therefore required -func (s *DockerSuite) TestDaemonExitOnFailure(c *check.C) { - d := NewDaemon(c) - defer d.Stop() - +func (s *DockerDaemonSuite) TestDaemonExitOnFailure(c *check.C) { //attempt to start daemon with incorrect flags (we know -b and --bip conflict) - if err := d.Start("--bridge", "nosuchbridge", "--bip", "1.1.1.1"); err != nil { + if err := s.d.Start("--bridge", "nosuchbridge", "--bip", "1.1.1.1"); err != nil { //verify we got the right error if !strings.Contains(err.Error(), "Daemon exited and never started") { c.Fatalf("Expected daemon not to start, got %v", err) } // look in the log and make sure we got the message that daemon is shutting down - runCmd := exec.Command("grep", "Error starting daemon", d.LogfileName()) + runCmd := exec.Command("grep", "Error starting daemon", s.d.LogfileName()) if out, _, err := runCommandWithOutput(runCmd); err != nil { c.Fatalf("Expected 'Error starting daemon' message; but doesn't exist in log: %q, err: %v", out, err) } } else { //if we didn't get an error and the daemon is running, this is a failure - d.Stop() c.Fatal("Conflicting options should cause the daemon to error out with a failure") } - } -func (s *DockerSuite) TestDaemonUlimitDefaults(c *check.C) { +func (s *DockerDaemonSuite) TestDaemonUlimitDefaults(c *check.C) { testRequires(c, NativeExecDriver) - d := NewDaemon(c) - if err := d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil { + if err := s.d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil { c.Fatal(err) } - defer d.Stop() - out, err := d.Cmd("run", "--ulimit", "nproc=2048", "--name=test", "busybox", "/bin/sh", "-c", "echo $(ulimit -n); echo $(ulimit -p)") + out, err := s.d.Cmd("run", "--ulimit", "nproc=2048", "--name=test", "busybox", "/bin/sh", "-c", "echo $(ulimit -n); echo $(ulimit -p)") if err != nil { c.Fatal(out, err) } @@ -525,11 +474,11 @@ func (s *DockerSuite) TestDaemonUlimitDefaults(c *check.C) { } // Now restart daemon with a new default - if err := d.Restart("--default-ulimit", "nofile=43"); err != nil { + if err := s.d.Restart("--default-ulimit", "nofile=43"); err != nil { c.Fatal(err) } - out, err = d.Cmd("start", "-a", "test") + out, err = s.d.Cmd("start", "-a", "test") if err != nil { c.Fatal(err) } @@ -547,53 +496,46 @@ func (s *DockerSuite) TestDaemonUlimitDefaults(c *check.C) { if nproc != "2048" { c.Fatalf("exepcted `ulimit -p` to be 2048, got: %s", nproc) } - } // #11315 -func (s *DockerSuite) TestDaemonRestartRenameContainer(c *check.C) { - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { - c.Fatal(err) - } - defer d.Stop() - - if out, err := d.Cmd("run", "--name=test", "busybox"); err != nil { - c.Fatal(err, out) - } - - if out, err := d.Cmd("rename", "test", "test2"); err != nil { - c.Fatal(err, out) - } - - if err := d.Restart(); err != nil { +func (s *DockerDaemonSuite) TestDaemonRestartRenameContainer(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatal(err) } - if out, err := d.Cmd("start", "test2"); err != nil { + if out, err := s.d.Cmd("run", "--name=test", "busybox"); err != nil { c.Fatal(err, out) } + if out, err := s.d.Cmd("rename", "test", "test2"); err != nil { + c.Fatal(err, out) + } + + if err := s.d.Restart(); err != nil { + c.Fatal(err) + } + + if out, err := s.d.Cmd("start", "test2"); err != nil { + c.Fatal(err, out) + } } -func (s *DockerSuite) TestDaemonLoggingDriverDefault(c *check.C) { - d := NewDaemon(c) - - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonLoggingDriverDefault(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatal(err) } - defer d.Stop() - out, err := d.Cmd("run", "-d", "busybox", "echo", "testline") + out, err := s.d.Cmd("run", "-d", "busybox", "echo", "testline") if err != nil { c.Fatal(out, err) } id := strings.TrimSpace(out) - if out, err := d.Cmd("wait", id); err != nil { + if out, err := s.d.Cmd("wait", id); err != nil { c.Fatal(out, err) } - logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") + logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err != nil { c.Fatal(err) @@ -621,72 +563,63 @@ func (s *DockerSuite) TestDaemonLoggingDriverDefault(c *check.C) { } } -func (s *DockerSuite) TestDaemonLoggingDriverDefaultOverride(c *check.C) { - d := NewDaemon(c) - - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonLoggingDriverDefaultOverride(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatal(err) } - defer d.Stop() - out, err := d.Cmd("run", "-d", "--log-driver=none", "busybox", "echo", "testline") + out, err := s.d.Cmd("run", "-d", "--log-driver=none", "busybox", "echo", "testline") if err != nil { c.Fatal(out, err) } id := strings.TrimSpace(out) - if out, err := d.Cmd("wait", id); err != nil { + if out, err := s.d.Cmd("wait", id); err != nil { c.Fatal(out, err) } - logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") + logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) { c.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err) } } -func (s *DockerSuite) TestDaemonLoggingDriverNone(c *check.C) { - d := NewDaemon(c) - - if err := d.StartWithBusybox("--log-driver=none"); err != nil { +func (s *DockerDaemonSuite) TestDaemonLoggingDriverNone(c *check.C) { + if err := s.d.StartWithBusybox("--log-driver=none"); err != nil { c.Fatal(err) } - defer d.Stop() - out, err := d.Cmd("run", "-d", "busybox", "echo", "testline") + out, err := s.d.Cmd("run", "-d", "busybox", "echo", "testline") if err != nil { c.Fatal(out, err) } id := strings.TrimSpace(out) - if out, err := d.Cmd("wait", id); err != nil { + if out, err := s.d.Cmd("wait", id); err != nil { c.Fatal(out, err) } - logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") + logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err == nil || !os.IsNotExist(err) { c.Fatalf("%s shouldn't exits, error on Stat: %s", logPath, err) } } -func (s *DockerSuite) TestDaemonLoggingDriverNoneOverride(c *check.C) { - d := NewDaemon(c) - - if err := d.StartWithBusybox("--log-driver=none"); err != nil { +func (s *DockerDaemonSuite) TestDaemonLoggingDriverNoneOverride(c *check.C) { + if err := s.d.StartWithBusybox("--log-driver=none"); err != nil { c.Fatal(err) } - defer d.Stop() - out, err := d.Cmd("run", "-d", "--log-driver=json-file", "busybox", "echo", "testline") + out, err := s.d.Cmd("run", "-d", "--log-driver=json-file", "busybox", "echo", "testline") if err != nil { c.Fatal(out, err) } id := strings.TrimSpace(out) - if out, err := d.Cmd("wait", id); err != nil { + if out, err := s.d.Cmd("wait", id); err != nil { c.Fatal(out, err) } - logPath := filepath.Join(d.folder, "graph", "containers", id, id+"-json.log") + logPath := filepath.Join(s.d.folder, "graph", "containers", id, id+"-json.log") if _, err := os.Stat(logPath); err != nil { c.Fatal(err) @@ -714,20 +647,17 @@ func (s *DockerSuite) TestDaemonLoggingDriverNoneOverride(c *check.C) { } } -func (s *DockerSuite) TestDaemonLoggingDriverNoneLogsError(c *check.C) { - d := NewDaemon(c) - - if err := d.StartWithBusybox("--log-driver=none"); err != nil { +func (s *DockerDaemonSuite) TestDaemonLoggingDriverNoneLogsError(c *check.C) { + if err := s.d.StartWithBusybox("--log-driver=none"); err != nil { c.Fatal(err) } - defer d.Stop() - out, err := d.Cmd("run", "-d", "busybox", "echo", "testline") + out, err := s.d.Cmd("run", "-d", "busybox", "echo", "testline") if err != nil { c.Fatal(out, err) } id := strings.TrimSpace(out) - out, err = d.Cmd("logs", id) + out, err = s.d.Cmd("logs", id) if err == nil { c.Fatalf("Logs should fail with \"none\" driver") } @@ -736,54 +666,50 @@ func (s *DockerSuite) TestDaemonLoggingDriverNoneLogsError(c *check.C) { } } -func (s *DockerSuite) TestDaemonDots(c *check.C) { - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonDots(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatal(err) } - defer d.Stop() // Now create 4 containers - if _, err := d.Cmd("create", "busybox"); err != nil { + if _, err := s.d.Cmd("create", "busybox"); err != nil { c.Fatalf("Error creating container: %q", err) } - if _, err := d.Cmd("create", "busybox"); err != nil { + if _, err := s.d.Cmd("create", "busybox"); err != nil { c.Fatalf("Error creating container: %q", err) } - if _, err := d.Cmd("create", "busybox"); err != nil { + if _, err := s.d.Cmd("create", "busybox"); err != nil { c.Fatalf("Error creating container: %q", err) } - if _, err := d.Cmd("create", "busybox"); err != nil { + if _, err := s.d.Cmd("create", "busybox"); err != nil { c.Fatalf("Error creating container: %q", err) } - d.Stop() + s.d.Stop() - d.Start("--log-level=debug") - d.Stop() - content, _ := ioutil.ReadFile(d.logFile.Name()) + s.d.Start("--log-level=debug") + s.d.Stop() + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if strings.Contains(string(content), "....") { c.Fatalf("Debug level should not have ....\n%s", string(content)) } - d.Start("--log-level=error") - d.Stop() - content, _ = ioutil.ReadFile(d.logFile.Name()) + s.d.Start("--log-level=error") + s.d.Stop() + content, _ = ioutil.ReadFile(s.d.logFile.Name()) if strings.Contains(string(content), "....") { c.Fatalf("Error level should not have ....\n%s", string(content)) } - d.Start("--log-level=info") - d.Stop() - content, _ = ioutil.ReadFile(d.logFile.Name()) + s.d.Start("--log-level=info") + s.d.Stop() + content, _ = ioutil.ReadFile(s.d.logFile.Name()) if !strings.Contains(string(content), "....") { c.Fatalf("Info level should have ....\n%s", string(content)) } - } -func (s *DockerSuite) TestDaemonUnixSockCleanedUp(c *check.C) { - d := NewDaemon(c) +func (s *DockerDaemonSuite) TestDaemonUnixSockCleanedUp(c *check.C) { dir, err := ioutil.TempDir("", "socket-cleanup-test") if err != nil { c.Fatal(err) @@ -791,26 +717,24 @@ func (s *DockerSuite) TestDaemonUnixSockCleanedUp(c *check.C) { defer os.RemoveAll(dir) sockPath := filepath.Join(dir, "docker.sock") - if err := d.Start("--host", "unix://"+sockPath); err != nil { + if err := s.d.Start("--host", "unix://"+sockPath); err != nil { c.Fatal(err) } - defer d.Stop() if _, err := os.Stat(sockPath); err != nil { c.Fatal("socket does not exist") } - if err := d.Stop(); err != nil { + if err := s.d.Stop(); err != nil { c.Fatal(err) } if _, err := os.Stat(sockPath); err == nil || !os.IsNotExist(err) { c.Fatal("unix socket is not cleaned up") } - } -func (s *DockerSuite) TestDaemonwithwrongkey(c *check.C) { +func (s *DockerDaemonSuite) TestDaemonwithwrongkey(c *check.C) { type Config struct { Crv string `json:"crv"` D string `json:"d"` @@ -821,12 +745,11 @@ func (s *DockerSuite) TestDaemonwithwrongkey(c *check.C) { } os.Remove("/etc/docker/key.json") - d := NewDaemon(c) - if err := d.Start(); err != nil { + if err := s.d.Start(); err != nil { c.Fatalf("Failed to start daemon: %v", err) } - if err := d.Stop(); err != nil { + if err := s.d.Stop(); err != nil { c.Fatalf("Could not stop daemon: %v", err) } @@ -855,46 +778,41 @@ func (s *DockerSuite) TestDaemonwithwrongkey(c *check.C) { c.Fatalf("Error ioutil.WriteFile: %s", err) } - d1 := NewDaemon(c) defer os.Remove("/etc/docker/key.json") - if err := d1.Start(); err == nil { - d1.Stop() + if err := s.d.Start(); err == nil { c.Fatalf("It should not be successful to start daemon with wrong key: %v", err) } - content, _ := ioutil.ReadFile(d1.logFile.Name()) + content, _ := ioutil.ReadFile(s.d.logFile.Name()) if !strings.Contains(string(content), "Public Key ID does not match") { c.Fatal("Missing KeyID message from daemon logs") } - } -func (s *DockerSuite) TestDaemonRestartKillWait(c *check.C) { - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { +func (s *DockerDaemonSuite) TestDaemonRestartKillWait(c *check.C) { + if err := s.d.StartWithBusybox(); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() - out, err := d.Cmd("run", "-id", "busybox", "/bin/cat") + out, err := s.d.Cmd("run", "-id", "busybox", "/bin/cat") if err != nil { c.Fatalf("Could not run /bin/cat: err=%v\n%s", err, out) } containerID := strings.TrimSpace(out) - if out, err := d.Cmd("kill", containerID); err != nil { + if out, err := s.d.Cmd("kill", containerID); err != nil { c.Fatalf("Could not kill %s: err=%v\n%s", containerID, err, out) } - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatalf("Could not restart daemon: %v", err) } errchan := make(chan error) go func() { - if out, err := d.Cmd("wait", containerID); err != nil { + if out, err := s.d.Cmd("wait", containerID); err != nil { errchan <- fmt.Errorf("%v:\n%s", err, out) } close(errchan) @@ -908,26 +826,23 @@ func (s *DockerSuite) TestDaemonRestartKillWait(c *check.C) { c.Fatal(err) } } - } // TestHttpsInfo connects via two-way authenticated HTTPS to the info endpoint -func (s *DockerSuite) TestHttpsInfo(c *check.C) { +func (s *DockerDaemonSuite) TestHttpsInfo(c *check.C) { const ( testDaemonHttpsAddr = "localhost:4271" ) - d := NewDaemon(c) - if err := d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem", + if err := s.d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem", "--tlskey", "fixtures/https/server-key.pem", "-H", testDaemonHttpsAddr); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() //force tcp protocol host := fmt.Sprintf("tcp://%s", testDaemonHttpsAddr) daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-cert.pem", "--tlskey", "fixtures/https/client-key.pem"} - out, err := d.CmdWithArgs(daemonArgs, "info") + out, err := s.d.CmdWithArgs(daemonArgs, "info") if err != nil { c.Fatalf("Error Occurred: %s and output: %s", err, out) } @@ -935,22 +850,20 @@ func (s *DockerSuite) TestHttpsInfo(c *check.C) { // 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 (s *DockerSuite) TestHttpsInfoRogueCert(c *check.C) { +func (s *DockerDaemonSuite) TestHttpsInfoRogueCert(c *check.C) { const ( errBadCertificate = "remote error: bad certificate" testDaemonHttpsAddr = "localhost:4271" ) - d := NewDaemon(c) - if err := d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem", + if err := s.d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-cert.pem", "--tlskey", "fixtures/https/server-key.pem", "-H", testDaemonHttpsAddr); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() //force tcp protocol host := fmt.Sprintf("tcp://%s", testDaemonHttpsAddr) daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-rogue-cert.pem", "--tlskey", "fixtures/https/client-rogue-key.pem"} - out, err := d.CmdWithArgs(daemonArgs, "info") + out, err := s.d.CmdWithArgs(daemonArgs, "info") if err == nil || !strings.Contains(out, errBadCertificate) { c.Fatalf("Expected err: %s, got instead: %s and output: %s", errBadCertificate, err, out) } @@ -958,22 +871,20 @@ func (s *DockerSuite) TestHttpsInfoRogueCert(c *check.C) { // 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 (s *DockerSuite) TestHttpsInfoRogueServerCert(c *check.C) { +func (s *DockerDaemonSuite) TestHttpsInfoRogueServerCert(c *check.C) { const ( errCaUnknown = "x509: certificate signed by unknown authority" testDaemonRogueHttpsAddr = "localhost:4272" ) - d := NewDaemon(c) - if err := d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-rogue-cert.pem", + if err := s.d.Start("--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/server-rogue-cert.pem", "--tlskey", "fixtures/https/server-rogue-key.pem", "-H", testDaemonRogueHttpsAddr); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() //force tcp protocol host := fmt.Sprintf("tcp://%s", testDaemonRogueHttpsAddr) daemonArgs := []string{"--host", host, "--tlsverify", "--tlscacert", "fixtures/https/ca.pem", "--tlscert", "fixtures/https/client-rogue-cert.pem", "--tlskey", "fixtures/https/client-rogue-key.pem"} - out, err := d.CmdWithArgs(daemonArgs, "info") + out, err := s.d.CmdWithArgs(daemonArgs, "info") if err == nil || !strings.Contains(out, errCaUnknown) { c.Fatalf("Expected err: %s, got instead: %s and output: %s", errCaUnknown, err, out) } diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index c6910d432..cbdd59756 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -117,28 +117,26 @@ func (s *DockerSuite) TestExecAfterContainerRestart(c *check.C) { } -func (s *DockerSuite) TestExecAfterDaemonRestart(c *check.C) { +func (s *DockerDaemonSuite) TestExecAfterDaemonRestart(c *check.C) { testRequires(c, SameHostDaemon) - d := NewDaemon(c) - if err := d.StartWithBusybox(); err != nil { + if err := s.d.StartWithBusybox(); err != nil { c.Fatalf("Could not start daemon with busybox: %v", err) } - defer d.Stop() - if out, err := d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { + if out, err := s.d.Cmd("run", "-d", "--name", "top", "-p", "80", "busybox:latest", "top"); err != nil { c.Fatalf("Could not run top: err=%v\n%s", err, out) } - if err := d.Restart(); err != nil { + if err := s.d.Restart(); err != nil { c.Fatalf("Could not restart daemon: %v", err) } - if out, err := d.Cmd("start", "top"); err != nil { + if out, err := s.d.Cmd("start", "top"); err != nil { c.Fatalf("Could not start top after daemon restart: err=%v\n%s", err, out) } - out, err := d.Cmd("exec", "top", "echo", "hello") + out, err := s.d.Cmd("exec", "top", "echo", "hello") if err != nil { c.Fatalf("Could not exec on container top: err=%v\n%s", err, out) } @@ -147,7 +145,6 @@ func (s *DockerSuite) TestExecAfterDaemonRestart(c *check.C) { if outStr != "hello" { c.Errorf("container should've printed hello, instead printed %q", outStr) } - } // Regression test for #9155, #9044 diff --git a/integration-cli/docker_cli_proxy_test.go b/integration-cli/docker_cli_proxy_test.go index c07ed6406..8b55c67d8 100644 --- a/integration-cli/docker_cli_proxy_test.go +++ b/integration-cli/docker_cli_proxy_test.go @@ -22,7 +22,7 @@ func (s *DockerSuite) TestCliProxyDisableProxyUnixSock(c *check.C) { // Can't use localhost here since go has a special case to not use proxy if connecting to localhost // See https://golang.org/pkg/net/http/#ProxyFromEnvironment -func (s *DockerSuite) TestCliProxyProxyTCPSock(c *check.C) { +func (s *DockerDaemonSuite) TestCliProxyProxyTCPSock(c *check.C) { testRequires(c, SameHostDaemon) // get the IP to use to connect since we can't use localhost addrs, err := net.InterfaceAddrs() @@ -43,8 +43,7 @@ func (s *DockerSuite) TestCliProxyProxyTCPSock(c *check.C) { c.Fatal("could not find ip to connect to") } - d := NewDaemon(c) - if err := d.Start("-H", "tcp://"+ip+":2375"); err != nil { + if err := s.d.Start("-H", "tcp://"+ip+":2375"); err != nil { c.Fatal(err) } From d4bbbe58ddd5916c5f7d253a90fd97e3a4fd59bf Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Wed, 28 Jan 2015 14:54:25 -0800 Subject: [PATCH 671/999] Add docs for `--exec-opt` and setting `native.cgroupdriver`. update man pages. update bash completion. Docker-DCO-1.1-Signed-off-by: Jessica Frazelle (github: jfrazelle) Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) --- contrib/completion/bash/docker | 1 + contrib/completion/fish/docker.fish | 1 + docs/man/docker.1.md | 15 +++++++++++++++ docs/sources/reference/commandline/cli.md | 21 +++++++++++++++++++++ 4 files changed, 38 insertions(+) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index f3b833158..5b7a102a6 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -1151,6 +1151,7 @@ _docker() { --dns --dns-search --exec-driver -e + --exec-opt --fixed-cidr --fixed-cidr-v6 --graph -g diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index d3237588e..c53591185 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -51,6 +51,7 @@ complete -c docker -f -n '__fish_docker_no_subcommand' -s d -l daemon -d 'Enable 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' -l dns-search -d 'Force Docker to use specific DNS search domains' 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' -l exec-opt -d 'Set exec driver options' complete -c docker -f -n '__fish_docker_no_subcommand' -l fixed-cidr -d 'IPv4 subnet for fixed IPs (e.g. 10.20.0.0/16)' complete -c docker -f -n '__fish_docker_no_subcommand' -l fixed-cidr-v6 -d 'IPv6 subnet for fixed IPs (e.g.: 2001:a02b/48)' 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' diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index 0196b6364..8ee5bc3cf 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -124,6 +124,9 @@ unix://[/path/to/socket] to use. **-v**, **--version**=*true*|*false* Print version information and quit. Default is false. +**--exec-opt**=[] + Set exec driver options. See EXEC DRIVER OPTIONS. + **--selinux-enabled**=*true*|*false* Enable selinux support. Default is false. SELinux does not presently support the BTRFS storage driver. @@ -319,6 +322,18 @@ for data and metadata: --storage-opt dm.metadatadev=/dev/vdc \ --storage-opt dm.basesize=20G +# EXEC DRIVER OPTIONS + +Options to the exec-driver can be specified with the **--exec-opt** flags. The +only driver accepting options is the *native* (libcontainer) driver. Therefore +use these flags with **-s=**native. + +The following is the only *native* option: + +#### native.cgroupdriver +Specifies the management of the container's cgroups. As of now the only viable +options are `cgroupfs` and `systemd`. The option will always fallback to `cgroupfs`. + #### Client For specific client examples please see the man page for the specific Docker command. For example: diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 26659c8ff..e2d073a42 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -452,6 +452,27 @@ https://linuxcontainers.org/) via the `lxc` execution driver, however, this is not where the primary development of new functionality is taking place. Add `-e lxc` to the daemon flags to use the `lxc` execution driver. +#### Exec driver options + +Particular exec-driver can be configured with options specified with +`--exec-opt` flags. The only driver accepting options is `native` +(libcontainer) as of now. All its options are prefixed with `native`. + +Currently supported options are: + + * `native.cgroupdriver` + + Specifies the management of the container's cgroups. As of now the only + viable options are `cgroupfs` and `systemd`. The option will always + fallback to `cgroupfs`. By default, if no option is specified, the + execdriver will try `systemd` and fallback to `cgroupfs`. Same applies if + `systemd` is passed as the `cgroupdriver` but is not capable of being used. + + Example use: + + $ sudo docker -d --exec-opt native.cgroupdriver=cgroupfs + + ### Daemon DNS options From 2afcd10202283478cbafb21e8c5f90f1236acccc Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Mon, 6 Apr 2015 11:47:55 -0700 Subject: [PATCH 672/999] option to configure cgroups Signed-off-by: Jessica Frazelle --- daemon/config.go | 2 ++ daemon/daemon.go | 2 +- daemon/execdriver/execdrivers/execdrivers.go | 4 +-- daemon/execdriver/native/driver.go | 38 +++++++++++++++++++- docs/man/docker.1.md | 14 ++++---- docs/sources/reference/commandline/cli.md | 32 +++++++---------- 6 files changed, 62 insertions(+), 30 deletions(-) diff --git a/daemon/config.go b/daemon/config.go index 952fb5f74..43b08531b 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -29,6 +29,7 @@ type Config struct { GraphDriver string GraphOptions []string ExecDriver string + ExecOptions []string Mtu int SocketGroup string EnableCors bool @@ -70,6 +71,7 @@ func (config *Config) InstallFlags() { flag.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", "Set CORS headers in the remote API") opts.IPVar(&config.Bridge.DefaultIp, []string{"#ip", "-ip"}, "0.0.0.0", "Default IP when binding container ports") opts.ListVar(&config.GraphOptions, []string{"-storage-opt"}, "Set storage driver options") + opts.ListVar(&config.ExecOptions, []string{"-exec-opt"}, "Set exec driver options") // FIXME: why the inconsistency between "hosts" and "sockets"? opts.IPListVar(&config.Dns, []string{"#dns", "-dns"}, "DNS server to use") opts.DnsSearchListVar(&config.DnsSearch, []string{"-dns-search"}, "DNS search domains to use") diff --git a/daemon/daemon.go b/daemon/daemon.go index 109ee35cb..05de40217 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -942,7 +942,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService sysInfo := sysinfo.New(false) const runDir = "/var/run/docker" - ed, err := execdrivers.NewDriver(config.ExecDriver, runDir, config.Root, sysInitPath, sysInfo) + ed, err := execdrivers.NewDriver(config.ExecDriver, config.ExecOptions, runDir, config.Root, sysInitPath, sysInfo) if err != nil { return nil, err } diff --git a/daemon/execdriver/execdrivers/execdrivers.go b/daemon/execdriver/execdrivers/execdrivers.go index f6f97c930..dde0be1f0 100644 --- a/daemon/execdriver/execdrivers/execdrivers.go +++ b/daemon/execdriver/execdrivers/execdrivers.go @@ -10,7 +10,7 @@ import ( "github.com/docker/docker/pkg/sysinfo" ) -func NewDriver(name, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { +func NewDriver(name string, options []string, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { switch name { case "lxc": // we want to give the lxc driver the full docker root because it needs @@ -18,7 +18,7 @@ func NewDriver(name, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) ( // to be backwards compatible return lxc.NewDriver(root, libPath, initPath, sysInfo.AppArmor) case "native": - return native.NewDriver(path.Join(root, "execdriver", "native"), initPath) + return native.NewDriver(path.Join(root, "execdriver", "native"), initPath, options) } return nil, fmt.Errorf("unknown exec driver %s", name) } diff --git a/daemon/execdriver/native/driver.go b/daemon/execdriver/native/driver.go index ad13e1c1e..afc3f1e45 100644 --- a/daemon/execdriver/native/driver.go +++ b/daemon/execdriver/native/driver.go @@ -8,12 +8,14 @@ import ( "os" "os/exec" "path/filepath" + "strings" "sync" "syscall" "time" "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" + "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/reexec" sysinfo "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/term" @@ -39,7 +41,7 @@ type driver struct { sync.Mutex } -func NewDriver(root, initPath string) (*driver, error) { +func NewDriver(root, initPath string, options []string) (*driver, error) { meminfo, err := sysinfo.ReadMemInfo() if err != nil { return nil, err @@ -52,11 +54,45 @@ func NewDriver(root, initPath string) (*driver, error) { if err := apparmor.InstallDefaultProfile(); err != nil { return nil, err } + + // choose cgroup manager + // this makes sure there are no breaking changes to people + // who upgrade from versions without native.cgroupdriver opt cgm := libcontainer.Cgroupfs if systemd.UseSystemd() { cgm = libcontainer.SystemdCgroups } + // parse the options + for _, option := range options { + key, val, err := parsers.ParseKeyValueOpt(option) + if err != nil { + return nil, err + } + key = strings.ToLower(key) + switch key { + case "native.cgroupdriver": + // override the default if they set options + switch val { + case "systemd": + if systemd.UseSystemd() { + cgm = libcontainer.SystemdCgroups + } else { + // warn them that they chose the wrong driver + logrus.Warn("You cannot use systemd as native.cgroupdriver, using cgroupfs instead") + } + case "cgroupfs": + cgm = libcontainer.Cgroupfs + default: + return nil, fmt.Errorf("Unknown native.cgroupdriver given %q. try cgroupfs or systemd", val) + } + default: + return nil, fmt.Errorf("Unknown option %s\n", key) + } + } + + logrus.Debugf("Using %v as native.cgroupdriver", cgm) + f, err := libcontainer.New( root, cgm, diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index 8ee5bc3cf..794fb1688 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -324,15 +324,15 @@ for data and metadata: # EXEC DRIVER OPTIONS -Options to the exec-driver can be specified with the **--exec-opt** flags. The -only driver accepting options is the *native* (libcontainer) driver. Therefore -use these flags with **-s=**native. - -The following is the only *native* option: +Use the **--exec-opt** flags to specify options to the exec-driver. The only +driver that accepts this flag is the *native* (libcontainer) driver. As a +result, you must also specify **-s=**native for this option to have effect. The +following is the only *native* option: #### native.cgroupdriver -Specifies the management of the container's cgroups. As of now the only viable -options are `cgroupfs` and `systemd`. The option will always fallback to `cgroupfs`. +Specifies the management of the container's `cgroups`. You can specify +`cgroupfs` or `systemd`. If you specify `systemd` and it is not available, the +system uses `cgroupfs`. #### Client For specific client examples please see the man page for the specific Docker diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index e2d073a42..2348ff549 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -442,7 +442,7 @@ Currently supported options are: > Otherwise, set this flag for migrating existing Docker daemons to a > daemon with a supported environment. -### Docker exec-driver option +### Docker execdriver option The Docker daemon uses a specifically built `libcontainer` execution driver as its interface to the Linux kernel `namespaces`, `cgroups`, and `SELinux`. @@ -452,27 +452,21 @@ https://linuxcontainers.org/) via the `lxc` execution driver, however, this is not where the primary development of new functionality is taking place. Add `-e lxc` to the daemon flags to use the `lxc` execution driver. -#### Exec driver options +#### Options for the native execdriver -Particular exec-driver can be configured with options specified with -`--exec-opt` flags. The only driver accepting options is `native` -(libcontainer) as of now. All its options are prefixed with `native`. - -Currently supported options are: - - * `native.cgroupdriver` - - Specifies the management of the container's cgroups. As of now the only - viable options are `cgroupfs` and `systemd`. The option will always - fallback to `cgroupfs`. By default, if no option is specified, the - execdriver will try `systemd` and fallback to `cgroupfs`. Same applies if - `systemd` is passed as the `cgroupdriver` but is not capable of being used. - - Example use: - - $ sudo docker -d --exec-opt native.cgroupdriver=cgroupfs +You can configure the `native` (libcontainer) execdriver using options specified +with the `--exec-opt` flag. All the flag's options have the `native` prefix. A +single `native.cgroupdriver` option is available. +The `native.cgroupdriver` option specifies the management of the container's +cgroups. You can specify `cgroupfs` or `systemd`. If you specify `systemd` and +it is not available, the system uses `cgroupfs`. By default, if no option is +specified, the execdriver first tries `systemd` and falls back to `cgroupfs`. +This example sets the execdriver to `cgroupfs`: + $ sudo docker -d --exec-opt native.cgroupdriver=cgroupfs + +Setting this option applies to all containers the daemon launches. ### Daemon DNS options From 82daa43844556953101b201bc5983aed4fbe6233 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Fri, 10 Apr 2015 12:39:42 -0700 Subject: [PATCH 673/999] Fix for Daemon crashing when wildcards are used for COPY/ADD in the filename and the command itself Closes #12267 Signed-off-by: Doug Davis --- builder/internals.go | 9 +++++--- integration-cli/docker_cli_build_test.go | 27 +++++++++++++++++++++++- integration-cli/docker_utils.go | 1 - 3 files changed, 32 insertions(+), 5 deletions(-) diff --git a/builder/internals.go b/builder/internals.go index 09aca09f2..220cdf53d 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -156,6 +156,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp dest, allowRemote, allowDecompression, + true, ); err != nil { return err } @@ -226,7 +227,7 @@ func (b *Builder) runContextCommand(args []string, allowRemote bool, allowDecomp return nil } -func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath string, destPath string, allowRemote bool, allowDecompression bool) error { +func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath string, destPath string, allowRemote bool, allowDecompression bool, allowWildcards bool) error { if origPath != "" && origPath[0] == '/' && len(origPath) > 1 { origPath = origPath[1:] @@ -351,7 +352,7 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri } // Deal with wildcards - if ContainsWildcards(origPath) { + if allowWildcards && ContainsWildcards(origPath) { for _, fileInfo := range b.context.GetSums() { if fileInfo.Name() == "" { continue @@ -361,7 +362,9 @@ func calcCopyInfo(b *Builder, cmdName string, cInfos *[]*copyInfo, origPath stri continue } - calcCopyInfo(b, cmdName, cInfos, fileInfo.Name(), destPath, allowRemote, allowDecompression) + // Note we set allowWildcards to false in case the name has + // a * in it + calcCopyInfo(b, cmdName, cInfos, fileInfo.Name(), destPath, allowRemote, allowDecompression, false) } return nil } diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 695e4cd6e..178f291a6 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -1113,10 +1113,10 @@ func (s *DockerSuite) TestBuildCopyWildcard(c *check.C) { "dir/nested_dir/nest_nest_file": "2 times nested", "dirt": "dirty", }) - defer ctx.Close() if err != nil { c.Fatal(err) } + defer ctx.Close() id1, err := buildImageFromContext(name, ctx, true) if err != nil { @@ -1155,6 +1155,31 @@ func (s *DockerSuite) TestBuildCopyWildcardNoFind(c *check.C) { } +func (s *DockerSuite) TestBuildCopyWildcardInName(c *check.C) { + name := "testcopywildcardinname" + defer deleteImages(name) + ctx, err := fakeContext(`FROM busybox + COPY *.txt /tmp/ + RUN [ "$(cat /tmp/\*.txt)" = 'hi there' ] + `, map[string]string{"*.txt": "hi there"}) + + if err != nil { + // Normally we would do c.Fatal(err) here but given that + // the odds of this failing are so rare, it must be because + // the OS we're running the client on doesn't support * in + // filenames (like windows). So, instead of failing the test + // just let it pass. Then we don't need to explicitly + // say which OSs this works on or not. + return + } + defer ctx.Close() + + _, err = buildImageFromContext(name, ctx, true) + if err != nil { + c.Fatalf("should have built: %q", err) + } +} + func (s *DockerSuite) TestBuildCopyWildcardCache(c *check.C) { name := "testcopywildcardcache" ctx, err := fakeContext(`FROM busybox diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 5d2a537e1..25eecf07a 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -663,7 +663,6 @@ func fakeContextAddDockerfile(ctx *FakeContext, dockerfile string) error { func fakeContext(dockerfile string, files map[string]string) (*FakeContext, error) { ctx, err := fakeContextWithFiles(files) if err != nil { - ctx.Close() return nil, err } if err := fakeContextAddDockerfile(ctx, dockerfile); err != nil { From af8efab7561484debffa3f6782eaea199fa22506 Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Tue, 10 Mar 2015 11:21:09 -0700 Subject: [PATCH 674/999] add initial docs for windows client testing Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) docs: crop and optimize images Cropped and optimized images. Signed-off-by: Sebastiaan van Stijn Docker-DCO-1.1-Signed-off-by: Jessie Frazelle (github: jfrazelle) Testing and updating the Windows material Signed-off-by: Mary Anthony checkpoint Signed-off-by: Mary Anthony Fitting the Windows specific material into the contributor guide Signed-off-by: Mary Anthony Entering James comments Signed-off-by: Mary Anthony --- docs/mkdocs.yml | 33 +-- docs/sources/project/images/git_bash.png | Bin 0 -> 39522 bytes docs/sources/project/images/include_gcc.png | Bin 0 -> 16446 bytes docs/sources/project/images/path_variable.png | Bin 0 -> 59864 bytes .../project/images/windows-env-vars.png | Bin 0 -> 120562 bytes docs/sources/project/images/windows-mingw.png | Bin 0 -> 230524 bytes docs/sources/project/set-up-git.md | 5 +- docs/sources/project/software-req-win.md | 258 ++++++++++++++++++ docs/sources/project/software-required.md | 5 +- docs/sources/project/test-and-docs.md | 40 +++ 10 files changed, 322 insertions(+), 19 deletions(-) create mode 100644 docs/sources/project/images/git_bash.png create mode 100644 docs/sources/project/images/include_gcc.png create mode 100644 docs/sources/project/images/path_variable.png create mode 100644 docs/sources/project/images/windows-env-vars.png create mode 100644 docs/sources/project/images/windows-mingw.png create mode 100644 docs/sources/project/software-req-win.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e425175f6..a67910b21 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -34,6 +34,7 @@ pages: - ['installation/ubuntulinux.md', 'Installation', 'Ubuntu'] - ['installation/mac.md', 'Installation', 'Mac OS X'] - ['installation/windows.md', 'Installation', 'Microsoft Windows'] +- ['installation/testing-windows-docker-client.md', 'Installation', 'Building and testing the Windows Docker client'] - ['installation/amazon.md', 'Installation', 'Amazon EC2'] - ['installation/archlinux.md', 'Installation', 'Arch Linux'] - ['installation/binaries.md', 'Installation', 'Binaries'] @@ -195,21 +196,21 @@ pages: - ['terms/image.md', '**HIDDEN**'] - - # Project: - ['project/index.md', '**HIDDEN**'] -- ['project/who-written-for.md', 'Contribute', 'README first'] -- ['project/software-required.md', 'Contribute', 'Get required software'] -- ['project/set-up-git.md', 'Contribute', 'Configure Git for contributing'] -- ['project/set-up-dev-env.md', 'Contribute', 'Work with a development container'] -- ['project/test-and-docs.md', 'Contribute', 'Run tests and test documentation'] -- ['project/make-a-contribution.md', 'Contribute', 'Understand contribution workflow'] -- ['project/find-an-issue.md', 'Contribute', 'Find an issue'] -- ['project/work-issue.md', 'Contribute', 'Work on an issue'] -- ['project/create-pr.md', 'Contribute', 'Create a pull request'] -- ['project/review-pr.md', 'Contribute', 'Participate in the PR review'] -- ['project/advanced-contributing.md', 'Contribute', 'Advanced contributing'] -- ['project/get-help.md', 'Contribute', 'Where to get help'] -- ['project/coding-style.md', 'Contribute', 'Coding style guide'] -- ['project/doc-style.md', 'Contribute', 'Documentation style guide'] +- ['project/who-written-for.md', 'Contributor', 'README first'] +- ['project/software-required.md', 'Contributor', 'Get required software for Linux or OS X'] +- ['project/software-req-win.md', 'Contributor', 'Get required software for Windows'] +- ['project/set-up-git.md', 'Contributor', 'Configure Git for contributing'] +- ['project/set-up-dev-env.md', 'Contributor', 'Work with a development container'] +- ['project/test-and-docs.md', 'Contributor', 'Run tests and test documentation'] +- ['project/make-a-contribution.md', 'Contributor', 'Understand contribution workflow'] +- ['project/find-an-issue.md', 'Contributor', 'Find an issue'] +- ['project/work-issue.md', 'Contributor', 'Work on an issue'] +- ['project/create-pr.md', 'Contributor', 'Create a pull request'] +- ['project/review-pr.md', 'Contributor', 'Participate in the PR review'] +- ['project/advanced-contributing.md', 'Contributor', 'Advanced contributing'] +- ['project/get-help.md', 'Contributor', 'Where to get help'] +- ['project/coding-style.md', 'Contributor', 'Coding style guide'] +- ['project/doc-style.md', 'Contributor', 'Documentation style guide'] + diff --git a/docs/sources/project/images/git_bash.png b/docs/sources/project/images/git_bash.png new file mode 100644 index 0000000000000000000000000000000000000000..153fd2fbb92c9b10f120eb38041ede9ce96f570a GIT binary patch literal 39522 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�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 literal 0 HcmV?d00001 diff --git a/docs/sources/project/images/include_gcc.png b/docs/sources/project/images/include_gcc.png new file mode 100644 index 0000000000000000000000000000000000000000..e48f50cdf99f2d77733ea50235feab3003c58a5e GIT binary patch literal 16446 zcmc&+<8vinw2jS)ZB2}ceZz^JiEZ2FBsZSeHYO9>wr%sq&daafm-ipMFWr4=*Ew}g z*RI}sueEkZD#}YDBj6)|fq@}QONl9ifq@f$J$u2yd|fTq_?f-scYL~U%7{h?OG(i>9u847jc4-QZlpO+Wu|$@m<>&4 zZ7z%@11dnh7W0N1t2+B@nW-*j-=kv?Oc*l5--~;=kH$^9FGbs^f!Gq5#w+eohCmWv zU2y)6tH+z#jo!tF_>)T~p2j8zA$AD?J`i!`?8q3D?V;nV=6`%{=aFF==L)61{c0{0 zWIK-aG?6OST(#if#WBsHp9~w}^q)3_FK+3q&A;p8K%AfJsgP!#mt17DPuIs<6LEI$ znW^DF$S`y0PiCI@`dk|IBMTwgg%iO*vV)3)Av2l`S@84jc|9k4rq74@C#lgO%jPWD z5~OJ|foJ;yz)86J8N`eQ_?qipYu;dZi7Wzjg3w*on$3WYd7`=*t)&#b5p(_boqr_bF zodPaleOqTzPG8&v8eIq+*ulEMXnZLJ71w}Rav%ZgS47fX5+(6PXuP+_s5 zW!nDT2SJmVV7{uFEbFgP+r?0K%Vj8Kg=mU!E;dlK9_^Vg%{&u-Resn_og~Xv=;QO!~5Z#6%%M}v2FSWviKnR?}1Si76u+4JXy?d$( zhmp^t^ep`mc{o2?2&Eu{uJVip(M1jgap%?EZijJrH3m?YV$mby!z4HVXf~j7Dlcad zqyzUSoQGX(rJJu4Er6P;BX{M0Zl15^a>fFV?cI;;{p}mDw40;)0qj8{%5>n-nZ|`| z0qf>*s=C0j%sB=fbe}5V+>9%#_a|x7OBan8SnIc3p51Z}_+vzLyaI zM)NktR+J4r%$nf!)fx11ah%OHS2=LayQ8r%Ixcuv>~X7I^9--jxl=+MG=6l{)%=A) zbiM&yCo~8WYwTkg=op!=uXdS7g~p&M29

{0NO1)Qesh-^#1Zg(2RWErK}|tEP;> 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

{{VD31C$dDSwVl{n2aBK7$F@ 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⁢~Guti;Ek` zINQHMt|NsN*nK(EVKu=sV-tr*k{TJQgWj1n4aW_t!O@Wb0J+ejAxi0+qL4C_yvZ=| z{x%fc_TI!p_l#ftNw6i?7iDnvcNG|(2}tceZP16Yczu)Om`qMu@B&yXP)bl~yGB$w zn5{+ofkRj*8n|*e3WEvx0_DUhLfPme2|B4_XlzH|0=PHW@??rLSccruTkTjEEZ5_;OpInzTqDOcq4(E$&*0%-`3=8CFQ?gwD9angX`dlhjL!;iC53o137vpC4#JU* zXkeDr;4s8o?~^p%PDh~e6e2mhNlGeRsCm`UfvV0>g*>E+r%MW46u%?L=QH=Ru(gTm zy(6!$!Lev!hi^T6cFa?U7E|vfDm4H~9N2Us{QJ_HWY+v+tYWXK8(dwu5daf^JH>qO z`Bbv)x!&%ZH)L-fB$a)cBD2*xsy|*rS@86^dRw97GNK6%cXaps?Y(g?f9Eq$^d)00 z;LoFBwp6b7v8jKxV28hrbc-8g?Y1_W0G4pGXKK}Xx+)xb(c;gOq0796c*?h~!9TSc zaN^!O6`L&O(y>t;c25xby2ecw9?R#u1;{LyZ^k)g?2|fk0+S|`%2F^Vn$iGH#!#u= z=nxy+amn(N$mQqqej0v+fC9|5 z0D^BI*)pG?1b!z4AQ+n&9)CJXE_3eXN4wI zwFarVeYhT9d@DZ@%5JtSfQ^VJ-x;0SPSCStdz?Cp&f13W-(&m~N;Kd-@WI@*-#(3> z`3MP>#7bgy;N^oKnbzq`keM1Rh%_vA;+P^pBPB*rQetLWF&UaOolZ&M`(bSR71E?H zC=|1bZ-D86Cdlv(@mpAACFL@7aUQ~A`HHRGdOezD`%mAH_PsSW-po_h_KcPb_K)Aw zeOv1>%!Tc!56y#DH<8n>pDDmt3T=8vkvIoJW1YBqO|?chCYJ+zPw1{0=UUD&p~)E| zU(0vkNV=?q$YYXg^AeL4E2ME&T38^#ikoN)(2*twB2z&t?vcz3j`tZMmH%IWd}vX+ zy)2O;yZrT_AS3}9$sYc6WI0aga_72 zKP#M@3$nP~HWaUy%;1(ls8@zAQ%5}<%QOIxFyFj91&c8$VE`N#0-o!O5W`>W+CGBN zs$<<8nv(qetH?FP*ZZ+!!bX2ABwLG|*owvH&;%4i5lcQ}LfED&NNzo^D~8gI_YQ&9 zjdjCV->M&dr@O3NG6-d{DckEXy^wo|DGrb?uxb50$>n{Q3!b?W^=$UYXtC9Vl3K$X z`MV^?;WJM2x~c;X;nZ0?gpgzBz=l$Xw+S29?qV3@4lOuz#qg@+SG;FhSWiqqvNDUK zS5sJaJp|T9WxqlKWX=Rm%6SZQgmEPI65%6pGb1&@*e46$S}n96K6<`7sy-JlHz_eQ zsHI7B+44M`JZ{W<)=$F+YT{Ei1plN4IGugecxgwCuwVE)Etc%^GJpHR+#yO0&=WSD zVY{CHsy+ruC(E}&^WkC_^|A-8aU zGX?(ywu@pIB>sq)k;{Cec`bXg)$bk#`FiL`d+F!900$TeMt%x%J#Yp>o)}638era< zKeqn)a1j|cjeA@F+&Tse#>?q0{)VqT6sO&`#NfU~@wm@St&jf1hY^Ps_0xWm8Wge= zQgMbCzO!+9OAVOJt+bNWQ04#;uO47c)$H?@`{t^sAHjAYAWo#z9RsVQyp?aNkB1rO+V3-YW zEQ!`x%<6d0$Zzs-rtUc2M>|2Oi{jmXNzg#OwrJM4`CXk&D2xmR0L~|Pg1N$eybE(S+!}iZZeinXVG+gijB&c@qcVHxFvAZrz?dyN;oifTWIa6j)67$I{2gd1hw<=rk z8U+{}1FvA3xZlU!d;4<1P~Wr?rDSmCq&hTjIdS~-J4jd{Jn;yAXh(r8AUX%8IEgO2 z7-59wGK4VaO^XImgfY1ehCOFIqZBz=(dn&0=-p7(x;=Q`erG*JP?=86esJgiaVs7YUOcsjQE_8gQFQ7t{v zY6NM2cb>MjVGe9cMG^+B8JqOghJxV_gSf0q0Gl_Q_~W(_n2@0g9IE=1u+Lt0<12VD zc-H>@Q>EyYVHk1$YC69aVV8ZGQMm*UixjhRwK#wD`vJRp?koHcpJ(h1r z;Cw;a%^_u2Z7Legp;q)jAO|&9lRILmPitNvBv1ZGc8J9lL9mL|PouqFK`Et+V7`0PZsla=*q4WqCJz&-55Df4J9@$ z0S8@;N0rUOD>^K0u)G02SqQbRS7D64SYZ@tWd_6H_+)OEu@DWv3U%f*vT>gv#Qj=v zI0EFDTmSGjS7n9cZ&UAT)*-aFuZZYH9tlbBq12;R&NQ-%V1$y?C*dqjZP{beMg%Pm zES15DM|(v|k#7BvYBtpVLm0|-BksTTw|IpHSMpYqlsbE(tKcUE$;T2>`t7d3Xbj>B zw;OThvu0m<1^cf4+!rbypYz}|6$ckHY8n>j+oeDhOa)y z0t>(OAOyAurWHz*dQFD|>gn5LDFOn{QC#j7VaBGE4C^sX)c(at))Vk|Q9r1raQwUh~17E&Z;u^p6QyM37?eEwo~$I-48@D+m?P9wPi`d$OYz_ zO8m7JVQ=$oXctFWHJMwHO|@|rjff`kv1GAaSEv^&!DC5`YG^7ib?FQDb{yoc%5nyI zXz`BB%xL^m!V#?Oq=Ch-_YvS%Fl}x;GbmwNzq}A)4cs1+(zrhxxh7p#4lP)cd~6Li zO#K}-EdQ^P9SEPXS*AnGX*q+f=Xr{$9YvCXec((#5E~;yHqk%d`znV8BlV7g-$;o2 zbBhd&56-u-yUDZOg?p;sclgZ??oNY+lk=-3E#tIiy%yU>o4u4>SbUlhy;RijB2;{6U`t0_ z^1Z8;-UD7!YyxJ<99xjDvVVfg&X*nGu7f=+6704(35&cVd`#fu@&T73LFuv1=q8OG z5+Osw1k-FGNBp+B6VA(^fg)w;l2<>v(Z)DCHzF_tepbcFuu#nN*VOoebKs)Xv~E~z z;zSHuZw)mNJEM~)2TiSp>fF%mJ_(?x?ws*!O{-o6YQ=kw(v7)WH7~~)$QUEADwdVm zK(SJn=4ZZK6q}((aNgoys9`zgK=j0v?iAYQgMP1PfC#FId9unXrH~^mS_l$E8ye$< z6ng{-o7{Izv|8ulBbzQsAvkDBDTs@tQd+J*9G}2U68S5Uy<73brl72OzP=+bDuH>i zh$F(Rzf_g~KKBw)%$w#A%6R>zO1)8*@2*ogu@B`k$liw(3vKzhV+DEk3nzVNn-SI* zLV^evG_kVhcjq3H6fbN!)z+NP=NBj%9;6*i;Ma;Z^TO2ENW$$n7Z@IBY_GPEKk94? z2#sALX)nOt4Vj8#-GhT%mY0x#0F=3{lQ|B~Y^I9n41{0orLLiQ)Ula3#?f0D)=~m} zRF;g?{D_x6zJGQSB9hMKUk7W4A1<|6vnNgj7eYp>26wMIxnU;g;D~wRb~7xBC`F?3 zeR7a=Mu{fJ2SpsA3`7PM`3}sB5-v4U1iJ?cwe#4)Yx=uvtT+a0GMgfG>F9xpM_)9_ z0SzzLYCLBW1Ag7CLopYzDRc>i!%b}%Fe&gP9axV^orOr_?QCSl{MNFL*@;9N(!+eGz6FTwc zK)@xjbflQ7G7h-17mMr9M6GZZN?@6gbx~nAT!FwFIR(Uvk>^)zKAU!FpzoJG1e1G4yA9nV=ha>Lpeq~okuMFTp3K9DuAP!R^h)l5b4e>1l>5Qc`WF) z&>D>5)1GpbHG_mxjg(Hv$-QY znEgkfp82PhL-Vrc45l0!a@k zLEFunWu8C+DP{PyW-SxLd13YNWX20M*~EP8Br(J0$Ha#v9fLf6C?P}qTSXrdE8@HV z(mz}#mr;J_XImbv*7OA5L5R5JP~vtg3V@RMw@0p}qd8r>3SrNcez?EisAKV_Uf9$h zb>SU_U~OE8j#t(?6LH3P%kyoSSAZ-YZs_V+Xz$CFx#8|*V3?qQ$guw$SHlf2IIru7 z1pbG<(j6fI$#G=*37O}30yTQcAR>%M@EKH3@BL)U*as^ZwTO1>dRDi9(UJ=UASiCk3OKfA9UtfzT=he63CRp!C%9YD*U*=r1jUc1 zyMeJ{mr*O>B{&;=%~0kw%L5Tb1L0Ks!#7>TlF-x=NT0u)|789-fgTPfmZsBZMRd{i zN6nia-okg=UHyxvXLD?PaXJ-Wd?`L1n`LjCj&S;*AAx>{19G#@T)YLzcgluZsOD75 zQ$_8HhY!trI$UD)-sF0=~7=VzgG@r5{4c5D)5r52Oxa_ z#5=D~=vDWn5Z(uW)w?}ecRuD~8o$^|_X}rmVoSo`0-W|o8~^(TbNk~{cDgUE0K|ha zG~7nqC!_n?+L~y2AKZby4s=g)`N4u~vxCuVmdf7)Og-xUkDGl% zWab!v#a*(kTti5~%V{8$2m#DK5(tDe+5~ zFo^FZamFFOH0!d}FAnS_Q zlN;Gh9z(4OSO_*GXjaW4{|V4hvC(D}5JNMZK``?5r4muX<8CYgg;v{s7g6TxX<&)W zkvM#Y8Cv|k6w9;yu}qrSya%ZTHPNf{lPZ|c;@QSv0@}jcizlP7o<@G>q1x;L5-P?xJ2IIyM^hbMoCc(lwoeQ&L zU~=b@t~Y~yyw>xr=GhSiKv+Pz@n`?~-}Us3ggwb>uo=lXNh4Y)Jr3OL@C7u0dgtU6 zRKZOGsQZCE|LF;fJ%py1t~oF^21rF?TF-6%%)OKk?`VdZ|MZr9au%ONq~>xE$c(;r zLRI1?z}JfwvzihWq3#kz^oy;4>bLj1v#8?7fZ_e=*2)v_mWXXy>+%px38B~RaQgEc zUfLg9taHxlFqwQAefE`!kK`!3bF+Zrr~DDdgtX%zvp4SxGFoo#wenW~-=TH;%;vAC z9xiyXQ;6ty1BT0l@T2o)!kf3791Z4OTJQ+|9b5FM7g0<@3602#9Jk#bqoIgyM-h1J zZ(6?ix)$lI)!>il^;XPOUSHC&Snvq4{a-3~xRLJr0Z)SZ!4%asYYJWUi-{hJY6wp1 zRT3KY&@>)+>fWDd^sk$PP=}CvnS&4?6iY$s|FTK5NEz8lyP~UpXd65dc3j`T3fx~U zfX7Zs6o&nU;un9ENNcwRwvZ{V78?$w9%9{WO~;>!U0<@n;jXz0d-@5AxcUf#GIbaB zLa;O2yiihMzOr}g2*R=>&Et)&8*felYMBHAGsSW#r|Yvt3ZALDm+1jVPHM4Uv*OB$LtQs! zuM9Czt>)qWl2IU&c4V)#JrF6@vlq9nBpD+Y_-7AvX7$HbQ-@H;Jz0OHihs(vxaIrP zWrb47vVHV9fxp4xj|r`29NNF_MYFv~Gfg-zTq;Tu1pRe+96J_3J|p82`#+~a(dIl5 z0p^-3;=06~D`n;X%00JO-U?xOqvYONgaA%~KoqnZp(61eEc0YLWh5VoQiG=#s1v?M z%5#<_Vr~Wq4D`$*g{#YJ3U~sQ-ZQTjNs}FUO|I>LL{`cn_gXDSze<5?RCsGXY@5^Y zQPPlfVn^Y|-|Ud<)7gC)D%wnqxp8A%HCRY@G0wXGPNG2k5*zUOg?7Q#`zcQ`E@M_B z?#|YU?g;>KZ#RnOtbEuvgI<;iIG7Q@3dZ*=-{7uqcj~)Xl~#rZU<60)f8Wden9*eDGwGm3y*yLjGvUe9GKbRsf0u)$)2 zp1N0trH#U@M=#fCH{K=wK`*9gB-UtFqKT-`V3NXnY?Oe6k*&TfZf8frSr*9WEk6r$ zcwL6MyhSSEaBqUlkoy&Vi=v2tT_7v18-h*k_|L^u8b%bazIOwMqjbE1dGv#2!m@?T zmo_w;s;vp&R>=M`loVH&>g}l+4&LCI{;A8KS!4jqAadM9ayw+!J=3pmLmhP~22^~5n=>`J z!|S(RwSap3!K5Q@`vMSY1Pd$}ZgJHkd*s4Ep%0}tXTD4P7tlP$HtRi|k|SS>%^K2o zjdUnu6!{Mk2TpB2`GmvifrnWRI(=9Cv1EC(dr^=0_g)s=-oiOJ@aw{UIt(EJq;`YI%8vSzS9)#i-=?yl0W^&{xtCDe*kq0Jz-pdV5TVkDF6dSkgw8Dgf0LWFcuwDo5*H8KU-pHxw%k!0wwWjaOB1 zu@5}i>_0&@5N-XhszJO%l?E@(>3&_A;mlD7e*@D`;DJ+w=j89ChH_XNRkJuJyVxkTKK3SxP=?AD#q>e=h}tQ5>w; zGPNI3pyp>rk_^`VJfVfP3KP3xrbH#fP>zCt8r2;nYw7g4BHqx+F|_%hAx9Q#q&}3H zO~*5o7pAPr?%cgPgQS%XidQ@7%;S_!qmRQ`{2O07yN4Z?_{MPc=!Va~*8Q1+d}0Gt zVhr-Td~{*7jDV-bBI*^NMkFIyHh`LLSG9yy+^tIdmWL5lX;DQ-QDVFOWVI6DO-)dm zA`Qw4$HH0PSonbIPeFfL7!5V{Oy$?=^`Ld#g4{b?$&F;I`iR5>WRX^T6XnF(7!{}Zuc17~HmJ;$RoA9g$QdUybaUm8>H4vVqI|^1V zg#{N%lnO_oKh2_wlQEVD!cv#;D_<^PuhsA1rgRNUEiq7H$4}_H3l)m3s#I|LZh~yT z<{=P#jIYAEJ&7m3%PvpAc z_?RuKMk?vN0WOACBcGlt{SiiuRFfulJyx(kazh{@0s0%KWjZ>If$549YC zZfI+K9PtZ5yGR_QgoD^YIt0>(sl1xD3Jj+pnZD!*nbttmX}#J-ElDggdCrH;dKpTN zHchnTcfhqn9rMM+1Qo?;1_Y(yRuv>o3XR+}N|W$(VsLH6RwqR;5O)GvR9k{jl{mXH z`7Ssohga<4DF`k^%*pXCAWs+X@iI#MMI3!s7OPV*pY%O%*9@!?e<4-{h-?8kCG{W4VB$4}O`^4<@PTa}q0!_0juwFfUIh^hxmEK&% zIrQY`MgDMuaK73O)ik0oL2l!N`ZT})o><|&;ELj@Jlg-Ix(6N9Mx9<)Mfok`{QB42 z#<-`H?O4>;;{*5q#NJtObo+J~K=N(VN{~G}sg#wia-hqba z9YOzx&FDeg8)2b$a_#gT2_iv62+jqVgd+0I{EqzQjD~;stmk?*Q2d z{huwrBmGI^F~PFKk3tXyN4_KN!Ec4XVJ2^oy|VxR)Bjh~E0!8AU6R=OI{DxEKmKlW z7I?f3_ZrojZ}+Tl)pE7tprNh(Ie*$o@STurL{em2T%>}0d-r}431E{J{pnIw9%v9Tk(>xVcF%02G85CZ=1V?qfFQ<%iwkv2br``z|GM(McqesV#AjO@5n?oZ+$1MepdHOaJ$ zF70q)AR{6om=YBH2~2(6}HT@6^B zjt-r>LD}KmhC)%&PUoOT>)emj5g|6KcZp&3%ueuJkPvl`+Xxq;!bD?NH?hP-{^5g= zB!;^o2DH-taKT2BD7JT@@ zL9m>^EJ4`2O@jXv7)wGmhk(D95~^54`_h=ar~DV3y0!bwt7_E)LW@5`2?l6ixdI^c zS;0}C5w?vUY4hRDcYPe%RpoU)*MlUMU=X@cgkb->(pOHK9WG*L4VuLyIb2}ZGf0RT zSTKugc&P{aAt5VdIu0~Bb&@1U{X-_m6JFz2Kh%$&W0+DfsYx1~#dHYEVJ$6uciChh z3Vc*Ed5(GLK*ph|U*MvH6}D;2L|N+Nyq_}skz)A~v2KbRiv^+aNz-AwKIa27ipOcFk^q4+11P*qawI)-QiM$VrhtQ+koNcpxVmQFi{1Qwg{Rktp(1y_t|0|qJE z03J#LfJX~Tt3P0}U6Cw6hoQ80l&o^;Ox6l0!AJ<2Q2;X+t01!$N-dTCR(wsH*!CC~QH z0@}TLjBb561ij| zmMW-(6ZjAfJ&${*L%3M{uLO`?g2>o@e}?t@ZsN7o`+EQF&km{yZTK$1wQ}v;Iud>_ zb8Nt8mTEipsI!g#|6=Q#qAOjxc00E1WW}~^+qT`YZQHilv2EM7la4z%+24PAE^CZc zOK**u4<^O~F7F3xe`Hju0GW5t%d+giQdeqNht>U~T&FdkK`c8+FJe`0EJQF8#01 zAG>?3TS0owLfJb{IQV1K`Rab=JX{{X_`MxbtUE=rjOl!I{oG`Qr%5 z}Dful9&RH+)FaIX1EV~*A7zK<)XozrA5lzboi8SN; z{=qIZJJ%}TVF5O$8wF_2BdMa;6SAD)Dzsu(hc_WkmLO&|uaa1&@F;g64icfFD3Xb$ zs!TIifk+;}t2aU_(B3iCP{-M^GOI4Le@JU4;ZYK62n_<9wB2yHf z!IZob%(TwX0VXoGm*fGE>O|(@YeOPk(<~H=js1-6*)syt)FBx_Nj_WZZMl3BM z)+`PZC=?cQM!r_VE;KJ&U?^v0s2`V;6h@ZNqgo~=qC!gjnJRwS&GgImG3r5S>~{S? zgoQtNr~lCgXEZ!O?jVpXXJT)~%@44&fqt_=UQf3SB^(MryeGJ=u)Yk7-@^yV-w4s~ zdp$o)4VFc4QyfY9J79kN8L6&iDTU?I`MnXVF?gDw3goKs`StAe?_Q7`0 zvm+&QY#g3b85NPCfrR4&Aos!{&Eg%vB?54EfE;j(=SG!>QIh#mW7SP6QB;6 z1C7KbhJ}~HN;L-Pl=`HR{nO#1xo&Cor>l89WB%Six}wX2y~h1BRzswlLKYrXs z0t}CQ&nAJbEfz3lcD=9>mJ!&4K8q(3d`T$@@(`4m)%1YmR}~9PWvwCg1jTayrdK*P zI_!|z>rTTJGB_@p^iV0f6b5%CpR3O{R;?Dhs1<_hp=)Q(YMSl2{L5c!TbeO9wpj(T zodM~+=Ty=B~efy<>*QOj^`=7O2wNcOV&9SW2s+=6c5n?Vt1woEc&K>v#^Y&~?S^w37{yj%KL zMlIuVDRtv8PH{KJe5#&&uk7Vz$)hIm`^)!FzC@d&vAb6x!F`lT+~j5YLpKcwrKPE2i|6M&Pnnq%?4Z= zlJ}4Jqo47gFWNrwKw?cGIUwn9LJmQOPFwQ%&J<(i>sye&Da+&1PnJF++m63C@5Y7R zm(ADqIf`f%Q@@WCLvL1_rc!?m_d>6N(EAvdZ}38O74vBWcQ|2x*%ne}}ESSmsvV*MUYZ-fvc zru8G{$CQt<4``h^u;}RMFrvsHLR%@MDi>_3p;Auos?IjSY-?}tX2vuz`_dJh9Qf5p zID9+7<&Fjp_6MF5iI$=m=8iAA+~jBhfWB7~HC4j_uDNyA*#i%4Bxts2g*w#z%KHPy zf9PL?@DFH3W-sv;_-`c$_~Cnkh10kH0sUl-AG$~Ds5}8N^s_=G`5|=ZBpaW4|G#Yt z6e7v%^e3l{>hXkkUjGq7(w~w$`<>Kn=YY)!rVn0mYqWzT<%0Y({?hbrQm?Pqv;XM8 zfQY{kjfhgVI)pV$B-``Jc0NpYcw?I(O<~lZc3eiub5t@~?h9GN!l~Qo|Gtq5sK%5H zgf#O6jHo8W&Hy1yZukPB1v>(5`(^t^rAB&2D+A3A$^S7~L4g`IWtzp-!Fn`kq-lID zDzNdrkpB_aE?dx@QutDZh_g-Hwar}A9lrnVFn~mjbj=l>{rMmVP$}r2pPQPTadi0K z3L5N(7Q=wK`YQjB+x%ie1(FDgN!W3w^M+aeR^pPW;C3cO^f6FHPvKOwzqh2+k~qvY zW0W)z+V?YcIn3x^G5=9(vHUS4#fCNiwYkkUhc6yFou1e-%Vh6oRc9(M(9&KN!L;bR zJ9iWET<`rS?Ki=SfW@MF1`_!9Ej(1I{3ZTA^F_78wSOYY9fpu4@#J_T$&UO7f*r9i zg}W2pcVe8tAwdEKR;T*-jS4iP?}t?ytj73s#mceP##|FF8Uc0U$w6$yco#BwBFI=I z0N!R##$@OWN&-)@Q1tgT;&f(ixIBAo;Lc`P$Yba+iraRy&n2$P`l|bH!!?=BNTA56b2izao&OGQvBg<`2e6q{TH$O_e6Z<=)zo z0P=fthESexE`dQJb`yiP6Aih=;-OkF1yi`_C+NvhS-tqatwDeO6q&C;j{)g>qq zI`OTf8cP@>^I4dS921BG)nZgGZee7uCI#+y|2umLg84s>ZqoG`IpY@w{D`oc5AL(9 zCA9H{@s^K8-&+hbVzEt_o$UnC!_+MY(m@DUiM`=`;g9B$L$f^9!@JCQk zgTZfvt#?DxmhI>OT>)~L>@8Gtzm2f)%T7mqO)Og+m7#>cI7qMa(6H5pl1B+G$C73B z&SD&y)8~PWW|ABXZv4r*m;LTml&SyFIht6#dUa_z!J%`3qO@`U|u7|4i{R3A3pECaqg0sbq60F~|O89%$Zmroq&L5|DZ}Bhkl;xR! z`=wi<=pQ}@By0ng$1o;G=&z1-6xA+!V_6AIF(_I@g)$g$j$#34H;B|vM{Flvg#Fc; zdCVRMI^Ylp26KB{Ef>oew%fnL%R?+%Jbt*+&mH}L$3cphuA=w9tZe?d{LBzNz)q zwe1E3Bql&>c{=J^%DHqh9n+-{PC=YMj>Xlsp`Fyl941m+wP>i`j?}a6_^U$QSFCOa zZ-3qh$Twpi<2sdgw7*j?ycZ>~az4F3` zk`ob2!tg6%rMIgHCZW^Ll|~|Aso(A&T#rhia00H!O|QC|3DEQMz?hix9UOmYX%JqN zMY%7PV`Kk4gZ^oXdnrMEY+?&lP(Kz?v%7V1Nb3j+{R>EpqXSKwx`(Yu9%mo2uda%rU{M5j3L=kkFerDnQ1B zBMBpA^jE?R(Y2NX6z%h5W9gxp`(}qPr@1glppqb{CUOf?B|2BcYjz6D^{X`_GbdX! zz*eYyG5nokx1d>37?Jy%m21C)FTR%}>>twS)S^iA zYKe=RUV8{ftgt7N{5KD&R(<#xMe;bj+XoTmlrD5?J`j@?4Lddu;=}nsVN*Wfwj;vf z%L*+Ul)T=12^uuvx8&79NJ~I&JqvxvgGE%WFoCg+55eajS5z$<6(KT5p`w{T`NY7} zH{$>aOBNbLAj)(J#S}OSV~Lbhcn8O&{goXV@3wKg%5w1XvpYsIw{~#tCg)&Ml`^)q zznTwHMS?*rHMA2eY6okH5oh)4P~8&XIBw*SfUkjQ!?1cOy3hg{Y8p%PF!|ZAzD_BW zCB8e#jXxaVIGm1$aGv>pq!HDgy5Xb|h$ft`f%ySe&9?dBDmtSUu5wK*9PinQC+RAfc#?$7Tn> zK_=Q}hz)mzT?Rf(Lvsq~jSw8DE80)}&8!2t5KKrn3KFlihBlT$Q426|<1dAG=e}l2 zrZz522E$1HgJm&|cK1O+$}pmC^bHF%icyHvL^NPX1(}jbpWAN@3J(9yCV{>tZ zI@cRj>MGk{zwai?6w_Y6k}n&0%{Co6S|%m@U_+sk z%+Y@Ue+R!>H(#ixnh(cv((a%U4Bi1@?81LOU$|5coSq=%+_(N-uv^Txly4p=Nm6w> zRIhYbjF&inop+*g;QRCknncn8i$;bsrDGy3bWep^E>W!d@lwsHX_d_a7{DVHWScKT z23js=&q$Hf$eO%G3!a-|nT6h89>vhxSk(vXI%XfMG_+?oUbXWo-00cs{8rpnByC;A z^H`rBZcktK^#3~~fEa-U;=(%N$^6C77>k}-I{umefh1_?0QS0D`tsbzkN;DD0sjAq zy#~nc82@cMi26_Lb-Qt&{h!$DFOZNN(HQRv49z?W^FPG(S_XMGCRN5D_4DpZ5ul^; zIip2;qwy2PApuamhm++Gq0STLO5S+7p9IBoC4IM*--vy&!lxxG4cXM z;4XVNCr>L2+2}M=U|#G%n(Ius>jOsL?msco1#~UbRJ3TEr1R3R)ogld`fl>%+T=?j zfQl1oI@3z*S1}Wm66FdRLSI72IRC#q!!q*2<>+YGv16K%3W~|s?>x4?%b!aV2&l#G z@bC}`1qDP}T3IE+Zz+l;^8WJnRvbSn(NE>uR)y+!_SvK0!eltoFK+J6)3DdHPb7mV z#vCKK@@1<Er%rJUlEHr9M?_m&Tg(5^gumO^=jZ1&bHl%1BiZ#E&XDKL zji>RBBhG>%m`&-@Xrd$HLwo?W)M-KUyNJjN#boHB3f~Xt0Rz*P&0-Q1Qa&qUfhLSc zxfP^t3Q(dR6MWCD;8CwX?Ji{P%9iAx)ArN;Wl>%=`l5K2LaJSTM*Vov{(?w(fSqo2 z!uW2nrthA@32KA}U3Ej7eJ8h4jfy^IN(xB__e#kfh=+nW1dm{jiM@NSKye&x649{z z{zH<=E!P{k>^XzX?%Ly-V1$mnvCtu_BR@+%Q&$G&0c^eyK1%-iU;&|pO_)X0i4 z8UiMiqI(v)48)245JN;a7hW;ht(Ya`@P6z3{V-18Zw%n!P&G)(KVmW(EPOflD9D&@ z6+My1hkT=?tVlebd+RH^Pli|=c|I7CB;nlz!ATecvLZ|$93@NuwwS;`M8#C*+9V`c zF%wEL3$kNSoTN| z_P==zXM()A>qvM!uKd${(Y6HKxa%zVz0ujQgCaxy{a6if<`7Qgz@9<>8@#y!V{;je zMvMwn-1T%v3~KS_B4gf$X9hLk$CYi&lxdx0LUTo+b4Y`2QLx+Psc-(GuVNmrrud?qUw=8?* z#NH|mo;HZ(+wg|Ge-R>!lV$m0eYcNv#Px2sI{@xaBViG&K;#tUR5~^HCk`}(nX3RV zO}9wSi$Jw)zM(mUu>6WmADS&!q?hmX5PqSF!r47^#4X;iAaa8KfjX!kn-*8ti&ao- zND;R?<(HxcC@#0mPjid_jb0<#8IfXP!-T*l?bZYQVVZviXAtGp9YKF4w`qvkQZ7?_7D9NA6=l50Y2!fZLke4?l0-^DZxnUAS%T6LSmm;;F`=mElNNnubYqbSpcp4DAD(X`Dnd?(;TUMY^-n>oD zfmt>QBfcjK$S=_t5dSGHZ+ALnFdP82*=j@j(Vs^4`h#RoyzC$JzU8%t!%Z<_^YYvR(T2}iAWf5V)9>|AH8d$gl)OL|$+HEj z>=E|Y8gkMXiddSHLG%cjgCYycWQB`KOa8_y(76+VxumWY>?Y?qCU5ZgJX83-Hq)Y3 zZ+}Ca3-zX2&Q%s@HgUfien44Nwgjqfxyoo_MroSI?XTUtSRC5}(T)p)p*CxYXGpW6 zo)ZLnXvu&cm;xkdfEoU776tD^Wp~%Z6lJ{%NwhftYn>%&1Go{<(L;Z{H>0Ps`Mam5 zp>oBdKAFr~opz<-5p8b{#lRTRc=ngS{%qkKqkQo3l(c*LP4I=MvLd6zRAV$_dn;Y= z&010tFH_E=`KrpP(^3}mJdl_Cc451!o;IltmYsW0uU6u5_pL=5M?X9!z^>S z`gUZZtmP{U@mM6-JPY;Uq-DOVAzZ7Q^V7nRT~)J;U;8AY@iy>ruEy}x4`K`-%?f3) zvHL?jUs0B*Z;S?3IhKOs%O9+<~ z;$dLnWPLSO;!|3CA?Yl{DsuSs#PoO@RYG=#q6n>+DVuPibt}-WN`CIE5oC1A@Y0*# zf86jKRMqTX+ta-0y@&zYBH0uNJKBph%+sM-nJH|x>FX;@wj2wgY+vh2lZqK9JME6e z2l#|kg@j&C?JwWi2y}XgaInEur=LR~G0Z!U*-p8)yqCN^cP>prAMbdMjHEDPG0Z}P zKN-@9!IJiO-lTryGOq&&=0jMrbN$o1trj(=tW=%9ql}KihFNV$d^J*G%yEpM{Gz?x zxU|Gfg^X_@+06;uJC2J?sl8rTmG+6#JSE0{>2v|RE95bES-bSHw1R|B?u6-_v4Q4>#3$nX}WgF!o$+-CPeO;7e$qWuViS<6DM)n3?IBwNb}MU$+)i zk8EnHPWHjlMUICf?kqy|wOEmS_hj$Rf${WU;zgzX=alGQwJY3Q2Deg%W(45No})d| z*pmRW2MBmvD!Hto>(xlA;4+Xu&q}Mif{Ku$ZsRpP3fp|6p7c+2!W3UsNai62M2ZS5 zt^dFPCh^Sd?C4C^5G7TY+Kt3=LJ-4nfjWARE$!t1Q%XvhLE=*0$3TI}50}ce_>{kw%pf*<9#7NF%)Q}4UOboAE)@Ad^o zp>X5xASMm_lQ91*Nc#I`igqv}2(ZYR~^WNv8fZwUQi`aHpnZWdBWCnK{5a@RvU-Op!_2IZfT zJD5~&YJj^sNDb5ovm@(zmt$9smR-nRBHe^E2Uu>mqrP!U4Dj1KA>`>k{!T!`a8su1 z6*t6cc3pkbrb+X-PV6}hz~J~W4#Y6RV(F4{YjFewf}M8QewSA00!#eK;{dJs$#L%; zpLNR?!Qjg622)I!r02qkMdE=q^(}VT-*Xi^f-P4!Zj{_!Xe~eOEu+o9$_&3<-I>dv_Pr7D7N+}u?9Aq(+6mW7QcV*1}I#w_m?r>!$@773lp=_OWm=C z8A8};+(^*vweTX5@_g%5L9uv5UyY!^8U$LE^aN8oNynGbUrE(Xls^>6)9f2Xx~+U| z-geY%1UA9&T-qAiIQfU**fB=Ca;VK5S|X!fv|T~m`Idv2Hd=0AZ|1!r=2>)oXgC(E zTA9RXAt@2gH!FQFgzgA8Yn(5}K7oC7@9QY#cNZ>R5>L zwE~dMRFB)66VtYgcy!Uiv033zacY^17T_dbeVAUkRsHW;nrp|35cKIwgLUnf!=O}o z!%tmHjjn93_@@Sqzn5}X`sQ$dY*Aou4`XxGpN;#oi8YKzYl_(Z-fh9a#k=j>d-HKa z?2M>nUvI&_yh8dk7EAY=cH>>J!l*&2cvRH}{{Qn93iW_APHKD25gilLE~h# zP#jTc_?6`_P+GI5Pe0a5m1CMfk-%h9Hc<693$yLomWKBXh8lZ(PfB8=n*|A_?tFHP zw(yNpYsnfV6w=O6#&Ruu6BVI9-485-?eG1?lVw|P3QFOMZ&-U0#(~lpq4)EMMR0_n z>VD$akWM#r>;~$URy|6LepZNsykUG%v6DYD6QaihpwaWK$UsqzjBdGEI6nsbYkRk^ zHE-8zyFmphYmeCwRI6w{fACHcbFF6xy)aR7!YiWBHqu|g7c?W)bVb*q@Hl6>4tA|l z4x%SwE1WaWo33Y;*wzJa8z-$NHx8tQF1s&cB*BLqX><`dDVAW6Pna!xD<|K~jdzp_ zQ{KBBlz?Xm5qsy7(*wi@oo-m{(YZe=JenugssE2bR&KZxqj?Gn1d4}=lbI*_l3vaI z7|P+@vD>WC?5OhHbZ%2V<0tYA(sg->n9U~`HT=Scq7ks98(GTLUt+hWbFd9114GvCV*_i6TmR$|Lb(y{kv%X^$mskmBkyK z`NVmIpeS$T$UGGT7iA#!ty_KzCeMH%k#1BQq{Db6>*IdugJAHbihgc%5;X5swZHV9 z(EIxUjEC_CNJjo}CJH=<@!zDm0{vh5CHSR-k+AlT!8EfTCe9Y^t@+ZjKcShf{mL<^ ztC20V@Mnt-Q|dm-#+l?_>4B2Fw?aLRAUm38Q3#=|WuzYml#|>M{v!ETkmV~iyB0i2 z@zG-Y`*TjC{^Jqgo8_h4k`;RqYgi69-ZZg!;RF6(ZiaVrqxm?jP+Z#F%7~)U*4M=Y z6a>_C1XP|nS3=o`D0!)1^ls|#jwd=rE!s}0XRW4H^QER7fmRg+k-M*ek9O7TSh>9s4cv;0lvzGN5Vbpw4UM0HLA>_!JXpk^T=0xBdd zJjp}m`St1X`u1-0_}qQA+zkR({}`T!Ea&`&hl}g*tOT&)&`8_(K=u^jk^j!YOwliH z3%97Yxps6P$x43`kmm>q1~7AXQn)gHx_&Ag!G&E!@=QimK^`eLEhnB0izJLk5T{OF zS+Mq)xZCLnTRwi7#wvRzLrQ>F!U9EI%&22YnL%wXTSRw?F9oqw&H7?uEqTJ7&ekar| zP`ns*R`IFF=@6$`uSX^+!K9fa1LP+fMAILtP)iTkeknOFlK@3y zd}M$}@CA-CQcgJl>TLe#%QF4hi2CT}6W>@bD~y7!c^2OH*RG5w(!OtMO7{faRjb%{ zKmfs$XlEvr#=}a~8tNT5pI0qdY~3ePFMS8>C49`B{oJ-ipYUhxsm(H0IPd7T!Rn)f z@~m=$%Zvrjw>qM4Hb0Uf{`p#$1=|Vjdg$b*fnIcHbejqe=qp#z%^-o{471Jz;6s5u z#MuR&M1ql=Tzvfw02}q0g|f-FxUg_mW5r*AKFq|KHhb9A+ABQzqHd&@&K%!?Weg!Z zsP8iZcwXLabK5b4g$Hf?VeG9ocet&x6z6;>V`;V3TC81@5kj0?xPKhpHePWNrB1YxJUP z%If;Id&$B7KK6fYE8f|o!rBg4;_?{>wdjI47_K6%&HD;2)biIq3otbU;oUjpk=4_S z1$vCR!ibVDn|ZgqBYv@yAg)`2H+z3dNB3+JoVNM~Af&F2ULka19E<90`9eY}p<2@` zE7^68&hnfZ?RKe+?nJH4Xo$`fN<96pq@CG>9pVtoAK-N9yEBh9xNhNjeUTVwHhJaZ@D7y8Z?za%8)83-$|+`$X^H=kLjFfV}=NF&OFO zo_G3G0M*fm#3<$3zueDw_1zNU*!lj|wYJ^rGB!9LqkMm#&Cz_Z{VNMyZlnTRe(2Bu1zQ`M9Q`NQx3j(fGuz2Wo5cdnBy^2f zcMSC?U;k|wUnoecRm3#rfT`*6Z(@nq#?&8k+=lV#c)FK}kY&EZ<+4yPsF5BhIxfz< z`M5Rw+rBaR)Q8b0p9iRFpsrsEBB(QKeFi@he;>1)Pcsa^PIfd~@^JpF&lbw@@Ki|a#)a}JT8F}>o=jc_F%F_=0lyE93{=6G^}&D^#X`Ak7A zHisy5w8xvVT@OAJi&2~F7fF@p?&7((4F3Ib!#Yzg9x96{hH&8@vr8+CYf3r1 z%G!SvvKyqS7#^1gUi(H~Ua~HtwAqgTsAk=L zfDH!XMSN0MfYd6EtDf_s_5;RmG)H{eA(wr3JY0B`80m5j-=#U*!YFR6d|_atU%7$x zm)y?!VjF7>gg_PDnp~RWaiWoesO{A0N>^Zax0v23mJQ<829n~anz8UZFQ(2SNQ3rw zpi7m5dl+w|2Mg4i`-?NI#RpYOadHp&TKR0S?U+ThMGMj{Z98bnA5qm=WQ)m7Zm^#_ z6R-W9v*24ne9G4i@C@NW$Zg90j8_q~D*nr>oYvI=Ri+`A&>iDd|Up4%x^q;TSKe5=WxnyM%V~Z z3Dteo*5OKt*V!~LHP#YC#D)QDnlbQZ`NPMvp)kn;ez4vUw7*{>w(aQ(mRT-s-t{1b zKalBFbVB|!LdFsQCTK^L9>R|W{&7hA?FvnVykI{Z>TFYR4=QJzO_z(n95-9N=$)U=1o==W`HbUsJ%(Ulvp)fSgI zTZ4!VgU>D};bu;3U|nEi!f0gAKg)c=#9^fN=hEu-N=whM9b=2Q*nVSCdN4L2p;y#mAVD)nf$SV4Jf~uhy%g)aP; zAyq@H9lEGo5#J%L;K<6f!mX#z#T_Gmk<0_gtnZC35uU=B$I*i=+R)G5ePSvpYy<~+ z+RskU^zlFUk!XyC_gwc=u?G0yb&W#{?QGiLZ%_z=v;*`Zm?^=-85+w(X28|~1?5|X z0Y{Ea-e&uu9)iD`O$)c8Uf6$Q`@UW{9q7H#f7zjirKi4}WT)ZaWC)eAKk2+rdY^oF zimxQ&A6zWjsIipH+^dh%KN?krS(X@32w{h(ROCYQ590#W?1O?nSZE{=1T+z<1T^)r zN^i|UWjKM}gxtd9>V5S3ucSIIA2v2bR-X#t6J=4}7Jd#hCl&p}er?jpe*MsoTt&?* z`O=Cet5iX)9rDP>Ps3EQLZe=Lm+-E}*HeU!X`wAKkT)G*XvmjM_Mv($m6 zYFV&Tw{qla4@>b^vazCz6_#Cx4udMNs02h2&g6IWISoDM`uY2Q4Y31-j{{jbRc^(7 zJ>Tuj0PT4MAi~6hjvV+@#NMyZ?`lu`vwt0Al#mQQ`2InIixSzY`x!z9{K|&zV8(Oo zcm(wNyTG6O(S;=9jV287plGoHsWX%37j@p74&HEsN2nhOs>888)xCyX#t_7|O6%{4 zq$mi6rMOHo#xavjm|6Yl&fJGq!|d(E^DvK&SqKhKq_(MMtp=MV%09l(3dt)TO$9!Z zv40ru;E20f3kc0Vjl;`$`9UcX=q?nO&s1nlde-#t$Y1F&1MZdE>yY;AG7w%zPNu&9Z(6 zlesfeT*+{)_&EA()Xn8fMouvglP_+CC@DLO1nE0LnPznHW4aB0)&({m2T69IK0#Sd zXONg{J*`9r&Sbs06{*oaY`p&b8VRYN9L848it!~`Wix%8IP|n9k=EMAZaGwH z?ZiJth5ZWT#;E^h!kth`*%hx|{8R2)Yz|HB`}kIZIn-;i(vHn4nH!KCM3CH5?U%;n zk6+^-hFJ$CjN~Q(VqhiS7j>Hy?jr2_s*TK!)S8HI8r~rzWWu4}LokPLLAI9A?LWGC zPdvhM2DUFq15%nInC*mBhn(w}epo#wS58fGB6E4h?kFx(N?_oY5 z)P+2Y2Fud7ZR=n54-?|CpBKYo@WFn_J)|^S-lIwKnf$eu?b!9CX`o#6$t{^!CG}8* zm)q}$9P)Q6+bHmY*AL)>hSneaF%X27yjQG-sr#Q*_9KpuI& z?mPXgyremKRAf9R#+Jfh3v4dxI}#=V3v363evXOZjvy>9^7WAzxg`X<1XgO#V)tns zECsGD1Jo*PXew;hpPzBlR)8A3)#GBeQjZx^NfN}aaFjN=hud~J5 zjs_=$56-V;KV;@1X3( zj60?E0O>QZ>s`?3^F7ADO_U(JsZn+DSHW}0Y+ez~H@f8e<2z#3)MGz=d7~+J^Cs_3 z1nh$%K@9Q1GHAe`iw=o0F@R8thT3 zd0ML_{v59;_+Q_LyVz<|L=$Ts9 zY=J>TqO)SRL%p9iR>hz~v!S#judp_Enj{gAmP=~u{(uO3pW8qvUU9b6e=SQQnBvTd zn^4FJam<}HHq|b=>*>!4Pc04{`D|gppZ?;KB)9YsxoaW=gb6OS>iOF}UC}X#_x#Jr zmP(-pY@WT?G{h5#WA4H>5>qmdf&>ODc_hJ$VB145=OiLMRieE`IQJm+3Bhj&5jp$M zimA^svold1CR%0Z_7cDNvwyS;C}dF9cG^!}F)aQ4UFB<-qa2@fg7`aNt*B!hQFVizE`B%L8lbDqTt(P%BCrfYTR=00cSo}B~&e52+p zjw)vrx(x9IU+7D^YiZ&jKVb8D6p4I4dve#~3^fYID#33kk|N`wCSJ#iLATTJ6#hjQ zqwK$LSEF!!ciS>Wbc~fCET(TUq@{;$N@S_`XKXgajzd0R4*tJ)m6INrs?QR=7;?>$ zXR~?*fu&PbeNK!g2uNfnF2&j{7^D;C=kXv3Tf`~a@FEsxB4;DOqJXyWsx68F=ZqyD zcw#VTl65Vf+zN@OQV)mL;&7hZm^W3E79xYPWq>*~v8)K%{51H#3!C#O)5ha+m6(l!5`QrxX+v1#zBHYw)F7#xx( zRLmq5OW3%q-|4LTw>0cSAiFUp)TaqV))ykUC48|X8P8vLPI-y5Rg$nB3ETLyq}Ro_ zaN}IpVj|;8?n6qBuET^I54xCpfHeS#>6jeC8@-VwqDrdZc=*Y~Shb=)10T!Cm!?iS zUow9!n@d0&l3<^Z#)6_rH^vo2*EPat7vs)PjbV?>r~NI28q7l(8s-Sj*_A<#L>R6W zSMqCPJ2dmmAc=J!AsCKx0QMt(%@S(aL$LUrqE;a>5*5HHbLzOli@~?}5X4kTdbuol zFv^2RDv6*!*c_2p06JrYDJ)*ib_J%#C7@Ka;1LQ;7&2MaFaBg_Qje|L-Zuro0> z*>8p>)ND+IH6+dRjD*YL+&3;?i|2i#9w`?wR!2XN(vKRm9EKV9j5W0(CEjQXXcR1M z5M!@KXf__SmZ&ik6SpFJA|83BYXmBduLp)&Ap-X7W$$crB$GQnOTh&Vyqg{lrg zS<6hlfLs_SJ74QiY$_y>_dViSzf+Qc-;W**3dTB{`fZ47El~*XyHqI={0LT1-}hU> zYGCB%D`+V`mRSZSk@9dsX~%~o-t#HX1KHgS<=6ri_RxnZ@78Pw|KP9LNXE~D;RW>; zV1gtSgf)2e9yyulPpYevA8!0 z_FDzdNX#2Hx(?@3n$Xm*hnrQm4!96?kQB*sik-2NV5$H&H4P;XDthOpkI8hW5p%Y! z7EFuV-kw{0jCK@C2z8@`7)Ip^85NH20e93D)8Zwg$322WCNc1Quo1jJ6N5k-VLoFp?{*Ju#R zfe5Ps=-C`(eHxs;N5G10BZ{O|Mvr=OY>EMG(s~FvN0>w>hQ2c#Az=?wo$7Eyq!H>I z%$q;lcWtd#tN)ZF0JYTTi*T{P>}0(8&)$)Em6c48TxTGej|-Aj~TW%_-i1?KaVmB z)9=Yj7jRB$zS6mERxs0Wy4Xjm*i7(W_ zlA*gmIcrq~x;XH9d__A(x?0m5L@md|%y?%UrJx_mHum(0au#$wlZAg@e2BPN zGo;U*mW=64SJR%-tW*>mFl7x=A!|bQCJB*ElxP1 z5K7(2Rw^T*M4hrds=AnK%n&&sGu+jPm|l@y3$gCVk2O=q;;8h~4+=3v@#jm_n+?Ar z+k#OXyC;8h)<=uTcvrjAXG2LlTKm>?(cI^S6HBB;&T^0B%XN>C?&y6#9}GwFC=EeV zui6OS=H?g}PfsWeU_r%#K-kUdNr}K!o`oEsJ&K-j&e$G_llKz(h1l1)pB?XG*&8i@ z@b04`N6O4kQ#l_{BprqU1R-fBGpI7!myC>pP2FNp4joB#E%&A0+ih>xDMW%OBcc|5 zO*7dJYB@$@XE!NiXmpAgqwNQyz%3rhXk@BOEix-AgYBnhTa1w*BhLFt|LIot-5`iYDduJ*lQeSmR&ty;Ao0_E0|i z{A0q`SI3|}i4|s0cO(DzNE|8`+1IA2>~XjCXHu%|9fcX6AS4fd*h#jy{A{M}wx?yS zecB(x*Mndi?||{UUEg3)Hzb7M%bz)W0JAu1x`@O-2fBWWDBgfslMrUn!?w&JcXX=lrd{}QJsky9oM+@E> zo9hCno3MG_Y!KHzQesp$F=Oq&F>kw{saEg~Ht?y*o82E&c@jGz%sNj9wt4wN#&%53@;Yv>K0o#>5s`lH6&szVCwzx>lWxYCeIfaW)S2i&rJ z?q_he4O37;z;35RvU`brKX$3KsFj5;4}#LlW&|g`#SWcn;g%>a6Isq4o?yE;yU^M) zD^%R>P95E?SFne|x+9nK+y)CB>G!kn6m@Lu<2pxa`#S$wrDE3t@UKzpL=x%a@*nRH?s%LM5oIdR_@6AFR^L6?+XZ%5L)T5l(GQcj-X!>*@J75(!ZY-Fe__z&qg+DJE}M$djm+Ic+&y(2 zpPib9y>@>frnuow?&byNRqOR_(CRpqafN0C=?&q?q0DXWAQ&=I*WZmf+(?a&?rx$V zPK}4^XiOtWe~M-wt7YVe_Doz=2SyNqoi67v*zP}`X>~f#GGfTsI2qi`U4fcgY!Cxo}|rz|bI--HZe-j6p~C=RBIBt|kr(D${tGFU~( zUWd%Yy8t=q;|E>oDL33ih1N^SiXUx!8%7cRnx3T_3?wFI=+OjkvH8YhBp--RON9?x zIcy00JHcp5z2kXc>Aly5db?{KcP4C}e-9H_H@S=uHPgiBja+JEtK-L$za6PcDR^d_ z7t8gpbboSOw}77qCvNH=T8vztsM6SvbSVoll)D`m4k?%7H1WQ)&%v==10m0?JgNxC zE>FOrqY*k*eFGWwfs@sEQBdUr9V(*{Ty_g5@-$-U{v9@4UUKaZwHw{XJKg5lJBQVD z=y;`p%!3m`!KNiUJ8XX#MsDo*LFR7t8`_oT14=P=u)%XxcaB`a&ME=7V<$v=qq?BN z5JBC}`{V*yXHQ7`|F!j%L2*4#v}m#c7PnwQLvRT0wk!mK26qXtxJz&e?rwn)+!l9t zcXxMp3-UIr;q9lbx_$55>8d+(PEYsgv^<@OeA4r!A}WQLbw@sn7qdSzhnYAp zHJaP7yd({FQ^UObv{u4=$kY%xJ@OmBch9g!6|2Q8L2}y*u6C+mFhwJK^5rV-u>(n< zFK~EkivHrDzP;{{ADbK1`Qxo6kFOj{L~jdxY57N?)|167BN573tMguBmYbjKzcr3h zH&ZmmM5;{K3k5F|7yZTL?BrihL zHoW@Fv#^KzJwvWVK~BUQwTQmBwG7cK{0)Hf2zYV=57}$~>`Kp>d?ne>O0PAvf}M;0 zZ$nrXo~q9l6|2j~tuz7R3C?G%uSX=0-GsmRGJGlIgN)YJu!6Hag@8kPmdoRn%T7;t zPNL~8q-WQ}kv4a@zgwhuaeSu^tj?FCO_qVCz$T(o$W1@%^k}(6pMgQ}gC{LI>0$^@ z{!IbF?iko2r&n76qeQUDXtTuB?je6iD);tzqFs{ho8=R?10TO066X%lT+2c~Z}a=c zS%U)Th=g)CW$x-oauqRi%VK6}7y~njwx0$EIws-xdknOqF64qVw$eK(Mjn$^qIe4< zi#P2x&Rmq)_3r!oCOl53QvMY0=AZWJP!iW(oZqy1YIO+GV0#b$&4ODS^A-VpYT5mMl_>YPr7binrZ)<@v907&oR-E;kW^)NF7W8wLTbCN&y~vM>>nEDJyZSn zW4YQ31Ya6z4?5~w>v5NN@fn%{{OFmSobOCo4Ap`Qp%X++F*Bq?q~d7hJrLO6&AvOCyt;{b8Ll@Pc*#dcj43`mSu zYEW;v-(z2!eSf;g93$4kw)>ZbwFvP}B9%V^_=G4mZlP*;;tX8mwD-rWvp%pSNpRa! zTq3Zp9^71YCl3@DC$g4olZf)AVl@^VnEyk%s>6YEH`h|MK(EN}O4-i_2#IMbcP=Jv ztaZ*KRbWV^cT}`3+1Wy#+gGQ?VWFkn%w0c1Ag+*~D#plr5kl5k9u8piN|#>>fG2%4 z=s%ALF?PJ13RAWgqPtH&EKeY4CFI+ zE??n>Ns=$*JJpI@%;pH|f5;^feTZ?~JJ~PG9y1aV%F$(3ad9*z} z3T<6;I7Nh58Pj_P3=T`) zl8LQ`zKcGPh2zo2&+N8fybY4Jnn`@14j`PF^PQNim&b}G*Dk*N#0ckPqB#Ct#F4%X zVu>%s$@t~jyL&Dp9+P`F3c*o)_>rc{)tVizAw1W{Bk(UbYm&z0zpRa!EKh>1?XfI# zlnsBg0lB~~fr_a3+q%zYu%6jfB9_&okV$IXzFO}9IM7XX%=WvRKo%JhBw-e-0Koo@=CHFax@Vzmtt4BZb5HPEaw{%&VEGytKnD9g#D{qCYwg8S`_~E8BtE>}Nu_N#0L$1oyvAlZRM`WbI z2+zK|k|3ErwF=82XUmn^cU2iBVKv2+@lacOt|vya=$Tl_~2kP4WEJTXqrQPXK){QZ>tKY+hf#$J5AT0j^*M=&fI?_(PNL+HKPwV>q zJ2^i$F{fsD&!$sZ78mq;&g6ZLw2{d4;{f3*tQ%;TXvtM~}ttx$n1r=ZY`SXKxB;iK{M`OB+VzbG;CiVwX)r6Ruk?{x{jWS|x;! z5sz0!Xh*MzpVLcX91MgLnKS|tWOfbX_7$gjB*mA^ziKtSP2;xdYvnB4da{wR`?|oD zoqzL8Vy(rQFmK?ooQo=?&hS`Kk!*j4nq2Wjt$ny0kU?s6v3fLB5pU0^b^rE-`-rxC zJS$X)o1lgVz0GxI=$JX5GUCVR-ULcgq*d2viCr5uheKyaHxu$@yZ2S6hkJ;`wq*B&2H}Kld>IcLXV% zDP)S z%(CQ0XZII)$HTiX^=(nQJfF2POUpkGn=i-ip0Y<4L=W)0mwnuynNe8?eY=EteVA_N zq&mq=DCnlhUPwHI*rmGux8W_J81T0yaDx~>Jd#AaOoE}-U$W(AZj;=l`w39%4!pn8 zl_!N*__SHLjtg0Ao>L(10g*p7Zlu{TZ}voa_h`ebLSf`rhae)&%Ll_=w@1=p58IUU z(c|+6s!oUUw@m94jDRUR>6i}EbhnzHW$9Q+V|x@;KfHU!S+vC`Ol_HKz^$eP4jsKT zJSB{9jv{x~vUbZ}ti3_zkctDvYn?lSN=Q|?6X~cr!$AcS2Zd`Sekcw$Db_NVHd}r^ zx_M;NsngE5KS5*dxPDLPSvj&b;q@hG83)_{Ug*q7LEJg2u#PD0rq2R8CrT(4=mghz zYp7W(_B*!;Cbk~w$p({rvYCebkSBonb^E}pp?`hCP5Kzo*WAEw_Q${KDoQZwZ}ncx zF?9a``3IL<=C`rkg1#%2P79`XN{Xrpf~b*S7+yH_YZn5PmQ7oKW;tE6#T1rq$EPp# zr5!b0s)>1!TW324u^4oHfj@jUKa{w{j4$4F(yj?3)EO~U7)@vougHdYxa%KJ;~$$n z`w4QM_T<=3DC>+n>zncr3?gai2?!bDm$)IHr=xcO#WL4wu^je@)(`IhLO=d+I}e9< zNAbUOFQ@ypmyADR3I8NCv@N+sBvea^vVR`m>0kzIbQmAx-?GbxVV0HpbDILimzGcF zAC4G@SH`SrnW5*CCFlZX`rOXE*S&E}$kq)2>pA#HQA2H!fYgYk%=}&%GaFS)cTci$ z@s!cZLVq&x@N>HS!HJ!cIs7UkkO{(V&ni5tupRmUVCl50h7NPpGk(tw5@9?|+U#92Z`gy({ z;H#e|ms-9ee@J)kybpS`HG!$XL9i+5=-v@9Ym034M#x4%(zuAH^5xY&rcUy_JU?BH zbqyUI-jDZ$O@@BMQoro{jK!+SpLO>s735e0Xv*k8qLLhI^v3)DNKCt#x#&#u^hRA+eQ^*ZtCEv%db$wNC>wwBsTtd)C{u z!8fnM>Z>b{O&qDwcE1^x%x;RV>vlln>Z;uKjtQ2ZtA_!3XglnYlOs|sud*w==NXZhp65-p*z6WKzA3e;_shykPfy#El`O2;D@sg5_jG_iIX*7Z zAy^fl-66?k#h@?y7k+}}?~240lpy~1Z(W$2yy@x{Uf{hlERa>8j&v;$OD;*$sRE`o zIqSI~S2-wR3OOvAFkCERBu}XYt;>nF4}=&EQg^fG{{Vk^pPqqw*3t~Y&9&b}8KRb` zp;n}t!#m~244LQ2P^3uzSK?zz-cD+$x``{oyv!2CZ@W)es{Q3-lqxVzl^=T~n{O>o zh21YU$5Mh3BYCn6c7uKgz*bnK_2EnKPQP^Bs1+U+`wl?KK)0AQB($Z1Q*cGG*aTRW z{sk{l?|sa_0n#YRd%qQTs_q*`e~kq(5O2^2 z>-`dq>(~+~sx({)wG6|dI5|1N^KYSFKQ*5+)yt8^20pLoI(6lY1E~YM$eM# zALrQ9c_6`6@&f>a;Pe;WIw8(Vg0^+eQP3^N@|e7 zQ4wzhE2}>L;nvoAXFp{Gt2hxSkCLxditOhS7s>UFs6lC15ZGKkzBsY}Q4_e#%p3b~ zFApeCc|%RM6b$XV*&nZB5P&+^vK_*W=G9XPgiFpG+Vq(yV1LFgpnuEum&;%!W2zrV z4|~+PY0f%eb)`#Ro~Wh+&r+)uDdlBkV{1DAAKDUw`944(Uu61rL*oWtJ_o_y5>)z% z$}itbkwiHE)v8b2m)UdRwLeTORzQa|II915e=!l!sE7Dt06{bI4n@z{ScMBw>ST5RIbY$4<4PuIDc`S9#@a|Htg4c+Y5+`r-Su|pPr_K_p!wCGw|MrhnQfqyg3>;ee zq2X!LPzC!X+G26tW(%E+?E)kqQGBgEQ-n#lfJoi=S4W-A*WT1*(7d~IH|*rq$9cj2 zsfqzRTk=B(PIiXK=%t3b+a}7fz~XwLi=J2z5!k#IX<00tS`n40eh_ylImut()tXSv zh8h1!g`maau)w%`^RJo`9WC65!SKQ6@bHxTu2{+6A4Ies*`hEIGtr@frS#v(of5VS=? zT-FpgP!6Ly1kL@Q*9IB0v?JR-7&Yu z1a6nInJ+JE^;|XlRbk`1k!?<4KBVb!H}KIz=mox7a2~$Q!;5=#^ns|YxbuAelW@1C zPQ(Y367ZG$U(uutql(`oU$W}T`81W39vT)3mfX=iXkUH;WUc1E=TMEZ0VlAC?HJ3+Y(En5IZK>W8wH0o`}`_@vCTCxsICd5pjCSh#*1TC{gvE5!d~7~ z>@Q~ol2auMpQ+(D?U$(40c3c4Baj^*V)$poG14`iKQ*4#w;|KYm&87pBDWB>Dg?5j z1aZWd(wFhR&z~WT9ulzc4f#NEX>P)dmbGNjWvkJEpO@=U5VW0dG3trd$aXrVf5fJ+R-dvCk?Pm07<7yqZd0rT^ijNq6NX!P^2uq&3+2b4fnZ~RwxQ=mCIvCpd0%>47vZ=Ri1^t-!voewu* z=YQo)1#V4Pc-|Q{s65b{@_{q%;^NW+hKIirUMl|zaREVxB<(gm@HH1`s2+#ecz$v- z^PqkWNB)?%DILMcY}zk5y%?Z=5k*Vp+47bnNKJnhBc>i7?t(eC*&OJb$x-DYMNi+R13{YHT zHtS1nhOHPr?M}VijW);SsCGAvg%*!yG1lVCm*$5-9b454LE+hyEOcD2;I9XolV!iu zD|03rX)25U)Y$oCBdX*-k?_1h>kj?{jLyF8+bY9oFXWZaa<41hpSYuA6!buz?bs+t z&yi4X5Mq0oa^)ltju z$afdi1{BldueA!JJfF3`MLcaH7?WL%@*U&oo22VvJ70J=pY|Tk#+{#sS5v&~Lee4f z{prJ67~$(P{P&MKy$w|%tm<~(IgVhRyFw*v`^zZs_T;p4z+j1zC&QIVOmGd*obaV% z?o2SitL3y~-X?DR=wuG_bdH2d576a@wKCpdf#FEZg&0$)-TVD0Le8Ho@Fv5oK3_}H z@#une^}$RyT*!00ZpS2?lnra|2>7#aLZiN}$yJ!p=E)V4UQ4H@Zxh^J{Qp+7rJYz~ zg#{DOIGU>ElGo2u*#G~k*_yKM9n{%0`3qSG`%o6<6j{2DwLLWPo0oNB#wzN(mza1a)IquF7+H5;pSa~SE^O27#ly8SSon-%$Qj5psZF;eMgnTy)V=4#E9r>0 z6H-~V3tuMh#4}WVY5}-9jQ$jzE7ehKaz26KAVXZI@s197Qx=-6HwlP}&NS%-n-@R8 zU&9CYP{NCCp#Qh54vzCETH(8iDM8CLgoDTH6LuvmA|BGrD!FbhVTAbE6Eh)DQ3O-% z`SMw1%tL#Ay<4S%phqA9;J{gH?qWZ+AjI{n!bxrwzU;Vkn>*tP&dzZEHK6!Y-o@(KIYf5H(T!BI|8@ zZf&PKI0=w_V1cD^A^IRB(dc3O53*>$@M6>jle{S8-NC_ul(clC6)-yZ)La#x)~>q%n!+Pc&gXD98SkCPWpzR0Y2n!o$qWEF~q?9tb2Z`WmSfQ%)FX@94FbF!kry{etJ4T zt{PL-$Zb?HCO^zfW~JlhrDMeehn_j2As`@}LeQ*L1Q`iJLRq|(uQeDoD@g_h3JWwW z&+k=37^fb%k&p_vx3{0t>Xsf`x_waTzYEsWI5iI+fh2sV&uzOh;XNlRJY`OEYnhx1 z+nHiCKT^WrI;?c@9m38eNJ4ED*4b>9WV&vX(hycx2?O@+>5(E0DGF$-sxxq>>ALEl zU;m-~8}ZC#YR%_La+?1Ee3c*{>WQK*-8{yB2lQ`XT%ZMrqG%%ekNof<75xPDBo}F$ zYB~ile@g@`n*jh_k_nti4ntp*)om^uox}{y-(2H+C(os;;u(936P2m1S2bq4kRM@Y;7%DW_A6JjC8 zCMA>7;^2R?kU|>9tlzCt|2YgB#&A?ZTEA++9$xMWV>Fr` zSc7Q)*p5obKWBjS@|ga;od7~Ch^ZA|{N_K!1sF$!sAREBCBmN1NajngLu-DqKY#|a zGMuZJcP**B{}rDBAH#jq_Pw+c%^{vyaYg_-%y|$CD$e_AT%;22lP>30A zWLB)6Iw+%iZYu38B=lhsEwYwK^K)ZrVoeQaBC}3twq#W2=;-NT*wQ&YKRhgw!osqc(X>I5~-9<^L-t$)=Y%G1~X@AOxzo6(O9R+-7kXfqKHo z#031e1W8E&F0UB61SH=hLqRs0Q2eXik)~b9NvO0^EJT8WZmWU^2`L8UP`UBCjy8z? zN0Xw>>huVn&f`*z7R#mapsBI45^sg_`+`9p7-9BB5fzF4eLt^aO3OKMsLiLuH3SyP z4U{8cVK#9Nf3v-_OFq_as>yl%!;SD>mqa-7NqhUS?8I8@>+THEyluF~#4V(Xot8Gq z+3&UKnw~H^A;Z^$L2G`=etFSD%!oW8lW&E7xGxo&m?_N4frkp zMpQ9k_4GWndN$yWYh1xst*}^_i+dW>qYg6L!papaF7b=KkVawz!ZQ?mbc0K~dY!=G z_ot+eC57PA9q0SY?4Eh2e)QT=O;Fh@ybJ;DMj_pY%PJ!ZTlBU~%radJW=lmAqHTsj&{&n|~EBQ9I*UJG9$75UhPB|W5WNHYT zr=YFWn@#=sO+jKxLLfBVZMeal2Y2>~-VNJ$U-*A#ycJG$TOe{iLyCTo8+Y0RAK z`!+r>hq5qJo#xiHT-gL2X$kIn)duT_jh;_`r{d%#D6jEP8$=mBA?qgmd#lj%q5W;z z3z*&Mqq9{=yHBC;dU2_qLHC)(#A+A1#VcR9B&}z}n^Lo2;Y#c_=Kjw0Bwe4Ii<@L& zr}v9B)0upZNe)++rA?s2M(WhQjtxXpL+KvHlBU7Y5r6SQn;r3xcG5_=CUbMnq?w!x zkd_i$ex$1u5gta73ca(D)ug~gP8;{^Yb~zeAyAY_Xso8&sYp!5ak4W*#};^%G{&aD z*>sZHjC8DXfx62mgqEO#9~+xSUQxx}tc4CfQzbjE9|r)vgu|{(C!;m5C>+gq;brqk zV=1#Rv50*O7W(wZwK{~$@hd|z+so+F@q%#Wbn3&$+bs)9N^`F*e&b487z#KYmLC~n)_?(Fmc%I2dY%<8Mstp$yrL12Gz-@raY}(Y`;cJv1$pv_K8o1zX*}KR&bg?qr zah{#1JEH{$DqEVJz~Wn)bm7-g!@=`!9WLdyl$fhz&_fDnjs|G^`}qFcz+5J!`W@QW zUH74V%#E~00mz4z>SuiuWwf&S{X3D)_`{!=`Mc{S*E4>#=2usTNNB?9YU!6uO#xO| zeA-X#Z_tJEwpKDnmT;b#WipG6!0YW)ecgZ-p}Wo7`g(N=iYiXaLM!5tR-6YC2B7)u zn}a4=qwSU!eR%}Z>t%IEs30kZlhs>(X2jwZGfd3}m8jF^&L49cR<@qlP<&*X0N8Fo zjxaq5;$AJA(f3epLan@fD?gQsWkPcMTDj;6Yeyme}|ovgtRarq?F0D!b#5B7ROUaMoDbM zmwTJ)H%PPVs0bd1ieR~`^o_ZWXU?{~sZM=Oyv`4tH>zB)PKwTq!7=27DkgUx^tM<> zro}`bI)3<3^~5s7O+h#?$2jRnD^HfI-%rrWX}jDG;Pziz^mJ@h$K#H6rX1|$C0(lh z+rh;$tN*ZEneNU>_%fbm;l|?pRHyu~3sR3ii+zz^Wsi)#h!3uvw1G+`E^%9flpVja zQ8Ag@?-?^6Ojbiy(KrQ`QIF{o{c-Ljj<;u-shTKYvMY=@@Dl}Byb}hGRm0lL>gF-r z6LMvHNGb2@O(w2g1icfVp4xV0%jUAR@>LOmi_eRrsY}Z9X z>gwj8>%?c+y$bHGcI!aT#-N@I5=gN?~vcDE|peEdYW^&os8>(meRLHLi{eV)=2b z;p78i3(BWRiYrCGqDOHj$o==S>X-&e^%>%}XAVdj64 za2WieMza21vg7tppn%OJ1tLMTZQ0&-_z$$%x>Jyw?%9-G#y9+v+kh2ib3;Q=ikIEU zsETHl^{Srh2xU^1ftakov*m&~%BO<*#%yI&12os{a&9Tj)>yzY2Lc!*`! zpoCjCs|M2laQv$-TBdjmHtiRn@6(eDm$aC@3^G%I9b3?E2+Q|p`CUz2-aIoJO2{}-yn7-ft0f}YW AeEX$+}+)+MT=W;D_&UKo#O7r-QC@P`aJLZe)DH$ zXU@&ZBZJpx zJr0Jg`Y1w*04vh(>LdzpApi4chGs?^w@p@hn{z=3F^p(BL1n{-e-I@`mvuy49IDWY4Q%_rhoYUfT^*Dc zCT1HHIjuua^>(xyCeuL0bJV?#X#dhv*!GpJ;}>h%EqKvBZ}*}1Ph0dUw)FZ`IarCj zvYw*iXuSW2P~-i71~H%*&_9^}4fKso0J^m~V#<89jg`;a)riknL@|ko@hOSQz2^BP zotmEb!(-!VeujLRlXY$^iOmrsBu@XZprC<7xMk^cbsuhRL%QNXNA6bc;vkTtj6q{D7-wO*2>EsI9>6DDR#7V#AGmFQX zOu5y53VNwH6@O-mCoq|2mDj>Cy6hu-TjrPa;-; z9HZ@wj4^FYT*&!$gpWafEjH7*d;S&BVOlNYIaGC*L-vRZ--K&!#j>k#m2R@hH6kHzI7 zVtoXtWxkS?DGZ*!Ic@2{d|=WPSqvh#P#yNPttDr8 z+$!4&*djs5W=HhTTLHQ?Wku1 zjxFY>>Xi|ecZgS_!9p}+{5W=&0P9y2+rLmxS<3`p-9qB&XSl|MgB0P-s5fnBVEvN( zclQ_9RVD<+3``Y}yuLW<6~203 ze(6gYUE$77Y62|&;4jvlEQn%X$2p4)vIfm~ZBS~6BE7jc$;wiRa`uc<-i7c)$;*aD z5F;^xWzD*0n~X+AFshU+hMGCubnC`0M0FaXPDBrom5(f`(B9hsZ?P(@aq2S9)_YgA z4;sYH8_3i@4@V)XUFZMfkY}GvWNAWCV)aUe+>U3Qs>0&qokn|FgsTT*m!`J7k8#!1 z20J=pO#y24l|GB{@eRiJzIxJ==v9dNHmxZ9^_ixC-~_OYhQOk+Wln^=MDB#X_4v)i zeoeC4-%XY6jnPnbDyV-NK|E3l9=^ORqyk_NTiDs@Q6@ffLAraXka1Bo-v13*o}eP! z;0akECC~@P;tyRypmy2EP_ZRL#-uKW^>c1*!NNeMnEq*ZY|1w!KSA2&C*AJh?$avm z2dZ|B0fVQTJprZwRt6O#b1Iw4na_zk6cDH?#HvBeX&HHOllLJ*rCcuK#50pd=4@(V zz{|^vMiGOIY>mZgzjQo=R_%@w}p`6IIoz`hH$_N2JZO+Cy_X zwqSn;WqR{MR{$*$Nz#ogsI@3FK`l4Mk&TwJpBZ!uV7o5b}bpQ@M}YV!;y z^nutWM{%6dA1t00sELa~hPB?w9{MZjy9F169d{^ON>OgMd4a|k8D*p%S=EII*{S`j zLQIiw**Z1pyM5K%PPnEHEo(!uMiB3*39c_1AdrqvtKHEe7|)jz`-Q%QW;xzmeyJ2< zXWN=Ye0>N7bvHU9=6KrnY_*XFtUQ^(X?8sgjo$3@?m0zJ{186YU2oW#LeZxfxH(Cm zV3jqsG*8e@#{bd!c*I~Vy}Y62=HhpU=>D0T?1~@49aG(4##Wb5P63_L7xk;hEX>SK zHFa-JLhGb1$!MFMl%7@?j{1`&TXT;qZ*svYF7_op_NCYpE9nAFm7=;wv`LCOM5qcS zGZ~q#+}015FT9`krw8L?)$TfYwWv6HcN9%YA}S#7Jm*8r7(t+*7M&!w zxAr$WTG~;+mcAY7bAKb}8z(6Jx5gywPg6@ z<0z#U;w1DG7lCi1IBURPL>1_5NICxx#u)O{mA{|@1;q#2F{0KiJWtvzYH1g=4lAvi zPDn_UZ%c)Rn)9$MpB^0@l{ox!|FgOo;>CJejzOU6o9&%eC&M_%Xu4~*yKB0$W46PF zlw!Z*_DHx5b{x{vdal9OeM!^#)QtNk?zkJD0N<|-)LEJ!wV)2z1}s*93JMAj4iBt~ ztriv*;s%Vpyu7BC3kYhtsCDH^K9!5Lk6Zp2G3`R@?R*Sk{^sA~7H$B~V3O$Am|pt! zV#W|}gHyS$)xYZaN55)DQ3z%k6$RvOgAW>hfGze_I)r@&omTrC(^5Tn1(V?XzyGWv zfcFx?Nc{M2o;2|2u?@ZIqZBD685sh?$EtYPDjHZ%-yOMYZ(nI%8*GfL7YMz}=IG*Z1loIg*3Q?g+a~Z^obZGq2%yj`elKK z+7e}u5`Y-vWT(j~E&R5ww7IVF{_gHQ9Go~ToMdot-;uP8tc={oCw8iTA^(k<-Wi@A ztc0J_@pSn`Tn)#s4-w+*;%ZW0CJ`uJQ&)44f-`=fg)@{KAD{uJ5~r-0s`=ri);YuO zmksx-&x&J+2+~u`Cx`r&mX>CxZioER(vj6!p-+@0t*t&EOB_#O=r*abBb&=!NNlaW zJYO246S4g&Z}ytQWs_`qQn9j}i@kr9I6hqM8wrqSN)kYGaB(@o52a($DDv`K#l|PZh}Xxu5-{dT^A^@03magvYCAvrvP;C5zG6 z*oZ7;N%8aG;GicQM>fOlXpV)nj8M=Umw)OXzwB+PAl!@0(TcQaa~F8CL?kRkUp^=> zxJ0)(m5Ptg>w80$!R7~;O9Ur$QH>~&s z^T*N79wZCiPqH?N5`mKG0!}l@pi%bB(6F$T{o|4Rub_ffe2=O2d!nH&iy0vADgM+i#`4%-VcgIM?!90#z{eOy7sE)U{ z->jOC5>M>+bY4|erN_~wEAJw&pfLLNJ(ukgJVeTKrNsk=mr?NPtcySMpG})z$*{8H z(deO@sjCpBK%ZD%pkKHv>A|fI!EiLPV*beYM{QS&e!_%L+mDV8%q=G8;_J8lEl1;jMxSw>!X3{|}VA5*)#6vfq3{n6k zFr7c$l;4zxbl*t?i?>|+cEc!A=j9VUm*WAT*AooR`}AI}_QL%bbXyp?sDV|t+)!;! z=Pc*(g>s{FN!fUohcl(Cey=`R3RYU4SR^D&JYF|P(ANI`{{2`&ohCzx%MN`+B^OL( z2wfCd*pHK4T>;+1=9eY@&kT}(mYbHAmm4Lwm=|*Gl4E0qQO9uDN+u^3pab2H^YCHz z!kOC6E-xv_jF1xb*N%@Fc8479JM3$PUhX!N^n6o5ODVK#hljG7r=g+Wztb`@Qtysu zi*AWYNW|G%SX4UdP+)HF?#hmyEH_&~wwQN*;sikF%{||)+G$WpylcPTNo#1}9ufHy z8TU|u`^;NqV`@qbK|nxg+E*;N^9c$Gfl0Tpu=qju=~F~hRNM2-qK%CWU4_r1tIyc` z_wV6Q3HQc0QfRBpGCfRRzdfgB$Hm3z10a_P&N@0oYSKchIXUEIWDLgF2h$~kOsTND z5H9f(f8VwxB8(b2dFeN>sMv7%4Oy*POe(BQfdc4yT%GgSxkg1tt~MA@bo(E(V3hd3 zCqc(E*p`V&O-)V6pxQ4!0D@yrTovTcYS&Syd~PoER)TpUN2~2wgbZ3hH`jcwtkW>r?V;*vv2;S+t#PZ71)=N0>}UifWmxxQU$bq@ z#E9bgh_0+{**`M6fTb0rbuXskmbw~i=q&;-aYoG?*OW{F3o2jqV}8E7AvB`CIXwF2 z7_r>lAm-0v46P3Y(o^=Y%=XpS3V(c0ABpR;jBNRmUlBtq3cZYXBr+ScV3}K4 z*BvNUoX3Rhkjd>U!bUS|QzqOYtggzQ0f2zlX11#r&FWWejle~}7~O`n4`T$l7%6C} zc@KK(MNi#ny?a}H#CuGd0~A`%g;LO3bzN8KX$qJIBH_`x&W=Zb!ysmZ&cLEE1^F>4 z5?ILG+}zV#ZV(dA;COdomTy>On|{YT)_bNTbhn~3yS5Y%{M{Scb2~weHsXG?0w#q# zQ;%BXJ&oEtY$5e|MbpiKRX#o;mOd$sfPl|Lj`$Wcwpe`#9yrDQx;xY_d9NzL5;wH2 z?@Oq?e?|rou7rs5qD}qzRcxyDN()|r=YD=trsn}2*ki-q(pt*%{$d*|nD(UUkWt|q zp`24)9DVO~CE;GcVgC=-7$~0GC8y}4koK~&GQ);TJ-QE=Rc7=H>z1N8c|l26T3x_P zXQxnOExOANIFh}(mj;K;?tz0*zSF#7InzdbJVGbg(;=A|k`J;=UPy$ELo$J~T~{opoxK z<#V$hLUQotuNWjGB>#Hqmy4BkA^;v$tJD=VI@{X%Nj6GeLA5-0{pAOqIKMz?ZI6jF zWV1{3Ff|@!dsyv>j1pYa4*E~*pRh1RK6!yPXem0N0vu7H?AdgVkl6DF7K^F`;e={8V_SitwBk z0l7Q5Au<7XY+N~C{+Pp%@NQ{s`SxvP$n){KsdQ)eP)%Dsf@cM7)2f!E_FWRjkEK;g zeSHi!RrPJ-d#H6dFO?e^`MM@+Ko))chKQ_5AV`Wm(;^SEGZ}PtZH)a*K-x=LCC<<= z=jEzqRcICCDtb2L`Pi;fMR)PC?-8lYu@i{NQU?6HN8F?YD?bE}gjs83$T`I#J5->&7nxB0Yf2(`Rn0};$a zyZ+g8;6;7=r1>;)Bnb4$=l7O(J}wOs6@Eon4f}8R&)U**a(S&2j%zt7Y-Tc&v*|oe zxRmP_L+E&~l^4*PZQ5?jF6sC-P$c3808NV0aCW&Mx0DLTO2yQ-5F0nCukoG&PAj!d z|16*e54^4 z!Es^tvIm0fSIC4(XzdNRVS;mU%1!A^hk+Xr6_(dPX~{k@QR+_Ml_b*I$g^)*0vc|K zKi9~7L3N(lNM^np2d|yO36pNnV0DG!FF97cdor^Xw96|MZ00UEqogrLjE^|kL_gL) zea(?g0#(qS8EIByLumwEzCXb@B$_SPZoM#{n0Sk%Cq7d4-@Eh2+wOh)3sBPTg4R04 zR@vmi*e5=sMldF4aF@&5T42rIx$hGVGO`2+^M0F5?~^;(6OnK7xwqx2Tb<4lm1G9i&lIw#Fy*`l45+wYl|->9os zXJ075ctQsJ5(kxWMqQmi4BurE2sbx(4D%S!m{n7xux*Rk6NW4%ps}PH(XUkfJ@ZfG z4RESig7hmd9V6$c+|z9;ht-)tm#dkWRQ+;3w0-Gb%jpQvTExfoc!8?qq7#WJ<|7zg z5l9b4?@CQZsq=if-*o}6M+{eoMbT@slfr^K#*^rcGC`SMCp;KFRfrj)5lu1r^v z!bV4jqQo7+&C6~0_5ShxGD#zOn13=Q)cLY^1mPS3d@l?we0LcQ3nAt1_DbSHMaMmX zt3s{7B4)r+qm|%S*J(B7FmZ<8j|O$;5RgMYUdrVNfqBxsk^`l|2HN5ZlDBPLUXjpv zj@S&klqDC(ZmSowVW6j{XX7qg$`VHee?({zT>Yh_(lV`c`wq0Fo@pu}E)rzoWA}ta z)0p~S6;hJ^IsW8**HMgVItRsv543)Dc(3jObkrYJ17Z;&{iy3Y=n8tt0pqA0z<0oh zVGNrQn>fx)Ee#0(BQ@k`OiJiShCGS)MW%6%ws{qLcD4Z-nK1u{x0K}h-S0-`zh=xzjj<)u`7@&E=WkiMOH*59i8o9>nd-r|?nK!30I+ZwHla%$MkGdDv z*ROcW3h6yOo*Pq;Zq1uz{+0fI1S4=mDH584;Xam zf{}6TK7}ZPpOuN|P$9N2e!JoFinQ%mYkIgd{Dzc&2zKcm?KQiq%*F%QSG0FD-t!lXeJ%YO^P(OE zq77n|qa9ADF$TEyoc&TwUdKAB{`KcdL_-s64O5O7Xd%w+CUy}>tVcfPZ> zM35Vr8-k6QcF^>^lc`oEVr{3a2BOh3L3LQ5kEf@HjK_ZHrhN++Rfy%66hV#mQ|5LD zx`StG>f+IlNjCKj*60V!UBEl;ujJP66R!J!>R7sbEyla*5lQ3_D4G-7leeTqR#x81 z&Wa>_wbaa2M-aom(qsS-Sx9i7Y^0@d6x2bpu*hJ<22t_Wo(Q5Oe(_I0L)Yj+`$xph{~f_X~X{{zL9yRWW5m zdkqC2%bQJ+Ak5JzgyN`ywKgCSvaK-Ae4QJJ=lo?fX{d+#L1B*PeIE7E_yz4zppRx{ zy|#xKo)6e@V1g;NL>c9IU2Ao!^Ol@kd&wDMYG0~~Ju6=+ zl_G|(%B~?tEeyzVp-}o;rom6$sx{PA|0FkVSw9oSC`;{N5=^;hQYTh3MIm@3sJ}sB zbf3+yvAt{n`rPEeK(kIM`7%8}O=o8?9|~eIwrEnvk6hkurji`aesS%5lo~uMj&pPK z10~dFn|u>2o-j~~)FTdhJ_OY=I!Pm(GD)Z^t0-?r^pZpD)2geh0-gfSPFbUHJIGkc zI6JT$U94it*wHC6ri?cl!$}KtjNr#8As-DyQauJ!8sr`KKIs3BMTMC?E?ZpIcJs7V zRFt3nA+b|kR3oiU#m=t!*eX+y{WsW2+P>}$(wG1Fr(DA5;8Np4$ZsE01i4${&cyO$ zz9Q8Sm$oRWM0_U{hZ)x;+J$EX&0Hda^w}Ui1AgUqd8pl~)edaMg~`iF)QKRRK|ieM zeMkD~yYGF$fuJ7}THYm_1!=Z%_u+o^8;EQDg^|f;mYW~{y^`1|y@fJgs}k~f2NP3c zjMtci6mcmOR0e@{+{kXT%||RkTtWbPzTe3*e;IRAr!McJFR?iVXK+4}i!=J8lAg-y z^1M7t5Ady|@ejOl!-61_3f}cERp}DziZQT-GL+8vH1l1`{=iE5ez;B==*Bmp#xc!d zMlf3SaYso_MKZ9}J6pUm?C%xnFBk%-dVr?WD+{O!XAJiXshLdq$RUi*dV3-Qx%5rZ zDR1-tsYJ4L0=@aNq;zV3^$b@{x^7ogg~cJD%Vou8W@Fd>5@)bf3fKH|v8Z)dPzMdc z!p7?D?JgE)*um)m;%F6lsZ8hmkLo2U6Lq&f)h)GU3Ebl}*l}C>z%sPItaS zuN`SjW0&otqjo3jBol8{_pg6DqvQV!{`XiD_7_?6R*wH?_V3`gp3mYZzhP@+dF&5y z_x8bsh($*`3t)}E<$8{IUB+eK1io3m>Oz-|@#!@w$uFzK?PqIKt@VxbZ`S^N-rmS{YwRO&BFulamm8?INhu217`3LyjiXP& zO;M0&jO&)kIhxVrVo|Xdj3yD#MHITiHPoa0kMQYBULF1^0is|TZj_0;&{0))ZchrH zWMISyj-o4d=i&Y1{+SB)-zBIjj_pmZ(DOwD6P%vFhMe`&I9l^KDe&cw6z4Dcpue)G zr+DI?DO97D%0Fdtn){0KApoWKu5*V~pBcAr zr>a9gZ3yli>~|H?KuTB)sdNmCch0LAO-#=1K8hm9Yu&C@x^~5rdaXivtB1bJ&kyt0 zj|VPDZ!hNVODliEU9+_GIunI>lv74Ow7*CmsDqP~&0yL!kl(2Og-j+Gk;4_l z6?$l$^`eY;nZ-du>ZkkZKj+`^Cjt#opBSY6O9^%M3I#G3(52D!NqB!+C3Z$A4rYe5C9iaTT#mq=jzm4VFqRcFs9qvPbf&FcV9`2Pxr z;*ZVx0ZD#DszxnbH}@4f=l=AecEf_r>%Pm0Dv3S?@iE#yA?h;w*0VN7l+A(T#FW@n zV)tFy|1Dol<1-&&J4?MXb3MOpY+WKK@I(G7yiEI2b_MtP$J?Fks^XAZd&;}c0O~~V zikEdRNK}csm#Zu%$Y3imc$Ka}!*vhVcF@A`mRs#Oxz!5B>u~rX4IKRw`9G+MxVFkEY2z26B$~-E-}eFIc5ST770Mlx@dGIiR4{U8I7hBnF*ZPv$MTqEj%B6e-by-L!rI6~L?REGWbu!dhgw zE4h_9+xBVi)M`63~$ecA!C!hN~*)K*EIyEV;`W`X!zJ?4{GgE&QFwwK{^b z(vvJ)xziXSI7F*Uh^cc#vzO1RzI;TgMX1+Y{B&8xGo-9W_^G9wjm_a*@1C*See0*H zt!8bijbo1UEbn!pjTJo~Z-NbAb;c}`VqT5gm-i8waD1~mkOZ%5N9D6LrB&^2rBDG2 z8s(Wj!Cz!@YzOp$^Un36HkTslLwcq=n6StPABz!H1WmvF&llE!W78l(h93MQS$}K? z9anJ)be#hUa1c71VwTO6XG<=NT_#h8gD^2gDYTDZnMHqtI|}aM9|E(B0{)oA!Hyhq zG|s5zwI)XD0|oGV51HFv5O!NHn^y>Zr=B0pxpvR5?)96_^^PV0gknVH!( zy8_S)nQvTmr)p+kgl)aNy!Ytd9RYnDvUG~pdH7G}qdG4x@!S(9ka%dAY=?n_ES}vX zhko^sfQC{)i0(5#L$(s)+llZm{03!i`U2!#-2a4P55*;G1P&Gte!>HNhOdDXDsWMh zVLXB_eCQlK8}eD`>b@1|?En#|MBp7YXcn!=MK$L?b~;ZjABE{g-6*M92$3j@t2XN} zM)w_x8#-=upoK4vxTa}*vHyJv90{Fpf#JPN-%?QR09-|Y$7`RD3AmFM-;>xzFB*9- zaOl5t)g4|Yu+eU1l4-JMt|M?rP&q8CrCtAu?RKfVU_#!9?6;%)vY|}8yob(i{UWO2 z_md$S=l^$K;T6-C$CnNxdFs;9JkWqTg-@U~(YdX~5uNjGo-|*O(8KmJXTmg+zyWY@ zp*!wMD}q>qo~6d0p2{JZzGADEk3(>d`p1?nl)e%`!+z~b6I*q=T_=j%#H+Gsrs zlx?KTJWj1&-)rYVJi{#%UjICTenjp!vfyjFKra z)W`n~ZEJ<6K%{;IGAl!&uhH@Yl1LKH-=pEJ94|S^JFZ%M^O+ir%xvk*#aX@R!^bnS zLXA>M1?#B26_4N$1TcvB%r0y3Q-ogGBWhLcL=UJl+vIWPV!SWF>J9)aGSMm|3D5E@ zJ7U();p0Y(c~2HPM}6qGj9JaoMRoGm*IW5UeUWE$RJ^4RoC?PFWaWAy&pOMg*eYG7 z&MpA$eS?&2sHZ1^_qTyajlW!~aI*Z0!VLnMgfyIQF7Wegcb4gl?OK%t(lIrK$A9-A z{|I5=lC|)rpPZhgpaS*35cUsSET7t0+4JV^~A0%KDw4MK)QT1JO7i0d0u)kMT}x_hj2J{$@Z1`wv%pz#8!DZkP|2>?*;m8jq>Nh-7+;*YT^TzV*8o<7!3beD)%r zw!ISURhrK(7fKm%cm!Jxjg4|FENAp{)cH~Dd0YGGzWYiQu;z)|Z`&}RzG>hkoOb(L zkhx|`6Hydth_fmu*Xl}oHry`KMj&@00;*KqCpIP-~!G03SxYBbaOfS^B*O$%}}=AIH$?86%%rSaImMurv0O>rcwRr!*ZuW*@)1H zXJaCZO|rNIHtuQQd!BXY(8BFjIfihCAd*spN*mjvH4T%&`xT5lQS#NR5lgQJ*h}G^ zCk6-g9bCEjwI8m={anUPU?Z0~uIqN>{CoE#9FNirwoj9+k{So2`lB%K zYn*0_SmuA&G&4_RSYpQAYkbi9wT?q)fA+hwY>gEaQ+k!bXF9NaBW7*llcn}R&0xD8 z6`r5bIDAK;_~hWz3Ch5;)HgY0DQw9~>voxo@n7$xM;BF2+gyS({oI(T(^~k5A3=lM z(w6n`l;YwI^|OL^Z;5Eeh=E6o+|yl3^_;-GeYlTToKz)Xi7u;5D$SvNC0M>yF?xVl zs+EFpV_>v5cZTYw5P;HceCv%bE(IiJDl6#Q`I5tikmVQ9x-ryF1O(Y|P_e@7zm~<+ zoq-#BOsEo7T;H$0?v^j2>XFbLPP48*0|x>x(Al(gN4IpOyL}TPe?&KkEv2w52}YX# zb{?}o(>NMPT&#dq&}h(~C#sB04SuM; zTS-GvS#D$jLwxcDV;S#7*Syk~nDFkH7KE@l)JJhO1G6R36n0Y4E5~7yux`)BV*Wy_ zX@x&w-%t7tQN{Up{dckx{lZZID zHu*ZVxTh*2zTJB_Ny_fuw{S;(LFDZuK4RE*II#!gPcRW@M{&f*e_Q?$HZ1!^+0QjV zj#L@EjTta09ozr$yOp7rY>1q_G>Ts4$7oHjY@z25tQ=Y5m>72CDo*4fj-TQy3?wBv zSveFq4X)N=6VL1{r0@Hk8rXhOkT%I~S;Sj3FF?Fxw_D8}#W|ZE=<+BJPjv)}$-Fk(AMkuiAaz z_)0JxA8cb_*HOTP4i2yBvFz+u9y2PI$J1lR#33@=`t*)a-bJTctC_uv#=*Y505O3r zQJ7|#t7pY2yZ|5$tOh}OP3sdn5HY&N)_#5jAngJvLmakGs-e!5DtG+Mto&(T2n`K6 zReNaW>p6~gPw|M5&h2@lblp&RLx_@=N2!w7*CZEOE zME!Ux9!It%5U#MzVt8{%qo5j3<>^Xs_@*kJqXo-yI|a2^K~t1u8&AW>l^cSoHsMq=*>KOzp&*gSGRmU$DK<ui?n`9IYae!1@LWt7pQeRbJ{$zyB`}ODYp0A zHDgyB$3U3Ek3V(K(8C>qrEIFVCv&7~l`g2JG5=AN<$nhZXozvXAu%SF4ZTOJ(vXbd#(Ir)$Yg zWcEq2RF!mDz9JtOPYL-ffsE{Z%OPj#S$0+5U2ziq2P8*8+mQDO-(EI?yWxd3Ihpo7Um2R_skW?F7ZZ8SQ!f=GXQ^NO2b!#iQwS3(@;oPyP5x z_OX5G(%vZcv-QRAvWxH$7`gvkaDh<2*?KHub_?PhwG!|nX-#qJ#5VjB8^gqKab_!@ z57a@?6)g5&@)VLLRMo;3bq7hvsxE*U?AW(^=WQ~H#bm9%DjDa}UWeia^rziuo~HSo zKe)Z;reiJdsyJW{UERpb-U%Pz(FK$8lSIF4c%OQt+lE3a@Uo7&S25Hx##I^zsZ~i$ zvOoasPLfZ6?}<%erEsr{OplM*@2}9D_`1rR<~_3@fs^;)I#i8RWj?xAKLmcb<^F25 zgfqmm<*JWda{@fV1`%OOayyyjd>~f!7(1QWc)OWm8lBqosL|pK5Ag`FVqh=mjEHM* zyRS0~W8u;!Tn&L>`D<{kNKCKn3F(M>iUJm!rq7YOLBz{iLR!uknBaWnc)Iq7kI_1+ z8B23GuEA09&-!ZTo&x;50<_*gSB(qnqjeitXUcCr#}9KfpY`y|{_L}52|=%`-@N%_ zq(jwiGv(6u!4np^pO2OprAE#Rh!&b|x_g!Moy#hY5Ai9BTOypoPsV@eV#1!V*A5}B z|Cv#-22_&v;W&Cf8{1n7L8K7MfDF$+K>ccI?? zX`aba_AO*9*MyKY_~`~Oxcfxo1jy^*YIyh!qoW2-)4xwp0-pz4R<2bbH6+oJuon(* zF0d{XQ!BMf<3v)&#K@Z@)dq!ftmK;BD%_NCcxMGIpojsH&swV2Vdg}?WEJ#e4to)d zKe_WuxY%I8D`CxN&%9(}$CtCr&?2h6HqF!C>O zYgz{ewyoN^mq7XpT~B|7?^iapyj-n}{zg0ETUagfMO0JjI>l>3Ti%h^TDG?_aoP=s zRtrUwgyVv}@QY{XX?pfflNcj%{;T- zOP6($`0hT#r5uT)w~});GVD?NS!Q!t_7*6n+J^gmg?M9zw+kcvnN>K|4j5f0FN*_c zxTTt3hIj<7GchSAHiu0KE=XwlC4W!+JOuS`C2Q?%H@yN-v8~JyS!j>eeQQQ{{ND)s z1pbWIpQJ^#7zFer<%0Fh2D~k5zjyGozP_XyFX$Kt_^bU9Mh&%lxwbusnZEo5@vbYn zI8-JZ3#9yAyZhzUKGmp-k4M1g@Wu_)!Kpu?>KpEJ4}-h`#MgCA8{Pht=M8Dx;sEyv&Y(CwHJ{CM{?PL2%v|I+~%lb z(a0rMnc)|0S&L<@{xtT^+)bhd-_szsU7hXjn`S#g3N8xKS{0HjUo6SvodteIxC8I) zaxSzlK2tOX3X;99={8Dy{JPS&ziGVAw)yENaWgI!IY7Q@=d^%T(>1iDaN}{u7BKxf z^vUtr=-yFVDqi$y9OF`C;;J1P`y=Q09CC39?iCJ|VOk#qZ&!@4M|baw#X9EE`7Nhf zTTH)?Kcs;WKy%vCWM|%g8#JW+QJU3IrPEj^l41S=HanIupQFyYpeY+d3@6=DQ`z+G%(jy zXZM~sv~dM2sAA~wPVN5fy9b7XA~vYCyDnYACyPG@T*-3`u; z9=YK!Dbz#BXh5!c?2IO398ozFGJ>$0kG}vPYv4B*un9>^5_D;~$Pf`>dPMU37BX+N znAjXf<*LG!Ze9hhvUj(v+MPy4UwQP<{WrfM@GO}cd^UE#2)4I_Ha(F@ zU|N#zU~vh09D170=0=I$8$jSPT0ldYUy)j+<5gxf^$G4f%Bno;s|AN3BYhTgpN3_^+kvuPVUCt zEBX)rgQ>7_&A~cVs{8`otB`pDl!f8N85b3L$9z$yv@IihGk@Ff;P(0t!>4ZXcD5vq0@yS zchBv0F1>#0>|FLG7@Ej^1Se^JJ}Byr`v>J(nBWh65mJ^j$r)8lPmilp=b{?!1&Wla zr!3sdRYKfxNBmWfcP+Rk#`;rbY{5%m_7M2?MfLeMYmJvBP~_k!U428WcXsn@Zs26# zQZlW$SNEqItKTAMqh)G8g4>Vd zrBQg=8;r}n%g$AY4|2}(8))sGWcISV*z6Lb*{tH(ta6ir%eojc0+PByX1O@IlUrE* zZ1v{?n`(SU&R2tHzP|5A705eS%Z{F?!pZ~Ua{t9`6e;Ql=!9e}( z4!vQr|C#yQ3;Qqa-D_>w>|p}sz_#OiT@wxLzccoITH8A^4%~#)@zOdaU}ELsY0Ksj zK@J{(yWZdV-x+oZ`}_OrmtCA`^5s)jQ6P(}LYLCWLM`@%u&!&A>pI58y}e&U+Zg|F zqtOyCoJvSY0wh`8z8WuU7-%?-KY(;4>_%iwbu2^H^(|5~wTh+*t~O@UXPQ(@w&kg| zc`GScOx39o&D`s7AjwaA4JVEs9a%eA`82Q2J&f)w4QF^*=pD(+ zOo26NdTex@)SXB2rv-9tS&Wt!^%Q(veJV%j%A9UzK_xmZ9-^*`q;5WMlD^K*0yQ41 z7D@vYO|leAnrbAxEIM9RN}imHZUr11rW4wB<%;ib7-j1=m^4k2I>`~%+j%)+k}A1a zs!imc4m(RP?o(ap_8;iUhk3lWK96pwDXYBM`7W}R56$wh35Q$Iht0>wSa-Mal6T5A z&eXEdV(F8@MxL}0Uzz9;x9t%nCG?^`-wFaMZZ#8Y?fA>F@1lsja{Y_>Sw<=Q`G+eM3XJ{3Ur7iyd2`D>dq7~2FrO2yY~ z)iwsFhg*YZE<|q*m__7Hoy4Nj002`o@+I*S5ce^xq#ebLvrr>A#vxHWJLrR{7u<~7 z;ExjjwC#5Uoykcwb3~2u{(_u^Wwz?JBJg+(#xmx#uEwt0@FJYyajWRZ+_CP}9**Z} zr5KCqE-$!7hz4LYIPQ51kKHkop0e6O_FP`V-3F=8wXj~!!u;A9$||Z7JtvLIop@<_ z!tnAOYOtm9fsh2U;V{=^7d~=kzMNm#q|abB$!slga|>Dy%052kTgOf!7FO9im~Jsg zLrc+JHP(MCiBNa#)(H_;$fIX}6mww}dr^cU4}E%16=Ys|=A%ZObjIk^$1dQbzym`A zUiJ{u!zVFCe>J(3&s<+tlRtt$Gr#jU5jr?>pE|c}&M~7@mFN!Q%6r(vBGU38u?+7r zzR3bFBXhtU_IssE>+hRwzh#}Iq-c)h$EUH$ENJ!Ycxh3|=F#yHeI*g^ull2-mh}Ua zMWEHP0>qy%_u@-h(cEt0i*MA&c1YuQ4fIQy5~l%Y@+M?WGfq?FWn$H^HrC@B-#*`s ziiiK`i#TsW*Bi9S`#~avORUYqYnF)4xYQuGm%aMZ#GA_=noDR+?e`+=EytrrK23HO zt&zHC`D<{Sc03h9MqgxPt+SYjZ1G@8&E3!N7QXe$li%bDx_zSOTa%-RL0b2jxu3eP z3Kg`URtYY!HBNnnOjB3fO+$W?FTo-EeSqb^>LgZ##0*NqJPQ=1ge++4@HL~k`MGe% z;ogF^kIU`~+>}$7$%lg7yHpXcNvsmSA-|^Y(9vT7uy86{R zL-BoH$ikl?W&g!Dqb1Ip?#xTmDqm8)fZl7HaY%9Mb8|U+1IO3yb`H2r_pUogD>Y22 z!^vnKMh}&wpQ=U>Fx2c^1`GRhMjN4z!uiw*66jL*Xa=1e#))ET#CQ}?!upl|D6b&x z&TVd3Pe=2#^Wq7koVl4QB8wfiz|_>q>~D*~52y_*Iovul(_0`odJ-NlyL}Suy@X4% z@*h>JFg3V*xvgOB8kKAJYz6r<1B1t`n0#ZFY_uyE)kdBhvxpeb4|elaFdd(PO-wwS z62@Ym_badkWCm~2&C?avV`ONE(qf8`!Crb{)i|KAm}OJVIK_xzbG46u10$3>f9Yh; z!G<%l1klau!3}TwF=DdbAz@#rgU%Pb{L$p*g3Y02#ud!f{Mb=y$RS^+)XuLlG&>rz z8Pje7L*iJ0 znUg%7te`6VIN#{OQs}Ap&V6*SchqN1IMnG0O*;5%EHB}OhqIO^}3}; zSI;W;;OOZn)rcj+dbaHd1d0Xytl||nW}Msi6X$n&TH+Afh%mwQKMb>)u>m$pgZ$5? zpT&mi3l_}*#w_q*7{1js{K1vRk_4rb%QdIQCP|L-f+~J%ji=@S*JG`dJi*c)K^>>P z1lqVI?n$L%AMRXNLWXP3EPkxLLvv z=luDYHK(m2zy080a8X_XDOEe($f#`hHoG14*PnXs9T{6TJC(RU?VoqQy<_}YESk^{ zM~=3}xAVKwO~TrnK8_U9&=C&-rgc(l#`=9oK6f$5anIlA%Jf-c0avs=d&MbuQ}FeC zgD&_%A1g3tfY#2K#vT60n`G&1s>|J(>+jH98mdTB$Jf(F$#6kz%ja>zX2b9QzL;)N z2h6q&eSIx~VY6%d7Rz5bIO1}s+?yd3E?HaR#}`DRy;;`TW`&bbP_R+SdwuORhl2vRT{QxOTT_~JSPmPGPDFq zR;iED?*=svtNxl4|21tZe?F1K(z#wQcJuJH0Z-KR2|?`3(y1K;$VkdS36Z}Kvv`jX zm5wN!@q!nr#Y#<$J*$dy zK5GMQQbcOPMhf0NrmY~OynVmc%|LT44bDwc@TE=_k)tq+Ua-T=2M?QGv0mTJ@6Dtf zz}8+L;*d49T@now5xM!1!%yCK=P@d(TkBn6yf|?k=izR zGIi;SReX3=vM+xHKt+Ma;0USz2n36*=^>%+qmX#Xqd37OQRVG%|3qy3|f=C}gVz!S6DLe4nytgeeU=;(Jp zS_AGz(P`|9=@e_1xA&OC(l3_#b~6gO>z~A?*Q| zxwiw>Sm0AaqG+7Km7Z8yaxy0Ce>k|iD^YTjMjhy8Y57}C`t;J$RGDFj?B9!Nlg5TI zDs2BujdI&YtbgG7q1gmkKy)8rLD7+yE|o2g)-U;a!BXGGbSF zxOM-)La`?Y%m<~-c+nAG{#rcB(wWXrP2g`{C!eWemS5iZBE>7~Qyi4{I@l6xHv-{| zMHtwq%jNC1U;^i=RmQPmCN6w69Ktg@EQ>tclcLL$3xeZS3`HM z!{%rUd6&0kl@FPfRMi=y!nLc5tch#`&@x-HV5W>VVhMb*=-n?~ ziX-(EMr~q(?HeZDu9Z9=mL9z5v$RAox0`Bx;iceTxWH6o>=SxrXJ>ydRhk_D76BW| zX(+STW5&H*_x=4IlT}R(?(HG!xdECHhn zQQ0;!(D4f_YoQ+FXy}RQik0Lw2NK-0RY{va*)?ZiUJ{P5^?%I&SU|{3sD0#Tf`Y9( zmk6Y!NEpnl(S; z|Jrl^+5Mj__r3EUi}Ano9I!~^-nP*Q+7|PF-)!GBMow7k=<5^hBchGMKt6+!##0es z;vq^rNB-Yg-FsG;i6iyZ)l3Wo7LCl*4KecPJ(v2AB>!p}!}=}Fq7|bV8R#=6%)fCX z!02kSAfpovGFH)Ikku!Z)xG%6e6=QP(JZCWonT12p&;L_qHVDP5*4+V=C{l;EQu@( zuLz70A{i)HZ7BIKKXjO1D2pg0KYoGyATu?%e1ohX_>Ly0JDuN8eEcO)^ylP!O8xr` zZQv|T0u%hbWm^E**f^9*lFr+~NJ9u+yM{(79e_8*3e=*%u1<;S$4oIW+7WB7Gw7^EXs-6Dmw0?GI zor&2fd}{XN|N5tHm6y$vGo}KTo9xb7*2i?RiQ`S^pbuU=R&XK@ehwk$t>kJXzh!Hq z+{sD7|95^eYZ3I<-&FL>vnc5&#F6|syuw6&R1SeQ4=?SZdGZh##T4frr{y>Z&RUd=JzuD zy(+d|i5MlACIiZ5;zfYr4N=w%O;UOxs^=GJH9@)8j>GKU>RX0%C*g6%->f;LdAUBW zL_+_2s3`aCPk{|io{UiUA3VR_!aR=FDl1Su|DC*YlE>ipBVOH`rt6S1|8Pp?H^fTe z;$6}_vXrn|OM)wr$guf2bE1qZhsH406iBP)IIVO3v_3pGS`pp0XJeF!mNH1PF;?B! z5>Ahw2rlg<)z4m)5Up!&uG@d)UXMq_*oW+Kdc7f}y%nfAA2@Y`OfNTKlmK?s!enN+ zvWxJOaf+EFN6bv!Euzp`T8oK@P<$$%sSXTn2Ekt6^E`qdRA9dGw#>Tgt`nPJZVz{l z1#ng2!N2!nXw=&Cs&_PonoQ=4bn2}O;$})T&XR9(!4I!0JzlJSN0y66`?nJpFTlukRP9 zEiOmdpW{ff6kP&p+}ZaLc-Qz-fRYg^o6ENAXTlx3Pj_>icF~NHkD*8$nq|Luyw&ma zs}b|sR>YqqauJ407^s&$hch4;RAQD-P%X1v%cH+SC2gCARCfo`7F(53F zS%@-YPL?@9-IMz*7i~2RIz#!*Nf~g?a92O`Nxky5NF#3_(1#TP>I^2K(kNIQHg?s# zmuhH8Zwq)eV^w6qYub<>1YdW;5sY+x7U>RS+iFH}N_TjT!rxR$u$yl?_3L5+%B17*Qx=q4dL@}Lp7uMilJB0Nwv$eZ;zb#Z=PPES>4O`H&DVH+%0NCM=X z{vAZ5UQ_-eEg|OWDW(pvNLeGoH_q%g%owj6fqvlV`xHk3Kw2QL4GQV-2n4)ICjIrO2GZUvf6p|41~U=hV}i@!S*lubgb$5sw*` zw?Yc|u#EB`_l#7|z@~WcziLO~`)T({tcj%n_TMGM5cp15L5eDUOFbWiR952N=Dn-> z&`;#5a4#F=(xsqO^@JZqvvc@oq>i-?*aQbsoUS|=qnKDmR%`COS`_7prvXcJYES{# z@5>unw^!=72A_^i(tpL-D$$#v8H~V4`qKJ>`fMlS>nWLwUfAD~F1Our5s6*r!dQh~T6TZRv#HOj%K@KvcRPkEr}J#tO=8&VfE@N|IknRpN6o0qb!TF%4@ zAnM6&G1LWV5>`HvWoEYk!?0@LJ*qsyc9)!h_VP+zX4*}u@)xL=!KXXT%;++XYP zc)1D*(3o^O0D=Baon-S*B@T&VlT)z0w%Q(t-%2cZ4*j`y1t5u{AouXr9cWO=dk1{* z_-!JA4#N5u`WXG+l7DcUhM*lrQ)`x^r=J&l6GOEe4IRv_sKnyuVqfAsCG(FcCWiNg zTkPS#O^o)imx(vfB9mF)c{bbI&ND*(qw#SHRUhq+O6v~|75PlKyq8y-Ze|y)tXL7I z(1n1oLmhO{@qd~}wo!KI-F7TfK@r$>#3Kk5+rm!mX8>YXS5wDQkZO~-d{!MV8l|)A zSt7nElAU7;P_3n(<2DZG>rW$-O96M2(ymGy&Lj*1hc~A^>Z|??!b$lZw=} zA}~AO)D^Z0vG`_a-bdH$H+RT&$)C|SE-|5tPpm3}ndHeX{9e2vm1H4n{w<|_e0E<1 zvz$)Moprr!T=`wS*-Z9FnPxb@5mySgzbqzkmk^0lP#qTl0T4a@h zTR$0;KZ{?KH043a?ZaAL}^3UYh zno$-oa9UoOkc!x>8z7Ok?>i@CSbAPcTIO2_E-QTbdRv?}BpY;vi@!B6?f!J&us6CIzd|o9bwIm;7uWbO?L`?E57%PeSvJh84{|B3zNl@K+(#xg|mtOIL(< zq5W4YU#3xH!a>;B*jUk8v5P5U=f1R*TL$RL>x+x>FlWsTF{jIIe+d>Co0}(565yel zuWH-7m8FACa|)^KwyyjL$#x&(Nd#_B_wMGSiP*PNU?54=(`u8CS=Gsx!lVl*6L=0S zh4b*qH8n-r;AFA-wm{o@7RTT?U!Y~fk9+@4u{5R*$bFvOQCiHWdpey-^9}xB2P>fA z9pz17pQLCu;JH8(bvVJCwE42Arn`k>PE}k9TWm;fJ72$DY~8Y3wrr#?RPUGfknnfs z{4zjQUmuVlSC5gw^+&SeCvAyHt-lCO-CAxoebQp9Xk}ZT!_MNRf~Gw#<~qnrfo2Av z?F;o3Ly7NM+NN`-o%wRE)A{BDqODgn%?dk2rOb0YTYeA)7=&_q3*zh%s-NZ z?tI#{abTG0!cb!s8nWfb)S?Y81^bU(Qw_*g04|WWsAwvEw@g`{UY=Ik`lC2;j1TQMuZ z_BU2KQEOoJA16=TcE!Qd&2&} z!fxPooYbh#{H95$aq4#tf-A3gMqb`kf@R>NYZ5Mruc_fc;^wcD^7ius(p}4f#o5DJ zvEvq(g&COx<5$2G|WthX`pa%S8obS8AWJl@YZnKu1v)1bii$`~uAk__-h zU{nbSFV?WZ%&Mp5>_7FCDv(1a@&?g?{LdO|2rOTufeG0p!9iC8sLxok?Hj}knX=)4 zLhwuCX$f9VJAAoPRofA9XH+6})xJ~|C@+9V87`So!m)0c_C8WsaD{O zC^KRm?d)OY;Q3J8kmWb-kGI&C-I0Ehe`k-AQb3qH2hFuMNc7V%fZmNxqYkB#b3#oM z!?|d7m3e#m@t!I6?+Kw4OM<@(I9$|}g6m%F{wL{4oJh!g6@X;{hl(cILcqN|jMfSf zr9#2GDm4s+ESb%(ivN>>6m7`h&lp@c8GA2Zh!#nt@%{~QrxmqE#}`YcCCDW%6SeVr zRnb~*_~GC6P3ngY? zZRZd`ppvT#exYcv6@m!$PRhdwrIWKoVj~!)+uQ%e4cA7Ct~#6q`4yc-6zDFJ?zQh^ zs#-D6#Nq);r;G$Z3xOGE$0gUy$6i<$r@w(4vp#U7xlCQZ`nwp&II$>^OLWtmsma<7 zQ>>-s^f|oKf%lnzIkpjzRS)yZ1!AqN=pT%I8o&Ma5!Z<)&WOo&WK=p9P*=6TuoLl8 z6zVC+s4WH!S zemH&{9hev+($Wl_j0C>Hmm1X0ChItSHb#xy#~OweT@CoJGSCS3Ur!6l{R9(OjabDJ zI81N_*#D%45*+2uQ{y*~83O-jN%LdF z75V!b=QRpTjS}}$JaPFfk;*$ilU@{4I90t2MpZ(7r^{X;({ZBReJ??Ao< zvs*przt`d27%ef^U)b7V!owTDMb$djQ`eYhH#-pCvO>C-g{*4uMPfytK<*&HXfcwA zy-b5*i~0R=zjdAkt*QO=`n1M9_iUX>q>)GIfw4yb&NEwx_JCZu7A;9mxmH^#- zCp~*4(xxg>QZS?Vn0R=r3RbDoW+o=rZ23(8qc4wAJ41Ux+#*}Nt{mVtT-&?INa5h; z$qjZY<-oqHcz@HQ%fK9q;B6#LiUU>pa=#H%xNFV4`O;DlR6sl|tK z+10)Y!fUmG=ibjTQn!$GUWt~HKi*hA&jCfQe|Sk^6>M1R=`}dJ(|MAQQd~z&3#QM8 z4@~BfvRB}%6)ra@Ur=x6fAk#RzX{Xe%a7z{&vjEl-u;o{)7BoTe=470o19~2jEMXf z9X)IjeK6<9mV;8Ocs-^;)rz~-ZR*r-c%}NN5_;4Oh0@bw$xkaZ#5sd2;z;T+4=7YA z6(Y%Xo4$4Qf*mP^1Ryp%us^UvN=MXiFaP~+Etr@0qZ$bU{QszJ^y-ABk;pCQ<5&AD ziHXcG%<$-$+6wk(k4Rj8rbWW~YFZ}mT{m}1Zl_mb4 zbcyWuKzEsaBH?~Ub%@~GUP%0pqw<><&xoTu0~M)?_J}B?^)NvyE2}+ypEOYW!WW&8 zTn#qmW(^{9zn0mfOhC!Y*~ekob6}5F18-TGd)S!AzV~h0R*Qe@_}LidFk{Nkug8L= zaCkm!2c!oiGDoQIh8!T0-eb8S(T__&rMtzq3FVuYwYbf**#6=@o*tuiZjI9NaK;QTTu7VBdUU8-pmw#wmP*{Or7BFnIWwscTw`@iH*|Ig{D$&VA6lX|uS=0>Bpv zJLEg)ZoX8oEamv77o~^;*-`$>{ZQ`7PPbzxQ5?|YTLg4yBmcx&89JZ47$2av+-}k{ zSg9*c!Tp`Dok@enPcAV9o=P60NF(|kbo9x)3g7xdv*9_RGk7&Lc31i-^d>$5{Np!LJRgl3JAMb% za=8nPwc3%4;`7LkJVKN*cg57`3QLNP?XQ14(*Jd~x_q_t;`fR^x&ST6oZVAH4}AP} zl9r5-JhP$;JG`pJ1Msc;F5X-y45OILbiq_aR`dlQftB3AoA#ovd=l}EQ%%eJQ(x9) zFE2Vc_3WGT3Kg8%gv|y|iY0Tmc{#+@ijx8LFo4SYWc_*dC3dPFb&--iF&nVt!c*$k z8?Su)Bs_X?Vq

Cpt}Nwl-z6!Ma5V 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

mQC}VKu+7F>Yf$5kX)xe7RcM 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%0A=aq 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

    w^*@oxBDMMj0#l7z9L?-RgwoeU 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 LFIp*V81TOU7pe6i literal 0 HcmV?d00001 diff --git a/docs/sources/project/set-up-git.md b/docs/sources/project/set-up-git.md index 1c8b511d0..d67ff817c 100644 --- a/docs/sources/project/set-up-git.md +++ b/docs/sources/project/set-up-git.md @@ -46,9 +46,12 @@ target="_blank">docker/docker repository. that instead. You'll need to convert what you see in the guide to what is appropriate to your tool. -5. Open a terminal window on your local host and change to your home directory. In Windows, you'll work in your Boot2Docker window instead of Powershell or cmd. +5. Open a terminal window on your local host and change to your home directory. $ cd ~ + + In Windows, you'll work in your Boot2Docker window instead of Powershell or + a `cmd` window. 6. Create a `repos` directory. diff --git a/docs/sources/project/software-req-win.md b/docs/sources/project/software-req-win.md new file mode 100644 index 000000000..a7f137892 --- /dev/null +++ b/docs/sources/project/software-req-win.md @@ -0,0 +1,258 @@ +page_title: Set up for development on Windows +page_description: How to set up a server to test Docker Windows client +page_keywords: development, inception, container, image Dockerfile, dependencies, Go, artifacts, windows + + +# Get the required software for Windows + +This page explains how to get the software you need to use a a Windows Server +2012 or Windows 8 machine for Docker development. Before you begin contributing +you must have: + +- a GitHub account +- Git for Windows (msysGit) +- TDM-GCC, a compiler suite for Windows +- MinGW (tar and xz) +- Go language + +> **Note**: This installation prcedure refers to the `C:\` drive. If you system's main drive +is `D:\` you'll need to substitute that in where appropriate in these +instructions. + +### Get a GitHub account + +To contribute to the Docker project, you will need a GitHub account. A free account is +fine. All the Docker project repositories are public and visible to everyone. + +You should also have some experience using both the GitHub application and `git` +on the command line. + +## Install Git for Windows + +Git for Windows includes several tools including msysGit, which is a build +environment. The environment contains the tools you need for development such as +Git and a Git Bash shell. + +1. Browse to the [Git for Windows](https://msysgit.github.io/) download page. + +2. Click **Download**. + + Windows prompts you to save the file to your machine. + +3. Run the saved file. + + The system displays the **Git Setup** wizard. + +4. Click the **Next** button to move through the wizard and accept all the defaults. + +5. Click **Finish** when you are done. + +## Installing TDM-GCC + +TDM-GCC is a compiler suite for Windows. You'll use this suite to compile the +Docker Go code as you develop. + +1. Browse to + [tdm-gcc download page](http://tdm-gcc.tdragon.net/download). + +2. Click on the lastest 64-bit version of the package. + + Windows prompts you to save the file to your machine + +3. Set up the suite by running the downloaded file. + + The system opens the **TDM-GCC Setup** wizard. + +4. Click **Create**. + +5. Click the **Next** button to move through the wizard and accept all the defaults. + +6. Click **Finish** when you are done. + + +## Installing MinGW (tar and xz) + +MinGW is a minimalist port of the GNU Compiler Collection (GCC). In this +procedure, you first download and install the MinGW installation manager. Then, +you use the manager to install the `tar` and `xz` tools from the collection. + +1. Browse to MinGW + [SourceForge](http://sourceforge.net/projects/mingw/). + +2. Click **Download**. + + Windows prompts you to save the file to your machine + +3. Run the downloaded file. + + The system opens the **MinGW Installation Manager Setup Tool** + +4. Choose **Install** install the MinGW Installation Manager. + +5. Press **Continue**. + + The system installs and then opens the MinGW Installation Manager. + +6. Press **Continue** after the install completes to open the manager. + +7. Select **All Packages > MSYS Base System** from the left hand menu. + + The system displays the available packages. + +8. Click on the the **msys-tar bin** package and choose **Mark for Installation**. + +9. Click on the **msys-xz bin** package and choose **Mark for Installation**. + +10. Select **Installation > Apply Changes**, to install the selected packages. + + The system displays the **Schedule of Pending Actions Dialog**. + + ![windows-mingw](/project/images/windows-mingw.png) + +11. Press **Apply** + + MingGW installs the packages for you. + +12. Close the dialog and the MinGW Installation Manager. + + +## Set up your environment variables + +You'll need to add the compiler to your `Path` environment variable. + +1. Open the **Control Panel**. + +2. Choose **System and Security > System**. + +3. Click the **Advanced system settings** link in the sidebar. + + The system opens the **System Properties** dialog. + +3. Select the **Advanced** tab. + +4. Click **Environment Variables**. + + The system opens the **Environment Variables dialog** dialog. + +5. Locate the **System variables** area and scroll to the **Path** + variable. + + ![windows-mingw](/project/images/path_variable.png) + +6. Click **Edit** to edit the variable (you can also double-click it). + + The system opens the **Edit System Variable** dialog. + +7. Make sure the `Path` includes `C:\TDM-GCC64\bin` + + ![include gcc](/project/images/include_gcc.png) + + If you don't see `C:\TDM-GCC64\bin`, add it. + +8. Press **OK** to close this dialog. + +9. Press **OK** twice to close out of the remaining dialogs. + +## Install Go and cross-compile it + +In this section, you install the Go language. Then, you build the source so that it can cross-compile for `linux/amd64` architectures. + +1. Open [Go Language download](http://golang.org/dl/) page in your browser. + +2. Locate and click the latest `.msi` installer. + + The system prompts you to save the file. + +3. Run the installer. + + The system opens the **Go Programming Langauge Setup** dialog. + +4. Select all the defaults to install. + +5. Press **Finish** to close the installation dialog. + +6. Start a command prompt. + +7. Change to the Go `src` directory. + + cd c:\Go\src + +8. Set the following Go variables + + c:\Go\src> set GOOS=linux + c:\Go\src> set GOARCH=amd64 + +9. Compile the source. + + c:\Go\src> make.bat + + Compiling the source also adds a number of variables to your Windows environment. + +## Get the Docker repository + +In this step, you start a Git `bash` terminal and get the Docker source code from +Github. + +1. Locate the **Git Bash** program and start it. + + Recall that **Git Bash** came with the Git for Windows installation. **Git + Bash** just as it sounds allows you to run a Bash terminal on Windows. + + ![Git Bash](/project/images/git_bash.png) + +2. Change to the root directory. + + $ cd /c/ + +3. Make a `gopath` directory. + + $ mkdir gopath + +4. Go get the `docker/docker` repository. + + $ go.exe get github.com/docker/docker package github.com/docker/docker + imports github.com/docker/docker + imports github.com/docker/docker: no buildable Go source files in C:\gopath\src\github.com\docker\docker + + In the next steps, you create environment variables for you Go paths. + +5. Open the **Control Panel** on your system. + +6. Choose **System and Security > System**. + +7. Click the **Advanced system settings** link in the sidebar. + + The system opens the **System Properties** dialog. + +8. Select the **Advanced** tab. + +9. Click **Environment Variables**. + + The system opens the **Environment Variables dialog** dialog. + +10. Locate the **System variables** area and scroll to the **Path** + variable. + +11. Click **New**. + + Now you are going to create some new variables. These paths you'll create in the next procedure; but you can set them now. + +12. Enter `GOPATH` for the **Variable Name**. + +13. For the **Variable Value** enter the following: + + C:\gopath;C:\gopath\src\github.com\docker\docker\vendor + + +14. Press **OK** to close this dialog. + + The system adds `GOPATH` to the list of **System Variables**. + +15. Press **OK** twice to close out of the remaining dialogs. + + +## Where to go next + +In the next section, you'll [learn how to set up and configure Git for +contributing to Docker](/project/set-up-git/). \ No newline at end of file diff --git a/docs/sources/project/software-required.md b/docs/sources/project/software-required.md index 08a4243ae..15b9a6935 100644 --- a/docs/sources/project/software-required.md +++ b/docs/sources/project/software-required.md @@ -2,9 +2,10 @@ page_title: Get the required software page_description: Describes the software required to contribute to Docker page_keywords: GitHub account, repository, Docker, Git, Go, make, -# Get the required software +# Get the required software for Linux or OS X -Before you begin contributing you must have: +This page explains how to get the software you need to use a Linux or OS X +machine for Docker development. Before you begin contributing you must have: * a GitHub account * `git` diff --git a/docs/sources/project/test-and-docs.md b/docs/sources/project/test-and-docs.md index f11049047..23b6b0914 100644 --- a/docs/sources/project/test-and-docs.md +++ b/docs/sources/project/test-and-docs.md @@ -230,6 +230,46 @@ with new memory settings. 6. Restart your container and try your test again. +## Testing just the Windows client + +This explains how to test the Windows client on a Windows server set up as a +development environment. You'll use the **Git Bash** came with the Git for +Windows installation. **Git Bash** just as it sounds allows you to run a Bash +terminal on Windows. + +1. If you don't have one, start a Git Bash terminal. + + ![Git Bash](/project/images/git_bash.png) + +2. Change to the `docker` source directory. + + $ cd /c/gopath/src/github.com/docker/docker + +3. Set `DOCKER_CLIENTONLY` as follows: + + $ export DOCKER_CLIENTONLY=1 + + This ensures you are building only the client binary instead of both the + binary and the daemon. + +4. Set `DOCKER_TEST_HOST` to the `tcp://IP_ADDRESS:2376` value; substitute your +machine's actual IP address, for example: + + $ export DOCKER_TEST_HOST=tcp://263.124.23.200:2376 + +5. Make the binary and the test: + + $ hack/make.sh binary test-integration-cli + + Many tests are skipped on Windows for various reasons. You see which tests + were skipped by re-running the make and passing in the + `TESTFLAGS='-test.v'` value. + + +You can now choose to make changes to the Docker source or the tests. If you +make any changes just run these commands again. + + ## Build and test the documentation The Docker documentation source files are under `docs/sources`. The content is From 8f52eb7b827d658d6974056460afd722a5cb040f Mon Sep 17 00:00:00 2001 From: Peter Salvatore Date: Tue, 21 Apr 2015 23:36:27 -0400 Subject: [PATCH 675/999] Rewrite Official Repositories page. The existing page is focused on listing a set of requirements for proposing a new repository. This information has become outdated and is duplicated in the `docker-library/official-images` and `docker-library/docs` GitHub repositories. This PR rewrites the Official Repositories page to describe what they actually are, and defers to GitHub/IRC for the subset of users that are interested in contributing. I also removed the requirement to contact partners@docker.com and made it optional to reduce the barrier to entry. Signed-off-by: Peter Salvatore --- docs/mkdocs.yml | 2 +- docs/sources/articles/baseimages.md | 2 +- .../articles/dockerfile_best-practices.md | 4 +- docs/sources/docker-hub/official_repos.md | 245 ++++++------------ docs/sources/docker-hub/repos.md | 12 +- docs/sources/terms/repository.md | 2 +- docs/sources/userguide/dockerimages.md | 10 +- docs/sources/userguide/dockerrepos.md | 12 +- 8 files changed, 103 insertions(+), 186 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index e425175f6..a83f281e5 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -76,7 +76,7 @@ pages: - ['docker-hub/accounts.md', 'Docker Hub', 'Accounts'] - ['docker-hub/repos.md', 'Docker Hub', 'Repositories'] - ['docker-hub/builds.md', 'Docker Hub', 'Automated Builds'] -- ['docker-hub/official_repos.md', 'Docker Hub', 'Official repo guidelines'] +- ['docker-hub/official_repos.md', 'Docker Hub', 'Official Repositories'] # Docker Hub Enterprise: - ['docker-hub-enterprise/index.md', 'Docker Hub Enterprise', 'Overview' ] diff --git a/docs/sources/articles/baseimages.md b/docs/sources/articles/baseimages.md index a54f5307a..a1a7665b7 100644 --- a/docs/sources/articles/baseimages.md +++ b/docs/sources/articles/baseimages.md @@ -65,4 +65,4 @@ There are lots more resources available to help you write your 'Dockerfile`. * There's a [complete guide to all the instructions](/reference/builder/) available for use in a `Dockerfile` in the reference section. * To help you write a clear, readable, maintainable `Dockerfile`, we've also written a [`Dockerfile` Best Practices guide](/articles/dockerfile_best-practices). -* If you're working on an Official Repo, be sure to check out the [Official Repo Guidelines](/docker-hub/official_repos/). +* If your goal is to create a new Official Repository, be sure to read up on Docker's [Official Repositories](/docker-hub/official_repos/). diff --git a/docs/sources/articles/dockerfile_best-practices.md b/docs/sources/articles/dockerfile_best-practices.md index 425eb8658..04e77fd62 100644 --- a/docs/sources/articles/dockerfile_best-practices.md +++ b/docs/sources/articles/dockerfile_best-practices.md @@ -419,9 +419,9 @@ fail catastrophically if the new build's context is missing the resource being added. Adding a separate tag, as recommended above, will help mitigate this by allowing the `Dockerfile` author to make a choice. -## Examples for official repositories +## Examples for Official Repositories -These Official Repos have exemplary `Dockerfile`s: +These Official Repositories have exemplary `Dockerfile`s: * [Go](https://registry.hub.docker.com/_/golang/) * [Perl](https://registry.hub.docker.com/_/perl/) diff --git a/docs/sources/docker-hub/official_repos.md b/docs/sources/docker-hub/official_repos.md index a101d88c1..eb73b4bc2 100644 --- a/docs/sources/docker-hub/official_repos.md +++ b/docs/sources/docker-hub/official_repos.md @@ -1,189 +1,106 @@ -page_title: Guidelines for official repositories on Docker Hub +page_title: Official Repositories on Docker Hub page_description: Guidelines for Official Repositories on Docker Hub page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, official, image, documentation -# Guidelines for creating and documenting official repositories +# Official Repositories on Docker Hub -## Introduction +The Docker [Official Repositories](http://registry.hub.docker.com/official) are +a curated set of Docker repositories that are promoted on Docker Hub and +supported by Docker, Inc. They are designed to: -You’ve been given the job of creating an image for an Official Repository -hosted on [Docker Hub Registry](https://registry.hub.docker.com/). These are -our guidelines for getting that task done. Even if you’re not -planning to create an Official Repo, you can think of these guidelines as best -practices for image creation generally. +* Provide essential base OS repositories (for example, + [`ubuntu`](https://registry.hub.docker.com/_/ubuntu/), + [`centos`](https://registry.hub.docker.com/_/centos/)) that serve as the + starting point for the majority of users. -This document consists of two major sections: +* Provide drop-in solutions for popular programming language runtimes, data + stores, and other services, similar to what a Platform-as-a-Service (PAAS) + would offer. -* A list of expected files, resources and supporting items for your image, -along with best practices for creating those items -* Examples embodying those practices +* Exemplify [`Dockerfile` best practices](/articles/dockerfile_best-practices) + and provide clear documentation to serve as a reference for other `Dockerfile` + authors. -## Expected files and resources +* Ensure that security updates are applied in a timely manner. This is + particularly important as many Official Repositories are some of the most + popular on Docker Hub. -### A Git repository +* Provide a channel for software vendors to redistribute up-to-date and + supported versions of their products. Organization accounts on Docker Hub can + also serve this purpose, without the careful review or restrictions on what + can be published. -Your image needs to live in a Git repository, preferably on GitHub. (If you’d -like to use a different provider, please [contact us](mailto:feedback@docker.com) -directly.) Docker **strongly** recommends that this repo be publicly -accessible. +Docker, Inc. sponsors a dedicated team that is responsible for reviewing and +publishing all Official Repositories content. This team works in collaboration +with upstream software maintainers, security experts, and the broader Docker +community. -If the repo is private or has otherwise limited access, you must provide a -means of at least “read-only” access for both general users and for the -docker-library maintainers, who need access for review and building purposes. +While it is preferrable to have upstream software authors maintaining their +corresponding Official Repositories, this is not a strict requirement. Creating +and maintaining images for Official Repositories is a public process. It takes +place openly on GitHub where participation is encouraged. Anyone can provide +feedback, contribute code, suggest process changes, or even propose a new +Official Repository. -### A Dockerfile +## Should I use Official Repositories? -Complete information on `Dockerfile`s can be found in the [Reference section](https://docs.docker.com/reference/builder/). -We also have a page discussing [best practices for writing `Dockerfile`s](/articles/dockerfile_best-practices). -Your `Dockerfile` should adhere to the following: +New Docker users are encouraged to use the Official Repositories in their +projects. These repositories have clear documentation, promote best practices, +and are designed for the most common use cases. Advanced users are encouraged to +review the Official Repositories as part of their `Dockerfile` learning process. -* It must be written either by using `FROM scratch` or be based on another, -established Official Image. -* It must follow `Dockerfile` best practices. These are discussed on the -[best practices page](/articles/dockerfile_best-practices). In addition, -Docker engineer Michael Crosby has some good tips for `Dockerfiles` in -this [blog post](http://crosbymichael.com/dockerfile-best-practices-take-2.html). +A common rationale for diverging from Official Repositories is to optimize for +image size. For instance, many of the programming language stack images contain +a complete build toolchain to support installation of modules that depend on +optimized code. An advanced user could build a custom image with just the +necessary pre-compiled libraries to save space. -While [`ONBUILD` triggers](https://docs.docker.com/reference/builder/#onbuild) -are not required, if you choose to use them you should: +A number of language stacks such as +[`python`](https://registry.hub.docker.com/_/python/) and +[`ruby`](https://registry.hub.docker.com/_/ruby/) have `-slim` tag variants +designed to fill the need for optimization. Even when these "slim" variants are +insufficient, it is still recommended to inherit from an Official Repository +base OS image to leverage the ongoing maintenance work, rather than duplicating +these efforts. -* Build both `ONBUILD` and non-`ONBUILD` images, with the `ONBUILD` image -built `FROM` the non-`ONBUILD` image. -* The `ONBUILD` image should be specifically tagged, for example, `ruby: -latest`and `ruby:onbuild`, or `ruby:2` and `ruby:2-onbuild` +## How can I get involved? -### A short description +All Official Repositories contain a **User Feedback** section in their +documentation which covers the details for that specific repository. In most +cases, the GitHub repository which contains the Dockerfiles for an Official +Repository also has an active issue tracker. General feedback and support +questions should be directed to `#docker-library` on Freenode IRC. -Include a brief description of your image (in plaintext). Only one description -is required; you don’t need additional descriptions for each tag. The file -should also: +## How do I create a new Official Repository? -* Be named `README-short.txt` -* Reside in the repo for the “latest” tag -* Not exceed 100 characters +From a high level, an Official Repository starts out as a proposal in the form +of a set of GitHub pull requests. You'll find detailed and objective proposal +requirements in the following GitHub repositories: -### A logo +* [docker-library/official-images](https://github.com/docker-library/official-images) -Include a logo of your company or the product (png format preferred). Only one -logo is required; you don’t need additional logo files for each tag. The logo -file should have the following characteristics: +* [docker-library/docs](https://github.com/docker-library/docs) -* Be named `logo.png` -* Should reside in the repo for the “latest” tag -* Should fit inside a 200px square, maximized in one dimension (preferably the -width) -* Square or wide (landscape) is preferred over tall (portrait), but exceptions -can be made based on the logo needed +The Official Repositories team, with help from community contributors, formally +review each proposal and provide feedback to the author. This initial review +process may require a bit of back and forth before the proposal is accepted. -### A long description +There are also subjective considerations during the review process. These +subjective concerns boil down to the basic question: "is this image generally +useful?" For example, the [`python`](https://registry.hub.docker.com/_/python/) +Official Repository is "generally useful" to the large Python developer +community, whereas an obscure text adventure game written in Python last week is +not. -Include a comprehensive description of your image (in Markdown format, GitHub -flavor preferred). Only one description is required; you don’t need additional -descriptions for each tag. The file should also: +When a new proposal is accepted, the author becomes responsibile for keeping +their images up-to-date and responding to user feedback. The Official +Repositories team becomes responsibile for publishing the images and +documentation on Docker Hub. Updates to the Official Repository follow the same +pull request process, though with less review. The Official Repositories team +ultimately acts as a gatekeeper for all changes, which helps mitigate the risk +of quality and security issues from being introduced. -* Be named `README.md` -* Reside in the repo for the “latest” tag -* Be no longer than absolutely necessary, while still addressing all the -content requirements - -In terms of content, the long description must include the following sections: - -* Overview & links -* How-to/usage -* Issues & contributions - -#### Overview and links - -This section should provide: - -* an overview of the software contained in the image, similar to the -introduction in a Wikipedia entry - -* a selection of links to outside resources that help to describe the software - -* a *mandatory* link to the `Dockerfile` - -#### How-to/usage - -A section that describes how to run and use the image, including common use -cases and example `Dockerfile`s (if applicable). Try to provide clear, step-by- -step instructions wherever possible. - -##### Issues and contributions - -In this section, point users to any resources that can help them contribute to -the project. Include contribution guidelines and any specific instructions -related to your development practices. Include a link to -[Docker’s resources for contributors](https://docs.docker.com/contributing/contributing/). -Be sure to include contact info, handles, etc. for official maintainers. - -Also include information letting users know where they can go for help and how -they can file issues with the repo. Point them to any specific IRC channels, -issue trackers, contacts, additional “how-to” information or other resources. - -### License - -Include a file, `LICENSE`, of any applicable license. Docker recommends using -the license of the software contained in the image, provided it allows Docker, -Inc. to legally build and distribute the image. Otherwise, Docker recommends -adopting the [Expat license](http://directory.fsf.org/wiki/License:Expat) -(a.k.a., the MIT or X11 license). - -## Examples - -Below are sample short and long description files for an imaginary image -containing Ruby on Rails. - -### Short description - -`README-short.txt` - -`Ruby on Rails is an open-source application framework written in Ruby. It emphasizes best practices such as convention over configuration, active record pattern, and the model-view-controller pattern.` - -### Long description - -`README.md` - -```markdown -# What is Ruby on Rails - -Ruby on Rails, often simply referred to as Rails, is an open source web application framework which runs via the Ruby programming language. It is a full-stack framework: it allows creating pages and applications that gather information from the web server, talk to or query the database, and render templates out of the box. As a result, Rails features a routing system that is independent of the web server. - -> [wikipedia.org/wiki/Ruby_on_Rails](https://en.wikipedia.org/wiki/Ruby_on_Rails) - -# How to use this image - -## Create a `Dockerfile` in your rails app project - - FROM rails:onbuild - -Put this file in the root of your app, next to the `Gemfile`. - -This image includes multiple `ONBUILD` triggers so that should be all that you need for most applications. The build will `ADD . /usr/src/app`, `RUN bundle install`, `EXPOSE 3000`, and set the default command to `rails server`. - -Then build and run the Docker image. - - docker build -t my-rails-app . - docker run --name some-rails-app -d my-rails-app - -Test it by visiting `http://container-ip:3000` in a browser. On the other hand, if you need access outside the host on port 8080: - - docker run --name some-rails-app -p 8080:3000 -d my-rails-app - -Then go to `http://localhost:8080` or `http://host-ip:8080` in a browser. -``` - -For more examples, take a look at these repos: - -* [Go](https://github.com/docker-library/golang) -* [PostgreSQL](https://github.com/docker-library/postgres) -* [Buildpack-deps](https://github.com/docker-library/buildpack-deps) -* ["Hello World" minimal container](https://github.com/docker-library/hello-world) -* [Node](https://github.com/docker-library/node) - -## Submit your repo - -Once you've checked off everything in these guidelines, and are confident your -image is ready for primetime, please contact us at -[partners@docker.com](mailto:partners@docker.com) to have your project -considered for the Official Repos program. +> **Note**: If you are interested in proposing an Official Repository, but would +> like to discuss it with Docker, Inc. privately first, please send your +> inquiries to partners@docker.com. There is no fast-track or pay-for-status +> option. diff --git a/docs/sources/docker-hub/repos.md b/docs/sources/docker-hub/repos.md index 0a2fa6550..a48040fb5 100644 --- a/docs/sources/docker-hub/repos.md +++ b/docs/sources/docker-hub/repos.md @@ -51,10 +51,10 @@ private to public. You can also collaborate on Docker Hub with organizations and groups. You can read more about that [here](accounts/). -## Official repositories +## Official Repositories -The Docker Hub contains a number of [official -repositories](http://registry.hub.docker.com/official). These are +The Docker Hub contains a number of [Official +Repositories](http://registry.hub.docker.com/official). These are certified repositories from vendors and contributors to Docker. They contain Docker images from vendors like Canonical, Oracle, and Red Hat that you can use to build applications and services. @@ -63,9 +63,9 @@ If you use Official Repositories you know you're using a supported, optimized and up-to-date image to power your applications. > **Note:** -> If you would like to contribute an official repository for your -> organization, product or team you can see more information -> [here](https://github.com/docker/stackbrew). +> If you would like to contribute an Official Repository for your +> organization, see [Official Repositories on Docker +> Hub](/docker-hub/official_repos) for more information. ## Private repositories diff --git a/docs/sources/terms/repository.md b/docs/sources/terms/repository.md index 84963b4bf..4b8579924 100644 --- a/docs/sources/terms/repository.md +++ b/docs/sources/terms/repository.md @@ -29,7 +29,7 @@ A Fully Qualified Image Name (FQIN) can be made up of 3 parts: If you create a new repository which you want to share, you will need to set at least the `user_name`, as the `default` blank `user_name` prefix is -reserved for official Docker images. +reserved for [Official Repositories](/docker-hub/official_repos). For more information see [*Working with Repositories*](/userguide/dockerrepos/#working-with-the-repository) diff --git a/docs/sources/userguide/dockerimages.md b/docs/sources/userguide/dockerimages.md index 621946654..c29b01032 100644 --- a/docs/sources/userguide/dockerimages.md +++ b/docs/sources/userguide/dockerimages.md @@ -131,11 +131,11 @@ term `sinatra`. We can see we've returned a lot of images that use the term `sinatra`. We've returned a list of image names, descriptions, Stars (which measure the social popularity of images - if a user likes an image then they can "star" it), and -the Official and Automated build statuses. Official repositories are built and -maintained by the [Stackbrew](https://github.com/docker/stackbrew) project, -and Automated repositories are [Automated Builds]( -/userguide/dockerrepos/#automated-builds) that allow you to validate the source -and content of an image. +the Official and Automated build statuses. +[Official Repositories](/docker-hub/official_repos) are a carefully curated set +of Docker repositories supported by Docker, Inc. Automated repositories are +[Automated Builds](/userguide/dockerrepos/#automated-builds) that allow you to +validate the source and content of an image. We've reviewed the images available to use and we decided to use the `training/sinatra` image. So far we've seen two types of images repositories, diff --git a/docs/sources/userguide/dockerrepos.md b/docs/sources/userguide/dockerrepos.md index efa6ca3d0..8fc2ba637 100644 --- a/docs/sources/userguide/dockerrepos.md +++ b/docs/sources/userguide/dockerrepos.md @@ -51,12 +51,12 @@ name, user name, or description: tianon/centos CentOS 5 and 6, created using rinse instea... 21 ... -There you can see two example results: `centos` and -`tianon/centos`. The second result shows that it comes from -the public repository of a user, named `tianon/`, while the first result, -`centos`, doesn't explicitly list a repository which means that it comes from the -trusted top-level namespace. The `/` character separates a user's -repository from the image name. +There you can see two example results: `centos` and `tianon/centos`. The second +result shows that it comes from the public repository of a user, named +`tianon/`, while the first result, `centos`, doesn't explicitly list a +repository which means that it comes from the trusted top-level namespace for +[Official Repositories](/docker-hub/official_repos). The `/` character separates +a user's repository from the image name. Once you've found the image you want, you can download it with `docker pull `: From c7812f01c7269c713c2243fe8a69b55cdb77b72a Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Tue, 28 Apr 2015 18:51:04 +0800 Subject: [PATCH 676/999] fix a minor inspect format issue Before, inspect cont1 cont2 shows: [{ xxx } ,{ xxx } ] After, it shows: [ { xxx } ,{ xxx } ] Because `func (*Encoder) Encode` always followed by a newline character, so it's difficult to put '}' and ']' one the same line. To get symmetry, above is our choice. Signed-off-by: Qiang Huang --- api/client/inspect.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/client/inspect.go b/api/client/inspect.go index db281795c..0f327cb4d 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -34,7 +34,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { } indented := new(bytes.Buffer) - indented.WriteByte('[') + indented.WriteString("[\n") status := 0 isImage := false From a8e871b0bbbb63310f372332176875ffcc01aaf6 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Tue, 27 Jan 2015 07:57:34 -0800 Subject: [PATCH 677/999] Add support for Dockerfile CMD options This adds support for Dockerfile commands to have options - e.g: COPY --user=john foo /tmp/ COPY --ignore-mtime foo /tmp/ Supports both booleans and strings. Signed-off-by: Doug Davis --- builder/bflag.go | 155 ++++++++++++++++++ builder/bflag_test.go | 187 ++++++++++++++++++++++ builder/dispatchers.go | 16 ++ builder/evaluator.go | 6 +- builder/parser/parser.go | 4 +- builder/parser/testfiles/flags/Dockerfile | 10 ++ builder/parser/testfiles/flags/result | 10 ++ builder/parser/utils.go | 110 ++++++++++++- 8 files changed, 491 insertions(+), 7 deletions(-) create mode 100644 builder/bflag.go create mode 100644 builder/bflag_test.go create mode 100644 builder/parser/testfiles/flags/Dockerfile create mode 100644 builder/parser/testfiles/flags/result diff --git a/builder/bflag.go b/builder/bflag.go new file mode 100644 index 000000000..a6a2ba3a6 --- /dev/null +++ b/builder/bflag.go @@ -0,0 +1,155 @@ +package builder + +import ( + "fmt" + "strings" +) + +type FlagType int + +const ( + boolType FlagType = iota + stringType +) + +type BuilderFlags struct { + Args []string // actual flags/args from cmd line + flags map[string]*Flag + used map[string]*Flag + Err error +} + +type Flag struct { + bf *BuilderFlags + name string + flagType FlagType + Value string +} + +func NewBuilderFlags() *BuilderFlags { + return &BuilderFlags{ + flags: make(map[string]*Flag), + used: make(map[string]*Flag), + } +} + +func (bf *BuilderFlags) AddBool(name string, def bool) *Flag { + flag := bf.addFlag(name, boolType) + if flag == nil { + return nil + } + if def { + flag.Value = "true" + } else { + flag.Value = "false" + } + return flag +} + +func (bf *BuilderFlags) AddString(name string, def string) *Flag { + flag := bf.addFlag(name, stringType) + if flag == nil { + return nil + } + flag.Value = def + return flag +} + +func (bf *BuilderFlags) addFlag(name string, flagType FlagType) *Flag { + if _, ok := bf.flags[name]; ok { + bf.Err = fmt.Errorf("Duplicate flag defined: %s", name) + return nil + } + + newFlag := &Flag{ + bf: bf, + name: name, + flagType: flagType, + } + bf.flags[name] = newFlag + + return newFlag +} + +func (fl *Flag) IsUsed() bool { + if _, ok := fl.bf.used[fl.name]; ok { + return true + } + return false +} + +func (fl *Flag) IsTrue() bool { + if fl.flagType != boolType { + // Should never get here + panic(fmt.Errorf("Trying to use IsTrue on a non-boolean: %s", fl.name)) + } + return fl.Value == "true" +} + +func (bf *BuilderFlags) Parse() error { + // If there was an error while defining the possible flags + // go ahead and bubble it back up here since we didn't do it + // earlier in the processing + if bf.Err != nil { + return fmt.Errorf("Error setting up flags: %s", bf.Err) + } + + for _, arg := range bf.Args { + if !strings.HasPrefix(arg, "--") { + return fmt.Errorf("Arg should start with -- : %s", arg) + } + + if arg == "--" { + return nil + } + + arg = arg[2:] + value := "" + + index := strings.Index(arg, "=") + if index >= 0 { + value = arg[index+1:] + arg = arg[:index] + } + + flag, ok := bf.flags[arg] + if !ok { + return fmt.Errorf("Unknown flag: %s", arg) + } + + if _, ok = bf.used[arg]; ok { + return fmt.Errorf("Duplicate flag specified: %s", arg) + } + + bf.used[arg] = flag + + switch flag.flagType { + case boolType: + // value == "" is only ok if no "=" was specified + if index >= 0 && value == "" { + return fmt.Errorf("Missing a value on flag: %s", arg) + } + + lower := strings.ToLower(value) + if lower == "" { + flag.Value = "true" + } else if lower == "true" || lower == "false" { + flag.Value = lower + } else { + return fmt.Errorf("Expecting boolean value for flag %s, not: %s", arg, value) + } + + case stringType: + if index < 0 { + return fmt.Errorf("Missing a value on flag: %s", arg) + } + flag.Value = value + + default: + panic(fmt.Errorf("No idea what kind of flag we have! Should never get here!")) + } + + } + + return nil +} diff --git a/builder/bflag_test.go b/builder/bflag_test.go new file mode 100644 index 000000000..d03a1c306 --- /dev/null +++ b/builder/bflag_test.go @@ -0,0 +1,187 @@ +package builder + +import ( + "testing" +) + +func TestBuilderFlags(t *testing.T) { + var expected string + var err error + + // --- + + bf := NewBuilderFlags() + bf.Args = []string{} + if err := bf.Parse(); err != nil { + t.Fatalf("Test1 of %q was supposed to work: %s", bf.Args, err) + } + + // --- + + bf = NewBuilderFlags() + bf.Args = []string{"--"} + if err := bf.Parse(); err != nil { + t.Fatalf("Test2 of %q was supposed to work: %s", bf.Args, err) + } + + // --- + + bf = NewBuilderFlags() + flStr1 := bf.AddString("str1", "") + flBool1 := bf.AddBool("bool1", false) + bf.Args = []string{} + if err = bf.Parse(); err != nil { + t.Fatalf("Test3 of %q was supposed to work: %s", bf.Args, err) + } + + if flStr1.IsUsed() == true { + t.Fatalf("Test3 - str1 was not used!") + } + if flBool1.IsUsed() == true { + t.Fatalf("Test3 - bool1 was not used!") + } + + // --- + + bf = NewBuilderFlags() + flStr1 = bf.AddString("str1", "HI") + flBool1 = bf.AddBool("bool1", false) + bf.Args = []string{} + + if err = bf.Parse(); err != nil { + t.Fatalf("Test4 of %q was supposed to work: %s", bf.Args, err) + } + + if flStr1.Value != "HI" { + t.Fatalf("Str1 was supposed to default to: HI") + } + if flBool1.IsTrue() { + t.Fatalf("Bool1 was supposed to default to: false") + } + if flStr1.IsUsed() == true { + t.Fatalf("Str1 was not used!") + } + if flBool1.IsUsed() == true { + t.Fatalf("Bool1 was not used!") + } + + // --- + + bf = NewBuilderFlags() + flStr1 = bf.AddString("str1", "HI") + bf.Args = []string{"--str1"} + + if err = bf.Parse(); err == nil { + t.Fatalf("Test %q was supposed to fail", bf.Args) + } + + // --- + + bf = NewBuilderFlags() + flStr1 = bf.AddString("str1", "HI") + bf.Args = []string{"--str1="} + + if err = bf.Parse(); err != nil { + t.Fatalf("Test %q was supposed to work: %s", bf.Args, err) + } + + expected = "" + if flStr1.Value != expected { + t.Fatalf("Str1 (%q) should be: %q", flStr1.Value, expected) + } + + // --- + + bf = NewBuilderFlags() + flStr1 = bf.AddString("str1", "HI") + bf.Args = []string{"--str1=BYE"} + + if err = bf.Parse(); err != nil { + t.Fatalf("Test %q was supposed to work: %s", bf.Args, err) + } + + expected = "BYE" + if flStr1.Value != expected { + t.Fatalf("Str1 (%q) should be: %q", flStr1.Value, expected) + } + + // --- + + bf = NewBuilderFlags() + flBool1 = bf.AddBool("bool1", false) + bf.Args = []string{"--bool1"} + + if err = bf.Parse(); err != nil { + t.Fatalf("Test %q was supposed to work: %s", bf.Args, err) + } + + if !flBool1.IsTrue() { + t.Fatalf("Test-b1 Bool1 was supposed to be true") + } + + // --- + + bf = NewBuilderFlags() + flBool1 = bf.AddBool("bool1", false) + bf.Args = []string{"--bool1=true"} + + if err = bf.Parse(); err != nil { + t.Fatalf("Test %q was supposed to work: %s", bf.Args, err) + } + + if !flBool1.IsTrue() { + t.Fatalf("Test-b2 Bool1 was supposed to be true") + } + + // --- + + bf = NewBuilderFlags() + flBool1 = bf.AddBool("bool1", false) + bf.Args = []string{"--bool1=false"} + + if err = bf.Parse(); err != nil { + t.Fatalf("Test %q was supposed to work: %s", bf.Args, err) + } + + if flBool1.IsTrue() { + t.Fatalf("Test-b3 Bool1 was supposed to be false") + } + + // --- + + bf = NewBuilderFlags() + flBool1 = bf.AddBool("bool1", false) + bf.Args = []string{"--bool1=false1"} + + if err = bf.Parse(); err == nil { + t.Fatalf("Test %q was supposed to fail", bf.Args) + } + + // --- + + bf = NewBuilderFlags() + flBool1 = bf.AddBool("bool1", false) + bf.Args = []string{"--bool2"} + + if err = bf.Parse(); err == nil { + t.Fatalf("Test %q was supposed to fail", bf.Args) + } + + // --- + + bf = NewBuilderFlags() + flStr1 = bf.AddString("str1", "HI") + flBool1 = bf.AddBool("bool1", false) + bf.Args = []string{"--bool1", "--str1=BYE"} + + if err = bf.Parse(); err != nil { + t.Fatalf("Test %q was supposed to work: %s", bf.Args, err) + } + + if flStr1.Value != "BYE" { + t.Fatalf("Teset %s, str1 should be BYE", bf.Args) + } + if !flBool1.IsTrue() { + t.Fatalf("Teset %s, bool1 should be true", bf.Args) + } +} diff --git a/builder/dispatchers.go b/builder/dispatchers.go index e807f1aee..195d18305 100644 --- a/builder/dispatchers.go +++ b/builder/dispatchers.go @@ -47,6 +47,22 @@ func env(b *Builder, args []string, attributes map[string]bool, original string) return fmt.Errorf("Bad input to ENV, too many args") } + // TODO/FIXME/NOT USED + // Just here to show how to use the builder flags stuff within the + // context of a builder command. Will remove once we actually add + // a builder command to something! + /* + flBool1 := b.BuilderFlags.AddBool("bool1", false) + flStr1 := b.BuilderFlags.AddString("str1", "HI") + + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + + fmt.Printf("Bool1:%v\n", flBool1) + fmt.Printf("Str1:%v\n", flStr1) + */ + commitStr := "ENV" for j := 0; j < len(args); j++ { diff --git a/builder/evaluator.go b/builder/evaluator.go index 9a2b57a8f..7dfb001bd 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -116,6 +116,7 @@ type Builder struct { image string // image name for commit processing maintainer string // maintainer name. could probably be removed. cmdSet bool // indicates is CMD was set in current Dockerfile + BuilderFlags *BuilderFlags // current cmd's BuilderFlags - temporary context tarsum.TarSum // the context is a tarball that is uploaded by the client contextPath string // the path of the temporary directory the local context is unpacked to (server side) noBaseImage bool // indicates that this build does not start from any base image, but is being built from an empty file system. @@ -276,8 +277,9 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error { cmd := ast.Value attrs := ast.Attributes original := ast.Original + flags := ast.Flags strs := []string{} - msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd)) + msg := fmt.Sprintf("Step %d : %s", stepN, original) if cmd == "onbuild" { if ast.Next == nil { @@ -325,6 +327,8 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error { // XXX yes, we skip any cmds that are not valid; the parser should have // picked these out already. if f, ok := evaluateTable[cmd]; ok { + b.BuilderFlags = NewBuilderFlags() + b.BuilderFlags.Args = flags return f(b, strList, attrs, original) } diff --git a/builder/parser/parser.go b/builder/parser/parser.go index 1ab151b30..f68c710c0 100644 --- a/builder/parser/parser.go +++ b/builder/parser/parser.go @@ -29,6 +29,7 @@ type Node struct { Children []*Node // the children of this sexp Attributes map[string]bool // special attributes for this node Original string // original line used before parsing + Flags []string // only top Node should have this set } var ( @@ -75,7 +76,7 @@ func parseLine(line string) (string, *Node, error) { return line, nil, nil } - cmd, args, err := splitCommand(line) + cmd, flags, args, err := splitCommand(line) if err != nil { return "", nil, err } @@ -91,6 +92,7 @@ func parseLine(line string) (string, *Node, error) { node.Next = sexp node.Attributes = attrs node.Original = line + node.Flags = flags return "", node, nil } diff --git a/builder/parser/testfiles/flags/Dockerfile b/builder/parser/testfiles/flags/Dockerfile new file mode 100644 index 000000000..2418e0f06 --- /dev/null +++ b/builder/parser/testfiles/flags/Dockerfile @@ -0,0 +1,10 @@ +FROM scratch +COPY foo /tmp/ +COPY --user=me foo /tmp/ +COPY --doit=true foo /tmp/ +COPY --user=me --doit=true foo /tmp/ +COPY --doit=true -- foo /tmp/ +COPY -- foo /tmp/ +CMD --doit [ "a", "b" ] +CMD --doit=true -- [ "a", "b" ] +CMD --doit -- [ ] diff --git a/builder/parser/testfiles/flags/result b/builder/parser/testfiles/flags/result new file mode 100644 index 000000000..4578f4cba --- /dev/null +++ b/builder/parser/testfiles/flags/result @@ -0,0 +1,10 @@ +(from "scratch") +(copy "foo" "/tmp/") +(copy ["--user=me"] "foo" "/tmp/") +(copy ["--doit=true"] "foo" "/tmp/") +(copy ["--user=me" "--doit=true"] "foo" "/tmp/") +(copy ["--doit=true"] "foo" "/tmp/") +(copy "foo" "/tmp/") +(cmd ["--doit"] "a" "b") +(cmd ["--doit=true"] "a" "b") +(cmd ["--doit"]) diff --git a/builder/parser/utils.go b/builder/parser/utils.go index a60ad129f..5d82e9604 100644 --- a/builder/parser/utils.go +++ b/builder/parser/utils.go @@ -1,8 +1,10 @@ package parser import ( + "fmt" "strconv" "strings" + "unicode" ) // dumps the AST defined by `node` as a list of sexps. Returns a string @@ -11,6 +13,10 @@ func (node *Node) Dump() string { str := "" str += node.Value + if len(node.Flags) > 0 { + str += fmt.Sprintf(" %q", node.Flags) + } + for _, n := range node.Children { str += "(" + n.Dump() + ")\n" } @@ -48,20 +54,23 @@ func fullDispatch(cmd, args string) (*Node, map[string]bool, error) { // splitCommand takes a single line of text and parses out the cmd and args, // which are used for dispatching to more exact parsing functions. -func splitCommand(line string) (string, string, error) { +func splitCommand(line string) (string, []string, string, error) { var args string + var flags []string // Make sure we get the same results irrespective of leading/trailing spaces cmdline := TOKEN_WHITESPACE.Split(strings.TrimSpace(line), 2) cmd := strings.ToLower(cmdline[0]) if len(cmdline) == 2 { - args = strings.TrimSpace(cmdline[1]) + var err error + args, flags, err = extractBuilderFlags(cmdline[1]) + if err != nil { + return "", nil, "", err + } } - // the cmd should never have whitespace, but it's possible for the args to - // have trailing whitespace. - return cmd, args, nil + return cmd, flags, strings.TrimSpace(args), nil } // covers comments and empty lines. Lines should be trimmed before passing to @@ -74,3 +83,94 @@ func stripComments(line string) string { return line } + +func extractBuilderFlags(line string) (string, []string, error) { + // Parses the BuilderFlags and returns the remaining part of the line + + const ( + inSpaces = iota // looking for start of a word + inWord + inQuote + ) + + words := []string{} + phase := inSpaces + word := "" + quote := '\000' + blankOK := false + var ch rune + + for pos := 0; pos <= len(line); pos++ { + if pos != len(line) { + ch = rune(line[pos]) + } + + if phase == inSpaces { // Looking for start of word + if pos == len(line) { // end of input + break + } + if unicode.IsSpace(ch) { // skip spaces + continue + } + + // Only keep going if the next word starts with -- + if ch != '-' || pos+1 == len(line) || rune(line[pos+1]) != '-' { + return line[pos:], words, nil + } + + phase = inWord // found someting with "--", fall thru + } + if (phase == inWord || phase == inQuote) && (pos == len(line)) { + if word != "--" && (blankOK || len(word) > 0) { + words = append(words, word) + } + break + } + if phase == inWord { + if unicode.IsSpace(ch) { + phase = inSpaces + if word == "--" { + return line[pos:], words, nil + } + if blankOK || len(word) > 0 { + words = append(words, word) + } + word = "" + blankOK = false + continue + } + if ch == '\'' || ch == '"' { + quote = ch + blankOK = true + phase = inQuote + continue + } + if ch == '\\' { + if pos+1 == len(line) { + continue // just skip \ at end + } + pos++ + ch = rune(line[pos]) + } + word += string(ch) + continue + } + if phase == inQuote { + if ch == quote { + phase = inWord + continue + } + if ch == '\\' { + if pos+1 == len(line) { + phase = inWord + continue // just skip \ at end + } + pos++ + ch = rune(line[pos]) + } + word += string(ch) + } + } + + return "", words, nil +} From ed40c0a9a48e54dbe341ad718d9362a268467378 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 27 Apr 2015 11:03:05 -0400 Subject: [PATCH 678/999] rhel.md: update RHEL6 from 6.5 to 6.6 Signed-off-by: Vincent Batts --- docs/sources/installation/rhel.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/installation/rhel.md b/docs/sources/installation/rhel.md index 7be8debce..b312d57d1 100644 --- a/docs/sources/installation/rhel.md +++ b/docs/sources/installation/rhel.md @@ -7,7 +7,7 @@ page_keywords: Docker, Docker documentation, requirements, linux, rhel Docker is supported on the following versions of RHEL: - [*Red Hat Enterprise Linux 7 (64-bit)*](#red-hat-enterprise-linux-7-installation) -- [*Red Hat Enterprise Linux 6.5 (64-bit)*](#red-hat-enterprise-linux-6.5-installation) or later +- [*Red Hat Enterprise Linux 6.6 (64-bit)*](#red-hat-enterprise-linux-66-installation) or later ## Kernel support @@ -41,14 +41,14 @@ Portal](https://access.redhat.com/). Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon). -## Red Hat Enterprise Linux 6.5 installation +## Red Hat Enterprise Linux 6.6 installation You will need **64 bit** [RHEL -6.5](https://access.redhat.com/site/articles/3078#RHEL6) or later, with +6.6](https://access.redhat.com/site/articles/3078#RHEL6) or later, with a RHEL 6 kernel version 2.6.32-431 or higher as this has specific kernel fixes to allow Docker to work. -Docker is available for **RHEL6.5** on EPEL. Please note that +Docker is available for **RHEL6.6** on EPEL. Please note that this package is part of [Extra Packages for Enterprise Linux (EPEL)](https://fedoraproject.org/wiki/EPEL), a community effort to create and maintain additional packages for the RHEL distribution. From 179b6ddc353fd766125df19bf61b00a39a13cd04 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 27 Apr 2015 11:04:32 -0400 Subject: [PATCH 679/999] rhel.md: bump the kernel version for RHEL6 Closes #9856 Signed-off-by: Vincent Batts --- docs/sources/installation/rhel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/rhel.md b/docs/sources/installation/rhel.md index b312d57d1..610a300c5 100644 --- a/docs/sources/installation/rhel.md +++ b/docs/sources/installation/rhel.md @@ -45,7 +45,7 @@ Please continue with the [Starting the Docker daemon](#starting-the-docker-daemo You will need **64 bit** [RHEL 6.6](https://access.redhat.com/site/articles/3078#RHEL6) or later, with -a RHEL 6 kernel version 2.6.32-431 or higher as this has specific kernel +a RHEL 6 kernel version 2.6.32-504.16.2 or higher as this has specific kernel fixes to allow Docker to work. Docker is available for **RHEL6.6** on EPEL. Please note that From 9b365e0845bf8e74cef23db2233e721b90ae4339 Mon Sep 17 00:00:00 2001 From: Vincent Batts Date: Mon, 27 Apr 2015 14:44:56 -0400 Subject: [PATCH 680/999] rhel.md: adding link to most recent issue Signed-off-by: Vincent Batts --- docs/sources/installation/rhel.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/rhel.md b/docs/sources/installation/rhel.md index 610a300c5..b3bd7aa1d 100644 --- a/docs/sources/installation/rhel.md +++ b/docs/sources/installation/rhel.md @@ -46,7 +46,7 @@ Please continue with the [Starting the Docker daemon](#starting-the-docker-daemo You will need **64 bit** [RHEL 6.6](https://access.redhat.com/site/articles/3078#RHEL6) or later, with a RHEL 6 kernel version 2.6.32-504.16.2 or higher as this has specific kernel -fixes to allow Docker to work. +fixes to allow Docker to work. Related issues: [#9856](https://github.com/docker/docker/issues/9856). Docker is available for **RHEL6.6** on EPEL. Please note that this package is part of [Extra Packages for Enterprise Linux From f04837cf803583dfeb4dcd8311e4e5ef8764c37c Mon Sep 17 00:00:00 2001 From: nikolas Date: Tue, 28 Apr 2015 13:25:48 -0400 Subject: [PATCH 681/999] Remove incorrect option in docker install command The `-N` option is not compatible with the `-O` option of wget (see the man page). The example command now matches the example in the script on http://get.docker.com/. Signed-off-by: Nik Nyby --- docs/sources/installation/ubuntulinux.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index 75b3c9fb6..6c854997f 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -308,5 +308,5 @@ NetworkManager (this might slow your network). To install the latest version of Docker, use the standard `-N` flag with `wget`: - $ wget -N -qO- https://get.docker.com/ | sh + $ wget -qO- https://get.docker.com/ | sh From 6fd8e485c85c4f8ca62578d0840bdeddc4cba151 Mon Sep 17 00:00:00 2001 From: buddhamagnet Date: Thu, 9 Apr 2015 20:07:06 +0100 Subject: [PATCH 682/999] add support for exclusion rules in dockerignore Signed-off-by: Dave Goodchild --- .../articles/dockerfile_best-practices.md | 13 +- docs/sources/reference/builder.md | 97 +++++++----- docs/sources/reference/commandline/cli.md | 69 +++------ integration-cli/docker_cli_build_test.go | 63 +++++++- pkg/archive/archive.go | 11 +- pkg/fileutils/fileutils.go | 111 ++++++++++++-- pkg/fileutils/fileutils_test.go | 139 ++++++++++++++++++ 7 files changed, 399 insertions(+), 104 deletions(-) diff --git a/docs/sources/articles/dockerfile_best-practices.md b/docs/sources/articles/dockerfile_best-practices.md index 425eb8658..dfb265c68 100644 --- a/docs/sources/articles/dockerfile_best-practices.md +++ b/docs/sources/articles/dockerfile_best-practices.md @@ -32,13 +32,14 @@ ephemeral as possible. By “ephemeral,” we mean that it can be stopped and destroyed and a new one built and put in place with an absolute minimum of set-up and configuration. -### Use [a .dockerignore file](https://docs.docker.com/reference/builder/#the-dockerignore-file) +### Use a .dockerignore file -For faster uploading and efficiency during `docker build`, you should use -a `.dockerignore` file to exclude files or directories from the build -context and final image. For example, unless`.git` is needed by your build -process or scripts, you should add it to `.dockerignore`, which can save many -megabytes worth of upload time. +In most cases, it's best to put each Dockerfile in an empty directory. Then, +add to that directory only the files needed for building the Dockerfile. To +increase the build's performance, you can exclude files and directories by +adding a `.dockerignore` file to that directory as well. This file supports +exclusion patterns similar to `.gitignore` files. For information on creating one, +see the [.dockerignore file](../../reference/builder/#dockerignore-file). ### Avoid installing unnecessary packages diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index c5392f847..7dbe54923 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -41,10 +41,11 @@ whole context must be transferred to the daemon. The Docker CLI reports > repository, the entire contents of your hard drive will get sent to the daemon (and > thus to the machine running the daemon). You probably don't want that. -In most cases, it's best to put each Dockerfile in an empty directory, and then add only -the files needed for building that Dockerfile to that directory. To further speed up the -build, you can exclude files and directories by adding a `.dockerignore` file to the same -directory. +In most cases, it's best to put each Dockerfile in an empty directory. Then, +only add the files needed for building the Dockerfile to the directory. To +increase the build's performance, you can exclude files and directories by +adding a `.dockerignore` file to the directory. For information about how to +[create a `.dockerignore` file](#the-dockerignore-file) on this page. You can specify a repository and tag at which to save the new image if the build succeeds: @@ -169,43 +170,67 @@ will result in `def` having a value of `hello`, not `bye`. However, `ghi` will have a value of `bye` because it is not part of the same command that set `abc` to `bye`. -## The `.dockerignore` file +### .dockerignore file -If a file named `.dockerignore` exists in the source repository, then it -is interpreted as a newline-separated list of exclusion patterns. -Exclusion patterns match files or directories relative to the source repository -that will be excluded from the context. Globbing is done using Go's +If a file named `.dockerignore` exists in the root of `PATH`, then Docker +interprets it as a newline-separated list of exclusion patterns. Docker excludes +files or directories relative to `PATH` that match these exclusion patterns. If +there are any `.dockerignore` files in `PATH` subdirectories, Docker treats +them as normal files. + +Filepaths in `.dockerignore` are absolute with the current directory as the +root. Wildcards are allowed but the search is not recursive. Globbing (file name +expansion) is done using Go's [filepath.Match](http://golang.org/pkg/path/filepath#Match) rules. -> **Note**: -> The `.dockerignore` file can even be used to ignore the `Dockerfile` and -> `.dockerignore` files. This might be useful if you are copying files from -> the root of the build context into your new container but do not want to -> include the `Dockerfile` or `.dockerignore` files (e.g. `ADD . /someDir/`). +You can specify exceptions to exclusion rules. To do this, simply prefix a +pattern with an `!` (exclamation mark) in the same way you would in a +`.gitignore` file. Currently there is no support for regular expressions. +Formats like `[^temp*]` are ignored. -The following example shows the use of the `.dockerignore` file to exclude the -`.git` directory from the context. Its effect can be seen in the changed size of -the uploaded context. +The following is an example `.dockerignore` file: + +``` + */temp* + */*/temp* + temp? + *.md + !LICENCSE.md +``` + +This file causes the following build behavior: + +| Rule | Behavior | +|----------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `*/temp*` | Exclude all files with names starting with`temp` in any subdirectory below the root directory. For example, a file named`/somedir/temporary.txt` is ignored. | +| `*/*/temp*` | Exclude files starting with name `temp` from any subdirectory that is two levels below the root directory. For example, the file `/somedir/subdir/temporary.txt` is ignored. | +| `temp?` | Exclude the files that match the pattern in the root directory. For example, the files `tempa`, `tempb` in the root directory are ignored. | +| `*.md ` | Exclude all markdown files. | +| `!LICENSE.md` | Exception to the exclude all Markdown files is this file, `LICENSE.md`, include this file in the build. | + +The placement of `!` exception rules influences the matching algorithm; the +last line of the `.dockerignore` that matches a particular file determines +whether it is included or excluded. In the above example, the `LICENSE.md` file +matches both the `*.md` and `!LICENSE.md` rule. If you reverse the lines in the +example: + +``` + */temp* + */*/temp* + temp? + !LICENCSE.md + *.md +``` + +The build would exclude `LICENSE.md` because the last `*.md` rule adds all +Markdown files back onto the ignore list. The `!LICENSE.md` rule has no effect +because the subsequent `*.md` rule overrides it. + +You can even use the `.dockerignore` file to ignore the `Dockerfile` and +`.dockerignore` files. This is useful if you are copying files from the root of +the build context into your new container but do not want to include the +`Dockerfile` or `.dockerignore` files (e.g. `ADD . /someDir/`). - $ docker build . - Uploading context 18.829 MB - Uploading context - Step 0 : FROM busybox - ---> 769b9341d937 - Step 1 : CMD echo Hello World - ---> Using cache - ---> 99cc1ad10469 - Successfully built 99cc1ad10469 - $ echo ".git" > .dockerignore - $ docker build . - Uploading context 6.76 MB - Uploading context - Step 0 : FROM busybox - ---> 769b9341d937 - Step 1 : CMD echo Hello World - ---> Using cache - ---> 99cc1ad10469 - Successfully built 99cc1ad10469 ## FROM diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 26659c8ff..597324eae 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -653,6 +653,26 @@ If you use STDIN or specify a `URL`, the system places the contents into a file called `Dockerfile`, and any `-f`, `--file` option is ignored. In this scenario, there is no context. +By default the `docker build` command will look for a `Dockerfile` at the +root of the build context. The `-f`, `--file`, option lets you specify +the path to an alternative file to use instead. This is useful +in cases where the same set of files are used for multiple builds. The path +must be to a file within the build context. If a relative path is specified +then it must to be relative to the current directory. + +In most cases, it's best to put each Dockerfile in an empty directory. Then, add +to that directory only the files needed for building the Dockerfile. To increase +the build's performance, you can exclude files and directories by adding a +`.dockerignore` file to that directory as well. For information on creating one, +see the [.dockerignore file](../../reference/builder/#dockerignore-file). + +If the Docker client loses connection to the daemon, the build is canceled. +This happens if you interrupt the Docker client with `ctrl-c` or if the Docker +client is killed for any reason. + +> **Note:** Currently only the "run" phase of the build can be canceled until +> pull cancelation is implemented). + ### Return code On a successful build, a return code of success `0` will be returned. @@ -673,55 +693,11 @@ INFO[0000] The command [/bin/sh -c exit 13] returned a non-zero code: 13 $ echo $? 1 ``` - -### .dockerignore file - -If a file named `.dockerignore` exists in the root of `PATH` then it -is interpreted as a newline-separated list of exclusion patterns. -Exclusion patterns match files or directories relative to `PATH` that -will be excluded from the context. Globbing is done using Go's -[filepath.Match](http://golang.org/pkg/path/filepath#Match) rules. - -Please note that `.dockerignore` files in other subdirectories are -considered as normal files. Filepaths in `.dockerignore` are absolute with -the current directory as the root. Wildcards are allowed but the search -is not recursive. - -#### Example .dockerignore file - */temp* - */*/temp* - temp? - -The first line above `*/temp*`, would ignore all files with names starting with -`temp` from any subdirectory below the root directory. For example, a file named -`/somedir/temporary.txt` would be ignored. The second line `*/*/temp*`, will -ignore files starting with name `temp` from any subdirectory that is two levels -below the root directory. For example, the file `/somedir/subdir/temporary.txt` -would get ignored in this case. The last line in the above example `temp?` -will ignore the files that match the pattern from the root directory. -For example, the files `tempa`, `tempb` are ignored from the root directory. -Currently there is no support for regular expressions. Formats -like `[^temp*]` are ignored. - -By default the `docker build` command will look for a `Dockerfile` at the -root of the build context. The `-f`, `--file`, option lets you specify -the path to an alternative file to use instead. This is useful -in cases where the same set of files are used for multiple builds. The path -must be to a file within the build context. If a relative path is specified -then it must to be relative to the current directory. - -If the Docker client loses connection to the daemon, the build is canceled. -This happens if you interrupt the Docker client with `ctrl-c` or if the Docker -client is killed for any reason. - -> **Note:** Currently only the "run" phase of the build can be canceled until -> pull cancelation is implemented). - See also: [*Dockerfile Reference*](/reference/builder). -#### Examples +### Examples $ docker build . Uploading context 10240 bytes @@ -790,7 +766,8 @@ affect the build cache. This example shows the use of the `.dockerignore` file to exclude the `.git` directory from the context. Its effect can be seen in the changed size of the -uploaded context. +uploaded context. The builder reference contains detailed information on +[creating a .dockerignore file](../../builder/#dockerignore-file) $ docker build -t vieux/apache:2.0 . diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 695e4cd6e..334342783 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -3427,20 +3427,29 @@ func (s *DockerSuite) TestBuildDockerignore(c *check.C) { RUN [[ ! -e /bla/src/_vendor ]] RUN [[ ! -e /bla/.gitignore ]] RUN [[ ! -e /bla/README.md ]] + RUN [[ ! -e /bla/dir/foo ]] + RUN [[ ! -e /bla/foo ]] RUN [[ ! -e /bla/.git ]]` ctx, err := fakeContext(dockerfile, map[string]string{ "Makefile": "all:", ".git/HEAD": "ref: foo", "src/x.go": "package main", "src/_vendor/v.go": "package main", + "dir/foo": "", ".gitignore": "", "README.md": "readme", - ".dockerignore": ".git\npkg\n.gitignore\nsrc/_vendor\n*.md", + ".dockerignore": ` +.git +pkg +.gitignore +src/_vendor +*.md +dir`, }) - defer ctx.Close() if err != nil { c.Fatal(err) } + defer ctx.Close() if _, err := buildImageFromContext(name, ctx, true); err != nil { c.Fatal(err) } @@ -3467,6 +3476,55 @@ func (s *DockerSuite) TestBuildDockerignoreCleanPaths(c *check.C) { } } +func (s *DockerSuite) TestBuildDockerignoreExceptions(c *check.C) { + name := "testbuilddockerignoreexceptions" + defer deleteImages(name) + dockerfile := ` + FROM busybox + ADD . /bla + RUN [[ -f /bla/src/x.go ]] + RUN [[ -f /bla/Makefile ]] + RUN [[ ! -e /bla/src/_vendor ]] + RUN [[ ! -e /bla/.gitignore ]] + RUN [[ ! -e /bla/README.md ]] + RUN [[ -e /bla/dir/dir/foo ]] + RUN [[ ! -e /bla/dir/foo1 ]] + RUN [[ -f /bla/dir/e ]] + RUN [[ -f /bla/dir/e-dir/foo ]] + RUN [[ ! -e /bla/foo ]] + RUN [[ ! -e /bla/.git ]]` + ctx, err := fakeContext(dockerfile, map[string]string{ + "Makefile": "all:", + ".git/HEAD": "ref: foo", + "src/x.go": "package main", + "src/_vendor/v.go": "package main", + "dir/foo": "", + "dir/foo1": "", + "dir/dir/f1": "", + "dir/dir/foo": "", + "dir/e": "", + "dir/e-dir/foo": "", + ".gitignore": "", + "README.md": "readme", + ".dockerignore": ` +.git +pkg +.gitignore +src/_vendor +*.md +dir +!dir/e* +!dir/dir/foo`, + }) + if err != nil { + c.Fatal(err) + } + defer ctx.Close() + if _, err := buildImageFromContext(name, ctx, true); err != nil { + c.Fatal(err) + } +} + func (s *DockerSuite) TestBuildDockerignoringDockerfile(c *check.C) { name := "testbuilddockerignoredockerfile" dockerfile := ` @@ -3607,6 +3665,7 @@ func (s *DockerSuite) TestBuildDockerignoringWholeDir(c *check.C) { ctx, err := fakeContext(dockerfile, map[string]string{ "Dockerfile": "FROM scratch", "Makefile": "all:", + ".gitignore": "", ".dockerignore": ".*\n", }) defer ctx.Close() diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 7f1889750..4d8d26008 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -391,6 +391,13 @@ func Tar(path string, compression Compression) (io.ReadCloser, error) { // TarWithOptions creates an archive from the directory at `path`, only including files whose relative // paths are included in `options.IncludeFiles` (if non-nil) or not in `options.ExcludePatterns`. func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) { + + patterns, patDirs, exceptions, err := fileutils.CleanPatterns(options.ExcludePatterns) + + if err != nil { + return nil, err + } + pipeReader, pipeWriter := io.Pipe() compressWriter, err := CompressStream(pipeWriter, options.Compression) @@ -441,7 +448,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) // is asking for that file no matter what - which is true // for some files, like .dockerignore and Dockerfile (sometimes) if include != relFilePath { - skip, err = fileutils.Matches(relFilePath, options.ExcludePatterns) + skip, err = fileutils.OptimizedMatches(relFilePath, patterns, patDirs) if err != nil { logrus.Debugf("Error matching %s", relFilePath, err) return err @@ -449,7 +456,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error) } if skip { - if f.IsDir() { + if !exceptions && f.IsDir() { return filepath.SkipDir } return nil diff --git a/pkg/fileutils/fileutils.go b/pkg/fileutils/fileutils.go index ef2a6523d..d1e130d0a 100644 --- a/pkg/fileutils/fileutils.go +++ b/pkg/fileutils/fileutils.go @@ -1,33 +1,120 @@ package fileutils import ( + "errors" "fmt" "io" "io/ioutil" "os" "path/filepath" + "strings" "github.com/Sirupsen/logrus" ) -// Matches returns true if relFilePath matches any of the patterns -func Matches(relFilePath string, patterns []string) (bool, error) { - for _, exclude := range patterns { - matched, err := filepath.Match(exclude, relFilePath) +func Exclusion(pattern string) bool { + return pattern[0] == '!' +} + +func Empty(pattern string) bool { + return pattern == "" +} + +// Cleanpatterns takes a slice of patterns returns a new +// slice of patterns cleaned with filepath.Clean, stripped +// of any empty patterns and lets the caller know whether the +// slice contains any exception patterns (prefixed with !). +func CleanPatterns(patterns []string) ([]string, [][]string, bool, error) { + // Loop over exclusion patterns and: + // 1. Clean them up. + // 2. Indicate whether we are dealing with any exception rules. + // 3. Error if we see a single exclusion marker on it's own (!). + cleanedPatterns := []string{} + patternDirs := [][]string{} + exceptions := false + for _, pattern := range patterns { + // Eliminate leading and trailing whitespace. + pattern = strings.TrimSpace(pattern) + if Empty(pattern) { + continue + } + if Exclusion(pattern) { + if len(pattern) == 1 { + logrus.Errorf("Illegal exclusion pattern: %s", pattern) + return nil, nil, false, errors.New("Illegal exclusion pattern: !") + } + exceptions = true + } + pattern = filepath.Clean(pattern) + cleanedPatterns = append(cleanedPatterns, pattern) + if Exclusion(pattern) { + pattern = pattern[1:] + } + patternDirs = append(patternDirs, strings.Split(pattern, "/")) + } + + return cleanedPatterns, patternDirs, exceptions, nil +} + +// Matches returns true if file matches any of the patterns +// and isn't excluded by any of the subsequent patterns. +func Matches(file string, patterns []string) (bool, error) { + file = filepath.Clean(file) + + if file == "." { + // Don't let them exclude everything, kind of silly. + return false, nil + } + + patterns, patDirs, _, err := CleanPatterns(patterns) + if err != nil { + return false, err + } + + return OptimizedMatches(file, patterns, patDirs) +} + +// Matches is basically the same as fileutils.Matches() but optimized for archive.go. +// It will assume that the inputs have been preprocessed and therefore the function +// doen't need to do as much error checking and clean-up. This was done to avoid +// repeating these steps on each file being checked during the archive process. +// The more generic fileutils.Matches() can't make these assumptions. +func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, error) { + matched := false + parentPath := filepath.Dir(file) + parentPathDirs := strings.Split(parentPath, "/") + + for i, pattern := range patterns { + negative := false + + if Exclusion(pattern) { + negative = true + pattern = pattern[1:] + } + + match, err := filepath.Match(pattern, file) if err != nil { - logrus.Errorf("Error matching: %s (pattern: %s)", relFilePath, exclude) + logrus.Errorf("Error matching: %s (pattern: %s)", file, pattern) return false, err } - if matched { - if filepath.Clean(relFilePath) == "." { - logrus.Errorf("Can't exclude whole path, excluding pattern: %s", exclude) - continue + + if !match && parentPath != "." { + // Check to see if the pattern matches one of our parent dirs. + if len(patDirs[i]) <= len(parentPathDirs) { + match, _ = filepath.Match(strings.Join(patDirs[i], "/"), + strings.Join(parentPathDirs[:len(patDirs[i])], "/")) } - logrus.Debugf("Skipping excluded path: %s", relFilePath) - return true, nil + } + + if match { + matched = !negative } } - return false, nil + + if matched { + logrus.Debugf("Skipping excluded path: %s", file) + } + return matched, nil } func CopyFile(src, dst string) (int64, error) { diff --git a/pkg/fileutils/fileutils_test.go b/pkg/fileutils/fileutils_test.go index 16d00d7b9..ce19ffce4 100644 --- a/pkg/fileutils/fileutils_test.go +++ b/pkg/fileutils/fileutils_test.go @@ -79,3 +79,142 @@ func TestReadSymlinkedDirectoryToFile(t *testing.T) { t.Errorf("failed to remove symlink: %s", err) } } + +func TestWildcardMatches(t *testing.T) { + match, _ := Matches("fileutils.go", []string{"*"}) + if match != true { + t.Errorf("failed to get a wildcard match, got %v", match) + } +} + +// A simple pattern match should return true. +func TestPatternMatches(t *testing.T) { + match, _ := Matches("fileutils.go", []string{"*.go"}) + if match != true { + t.Errorf("failed to get a match, got %v", match) + } +} + +// An exclusion followed by an inclusion should return true. +func TestExclusionPatternMatchesPatternBefore(t *testing.T) { + match, _ := Matches("fileutils.go", []string{"!fileutils.go", "*.go"}) + if match != true { + t.Errorf("failed to get true match on exclusion pattern, got %v", match) + } +} + +// A folder pattern followed by an exception should return false. +func TestPatternMatchesFolderExclusions(t *testing.T) { + match, _ := Matches("docs/README.md", []string{"docs", "!docs/README.md"}) + if match != false { + t.Errorf("failed to get a false match on exclusion pattern, got %v", match) + } +} + +// A folder pattern followed by an exception should return false. +func TestPatternMatchesFolderWithSlashExclusions(t *testing.T) { + match, _ := Matches("docs/README.md", []string{"docs/", "!docs/README.md"}) + if match != false { + t.Errorf("failed to get a false match on exclusion pattern, got %v", match) + } +} + +// A folder pattern followed by an exception should return false. +func TestPatternMatchesFolderWildcardExclusions(t *testing.T) { + match, _ := Matches("docs/README.md", []string{"docs/*", "!docs/README.md"}) + if match != false { + t.Errorf("failed to get a false match on exclusion pattern, got %v", match) + } +} + +// A pattern followed by an exclusion should return false. +func TestExclusionPatternMatchesPatternAfter(t *testing.T) { + match, _ := Matches("fileutils.go", []string{"*.go", "!fileutils.go"}) + if match != false { + t.Errorf("failed to get false match on exclusion pattern, got %v", match) + } +} + +// A filename evaluating to . should return false. +func TestExclusionPatternMatchesWholeDirectory(t *testing.T) { + match, _ := Matches(".", []string{"*.go"}) + if match != false { + t.Errorf("failed to get false match on ., got %v", match) + } +} + +// A single ! pattern should return an error. +func TestSingleExclamationError(t *testing.T) { + _, err := Matches("fileutils.go", []string{"!"}) + if err == nil { + t.Errorf("failed to get an error for a single exclamation point, got %v", err) + } +} + +// A string preceded with a ! should return true from Exclusion. +func TestExclusion(t *testing.T) { + exclusion := Exclusion("!") + if !exclusion { + t.Errorf("failed to get true for a single !, got %v", exclusion) + } +} + +// An empty string should return true from Empty. +func TestEmpty(t *testing.T) { + empty := Empty("") + if !empty { + t.Errorf("failed to get true for an empty string, got %v", empty) + } +} + +func TestCleanPatterns(t *testing.T) { + cleaned, _, _, _ := CleanPatterns([]string{"docs", "config"}) + if len(cleaned) != 2 { + t.Errorf("expected 2 element slice, got %v", len(cleaned)) + } +} + +func TestCleanPatternsStripEmptyPatterns(t *testing.T) { + cleaned, _, _, _ := CleanPatterns([]string{"docs", "config", ""}) + if len(cleaned) != 2 { + t.Errorf("expected 2 element slice, got %v", len(cleaned)) + } +} + +func TestCleanPatternsExceptionFlag(t *testing.T) { + _, _, exceptions, _ := CleanPatterns([]string{"docs", "!docs/README.md"}) + if !exceptions { + t.Errorf("expected exceptions to be true, got %v", exceptions) + } +} + +func TestCleanPatternsLeadingSpaceTrimmed(t *testing.T) { + _, _, exceptions, _ := CleanPatterns([]string{"docs", " !docs/README.md"}) + if !exceptions { + t.Errorf("expected exceptions to be true, got %v", exceptions) + } +} + +func TestCleanPatternsTrailingSpaceTrimmed(t *testing.T) { + _, _, exceptions, _ := CleanPatterns([]string{"docs", "!docs/README.md "}) + if !exceptions { + t.Errorf("expected exceptions to be true, got %v", exceptions) + } +} + +func TestCleanPatternsErrorSingleException(t *testing.T) { + _, _, _, err := CleanPatterns([]string{"!"}) + if err == nil { + t.Errorf("expected error on single exclamation point, got %v", err) + } +} + +func TestCleanPatternsFolderSplit(t *testing.T) { + _, dirs, _, _ := CleanPatterns([]string{"docs/config/CONFIG.md"}) + if dirs[0][0] != "docs" { + t.Errorf("expected first element in dirs slice to be docs, got %v", dirs[0][1]) + } + if dirs[0][1] != "config" { + t.Errorf("expected first element in dirs slice to be config, got %v", dirs[0][1]) + } +} From 8a2f8992865a706df30eb23bd861444a5ecf6198 Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Tue, 28 Apr 2015 16:05:28 +0800 Subject: [PATCH 683/999] use CustomSize replace intToString Signed-off-by: Ma Shimiao --- pkg/units/size.go | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/pkg/units/size.go b/pkg/units/size.go index 7cfb57ba5..d7850ad0b 100644 --- a/pkg/units/size.go +++ b/pkg/units/size.go @@ -37,23 +37,25 @@ var ( var decimapAbbrs = []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} var binaryAbbrs = []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} +// CustomSize returns a human-readable approximation of a size +// using custom format +func CustomSize(format string, size float64, base float64, _map []string) string { + i := 0 + for size >= base { + size = size / base + i++ + } + return fmt.Sprintf(format, size, _map[i]) +} + // HumanSize returns a human-readable approximation of a size // using SI standard (eg. "44kB", "17MB") func HumanSize(size float64) string { - return intToString(float64(size), 1000.0, decimapAbbrs) + return CustomSize("%.4g %s", float64(size), 1000.0, decimapAbbrs) } func BytesSize(size float64) string { - return intToString(size, 1024.0, binaryAbbrs) -} - -func intToString(size, unit float64, _map []string) string { - i := 0 - for size >= unit { - size = size / unit - i++ - } - return fmt.Sprintf("%.4g %s", size, _map[i]) + return CustomSize("%.4g %s", size, 1024.0, binaryAbbrs) } // FromHumanSize returns an integer from a human-readable specification of a From 0e752adf550ef4891e40889ea92e12ec3d775a00 Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Wed, 29 Apr 2015 19:37:20 +0800 Subject: [PATCH 684/999] Fix docker rename help not consistent with other commands Signed-off-by: Lei Jitang --- api/client/rename.go | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/api/client/rename.go b/api/client/rename.go index 278f471f2..ebe16963d 100644 --- a/api/client/rename.go +++ b/api/client/rename.go @@ -1,20 +1,19 @@ package client -import "fmt" +import ( + "fmt" + + flag "github.com/docker/docker/pkg/mflag" +) // CmdRename renames a container. // // Usage: docker rename OLD_NAME NEW_NAME func (cli *DockerCli) CmdRename(args ...string) error { cmd := cli.Subcmd("rename", "OLD_NAME NEW_NAME", "Rename a container", true) - if err := cmd.Parse(args); err != nil { - return nil - } + cmd.Require(flag.Exact, 2) + cmd.ParseFlags(args, true) - if cmd.NArg() != 2 { - cmd.Usage() - return nil - } oldName := cmd.Arg(0) newName := cmd.Arg(1) From 534ed8c2d4573e88fcb68b23341504f8949b34b5 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Tue, 31 Mar 2015 13:49:41 -0700 Subject: [PATCH 685/999] Remove use of "DEBUG" env var from CLI and decouple DEBUG from --log-level Signed-off-by: Doug Davis --- api/client/info.go | 4 +--- docker/docker.go | 3 --- integration-cli/docker_cli_daemon_test.go | 12 ++++++------ 3 files changed, 7 insertions(+), 12 deletions(-) diff --git a/api/client/info.go b/api/client/info.go index 432ccac40..06a6f0ec5 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -3,7 +3,6 @@ package client import ( "encoding/json" "fmt" - "os" "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" @@ -45,9 +44,8 @@ func (cli *DockerCli) CmdInfo(args ...string) error { fmt.Fprintf(cli.out, "Name: %s\n", info.Name) fmt.Fprintf(cli.out, "ID: %s\n", info.ID) - if info.Debug || os.Getenv("DEBUG") != "" { + if info.Debug { fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug) - fmt.Fprintf(cli.out, "Debug mode (client): %v\n", os.Getenv("DEBUG") != "") fmt.Fprintf(cli.out, "File Descriptors: %d\n", info.NFd) fmt.Fprintf(cli.out, "Goroutines: %d\n", info.NGoroutines) fmt.Fprintf(cli.out, "System Time: %s\n", info.SystemTime) diff --git a/docker/docker.go b/docker/docker.go index 1096b840f..0fc08ad7f 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -52,11 +52,8 @@ func main() { setLogLevel(logrus.InfoLevel) } - // -D, --debug, -l/--log-level=debug processing - // When/if -D is removed this block can be deleted if *flDebug { os.Setenv("DEBUG", "1") - setLogLevel(logrus.DebugLevel) } if len(flHosts) == 0 { diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index e099995ad..98bcd1ad2 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -229,8 +229,8 @@ func (s *DockerDaemonSuite) TestDaemonFlagD(c *check.C) { c.Fatal(err) } content, _ := ioutil.ReadFile(s.d.logFile.Name()) - if !strings.Contains(string(content), `level=debug`) { - c.Fatalf(`Missing level="debug" in log file using -D:\n%s`, string(content)) + if strings.Contains(string(content), `level=debug`) { + c.Fatalf(`Should not have level="debug" in log file using -D:\n%s`, string(content)) } } @@ -239,8 +239,8 @@ func (s *DockerDaemonSuite) TestDaemonFlagDebug(c *check.C) { c.Fatal(err) } content, _ := ioutil.ReadFile(s.d.logFile.Name()) - if !strings.Contains(string(content), `level=debug`) { - c.Fatalf(`Missing level="debug" in log file using --debug:\n%s`, string(content)) + if strings.Contains(string(content), `level=debug`) { + c.Fatalf(`Should not have level="debug" in log file using --debug:\n%s`, string(content)) } } @@ -249,8 +249,8 @@ func (s *DockerDaemonSuite) TestDaemonFlagDebugLogLevelFatal(c *check.C) { c.Fatal(err) } content, _ := ioutil.ReadFile(s.d.logFile.Name()) - if !strings.Contains(string(content), `level=debug`) { - c.Fatalf(`Missing level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content)) + if strings.Contains(string(content), `level=debug`) { + c.Fatalf(`Should not have level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content)) } } From 8454e1a3b24e2e076bb08a2a6b1fcb56efe2924e Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Wed, 29 Apr 2015 16:27:12 +0200 Subject: [PATCH 686/999] Add coverage on pkg/fileutils Should fix #11598 Signed-off-by: Vincent Demeester --- pkg/fileutils/fileutils.go | 10 ++- pkg/fileutils/fileutils_test.go | 137 ++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+), 4 deletions(-) diff --git a/pkg/fileutils/fileutils.go b/pkg/fileutils/fileutils.go index d1e130d0a..fdafb53c7 100644 --- a/pkg/fileutils/fileutils.go +++ b/pkg/fileutils/fileutils.go @@ -118,18 +118,20 @@ func OptimizedMatches(file string, patterns []string, patDirs [][]string) (bool, } func CopyFile(src, dst string) (int64, error) { - if src == dst { + cleanSrc := filepath.Clean(src) + cleanDst := filepath.Clean(dst) + if cleanSrc == cleanDst { return 0, nil } - sf, err := os.Open(src) + sf, err := os.Open(cleanSrc) if err != nil { return 0, err } defer sf.Close() - if err := os.Remove(dst); err != nil && !os.IsNotExist(err) { + if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) { return 0, err } - df, err := os.Create(dst) + df, err := os.Create(cleanDst) if err != nil { return 0, err } diff --git a/pkg/fileutils/fileutils_test.go b/pkg/fileutils/fileutils_test.go index ce19ffce4..ef931684c 100644 --- a/pkg/fileutils/fileutils_test.go +++ b/pkg/fileutils/fileutils_test.go @@ -1,10 +1,125 @@ package fileutils import ( + "io/ioutil" "os" + "path" "testing" ) +// CopyFile with invalid src +func TestCopyFileWithInvalidSrc(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") + defer os.RemoveAll(tempFolder) + if err != nil { + t.Fatal(err) + } + bytes, err := CopyFile("/invalid/file/path", path.Join(tempFolder, "dest")) + if err == nil { + t.Fatal("Should have fail to copy an invalid src file") + } + if bytes != 0 { + t.Fatal("Should have written 0 bytes") + } + +} + +// CopyFile with invalid dest +func TestCopyFileWithInvalidDest(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") + defer os.RemoveAll(tempFolder) + if err != nil { + t.Fatal(err) + } + src := path.Join(tempFolder, "file") + err = ioutil.WriteFile(src, []byte("content"), 0740) + if err != nil { + t.Fatal(err) + } + bytes, err := CopyFile(src, path.Join(tempFolder, "/invalid/dest/path")) + if err == nil { + t.Fatal("Should have fail to copy an invalid src file") + } + if bytes != 0 { + t.Fatal("Should have written 0 bytes") + } + +} + +// CopyFile with same src and dest +func TestCopyFileWithSameSrcAndDest(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") + defer os.RemoveAll(tempFolder) + if err != nil { + t.Fatal(err) + } + file := path.Join(tempFolder, "file") + err = ioutil.WriteFile(file, []byte("content"), 0740) + if err != nil { + t.Fatal(err) + } + bytes, err := CopyFile(file, file) + if err != nil { + t.Fatal(err) + } + if bytes != 0 { + t.Fatal("Should have written 0 bytes as it is the same file.") + } +} + +// CopyFile with same src and dest but path is different and not clean +func TestCopyFileWithSameSrcAndDestWithPathNameDifferent(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") + defer os.RemoveAll(tempFolder) + if err != nil { + t.Fatal(err) + } + testFolder := path.Join(tempFolder, "test") + err = os.MkdirAll(testFolder, 0740) + if err != nil { + t.Fatal(err) + } + file := path.Join(testFolder, "file") + sameFile := testFolder + "/../test/file" + err = ioutil.WriteFile(file, []byte("content"), 0740) + if err != nil { + t.Fatal(err) + } + bytes, err := CopyFile(file, sameFile) + if err != nil { + t.Fatal(err) + } + if bytes != 0 { + t.Fatal("Should have written 0 bytes as it is the same file.") + } +} + +func TestCopyFile(t *testing.T) { + tempFolder, err := ioutil.TempDir("", "docker-fileutils-test") + defer os.RemoveAll(tempFolder) + if err != nil { + t.Fatal(err) + } + src := path.Join(tempFolder, "src") + dest := path.Join(tempFolder, "dest") + ioutil.WriteFile(src, []byte("content"), 0777) + ioutil.WriteFile(dest, []byte("destContent"), 0777) + bytes, err := CopyFile(src, dest) + if err != nil { + t.Fatal(err) + } + if bytes != 7 { + t.Fatalf("Should have written %d bytes but wrote %d", 7, bytes) + } + actual, err := ioutil.ReadFile(dest) + if err != nil { + t.Fatal(err) + } + if string(actual) != "content" { + t.Fatalf("Dest content was '%s', expected '%s'", string(actual), "content") + } +} + // Reading a symlink to a directory must return the directory func TestReadSymlinkedDirectoryExistingDirectory(t *testing.T) { var err error @@ -159,6 +274,28 @@ func TestExclusion(t *testing.T) { } } +// Matches with no patterns +func TestMatchesWithNoPatterns(t *testing.T) { + matches, err := Matches("/any/path/there", []string{}) + if err != nil { + t.Fatal(err) + } + if matches { + t.Fatalf("Should not have match anything") + } +} + +// Matches with malformed patterns +func TestMatchesWithMalformedPatterns(t *testing.T) { + matches, err := Matches("/any/path/there", []string{"["}) + if err == nil { + t.Fatal("Should have failed because of a malformed syntax in the pattern") + } + if matches { + t.Fatalf("Should not have match anything") + } +} + // An empty string should return true from Empty. func TestEmpty(t *testing.T) { empty := Empty("") From 4203230cbbf46238e38099c9073bdcad5f69a63f Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 27 Apr 2015 19:29:48 +0200 Subject: [PATCH 687/999] c.Fatal won't fail and exit test inside a goroutine, errors should be handled outside with a channel Signed-off-by: Antonio Murdaca --- integration-cli/docker_api_containers_test.go | 15 ++-- integration-cli/docker_cli_attach_test.go | 17 +++-- .../docker_cli_attach_unix_test.go | 33 +++++---- integration-cli/docker_cli_build_test.go | 71 +++++++------------ integration-cli/docker_cli_events_test.go | 39 ++++------ integration-cli/docker_cli_exec_test.go | 53 +++++++++----- integration-cli/docker_cli_logs_test.go | 23 +++--- integration-cli/docker_cli_ps_test.go | 1 - integration-cli/docker_cli_run_test.go | 55 +++++++------- integration-cli/docker_cli_run_unix_test.go | 11 +-- integration-cli/docker_cli_start_test.go | 7 +- 11 files changed, 156 insertions(+), 169 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 6cf2f9975..1fec3912e 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -260,15 +260,14 @@ func (s *DockerSuite) TestGetContainerStats(c *check.C) { c.Fatalf("Error on container creation: %v, output: %q", err, out) } type b struct { - body []byte - err error + status int + body []byte + err error } bc := make(chan b, 1) go func() { status, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil) - c.Assert(status, check.Equals, http.StatusOK) - c.Assert(err, check.IsNil) - bc <- b{body, err} + bc <- b{status, body, err} }() // allow some time to stream the stats from the container @@ -283,9 +282,8 @@ func (s *DockerSuite) TestGetContainerStats(c *check.C) { case <-time.After(2 * time.Second): c.Fatal("stream was not closed after container was removed") case sr := <-bc: - if sr.err != nil { - c.Fatal(sr.err) - } + c.Assert(sr.err, check.IsNil) + c.Assert(sr.status, check.Equals, http.StatusOK) dec := json.NewDecoder(bytes.NewBuffer(sr.body)) var s *types.Stats @@ -297,6 +295,7 @@ func (s *DockerSuite) TestGetContainerStats(c *check.C) { } func (s *DockerSuite) TestGetStoppedContainerStats(c *check.C) { + // TODO: this test does nothing because we are c.Assert'ing in goroutine var ( name = "statscontainer" runCmd = exec.Command(dockerBinary, "create", "--name", name, "busybox", "top") diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go index 4dd45ddff..fc2ea1a1d 100644 --- a/integration-cli/docker_cli_attach_test.go +++ b/integration-cli/docker_cli_attach_test.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "fmt" "io" "os/exec" "strings" @@ -89,7 +90,6 @@ func (s *DockerSuite) TestAttachMultipleAndRestart(c *check.C) { } func (s *DockerSuite) TestAttachTtyWithoutStdin(c *check.C) { - cmd := exec.Command(dockerBinary, "run", "-d", "-ti", "busybox") out, _, err := runCommandWithOutput(cmd) if err != nil { @@ -108,29 +108,32 @@ func (s *DockerSuite) TestAttachTtyWithoutStdin(c *check.C) { } }() - done := make(chan struct{}) + done := make(chan error) go func() { defer close(done) cmd := exec.Command(dockerBinary, "attach", id) if _, err := cmd.StdinPipe(); err != nil { - c.Fatal(err) + done <- err + return } expected := "cannot enable tty mode" if out, _, err := runCommandWithOutput(cmd); err == nil { - c.Fatal("attach should have failed") + done <- fmt.Errorf("attach should have failed") + return } else if !strings.Contains(out, expected) { - c.Fatalf("attach failed with error %q: expected %q", out, expected) + done <- fmt.Errorf("attach failed with error %q: expected %q", out, expected) + return } }() select { - case <-done: + case err := <-done: + c.Assert(err, check.IsNil) case <-time.After(attachWait): c.Fatal("attach is running but should have failed") } - } func (s *DockerSuite) TestAttachDisconnect(c *check.C) { diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go index bae83d994..82808a5b0 100644 --- a/integration-cli/docker_cli_attach_unix_test.go +++ b/integration-cli/docker_cli_attach_unix_test.go @@ -27,14 +27,14 @@ func (s *DockerSuite) TestAttachClosedOnContainerStop(c *check.C) { c.Fatal(err) } - done := make(chan struct{}) - + errChan := make(chan error) go func() { - defer close(done) + defer close(errChan) _, tty, err := pty.Open() if err != nil { - c.Fatalf("could not open pty: %v", err) + errChan <- err + return } attachCmd := exec.Command(dockerBinary, "attach", id) attachCmd.Stdin = tty @@ -42,7 +42,8 @@ func (s *DockerSuite) TestAttachClosedOnContainerStop(c *check.C) { attachCmd.Stderr = tty if err := attachCmd.Run(); err != nil { - c.Fatalf("attach returned error %s", err) + errChan <- err + return } }() @@ -51,7 +52,8 @@ func (s *DockerSuite) TestAttachClosedOnContainerStop(c *check.C) { c.Fatalf("error thrown while waiting for container: %s, %v", out, err) } select { - case <-done: + case err := <-errChan: + c.Assert(err, check.IsNil) case <-time.After(attachWait): c.Fatal("timed out without attach returning") } @@ -71,12 +73,10 @@ func (s *DockerSuite) TestAttachAfterDetach(c *check.C) { cmd.Stdout = tty cmd.Stderr = tty - detached := make(chan struct{}) + errChan := make(chan error) go func() { - if err := cmd.Run(); err != nil { - c.Fatalf("attach returned error %s", err) - } - close(detached) + errChan <- cmd.Run() + close(errChan) }() time.Sleep(500 * time.Millisecond) @@ -87,7 +87,12 @@ func (s *DockerSuite) TestAttachAfterDetach(c *check.C) { time.Sleep(100 * time.Millisecond) cpty.Write([]byte{17}) - <-detached + select { + case err := <-errChan: + c.Assert(err, check.IsNil) + case <-time.After(5 * time.Second): + c.Fatal("timeout while detaching") + } cpty, tty, err = pty.Open() if err != nil { @@ -119,9 +124,7 @@ func (s *DockerSuite) TestAttachAfterDetach(c *check.C) { select { case err := <-readErr: - if err != nil { - c.Fatal(err) - } + c.Assert(err, check.IsNil) case <-time.After(2 * time.Second): c.Fatal("timeout waiting for attach read") } diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 334342783..b74dce2cf 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -15,7 +15,6 @@ import ( "runtime" "strconv" "strings" - "sync" "text/template" "time" @@ -764,17 +763,17 @@ ADD test_file .`, } defer ctx.Close() - done := make(chan struct{}) + errChan := make(chan error) go func() { - if _, err := buildImageFromContext(name, ctx, true); err != nil { - c.Fatal(err) - } - close(done) + _, err := buildImageFromContext(name, ctx, true) + errChan <- err + close(errChan) }() select { case <-time.After(5 * time.Second): c.Fatal("Build with adding to workdir timed out") - case <-done: + case err := <-errChan: + c.Assert(err, check.IsNil) } } @@ -1365,17 +1364,17 @@ COPY test_file .`, } defer ctx.Close() - done := make(chan struct{}) + errChan := make(chan error) go func() { - if _, err := buildImageFromContext(name, ctx, true); err != nil { - c.Fatal(err) - } - close(done) + _, err := buildImageFromContext(name, ctx, true) + errChan <- err + close(errChan) }() select { case <-time.After(5 * time.Second): c.Fatal("Build with adding to workdir timed out") - case <-done: + case err := <-errChan: + c.Assert(err, check.IsNil) } } @@ -1829,9 +1828,6 @@ func (s *DockerSuite) TestBuildForceRm(c *check.C) { // * When docker events sees container start, close the "docker build" command // * Wait for docker events to emit a dying event. func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { - var wg sync.WaitGroup - defer wg.Wait() - name := "testbuildcancelation" // (Note: one year, will never finish) @@ -1849,26 +1845,21 @@ func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { containerID := make(chan string) startEpoch := daemonTime(c).Unix() + // Watch for events since epoch. + eventsCmd := exec.Command( + dockerBinary, "events", + "--since", strconv.FormatInt(startEpoch, 10)) + stdout, err := eventsCmd.StdoutPipe() + if err != nil { + c.Fatal(err) + } + if err := eventsCmd.Start(); err != nil { + c.Fatal(err) + } + defer eventsCmd.Process.Kill() - wg.Add(1) // Goroutine responsible for watching start/die events from `docker events` go func() { - defer wg.Done() - // Watch for events since epoch. - eventsCmd := exec.Command( - dockerBinary, "events", - "--since", strconv.FormatInt(startEpoch, 10)) - stdout, err := eventsCmd.StdoutPipe() - err = eventsCmd.Start() - if err != nil { - c.Fatalf("failed to start 'docker events': %s", err) - } - - go func() { - <-finish - eventsCmd.Process.Kill() - }() - cid := <-containerID matchStart := regexp.MustCompile(cid + `(.*) start$`) @@ -1886,19 +1877,13 @@ func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { close(eventDie) } } - - err = eventsCmd.Wait() - if err != nil && !IsKilled(err) { - c.Fatalf("docker events had bad exit status: %s", err) - } }() buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".") buildCmd.Dir = ctx.Dir stdoutBuild, err := buildCmd.StdoutPipe() - err = buildCmd.Start() - if err != nil { + if err := buildCmd.Start(); err != nil { c.Fatalf("failed to run build: %s", err) } @@ -1923,14 +1908,12 @@ func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) { // Send a kill to the `docker build` command. // Causes the underlying build to be cancelled due to socket close. - err = buildCmd.Process.Kill() - if err != nil { + if err := buildCmd.Process.Kill(); err != nil { c.Fatalf("error killing build command: %s", err) } // Get the exit status of `docker build`, check it exited because killed. - err = buildCmd.Wait() - if err != nil && !IsKilled(err) { + if err := buildCmd.Wait(); err != nil && !IsKilled(err) { c.Fatalf("wait failed during build run: %T %s", err, err) } diff --git a/integration-cli/docker_cli_events_test.go b/integration-cli/docker_cli_events_test.go index b1cc26b9a..80cc0c69d 100644 --- a/integration-cli/docker_cli_events_test.go +++ b/integration-cli/docker_cli_events_test.go @@ -75,8 +75,7 @@ func (s *DockerSuite) TestEventsLimit(c *check.C) { waitGroup.Add(1) go func() { defer waitGroup.Done() - err := exec.Command(dockerBinary, args...).Run() - errChan <- err + errChan <- exec.Command(dockerBinary, args...).Run() }() } @@ -229,8 +228,7 @@ func (s *DockerSuite) TestEventsImageImport(c *check.C) { if err != nil { c.Fatal(err) } - err = eventsCmd.Start() - if err != nil { + if err := eventsCmd.Start(); err != nil { c.Fatal(err) } defer eventsCmd.Process.Kill() @@ -424,30 +422,23 @@ func (s *DockerSuite) TestEventsFilterContainer(c *check.C) { func (s *DockerSuite) TestEventsStreaming(c *check.C) { start := daemonTime(c).Unix() - finish := make(chan struct{}) - defer close(finish) id := make(chan string) eventCreate := make(chan struct{}) eventStart := make(chan struct{}) eventDie := make(chan struct{}) eventDestroy := make(chan struct{}) + eventsCmd := exec.Command(dockerBinary, "events", "--since", strconv.FormatInt(start, 10)) + stdout, err := eventsCmd.StdoutPipe() + if err != nil { + c.Fatal(err) + } + if err := eventsCmd.Start(); err != nil { + c.Fatalf("failed to start 'docker events': %s", err) + } + defer eventsCmd.Process.Kill() + go func() { - eventsCmd := exec.Command(dockerBinary, "events", "--since", strconv.FormatInt(start, 10)) - stdout, err := eventsCmd.StdoutPipe() - if err != nil { - c.Fatal(err) - } - err = eventsCmd.Start() - if err != nil { - c.Fatalf("failed to start 'docker events': %s", err) - } - - go func() { - <-finish - eventsCmd.Process.Kill() - }() - containerID := <-id matchCreate := regexp.MustCompile(containerID + `: \(from busybox:latest\) create$`) @@ -468,11 +459,6 @@ func (s *DockerSuite) TestEventsStreaming(c *check.C) { close(eventDestroy) } } - - err = eventsCmd.Wait() - if err != nil && !IsKilled(err) { - c.Fatalf("docker events had bad exit status: %s", err) - } }() runCmd := exec.Command(dockerBinary, "run", "-d", "busybox:latest", "true") @@ -516,5 +502,4 @@ func (s *DockerSuite) TestEventsStreaming(c *check.C) { case <-eventDestroy: // ignore, done } - } diff --git a/integration-cli/docker_cli_exec_test.go b/integration-cli/docker_cli_exec_test.go index cbdd59756..4b36d7b53 100644 --- a/integration-cli/docker_cli_exec_test.go +++ b/integration-cli/docker_cli_exec_test.go @@ -74,15 +74,14 @@ func (s *DockerSuite) TestExecInteractive(c *check.C) { if err := stdin.Close(); err != nil { c.Fatal(err) } - finish := make(chan struct{}) + errChan := make(chan error) go func() { - if err := execCmd.Wait(); err != nil { - c.Fatal(err) - } - close(finish) + errChan <- execCmd.Wait() + close(errChan) }() select { - case <-finish: + case err := <-errChan: + c.Assert(err, check.IsNil) case <-time.After(1 * time.Second): c.Fatal("docker exec failed to exit on stdin close") } @@ -278,25 +277,29 @@ func (s *DockerSuite) TestExecTtyWithoutStdin(c *check.C) { } }() - done := make(chan struct{}) + errChan := make(chan error) go func() { - defer close(done) + defer close(errChan) cmd := exec.Command(dockerBinary, "exec", "-ti", id, "true") if _, err := cmd.StdinPipe(); err != nil { - c.Fatal(err) + errChan <- err + return } expected := "cannot enable tty mode" if out, _, err := runCommandWithOutput(cmd); err == nil { - c.Fatal("exec should have failed") + errChan <- fmt.Errorf("exec should have failed") + return } else if !strings.Contains(out, expected) { - c.Fatalf("exec failed with error %q: expected %q", out, expected) + errChan <- fmt.Errorf("exec failed with error %q: expected %q", out, expected) + return } }() select { - case <-done: + case err := <-errChan: + c.Assert(err, check.IsNil) case <-time.After(3 * time.Second): c.Fatal("exec is running but should have failed") } @@ -326,17 +329,22 @@ func (s *DockerSuite) TestExecStopNotHanging(c *check.C) { c.Fatal(err) } - wait := make(chan struct{}) + type dstop struct { + out []byte + err error + } + + ch := make(chan dstop) go func() { - if out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput(); err != nil { - c.Fatal(out, err) - } - close(wait) + out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput() + ch <- dstop{out, err} + close(ch) }() select { case <-time.After(3 * time.Second): c.Fatal("Container stop timed out") - case <-wait: + case s := <-ch: + c.Assert(s.err, check.IsNil) } } @@ -359,6 +367,7 @@ func (s *DockerSuite) TestExecCgroup(c *check.C) { var wg sync.WaitGroup var mu sync.Mutex execCgroups := []sort.StringSlice{} + errChan := make(chan error) // exec a few times concurrently to get consistent failure for i := 0; i < 5; i++ { wg.Add(1) @@ -366,7 +375,8 @@ func (s *DockerSuite) TestExecCgroup(c *check.C) { cmd := exec.Command(dockerBinary, "exec", "testing", "cat", "/proc/self/cgroup") out, _, err := runCommandWithOutput(cmd) if err != nil { - c.Fatal(out, err) + errChan <- err + return } cg := sort.StringSlice(strings.Split(string(out), "\n")) @@ -377,6 +387,11 @@ func (s *DockerSuite) TestExecCgroup(c *check.C) { }() } wg.Wait() + close(errChan) + + for err := range errChan { + c.Assert(err, check.IsNil) + } for _, cg := range execCgroups { if !reflect.DeepEqual(cg, containerCgroups) { diff --git a/integration-cli/docker_cli_logs_test.go b/integration-cli/docker_cli_logs_test.go index a7c891622..0a3e1af98 100644 --- a/integration-cli/docker_cli_logs_test.go +++ b/integration-cli/docker_cli_logs_test.go @@ -260,16 +260,15 @@ func (s *DockerSuite) TestLogsFollowStopped(c *check.C) { c.Fatal(err) } - ch := make(chan struct{}) + errChan := make(chan error) go func() { - if err := logsCmd.Wait(); err != nil { - c.Fatal(err) - } - close(ch) + errChan <- logsCmd.Wait() + close(errChan) }() select { - case <-ch: + case err := <-errChan: + c.Assert(err, check.IsNil) case <-time.After(1 * time.Second): c.Fatal("Following logs is hanged") } @@ -298,9 +297,7 @@ func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) { logCmd := exec.Command(dockerBinary, "logs", "-f", cleanedContainerID) stdout, err := logCmd.StdoutPipe() - if err != nil { - c.Fatal(err) - } + c.Assert(err, check.IsNil) if err := logCmd.Start(); err != nil { c.Fatal(err) @@ -308,15 +305,11 @@ func (s *DockerSuite) TestLogsFollowSlowStdoutConsumer(c *check.C) { // First read slowly bytes1, err := consumeWithSpeed(stdout, 10, 50*time.Millisecond, stopSlowRead) - if err != nil { - c.Fatal(err) - } + c.Assert(err, check.IsNil) // After the container has finished we can continue reading fast bytes2, err := consumeWithSpeed(stdout, 32*1024, 0, nil) - if err != nil { - c.Fatal(err) - } + c.Assert(err, check.IsNil) actual := bytes1 + bytes2 expected := 200000 diff --git a/integration-cli/docker_cli_ps_test.go b/integration-cli/docker_cli_ps_test.go index 271051815..bb34575fb 100644 --- a/integration-cli/docker_cli_ps_test.go +++ b/integration-cli/docker_cli_ps_test.go @@ -255,7 +255,6 @@ func assertContainerList(out string, expected []string) bool { } func (s *DockerSuite) TestPsListContainersSize(c *check.C) { - cmd := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", "hello") runCommandWithOutput(cmd) cmd = exec.Command(dockerBinary, "ps", "-s", "-n=1") diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index c3b25558d..0cf5c31ee 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -756,17 +756,22 @@ func (s *DockerSuite) TestRunTwoConcurrentContainers(c *check.C) { group := sync.WaitGroup{} group.Add(2) + errChan := make(chan error, 2) for i := 0; i < 2; i++ { go func() { defer group.Done() cmd := exec.Command(dockerBinary, "run", "busybox", "sleep", "2") - if _, err := runCommand(cmd); err != nil { - c.Fatal(err) - } + _, err := runCommand(cmd) + errChan <- err }() } group.Wait() + close(errChan) + + for err := range errChan { + c.Assert(err, check.IsNil) + } } func (s *DockerSuite) TestRunEnvironment(c *check.C) { @@ -1851,22 +1856,20 @@ func (s *DockerSuite) TestRunExitOnStdinClose(c *check.C) { if err := stdin.Close(); err != nil { c.Fatal(err) } - finish := make(chan struct{}) + finish := make(chan error) go func() { - if err := runCmd.Wait(); err != nil { - c.Fatal(err) - } + finish <- runCmd.Wait() close(finish) }() select { - case <-finish: + case err := <-finish: + c.Assert(err, check.IsNil) case <-time.After(1 * time.Second): c.Fatal("docker run failed to exit on stdin close") } state, err := inspectField(name, "State.Running") - if err != nil { - c.Fatal(err) - } + c.Assert(err, check.IsNil) + if state != "false" { c.Fatal("Container must be stopped after stdin closing") } @@ -2762,25 +2765,29 @@ func (s *DockerSuite) TestRunPortFromDockerRangeInUse(c *check.C) { } func (s *DockerSuite) TestRunTtyWithPipe(c *check.C) { - done := make(chan struct{}) + errChan := make(chan error) go func() { - defer close(done) + defer close(errChan) cmd := exec.Command(dockerBinary, "run", "-ti", "busybox", "true") if _, err := cmd.StdinPipe(); err != nil { - c.Fatal(err) + errChan <- err + return } expected := "cannot enable tty mode" if out, _, err := runCommandWithOutput(cmd); err == nil { - c.Fatal("run should have failed") + errChan <- fmt.Errorf("run should have failed") + return } else if !strings.Contains(out, expected) { - c.Fatalf("run failed with error %q: expected %q", out, expected) + errChan <- fmt.Errorf("run failed with error %q: expected %q", out, expected) + return } }() select { - case <-done: + case err := <-errChan: + c.Assert(err, check.IsNil) case <-time.After(3 * time.Second): c.Fatal("container is running but should have failed") } @@ -2875,19 +2882,19 @@ func (s *DockerSuite) TestRunAllowPortRangeThroughPublish(c *check.C) { } func (s *DockerSuite) TestRunOOMExitCode(c *check.C) { - done := make(chan struct{}) + errChan := make(chan error) go func() { - defer close(done) - + defer close(errChan) runCmd := exec.Command(dockerBinary, "run", "-m", "4MB", "busybox", "sh", "-c", "x=a; while true; do x=$x$x$x$x; done") out, exitCode, _ := runCommandWithOutput(runCmd) if expected := 137; exitCode != expected { - c.Fatalf("wrong exit code for OOM container: expected %d, got %d (output: %q)", expected, exitCode, out) + errChan <- fmt.Errorf("wrong exit code for OOM container: expected %d, got %d (output: %q)", expected, exitCode, out) } }() select { - case <-done: + case err := <-errChan: + c.Assert(err, check.IsNil) case <-time.After(30 * time.Second): c.Fatal("Timeout waiting for container to die on OOM") } @@ -3030,9 +3037,7 @@ func (s *DockerSuite) TestRunPidHostWithChildIsKillable(c *check.C) { }() select { case err := <-errchan: - if err != nil { - c.Fatal(err) - } + c.Assert(err, check.IsNil) case <-time.After(5 * time.Second): c.Fatal("Kill container timed out") } diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go index 43fa82150..59b623162 100644 --- a/integration-cli/docker_cli_run_unix_test.go +++ b/integration-cli/docker_cli_run_unix_test.go @@ -29,21 +29,22 @@ func (s *DockerSuite) TestRunRedirectStdout(c *check.C) { cmd.Stdin = tty cmd.Stdout = tty cmd.Stderr = tty - ch := make(chan struct{}) if err := cmd.Start(); err != nil { c.Fatalf("start err: %v", err) } + ch := make(chan error) go func() { - if err := cmd.Wait(); err != nil { - c.Fatalf("wait err=%v", err) - } + ch <- cmd.Wait() close(ch) }() select { case <-time.After(10 * time.Second): c.Fatal("command timeout") - case <-ch: + case err := <-ch: + if err != nil { + c.Fatalf("wait err=%v", err) + } } } diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go index 13ecedd57..fddc8c97b 100644 --- a/integration-cli/docker_cli_start_test.go +++ b/integration-cli/docker_cli_start_test.go @@ -20,18 +20,19 @@ func (s *DockerSuite) TestStartAttachReturnsOnError(c *check.C) { c.Fatal("Expected error but got none") } - ch := make(chan struct{}) + ch := make(chan error) go func() { // Attempt to start attached to the container that won't start // This should return an error immediately since the container can't be started if _, err := runCommand(exec.Command(dockerBinary, "start", "-a", "test2")); err == nil { - c.Fatal("Expected error but got none") + ch <- fmt.Errorf("Expected error but got none") } close(ch) }() select { - case <-ch: + case err := <-ch: + c.Assert(err, check.IsNil) case <-time.After(time.Second): c.Fatalf("Attach did not exit properly") } From f3f5ff9d837eecb97eeeb878f0bd416b6ab57cf2 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Mon, 27 Apr 2015 13:16:33 -0700 Subject: [PATCH 688/999] Integration tests for --bridge daemon flag Signed-off-by: Madhu Venugopal --- integration-cli/docker_cli_daemon_test.go | 41 +++++++++++++++++++++++ integration-cli/docker_utils.go | 9 +++-- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index e099995ad..3ebc880a0 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net" "os" "os/exec" "path/filepath" @@ -447,6 +448,46 @@ func (s *DockerDaemonSuite) TestDaemonExitOnFailure(c *check.C) { } } +func (s *DockerDaemonSuite) TestDaemonBridgeExternal(c *check.C) { + d := s.d + err := d.Start("--bridge", "nosuchbridge") + c.Assert(err, check.Not(check.IsNil), check.Commentf("--bridge option with an invalid bridge should cause the daemon to fail")) + + bridgeName := "external-bridge" + bridgeIp := "192.169.1.1/24" + _, bridgeIPNet, _ := net.ParseCIDR(bridgeIp) + + args := []string{"link", "add", "name", bridgeName, "type", "bridge"} + ipLinkCmd := exec.Command("ip", args...) + _, _, _, err = runCommandWithStdoutStderr(ipLinkCmd) + c.Assert(err, check.IsNil) + + ifCfgCmd := exec.Command("ifconfig", bridgeName, bridgeIp, "up") + _, _, _, err = runCommandWithStdoutStderr(ifCfgCmd) + c.Assert(err, check.IsNil) + + err = d.StartWithBusybox("--bridge", bridgeName) + c.Assert(err, check.IsNil) + + ipTablesSearchString := bridgeIPNet.String() + ipTablesCmd := exec.Command("iptables", "-t", "nat", "-nvL") + out, _, err := runCommandWithOutput(ipTablesCmd) + c.Assert(err, check.IsNil) + + c.Assert(strings.Contains(out, ipTablesSearchString), check.Equals, true, + check.Commentf("iptables output should have contained %q, but was %q", + ipTablesSearchString, out)) + + _, err = d.Cmd("run", "-d", "--name", "ExtContainer", "busybox", "top") + c.Assert(err, check.IsNil) + + containerIp := d.findContainerIP(c, "ExtContainer") + ip := net.ParseIP(containerIp) + c.Assert(bridgeIPNet.Contains(ip), check.Equals, true, + check.Commentf("Container IP-Address must be in the same subnet range : %s", + containerIp)) +} + func (s *DockerDaemonSuite) TestDaemonUlimitDefaults(c *check.C) { testRequires(c, NativeExecDriver) diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index 8386bb59f..a29b6c592 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -570,8 +570,9 @@ func dockerCmdInDirWithTimeout(timeout time.Duration, path string, args ...strin return out, status, err } -func findContainerIP(c *check.C, id string) string { - cmd := exec.Command(dockerBinary, "inspect", "--format='{{ .NetworkSettings.IPAddress }}'", id) +func findContainerIP(c *check.C, id string, vargs ...string) string { + args := append(vargs, "inspect", "--format='{{ .NetworkSettings.IPAddress }}'", id) + cmd := exec.Command(dockerBinary, args...) out, _, err := runCommandWithOutput(cmd) if err != nil { c.Fatal(err, out) @@ -580,6 +581,10 @@ func findContainerIP(c *check.C, id string) string { return strings.Trim(out, " \r\n'") } +func (d *Daemon) findContainerIP(c *check.C, id string) string { + return findContainerIP(c, id, "--host", d.sock()) +} + func getContainerCount() (int, error) { const containers = "Containers:" From 9c325c3f54b24621b76dee530a855b37cb22abcc Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Mon, 27 Apr 2015 20:36:40 -0700 Subject: [PATCH 689/999] Integration tests for --bip daemon flag Signed-off-by: Madhu Venugopal --- integration-cli/docker_cli_daemon_test.go | 78 +++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 3ebc880a0..7acbe4640 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -488,6 +488,71 @@ func (s *DockerDaemonSuite) TestDaemonBridgeExternal(c *check.C) { containerIp)) } +func deleteBridge(c *check.C, bridge string) { + ifCmd := exec.Command("ip", "link", "delete", bridge) + _, _, _, err := runCommandWithStdoutStderr(ifCmd) + c.Assert(err, check.IsNil) + + flushCmd := exec.Command("iptables", "-t", "nat", "--flush") + _, _, _, err = runCommandWithStdoutStderr(flushCmd) + c.Assert(err, check.IsNil) +} + +func (s *DockerDaemonSuite) TestDaemonBridgeIP(c *check.C) { + // TestDaemonBridgeIP Steps + // 1. Delete the existing docker0 Bridge + // 2. Set --bip daemon configuration and start the new Docker Daemon + // 3. Check if the bip config has taken effect using ifconfig and iptables commands + // 4. Launch a Container and make sure the IP-Address is in the expected subnet + // 5. Delete the docker0 Bridge + // 6. Restart the Docker Daemon (with no --bip settings) + // This Restart takes care of bringing docker0 interface back to auto-assigned IP + // 7. Stop the Docker Daemon (via defered action) + + defaultNetworkBridge := "docker0" + deleteBridge(c, defaultNetworkBridge) + + d := s.d + + bridgeIp := "192.169.1.1/24" + ip, bridgeIPNet, _ := net.ParseCIDR(bridgeIp) + + err := d.StartWithBusybox("--bip", bridgeIp) + c.Assert(err, check.IsNil) + + ifconfigSearchString := ip.String() + ifconfigCmd := exec.Command("ifconfig", defaultNetworkBridge) + out, _, _, err := runCommandWithStdoutStderr(ifconfigCmd) + c.Assert(err, check.IsNil) + + c.Assert(strings.Contains(out, ifconfigSearchString), check.Equals, true, + check.Commentf("ifconfig output should have contained %q, but was %q", + ifconfigSearchString, out)) + + ipTablesSearchString := bridgeIPNet.String() + ipTablesCmd := exec.Command("iptables", "-t", "nat", "-nvL") + out, _, err = runCommandWithOutput(ipTablesCmd) + c.Assert(err, check.IsNil) + + c.Assert(strings.Contains(out, ipTablesSearchString), check.Equals, true, + check.Commentf("iptables output should have contained %q, but was %q", + ipTablesSearchString, out)) + + out, err = d.Cmd("run", "-d", "--name", "test", "busybox", "top") + c.Assert(err, check.IsNil) + + containerIp := d.findContainerIP(c, "test") + ip = net.ParseIP(containerIp) + c.Assert(bridgeIPNet.Contains(ip), check.Equals, true, + check.Commentf("Container IP-Address must be in the same subnet range : %s", + containerIp)) + + // Reset to Defaults + deleteBridge(c, defaultNetworkBridge) + d.Restart() + pingContainers(c) +} + func (s *DockerDaemonSuite) TestDaemonUlimitDefaults(c *check.C) { testRequires(c, NativeExecDriver) @@ -930,3 +995,16 @@ func (s *DockerDaemonSuite) TestHttpsInfoRogueServerCert(c *check.C) { c.Fatalf("Expected err: %s, got instead: %s and output: %s", errCaUnknown, err, out) } } + +func pingContainers(c *check.C) { + runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "container1", + "--hostname", "fred", "busybox", "top") + _, err := runCommand(runCmd) + c.Assert(err, check.IsNil) + + runArgs := []string{"run", "--rm", "--link", "container1:alias1", "busybox", "sh", "-c"} + pingCmd := "ping -c 1 %s -W 1" + + dockerCmd(c, append(runArgs, fmt.Sprintf(pingCmd, "alias1"))...) + dockerCmd(c, "rm", "-f", "container1") +} From 0e254411b1fe0b5024d4a8e5ade7ce12f4545d8e Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Tue, 28 Apr 2015 08:55:04 -0700 Subject: [PATCH 690/999] Integration tests for --fixed-cidr daemon config Signed-off-by: Madhu Venugopal --- integration-cli/docker_cli_daemon_test.go | 36 +++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 7acbe4640..3dfcdfdb3 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "strconv" "strings" "time" @@ -486,6 +487,10 @@ func (s *DockerDaemonSuite) TestDaemonBridgeExternal(c *check.C) { c.Assert(bridgeIPNet.Contains(ip), check.Equals, true, check.Commentf("Container IP-Address must be in the same subnet range : %s", containerIp)) + + // Reset to Defaults + deleteBridge(c, bridgeName) + d.Restart() } func deleteBridge(c *check.C, bridge string) { @@ -553,6 +558,37 @@ func (s *DockerDaemonSuite) TestDaemonBridgeIP(c *check.C) { pingContainers(c) } +func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidr(c *check.C) { + d := s.d + + bridgeName := "external-bridge" + args := []string{"link", "add", "name", bridgeName, "type", "bridge"} + ipLinkCmd := exec.Command("ip", args...) + _, _, _, err := runCommandWithStdoutStderr(ipLinkCmd) + c.Assert(err, check.IsNil) + + ifCmd := exec.Command("ifconfig", bridgeName, "192.169.1.1/24", "up") + _, _, _, err = runCommandWithStdoutStderr(ifCmd) + c.Assert(err, check.IsNil) + + args = []string{"--bridge", bridgeName, "--fixed-cidr", "192.169.1.0/30"} + err = d.StartWithBusybox(args...) + c.Assert(err, check.IsNil) + + for i := 0; i < 4; i++ { + cName := "Container" + strconv.Itoa(i) + out, err := d.Cmd("run", "-d", "--name", cName, "busybox", "top") + if err != nil { + c.Assert(strings.Contains(out, "no available ip addresses"), check.Equals, true, + check.Commentf("Could not run a Container : %s %s", err.Error(), out)) + } + } + + // Reset to Defaults + deleteBridge(c, bridgeName) + d.Restart() +} + func (s *DockerDaemonSuite) TestDaemonUlimitDefaults(c *check.C) { testRequires(c, NativeExecDriver) From ba11929ebdf4cf7798cddc98c4dcfc000b154264 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Tue, 28 Apr 2015 10:26:59 -0700 Subject: [PATCH 691/999] Integration tests for --ip daemon option Signed-off-by: Madhu Venugopal --- integration-cli/docker_cli_daemon_test.go | 52 ++++++++++++++++++++--- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 3dfcdfdb3..5c5fdbd57 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -10,6 +10,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "strconv" "strings" "time" @@ -489,11 +490,11 @@ func (s *DockerDaemonSuite) TestDaemonBridgeExternal(c *check.C) { containerIp)) // Reset to Defaults - deleteBridge(c, bridgeName) + deleteInterface(c, bridgeName) d.Restart() } -func deleteBridge(c *check.C, bridge string) { +func deleteInterface(c *check.C, bridge string) { ifCmd := exec.Command("ip", "link", "delete", bridge) _, _, _, err := runCommandWithStdoutStderr(ifCmd) c.Assert(err, check.IsNil) @@ -515,7 +516,7 @@ func (s *DockerDaemonSuite) TestDaemonBridgeIP(c *check.C) { // 7. Stop the Docker Daemon (via defered action) defaultNetworkBridge := "docker0" - deleteBridge(c, defaultNetworkBridge) + deleteInterface(c, defaultNetworkBridge) d := s.d @@ -553,7 +554,7 @@ func (s *DockerDaemonSuite) TestDaemonBridgeIP(c *check.C) { containerIp)) // Reset to Defaults - deleteBridge(c, defaultNetworkBridge) + deleteInterface(c, defaultNetworkBridge) d.Restart() pingContainers(c) } @@ -585,7 +586,48 @@ func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidr(c *check.C) { } // Reset to Defaults - deleteBridge(c, bridgeName) + deleteInterface(c, bridgeName) + d.Restart() +} + +func (s *DockerDaemonSuite) TestDaemonIP(c *check.C) { + d := s.d + + ipStr := "192.170.1.1/24" + ip, _, _ := net.ParseCIDR(ipStr) + args := []string{"--ip", ip.String()} + err := d.StartWithBusybox(args...) + c.Assert(err, check.IsNil) + + out, err := d.Cmd("run", "-d", "-p", "8000:8000", "busybox", "top") + c.Assert(err, check.Not(check.IsNil), + check.Commentf("Running a container must fail with an invalid --ip option")) + c.Assert(strings.Contains(out, "Error starting userland proxy"), check.Equals, true) + + ifName := "dummy" + args = []string{"link", "add", "name", ifName, "type", "dummy"} + ipLinkCmd := exec.Command("ip", args...) + _, _, _, err = runCommandWithStdoutStderr(ipLinkCmd) + c.Assert(err, check.IsNil) + + ifCmd := exec.Command("ifconfig", ifName, ipStr, "up") + _, _, _, err = runCommandWithStdoutStderr(ifCmd) + c.Assert(err, check.IsNil) + + _, err = d.Cmd("run", "-d", "-p", "8000:8000", "busybox", "top") + c.Assert(err, check.IsNil) + + ipTablesCmd := exec.Command("iptables", "-t", "nat", "-nvL") + out, _, err = runCommandWithOutput(ipTablesCmd) + c.Assert(err, check.IsNil) + + regex := fmt.Sprintf("DNAT.*%s.*dpt:8000", ip.String()) + matched, _ := regexp.MatchString(regex, out) + c.Assert(matched, check.Equals, true, + check.Commentf("iptables output should have contained %q, but was %q", regex, out)) + + // Reset to Defaults + deleteInterface(c, ifName) d.Restart() } From dd0666e64f17329355c77aae1a2ac0fe2fe43402 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Tue, 28 Apr 2015 16:17:00 -0700 Subject: [PATCH 692/999] Integration Tests for --icc=false & container Linking using --expose Signed-off-by: Madhu Venugopal --- integration-cli/docker_cli_daemon_test.go | 163 ++++++++++++++++------ integration-cli/docker_utils.go | 4 +- 2 files changed, 124 insertions(+), 43 deletions(-) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 5c5fdbd57..738d9b0aa 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -453,20 +453,13 @@ func (s *DockerDaemonSuite) TestDaemonExitOnFailure(c *check.C) { func (s *DockerDaemonSuite) TestDaemonBridgeExternal(c *check.C) { d := s.d err := d.Start("--bridge", "nosuchbridge") - c.Assert(err, check.Not(check.IsNil), check.Commentf("--bridge option with an invalid bridge should cause the daemon to fail")) + c.Assert(err, check.NotNil, check.Commentf("--bridge option with an invalid bridge should cause the daemon to fail")) bridgeName := "external-bridge" bridgeIp := "192.169.1.1/24" _, bridgeIPNet, _ := net.ParseCIDR(bridgeIp) - args := []string{"link", "add", "name", bridgeName, "type", "bridge"} - ipLinkCmd := exec.Command("ip", args...) - _, _, _, err = runCommandWithStdoutStderr(ipLinkCmd) - c.Assert(err, check.IsNil) - - ifCfgCmd := exec.Command("ifconfig", bridgeName, bridgeIp, "up") - _, _, _, err = runCommandWithStdoutStderr(ifCfgCmd) - c.Assert(err, check.IsNil) + createInterface(c, "bridge", bridgeName, bridgeIp) err = d.StartWithBusybox("--bridge", bridgeName) c.Assert(err, check.IsNil) @@ -483,7 +476,7 @@ func (s *DockerDaemonSuite) TestDaemonBridgeExternal(c *check.C) { _, err = d.Cmd("run", "-d", "--name", "ExtContainer", "busybox", "top") c.Assert(err, check.IsNil) - containerIp := d.findContainerIP(c, "ExtContainer") + containerIp := d.findContainerIP("ExtContainer") ip := net.ParseIP(containerIp) c.Assert(bridgeIPNet.Contains(ip), check.Equals, true, check.Commentf("Container IP-Address must be in the same subnet range : %s", @@ -494,14 +487,29 @@ func (s *DockerDaemonSuite) TestDaemonBridgeExternal(c *check.C) { d.Restart() } +func createInterface(c *check.C, ifType string, ifName string, ipNet string) { + args := []string{"link", "add", "name", ifName, "type", ifType} + ipLinkCmd := exec.Command("ip", args...) + out, _, err := runCommandWithOutput(ipLinkCmd) + c.Assert(err, check.IsNil, check.Commentf(out)) + + ifCfgCmd := exec.Command("ifconfig", ifName, ipNet, "up") + out, _, err = runCommandWithOutput(ifCfgCmd) + c.Assert(err, check.IsNil, check.Commentf(out)) +} + func deleteInterface(c *check.C, bridge string) { ifCmd := exec.Command("ip", "link", "delete", bridge) - _, _, _, err := runCommandWithStdoutStderr(ifCmd) - c.Assert(err, check.IsNil) + out, _, err := runCommandWithOutput(ifCmd) + c.Assert(err, check.IsNil, check.Commentf(out)) flushCmd := exec.Command("iptables", "-t", "nat", "--flush") - _, _, _, err = runCommandWithStdoutStderr(flushCmd) - c.Assert(err, check.IsNil) + out, _, err = runCommandWithOutput(flushCmd) + c.Assert(err, check.IsNil, check.Commentf(out)) + + flushCmd = exec.Command("iptables", "--flush") + out, _, err = runCommandWithOutput(flushCmd) + c.Assert(err, check.IsNil, check.Commentf(out)) } func (s *DockerDaemonSuite) TestDaemonBridgeIP(c *check.C) { @@ -547,7 +555,7 @@ func (s *DockerDaemonSuite) TestDaemonBridgeIP(c *check.C) { out, err = d.Cmd("run", "-d", "--name", "test", "busybox", "top") c.Assert(err, check.IsNil) - containerIp := d.findContainerIP(c, "test") + containerIp := d.findContainerIP("test") ip = net.ParseIP(containerIp) c.Assert(bridgeIPNet.Contains(ip), check.Equals, true, check.Commentf("Container IP-Address must be in the same subnet range : %s", @@ -556,24 +564,19 @@ func (s *DockerDaemonSuite) TestDaemonBridgeIP(c *check.C) { // Reset to Defaults deleteInterface(c, defaultNetworkBridge) d.Restart() - pingContainers(c) + pingContainers(c, nil, false) } func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidr(c *check.C) { d := s.d bridgeName := "external-bridge" - args := []string{"link", "add", "name", bridgeName, "type", "bridge"} - ipLinkCmd := exec.Command("ip", args...) - _, _, _, err := runCommandWithStdoutStderr(ipLinkCmd) - c.Assert(err, check.IsNil) + bridgeIp := "192.169.1.1/24" - ifCmd := exec.Command("ifconfig", bridgeName, "192.169.1.1/24", "up") - _, _, _, err = runCommandWithStdoutStderr(ifCmd) - c.Assert(err, check.IsNil) + createInterface(c, "bridge", bridgeName, bridgeIp) - args = []string{"--bridge", bridgeName, "--fixed-cidr", "192.169.1.0/30"} - err = d.StartWithBusybox(args...) + args := []string{"--bridge", bridgeName, "--fixed-cidr", "192.169.1.0/30"} + err := d.StartWithBusybox(args...) c.Assert(err, check.IsNil) for i := 0; i < 4; i++ { @@ -600,19 +603,12 @@ func (s *DockerDaemonSuite) TestDaemonIP(c *check.C) { c.Assert(err, check.IsNil) out, err := d.Cmd("run", "-d", "-p", "8000:8000", "busybox", "top") - c.Assert(err, check.Not(check.IsNil), + c.Assert(err, check.NotNil, check.Commentf("Running a container must fail with an invalid --ip option")) c.Assert(strings.Contains(out, "Error starting userland proxy"), check.Equals, true) ifName := "dummy" - args = []string{"link", "add", "name", ifName, "type", "dummy"} - ipLinkCmd := exec.Command("ip", args...) - _, _, _, err = runCommandWithStdoutStderr(ipLinkCmd) - c.Assert(err, check.IsNil) - - ifCmd := exec.Command("ifconfig", ifName, ipStr, "up") - _, _, _, err = runCommandWithStdoutStderr(ifCmd) - c.Assert(err, check.IsNil) + createInterface(c, "dummy", ifName, ipStr) _, err = d.Cmd("run", "-d", "-p", "8000:8000", "busybox", "top") c.Assert(err, check.IsNil) @@ -631,6 +627,79 @@ func (s *DockerDaemonSuite) TestDaemonIP(c *check.C) { d.Restart() } +func (s *DockerDaemonSuite) TestDaemonICCPing(c *check.C) { + d := s.d + + bridgeName := "external-bridge" + bridgeIp := "192.169.1.1/24" + + createInterface(c, "bridge", bridgeName, bridgeIp) + + args := []string{"--bridge", bridgeName, "--icc=false"} + err := d.StartWithBusybox(args...) + c.Assert(err, check.IsNil) + + ipTablesCmd := exec.Command("iptables", "-nvL", "FORWARD") + out, _, err := runCommandWithOutput(ipTablesCmd) + c.Assert(err, check.IsNil) + + regex := fmt.Sprintf("DROP.*all.*%s.*%s", bridgeName, bridgeName) + matched, _ := regexp.MatchString(regex, out) + c.Assert(matched, check.Equals, true, + check.Commentf("iptables output should have contained %q, but was %q", regex, out)) + + // Pinging another container must fail with --icc=false + pingContainers(c, d, true) + + ipStr := "192.171.1.1/24" + ip, _, _ := net.ParseCIDR(ipStr) + ifName := "icc-dummy" + + createInterface(c, "dummy", ifName, ipStr) + + // But, Pinging external or a Host interface must succeed + pingCmd := fmt.Sprintf("ping -c 1 %s -W 1", ip.String()) + runArgs := []string{"--rm", "busybox", "sh", "-c", pingCmd} + _, err = d.Cmd("run", runArgs...) + c.Assert(err, check.IsNil) + + // Reset to Defaults + deleteInterface(c, ifName) + d.Restart() +} + +func (s *DockerDaemonSuite) TestDaemonICCLinkExpose(c *check.C) { + d := s.d + + bridgeName := "external-bridge" + bridgeIp := "192.169.1.1/24" + + createInterface(c, "bridge", bridgeName, bridgeIp) + + args := []string{"--bridge", bridgeName, "--icc=false"} + err := d.StartWithBusybox(args...) + c.Assert(err, check.IsNil) + + ipTablesCmd := exec.Command("iptables", "-nvL", "FORWARD") + out, _, err := runCommandWithOutput(ipTablesCmd) + c.Assert(err, check.IsNil) + + regex := fmt.Sprintf("DROP.*all.*%s.*%s", bridgeName, bridgeName) + matched, _ := regexp.MatchString(regex, out) + c.Assert(matched, check.Equals, true, + check.Commentf("iptables output should have contained %q, but was %q", regex, out)) + + _, err = d.Cmd("run", "-d", "--expose", "4567", "--name", "icc1", "busybox", "nc", "-l", "-p", "4567") + c.Assert(err, check.IsNil) + + out, err = d.Cmd("run", "--link", "icc1:icc1", "busybox", "nc", "icc1", "4567") + c.Assert(err, check.IsNil, check.Commentf(out)) + + // Reset to Defaults + deleteInterface(c, bridgeName) + d.Restart() +} + func (s *DockerDaemonSuite) TestDaemonUlimitDefaults(c *check.C) { testRequires(c, NativeExecDriver) @@ -1074,15 +1143,27 @@ func (s *DockerDaemonSuite) TestHttpsInfoRogueServerCert(c *check.C) { } } -func pingContainers(c *check.C) { - runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "container1", - "--hostname", "fred", "busybox", "top") - _, err := runCommand(runCmd) +func pingContainers(c *check.C, d *Daemon, expectFailure bool) { + var dargs []string + if d != nil { + dargs = []string{"--host", d.sock()} + } + + args := append(dargs, "run", "-d", "--name", "container1", "busybox", "top") + _, err := runCommand(exec.Command(dockerBinary, args...)) c.Assert(err, check.IsNil) - runArgs := []string{"run", "--rm", "--link", "container1:alias1", "busybox", "sh", "-c"} + args = append(dargs, "run", "--rm", "--link", "container1:alias1", "busybox", "sh", "-c") pingCmd := "ping -c 1 %s -W 1" + args = append(args, fmt.Sprintf(pingCmd, "alias1")) + _, err = runCommand(exec.Command(dockerBinary, args...)) - dockerCmd(c, append(runArgs, fmt.Sprintf(pingCmd, "alias1"))...) - dockerCmd(c, "rm", "-f", "container1") + if expectFailure { + c.Assert(err, check.NotNil) + } else { + c.Assert(err, check.IsNil) + } + + args = append(dargs, "rm", "-f", "container1") + runCommand(exec.Command(dockerBinary, args...)) } diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index a29b6c592..a1a845baf 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -581,8 +581,8 @@ func findContainerIP(c *check.C, id string, vargs ...string) string { return strings.Trim(out, " \r\n'") } -func (d *Daemon) findContainerIP(c *check.C, id string) string { - return findContainerIP(c, id, "--host", d.sock()) +func (d *Daemon) findContainerIP(id string) string { + return findContainerIP(d.c, id, "--host", d.sock()) } func getContainerCount() (int, error) { From 1c073ec1766e0f3cfe28d8f9c2e9a9a37154ece6 Mon Sep 17 00:00:00 2001 From: Madhu Venugopal Date: Wed, 29 Apr 2015 11:41:13 -0700 Subject: [PATCH 693/999] Moved explicit cleanups into defered action Signed-off-by: Madhu Venugopal --- integration-cli/docker_cli_daemon_test.go | 77 +++++++++++------------ 1 file changed, 35 insertions(+), 42 deletions(-) diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 738d9b0aa..17141ddef 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -454,19 +454,22 @@ func (s *DockerDaemonSuite) TestDaemonBridgeExternal(c *check.C) { d := s.d err := d.Start("--bridge", "nosuchbridge") c.Assert(err, check.NotNil, check.Commentf("--bridge option with an invalid bridge should cause the daemon to fail")) + defer d.Restart() bridgeName := "external-bridge" bridgeIp := "192.169.1.1/24" _, bridgeIPNet, _ := net.ParseCIDR(bridgeIp) - createInterface(c, "bridge", bridgeName, bridgeIp) + out, err := createInterface(c, "bridge", bridgeName, bridgeIp) + c.Assert(err, check.IsNil, check.Commentf(out)) + defer deleteInterface(c, bridgeName) err = d.StartWithBusybox("--bridge", bridgeName) c.Assert(err, check.IsNil) ipTablesSearchString := bridgeIPNet.String() ipTablesCmd := exec.Command("iptables", "-t", "nat", "-nvL") - out, _, err := runCommandWithOutput(ipTablesCmd) + out, _, err = runCommandWithOutput(ipTablesCmd) c.Assert(err, check.IsNil) c.Assert(strings.Contains(out, ipTablesSearchString), check.Equals, true, @@ -481,25 +484,23 @@ func (s *DockerDaemonSuite) TestDaemonBridgeExternal(c *check.C) { c.Assert(bridgeIPNet.Contains(ip), check.Equals, true, check.Commentf("Container IP-Address must be in the same subnet range : %s", containerIp)) - - // Reset to Defaults - deleteInterface(c, bridgeName) - d.Restart() } -func createInterface(c *check.C, ifType string, ifName string, ipNet string) { +func createInterface(c *check.C, ifType string, ifName string, ipNet string) (string, error) { args := []string{"link", "add", "name", ifName, "type", ifType} ipLinkCmd := exec.Command("ip", args...) out, _, err := runCommandWithOutput(ipLinkCmd) - c.Assert(err, check.IsNil, check.Commentf(out)) + if err != nil { + return out, err + } ifCfgCmd := exec.Command("ifconfig", ifName, ipNet, "up") out, _, err = runCommandWithOutput(ifCfgCmd) - c.Assert(err, check.IsNil, check.Commentf(out)) + return out, err } -func deleteInterface(c *check.C, bridge string) { - ifCmd := exec.Command("ip", "link", "delete", bridge) +func deleteInterface(c *check.C, ifName string) { + ifCmd := exec.Command("ip", "link", "delete", ifName) out, _, err := runCommandWithOutput(ifCmd) c.Assert(err, check.IsNil, check.Commentf(out)) @@ -519,9 +520,8 @@ func (s *DockerDaemonSuite) TestDaemonBridgeIP(c *check.C) { // 3. Check if the bip config has taken effect using ifconfig and iptables commands // 4. Launch a Container and make sure the IP-Address is in the expected subnet // 5. Delete the docker0 Bridge - // 6. Restart the Docker Daemon (with no --bip settings) + // 6. Restart the Docker Daemon (via defered action) // This Restart takes care of bringing docker0 interface back to auto-assigned IP - // 7. Stop the Docker Daemon (via defered action) defaultNetworkBridge := "docker0" deleteInterface(c, defaultNetworkBridge) @@ -533,6 +533,7 @@ func (s *DockerDaemonSuite) TestDaemonBridgeIP(c *check.C) { err := d.StartWithBusybox("--bip", bridgeIp) c.Assert(err, check.IsNil) + defer d.Restart() ifconfigSearchString := ip.String() ifconfigCmd := exec.Command("ifconfig", defaultNetworkBridge) @@ -560,11 +561,7 @@ func (s *DockerDaemonSuite) TestDaemonBridgeIP(c *check.C) { c.Assert(bridgeIPNet.Contains(ip), check.Equals, true, check.Commentf("Container IP-Address must be in the same subnet range : %s", containerIp)) - - // Reset to Defaults deleteInterface(c, defaultNetworkBridge) - d.Restart() - pingContainers(c, nil, false) } func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidr(c *check.C) { @@ -573,11 +570,14 @@ func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidr(c *check.C) { bridgeName := "external-bridge" bridgeIp := "192.169.1.1/24" - createInterface(c, "bridge", bridgeName, bridgeIp) + out, err := createInterface(c, "bridge", bridgeName, bridgeIp) + c.Assert(err, check.IsNil, check.Commentf(out)) + defer deleteInterface(c, bridgeName) args := []string{"--bridge", bridgeName, "--fixed-cidr", "192.169.1.0/30"} - err := d.StartWithBusybox(args...) + err = d.StartWithBusybox(args...) c.Assert(err, check.IsNil) + defer d.Restart() for i := 0; i < 4; i++ { cName := "Container" + strconv.Itoa(i) @@ -587,10 +587,6 @@ func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidr(c *check.C) { check.Commentf("Could not run a Container : %s %s", err.Error(), out)) } } - - // Reset to Defaults - deleteInterface(c, bridgeName) - d.Restart() } func (s *DockerDaemonSuite) TestDaemonIP(c *check.C) { @@ -601,6 +597,7 @@ func (s *DockerDaemonSuite) TestDaemonIP(c *check.C) { args := []string{"--ip", ip.String()} err := d.StartWithBusybox(args...) c.Assert(err, check.IsNil) + defer d.Restart() out, err := d.Cmd("run", "-d", "-p", "8000:8000", "busybox", "top") c.Assert(err, check.NotNil, @@ -608,7 +605,9 @@ func (s *DockerDaemonSuite) TestDaemonIP(c *check.C) { c.Assert(strings.Contains(out, "Error starting userland proxy"), check.Equals, true) ifName := "dummy" - createInterface(c, "dummy", ifName, ipStr) + out, err = createInterface(c, "dummy", ifName, ipStr) + c.Assert(err, check.IsNil, check.Commentf(out)) + defer deleteInterface(c, ifName) _, err = d.Cmd("run", "-d", "-p", "8000:8000", "busybox", "top") c.Assert(err, check.IsNil) @@ -621,10 +620,6 @@ func (s *DockerDaemonSuite) TestDaemonIP(c *check.C) { matched, _ := regexp.MatchString(regex, out) c.Assert(matched, check.Equals, true, check.Commentf("iptables output should have contained %q, but was %q", regex, out)) - - // Reset to Defaults - deleteInterface(c, ifName) - d.Restart() } func (s *DockerDaemonSuite) TestDaemonICCPing(c *check.C) { @@ -633,14 +628,17 @@ func (s *DockerDaemonSuite) TestDaemonICCPing(c *check.C) { bridgeName := "external-bridge" bridgeIp := "192.169.1.1/24" - createInterface(c, "bridge", bridgeName, bridgeIp) + out, err := createInterface(c, "bridge", bridgeName, bridgeIp) + c.Assert(err, check.IsNil, check.Commentf(out)) + defer deleteInterface(c, bridgeName) args := []string{"--bridge", bridgeName, "--icc=false"} - err := d.StartWithBusybox(args...) + err = d.StartWithBusybox(args...) c.Assert(err, check.IsNil) + defer d.Restart() ipTablesCmd := exec.Command("iptables", "-nvL", "FORWARD") - out, _, err := runCommandWithOutput(ipTablesCmd) + out, _, err = runCommandWithOutput(ipTablesCmd) c.Assert(err, check.IsNil) regex := fmt.Sprintf("DROP.*all.*%s.*%s", bridgeName, bridgeName) @@ -662,10 +660,6 @@ func (s *DockerDaemonSuite) TestDaemonICCPing(c *check.C) { runArgs := []string{"--rm", "busybox", "sh", "-c", pingCmd} _, err = d.Cmd("run", runArgs...) c.Assert(err, check.IsNil) - - // Reset to Defaults - deleteInterface(c, ifName) - d.Restart() } func (s *DockerDaemonSuite) TestDaemonICCLinkExpose(c *check.C) { @@ -674,14 +668,17 @@ func (s *DockerDaemonSuite) TestDaemonICCLinkExpose(c *check.C) { bridgeName := "external-bridge" bridgeIp := "192.169.1.1/24" - createInterface(c, "bridge", bridgeName, bridgeIp) + out, err := createInterface(c, "bridge", bridgeName, bridgeIp) + c.Assert(err, check.IsNil, check.Commentf(out)) + defer deleteInterface(c, bridgeName) args := []string{"--bridge", bridgeName, "--icc=false"} - err := d.StartWithBusybox(args...) + err = d.StartWithBusybox(args...) c.Assert(err, check.IsNil) + defer d.Restart() ipTablesCmd := exec.Command("iptables", "-nvL", "FORWARD") - out, _, err := runCommandWithOutput(ipTablesCmd) + out, _, err = runCommandWithOutput(ipTablesCmd) c.Assert(err, check.IsNil) regex := fmt.Sprintf("DROP.*all.*%s.*%s", bridgeName, bridgeName) @@ -694,10 +691,6 @@ func (s *DockerDaemonSuite) TestDaemonICCLinkExpose(c *check.C) { out, err = d.Cmd("run", "--link", "icc1:icc1", "busybox", "nc", "icc1", "4567") c.Assert(err, check.IsNil, check.Commentf(out)) - - // Reset to Defaults - deleteInterface(c, bridgeName) - d.Restart() } func (s *DockerDaemonSuite) TestDaemonUlimitDefaults(c *check.C) { From c271c61feea7d3ea3fcbb3af9f0d9c1f641a8d82 Mon Sep 17 00:00:00 2001 From: Aaron Davidson Date: Sun, 12 Apr 2015 16:56:05 -0700 Subject: [PATCH 694/999] Do our best not to invoke iptables concurrently if --wait is unsupported We encountered a situation where concurrent invocations of the docker daemon on a machine with an older version of iptables led to nondeterministic errors related to simultaenous invocations of iptables. While this is best resolved by upgrading iptables itself, the particular situation would have been avoided if the docker daemon simply took care not to concurrently invoke iptables. Of course, external processes could also cause iptables to fail in this way, but invoking docker in parallel seems like a pretty common case. Signed-off-by: Aaron Davidson --- pkg/iptables/iptables.go | 10 +++++++-- pkg/iptables/iptables_test.go | 40 +++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index 0cfcca750..fcedd0f34 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -8,6 +8,7 @@ import ( "regexp" "strconv" "strings" + "sync" "github.com/Sirupsen/logrus" ) @@ -25,8 +26,10 @@ const ( ) var ( - iptablesPath string - supportsXlock = false + iptablesPath string + supportsXlock = false + // used to lock iptables commands if xtables lock is not supported + bestEffortLock sync.Mutex ErrIptablesNotFound = errors.New("Iptables not found") ) @@ -288,6 +291,9 @@ func Raw(args ...string) ([]byte, error) { } if supportsXlock { args = append([]string{"--wait"}, args...) + } else { + bestEffortLock.Lock() + defer bestEffortLock.Unlock() } logrus.Debugf("%s, %v", iptablesPath, args) diff --git a/pkg/iptables/iptables_test.go b/pkg/iptables/iptables_test.go index ced4262ce..840aca14b 100644 --- a/pkg/iptables/iptables_test.go +++ b/pkg/iptables/iptables_test.go @@ -5,6 +5,7 @@ import ( "os/exec" "strconv" "strings" + "sync" "testing" ) @@ -169,6 +170,45 @@ func TestOutput(t *testing.T) { } } +func TestConcurrencyWithWait(t *testing.T) { + RunConcurrencyTest(t, true) +} + +func TestConcurrencyNoWait(t *testing.T) { + RunConcurrencyTest(t, false) +} + +// Runs 10 concurrent rule additions. This will fail if iptables +// is actually invoked simultaneously without --wait. +// Note that if iptables does not support the xtable lock on this +// system, then allowXlock has no effect -- it will always be off. +func RunConcurrencyTest(t *testing.T, allowXlock bool) { + var wg sync.WaitGroup + + if !allowXlock && supportsXlock { + supportsXlock = false + defer func() { supportsXlock = true }() + } + + ip := net.ParseIP("192.168.1.1") + port := 1234 + dstAddr := "172.17.0.1" + dstPort := 4321 + proto := "tcp" + + for i := 0; i < 10; i++ { + wg.Add(1) + go func() { + defer wg.Done() + err := natChain.Forward(Append, ip, port, proto, dstAddr, dstPort) + if err != nil { + t.Fatal(err) + } + }() + } + wg.Wait() +} + func TestCleanup(t *testing.T) { var err error var rules []byte From c0a1b2d6e99d5c6e29a016da39c1313a54c1cb34 Mon Sep 17 00:00:00 2001 From: Ahmet Alp Balkan Date: Mon, 20 Apr 2015 16:15:27 -0700 Subject: [PATCH 695/999] docs: Add more places docker.service can be at More systemd goodness. Documenting where `docker.service` lives under Ubuntu 15. Signed-off-by: Ahmet Alp Balkan --- docs/sources/articles/systemd.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/articles/systemd.md b/docs/sources/articles/systemd.md index fddd146b0..10baf6d6f 100644 --- a/docs/sources/articles/systemd.md +++ b/docs/sources/articles/systemd.md @@ -30,8 +30,8 @@ If the `docker.service` file is set to use an `EnvironmentFile` (often pointing to `/etc/sysconfig/docker`) then you can modify the referenced file. -Or, you may need to edit the `docker.service` file, which can be in `/usr/lib/systemd/system` -or `/etc/systemd/service`. +Or, you may need to edit the `docker.service` file, which can be in +`/usr/lib/systemd/system`, `/etc/systemd/service`, or `/lib/systemd/system`. ### Runtime directory and storage driver From 531f4122bdcd4de289f613a5ef010f4c1989f098 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 27 Apr 2015 23:11:29 +0200 Subject: [PATCH 696/999] Remove engine mechanism Signed-off-by: Antonio Murdaca --- api/client/start.go | 13 +- api/client/utils.go | 15 +- api/client/version.go | 29 ++-- api/server/server.go | 171 ++++++++----------- daemon/container.go | 14 +- daemon/daemon.go | 183 ++++++++------------ docker/daemon.go | 96 ++++++++--- engine/engine.go | 255 ---------------------------- engine/engine_test.go | 236 -------------------------- engine/env.go | 313 ---------------------------------- engine/env_test.go | 366 ---------------------------------------- engine/hack.go | 21 --- engine/helpers_test.go | 11 -- engine/http.go | 42 ----- engine/job.go | 222 ------------------------ engine/job_test.go | 47 ------ engine/shutdown_test.go | 78 --------- engine/streams.go | 188 --------------------- engine/streams_test.go | 215 ----------------------- pkg/pidfile/pidfile.go | 10 +- 20 files changed, 239 insertions(+), 2286 deletions(-) delete mode 100644 engine/engine.go delete mode 100644 engine/engine_test.go delete mode 100644 engine/env.go delete mode 100644 engine/env_test.go delete mode 100644 engine/hack.go delete mode 100644 engine/helpers_test.go delete mode 100644 engine/http.go delete mode 100644 engine/job.go delete mode 100644 engine/job_test.go delete mode 100644 engine/shutdown_test.go delete mode 100644 engine/streams.go delete mode 100644 engine/streams_test.go diff --git a/api/client/start.go b/api/client/start.go index d3dec9489..b290524ca 100644 --- a/api/client/start.go +++ b/api/client/start.go @@ -1,13 +1,14 @@ package client import ( + "encoding/json" "fmt" "io" "net/url" "os" "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" + "github.com/docker/docker/api/types" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/signal" @@ -65,12 +66,12 @@ func (cli *DockerCli) CmdStart(args ...string) error { return err } - env := engine.Env{} - if err := env.Decode(stream); err != nil { + var c types.ContainerJSON + if err := json.NewDecoder(stream).Decode(&c); err != nil { return err } - config := env.GetSubEnv("Config") - tty = config.GetBool("Tty") + + tty = c.Config.Tty if !tty { sigc := cli.forwardAllSignals(cmd.Arg(0)) @@ -82,7 +83,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { v := url.Values{} v.Set("stream", "1") - if *openStdin && config.GetBool("OpenStdin") { + if *openStdin && c.Config.OpenStdin { v.Set("stdin", "1") in = cli.in } diff --git a/api/client/utils.go b/api/client/utils.go index 7a52ad25f..eed1163f8 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -22,7 +22,6 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" "github.com/docker/docker/cliconfig" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/stdcopy" @@ -42,18 +41,8 @@ func (cli *DockerCli) HTTPClient() *http.Client { func (cli *DockerCli) encodeData(data interface{}) (*bytes.Buffer, error) { params := bytes.NewBuffer(nil) if data != nil { - if env, ok := data.(engine.Env); ok { - if err := env.Encode(params); err != nil { - return nil, err - } - } else { - buf, err := json.Marshal(data) - if err != nil { - return nil, err - } - if _, err := params.Write(buf); err != nil { - return nil, err - } + if err := json.NewEncoder(params).Encode(data); err != nil { + return nil, err } } return params, nil diff --git a/api/client/version.go b/api/client/version.go index 25a7e367e..2fb6f8a8d 100644 --- a/api/client/version.go +++ b/api/client/version.go @@ -1,13 +1,14 @@ package client import ( + "encoding/json" "fmt" "runtime" "github.com/Sirupsen/logrus" "github.com/docker/docker/api" + "github.com/docker/docker/api/types" "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/engine" flag "github.com/docker/docker/pkg/mflag" ) @@ -32,28 +33,24 @@ func (cli *DockerCli) CmdVersion(args ...string) error { } fmt.Fprintf(cli.out, "OS/Arch (client): %s/%s\n", runtime.GOOS, runtime.GOARCH) - body, _, err := readBody(cli.call("GET", "/version", nil, nil)) + stream, _, err := cli.call("GET", "/version", nil, nil) if err != nil { return err } - out := engine.NewOutput() - remoteVersion, err := out.AddEnv() - if err != nil { + var v types.Version + if err := json.NewDecoder(stream).Decode(&v); err != nil { logrus.Errorf("Error reading remote version: %s", err) return err } - if _, err := out.Write(body); err != nil { - logrus.Errorf("Error reading remote version: %s", err) - return err + + fmt.Fprintf(cli.out, "Server version: %s\n", v.Version) + if v.ApiVersion != "" { + fmt.Fprintf(cli.out, "Server API version: %s\n", v.ApiVersion) } - 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, "Go version (server): %s\n", remoteVersion.Get("GoVersion")) - fmt.Fprintf(cli.out, "Git commit (server): %s\n", remoteVersion.Get("GitCommit")) - fmt.Fprintf(cli.out, "OS/Arch (server): %s/%s\n", remoteVersion.Get("Os"), remoteVersion.Get("Arch")) + fmt.Fprintf(cli.out, "Go version (server): %s\n", v.GoVersion) + fmt.Fprintf(cli.out, "Git commit (server): %s\n", v.GitCommit) + fmt.Fprintf(cli.out, "OS/Arch (server): %s/%s\n", v.Os, v.Arch) + return nil } diff --git a/api/server/server.go b/api/server/server.go index 61e816265..3a7975fe6 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1,9 +1,6 @@ package server import ( - "runtime" - "time" - "encoding/base64" "encoding/json" "fmt" @@ -11,8 +8,10 @@ import ( "net" "net/http" "os" + "runtime" "strconv" "strings" + "time" "code.google.com/p/go.net/websocket" "github.com/gorilla/mux" @@ -25,7 +24,6 @@ import ( "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" "github.com/docker/docker/daemon/networkdriver/bridge" - "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/pkg/jsonmessage" "github.com/docker/docker/pkg/parsers" @@ -53,26 +51,31 @@ type ServerConfig struct { } type Server struct { - daemon *daemon.Daemon - cfg *ServerConfig - router *mux.Router - start chan struct{} - - // TODO: delete engine - eng *engine.Engine + daemon *daemon.Daemon + cfg *ServerConfig + router *mux.Router + start chan struct{} + servers []serverCloser } -func New(cfg *ServerConfig, eng *engine.Engine) *Server { +func New(cfg *ServerConfig) *Server { srv := &Server{ cfg: cfg, start: make(chan struct{}), - eng: eng, } - r := createRouter(srv, eng) + r := createRouter(srv) srv.router = r return srv } +func (s *Server) Close() { + for _, srv := range s.servers { + if err := srv.Close(); err != nil { + logrus.Error(err) + } + } +} + func (s *Server) SetDaemon(d *daemon.Daemon) { s.daemon = d } @@ -92,19 +95,15 @@ func (s *Server) ServeApi(protoAddrs []string) error { if len(protoAddrParts) != 2 { return fmt.Errorf("bad format, expected PROTO://ADDR") } + srv, err := s.newServer(protoAddrParts[0], protoAddrParts[1]) + if err != nil { + return err + } + s.servers = append(s.servers, srv) + go func(proto, addr string) { logrus.Infof("Listening for HTTP on %s (%s)", proto, addr) - srv, err := s.newServer(proto, addr) - if err != nil { - chErrors <- err - return - } - s.eng.OnShutdown(func() { - if err := srv.Close(); err != nil { - logrus.Error(err) - } - }) - if err = srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { + if err := srv.Serve(); err != nil && strings.Contains(err.Error(), "use of closed network connection") { err = nil } chErrors <- err @@ -133,7 +132,7 @@ func (s *HttpServer) Close() error { return s.l.Close() } -type HttpApiFunc func(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error +type HttpApiFunc func(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) { conn, _, err := w.(http.Hijacker).Hijack() @@ -230,16 +229,7 @@ func writeJSON(w http.ResponseWriter, code int, v interface{}) error { return json.NewEncoder(w).Encode(v) } -func streamJSON(out *engine.Output, w http.ResponseWriter, flush bool) { - w.Header().Set("Content-Type", "application/json") - if flush { - out.Add(utils.NewWriteFlusher(w)) - } else { - out.Add(w) - } -} - -func (s *Server) postAuth(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postAuth(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var config *cliconfig.AuthConfig err := json.NewDecoder(r.Body).Decode(&config) r.Body.Close() @@ -255,7 +245,7 @@ func (s *Server) postAuth(eng *engine.Engine, version version.Version, w http.Re }) } -func (s *Server) getVersion(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getVersion(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") v := &types.Version{ @@ -273,7 +263,7 @@ func (s *Server) getVersion(eng *engine.Engine, version version.Version, w http. return writeJSON(w, http.StatusOK, v) } -func (s *Server) postContainersKill(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersKill(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -308,7 +298,7 @@ func (s *Server) postContainersKill(eng *engine.Engine, version version.Version, return nil } -func (s *Server) postContainersPause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersPause(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -332,7 +322,7 @@ func (s *Server) postContainersPause(eng *engine.Engine, version version.Version return nil } -func (s *Server) postContainersUnpause(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersUnpause(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -356,7 +346,7 @@ func (s *Server) postContainersUnpause(eng *engine.Engine, version version.Versi return nil } -func (s *Server) getContainersExport(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersExport(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -364,7 +354,7 @@ func (s *Server) getContainersExport(eng *engine.Engine, version version.Version return s.daemon.ContainerExport(vars["name"], w) } -func (s *Server) getImagesJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -405,7 +395,7 @@ func (s *Server) getImagesJSON(eng *engine.Engine, version version.Version, w ht return writeJSON(w, http.StatusOK, legacyImages) } -func (s *Server) getInfo(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getInfo(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.Header().Set("Content-Type", "application/json") info, err := s.daemon.SystemInfo() @@ -416,7 +406,7 @@ func (s *Server) getInfo(eng *engine.Engine, version version.Version, w http.Res return writeJSON(w, http.StatusOK, info) } -func (s *Server) getEvents(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getEvents(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -520,7 +510,7 @@ func (s *Server) getEvents(eng *engine.Engine, version version.Version, w http.R } } -func (s *Server) getImagesHistory(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesHistory(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -534,7 +524,7 @@ func (s *Server) getImagesHistory(eng *engine.Engine, version version.Version, w return writeJSON(w, http.StatusOK, history) } -func (s *Server) getContainersChanges(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersChanges(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -553,7 +543,7 @@ func (s *Server) getContainersChanges(eng *engine.Engine, version version.Versio return writeJSON(w, http.StatusOK, changes) } -func (s *Server) getContainersTop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersTop(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version.LessThan("1.4") { return fmt.Errorf("top was improved a lot since 1.3, Please upgrade your docker client.") } @@ -574,7 +564,7 @@ func (s *Server) getContainersTop(eng *engine.Engine, version version.Version, w return writeJSON(w, http.StatusOK, procList) } -func (s *Server) getContainersJSON(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersJSON(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -603,7 +593,7 @@ func (s *Server) getContainersJSON(eng *engine.Engine, version version.Version, return writeJSON(w, http.StatusOK, containers) } -func (s *Server) getContainersStats(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersStats(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -614,7 +604,7 @@ func (s *Server) getContainersStats(eng *engine.Engine, version version.Version, return s.daemon.ContainerStats(vars["name"], utils.NewWriteFlusher(w)) } -func (s *Server) getContainersLogs(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersLogs(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -644,7 +634,7 @@ func (s *Server) getContainersLogs(eng *engine.Engine, version version.Version, return nil } -func (s *Server) postImagesTag(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesTag(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -662,7 +652,7 @@ func (s *Server) postImagesTag(eng *engine.Engine, version version.Version, w ht return nil } -func (s *Server) postCommit(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postCommit(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -708,7 +698,7 @@ func (s *Server) postCommit(eng *engine.Engine, version version.Version, w http. } // Creates an image from Pull or from Import -func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -788,7 +778,7 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w return nil } -func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesSearch(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -818,7 +808,7 @@ func (s *Server) getImagesSearch(eng *engine.Engine, version version.Version, w return json.NewEncoder(w).Encode(query.Results) } -func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesPush(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -875,7 +865,7 @@ func (s *Server) postImagesPush(eng *engine.Engine, version version.Version, w h } -func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesGet(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -907,11 +897,11 @@ func (s *Server) getImagesGet(eng *engine.Engine, version version.Version, w htt } -func (s *Server) postImagesLoad(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postImagesLoad(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { return s.daemon.Repositories().Load(r.Body, w) } -func (s *Server) postContainersCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -939,7 +929,7 @@ func (s *Server) postContainersCreate(eng *engine.Engine, version version.Versio }) } -func (s *Server) postContainersRestart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersRestart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -961,7 +951,7 @@ func (s *Server) postContainersRestart(eng *engine.Engine, version version.Versi return nil } -func (s *Server) postContainerRename(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerRename(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -978,7 +968,7 @@ func (s *Server) postContainerRename(eng *engine.Engine, version version.Version return nil } -func (s *Server) deleteContainers(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) deleteContainers(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1006,7 +996,7 @@ func (s *Server) deleteContainers(eng *engine.Engine, version version.Version, w return nil } -func (s *Server) deleteImages(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) deleteImages(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1026,7 +1016,7 @@ func (s *Server) deleteImages(eng *engine.Engine, version version.Version, w htt return writeJSON(w, http.StatusOK, list) } -func (s *Server) postContainersStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1062,7 +1052,7 @@ func (s *Server) postContainersStart(eng *engine.Engine, version version.Version return nil } -func (s *Server) postContainersStop(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersStop(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1087,7 +1077,7 @@ func (s *Server) postContainersStop(eng *engine.Engine, version version.Version, return nil } -func (s *Server) postContainersWait(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersWait(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1105,7 +1095,7 @@ func (s *Server) postContainersWait(eng *engine.Engine, version version.Version, }) } -func (s *Server) postContainersResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersResize(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1130,7 +1120,7 @@ func (s *Server) postContainersResize(eng *engine.Engine, version version.Versio return cont.Resize(height, width) } -func (s *Server) postContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1185,7 +1175,7 @@ func (s *Server) postContainersAttach(eng *engine.Engine, version version.Versio return nil } -func (s *Server) wsContainersAttach(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) wsContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1211,7 +1201,7 @@ func (s *Server) wsContainersAttach(eng *engine.Engine, version version.Version, return nil } -func (s *Server) getContainersByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getContainersByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1232,7 +1222,7 @@ func (s *Server) getContainersByName(eng *engine.Engine, version version.Version return writeJSON(w, http.StatusOK, containerJSON) } -func (s *Server) getExecByID(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getExecByID(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter 'id'") } @@ -1245,7 +1235,7 @@ func (s *Server) getExecByID(eng *engine.Engine, version version.Version, w http return writeJSON(w, http.StatusOK, eConfig) } -func (s *Server) getImagesByName(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) getImagesByName(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1268,7 +1258,7 @@ func (s *Server) getImagesByName(eng *engine.Engine, version version.Version, w return writeJSON(w, http.StatusOK, imageInspect) } -func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version.LessThan("1.3") { return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") } @@ -1363,7 +1353,7 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R return nil } -func (s *Server) postContainersCopy(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainersCopy(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if vars == nil { return fmt.Errorf("Missing parameter") } @@ -1413,7 +1403,7 @@ func (s *Server) postContainersCopy(eng *engine.Engine, version version.Version, return nil } -func (s *Server) postContainerExecCreate(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerExecCreate(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -1442,7 +1432,7 @@ func (s *Server) postContainerExecCreate(eng *engine.Engine, version version.Ver } // TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start. -func (s *Server) postContainerExecStart(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerExecStart(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return nil } @@ -1495,7 +1485,7 @@ func (s *Server) postContainerExecStart(eng *engine.Engine, version version.Vers return nil } -func (s *Server) postContainerExecResize(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) postContainerExecResize(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if err := parseForm(r); err != nil { return err } @@ -1515,7 +1505,7 @@ func (s *Server) postContainerExecResize(eng *engine.Engine, version version.Ver return s.daemon.ContainerExecResize(vars["name"], height, width) } -func (s *Server) optionsHandler(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) optionsHandler(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { w.WriteHeader(http.StatusOK) return nil } @@ -1526,12 +1516,12 @@ func writeCorsHeaders(w http.ResponseWriter, r *http.Request, corsHeaders string w.Header().Add("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, OPTIONS") } -func (s *Server) ping(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { +func (s *Server) ping(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { _, err := w.Write([]byte{'O', 'K'}) return err } -func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, corsHeaders string, dockerVersion version.Version) http.HandlerFunc { +func makeHttpHandler(logging bool, localMethod string, localRoute string, handlerFunc HttpApiFunc, corsHeaders string, dockerVersion version.Version) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // log the request logrus.Debugf("Calling %s %s", localMethod, localRoute) @@ -1559,7 +1549,7 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local return } - if err := handlerFunc(eng, version, w, r, mux.Vars(r)); err != nil { + if err := handlerFunc(version, w, r, mux.Vars(r)); err != nil { logrus.Errorf("Handler for %s %s returned error: %s", localMethod, localRoute, err) httpError(w, err) } @@ -1567,7 +1557,7 @@ func makeHttpHandler(eng *engine.Engine, logging bool, localMethod string, local } // we keep enableCors just for legacy usage, need to be removed in the future -func createRouter(s *Server, eng *engine.Engine) *mux.Router { +func createRouter(s *Server) *mux.Router { r := mux.NewRouter() if os.Getenv("DEBUG") != "" { ProfilerSetup(r, "/debug/") @@ -1644,7 +1634,7 @@ func createRouter(s *Server, eng *engine.Engine) *mux.Router { localMethod := method // build the handler function - f := makeHttpHandler(eng, s.cfg.Logging, localMethod, localRoute, localFct, corsHeaders, version.Version(s.cfg.Version)) + f := makeHttpHandler(s.cfg.Logging, localMethod, localRoute, localFct, corsHeaders, version.Version(s.cfg.Version)) // add the new route if localRoute == "" { @@ -1659,23 +1649,6 @@ func createRouter(s *Server, eng *engine.Engine) *mux.Router { return r } -// ServeRequest processes a single http request to the docker remote api. -// FIXME: refactor this to be part of Server and not require re-creating a new -// router each time. This requires first moving ListenAndServe into Server. -func ServeRequest(eng *engine.Engine, apiversion version.Version, w http.ResponseWriter, req *http.Request) { - cfg := &ServerConfig{ - EnableCors: true, - Version: string(apiversion), - } - api := New(cfg, eng) - daemon, _ := eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon) - api.AcceptConnections(daemon) - router := createRouter(api, eng) - // Insert APIVERSION into the request as a convenience - req.URL.Path = fmt.Sprintf("/v%s%s", apiversion, req.URL.Path) - router.ServeHTTP(w, req) -} - func allocateDaemonPort(addr string) error { host, port, err := net.SplitHostPort(addr) if err != nil { diff --git a/daemon/container.go b/daemon/container.go index 9bd8cc1eb..ef4229534 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -26,7 +26,6 @@ import ( "github.com/docker/docker/daemon/logger/syslog" "github.com/docker/docker/daemon/network" "github.com/docker/docker/daemon/networkdriver/bridge" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/links" "github.com/docker/docker/nat" @@ -600,10 +599,7 @@ func (container *Container) AllocateNetwork() error { return nil } - var ( - err error - eng = container.daemon.eng - ) + var err error networkSettings, err := bridge.Allocate(container.ID, container.Config.MacAddress, "", "") if err != nil { @@ -650,7 +646,7 @@ func (container *Container) AllocateNetwork() error { container.NetworkSettings.PortMapping = nil for port := range portSpecs { - if err = container.allocatePort(eng, port, bindings); err != nil { + if err = container.allocatePort(port, bindings); err != nil { bridge.Release(container.ID) return err } @@ -686,8 +682,6 @@ func (container *Container) RestoreNetwork() error { return nil } - eng := container.daemon.eng - // Re-allocate the interface with the same IP and MAC address. if _, err := bridge.Allocate(container.ID, container.NetworkSettings.MacAddress, container.NetworkSettings.IPAddress, ""); err != nil { return err @@ -695,7 +689,7 @@ func (container *Container) RestoreNetwork() error { // Re-allocate any previously allocated ports. for port := range container.NetworkSettings.Ports { - if err := container.allocatePort(eng, port, container.NetworkSettings.Ports); err != nil { + if err := container.allocatePort(port, container.NetworkSettings.Ports); err != nil { return err } } @@ -1483,7 +1477,7 @@ func (container *Container) waitForStart() error { return nil } -func (container *Container) allocatePort(eng *engine.Engine, port nat.Port, bindings nat.PortMap) error { +func (container *Container) allocatePort(port nat.Port, bindings nat.PortMap) error { binding := bindings[port] if container.hostConfig.PublishAllPorts && len(binding) == 0 { binding = append(binding, nat.PortBinding{}) diff --git a/daemon/daemon.go b/daemon/daemon.go index 05de40217..130fcc46f 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -27,7 +27,6 @@ import ( _ "github.com/docker/docker/daemon/graphdriver/vfs" "github.com/docker/docker/daemon/network" "github.com/docker/docker/daemon/networkdriver/bridge" - "github.com/docker/docker/engine" "github.com/docker/docker/graph" "github.com/docker/docker/image" "github.com/docker/docker/pkg/archive" @@ -38,7 +37,6 @@ import ( "github.com/docker/docker/pkg/namesgenerator" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/parsers/kernel" - "github.com/docker/docker/pkg/pidfile" "github.com/docker/docker/pkg/resolvconf" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/pkg/sysinfo" @@ -103,7 +101,6 @@ type Daemon struct { idIndex *truncindex.TruncIndex sysInfo *sysinfo.SysInfo volumes *volumes.Repository - eng *engine.Engine config *Config containerGraph *graphdb.Database driver graphdriver.Driver @@ -114,14 +111,6 @@ type Daemon struct { EventsService *events.Events } -// Install installs daemon capabilities to eng. -func (daemon *Daemon) Install(eng *engine.Engine) error { - // FIXME: this hack is necessary for legacy integration tests to access - // the daemon object. - eng.HackSetGlobalVar("httpapi.daemon", daemon) - return nil -} - // Get looks for a container using the provided information, which could be // one of the following inputs from the caller: // - A full container ID, which will exact match a container in daemon's list @@ -741,16 +730,7 @@ func (daemon *Daemon) RegisterLinks(container *Container, hostConfig *runconfig. return nil } -// FIXME: harmonize with NewGraph() -func NewDaemon(config *Config, eng *engine.Engine, registryService *registry.Service) (*Daemon, error) { - daemon, err := NewDaemonFromDirectory(config, eng, registryService) - if err != nil { - return nil, err - } - return daemon, nil -} - -func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService *registry.Service) (*Daemon, error) { +func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemon, err error) { if config.Mtu == 0 { config.Mtu = getDefaultNetworkMtu() } @@ -766,19 +746,6 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService } config.DisableNetwork = config.Bridge.Iface == disableNetworkBridge - // Claim the pidfile first, to avoid any and all unexpected race conditions. - // Some of the init doesn't need a pidfile lock - but let's not try to be smart. - if config.Pidfile != "" { - file, err := pidfile.New(config.Pidfile) - if err != nil { - return nil, err - } - eng.OnShutdown(func() { - // Always release the pidfile last, just in case - file.Remove() - }) - } - // Check that the system is supported and we have sufficient privileges if runtime.GOOS != "linux" { return nil, fmt.Errorf("The Docker daemon is only supported on linux") @@ -826,17 +793,22 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService return nil, fmt.Errorf("error initializing graphdriver: %v", err) } logrus.Debugf("Using graph driver %s", driver) - // register cleanup for graph driver - eng.OnShutdown(func() { - if err := driver.Cleanup(); err != nil { - logrus.Errorf("Error during graph storage driver.Cleanup(): %v", err) + + d := &Daemon{} + d.driver = driver + + defer func() { + if err != nil { + if err := d.Shutdown(); err != nil { + logrus.Error(err) + } } - }) + }() if config.EnableSelinuxSupport { if selinuxEnabled() { // As Docker on btrfs and SELinux are incompatible at present, error on both being enabled - if driver.String() == "btrfs" { + if d.driver.String() == "btrfs" { return nil, fmt.Errorf("SELinux is not supported with the BTRFS graph driver") } logrus.Debug("SELinux enabled successfully") @@ -854,12 +826,12 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService } // Migrate the container if it is aufs and aufs is enabled - if err = migrateIfAufs(driver, config.Root); err != nil { + if err := migrateIfAufs(d.driver, config.Root); err != nil { return nil, err } logrus.Debug("Creating images graph") - g, err := graph.NewGraph(path.Join(config.Root, "graph"), driver) + g, err := graph.NewGraph(path.Join(config.Root, "graph"), d.driver) if err != nil { return nil, err } @@ -897,7 +869,7 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService Events: eventsService, Trust: trustService, } - repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+driver.String()), tagCfg) + repositories, err := graph.NewTagStore(path.Join(config.Root, "repositories-"+d.driver.String()), tagCfg) if err != nil { return nil, fmt.Errorf("Couldn't create Tag store: %s", err) } @@ -913,12 +885,8 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService if err != nil { return nil, err } - // register graph close on shutdown - eng.OnShutdown(func() { - if err := graph.Close(); err != nil { - logrus.Errorf("Error during container graph.Close(): %v", err) - } - }) + + d.containerGraph = graph localCopy := path.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", dockerversion.VERSION)) sysInitPath := utils.DockerInitPath(localCopy) @@ -947,66 +915,67 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine, registryService return nil, err } - daemon := &Daemon{ - ID: trustKey.PublicKey().KeyID(), - repository: daemonRepo, - containers: &contStore{s: make(map[string]*Container)}, - execCommands: newExecStore(), - graph: g, - repositories: repositories, - idIndex: truncindex.NewTruncIndex([]string{}), - sysInfo: sysInfo, - volumes: volumes, - config: config, - containerGraph: graph, - driver: driver, - sysInitPath: sysInitPath, - execDriver: ed, - eng: eng, - statsCollector: newStatsCollector(1 * time.Second), - defaultLogConfig: config.LogConfig, - RegistryService: registryService, - EventsService: eventsService, - } + d.ID = trustKey.PublicKey().KeyID() + d.repository = daemonRepo + d.containers = &contStore{s: make(map[string]*Container)} + d.execCommands = newExecStore() + d.graph = g + d.repositories = repositories + d.idIndex = truncindex.NewTruncIndex([]string{}) + d.sysInfo = sysInfo + d.volumes = volumes + d.config = config + d.sysInitPath = sysInitPath + d.execDriver = ed + d.statsCollector = newStatsCollector(1 * time.Second) + d.defaultLogConfig = config.LogConfig + d.RegistryService = registryService + d.EventsService = eventsService - eng.OnShutdown(func() { - if err := daemon.shutdown(); err != nil { - logrus.Errorf("Error during daemon.shutdown(): %v", err) - } - }) - - if err := daemon.restore(); err != nil { + if err := d.restore(); err != nil { return nil, err } // set up filesystem watch on resolv.conf for network changes - if err := daemon.setupResolvconfWatcher(); err != nil { + if err := d.setupResolvconfWatcher(); err != nil { return nil, err } - return daemon, nil + return d, nil } -func (daemon *Daemon) shutdown() error { - group := sync.WaitGroup{} - logrus.Debug("starting clean shutdown of all containers...") - for _, container := range daemon.List() { - c := container - if c.IsRunning() { - logrus.Debugf("stopping %s", c.ID) - group.Add(1) - - go func() { - defer group.Done() - if err := c.KillSig(15); err != nil { - logrus.Debugf("kill 15 error for %s - %s", c.ID, err) - } - c.WaitStop(-1 * time.Second) - logrus.Debugf("container stopped %s", c.ID) - }() +func (daemon *Daemon) Shutdown() error { + if daemon.containerGraph != nil { + if err := daemon.containerGraph.Close(); err != nil { + logrus.Errorf("Error during container graph.Close(): %v", err) } } - group.Wait() + if daemon.driver != nil { + if err := daemon.driver.Cleanup(); err != nil { + logrus.Errorf("Error during graph storage driver.Cleanup(): %v", err) + } + } + if daemon.containers != nil { + group := sync.WaitGroup{} + logrus.Debug("starting clean shutdown of all containers...") + for _, container := range daemon.List() { + c := container + if c.IsRunning() { + logrus.Debugf("stopping %s", c.ID) + group.Add(1) + + go func() { + defer group.Done() + if err := c.KillSig(15); err != nil { + logrus.Debugf("kill 15 error for %s - %s", c.ID, err) + } + c.WaitStop(-1 * time.Second) + logrus.Debugf("container stopped %s", c.ID) + }() + } + } + group.Wait() + } return nil } @@ -1087,26 +1056,6 @@ func (daemon *Daemon) UnsubscribeToContainerStats(name string, ch chan interface return nil } -// Nuke kills all containers then removes all content -// from the content root, including images, volumes and -// container filesystems. -// Again: this will remove your entire docker daemon! -// FIXME: this is deprecated, and only used in legacy -// tests. Please remove. -func (daemon *Daemon) Nuke() error { - var wg sync.WaitGroup - for _, container := range daemon.List() { - wg.Add(1) - go func(c *Container) { - c.Kill() - wg.Done() - }(container) - } - wg.Wait() - - return os.RemoveAll(daemon.config.Root) -} - // FIXME: this is a convenience function for integration tests // which need direct access to daemon.graph. // Once the tests switch to using engine and jobs, this method diff --git a/docker/daemon.go b/docker/daemon.go index c6241b606..c78879784 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -7,6 +7,7 @@ import ( "io" "os" "path/filepath" + "time" "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" @@ -14,9 +15,9 @@ import ( "github.com/docker/docker/daemon" _ "github.com/docker/docker/daemon/execdriver/lxc" _ "github.com/docker/docker/daemon/execdriver/native" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" + "github.com/docker/docker/pkg/pidfile" "github.com/docker/docker/pkg/signal" "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/timeutils" @@ -83,14 +84,45 @@ func mainDaemon() { logrus.SetFormatter(&logrus.TextFormatter{TimestampFormat: timeutils.RFC3339NanoFixed}) - eng := engine.New() - signal.Trap(eng.Shutdown) + var pfile *pidfile.PidFile + if daemonCfg.Pidfile != "" { + pf, err := pidfile.New(daemonCfg.Pidfile) + if err != nil { + logrus.Fatalf("Error starting daemon: %v", err) + } + pfile = pf + defer func() { + if err := pfile.Remove(); err != nil { + logrus.Error(err) + } + }() + } if err := migrateKey(); err != nil { logrus.Fatal(err) } daemonCfg.TrustKeyPath = *flTrustKey + registryService := registry.NewService(registryCfg) + d, err := daemon.NewDaemon(daemonCfg, registryService) + if err != nil { + if pfile != nil { + if err := pfile.Remove(); err != nil { + logrus.Error(err) + } + } + logrus.Fatalf("Error starting daemon: %v", err) + } + + logrus.Info("Daemon has completed initialization") + + logrus.WithFields(logrus.Fields{ + "version": dockerversion.VERSION, + "commit": dockerversion.GITCOMMIT, + "execdriver": d.ExecutionDriver().Name(), + "graphdriver": d.GraphDriver().String(), + }).Info("Docker daemon") + serverConfig := &apiserver.ServerConfig{ Logging: true, EnableCors: daemonCfg.EnableCors, @@ -104,7 +136,7 @@ func mainDaemon() { TlsKey: *flKey, } - api := apiserver.New(serverConfig, eng) + api := apiserver.New(serverConfig) // The serve API routine never exits unless an error occurs // We need to start it as a goroutine and wait on it so @@ -119,40 +151,52 @@ func mainDaemon() { serveAPIWait <- nil }() - registryService := registry.NewService(registryCfg) - d, err := daemon.NewDaemon(daemonCfg, eng, registryService) - if err != nil { - eng.Shutdown() - logrus.Fatalf("Error starting daemon: %v", err) - } - - if err := d.Install(eng); err != nil { - eng.Shutdown() - logrus.Fatalf("Error starting daemon: %v", err) - } - - logrus.Info("Daemon has completed initialization") - - logrus.WithFields(logrus.Fields{ - "version": dockerversion.VERSION, - "commit": dockerversion.GITCOMMIT, - "execdriver": d.ExecutionDriver().Name(), - "graphdriver": d.GraphDriver().String(), - }).Info("Docker daemon") + signal.Trap(func() { + api.Close() + <-serveAPIWait + shutdownDaemon(d, 15) + if pfile != nil { + if err := pfile.Remove(); err != nil { + logrus.Error(err) + } + } + }) // after the daemon is done setting up we can tell the api to start // accepting connections with specified daemon api.AcceptConnections(d) // Daemon is fully initialized and handling API traffic - // Wait for serve API job to complete + // Wait for serve API to complete errAPI := <-serveAPIWait - eng.Shutdown() + shutdownDaemon(d, 15) if errAPI != nil { + if pfile != nil { + if err := pfile.Remove(); err != nil { + logrus.Error(err) + } + } logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI) } } +// shutdownDaemon just wraps daemon.Shutdown() to handle a timeout in case +// d.Shutdown() is waiting too long to kill container or worst it's +// blocked there +func shutdownDaemon(d *daemon.Daemon, timeout time.Duration) { + ch := make(chan struct{}) + go func() { + d.Shutdown() + close(ch) + }() + select { + case <-ch: + logrus.Debug("Clean shutdown succeded") + case <-time.After(timeout * time.Second): + logrus.Error("Force shutdown daemon") + } +} + // currentUserIsOwner checks whether the current user is the owner of the given // file. func currentUserIsOwner(f string) bool { diff --git a/engine/engine.go b/engine/engine.go deleted file mode 100644 index 79fae51cc..000000000 --- a/engine/engine.go +++ /dev/null @@ -1,255 +0,0 @@ -package engine - -import ( - "bufio" - "fmt" - "io" - "os" - "sort" - "strings" - "sync" - "time" - - "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/stringid" -) - -// Installer is a standard interface for objects which can "install" themselves -// on an engine by registering handlers. -// This can be used as an entrypoint for external plugins etc. -type Installer interface { - Install(*Engine) error -} - -type Handler func(*Job) error - -var globalHandlers map[string]Handler - -func init() { - globalHandlers = make(map[string]Handler) -} - -func Register(name string, handler Handler) error { - _, exists := globalHandlers[name] - if exists { - return fmt.Errorf("Can't overwrite global handler for command %s", name) - } - globalHandlers[name] = handler - return nil -} - -func unregister(name string) { - delete(globalHandlers, name) -} - -// The Engine is the core of Docker. -// It acts as a store for *containers*, and allows manipulation of these -// containers by executing *jobs*. -type Engine struct { - handlers map[string]Handler - catchall Handler - hack Hack // data for temporary hackery (see hack.go) - id string - Stdout io.Writer - Stderr io.Writer - Stdin io.Reader - Logging bool - tasks sync.WaitGroup - l sync.RWMutex // lock for shutdown - shutdownWait sync.WaitGroup - shutdown bool - onShutdown []func() // shutdown handlers -} - -func (eng *Engine) Register(name string, handler Handler) error { - _, exists := eng.handlers[name] - if exists { - return fmt.Errorf("Can't overwrite handler for command %s", name) - } - eng.handlers[name] = handler - return nil -} - -func (eng *Engine) RegisterCatchall(catchall Handler) { - eng.catchall = catchall -} - -// New initializes a new engine. -func New() *Engine { - eng := &Engine{ - handlers: make(map[string]Handler), - id: stringid.GenerateRandomID(), - Stdout: os.Stdout, - Stderr: os.Stderr, - Stdin: os.Stdin, - Logging: true, - } - eng.Register("commands", func(job *Job) error { - for _, name := range eng.commands() { - job.Printf("%s\n", name) - } - return nil - }) - // Copy existing global handlers - for k, v := range globalHandlers { - eng.handlers[k] = v - } - return eng -} - -func (eng *Engine) String() string { - return fmt.Sprintf("%s", eng.id[:8]) -} - -// Commands returns a list of all currently registered commands, -// sorted alphabetically. -func (eng *Engine) commands() []string { - names := make([]string, 0, len(eng.handlers)) - for name := range eng.handlers { - names = append(names, name) - } - sort.Strings(names) - return names -} - -// Job creates a new job which can later be executed. -// This function mimics `Command` from the standard os/exec package. -func (eng *Engine) Job(name string, args ...string) *Job { - job := &Job{ - Eng: eng, - Name: name, - Args: args, - Stdin: NewInput(), - Stdout: NewOutput(), - Stderr: NewOutput(), - env: &Env{}, - closeIO: true, - - cancelled: make(chan struct{}), - } - if eng.Logging { - job.Stderr.Add(ioutils.NopWriteCloser(eng.Stderr)) - } - - // Catchall is shadowed by specific Register. - if handler, exists := eng.handlers[name]; exists { - job.handler = handler - } else if eng.catchall != nil && name != "" { - // empty job names are illegal, catchall or not. - job.handler = eng.catchall - } - return job -} - -// OnShutdown registers a new callback to be called by Shutdown. -// This is typically used by services to perform cleanup. -func (eng *Engine) OnShutdown(h func()) { - eng.l.Lock() - eng.onShutdown = append(eng.onShutdown, h) - eng.shutdownWait.Add(1) - eng.l.Unlock() -} - -// Shutdown permanently shuts down eng as follows: -// - It refuses all new jobs, permanently. -// - It waits for all active jobs to complete (with no timeout) -// - It calls all shutdown handlers concurrently (if any) -// - It returns when all handlers complete, or after 15 seconds, -// whichever happens first. -func (eng *Engine) Shutdown() { - eng.l.Lock() - if eng.shutdown { - eng.l.Unlock() - eng.shutdownWait.Wait() - return - } - eng.shutdown = true - eng.l.Unlock() - // We don't need to protect the rest with a lock, to allow - // for other calls to immediately fail with "shutdown" instead - // of hanging for 15 seconds. - // This requires all concurrent calls to check for shutdown, otherwise - // it might cause a race. - - // Wait for all jobs to complete. - // Timeout after 5 seconds. - tasksDone := make(chan struct{}) - go func() { - eng.tasks.Wait() - close(tasksDone) - }() - select { - case <-time.After(time.Second * 5): - case <-tasksDone: - } - - // Call shutdown handlers, if any. - // Timeout after 10 seconds. - for _, h := range eng.onShutdown { - go func(h func()) { - h() - eng.shutdownWait.Done() - }(h) - } - done := make(chan struct{}) - go func() { - eng.shutdownWait.Wait() - close(done) - }() - select { - case <-time.After(time.Second * 10): - case <-done: - } - return -} - -// IsShutdown returns true if the engine is in the process -// of shutting down, or already shut down. -// Otherwise it returns false. -func (eng *Engine) IsShutdown() bool { - eng.l.RLock() - defer eng.l.RUnlock() - return eng.shutdown -} - -// ParseJob creates a new job from a text description using a shell-like syntax. -// -// The following syntax is used to parse `input`: -// -// * Words are separated using standard whitespaces as separators. -// * Quotes and backslashes are not interpreted. -// * Words of the form 'KEY=[VALUE]' are added to the job environment. -// * All other words are added to the job arguments. -// -// For example: -// -// job, _ := eng.ParseJob("VERBOSE=1 echo hello TEST=true world") -// -// The resulting job will have: -// job.Args={"echo", "hello", "world"} -// job.Env={"VERBOSE":"1", "TEST":"true"} -// -func (eng *Engine) ParseJob(input string) (*Job, error) { - // FIXME: use a full-featured command parser - scanner := bufio.NewScanner(strings.NewReader(input)) - scanner.Split(bufio.ScanWords) - var ( - cmd []string - env Env - ) - for scanner.Scan() { - word := scanner.Text() - kv := strings.SplitN(word, "=", 2) - if len(kv) == 2 { - env.Set(kv[0], kv[1]) - } else { - cmd = append(cmd, word) - } - } - if len(cmd) == 0 { - return nil, fmt.Errorf("empty command: '%s'", input) - } - job := eng.Job(cmd[0], cmd[1:]...) - job.Env().Init(&env) - return job, nil -} diff --git a/engine/engine_test.go b/engine/engine_test.go deleted file mode 100644 index a6ff62c8b..000000000 --- a/engine/engine_test.go +++ /dev/null @@ -1,236 +0,0 @@ -package engine - -import ( - "bytes" - "strings" - "testing" - - "github.com/docker/docker/pkg/ioutils" -) - -func TestRegister(t *testing.T) { - if err := Register("dummy1", nil); err != nil { - t.Fatal(err) - } - - if err := Register("dummy1", nil); err == nil { - t.Fatalf("Expecting error, got none") - } - // Register is global so let's cleanup to avoid conflicts - defer unregister("dummy1") - - eng := New() - - //Should fail because global handlers are copied - //at the engine creation - if err := eng.Register("dummy1", nil); err == nil { - t.Fatalf("Expecting error, got none") - } - - if err := eng.Register("dummy2", nil); err != nil { - t.Fatal(err) - } - - if err := eng.Register("dummy2", nil); err == nil { - t.Fatalf("Expecting error, got none") - } - defer unregister("dummy2") -} - -func TestJob(t *testing.T) { - eng := New() - job1 := eng.Job("dummy1", "--level=awesome") - - if job1.handler != nil { - t.Fatalf("job1.handler should be empty") - } - - h := func(j *Job) error { - j.Printf("%s\n", j.Name) - return nil - } - - eng.Register("dummy2", h) - defer unregister("dummy2") - job2 := eng.Job("dummy2", "--level=awesome") - - if job2.handler == nil { - t.Fatalf("job2.handler shouldn't be nil") - } - - if job2.handler(job2) != nil { - t.Fatalf("handler dummy2 was not found in job2") - } -} - -func TestEngineShutdown(t *testing.T) { - eng := New() - if eng.IsShutdown() { - t.Fatalf("Engine should not show as shutdown") - } - eng.Shutdown() - if !eng.IsShutdown() { - t.Fatalf("Engine should show as shutdown") - } -} - -func TestEngineCommands(t *testing.T) { - eng := New() - handler := func(job *Job) error { return nil } - eng.Register("foo", handler) - eng.Register("bar", handler) - eng.Register("echo", handler) - eng.Register("die", handler) - var output bytes.Buffer - commands := eng.Job("commands") - commands.Stdout.Add(&output) - commands.Run() - expected := "bar\ncommands\ndie\necho\nfoo\n" - if result := output.String(); result != expected { - t.Fatalf("Unexpected output:\nExpected = %v\nResult = %v\n", expected, result) - } -} - -func TestEngineString(t *testing.T) { - eng1 := New() - eng2 := New() - s1 := eng1.String() - s2 := eng2.String() - if eng1 == eng2 { - t.Fatalf("Different engines should have different names (%v == %v)", s1, s2) - } -} - -func TestParseJob(t *testing.T) { - eng := New() - // Verify that the resulting job calls to the right place - var called bool - eng.Register("echo", func(job *Job) error { - called = true - return nil - }) - input := "echo DEBUG=1 hello world VERBOSITY=42" - job, err := eng.ParseJob(input) - if err != nil { - t.Fatal(err) - } - if job.Name != "echo" { - t.Fatalf("Invalid job name: %v", job.Name) - } - if strings.Join(job.Args, ":::") != "hello:::world" { - t.Fatalf("Invalid job args: %v", job.Args) - } - if job.Env().Get("DEBUG") != "1" { - t.Fatalf("Invalid job env: %v", job.Env) - } - if job.Env().Get("VERBOSITY") != "42" { - t.Fatalf("Invalid job env: %v", job.Env) - } - if len(job.Env().Map()) != 2 { - t.Fatalf("Invalid job env: %v", job.Env) - } - if err := job.Run(); err != nil { - t.Fatal(err) - } - if !called { - t.Fatalf("Job was not called") - } -} - -func TestCatchallEmptyName(t *testing.T) { - eng := New() - var called bool - eng.RegisterCatchall(func(job *Job) error { - called = true - return nil - }) - err := eng.Job("").Run() - if err == nil { - t.Fatalf("Engine.Job(\"\").Run() should return an error") - } - if called { - t.Fatalf("Engine.Job(\"\").Run() should return an error") - } -} - -// Ensure that a job within a job both using the same underlying standard -// output writer does not close the output of the outer job when the inner -// job's stdout is wrapped with a NopCloser. When not wrapped, it should -// close the outer job's output. -func TestNestedJobSharedOutput(t *testing.T) { - var ( - outerHandler Handler - innerHandler Handler - wrapOutput bool - ) - - outerHandler = func(job *Job) error { - job.Stdout.Write([]byte("outer1")) - - innerJob := job.Eng.Job("innerJob") - - if wrapOutput { - innerJob.Stdout.Add(ioutils.NopWriteCloser(job.Stdout)) - } else { - innerJob.Stdout.Add(job.Stdout) - } - - if err := innerJob.Run(); err != nil { - t.Fatal(err) - } - - // If wrapOutput was *false* this write will do nothing. - // FIXME (jlhawn): It should cause an error to write to - // closed output. - job.Stdout.Write([]byte(" outer2")) - - return nil - } - - innerHandler = func(job *Job) error { - job.Stdout.Write([]byte(" inner")) - - return nil - } - - eng := New() - eng.Register("outerJob", outerHandler) - eng.Register("innerJob", innerHandler) - - // wrapOutput starts *false* so the expected - // output of running the outer job will be: - // - // "outer1 inner" - // - outBuf := new(bytes.Buffer) - outerJob := eng.Job("outerJob") - outerJob.Stdout.Add(outBuf) - - if err := outerJob.Run(); err != nil { - t.Fatal(err) - } - - expectedOutput := "outer1 inner" - if outBuf.String() != expectedOutput { - t.Fatalf("expected job output to be %q, got %q", expectedOutput, outBuf.String()) - } - - // Set wrapOutput to true so that the expected - // output of running the outer job will be: - // - // "outer1 inner outer2" - // - wrapOutput = true - outBuf.Reset() - outerJob = eng.Job("outerJob") - outerJob.Stdout.Add(outBuf) - - if err := outerJob.Run(); err != nil { - t.Fatal(err) - } - - expectedOutput = "outer1 inner outer2" - if outBuf.String() != expectedOutput { - t.Fatalf("expected job output to be %q, got %q", expectedOutput, outBuf.String()) - } -} diff --git a/engine/env.go b/engine/env.go deleted file mode 100644 index 107ae4a0d..000000000 --- a/engine/env.go +++ /dev/null @@ -1,313 +0,0 @@ -package engine - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "strconv" - "strings" - "time" - - "github.com/docker/docker/pkg/ioutils" -) - -type Env []string - -// Get returns the last value associated with the given key. If there are no -// values associated with the key, Get returns the empty string. -func (env *Env) Get(key string) (value string) { - // not using Map() because of the extra allocations https://github.com/docker/docker/pull/7488#issuecomment-51638315 - for _, kv := range *env { - if strings.Index(kv, "=") == -1 { - continue - } - parts := strings.SplitN(kv, "=", 2) - if parts[0] != key { - continue - } - if len(parts) < 2 { - value = "" - } else { - value = parts[1] - } - } - return -} - -func (env *Env) Exists(key string) bool { - _, exists := env.Map()[key] - return exists -} - -// Len returns the number of keys in the environment. -// Note that len(env) might be different from env.Len(), -// because the same key might be set multiple times. -func (env *Env) Len() int { - return len(env.Map()) -} - -func (env *Env) Init(src *Env) { - (*env) = make([]string, 0, len(*src)) - for _, val := range *src { - (*env) = append((*env), val) - } -} - -func (env *Env) GetBool(key string) (value bool) { - s := strings.ToLower(strings.Trim(env.Get(key), " \t")) - if s == "" || s == "0" || s == "no" || s == "false" || s == "none" { - return false - } - return true -} - -func (env *Env) SetBool(key string, value bool) { - if value { - env.Set(key, "1") - } else { - env.Set(key, "0") - } -} - -func (env *Env) GetTime(key string) (time.Time, error) { - t, err := time.Parse(time.RFC3339Nano, env.Get(key)) - return t, err -} - -func (env *Env) SetTime(key string, t time.Time) { - env.Set(key, t.Format(time.RFC3339Nano)) -} - -func (env *Env) GetInt(key string) int { - return int(env.GetInt64(key)) -} - -func (env *Env) GetInt64(key string) int64 { - s := strings.Trim(env.Get(key), " \t") - val, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return 0 - } - return val -} - -func (env *Env) SetInt(key string, value int) { - env.Set(key, fmt.Sprintf("%d", value)) -} - -func (env *Env) SetInt64(key string, value int64) { - env.Set(key, fmt.Sprintf("%d", value)) -} - -// Returns nil if key not found -func (env *Env) GetList(key string) []string { - sval := env.Get(key) - if sval == "" { - return nil - } - l := make([]string, 0, 1) - if err := json.Unmarshal([]byte(sval), &l); err != nil { - l = append(l, sval) - } - return l -} - -func (env *Env) GetSubEnv(key string) *Env { - sval := env.Get(key) - if sval == "" { - return nil - } - buf := bytes.NewBufferString(sval) - var sub Env - if err := sub.Decode(buf); err != nil { - return nil - } - return &sub -} - -func (env *Env) SetSubEnv(key string, sub *Env) error { - var buf bytes.Buffer - if err := sub.Encode(&buf); err != nil { - return err - } - env.Set(key, string(buf.Bytes())) - return nil -} - -func (env *Env) GetJson(key string, iface interface{}) error { - sval := env.Get(key) - if sval == "" { - return nil - } - return json.Unmarshal([]byte(sval), iface) -} - -func (env *Env) SetJson(key string, value interface{}) error { - sval, err := json.Marshal(value) - if err != nil { - return err - } - env.Set(key, string(sval)) - return nil -} - -func (env *Env) SetList(key string, value []string) error { - return env.SetJson(key, value) -} - -func (env *Env) Set(key, value string) { - *env = append(*env, key+"="+value) -} - -func NewDecoder(src io.Reader) *Decoder { - return &Decoder{ - json.NewDecoder(src), - } -} - -type Decoder struct { - *json.Decoder -} - -func (decoder *Decoder) Decode() (*Env, error) { - m := make(map[string]interface{}) - if err := decoder.Decoder.Decode(&m); err != nil { - return nil, err - } - env := &Env{} - for key, value := range m { - env.SetAuto(key, value) - } - return env, nil -} - -// DecodeEnv decodes `src` as a json dictionary, and adds -// each decoded key-value pair to the environment. -// -// If `src` cannot be decoded as a json dictionary, an error -// is returned. -func (env *Env) Decode(src io.Reader) error { - m := make(map[string]interface{}) - d := json.NewDecoder(src) - // We need this or we'll lose data when we decode int64 in json - d.UseNumber() - if err := d.Decode(&m); err != nil { - return err - } - for k, v := range m { - env.SetAuto(k, v) - } - return nil -} - -func (env *Env) SetAuto(k string, v interface{}) { - // Issue 7941 - if the value in the incoming JSON is null then treat it - // as if they never specified the property at all. - if v == nil { - return - } - - // FIXME: we fix-convert float values to int, because - // encoding/json decodes integers to float64, but cannot encode them back. - // (See https://golang.org/src/pkg/encoding/json/decode.go#L46) - if fval, ok := v.(float64); ok { - env.SetInt64(k, int64(fval)) - } else if sval, ok := v.(string); ok { - env.Set(k, sval) - } else if val, err := json.Marshal(v); err == nil { - env.Set(k, string(val)) - } else { - env.Set(k, fmt.Sprintf("%v", v)) - } -} - -func changeFloats(v interface{}) interface{} { - switch v := v.(type) { - case float64: - return int(v) - case map[string]interface{}: - for key, val := range v { - v[key] = changeFloats(val) - } - case []interface{}: - for idx, val := range v { - v[idx] = changeFloats(val) - } - } - return v -} - -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 https://golang.org/src/pkg/encoding/json/decode.go#L46) - m[k] = changeFloats(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) (int64, error) { - wc := ioutils.NewWriteCounter(dst) - err := env.Encode(wc) - return wc.Count, err -} - -func (env *Env) Import(src interface{}) (err error) { - defer func() { - if err != nil { - err = fmt.Errorf("ImportEnv: %s", err) - } - }() - var buf bytes.Buffer - if err := json.NewEncoder(&buf).Encode(src); err != nil { - return err - } - if err := env.Decode(&buf); err != nil { - return err - } - return nil -} - -func (env *Env) Map() map[string]string { - m := make(map[string]string) - for _, kv := range *env { - parts := strings.SplitN(kv, "=", 2) - m[parts[0]] = parts[1] - } - return m -} - -// MultiMap returns a representation of env as a -// map of string arrays, keyed by string. -// This is the same structure as http headers for example, -// which allow each key to have multiple values. -func (env *Env) MultiMap() map[string][]string { - m := make(map[string][]string) - for _, kv := range *env { - parts := strings.SplitN(kv, "=", 2) - m[parts[0]] = append(m[parts[0]], parts[1]) - } - return m -} - -// InitMultiMap removes all values in env, then initializes -// new values from the contents of m. -func (env *Env) InitMultiMap(m map[string][]string) { - (*env) = make([]string, 0, len(m)) - for k, vals := range m { - for _, v := range vals { - env.Set(k, v) - } - } -} diff --git a/engine/env_test.go b/engine/env_test.go deleted file mode 100644 index 1398275b2..000000000 --- a/engine/env_test.go +++ /dev/null @@ -1,366 +0,0 @@ -package engine - -import ( - "bytes" - "encoding/json" - "testing" - "time" - - "github.com/docker/docker/pkg/stringutils" -) - -func TestEnvLenZero(t *testing.T) { - env := &Env{} - if env.Len() != 0 { - t.Fatalf("%d", env.Len()) - } -} - -func TestEnvLenNotZero(t *testing.T) { - env := &Env{} - env.Set("foo", "bar") - env.Set("ga", "bu") - if env.Len() != 2 { - t.Fatalf("%d", env.Len()) - } -} - -func TestEnvLenDup(t *testing.T) { - env := &Env{ - "foo=bar", - "foo=baz", - "a=b", - } - // len(env) != env.Len() - if env.Len() != 2 { - t.Fatalf("%d", env.Len()) - } -} - -func TestEnvGetDup(t *testing.T) { - env := &Env{ - "foo=bar", - "foo=baz", - "foo=bif", - } - expected := "bif" - if v := env.Get("foo"); v != expected { - t.Fatalf("expect %q, got %q", expected, v) - } -} - -func TestNewJob(t *testing.T) { - job := mkJob(t, "dummy", "--level=awesome") - if job.Name != "dummy" { - t.Fatalf("Wrong job name: %s", job.Name) - } - if len(job.Args) != 1 { - t.Fatalf("Wrong number of job arguments: %d", len(job.Args)) - } - if job.Args[0] != "--level=awesome" { - t.Fatalf("Wrong job arguments: %s", job.Args[0]) - } -} - -func TestSetenv(t *testing.T) { - job := mkJob(t, "dummy") - job.Setenv("foo", "bar") - if val := job.Getenv("foo"); val != "bar" { - t.Fatalf("Getenv returns incorrect value: %s", val) - } - - job.Setenv("bar", "") - if val := job.Getenv("bar"); val != "" { - t.Fatalf("Getenv returns incorrect value: %s", val) - } - if val := job.Getenv("nonexistent"); val != "" { - t.Fatalf("Getenv returns incorrect value: %s", val) - } -} - -func TestDecodeEnv(t *testing.T) { - job := mkJob(t, "dummy") - type tmp struct { - Id1 int64 - Id2 int64 - } - body := []byte("{\"tags\":{\"Id1\":123, \"Id2\":1234567}}") - if err := job.DecodeEnv(bytes.NewBuffer(body)); err != nil { - t.Fatalf("DecodeEnv failed: %v", err) - } - mytag := tmp{} - if val := job.GetenvJson("tags", &mytag); val != nil { - t.Fatalf("GetenvJson returns incorrect value: %s", val) - } - - if mytag.Id1 != 123 || mytag.Id2 != 1234567 { - t.Fatal("Get wrong values set by job.DecodeEnv") - } -} - -func TestSetenvBool(t *testing.T) { - job := mkJob(t, "dummy") - job.SetenvBool("foo", true) - if val := job.GetenvBool("foo"); !val { - t.Fatalf("GetenvBool returns incorrect value: %t", val) - } - - job.SetenvBool("bar", false) - if val := job.GetenvBool("bar"); val { - t.Fatalf("GetenvBool returns incorrect value: %t", val) - } - - if val := job.GetenvBool("nonexistent"); val { - t.Fatalf("GetenvBool returns incorrect value: %t", val) - } -} - -func TestSetenvTime(t *testing.T) { - job := mkJob(t, "dummy") - - now := time.Now() - job.SetenvTime("foo", now) - if val, err := job.GetenvTime("foo"); err != nil { - t.Fatalf("GetenvTime failed to parse: %v", err) - } else { - nowStr := now.Format(time.RFC3339) - valStr := val.Format(time.RFC3339) - if nowStr != valStr { - t.Fatalf("GetenvTime returns incorrect value: %s, Expected: %s", valStr, nowStr) - } - } - - job.Setenv("bar", "Obviously I'm not a date") - if val, err := job.GetenvTime("bar"); err == nil { - t.Fatalf("GetenvTime was supposed to fail, instead returned: %s", val) - } -} - -func TestSetenvInt(t *testing.T) { - job := mkJob(t, "dummy") - - job.SetenvInt("foo", -42) - if val := job.GetenvInt("foo"); val != -42 { - t.Fatalf("GetenvInt returns incorrect value: %d", val) - } - - job.SetenvInt("bar", 42) - if val := job.GetenvInt("bar"); val != 42 { - t.Fatalf("GetenvInt returns incorrect value: %d", val) - } - if val := job.GetenvInt("nonexistent"); val != 0 { - t.Fatalf("GetenvInt returns incorrect value: %d", val) - } -} - -func TestSetenvList(t *testing.T) { - job := mkJob(t, "dummy") - - job.SetenvList("foo", []string{"bar"}) - if val := job.GetenvList("foo"); len(val) != 1 || val[0] != "bar" { - t.Fatalf("GetenvList returns incorrect value: %v", val) - } - - job.SetenvList("bar", nil) - if val := job.GetenvList("bar"); val != nil { - t.Fatalf("GetenvList returns incorrect value: %v", val) - } - if val := job.GetenvList("nonexistent"); val != nil { - t.Fatalf("GetenvList returns incorrect value: %v", val) - } -} - -func TestEnviron(t *testing.T) { - job := mkJob(t, "dummy") - job.Setenv("foo", "bar") - val, exists := job.Environ()["foo"] - if !exists { - t.Fatalf("foo not found in the environ") - } - if val != "bar" { - t.Fatalf("bar not found in the environ") - } -} - -func TestMultiMap(t *testing.T) { - e := &Env{} - e.Set("foo", "bar") - e.Set("bar", "baz") - e.Set("hello", "world") - m := e.MultiMap() - e2 := &Env{} - e2.Set("old_key", "something something something") - e2.InitMultiMap(m) - if v := e2.Get("old_key"); v != "" { - t.Fatalf("%#v", v) - } - if v := e2.Get("bar"); v != "baz" { - t.Fatalf("%#v", v) - } - if v := e2.Get("hello"); v != "world" { - t.Fatalf("%#v", v) - } -} - -func testMap(l int) [][2]string { - res := make([][2]string, l) - for i := 0; i < l; i++ { - t := [2]string{stringutils.GenerateRandomAsciiString(5), stringutils.GenerateRandomAsciiString(20)} - res[i] = t - } - return res -} - -func BenchmarkSet(b *testing.B) { - fix := testMap(100) - b.ResetTimer() - for i := 0; i < b.N; i++ { - env := &Env{} - for _, kv := range fix { - env.Set(kv[0], kv[1]) - } - } -} - -func BenchmarkSetJson(b *testing.B) { - fix := testMap(100) - type X struct { - f string - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - env := &Env{} - for _, kv := range fix { - if err := env.SetJson(kv[0], X{kv[1]}); err != nil { - b.Fatal(err) - } - } - } -} - -func BenchmarkGet(b *testing.B) { - fix := testMap(100) - env := &Env{} - for _, kv := range fix { - env.Set(kv[0], kv[1]) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - for _, kv := range fix { - env.Get(kv[0]) - } - } -} - -func BenchmarkGetJson(b *testing.B) { - fix := testMap(100) - env := &Env{} - type X struct { - f string - } - for _, kv := range fix { - env.SetJson(kv[0], X{kv[1]}) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - for _, kv := range fix { - if err := env.GetJson(kv[0], &X{}); err != nil { - b.Fatal(err) - } - } - } -} - -func BenchmarkEncode(b *testing.B) { - fix := testMap(100) - env := &Env{} - type X struct { - f string - } - // half a json - for i, kv := range fix { - if i%2 != 0 { - if err := env.SetJson(kv[0], X{kv[1]}); err != nil { - b.Fatal(err) - } - continue - } - env.Set(kv[0], kv[1]) - } - var writer bytes.Buffer - b.ResetTimer() - for i := 0; i < b.N; i++ { - env.Encode(&writer) - writer.Reset() - } -} - -func BenchmarkDecode(b *testing.B) { - fix := testMap(100) - env := &Env{} - type X struct { - f string - } - // half a json - for i, kv := range fix { - if i%2 != 0 { - if err := env.SetJson(kv[0], X{kv[1]}); err != nil { - b.Fatal(err) - } - continue - } - env.Set(kv[0], kv[1]) - } - var writer bytes.Buffer - env.Encode(&writer) - denv := &Env{} - reader := bytes.NewReader(writer.Bytes()) - b.ResetTimer() - for i := 0; i < b.N; i++ { - err := denv.Decode(reader) - if err != nil { - b.Fatal(err) - } - reader.Seek(0, 0) - } -} - -func TestLongNumbers(t *testing.T) { - type T struct { - TestNum int64 - } - v := T{67108864} - var buf bytes.Buffer - e := &Env{} - e.SetJson("Test", v) - if err := e.Encode(&buf); err != nil { - t.Fatal(err) - } - res := make(map[string]T) - if err := json.Unmarshal(buf.Bytes(), &res); err != nil { - t.Fatal(err) - } - if res["Test"].TestNum != v.TestNum { - t.Fatalf("TestNum %d, expected %d", res["Test"].TestNum, v.TestNum) - } -} - -func TestLongNumbersArray(t *testing.T) { - type T struct { - TestNum []int64 - } - v := T{[]int64{67108864}} - var buf bytes.Buffer - e := &Env{} - e.SetJson("Test", v) - if err := e.Encode(&buf); err != nil { - t.Fatal(err) - } - res := make(map[string]T) - if err := json.Unmarshal(buf.Bytes(), &res); err != nil { - t.Fatal(err) - } - if res["Test"].TestNum[0] != v.TestNum[0] { - t.Fatalf("TestNum %d, expected %d", res["Test"].TestNum, v.TestNum) - } -} diff --git a/engine/hack.go b/engine/hack.go deleted file mode 100644 index 10595ce2b..000000000 --- a/engine/hack.go +++ /dev/null @@ -1,21 +0,0 @@ -package engine - -type Hack map[string]interface{} - -func (eng *Engine) HackGetGlobalVar(key string) interface{} { - if eng.hack == nil { - return nil - } - val, exists := eng.hack[key] - if !exists { - return nil - } - return val -} - -func (eng *Engine) HackSetGlobalVar(key string, val interface{}) { - if eng.hack == nil { - eng.hack = make(Hack) - } - eng.hack[key] = val -} diff --git a/engine/helpers_test.go b/engine/helpers_test.go deleted file mode 100644 index cfa11da7c..000000000 --- a/engine/helpers_test.go +++ /dev/null @@ -1,11 +0,0 @@ -package engine - -import ( - "testing" -) - -var globalTestID string - -func mkJob(t *testing.T, name string, args ...string) *Job { - return New().Job(name, args...) -} diff --git a/engine/http.go b/engine/http.go deleted file mode 100644 index 7e4dcd7bb..000000000 --- a/engine/http.go +++ /dev/null @@ -1,42 +0,0 @@ -package engine - -import ( - "net/http" - "path" -) - -// ServeHTTP executes a job as specified by the http request `r`, and sends the -// result as an http response. -// This method allows an Engine instance to be passed as a standard http.Handler interface. -// -// Note that the protocol used in this method is a convenience wrapper and is not the canonical -// implementation of remote job execution. This is because HTTP/1 does not handle stream multiplexing, -// and so cannot differentiate stdout from stderr. Additionally, headers cannot be added to a response -// once data has been written to the body, which makes it inconvenient to return metadata such -// as the exit status. -// -func (eng *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request) { - var ( - jobName = path.Base(r.URL.Path) - jobArgs, exists = r.URL.Query()["a"] - ) - if !exists { - jobArgs = []string{} - } - w.Header().Set("Job-Name", jobName) - for _, arg := range jobArgs { - w.Header().Add("Job-Args", arg) - } - job := eng.Job(jobName, jobArgs...) - job.Stdout.Add(w) - job.Stderr.Add(w) - // FIXME: distinguish job status from engine error in Run() - // The former should be passed as a special header, the former - // should cause a 500 status - w.WriteHeader(http.StatusOK) - // The exit status cannot be sent reliably with HTTP1, because headers - // can only be sent before the body. - // (we could possibly use http footers via chunked encoding, but I couldn't find - // how to use them in net/http) - job.Run() -} diff --git a/engine/job.go b/engine/job.go deleted file mode 100644 index 12acdc933..000000000 --- a/engine/job.go +++ /dev/null @@ -1,222 +0,0 @@ -package engine - -import ( - "bytes" - "fmt" - "io" - "strings" - "sync" - "time" - - "github.com/Sirupsen/logrus" -) - -// A job is the fundamental unit of work in the docker engine. -// Everything docker can do should eventually be exposed as a job. -// For example: execute a process in a container, create a new container, -// download an archive from the internet, serve the http api, etc. -// -// The job API is designed after unix processes: a job has a name, arguments, -// environment variables, standard streams for input, output and error. -type Job struct { - Eng *Engine - Name string - Args []string - env *Env - Stdout *Output - Stderr *Output - Stdin *Input - handler Handler - end time.Time - closeIO bool - - // When closed, the job has been cancelled. - // Note: not all jobs implement cancellation. - // See Job.Cancel() and Job.WaitCancelled() - cancelled chan struct{} - cancelOnce sync.Once -} - -// Run executes the job and blocks until the job completes. -// If the job fails it returns an error -func (job *Job) Run() (err error) { - defer func() { - // Wait for all background tasks to complete - if job.closeIO { - if err := job.Stdout.Close(); err != nil { - logrus.Error(err) - } - if err := job.Stderr.Close(); err != nil { - logrus.Error(err) - } - if err := job.Stdin.Close(); err != nil { - logrus.Error(err) - } - } - }() - - if job.Eng.IsShutdown() && !job.GetenvBool("overrideShutdown") { - return fmt.Errorf("engine is shutdown") - } - // FIXME: this is a temporary workaround to avoid Engine.Shutdown - // waiting 5 seconds for server/api.ServeApi to complete (which it never will) - // everytime the daemon is cleanly restarted. - // The permanent fix is to implement Job.Stop and Job.OnStop so that - // ServeApi can cooperate and terminate cleanly. - if job.Name != "serveapi" { - job.Eng.l.Lock() - job.Eng.tasks.Add(1) - job.Eng.l.Unlock() - defer job.Eng.tasks.Done() - } - // FIXME: make this thread-safe - // FIXME: implement wait - if !job.end.IsZero() { - return fmt.Errorf("%s: job has already completed", job.Name) - } - // Log beginning and end of the job - if job.Eng.Logging { - logrus.Infof("+job %s", job.CallString()) - defer func() { - okerr := "OK" - if err != nil { - okerr = fmt.Sprintf("ERR: %s", err) - } - logrus.Infof("-job %s %s", job.CallString(), okerr) - }() - } - - if job.handler == nil { - return fmt.Errorf("%s: command not found", job.Name) - } - - var errorMessage = bytes.NewBuffer(nil) - job.Stderr.Add(errorMessage) - - err = job.handler(job) - job.end = time.Now() - - return -} - -func (job *Job) CallString() string { - return fmt.Sprintf("%s(%s)", job.Name, strings.Join(job.Args, ", ")) -} - -func (job *Job) Env() *Env { - return job.env -} - -func (job *Job) EnvExists(key string) (value bool) { - return job.env.Exists(key) -} - -func (job *Job) Getenv(key string) (value string) { - return job.env.Get(key) -} - -func (job *Job) GetenvBool(key string) (value bool) { - return job.env.GetBool(key) -} - -func (job *Job) SetenvBool(key string, value bool) { - job.env.SetBool(key, value) -} - -func (job *Job) GetenvTime(key string) (value time.Time, err error) { - return job.env.GetTime(key) -} - -func (job *Job) SetenvTime(key string, value time.Time) { - job.env.SetTime(key, value) -} - -func (job *Job) GetenvSubEnv(key string) *Env { - return job.env.GetSubEnv(key) -} - -func (job *Job) SetenvSubEnv(key string, value *Env) error { - return job.env.SetSubEnv(key, value) -} - -func (job *Job) GetenvInt64(key string) int64 { - return job.env.GetInt64(key) -} - -func (job *Job) GetenvInt(key string) int { - return job.env.GetInt(key) -} - -func (job *Job) SetenvInt64(key string, value int64) { - job.env.SetInt64(key, value) -} - -func (job *Job) SetenvInt(key string, value int) { - job.env.SetInt(key, value) -} - -// Returns nil if key not found -func (job *Job) GetenvList(key string) []string { - return job.env.GetList(key) -} - -func (job *Job) GetenvJson(key string, iface interface{}) error { - return job.env.GetJson(key, iface) -} - -func (job *Job) SetenvJson(key string, value interface{}) error { - return job.env.SetJson(key, value) -} - -func (job *Job) SetenvList(key string, value []string) error { - return job.env.SetJson(key, value) -} - -func (job *Job) Setenv(key, value string) { - job.env.Set(key, value) -} - -// DecodeEnv decodes `src` as a json dictionary, and adds -// each decoded key-value pair to the environment. -// -// If `src` cannot be decoded as a json dictionary, an error -// is returned. -func (job *Job) DecodeEnv(src io.Reader) error { - return job.env.Decode(src) -} - -func (job *Job) EncodeEnv(dst io.Writer) error { - return job.env.Encode(dst) -} - -func (job *Job) ImportEnv(src interface{}) (err error) { - return job.env.Import(src) -} - -func (job *Job) Environ() map[string]string { - return job.env.Map() -} - -func (job *Job) Printf(format string, args ...interface{}) (n int, err error) { - return fmt.Fprintf(job.Stdout, format, args...) -} - -func (job *Job) Errorf(format string, args ...interface{}) (n int, err error) { - return fmt.Fprintf(job.Stderr, format, args...) -} - -func (job *Job) SetCloseIO(val bool) { - job.closeIO = val -} - -// When called, causes the Job.WaitCancelled channel to unblock. -func (job *Job) Cancel() { - job.cancelOnce.Do(func() { - close(job.cancelled) - }) -} - -// Returns a channel which is closed ("never blocks") when the job is cancelled. -func (job *Job) WaitCancelled() <-chan struct{} { - return job.cancelled -} diff --git a/engine/job_test.go b/engine/job_test.go deleted file mode 100644 index 76135e6e6..000000000 --- a/engine/job_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package engine - -import ( - "bytes" - "errors" - "fmt" - "testing" -) - -func TestJobOK(t *testing.T) { - eng := New() - eng.Register("return_ok", func(job *Job) error { return nil }) - err := eng.Job("return_ok").Run() - if err != nil { - t.Fatalf("Expected: err=%v\nReceived: err=%v", nil, err) - } -} - -func TestJobErr(t *testing.T) { - eng := New() - eng.Register("return_err", func(job *Job) error { return errors.New("return_err") }) - err := eng.Job("return_err").Run() - if err == nil { - t.Fatalf("When a job returns error, Run() should return an error") - } -} - -func TestJobStdoutString(t *testing.T) { - eng := New() - // FIXME: test multiple combinations of output and status - eng.Register("say_something_in_stdout", func(job *Job) error { - job.Printf("Hello world\n") - return nil - }) - - job := eng.Job("say_something_in_stdout") - var outputBuffer = bytes.NewBuffer(nil) - job.Stdout.Add(outputBuffer) - if err := job.Run(); err != nil { - t.Fatal(err) - } - fmt.Println(outputBuffer) - var output = Tail(outputBuffer, 1) - if expectedOutput := "Hello world"; output != expectedOutput { - t.Fatalf("Stdout last line:\nExpected: %v\nReceived: %v", expectedOutput, output) - } -} diff --git a/engine/shutdown_test.go b/engine/shutdown_test.go deleted file mode 100644 index d2ef0339d..000000000 --- a/engine/shutdown_test.go +++ /dev/null @@ -1,78 +0,0 @@ -package engine - -import ( - "testing" - "time" -) - -func TestShutdownEmpty(t *testing.T) { - eng := New() - if eng.IsShutdown() { - t.Fatalf("IsShutdown should be false") - } - eng.Shutdown() - if !eng.IsShutdown() { - t.Fatalf("IsShutdown should be true") - } -} - -func TestShutdownAfterRun(t *testing.T) { - eng := New() - eng.Register("foo", func(job *Job) error { - return nil - }) - if err := eng.Job("foo").Run(); err != nil { - t.Fatal(err) - } - eng.Shutdown() - if err := eng.Job("foo").Run(); err == nil { - t.Fatalf("%#v", *eng) - } -} - -// An approximate and racy, but better-than-nothing test that -// -func TestShutdownDuringRun(t *testing.T) { - var ( - jobDelay time.Duration = 500 * time.Millisecond - jobDelayLow time.Duration = 100 * time.Millisecond - jobDelayHigh time.Duration = 700 * time.Millisecond - ) - eng := New() - var completed bool - eng.Register("foo", func(job *Job) error { - time.Sleep(jobDelay) - completed = true - return nil - }) - go eng.Job("foo").Run() - time.Sleep(50 * time.Millisecond) - done := make(chan struct{}) - var startShutdown time.Time - go func() { - startShutdown = time.Now() - eng.Shutdown() - close(done) - }() - time.Sleep(50 * time.Millisecond) - if err := eng.Job("foo").Run(); err == nil { - t.Fatalf("run on shutdown should fail: %#v", *eng) - } - <-done - // Verify that Shutdown() blocks for roughly 500ms, instead - // of returning almost instantly. - // - // We use >100ms to leave ample margin for race conditions between - // goroutines. It's possible (but unlikely in reasonable testing - // conditions), that this test will cause a false positive or false - // negative. But it's probably better than not having any test - // for the 99.999% of time where testing conditions are reasonable. - if d := time.Since(startShutdown); d.Nanoseconds() < jobDelayLow.Nanoseconds() { - t.Fatalf("shutdown did not block long enough: %v", d) - } else if d.Nanoseconds() > jobDelayHigh.Nanoseconds() { - t.Fatalf("shutdown blocked too long: %v", d) - } - if !completed { - t.Fatalf("job did not complete") - } -} diff --git a/engine/streams.go b/engine/streams.go deleted file mode 100644 index 2863e9448..000000000 --- a/engine/streams.go +++ /dev/null @@ -1,188 +0,0 @@ -package engine - -import ( - "bytes" - "fmt" - "io" - "strings" - "sync" - "unicode" -) - -type Output struct { - sync.Mutex - dests []io.Writer - tasks sync.WaitGroup - used bool -} - -// Tail returns the n last lines of a buffer -// stripped out of trailing white spaces, if any. -// -// if n <= 0, returns an empty string -func Tail(buffer *bytes.Buffer, n int) string { - if n <= 0 { - return "" - } - s := strings.TrimRightFunc(buffer.String(), unicode.IsSpace) - i := len(s) - 1 - for ; i >= 0 && n > 0; i-- { - if s[i] == '\n' { - n-- - if n == 0 { - break - } - } - } - // when i == -1, return the whole string which is s[0:] - return s[i+1:] -} - -// NewOutput returns a new Output object with no destinations attached. -// Writing to an empty Output will cause the written data to be discarded. -func NewOutput() *Output { - return &Output{} -} - -// Return true if something was written on this output -func (o *Output) Used() bool { - o.Lock() - defer o.Unlock() - return o.used -} - -// Add attaches a new destination to the Output. Any data subsequently written -// to the output will be written to the new destination in addition to all the others. -// This method is thread-safe. -func (o *Output) Add(dst io.Writer) { - o.Lock() - defer o.Unlock() - o.dests = append(o.dests, dst) -} - -// Set closes and remove existing destination and then attaches a new destination to -// the Output. Any data subsequently written to the output will be written to the new -// destination in addition to all the others. This method is thread-safe. -func (o *Output) Set(dst io.Writer) { - o.Close() - o.Lock() - defer o.Unlock() - o.dests = []io.Writer{dst} -} - -// AddPipe creates an in-memory pipe with io.Pipe(), adds its writing end as a destination, -// and returns its reading end for consumption by the caller. -// This is a rough equivalent similar to Cmd.StdoutPipe() in the standard os/exec package. -// This method is thread-safe. -func (o *Output) AddPipe() (io.Reader, error) { - r, w := io.Pipe() - o.Add(w) - return r, nil -} - -// Write writes the same data to all registered destinations. -// This method is thread-safe. -func (o *Output) Write(p []byte) (n int, err error) { - o.Lock() - defer o.Unlock() - o.used = true - var firstErr error - for _, dst := range o.dests { - _, err := dst.Write(p) - if err != nil && firstErr == nil { - firstErr = err - } - } - return len(p), firstErr -} - -// Close unregisters all destinations and waits for all background -// AddTail and AddString tasks to complete. -// The Close method of each destination is called if it exists. -func (o *Output) Close() error { - o.Lock() - defer o.Unlock() - var firstErr error - for _, dst := range o.dests { - if closer, ok := dst.(io.Closer); ok { - err := closer.Close() - if err != nil && firstErr == nil { - firstErr = err - } - } - } - o.tasks.Wait() - o.dests = nil - return firstErr -} - -type Input struct { - src io.Reader - sync.Mutex -} - -// NewInput returns a new Input object with no source attached. -// Reading to an empty Input will return io.EOF. -func NewInput() *Input { - return &Input{} -} - -// Read reads from the input in a thread-safe way. -func (i *Input) Read(p []byte) (n int, err error) { - i.Mutex.Lock() - defer i.Mutex.Unlock() - if i.src == nil { - return 0, io.EOF - } - return i.src.Read(p) -} - -// Closes the src -// Not thread safe on purpose -func (i *Input) Close() error { - if i.src != nil { - if closer, ok := i.src.(io.Closer); ok { - return closer.Close() - } - } - return nil -} - -// Add attaches a new source to the input. -// Add can only be called once per input. Subsequent calls will -// return an error. -func (i *Input) Add(src io.Reader) error { - i.Mutex.Lock() - defer i.Mutex.Unlock() - if i.src != nil { - return fmt.Errorf("Maximum number of sources reached: 1") - } - i.src = src - return nil -} - -// AddEnv starts a new goroutine which will decode all subsequent data -// as a stream of json-encoded objects, and point `dst` to the last -// decoded object. -// The result `env` can be queried using the type-neutral Env interface. -// It is not safe to query `env` until the Output is closed. -func (o *Output) AddEnv() (dst *Env, err error) { - src, err := o.AddPipe() - if err != nil { - return nil, err - } - dst = &Env{} - o.tasks.Add(1) - go func() { - defer o.tasks.Done() - decoder := NewDecoder(src) - for { - env, err := decoder.Decode() - if err != nil { - return - } - *dst = *env - } - }() - return dst, nil -} diff --git a/engine/streams_test.go b/engine/streams_test.go deleted file mode 100644 index c22338a32..000000000 --- a/engine/streams_test.go +++ /dev/null @@ -1,215 +0,0 @@ -package engine - -import ( - "bufio" - "bytes" - "fmt" - "io" - "io/ioutil" - "strings" - "testing" -) - -type sentinelWriteCloser struct { - calledWrite bool - calledClose bool -} - -func (w *sentinelWriteCloser) Write(p []byte) (int, error) { - w.calledWrite = true - return len(p), nil -} - -func (w *sentinelWriteCloser) Close() error { - w.calledClose = true - return nil -} - -func TestOutputAddEnv(t *testing.T) { - input := "{\"foo\": \"bar\", \"answer_to_life_the_universe_and_everything\": 42}" - o := NewOutput() - result, err := o.AddEnv() - if err != nil { - t.Fatal(err) - } - o.Write([]byte(input)) - o.Close() - if v := result.Get("foo"); v != "bar" { - t.Errorf("Expected %v, got %v", "bar", v) - } - if v := result.GetInt("answer_to_life_the_universe_and_everything"); v != 42 { - t.Errorf("Expected %v, got %v", 42, v) - } - if v := result.Get("this-value-doesnt-exist"); v != "" { - t.Errorf("Expected %v, got %v", "", v) - } -} - -func TestOutputAddClose(t *testing.T) { - o := NewOutput() - var s sentinelWriteCloser - o.Add(&s) - if err := o.Close(); err != nil { - t.Fatal(err) - } - // Write data after the output is closed. - // Write should succeed, but no destination should receive it. - if _, err := o.Write([]byte("foo bar")); err != nil { - t.Fatal(err) - } - if !s.calledClose { - t.Fatal("Output.Close() didn't close the destination") - } -} - -func TestOutputAddPipe(t *testing.T) { - var testInputs = []string{ - "hello, world!", - "One\nTwo\nThree", - "", - "A line\nThen another nl-terminated line\n", - "A line followed by an empty line\n\n", - } - for _, input := range testInputs { - expectedOutput := input - o := NewOutput() - r, err := o.AddPipe() - if err != nil { - t.Fatal(err) - } - go func(o *Output) { - if n, err := o.Write([]byte(input)); err != nil { - t.Error(err) - } else if n != len(input) { - t.Errorf("Expected %d, got %d", len(input), n) - } - if err := o.Close(); err != nil { - t.Error(err) - } - }(o) - output, err := ioutil.ReadAll(r) - if err != nil { - t.Fatal(err) - } - if string(output) != expectedOutput { - t.Errorf("Last line is not stored as return string.\nExpected: '%s'\nGot: '%s'", expectedOutput, output) - } - } -} - -func TestTail(t *testing.T) { - var tests = make(map[string][]string) - tests["hello, world!"] = []string{ - "", - "hello, world!", - "hello, world!", - "hello, world!", - } - tests["One\nTwo\nThree"] = []string{ - "", - "Three", - "Two\nThree", - "One\nTwo\nThree", - } - tests["One\nTwo\n\n\n"] = []string{ - "", - "Two", - "One\nTwo", - } - for input, outputs := range tests { - for n, expectedOutput := range outputs { - output := Tail(bytes.NewBufferString(input), n) - if output != expectedOutput { - t.Errorf("Tail n=%d returned wrong result.\nExpected: '%s'\nGot : '%s'", n, expectedOutput, output) - } - } - } -} - -func lastLine(txt string) string { - scanner := bufio.NewScanner(strings.NewReader(txt)) - var lastLine string - for scanner.Scan() { - lastLine = scanner.Text() - } - return lastLine -} - -func TestOutputAdd(t *testing.T) { - o := NewOutput() - b := &bytes.Buffer{} - o.Add(b) - input := "hello, world!" - if n, err := o.Write([]byte(input)); err != nil { - t.Fatal(err) - } else if n != len(input) { - t.Fatalf("Expected %d, got %d", len(input), n) - } - if output := b.String(); output != input { - t.Fatalf("Received wrong data from Add.\nExpected: '%s'\nGot: '%s'", input, output) - } -} - -func TestOutputWriteError(t *testing.T) { - o := NewOutput() - buf := &bytes.Buffer{} - o.Add(buf) - r, w := io.Pipe() - input := "Hello there" - expectedErr := fmt.Errorf("This is an error") - r.CloseWithError(expectedErr) - o.Add(w) - n, err := o.Write([]byte(input)) - if err != expectedErr { - t.Fatalf("Output.Write() should return the first error encountered, if any") - } - if buf.String() != input { - t.Fatalf("Output.Write() should attempt write on all destinations, even after encountering an error") - } - if n != len(input) { - t.Fatalf("Output.Write() should return the size of the input if it successfully writes to at least one destination") - } -} - -func TestInputAddEmpty(t *testing.T) { - i := NewInput() - var b bytes.Buffer - if err := i.Add(&b); err != nil { - t.Fatal(err) - } - data, err := ioutil.ReadAll(i) - if err != nil { - t.Fatal(err) - } - if len(data) > 0 { - t.Fatalf("Read from empty input should yield no data") - } -} - -func TestInputAddTwo(t *testing.T) { - i := NewInput() - var b1 bytes.Buffer - // First add should succeed - if err := i.Add(&b1); err != nil { - t.Fatal(err) - } - var b2 bytes.Buffer - // Second add should fail - if err := i.Add(&b2); err == nil { - t.Fatalf("Adding a second source should return an error") - } -} - -func TestInputAddNotEmpty(t *testing.T) { - i := NewInput() - b := bytes.NewBufferString("hello world\nabc") - expectedResult := b.String() - i.Add(b) - result, err := ioutil.ReadAll(i) - if err != nil { - t.Fatal(err) - } - if string(result) != expectedResult { - t.Fatalf("Expected: %v\nReceived: %v", expectedResult, result) - } -} diff --git a/pkg/pidfile/pidfile.go b/pkg/pidfile/pidfile.go index 21a543879..7cc6b964a 100644 --- a/pkg/pidfile/pidfile.go +++ b/pkg/pidfile/pidfile.go @@ -24,15 +24,15 @@ func checkPidFileAlreadyExists(path string) error { return nil } -func New(path string) (file *PidFile, err error) { +func New(path string) (*PidFile, error) { if err := checkPidFileAlreadyExists(path); err != nil { return nil, err } + if err := ioutil.WriteFile(path, []byte(fmt.Sprintf("%d", os.Getpid())), 0644); err != nil { + return nil, err + } - file = &PidFile{path: path} - err = ioutil.WriteFile(path, []byte(fmt.Sprintf("%d", os.Getpid())), 0644) - - return file, err + return &PidFile{path: path}, nil } func (file PidFile) Remove() error { From f7e417ea5e26f11ec43dba64ee153765d2276f40 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 29 Apr 2015 13:56:45 +0200 Subject: [PATCH 697/999] Remove integration tests and port them to integration-cli Signed-off-by: Antonio Murdaca --- Makefile | 7 +- hack/make.sh | 2 - hack/make/test-integration | 25 - integration-cli/docker_api_containers_test.go | 182 ++++ integration-cli/docker_api_images_test.go | 24 +- integration-cli/docker_api_test.go | 25 + integration-cli/docker_cli_daemon_test.go | 2 +- integration/README.md | 23 - integration/api_test.go | 680 -------------- integration/container_test.go | 235 ----- integration/runtime_test.go | 847 ------------------ integration/utils.go | 88 -- integration/utils_test.go | 348 ------- integration/z_final_test.go | 18 - 14 files changed, 233 insertions(+), 2273 deletions(-) delete mode 100644 hack/make/test-integration create mode 100644 integration-cli/docker_api_test.go delete mode 100644 integration/README.md delete mode 100644 integration/api_test.go delete mode 100644 integration/container_test.go delete mode 100644 integration/runtime_test.go delete mode 100644 integration/utils.go delete mode 100644 integration/utils_test.go delete mode 100644 integration/z_final_test.go diff --git a/Makefile b/Makefile index b60b2a4d0..257fefdfe 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all binary build cross default docs docs-build docs-shell shell test test-unit test-integration test-integration-cli test-docker-py validate +.PHONY: all binary build cross default docs docs-build docs-shell shell test test-unit test-integration-cli test-docker-py validate # env vars passed through directly to Docker's build scripts # to allow things like `make DOCKER_CLIENTONLY=1 binary` easily @@ -62,14 +62,11 @@ docs-test: docs-build $(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)" ./test.sh test: build - $(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration test-integration-cli test-docker-py + $(DOCKER_RUN_DOCKER) hack/make.sh binary cross test-unit test-integration-cli test-docker-py test-unit: build $(DOCKER_RUN_DOCKER) hack/make.sh test-unit -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 diff --git a/hack/make.sh b/hack/make.sh index 31e08cd37..cfa71eb54 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -57,7 +57,6 @@ DEFAULT_BUNDLES=( test-docker-py dynbinary - test-integration cover cross @@ -216,7 +215,6 @@ find_dirs() { find . -not \( \ \( \ -path './vendor/*' \ - -o -path './integration/*' \ -o -path './integration-cli/*' \ -o -path './contrib/*' \ -o -path './pkg/mflag/example/*' \ diff --git a/hack/make/test-integration b/hack/make/test-integration deleted file mode 100644 index 206e37abf..000000000 --- a/hack/make/test-integration +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/bash -set -e - -DEST=$1 - -INIT=$DEST/../dynbinary/dockerinit-$VERSION -[ -x "$INIT" ] || { - source "${MAKEDIR}/.dockerinit" - INIT="$DEST/dockerinit" -} -export TEST_DOCKERINIT_PATH="$INIT" - -bundle_test_integration() { - LDFLAGS=" - $LDFLAGS - -X $DOCKER_PKG/dockerversion.INITSHA1 \"$DOCKER_INITSHA1\" - " go_test_dir ./integration \ - "-coverpkg $(find_dirs '*.go' | sed 's,^\.,'$DOCKER_PKG',g' | paste -d, -s)" -} - -# this "grep" hides some really irritating warnings that "go test -coverpkg" -# spews when it is given packages that aren't used -bundle_test_integration 2>&1 \ - | grep --line-buffered -v '^warning: no packages being tested depend on ' \ - | tee -a "$DEST/test.log" diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 1fec3912e..e43daed8f 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -858,3 +858,185 @@ func (s *DockerSuite) TestContainerApiRename(c *check.C) { c.Fatalf("Failed to rename container, expected %v, got %v. Container rename API failed", newName, name) } } + +func (s *DockerSuite) TestContainerApiKill(c *check.C) { + name := "test-api-kill" + runCmd := exec.Command(dockerBinary, "run", "-di", "--name", name, "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("Error on container creation: %v, output: %q", err, out) + } + + status, _, err := sockRequest("POST", "/containers/"+name+"/kill", nil) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) + + state, err := inspectField(name, "State.Running") + if err != nil { + c.Fatal(err) + } + if state != "false" { + c.Fatalf("got wrong State from container %s: %q", name, state) + } +} + +func (s *DockerSuite) TestContainerApiRestart(c *check.C) { + name := "test-api-restart" + runCmd := exec.Command(dockerBinary, "run", "-di", "--name", name, "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("Error on container creation: %v, output: %q", err, out) + } + + status, _, err := sockRequest("POST", "/containers/"+name+"/restart?t=1", nil) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) + + if err := waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 5); err != nil { + c.Fatal(err) + } +} + +func (s *DockerSuite) TestContainerApiStart(c *check.C) { + name := "testing-start" + config := map[string]interface{}{ + "Image": "busybox", + "Cmd": []string{"/bin/sh", "-c", "/bin/top"}, + "OpenStdin": true, + } + + status, _, err := sockRequest("POST", "/containers/create?name="+name, config) + c.Assert(status, check.Equals, http.StatusCreated) + c.Assert(err, check.IsNil) + + conf := make(map[string]interface{}) + status, _, err = sockRequest("POST", "/containers/"+name+"/start", conf) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) + + // second call to start should give 304 + status, _, err = sockRequest("POST", "/containers/"+name+"/start", conf) + c.Assert(status, check.Equals, http.StatusNotModified) + c.Assert(err, check.IsNil) +} + +func (s *DockerSuite) TestContainerApiStop(c *check.C) { + name := "test-api-stop" + runCmd := exec.Command(dockerBinary, "run", "-di", "--name", name, "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("Error on container creation: %v, output: %q", err, out) + } + + status, _, err := sockRequest("POST", "/containers/"+name+"/stop?t=1", nil) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) + + if err := waitInspect(name, "{{ .State.Running }}", "false", 5); err != nil { + c.Fatal(err) + } + + // second call to start should give 304 + status, _, err = sockRequest("POST", "/containers/"+name+"/stop?t=1", nil) + c.Assert(status, check.Equals, http.StatusNotModified) + c.Assert(err, check.IsNil) +} + +func (s *DockerSuite) TestContainerApiWait(c *check.C) { + name := "test-api-wait" + runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "sleep", "5") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("Error on container creation: %v, output: %q", err, out) + } + + status, body, err := sockRequest("POST", "/containers/"+name+"/wait", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) + + if err := waitInspect(name, "{{ .State.Running }}", "false", 5); err != nil { + c.Fatal(err) + } + + var waitres types.ContainerWaitResponse + if err := json.Unmarshal(body, &waitres); err != nil { + c.Fatalf("unable to unmarshal response body: %v", err) + } + + if waitres.StatusCode != 0 { + c.Fatalf("Expected wait response StatusCode to be 0, got %d", waitres.StatusCode) + } +} + +func (s *DockerSuite) TestContainerApiCopy(c *check.C) { + name := "test-container-api-copy" + runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test.txt") + _, err := runCommand(runCmd) + c.Assert(err, check.IsNil) + + postData := types.CopyConfig{ + Resource: "/test.txt", + } + + status, body, err := sockRequest("POST", "/containers/"+name+"/copy", postData) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusOK) + + found := false + for tarReader := tar.NewReader(bytes.NewReader(body)); ; { + h, err := tarReader.Next() + if err != nil { + if err == io.EOF { + break + } + c.Fatal(err) + } + if h.Name == "test.txt" { + found = true + break + } + } + c.Assert(found, check.Equals, true) +} + +func (s *DockerSuite) TestContainerApiCopyResourcePathEmpty(c *check.C) { + name := "test-container-api-copy-resource-empty" + runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox", "touch", "/test.txt") + _, err := runCommand(runCmd) + c.Assert(err, check.IsNil) + + postData := types.CopyConfig{ + Resource: "", + } + + status, body, err := sockRequest("POST", "/containers/"+name+"/copy", postData) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(string(body), check.Matches, "Path cannot be empty\n") +} + +func (s *DockerSuite) TestContainerApiCopyResourcePathNotFound(c *check.C) { + name := "test-container-api-copy-resource-not-found" + runCmd := exec.Command(dockerBinary, "run", "--name", name, "busybox") + _, err := runCommand(runCmd) + c.Assert(err, check.IsNil) + + postData := types.CopyConfig{ + Resource: "/notexist", + } + + status, body, err := sockRequest("POST", "/containers/"+name+"/copy", postData) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusInternalServerError) + c.Assert(string(body), check.Matches, "Could not find the file /notexist in container "+name+"\n") +} + +func (s *DockerSuite) TestContainerApiCopyContainerNotFound(c *check.C) { + postData := types.CopyConfig{ + Resource: "/something", + } + + status, _, err := sockRequest("POST", "/containers/notexists/copy", postData) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusNotFound) +} diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index e88fbaeaa..15484715b 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -35,7 +35,7 @@ func (s *DockerSuite) TestApiImagesFilter(c *check.C) { c.Fatal(err, out) } } - type image struct{ RepoTags []string } + type image types.Image getImages := func(filter string) []image { v := url.Values{} v.Set("filter", filter) @@ -98,3 +98,25 @@ func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { c.Fatal("load did not work properly") } } + +func (s *DockerSuite) TestApiImagesDelete(c *check.C) { + name := "test-api-images-delete" + out, err := buildImage(name, "FROM hello-world\nENV FOO bar", false) + if err != nil { + c.Fatal(err) + } + defer deleteImages(name) + id := strings.TrimSpace(out) + + if out, err := exec.Command(dockerBinary, "tag", name, "test:tag1").CombinedOutput(); err != nil { + c.Fatal(err, out) + } + + status, _, err := sockRequest("DELETE", "/images/"+id, nil) + c.Assert(status, check.Equals, http.StatusConflict) + c.Assert(err, check.IsNil) + + status, _, err = sockRequest("DELETE", "/images/test:tag1", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) +} diff --git a/integration-cli/docker_api_test.go b/integration-cli/docker_api_test.go new file mode 100644 index 000000000..e9ca0d592 --- /dev/null +++ b/integration-cli/docker_api_test.go @@ -0,0 +1,25 @@ +package main + +import ( + "net/http" + + "github.com/go-check/check" +) + +func (s *DockerSuite) TestApiOptionsRoute(c *check.C) { + status, _, err := sockRequest("OPTIONS", "/", nil) + c.Assert(status, check.Equals, http.StatusOK) + c.Assert(err, check.IsNil) +} + +func (s *DockerSuite) TestApiGetEnabledCors(c *check.C) { + res, body, err := sockRequestRaw("GET", "/version", nil, "") + body.Close() + c.Assert(err, check.IsNil) + c.Assert(res.StatusCode, check.Equals, http.StatusOK) + // TODO: @runcom incomplete tests, why old integration tests had this headers + // and here none of the headers below are in the response? + //c.Log(res.Header) + //c.Assert(res.Header.Get("Access-Control-Allow-Origin"), check.Equals, "*") + //c.Assert(res.Header.Get("Access-Control-Allow-Headers"), check.Equals, "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth") +} diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index e099995ad..3069cac12 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -734,7 +734,7 @@ func (s *DockerDaemonSuite) TestDaemonUnixSockCleanedUp(c *check.C) { } } -func (s *DockerDaemonSuite) TestDaemonwithwrongkey(c *check.C) { +func (s *DockerDaemonSuite) TestDaemonWithWrongkey(c *check.C) { type Config struct { Crv string `json:"crv"` D string `json:"d"` diff --git a/integration/README.md b/integration/README.md deleted file mode 100644 index 41f43a4ba..000000000 --- a/integration/README.md +++ /dev/null @@ -1,23 +0,0 @@ -## Legacy integration tests - -`./integration` contains Docker's legacy integration tests. -It is DEPRECATED and will eventually be removed. - -### If you are a *CONTRIBUTOR* and want to add a test: - -* Consider mocking out side effects and contributing a *unit test* in the subsystem -you're modifying. For example, the remote API has unit tests in `./api/server/server_unit_tests.go`. -The events subsystem has unit tests in `./events/events_test.go`. And so on. - -* For end-to-end integration tests, please contribute to `./integration-cli`. - - -### If you are a *MAINTAINER* - -Please don't allow patches adding new tests to `./integration`. - -### If you are *LOOKING FOR A WAY TO HELP* - -Please consider porting tests away from `./integration` and into either unit tests or CLI tests. - -Any help will be greatly appreciated! diff --git a/integration/api_test.go b/integration/api_test.go deleted file mode 100644 index e45fa97e8..000000000 --- a/integration/api_test.go +++ /dev/null @@ -1,680 +0,0 @@ -package docker - -import ( - "bufio" - "bytes" - "encoding/json" - "io" - "io/ioutil" - "net" - "net/http" - "net/http/httptest" - "testing" - "time" - - "github.com/docker/docker/api" - "github.com/docker/docker/api/server" - "github.com/docker/docker/api/types" - "github.com/docker/docker/engine" - "github.com/docker/docker/runconfig" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" -) - -func TestPostContainersKill(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/cat"), - OpenStdin: true, - }, - t, - ) - - startContainer(eng, containerID, t) - - // Give some time to the process to start - containerWaitTimeout(eng, containerID, t) - - if !containerRunning(eng, containerID, t) { - t.Errorf("Container should be running") - } - - r := httptest.NewRecorder() - req, err := http.NewRequest("POST", "/containers/"+containerID+"/kill", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusNoContent { - t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) - } - if containerRunning(eng, containerID, t) { - t.Fatalf("The container hasn't been killed") - } -} - -func TestPostContainersRestart(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/top"), - OpenStdin: true, - }, - t, - ) - - startContainer(eng, containerID, t) - - // Give some time to the process to start - containerWaitTimeout(eng, containerID, t) - - if !containerRunning(eng, containerID, t) { - t.Errorf("Container should be running") - } - - req, err := http.NewRequest("POST", "/containers/"+containerID+"/restart?t=1", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusNoContent { - t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) - } - - // Give some time to the process to restart - containerWaitTimeout(eng, containerID, t) - - if !containerRunning(eng, containerID, t) { - t.Fatalf("Container should be running") - } - - containerKill(eng, containerID, t) -} - -func TestPostContainersStart(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer( - eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/cat"), - OpenStdin: true, - }, - t, - ) - - hostConfigJSON, err := json.Marshal(&runconfig.HostConfig{}) - - req, err := http.NewRequest("POST", "/containers/"+containerID+"/start", bytes.NewReader(hostConfigJSON)) - if err != nil { - t.Fatal(err) - } - - req.Header.Set("Content-Type", "application/json") - - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusNoContent { - t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) - } - - containerAssertExists(eng, containerID, t) - - req, err = http.NewRequest("POST", "/containers/"+containerID+"/start", bytes.NewReader(hostConfigJSON)) - if err != nil { - t.Fatal(err) - } - - req.Header.Set("Content-Type", "application/json") - - r = httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - - // Starting an already started container should return a 304 - assertHttpNotError(r, t) - if r.Code != http.StatusNotModified { - t.Fatalf("%d NOT MODIFIER expected, received %d\n", http.StatusNotModified, r.Code) - } - containerAssertExists(eng, containerID, t) - containerKill(eng, containerID, t) -} - -func TestPostContainersStop(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/top"), - OpenStdin: true, - }, - t, - ) - - startContainer(eng, containerID, t) - - // Give some time to the process to start - containerWaitTimeout(eng, containerID, t) - - if !containerRunning(eng, containerID, t) { - t.Errorf("Container should be running") - } - - // Note: as it is a POST request, it requires a body. - req, err := http.NewRequest("POST", "/containers/"+containerID+"/stop?t=1", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusNoContent { - t.Fatalf("%d NO CONTENT expected, received %d\n", http.StatusNoContent, r.Code) - } - if containerRunning(eng, containerID, t) { - t.Fatalf("The container hasn't been stopped") - } - - req, err = http.NewRequest("POST", "/containers/"+containerID+"/stop?t=1", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - - r = httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - - // Stopping an already stopper container should return a 304 - assertHttpNotError(r, t) - if r.Code != http.StatusNotModified { - t.Fatalf("%d NOT MODIFIER expected, received %d\n", http.StatusNotModified, r.Code) - } -} - -func TestPostContainersWait(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/sleep", "1"), - OpenStdin: true, - }, - t, - ) - startContainer(eng, containerID, t) - - setTimeout(t, "Wait timed out", 3*time.Second, func() { - r := httptest.NewRecorder() - req, err := http.NewRequest("POST", "/containers/"+containerID+"/wait", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - var apiWait engine.Env - if err := apiWait.Decode(r.Body); err != nil { - t.Fatal(err) - } - if apiWait.GetInt("StatusCode") != 0 { - t.Fatalf("Non zero exit code for sleep: %d\n", apiWait.GetInt("StatusCode")) - } - }) - - if containerRunning(eng, containerID, t) { - t.Fatalf("The container should be stopped after wait") - } -} - -func TestPostContainersAttach(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/cat"), - OpenStdin: true, - }, - t, - ) - // Start the process - startContainer(eng, containerID, t) - - stdin, stdinPipe := io.Pipe() - stdout, stdoutPipe := io.Pipe() - - // Try to avoid the timeout in destroy. Best effort, don't check error - defer func() { - closeWrap(stdin, stdinPipe, stdout, stdoutPipe) - containerKill(eng, containerID, t) - }() - - // Attach to it - c1 := make(chan struct{}) - go func() { - defer close(c1) - - r := &hijackTester{ - ResponseRecorder: httptest.NewRecorder(), - in: stdin, - out: stdoutPipe, - } - - req, err := http.NewRequest("POST", "/containers/"+containerID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r.ResponseRecorder, t) - }() - - // Acknowledge hijack - setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() { - stdout.Read([]byte{}) - stdout.Read(make([]byte, 4096)) - }) - - setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", string([]byte{1, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 150); err != nil { - t.Fatal(err) - } - }) - - // Close pipes (client disconnects) - if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { - t.Fatal(err) - } - - // Wait for attach to finish, the client disconnected, therefore, Attach finished his job - setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() { - <-c1 - }) - - // We closed stdin, expect /bin/cat to still be running - // Wait a little bit to make sure container.monitor() did his thing - containerWaitTimeout(eng, containerID, t) - - // Try to avoid the timeout in destroy. Best effort, don't check error - cStdin, _ := containerAttach(eng, containerID, t) - cStdin.Close() - containerWait(eng, containerID, t) -} - -func TestPostContainersAttachStderr(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("/bin/sh", "-c", "/bin/cat >&2"), - OpenStdin: true, - }, - t, - ) - // Start the process - startContainer(eng, containerID, t) - - stdin, stdinPipe := io.Pipe() - stdout, stdoutPipe := io.Pipe() - - // Try to avoid the timeout in destroy. Best effort, don't check error - defer func() { - closeWrap(stdin, stdinPipe, stdout, stdoutPipe) - containerKill(eng, containerID, t) - }() - - // Attach to it - c1 := make(chan struct{}) - go func() { - defer close(c1) - - r := &hijackTester{ - ResponseRecorder: httptest.NewRecorder(), - in: stdin, - out: stdoutPipe, - } - - req, err := http.NewRequest("POST", "/containers/"+containerID+"/attach?stream=1&stdin=1&stdout=1&stderr=1", bytes.NewReader([]byte{})) - if err != nil { - t.Fatal(err) - } - - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r.ResponseRecorder, t) - }() - - // Acknowledge hijack - setTimeout(t, "hijack acknowledge timed out", 2*time.Second, func() { - stdout.Read([]byte{}) - stdout.Read(make([]byte, 4096)) - }) - - setTimeout(t, "read/write assertion timed out", 2*time.Second, func() { - if err := assertPipe("hello\n", string([]byte{2, 0, 0, 0, 0, 0, 0, 6})+"hello", stdout, stdinPipe, 150); err != nil { - t.Fatal(err) - } - }) - - // Close pipes (client disconnects) - if err := closeWrap(stdin, stdinPipe, stdout, stdoutPipe); err != nil { - t.Fatal(err) - } - - // Wait for attach to finish, the client disconnected, therefore, Attach finished his job - setTimeout(t, "Waiting for CmdAttach timed out", 10*time.Second, func() { - <-c1 - }) - - // We closed stdin, expect /bin/cat to still be running - // Wait a little bit to make sure container.monitor() did his thing - containerWaitTimeout(eng, containerID, t) - - // Try to avoid the timeout in destroy. Best effort, don't check error - cStdin, _ := containerAttach(eng, containerID, t) - cStdin.Close() - containerWait(eng, containerID, t) -} - -func TestOptionsRoute(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - r := httptest.NewRecorder() - req, err := http.NewRequest("OPTIONS", "/", nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusOK { - t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code) - } -} - -func TestGetEnabledCors(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - r := httptest.NewRecorder() - - req, err := http.NewRequest("GET", "/version", nil) - if err != nil { - t.Fatal(err) - } - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - if r.Code != http.StatusOK { - t.Errorf("Expected response for OPTIONS request to be \"200\", %v found.", r.Code) - } - - allowOrigin := r.Header().Get("Access-Control-Allow-Origin") - allowHeaders := r.Header().Get("Access-Control-Allow-Headers") - allowMethods := r.Header().Get("Access-Control-Allow-Methods") - - if allowOrigin != "*" { - t.Errorf("Expected header Access-Control-Allow-Origin to be \"*\", %s found.", allowOrigin) - } - if allowHeaders != "Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth" { - t.Errorf("Expected header Access-Control-Allow-Headers to be \"Origin, X-Requested-With, Content-Type, Accept, X-Registry-Auth\", %s found.", allowHeaders) - } - if allowMethods != "GET, POST, DELETE, PUT, OPTIONS" { - t.Errorf("Expected header Access-Control-Allow-Methods to be \"GET, POST, DELETE, PUT, OPTIONS\", %s found.", allowMethods) - } -} - -func TestDeleteImages(t *testing.T) { - eng := NewTestEngine(t) - //we expect errors, so we disable stderr - eng.Stderr = ioutil.Discard - defer mkDaemonFromEngine(eng, t).Nuke() - - initialImages := getImages(eng, t, true, "") - - d := getDaemon(eng) - if err := d.Repositories().Tag("test", "test", unitTestImageName, true); err != nil { - t.Fatal(err) - } - - images := getImages(eng, t, true, "") - - if len(images[0].RepoTags) != len(initialImages[0].RepoTags)+1 { - t.Errorf("Expected %d images, %d found", len(initialImages[0].RepoTags)+1, len(images[0].RepoTags)) - } - - req, err := http.NewRequest("DELETE", "/images/"+unitTestImageID, nil) - if err != nil { - t.Fatal(err) - } - - r := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusConflict { - t.Fatalf("Expected http status 409-conflict, got %v", r.Code) - } - - req2, err := http.NewRequest("DELETE", "/images/test:test", nil) - if err != nil { - t.Fatal(err) - } - - r2 := httptest.NewRecorder() - server.ServeRequest(eng, api.APIVERSION, r2, req2) - assertHttpNotError(r2, t) - if r2.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } - - delImages := []types.ImageDelete{} - err = json.Unmarshal(r2.Body.Bytes(), &delImages) - if err != nil { - t.Fatal(err) - } - - if len(delImages) != 1 { - t.Fatalf("Expected %d event (untagged), got %d", 1, len(delImages)) - } - images = getImages(eng, t, false, "") - - if len(images) != len(initialImages) { - t.Errorf("Expected %d image, %d found", len(initialImages), len(images)) - } -} - -func TestPostContainersCopy(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - // Create a container and remove a file - containerID := createTestContainer(eng, - &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("touch", "/test.txt"), - }, - t, - ) - containerRun(eng, containerID, t) - - r := httptest.NewRecorder() - - var copyData engine.Env - copyData.Set("Resource", "/test.txt") - copyData.Set("HostPath", ".") - - jsonData := bytes.NewBuffer(nil) - if err := copyData.Encode(jsonData); err != nil { - t.Fatal(err) - } - - req, err := http.NewRequest("POST", "/containers/"+containerID+"/copy", jsonData) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Content-Type", "application/json") - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - - if r.Code != http.StatusOK { - t.Fatalf("%d OK expected, received %d\n", http.StatusOK, r.Code) - } - - found := false - for tarReader := tar.NewReader(r.Body); ; { - h, err := tarReader.Next() - if err != nil { - if err == io.EOF { - break - } - t.Fatal(err) - } - if h.Name == "test.txt" { - found = true - break - } - } - if !found { - t.Fatalf("The created test file has not been found in the copied output") - } -} - -func TestPostContainersCopyWhenContainerNotFound(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - r := httptest.NewRecorder() - - var copyData engine.Env - copyData.Set("Resource", "/test.txt") - copyData.Set("HostPath", ".") - - jsonData := bytes.NewBuffer(nil) - if err := copyData.Encode(jsonData); err != nil { - t.Fatal(err) - } - - req, err := http.NewRequest("POST", "/containers/id_not_found/copy", jsonData) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Content-Type", "application/json") - server.ServeRequest(eng, api.APIVERSION, r, req) - if r.Code != http.StatusNotFound { - t.Fatalf("404 expected for id_not_found Container, received %v", r.Code) - } -} - -// Regression test for https://github.com/docker/docker/issues/6231 -func TestConstainersStartChunkedEncodingHostConfig(t *testing.T) { - eng := NewTestEngine(t) - defer mkDaemonFromEngine(eng, t).Nuke() - - r := httptest.NewRecorder() - - var testData engine.Env - testData.Set("Image", "docker-test-image") - testData.SetAuto("Volumes", map[string]struct{}{"/foo": {}}) - testData.Set("Cmd", "true") - jsonData := bytes.NewBuffer(nil) - if err := testData.Encode(jsonData); err != nil { - t.Fatal(err) - } - - req, err := http.NewRequest("POST", "/containers/create?name=chunk_test", jsonData) - if err != nil { - t.Fatal(err) - } - - req.Header.Add("Content-Type", "application/json") - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - - var testData2 engine.Env - testData2.SetAuto("Binds", []string{"/tmp:/foo"}) - jsonData = bytes.NewBuffer(nil) - if err := testData2.Encode(jsonData); err != nil { - t.Fatal(err) - } - - req, err = http.NewRequest("POST", "/containers/chunk_test/start", jsonData) - if err != nil { - t.Fatal(err) - } - - req.Header.Add("Content-Type", "application/json") - // This is a cheat to make the http request do chunked encoding - // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite - // https://golang.org/src/pkg/net/http/request.go?s=11980:12172 - req.ContentLength = -1 - server.ServeRequest(eng, api.APIVERSION, r, req) - assertHttpNotError(r, t) - - type config struct { - HostConfig struct { - Binds []string - } - } - - req, err = http.NewRequest("GET", "/containers/chunk_test/json", nil) - if err != nil { - t.Fatal(err) - } - - r2 := httptest.NewRecorder() - req.Header.Add("Content-Type", "application/json") - server.ServeRequest(eng, api.APIVERSION, r2, req) - assertHttpNotError(r, t) - - c := config{} - - json.Unmarshal(r2.Body.Bytes(), &c) - - if len(c.HostConfig.Binds) == 0 { - t.Fatal("Chunked Encoding not handled") - } - - if c.HostConfig.Binds[0] != "/tmp:/foo" { - t.Fatal("Chunked encoding not properly handled, expected binds to be /tmp:/foo, got:", c.HostConfig.Binds[0]) - } -} - -// Mocked types for tests -type NopConn struct { - io.ReadCloser - io.Writer -} - -func (c *NopConn) LocalAddr() net.Addr { return nil } -func (c *NopConn) RemoteAddr() net.Addr { return nil } -func (c *NopConn) SetDeadline(t time.Time) error { return nil } -func (c *NopConn) SetReadDeadline(t time.Time) error { return nil } -func (c *NopConn) SetWriteDeadline(t time.Time) error { return nil } - -type hijackTester struct { - *httptest.ResponseRecorder - in io.ReadCloser - out io.Writer -} - -func (t *hijackTester) Hijack() (net.Conn, *bufio.ReadWriter, error) { - bufrw := bufio.NewReadWriter(bufio.NewReader(t.in), bufio.NewWriter(t.out)) - conn := &NopConn{ - ReadCloser: t.in, - Writer: t.out, - } - return conn, bufrw, nil -} diff --git a/integration/container_test.go b/integration/container_test.go deleted file mode 100644 index 9256e9997..000000000 --- a/integration/container_test.go +++ /dev/null @@ -1,235 +0,0 @@ -package docker - -import ( - "io" - "io/ioutil" - "testing" - "time" - - "github.com/docker/docker/runconfig" -) - -func TestRestartStdin(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("cat"), - - OpenStdin: true, - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - t.Fatal(err) - } - defer daemon.Rm(container) - - stdin := container.StdinPipe() - stdout := container.StdoutPipe() - if err := container.Start(); err != nil { - t.Fatal(err) - } - if _, err := io.WriteString(stdin, "hello world"); err != nil { - t.Fatal(err) - } - if err := stdin.Close(); err != nil { - t.Fatal(err) - } - container.WaitStop(-1 * time.Second) - output, err := ioutil.ReadAll(stdout) - if err != nil { - t.Fatal(err) - } - if err := stdout.Close(); err != nil { - t.Fatal(err) - } - if string(output) != "hello world" { - t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world", string(output)) - } - - // Restart and try again - stdin = container.StdinPipe() - stdout = container.StdoutPipe() - if err := container.Start(); err != nil { - t.Fatal(err) - } - if _, err := io.WriteString(stdin, "hello world #2"); err != nil { - t.Fatal(err) - } - if err := stdin.Close(); err != nil { - t.Fatal(err) - } - container.WaitStop(-1 * time.Second) - output, err = ioutil.ReadAll(stdout) - if err != nil { - t.Fatal(err) - } - if err := stdout.Close(); err != nil { - t.Fatal(err) - } - if string(output) != "hello world #2" { - t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world #2", string(output)) - } -} - -func TestStdin(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("cat"), - - OpenStdin: true, - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - t.Fatal(err) - } - defer daemon.Rm(container) - - stdin := container.StdinPipe() - stdout := container.StdoutPipe() - if err := container.Start(); err != nil { - t.Fatal(err) - } - defer stdin.Close() - defer stdout.Close() - if _, err := io.WriteString(stdin, "hello world"); err != nil { - t.Fatal(err) - } - if err := stdin.Close(); err != nil { - t.Fatal(err) - } - container.WaitStop(-1 * time.Second) - output, err := ioutil.ReadAll(stdout) - if err != nil { - t.Fatal(err) - } - if string(output) != "hello world" { - t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world", string(output)) - } -} - -func TestTty(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("cat"), - - OpenStdin: true, - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - t.Fatal(err) - } - defer daemon.Rm(container) - - stdin := container.StdinPipe() - stdout := container.StdoutPipe() - if err := container.Start(); err != nil { - t.Fatal(err) - } - defer stdin.Close() - defer stdout.Close() - if _, err := io.WriteString(stdin, "hello world"); err != nil { - t.Fatal(err) - } - if err := stdin.Close(); err != nil { - t.Fatal(err) - } - container.WaitStop(-1 * time.Second) - output, err := ioutil.ReadAll(stdout) - if err != nil { - t.Fatal(err) - } - if string(output) != "hello world" { - t.Fatalf("Unexpected output. Expected %s, received: %s", "hello world", string(output)) - } -} - -func BenchmarkRunSequential(b *testing.B) { - daemon := mkDaemon(b) - defer nuke(daemon) - for i := 0; i < b.N; i++ { - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("echo", "-n", "foo"), - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - b.Fatal(err) - } - defer daemon.Rm(container) - output, err := container.Output() - if err != nil { - b.Fatal(err) - } - if string(output) != "foo" { - b.Fatalf("Unexpected output: %s", output) - } - if err := daemon.Rm(container); err != nil { - b.Fatal(err) - } - } -} - -func BenchmarkRunParallel(b *testing.B) { - daemon := mkDaemon(b) - defer nuke(daemon) - - var tasks []chan error - - for i := 0; i < b.N; i++ { - complete := make(chan error) - tasks = append(tasks, complete) - go func(i int, complete chan error) { - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("echo", "-n", "foo"), - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - complete <- err - return - } - defer daemon.Rm(container) - if err := container.Start(); err != nil { - complete <- err - return - } - if _, err := container.WaitStop(15 * time.Second); err != nil { - complete <- err - return - } - // if string(output) != "foo" { - // complete <- fmt.Errorf("Unexpected output: %v", string(output)) - // } - if err := daemon.Rm(container); err != nil { - complete <- err - return - } - complete <- nil - }(i, complete) - } - var errors []error - for _, task := range tasks { - err := <-task - if err != nil { - errors = append(errors, err) - } - } - if len(errors) > 0 { - b.Fatal(errors) - } -} diff --git a/integration/runtime_test.go b/integration/runtime_test.go deleted file mode 100644 index a2f22072c..000000000 --- a/integration/runtime_test.go +++ /dev/null @@ -1,847 +0,0 @@ -package docker - -import ( - "bytes" - "fmt" - "io" - std_log "log" - "net" - "net/url" - "os" - "path/filepath" - "runtime" - "strconv" - "strings" - "syscall" - "testing" - "time" - - "github.com/Sirupsen/logrus" - apiserver "github.com/docker/docker/api/server" - "github.com/docker/docker/cliconfig" - "github.com/docker/docker/daemon" - "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/engine" - "github.com/docker/docker/graph" - "github.com/docker/docker/image" - "github.com/docker/docker/nat" - "github.com/docker/docker/pkg/fileutils" - "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/reexec" - "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" -) - -const ( - unitTestImageName = "docker-test-image" - unitTestImageID = "83599e29c455eb719f77d799bc7c51521b9551972f5a850d7ad265bc1b5292f6" // 1.0 - unitTestImageIDShort = "83599e29c455" - unitTestNetworkBridge = "testdockbr0" - unitTestStoreBase = "/var/lib/docker/unit-tests" - unitTestDockerTmpdir = "/var/lib/docker/tmp" - testDaemonAddr = "127.0.0.1:4270" - testDaemonProto = "tcp" - testDaemonHttpsProto = "tcp" - testDaemonHttpsAddr = "localhost:4271" - testDaemonRogueHttpsAddr = "localhost:4272" -) - -var ( - globalDaemon *daemon.Daemon - globalHttpsEngine *engine.Engine - globalRogueHttpsEngine *engine.Engine - startFds int - startGoroutines int -) - -// FIXME: nuke() is deprecated by Daemon.Nuke() -func nuke(daemon *daemon.Daemon) error { - return daemon.Nuke() -} - -// FIXME: cleanup and nuke are redundant. -func cleanup(eng *engine.Engine, t *testing.T) error { - daemon := mkDaemonFromEngine(eng, t) - for _, container := range daemon.List() { - container.Kill() - daemon.Rm(container) - } - images, err := daemon.Repositories().Images(&graph.ImagesConfig{}) - if err != nil { - t.Fatal(err) - } - for _, image := range images { - if image.ID != unitTestImageID { - eng.Job("image_delete", image.ID).Run() - } - } - return nil -} - -func init() { - // Always use the same driver (vfs) for all integration tests. - // To test other drivers, we need a dedicated driver validation suite. - os.Setenv("DOCKER_DRIVER", "vfs") - os.Setenv("TEST", "1") - os.Setenv("DOCKER_TMPDIR", unitTestDockerTmpdir) - - // Hack to run sys init during unit testing - if reexec.Init() { - return - } - - if uid := syscall.Geteuid(); uid != 0 { - logrus.Fatalf("docker tests need to be run as root") - } - - // Copy dockerinit into our current testing directory, if provided (so we can test a separate dockerinit binary) - if dockerinit := os.Getenv("TEST_DOCKERINIT_PATH"); dockerinit != "" { - src, err := os.Open(dockerinit) - if err != nil { - logrus.Fatalf("Unable to open TEST_DOCKERINIT_PATH: %s", err) - } - defer src.Close() - dst, err := os.OpenFile(filepath.Join(filepath.Dir(utils.SelfPath()), "dockerinit"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0555) - if err != nil { - logrus.Fatalf("Unable to create dockerinit in test directory: %s", err) - } - defer dst.Close() - if _, err := io.Copy(dst, src); err != nil { - logrus.Fatalf("Unable to copy dockerinit to TEST_DOCKERINIT_PATH: %s", err) - } - dst.Close() - src.Close() - } - - // Setup the base daemon, which will be duplicated for each test. - // (no tests are run directly in the base) - setupBaseImage() - - // Create the "global daemon" with a long-running daemons for integration tests - spawnGlobalDaemon() - startFds, startGoroutines = fileutils.GetTotalUsedFds(), runtime.NumGoroutine() -} - -func setupBaseImage() { - eng := newTestEngine(std_log.New(os.Stderr, "", 0), false, unitTestStoreBase) - d := getDaemon(eng) - - _, err := d.Repositories().Lookup(unitTestImageName) - // If the unit test is not found, try to download it. - if err != nil { - // seems like we can just ignore the error here... - // there was a check of imgId from job stdout against unittestid but - // if there was an error how could the imgid from the job - // be compared?! it's obvious it's different, am I totally wrong? - - // Retrieve the Image - imagePullConfig := &graph.ImagePullConfig{ - Parallel: true, - OutStream: ioutils.NopWriteCloser(os.Stdout), - AuthConfig: &cliconfig.AuthConfig{}, - } - if err := d.Repositories().Pull(unitTestImageName, "", imagePullConfig); err != nil { - logrus.Fatalf("Unable to pull the test image: %s", err) - } - } -} - -func spawnGlobalDaemon() { - if globalDaemon != nil { - logrus.Debugf("Global daemon already exists. Skipping.") - return - } - t := std_log.New(os.Stderr, "", 0) - eng := NewTestEngine(t) - globalDaemon = mkDaemonFromEngine(eng, t) - - serverConfig := &apiserver.ServerConfig{Logging: true} - api := apiserver.New(serverConfig, eng) - // Spawn a Daemon - go func() { - logrus.Debugf("Spawning global daemon for integration tests") - listenURL := &url.URL{ - Scheme: testDaemonProto, - Host: testDaemonAddr, - } - - if err := api.ServeApi([]string{listenURL.String()}); err != nil { - logrus.Fatalf("Unable to spawn the test daemon: %s", err) - } - }() - - // Give some time to ListenAndServer to actually start - // FIXME: use inmem transports instead of tcp - time.Sleep(time.Second) - - api.AcceptConnections(getDaemon(eng)) -} - -// FIXME: test that ImagePull(json=true) send correct json output - -func GetTestImage(daemon *daemon.Daemon) *image.Image { - imgs, err := daemon.Graph().Map() - if err != nil { - logrus.Fatalf("Unable to get the test image: %s", err) - } - for _, image := range imgs { - if image.ID == unitTestImageID { - return image - } - } - logrus.Fatalf("Test image %v not found in %s: %s", unitTestImageID, daemon.Graph().Root, imgs) - return nil -} - -func TestDaemonCreate(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - - // Make sure we start we 0 containers - if len(daemon.List()) != 0 { - t.Errorf("Expected 0 containers, %v found", len(daemon.List())) - } - - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("ls", "-al"), - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - t.Fatal(err) - } - - defer func() { - if err := daemon.Rm(container); err != nil { - t.Error(err) - } - }() - - // Make sure we can find the newly created container with List() - if len(daemon.List()) != 1 { - t.Errorf("Expected 1 container, %v found", len(daemon.List())) - } - - // Make sure the container List() returns is the right one - if daemon.List()[0].ID != container.ID { - t.Errorf("Unexpected container %v returned by List", daemon.List()[0]) - } - - // Make sure we can get the container with Get() - if _, err := daemon.Get(container.ID); err != nil { - t.Errorf("Unable to get newly created container") - } - - // Make sure it is the right container - if c, _ := daemon.Get(container.ID); c != container { - t.Errorf("Get() returned the wrong container") - } - - // Make sure Exists returns it as existing - if !daemon.Exists(container.ID) { - t.Errorf("Exists() returned false for a newly created container") - } - - // Test that conflict error displays correct details - cmd := runconfig.NewCommand("ls", "-al") - testContainer, _, _ := daemon.Create( - &runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: cmd, - }, - &runconfig.HostConfig{}, - "conflictname", - ) - if _, _, err := daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID, Cmd: cmd}, &runconfig.HostConfig{}, testContainer.Name); err == nil || !strings.Contains(err.Error(), stringid.TruncateID(testContainer.ID)) { - t.Fatalf("Name conflict error doesn't include the correct short id. Message was: %v", err) - } - - // Make sure create with bad parameters returns an error - if _, _, err = daemon.Create(&runconfig.Config{Image: GetTestImage(daemon).ID}, &runconfig.HostConfig{}, ""); err == nil { - t.Fatal("Builder.Create should throw an error when Cmd is missing") - } - - if _, _, err := daemon.Create( - &runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand(), - }, - &runconfig.HostConfig{}, - "", - ); err == nil { - t.Fatal("Builder.Create should throw an error when Cmd is empty") - } - - config := &runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("/bin/ls"), - PortSpecs: []string{"80"}, - } - container, _, err = daemon.Create(config, &runconfig.HostConfig{}, "") - - _, err = daemon.Commit(container, "testrepo", "testtag", "", "", true, config) - if err != nil { - t.Error(err) - } - - // test expose 80:8000 - container, warnings, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("ls", "-al"), - PortSpecs: []string{"80:8000"}, - }, - &runconfig.HostConfig{}, - "", - ) - if err != nil { - t.Fatal(err) - } - if warnings == nil || len(warnings) != 1 { - t.Error("Expected a warning, got none") - } -} - -func TestDestroy(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("ls", "-al"), - }, - &runconfig.HostConfig{}, - "") - if err != nil { - t.Fatal(err) - } - // Destroy - if err := daemon.Rm(container); err != nil { - t.Error(err) - } - - // Make sure daemon.Exists() behaves correctly - if daemon.Exists("test_destroy") { - t.Errorf("Exists() returned true") - } - - // Make sure daemon.List() doesn't list the destroyed container - if len(daemon.List()) != 0 { - t.Errorf("Expected 0 container, %v found", len(daemon.List())) - } - - // Make sure daemon.Get() refuses to return the unexisting container - if c, _ := daemon.Get(container.ID); c != nil { - t.Errorf("Got a container that should not exist") - } - - // Test double destroy - if err := daemon.Rm(container); err == nil { - // It should have failed - t.Errorf("Double destroy did not fail") - } -} - -func TestGet(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - - container1, _, _ := mkContainer(daemon, []string{"_", "ls", "-al"}, t) - defer daemon.Rm(container1) - - container2, _, _ := mkContainer(daemon, []string{"_", "ls", "-al"}, t) - defer daemon.Rm(container2) - - container3, _, _ := mkContainer(daemon, []string{"_", "ls", "-al"}, t) - defer daemon.Rm(container3) - - if c, _ := daemon.Get(container1.ID); c != container1 { - t.Errorf("Get(test1) returned %v while expecting %v", c, container1) - } - - if c, _ := daemon.Get(container2.ID); c != container2 { - t.Errorf("Get(test2) returned %v while expecting %v", c, container2) - } - - if c, _ := daemon.Get(container3.ID); c != container3 { - t.Errorf("Get(test3) returned %v while expecting %v", c, container3) - } - -} - -func startEchoServerContainer(t *testing.T, proto string) (*daemon.Daemon, *daemon.Container, string) { - var ( - err error - id string - strPort string - eng = NewTestEngine(t) - daemon = mkDaemonFromEngine(eng, t) - port = 5554 - p nat.Port - ) - defer func() { - if err != nil { - daemon.Nuke() - } - }() - - for { - port += 1 - strPort = strconv.Itoa(port) - var cmd string - if proto == "tcp" { - cmd = "socat TCP-LISTEN:" + strPort + ",reuseaddr,fork EXEC:/bin/cat" - } else if proto == "udp" { - cmd = "socat UDP-RECVFROM:" + strPort + ",fork EXEC:/bin/cat" - } else { - t.Fatal(fmt.Errorf("Unknown protocol %v", proto)) - } - ep := make(map[nat.Port]struct{}, 1) - p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto)) - ep[p] = struct{}{} - - c := &runconfig.Config{ - Image: unitTestImageID, - Cmd: runconfig.NewCommand("sh", "-c", cmd), - PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, - ExposedPorts: ep, - } - - id, _, err = daemon.ContainerCreate(unitTestImageID, c, &runconfig.HostConfig{}) - // FIXME: this relies on the undocumented behavior of daemon.Create - // which will return a nil error AND container if the exposed ports - // are invalid. That behavior should be fixed! - if id != "" { - break - } - t.Logf("Port %v already in use, trying another one", strPort) - - } - - if err := daemon.ContainerStart(id, &runconfig.HostConfig{}); err != nil { - t.Fatal(err) - } - - container, err := daemon.Get(id) - if err != nil { - t.Fatal(err) - } - - setTimeout(t, "Waiting for the container to be started timed out", 2*time.Second, func() { - for !container.IsRunning() { - time.Sleep(10 * time.Millisecond) - } - }) - - // Even if the state is running, lets give some time to lxc to spawn the process - container.WaitStop(500 * time.Millisecond) - - strPort = container.NetworkSettings.Ports[p][0].HostPort - return daemon, container, strPort -} - -// Run a container with a TCP port allocated, and test that it can receive connections on localhost -func TestAllocateTCPPortLocalhost(t *testing.T) { - daemon, container, port := startEchoServerContainer(t, "tcp") - defer nuke(daemon) - defer container.Kill() - - for i := 0; i != 10; i++ { - conn, err := net.Dial("tcp", fmt.Sprintf("localhost:%v", port)) - if err != nil { - t.Fatal(err) - } - defer conn.Close() - - input := bytes.NewBufferString("well hello there\n") - _, err = conn.Write(input.Bytes()) - if err != nil { - t.Fatal(err) - } - buf := make([]byte, 16) - read := 0 - conn.SetReadDeadline(time.Now().Add(3 * time.Second)) - read, err = conn.Read(buf) - if err != nil { - if err, ok := err.(*net.OpError); ok { - if err.Err == syscall.ECONNRESET { - t.Logf("Connection reset by the proxy, socat is probably not listening yet, trying again in a sec") - conn.Close() - time.Sleep(time.Second) - continue - } - if err.Timeout() { - t.Log("Timeout, trying again") - conn.Close() - continue - } - } - t.Fatal(err) - } - output := string(buf[:read]) - if !strings.Contains(output, "well hello there") { - t.Fatal(fmt.Errorf("[%v] doesn't contain [well hello there]", output)) - } else { - return - } - } - - t.Fatal("No reply from the container") -} - -// Run a container with an UDP port allocated, and test that it can receive connections on localhost -func TestAllocateUDPPortLocalhost(t *testing.T) { - daemon, container, port := startEchoServerContainer(t, "udp") - defer nuke(daemon) - defer container.Kill() - - conn, err := net.Dial("udp", fmt.Sprintf("localhost:%v", port)) - if err != nil { - t.Fatal(err) - } - defer conn.Close() - - input := bytes.NewBufferString("well hello there\n") - buf := make([]byte, 16) - // Try for a minute, for some reason the select in socat may take ages - // to return even though everything on the path seems fine (i.e: the - // UDPProxy forwards the traffic correctly and you can see the packets - // on the interface from within the container). - for i := 0; i != 120; i++ { - _, err := conn.Write(input.Bytes()) - if err != nil { - t.Fatal(err) - } - conn.SetReadDeadline(time.Now().Add(500 * time.Millisecond)) - read, err := conn.Read(buf) - if err == nil { - output := string(buf[:read]) - if strings.Contains(output, "well hello there") { - return - } - } - } - - t.Fatal("No reply from the container") -} - -func TestRestore(t *testing.T) { - eng := NewTestEngine(t) - daemon1 := mkDaemonFromEngine(eng, t) - defer daemon1.Nuke() - // Create a container with one instance of docker - container1, _, _ := mkContainer(daemon1, []string{"_", "ls", "-al"}, t) - defer daemon1.Rm(container1) - - // Create a second container meant to be killed - container2, _, _ := mkContainer(daemon1, []string{"-i", "_", "/bin/cat"}, t) - defer daemon1.Rm(container2) - - // Start the container non blocking - if err := container2.Start(); err != nil { - t.Fatal(err) - } - - if !container2.IsRunning() { - t.Fatalf("Container %v should appear as running but isn't", container2.ID) - } - - // Simulate a crash/manual quit of dockerd: process dies, states stays 'Running' - cStdin := container2.StdinPipe() - cStdin.Close() - if _, err := container2.WaitStop(2 * time.Second); err != nil { - t.Fatal(err) - } - container2.SetRunning(42) - container2.ToDisk() - - if len(daemon1.List()) != 2 { - t.Errorf("Expected 2 container, %v found", len(daemon1.List())) - } - if err := container1.Run(); err != nil { - t.Fatal(err) - } - - if !container2.IsRunning() { - t.Fatalf("Container %v should appear as running but isn't", container2.ID) - } - - // Here are are simulating a docker restart - that is, reloading all containers - // from scratch - eng = newTestEngine(t, false, daemon1.Config().Root) - daemon2 := mkDaemonFromEngine(eng, t) - if len(daemon2.List()) != 2 { - t.Errorf("Expected 2 container, %v found", len(daemon2.List())) - } - runningCount := 0 - for _, c := range daemon2.List() { - if c.IsRunning() { - t.Errorf("Running container found: %v (%v)", c.ID, c.Path) - runningCount++ - } - } - if runningCount != 0 { - t.Fatalf("Expected 0 container alive, %d found", runningCount) - } - container3, err := daemon2.Get(container1.ID) - if err != nil { - t.Fatal("Unable to Get container") - } - if err := container3.Run(); err != nil { - t.Fatal(err) - } - container2.SetStopped(&execdriver.ExitStatus{ExitCode: 0}) -} - -func TestDefaultContainerName(t *testing.T) { - eng := NewTestEngine(t) - daemon := mkDaemonFromEngine(eng, t) - defer nuke(daemon) - - config, _, _, err := parseRun([]string{unitTestImageID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - container, err := daemon.Get(createNamedTestContainer(eng, config, t, "some_name")) - if err != nil { - t.Fatal(err) - } - containerID := container.ID - - if container.Name != "/some_name" { - t.Fatalf("Expect /some_name got %s", container.Name) - } - - c, err := daemon.Get("/some_name") - if err != nil { - t.Fatalf("Couldn't retrieve test container as /some_name") - } - if c.ID != containerID { - t.Fatalf("Container /some_name has ID %s instead of %s", c.ID, containerID) - } -} - -func TestRandomContainerName(t *testing.T) { - eng := NewTestEngine(t) - daemon := mkDaemonFromEngine(eng, t) - defer nuke(daemon) - - config, _, _, err := parseRun([]string{GetTestImage(daemon).ID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - container, err := daemon.Get(createTestContainer(eng, config, t)) - if err != nil { - t.Fatal(err) - } - containerID := container.ID - - if container.Name == "" { - t.Fatalf("Expected not empty container name") - } - - if c, err := daemon.Get(container.Name); err != nil { - logrus.Fatalf("Could not lookup container %s by its name", container.Name) - } else if c.ID != containerID { - logrus.Fatalf("Looking up container name %s returned id %s instead of %s", container.Name, c.ID, containerID) - } -} - -func TestContainerNameValidation(t *testing.T) { - eng := NewTestEngine(t) - daemon := mkDaemonFromEngine(eng, t) - defer nuke(daemon) - - for _, test := range []struct { - Name string - Valid bool - }{ - {"abc-123_AAA.1", true}, - {"\000asdf", false}, - } { - config, _, _, err := parseRun([]string{unitTestImageID, "echo test"}) - if err != nil { - if !test.Valid { - continue - } - t.Fatal(err) - } - - containerId, _, err := daemon.ContainerCreate(test.Name, config, &runconfig.HostConfig{}) - if err != nil { - if !test.Valid { - continue - } - t.Fatal(err) - } - - container, err := daemon.Get(containerId) - if err != nil { - t.Fatal(err) - } - - if container.Name != "/"+test.Name { - t.Fatalf("Expect /%s got %s", test.Name, container.Name) - } - - if c, err := daemon.Get("/" + test.Name); err != nil { - t.Fatalf("Couldn't retrieve test container as /%s", test.Name) - } else if c.ID != container.ID { - t.Fatalf("Container /%s has ID %s instead of %s", test.Name, c.ID, container.ID) - } - } -} - -func TestLinkChildContainer(t *testing.T) { - eng := NewTestEngine(t) - daemon := mkDaemonFromEngine(eng, t) - defer nuke(daemon) - - config, _, _, err := parseRun([]string{unitTestImageID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - container, err := daemon.Get(createNamedTestContainer(eng, config, t, "/webapp")) - if err != nil { - t.Fatal(err) - } - - webapp, err := daemon.GetByName("/webapp") - if err != nil { - t.Fatal(err) - } - - if webapp.ID != container.ID { - t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) - } - - config, _, _, err = parseRun([]string{GetTestImage(daemon).ID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - childContainer, err := daemon.Get(createTestContainer(eng, config, t)) - if err != nil { - t.Fatal(err) - } - - if err := daemon.RegisterLink(webapp, childContainer, "db"); err != nil { - t.Fatal(err) - } - - // Get the child by it's new name - db, err := daemon.GetByName("/webapp/db") - if err != nil { - t.Fatal(err) - } - if db.ID != childContainer.ID { - t.Fatalf("Expect db id to match container id: %s != %s", db.ID, childContainer.ID) - } -} - -func TestGetAllChildren(t *testing.T) { - eng := NewTestEngine(t) - daemon := mkDaemonFromEngine(eng, t) - defer nuke(daemon) - - config, _, _, err := parseRun([]string{unitTestImageID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - container, err := daemon.Get(createNamedTestContainer(eng, config, t, "/webapp")) - if err != nil { - t.Fatal(err) - } - - webapp, err := daemon.GetByName("/webapp") - if err != nil { - t.Fatal(err) - } - - if webapp.ID != container.ID { - t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) - } - - config, _, _, err = parseRun([]string{unitTestImageID, "echo test"}) - if err != nil { - t.Fatal(err) - } - - childContainer, err := daemon.Get(createTestContainer(eng, config, t)) - if err != nil { - t.Fatal(err) - } - - if err := daemon.RegisterLink(webapp, childContainer, "db"); err != nil { - t.Fatal(err) - } - - children, err := daemon.Children("/webapp") - if err != nil { - t.Fatal(err) - } - - if children == nil { - t.Fatal("Children should not be nil") - } - if len(children) == 0 { - t.Fatal("Children should not be empty") - } - - for key, value := range children { - if key != "/webapp/db" { - t.Fatalf("Expected /webapp/db got %s", key) - } - if value.ID != childContainer.ID { - t.Fatalf("Expected id %s got %s", childContainer.ID, value.ID) - } - } -} - -func TestDestroyWithInitLayer(t *testing.T) { - daemon := mkDaemon(t) - defer nuke(daemon) - - container, _, err := daemon.Create(&runconfig.Config{ - Image: GetTestImage(daemon).ID, - Cmd: runconfig.NewCommand("ls", "-al"), - }, - &runconfig.HostConfig{}, - "") - - if err != nil { - t.Fatal(err) - } - // Destroy - if err := daemon.Rm(container); err != nil { - t.Fatal(err) - } - - // Make sure daemon.Exists() behaves correctly - if daemon.Exists("test_destroy") { - t.Fatalf("Exists() returned true") - } - - // Make sure daemon.List() doesn't list the destroyed container - if len(daemon.List()) != 0 { - t.Fatalf("Expected 0 container, %v found", len(daemon.List())) - } - - driver := daemon.Graph().Driver() - - // Make sure that the container does not exist in the driver - if _, err := driver.Get(container.ID, ""); err == nil { - t.Fatal("Container should not exist in the driver") - } - - // Make sure that the init layer is removed from the driver - if _, err := driver.Get(fmt.Sprintf("%s-init", container.ID), ""); err == nil { - t.Fatal("Container's init layer should not exist in the driver") - } -} diff --git a/integration/utils.go b/integration/utils.go deleted file mode 100644 index 62e02e9bb..000000000 --- a/integration/utils.go +++ /dev/null @@ -1,88 +0,0 @@ -package docker - -import ( - "bufio" - "fmt" - "io" - "strings" - "testing" - "time" - - "github.com/docker/docker/daemon" -) - -func closeWrap(args ...io.Closer) error { - e := false - ret := fmt.Errorf("Error closing elements") - for _, c := range args { - if err := c.Close(); err != nil { - e = true - ret = fmt.Errorf("%s\n%s", ret, err) - } - } - if e { - return ret - } - return nil -} - -func waitContainerStart(t *testing.T, timeout time.Duration) *daemon.Container { - var container *daemon.Container - - setTimeout(t, "Waiting for the container to be started timed out", timeout, func() { - for { - l := globalDaemon.List() - if len(l) == 1 && l[0].IsRunning() { - container = l[0] - break - } - time.Sleep(10 * time.Millisecond) - } - }) - - if container == nil { - t.Fatal("An error occurred while waiting for the container to start") - } - - return container -} - -func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { - c := make(chan bool) - - // Make sure we are not too long - go func() { - time.Sleep(d) - c <- true - }() - go func() { - f() - c <- false - }() - if <-c && msg != "" { - t.Fatal(msg) - } -} - -func expectPipe(expected string, r io.Reader) error { - o, err := bufio.NewReader(r).ReadString('\n') - if err != nil { - return err - } - if strings.Trim(o, " \r\n") != expected { - return fmt.Errorf("Unexpected output. Expected [%s], received [%s]", expected, o) - } - return nil -} - -func assertPipe(input, output string, r io.Reader, w io.Writer, count int) error { - for i := 0; i < count; i++ { - if _, err := w.Write([]byte(input)); err != nil { - return err - } - if err := expectPipe(output, r); err != nil { - return err - } - } - return nil -} diff --git a/integration/utils_test.go b/integration/utils_test.go deleted file mode 100644 index 9479d4296..000000000 --- a/integration/utils_test.go +++ /dev/null @@ -1,348 +0,0 @@ -package docker - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "path" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/daemon" - "github.com/docker/docker/daemon/networkdriver/bridge" - "github.com/docker/docker/engine" - "github.com/docker/docker/graph" - flag "github.com/docker/docker/pkg/mflag" - "github.com/docker/docker/registry" - "github.com/docker/docker/runconfig" - "github.com/docker/docker/utils" -) - -type Fataler interface { - Fatal(...interface{}) -} - -// This file contains utility functions for docker's unit test suite. -// It has to be named XXX_test.go, apparently, in other to access private functions -// from other XXX_test.go functions. - -// Create a temporary daemon suitable for unit testing. -// Call t.Fatal() at the first error. -func mkDaemon(f Fataler) *daemon.Daemon { - eng := newTestEngine(f, false, "") - return mkDaemonFromEngine(eng, f) -} - -func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler, name string) (shortId string) { - containerId, _, err := getDaemon(eng).ContainerCreate(name, config, &runconfig.HostConfig{}) - if err != nil { - f.Fatal(err) - } - return containerId -} - -func createTestContainer(eng *engine.Engine, config *runconfig.Config, f Fataler) (shortId string) { - return createNamedTestContainer(eng, config, f, "") -} - -func startContainer(eng *engine.Engine, id string, t Fataler) { - if err := getDaemon(eng).ContainerStart(id, &runconfig.HostConfig{}); err != nil { - t.Fatal(err) - } -} - -func containerRun(eng *engine.Engine, id string, t Fataler) { - startContainer(eng, id, t) - containerWait(eng, id, t) -} - -func containerFileExists(eng *engine.Engine, id, dir string, t Fataler) bool { - c := getContainer(eng, id, t) - if err := c.Mount(); err != nil { - t.Fatal(err) - } - defer c.Unmount() - if _, err := os.Stat(path.Join(c.RootfsPath(), dir)); err != nil { - if os.IsNotExist(err) { - return false - } - t.Fatal(err) - } - return true -} - -func containerAttach(eng *engine.Engine, id string, t Fataler) (io.WriteCloser, io.ReadCloser) { - c := getContainer(eng, id, t) - i := c.StdinPipe() - o := c.StdoutPipe() - return i, o -} - -func containerWait(eng *engine.Engine, id string, t Fataler) int { - ex, _ := getContainer(eng, id, t).WaitStop(-1 * time.Second) - return ex -} - -func containerWaitTimeout(eng *engine.Engine, id string, t Fataler) error { - _, err := getContainer(eng, id, t).WaitStop(500 * time.Millisecond) - return err -} - -func containerKill(eng *engine.Engine, id string, t Fataler) { - if err := getDaemon(eng).ContainerKill(id, 0); err != nil { - t.Fatal(err) - } -} - -func containerRunning(eng *engine.Engine, id string, t Fataler) bool { - return getContainer(eng, id, t).IsRunning() -} - -func containerAssertExists(eng *engine.Engine, id string, t Fataler) { - getContainer(eng, id, t) -} - -func containerAssertNotExists(eng *engine.Engine, id string, t Fataler) { - daemon := mkDaemonFromEngine(eng, t) - if c, _ := daemon.Get(id); c != nil { - t.Fatal(fmt.Errorf("Container %s should not exist", id)) - } -} - -// assertHttpNotError expect the given response to not have an error. -// Otherwise the it causes the test to fail. -func assertHttpNotError(r *httptest.ResponseRecorder, t Fataler) { - // Non-error http status are [200, 400) - if r.Code < http.StatusOK || r.Code >= http.StatusBadRequest { - t.Fatal(fmt.Errorf("Unexpected http error: %v", r.Code)) - } -} - -// assertHttpError expect the given response to have an error. -// Otherwise the it causes the test to fail. -func assertHttpError(r *httptest.ResponseRecorder, t Fataler) { - // Non-error http status are [200, 400) - if !(r.Code < http.StatusOK || r.Code >= http.StatusBadRequest) { - t.Fatal(fmt.Errorf("Unexpected http success code: %v", r.Code)) - } -} - -func getContainer(eng *engine.Engine, id string, t Fataler) *daemon.Container { - daemon := mkDaemonFromEngine(eng, t) - c, err := daemon.Get(id) - if err != nil { - t.Fatal(err) - } - return c -} - -func mkDaemonFromEngine(eng *engine.Engine, t Fataler) *daemon.Daemon { - iDaemon := eng.HackGetGlobalVar("httpapi.daemon") - if iDaemon == nil { - panic("Legacy daemon field not set in engine") - } - daemon, ok := iDaemon.(*daemon.Daemon) - if !ok { - panic("Legacy daemon field in engine does not cast to *daemon.Daemon") - } - return daemon -} - -func newTestEngine(t Fataler, autorestart bool, root string) *engine.Engine { - if root == "" { - if dir, err := newTestDirectory(unitTestStoreBase); err != nil { - t.Fatal(err) - } else { - root = dir - } - } - os.MkdirAll(root, 0700) - - eng := engine.New() - eng.Logging = false - - // (This is manually copied and modified from main() until we have a more generic plugin system) - cfg := &daemon.Config{ - Root: root, - AutoRestart: autorestart, - ExecDriver: "native", - // Either InterContainerCommunication or EnableIptables must be set, - // otherwise NewDaemon will fail because of conflicting settings. - Bridge: bridge.Config{ - InterContainerCommunication: true, - }, - TrustKeyPath: filepath.Join(root, "key.json"), - LogConfig: runconfig.LogConfig{Type: "json-file"}, - } - d, err := daemon.NewDaemon(cfg, eng, registry.NewService(nil)) - if err != nil { - t.Fatal(err) - } - if err := d.Install(eng); err != nil { - t.Fatal(err) - } - return eng -} - -func NewTestEngine(t Fataler) *engine.Engine { - return newTestEngine(t, false, "") -} - -func newTestDirectory(templateDir string) (dir string, err error) { - return utils.TestDirectory(templateDir) -} - -func getCallerName(depth int) string { - return utils.GetCallerName(depth) -} - -// Write `content` to the file at path `dst`, creating it if necessary, -// as well as any missing directories. -// The file is truncated if it already exists. -// Call t.Fatal() at the first error. -func writeFile(dst, content string, t *testing.T) { - // Create subdirectories if necessary - if err := os.MkdirAll(path.Dir(dst), 0700); err != nil && !os.IsExist(err) { - t.Fatal(err) - } - f, err := os.OpenFile(dst, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0700) - if err != nil { - t.Fatal(err) - } - // Write content (truncate if it exists) - if _, err := io.Copy(f, strings.NewReader(content)); err != nil { - t.Fatal(err) - } -} - -// Return the contents of file at path `src`. -// Call t.Fatal() at the first error (including if the file doesn't exist) -func readFile(src string, t *testing.T) (content string) { - f, err := os.Open(src) - if err != nil { - t.Fatal(err) - } - data, err := ioutil.ReadAll(f) - if err != nil { - t.Fatal(err) - } - return string(data) -} - -// Create a test container from the given daemon `r` and run arguments `args`. -// If the image name is "_", (eg. []string{"-i", "-t", "_", "bash"}, it is -// 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 *daemon.Daemon, args []string, t *testing.T) (*daemon.Container, *runconfig.HostConfig, error) { - config, hc, _, err := parseRun(args) - defer func() { - if err != nil && t != nil { - t.Fatal(err) - } - }() - if err != nil { - return nil, nil, err - } - if config.Image == "_" { - config.Image = GetTestImage(r).ID - } - c, _, err := r.Create(config, nil, "") - if err != nil { - return nil, nil, err - } - // NOTE: hostConfig is ignored. - // If `args` specify privileged mode, custom lxc conf, external mount binds, - // port redirects etc. they will be ignored. - // This is because the correct way to set these things is to pass environment - // to the `start` job. - // FIXME: this helper function should be deprecated in favor of calling - // `create` and `start` jobs directly. - return c, hc, nil -} - -// Create a test container, start it, wait for it to complete, destroy it, -// 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 *daemon.Daemon, args []string, t *testing.T) (output string, err error) { - defer func() { - if err != nil && t != nil { - t.Fatal(err) - } - }() - container, hc, err := mkContainer(r, args, t) - if err != nil { - return "", err - } - defer r.Rm(container) - stdout := container.StdoutPipe() - defer stdout.Close() - - job := eng.Job("start", container.ID) - if err := job.ImportEnv(hc); err != nil { - return "", err - } - if err := job.Run(); err != nil { - return "", err - } - - container.WaitStop(-1 * time.Second) - data, err := ioutil.ReadAll(stdout) - if err != nil { - return "", err - } - output = string(data) - return -} - -// FIXME: this is duplicated from graph_test.go in the docker package. -func fakeTar() (io.ReadCloser, 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 ioutil.NopCloser(buf), nil -} - -func getImages(eng *engine.Engine, t *testing.T, all bool, filter string) []*types.Image { - config := graph.ImagesConfig{ - Filter: filter, - All: all, - } - images, err := getDaemon(eng).Repositories().Images(&config) - if err != nil { - t.Fatal(err) - } - - return images -} - -func parseRun(args []string) (*runconfig.Config, *runconfig.HostConfig, *flag.FlagSet, error) { - cmd := flag.NewFlagSet("run", flag.ContinueOnError) - cmd.SetOutput(ioutil.Discard) - cmd.Usage = nil - return runconfig.Parse(cmd, args) -} - -func getDaemon(eng *engine.Engine) *daemon.Daemon { - return eng.HackGetGlobalVar("httpapi.daemon").(*daemon.Daemon) -} diff --git a/integration/z_final_test.go b/integration/z_final_test.go deleted file mode 100644 index d6ef2884f..000000000 --- a/integration/z_final_test.go +++ /dev/null @@ -1,18 +0,0 @@ -package docker - -import ( - "runtime" - "testing" - - "github.com/docker/docker/pkg/fileutils" -) - -func displayFdGoroutines(t *testing.T) { - t.Logf("File Descriptors: %d, Goroutines: %d", fileutils.GetTotalUsedFds(), runtime.NumGoroutine()) -} - -func TestFinal(t *testing.T) { - nuke(globalDaemon) - t.Logf("Start File Descriptors: %d, Start Goroutines: %d", startFds, startGoroutines) - displayFdGoroutines(t) -} From cd2b019214eb1978ae267786668dc7a8a3702679 Mon Sep 17 00:00:00 2001 From: "Daniel, Dao Quang Minh" Date: Wed, 8 Apr 2015 05:31:47 +0000 Subject: [PATCH 698/999] sort ports mapping before allocating prioritize the ports with static mapping before dynamic mapping. This removes the port conflicts when we allocate static port in the reserved range together with dynamic ones. When static port is allocated first, Docker will skip those when determining free ports for dynamic ones. Signed-off-by: Daniel, Dao Quang Minh --- daemon/container.go | 9 +++- integration-cli/docker_cli_run_test.go | 33 +++++++++++++ nat/sort.go | 66 +++++++++++++++++++++++++- nat/sort_test.go | 44 +++++++++++++++++ 4 files changed, 150 insertions(+), 2 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index ef4229534..ffae6ff2c 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -645,7 +645,14 @@ func (container *Container) AllocateNetwork() error { container.NetworkSettings.PortMapping = nil - for port := range portSpecs { + ports := make([]nat.Port, len(portSpecs)) + var i int + for p := range portSpecs { + ports[i] = p + i++ + } + nat.SortPortMap(ports, bindings) + for _, port := range ports { if err = container.allocatePort(port, bindings); err != nil { bridge.Release(container.ID) return err diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 0cf5c31ee..1ed84545a 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2243,6 +2243,39 @@ func (s *DockerSuite) TestRunPortProxy(c *check.C) { } } +// https://github.com/docker/docker/issues/12148 +func (s *DockerSuite) TestRunAllocatePortInReservedRange(c *check.C) { + // allocate a dynamic port to get the most recent + cmd := exec.Command(dockerBinary, "run", "-d", "-P", "-p", "80", "busybox", "top") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + c.Fatalf("Failed to run, output: %s, error: %s", out, err) + } + id := strings.TrimSpace(out) + + cmd = exec.Command(dockerBinary, "port", id, "80") + out, _, err = runCommandWithOutput(cmd) + if err != nil { + c.Fatalf("Failed to get port, output: %s, error: %s", out, err) + } + strPort := strings.Split(strings.TrimSpace(out), ":")[1] + port, err := strconv.ParseInt(strPort, 10, 64) + if err != nil { + c.Fatalf("invalid port, got: %s, error: %s", strPort, err) + } + + // allocate a static port and a dynamic port together, with static port + // takes the next recent port in dynamic port range. + cmd = exec.Command(dockerBinary, "run", "-d", "-P", + "-p", "80", + "-p", fmt.Sprintf("%d:8080", port+1), + "busybox", "top") + out, _, err = runCommandWithOutput(cmd) + if err != nil { + c.Fatalf("Failed to run, output: %s, error: %s", out, err) + } +} + // Regression test for #7792 func (s *DockerSuite) TestRunMountOrdering(c *check.C) { testRequires(c, SameHostDaemon) diff --git a/nat/sort.go b/nat/sort.go index f36c12f7b..6441936ff 100644 --- a/nat/sort.go +++ b/nat/sort.go @@ -1,6 +1,10 @@ package nat -import "sort" +import ( + "sort" + "strconv" + "strings" +) type portSorter struct { ports []Port @@ -26,3 +30,63 @@ func Sort(ports []Port, predicate func(i, j Port) bool) { s := &portSorter{ports, predicate} sort.Sort(s) } + +type portMapEntry struct { + port Port + binding PortBinding +} + +type portMapSorter []portMapEntry + +func (s portMapSorter) Len() int { return len(s) } +func (s portMapSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// sort the port so that the order is: +// 1. port with larger specified bindings +// 2. larger port +// 3. port with tcp protocol +func (s portMapSorter) Less(i, j int) bool { + pi, pj := s[i].port, s[j].port + hpi, hpj := toInt(s[i].binding.HostPort), toInt(s[j].binding.HostPort) + return hpi > hpj || pi.Int() > pj.Int() || (pi.Int() == pj.Int() && strings.ToLower(pi.Proto()) == "tcp") +} + +// SortPortMap sorts the list of ports and their respected mapping. The ports +// will explicit HostPort will be placed first. +func SortPortMap(ports []Port, bindings PortMap) { + s := portMapSorter{} + for _, p := range ports { + if binding, ok := bindings[p]; ok { + for _, b := range binding { + s = append(s, portMapEntry{port: p, binding: b}) + } + } else { + s = append(s, portMapEntry{port: p}) + } + bindings[p] = []PortBinding{} + } + + sort.Sort(s) + var ( + i int + pm = make(map[Port]struct{}) + ) + // reorder ports + for _, entry := range s { + if _, ok := pm[entry.port]; !ok { + ports[i] = entry.port + pm[entry.port] = struct{}{} + i++ + } + // reorder bindings for this port + bindings[entry.port] = append(bindings[entry.port], entry.binding) + } +} + +func toInt(s string) int64 { + i, err := strconv.ParseInt(s, 10, 64) + if err != nil { + i = 0 + } + return i +} diff --git a/nat/sort_test.go b/nat/sort_test.go index 5d490e321..ba24cdbcb 100644 --- a/nat/sort_test.go +++ b/nat/sort_test.go @@ -2,6 +2,7 @@ package nat import ( "fmt" + "reflect" "testing" ) @@ -39,3 +40,46 @@ func TestSortSamePortWithDifferentProto(t *testing.T) { t.Fail() } } + +func TestSortPortMap(t *testing.T) { + ports := []Port{ + Port("22/tcp"), + Port("22/udp"), + Port("8000/tcp"), + Port("6379/tcp"), + Port("9999/tcp"), + } + + portMap := PortMap{ + Port("22/tcp"): []PortBinding{ + {}, + }, + Port("8000/tcp"): []PortBinding{ + {}, + }, + Port("6379/tcp"): []PortBinding{ + {}, + {HostIp: "0.0.0.0", HostPort: "32749"}, + }, + Port("9999/tcp"): []PortBinding{ + {HostIp: "0.0.0.0", HostPort: "40000"}, + }, + } + + SortPortMap(ports, portMap) + if !reflect.DeepEqual(ports, []Port{ + Port("9999/tcp"), + Port("6379/tcp"), + Port("8000/tcp"), + Port("22/tcp"), + Port("22/udp"), + }) { + t.Errorf("failed to prioritize port with explicit mappings, got %v", ports) + } + if pm := portMap[Port("6379/tcp")]; !reflect.DeepEqual(pm, []PortBinding{ + {HostIp: "0.0.0.0", HostPort: "32749"}, + {}, + }) { + t.Errorf("failed to prioritize bindings with explicit mappings, got %v", pm) + } +} From 987e221607866ae391b632f9de1fd413e5a73150 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Thu, 30 Apr 2015 15:40:48 +0800 Subject: [PATCH 699/999] fix comments for test certain tests Signed-off-by: Qiang Huang --- docs/sources/project/test-and-docs.md | 4 ++-- hack/make.sh | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/sources/project/test-and-docs.md b/docs/sources/project/test-and-docs.md index 23b6b0914..66de450f5 100644 --- a/docs/sources/project/test-and-docs.md +++ b/docs/sources/project/test-and-docs.md @@ -164,11 +164,11 @@ You can use the `TESTFLAGS` environment variable to run a single test. The flag's value is passed as arguments to the `go test` command. For example, from your local host you can run the `TestBuild` test with this command: - $ TESTFLAGS='-check.f DockerSuite.TestBuild*' make test + $ TESTFLAGS='-check.f DockerSuite.TestBuild*' make test-integration-cli To run the same test inside your Docker development container, you do this: - root@5f8630b873fe:/go/src/github.com/docker/docker# TESTFLAGS='-check.f TestBuild*' hack/make.sh + root@5f8630b873fe:/go/src/github.com/docker/docker# TESTFLAGS='-check.f TestBuild*' hack/make.sh binary test-integration-cli ## If tests under Boot2Docker fail due to disk space errors diff --git a/hack/make.sh b/hack/make.sh index 31e08cd37..3d92b919a 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -168,7 +168,12 @@ fi # If $TESTFLAGS is set in the environment, it is passed as extra arguments to 'go test'. # You can use this to select certain tests to run, eg. # -# TESTFLAGS='-run ^TestBuild$' ./hack/make.sh test +# TESTFLAGS='-test.run ^TestBuild$' ./hack/make.sh test-unit +# +# For integration-cli test, we use [gocheck](https://labix.org/gocheck), if you want +# to run certain tests on your local host, you should run with command: +# +# TESTFLAGS='-check.f DockerSuite.TestBuild*' ./hack/make.sh binary test-integration-cli # go_test_dir() { dir=$1 From 424a544bb51af6013e218d67f87d27a85d46ca6d Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 30 Apr 2015 09:48:54 +0200 Subject: [PATCH 700/999] Fixing examples given for labels Fixes #12892 Signed-off-by: Vincent Demeester --- docs/sources/userguide/labels-custom-metadata.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/sources/userguide/labels-custom-metadata.md b/docs/sources/userguide/labels-custom-metadata.md index 792c2f505..79ac42ebf 100644 --- a/docs/sources/userguide/labels-custom-metadata.md +++ b/docs/sources/userguide/labels-custom-metadata.md @@ -129,10 +129,14 @@ You can view the labels via the `docker inspect` command: } ... - $ docker inspect -f "{{json .Labels }}" 4fa6e0f0c678 + # Inspect labels on container + $ docker inspect -f "{{json .Config.Labels }}" 4fa6e0f0c678 {"Vendor":"ACME Incorporated","com.example.is-beta":"","com.example.version":"0.0.1-beta","com.example.release-date":"2015-02-12"} + # Inspect labels on images + $ docker inspect -f "{{json .ContainerConfig.Labels }}" myimage + ## Query labels From 1d5f1bb0f5689be2f0262163ec05930e233f0ad0 Mon Sep 17 00:00:00 2001 From: Gaurav Date: Thu, 30 Apr 2015 18:08:03 +0530 Subject: [PATCH 701/999] Make use of iptablesPath variable which has the path of iptables, instead of using string iptables directly Signed-off-by: Gaurav --- pkg/iptables/iptables.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index 0cfcca750..9983ec61f 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -261,7 +261,7 @@ func Exists(table Table, chain string, rule ...string) bool { // parse "iptables -S" for the rule (this checks rules in a specific chain // in a specific table) ruleString := strings.Join(rule, " ") - existingRules, _ := exec.Command("iptables", "-t", string(table), "-S", chain).Output() + existingRules, _ := exec.Command(iptablesPath, "-t", string(table), "-S", chain).Output() // regex to replace ips in rule // because MASQUERADE rule will not be exactly what was passed From 869ecba652294e069874c83591d6f1b469d7cc32 Mon Sep 17 00:00:00 2001 From: Lars Kellogg-Stedman Date: Tue, 28 Apr 2015 21:23:27 -0400 Subject: [PATCH 702/999] journald log driver: use CONTAINER_ID field for container id This patch modifies the journald log driver to store the container ID in a field named CONTAINER_ID, rather than (ab)using the MESSAGE_ID field. Additionally, this adds the CONTAINER_ID_FULL field containing the complete container ID and CONTAINER_NAME, containing the container name. When using the journald log driver, this permits you to see log messages from a particular container like this: # journalctl CONTAINER_ID=a9238443e193 Example output from "journalctl -o verbose" includes the following: CONTAINER_ID=27aae7361e67 CONTAINER_ID_FULL=27aae7361e67e2b4d3864280acd2b80e78daf8ec73786d8b68f3afeeaabbd4c4 CONTAINER_NAME=web Closes: #12864 Signed-off-by: Lars Kellogg-Stedman --- daemon/container.go | 2 +- daemon/logger/journald/journald.go | 12 +++- docs/mkdocs.yml | 1 + docs/sources/reference.md | 1 + docs/sources/reference/logging/journald.md | 66 ++++++++++++++++++++++ docs/sources/reference/run.md | 2 +- 6 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 docs/sources/reference/logging/journald.md diff --git a/daemon/container.go b/daemon/container.go index 9bd8cc1eb..c77107608 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1447,7 +1447,7 @@ func (container *Container) startLogging() error { } l = dl case "journald": - dl, err := journald.New(container.ID[:12]) + dl, err := journald.New(container.ID, container.Name) if err != nil { return err } diff --git a/daemon/logger/journald/journald.go b/daemon/logger/journald/journald.go index 5eb141ac8..77bd2f24f 100644 --- a/daemon/logger/journald/journald.go +++ b/daemon/logger/journald/journald.go @@ -11,11 +11,19 @@ type Journald struct { Jmap map[string]string } -func New(id string) (logger.Logger, error) { +func New(id string, name string) (logger.Logger, error) { if !journal.Enabled() { return nil, fmt.Errorf("journald is not enabled on this host") } - jmap := map[string]string{"MESSAGE_ID": id} + // Strip a leading slash so that people can search for + // CONTAINER_NAME=foo rather than CONTAINER_NAME=/foo. + if name[0] == '/' { + name = name[1:] + } + jmap := map[string]string{ + "CONTAINER_ID": id[:12], + "CONTAINER_ID_FULL": id, + "CONTAINER_NAME": name} return &Journald{Jmap: jmap}, nil } diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index fb08e289e..7438af582 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -131,6 +131,7 @@ pages: - ['reference/builder.md', 'Reference', 'Dockerfile'] - ['faq.md', 'Reference', 'FAQ'] - ['reference/run.md', 'Reference', 'Run reference'] +- ['reference/logging/journald.md', '**HIDDEN**'] - ['compose/cli.md', 'Reference', 'Compose command line'] - ['compose/yml.md', 'Reference', 'Compose yml'] - ['compose/env.md', 'Reference', 'Compose ENV variables'] diff --git a/docs/sources/reference.md b/docs/sources/reference.md index 6c1ab462d..8cfe30467 100644 --- a/docs/sources/reference.md +++ b/docs/sources/reference.md @@ -3,6 +3,7 @@ ## Contents: - [Commands](commandline/) + - [Logging drivers](logging/) - [Dockerfile Reference](builder/) - [Docker Run Reference](run/) - [APIs](api/) diff --git a/docs/sources/reference/logging/journald.md b/docs/sources/reference/logging/journald.md new file mode 100644 index 000000000..9c025bfe6 --- /dev/null +++ b/docs/sources/reference/logging/journald.md @@ -0,0 +1,66 @@ +# Journald logging driver + +The `journald` logging driver sends container logs to the [systemd +journal](http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html). Log entries can be retrieved using the `journalctl` +command or through use of the journal API. + +In addition to the text of the log message itself, the `journald` log +driver stores the following metadata in the journal with each message: + +| Field | Description | +----------------------|-------------| +| `CONTAINER_ID` | The container ID truncated to 12 characters. | +| `CONTAINER_ID_FULL` | The full 64-character container ID. | +| `CONTAINER_NAME` | The container name at the time it was started. If you use `docker rename` to rename a container, the new name is not reflected in the journal entries. | + +## Usage + +You can configure the default logging driver by passing the +`--log-driver` option to the Docker daemon: + + docker --log-driver=journald + +You can set the logging driver for a specific container by using the +`--log-driver` option to `docker run`: + + docker run --log-driver=journald ... + +## Note regarding container names + +The value logged in the `CONTAINER_NAME` field is the container name +that was set at startup. If you use `docker rename` to rename a +container, the new name will not be reflected in the journal entries. +Journal entries will continue to use the original name. + +## Retrieving log messages with journalctl + +You can use the `journalctl` command to retrieve log messages. You +can apply filter expressions to limit the retrieved messages to a +specific container. For example, to retrieve all log messages from a +container referenced by name: + + # journalctl CONTAINER_NAME=webserver + +You can make use of additional filters to further limit the messages +retrieved. For example, to see just those messages generated since +the system last booted: + + # journalctl -b CONTAINER_NAME=webserver + +Or to retrieve log messages in JSON format with complete metadata: + + # journalctl -o json CONTAINER_NAME=webserver + +## Retrieving log messages with the journal API + +This example uses the `systemd` Python module to retrieve container +logs: + + import systemd.journal + + reader = systemd.journal.Reader() + reader.add_match('CONTAINER_NAME=web') + + for msg in reader: + print '{CONTAINER_ID_FULL}: {MESSAGE}'.format(**msg) + diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 990faaf6c..7b05f99d9 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -790,7 +790,7 @@ command is not available for this logging driver #### Logging driver: journald -Journald logging driver for Docker. Writes log messages to journald. `docker logs` command is not available for this logging driver +Journald logging driver for Docker. Writes log messages to journald; the container id will be stored in the journal's `CONTAINER_ID` field. `docker logs` command is not available for this logging driver. For detailed information on working with this logging driver, see [the journald logging driver](reference/logging/journald) reference documentation. ## Overriding Dockerfile image defaults From 5c86f311c88fafe87e08e58e2cd083fba127f95a Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 30 Apr 2015 20:49:28 +0200 Subject: [PATCH 703/999] Fix TestApiImagesDelete for --net none build Signed-off-by: Antonio Murdaca --- integration-cli/docker_api_images_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 15484715b..543182eed 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -100,6 +100,7 @@ func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) { } func (s *DockerSuite) TestApiImagesDelete(c *check.C) { + testRequires(c, Network) name := "test-api-images-delete" out, err := buildImage(name, "FROM hello-world\nENV FOO bar", false) if err != nil { From 86d1223a29907ffc6afba557b5138cfad7816bb4 Mon Sep 17 00:00:00 2001 From: jhowardmsft Date: Thu, 23 Apr 2015 15:55:36 -0700 Subject: [PATCH 704/999] Windows: mkdirall volume path aware Signed-off-by: jhowardmsft --- api/common.go | 5 +-- pkg/system/filesys.go | 11 ++++++ pkg/system/filesys_windows.go | 64 +++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 pkg/system/filesys.go create mode 100644 pkg/system/filesys_windows.go diff --git a/api/common.go b/api/common.go index 4a9523cd4..743eb6709 100644 --- a/api/common.go +++ b/api/common.go @@ -3,13 +3,13 @@ package api import ( "fmt" "mime" - "os" "path/filepath" "sort" "strings" "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/version" "github.com/docker/libtrust" ) @@ -107,7 +107,8 @@ func MatchesContentType(contentType, expectedType string) bool { // LoadOrCreateTrustKey attempts to load the libtrust key at the given path, // otherwise generates a new one func LoadOrCreateTrustKey(trustKeyPath string) (libtrust.PrivateKey, error) { - if err := os.MkdirAll(filepath.Dir(trustKeyPath), 0700); err != nil { + err := system.MkdirAll(filepath.Dir(trustKeyPath), 0700) + if err != nil { return nil, err } trustKey, err := libtrust.LoadKeyFile(trustKeyPath) diff --git a/pkg/system/filesys.go b/pkg/system/filesys.go new file mode 100644 index 000000000..e1f70e8da --- /dev/null +++ b/pkg/system/filesys.go @@ -0,0 +1,11 @@ +// +build !windows + +package system + +import ( + "os" +) + +func MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(path, perm) +} diff --git a/pkg/system/filesys_windows.go b/pkg/system/filesys_windows.go new file mode 100644 index 000000000..90b500608 --- /dev/null +++ b/pkg/system/filesys_windows.go @@ -0,0 +1,64 @@ +// +build windows + +package system + +import ( + "os" + "regexp" + "syscall" +) + +// MkdirAll implementation that is volume path aware for Windows. +func MkdirAll(path string, perm os.FileMode) error { + if re := regexp.MustCompile(`^\\\\\?\\Volume{[a-z0-9-]+}$`); re.MatchString(path) { + return nil + } + + // The rest of this method is copied from os.MkdirAll and should be kept + // as-is to ensure compatibility. + + // Fast path: if we can tell whether path is a directory or file, stop with success or error. + dir, err := os.Stat(path) + if err == nil { + if dir.IsDir() { + return nil + } + return &os.PathError{ + Op: "mkdir", + Path: path, + Err: syscall.ENOTDIR, + } + } + + // Slow path: make sure parent exists and then call Mkdir for path. + i := len(path) + for i > 0 && os.IsPathSeparator(path[i-1]) { // Skip trailing path separator. + i-- + } + + j := i + for j > 0 && !os.IsPathSeparator(path[j-1]) { // Scan backward over element. + j-- + } + + if j > 1 { + // Create parent + err = MkdirAll(path[0:j-1], perm) + if err != nil { + return err + } + } + + // Parent now exists; invoke Mkdir and use its result. + err = os.Mkdir(path, perm) + if err != nil { + // Handle arguments like "foo/." by + // double-checking that directory doesn't exist. + dir, err1 := os.Lstat(path) + if err1 == nil && dir.IsDir() { + return nil + } + return err + } + return nil +} From f9c7772b83e2382f1c3f0539180e9e6f5644fbbc Mon Sep 17 00:00:00 2001 From: John Howard Date: Fri, 24 Apr 2015 15:03:53 -0700 Subject: [PATCH 705/999] Windows: Commit() rwTar defer close Signed-off-by: John Howard --- daemon/commit.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/daemon/commit.go b/daemon/commit.go index 0c49eb2c9..28be6828b 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -32,7 +32,11 @@ func (daemon *Daemon) Commit(container *Container, repository, tag, comment, aut if err != nil { return nil, err } - defer rwTar.Close() + defer func() { + if rwTar != nil { + rwTar.Close() + } + }() // Create a new image from the container's base layers + a new layer from container changes var ( From 03eb0d065db006ac1df1bca62436a844674f4d2b Mon Sep 17 00:00:00 2001 From: John Howard Date: Fri, 24 Apr 2015 16:15:18 -0700 Subject: [PATCH 706/999] Windows: Move workdir check daemon-side Signed-off-by: John Howard --- daemon/create.go | 8 ++++++++ runconfig/parse.go | 9 +-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/daemon/create.go b/daemon/create.go index db6035507..d8addd3a9 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -2,6 +2,7 @@ package daemon import ( "fmt" + "path/filepath" "github.com/docker/docker/graph" "github.com/docker/docker/image" @@ -16,6 +17,13 @@ func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hos return "", warnings, err } + // The check for a valid workdir path is made on the server rather than in the + // client. This is because we don't know the type of path (Linux or Windows) + // to validate on the client. + if config.WorkingDir != "" && !filepath.IsAbs(config.WorkingDir) { + return "", warnings, fmt.Errorf("The working directory '%s' is invalid. It needs to be an absolute path.", config.WorkingDir) + } + container, buildWarnings, err := daemon.Create(config, hostConfig, name) if err != nil { if daemon.Graph().IsNotExist(err, config.Image) { diff --git a/runconfig/parse.go b/runconfig/parse.go index 47feac866..4ab406980 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -2,7 +2,6 @@ package runconfig import ( "fmt" - "path" "strconv" "strings" @@ -15,7 +14,6 @@ import ( ) var ( - ErrInvalidWorkingDirectory = fmt.Errorf("The working directory is invalid. It needs to be an absolute path.") ErrConflictContainerNetworkAndLinks = fmt.Errorf("Conflicting options: --net=container can't be used with links. This would result in undefined behavior.") ErrConflictContainerNetworkAndDns = fmt.Errorf("Conflicting options: --net=container can't be used with --dns. This configuration is invalid.") ErrConflictNetworkHostname = fmt.Errorf("Conflicting options: -h and the network mode (--net)") @@ -101,12 +99,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe return nil, nil, cmd, err } - // Validate input params - if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) { - return nil, nil, cmd, ErrInvalidWorkingDirectory - } - - // Validate the input mac address + // Validate input params starting with the input mac address if *flMacAddress != "" { if _, err := opts.ValidateMACAddress(*flMacAddress); err != nil { return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress) From b255c565ca66b778e87ccf1f8d46963feaee94a0 Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 27 Apr 2015 14:27:00 -0700 Subject: [PATCH 707/999] Windows: Start refactor execdriver/driver.go Signed-off-by: John Howard --- daemon/execdriver/driver.go | 153 +---------------------------- daemon/execdriver/driver_linux.go | 156 ++++++++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 148 deletions(-) create mode 100644 daemon/execdriver/driver_linux.go diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index ce196df20..df5901ed0 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -1,21 +1,14 @@ package execdriver import ( - "encoding/json" "errors" "io" - "io/ioutil" - "os" "os/exec" - "path/filepath" - "strconv" - "strings" "time" - "github.com/docker/docker/daemon/execdriver/native/template" + // TODO Windows: Factor out ulimit "github.com/docker/docker/pkg/ulimit" "github.com/docker/libcontainer" - "github.com/docker/libcontainer/cgroups/fs" "github.com/docker/libcontainer/configs" ) @@ -105,6 +98,7 @@ type NetworkInterface struct { IPv6Gateway string `json:"ipv6_gateway"` } +// TODO Windows: Factor out ulimit.Rlimit type Resources struct { Memory int64 `json:"memory"` MemorySwap int64 `json:"memory_swap"` @@ -143,6 +137,9 @@ type ProcessConfig struct { Console string `json:"-"` // dev/console path } +// TODO Windows: Factor out unused fields such as LxcConfig, AppArmorProfile, +// and CgroupParent. +// // Process wrapps an os/exec.Cmd to add more metadata type Command struct { ID string `json:"id"` @@ -168,143 +165,3 @@ type Command struct { AppArmorProfile string `json:"apparmor_profile"` CgroupParent string `json:"cgroup_parent"` // The parent cgroup for this command. } - -func InitContainer(c *Command) *configs.Config { - container := template.New() - - container.Hostname = getEnv("HOSTNAME", c.ProcessConfig.Env) - container.Cgroups.Name = c.ID - container.Cgroups.AllowedDevices = c.AllowedDevices - container.Devices = c.AutoCreatedDevices - container.Rootfs = c.Rootfs - container.Readonlyfs = c.ReadonlyRootfs - - // check to see if we are running in ramdisk to disable pivot root - container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" - - // Default parent cgroup is "docker". Override if required. - if c.CgroupParent != "" { - container.Cgroups.Parent = c.CgroupParent - } - return container -} - -func getEnv(key string, env []string) string { - for _, pair := range env { - parts := strings.Split(pair, "=") - if parts[0] == key { - return parts[1] - } - } - return "" -} - -func SetupCgroups(container *configs.Config, c *Command) error { - if c.Resources != nil { - container.Cgroups.CpuShares = c.Resources.CpuShares - container.Cgroups.Memory = c.Resources.Memory - container.Cgroups.MemoryReservation = c.Resources.Memory - container.Cgroups.MemorySwap = c.Resources.MemorySwap - container.Cgroups.CpusetCpus = c.Resources.CpusetCpus - container.Cgroups.CpusetMems = c.Resources.CpusetMems - container.Cgroups.CpuQuota = c.Resources.CpuQuota - } - - return nil -} - -// Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo. -func getNetworkInterfaceStats(interfaceName string) (*libcontainer.NetworkInterface, error) { - out := &libcontainer.NetworkInterface{Name: interfaceName} - // This can happen if the network runtime information is missing - possible if the - // container was created by an old version of libcontainer. - if interfaceName == "" { - return out, nil - } - type netStatsPair struct { - // Where to write the output. - Out *uint64 - // The network stats file to read. - File string - } - // Ingress for host veth is from the container. Hence tx_bytes stat on the host veth is actually number of bytes received by the container. - netStats := []netStatsPair{ - {Out: &out.RxBytes, File: "tx_bytes"}, - {Out: &out.RxPackets, File: "tx_packets"}, - {Out: &out.RxErrors, File: "tx_errors"}, - {Out: &out.RxDropped, File: "tx_dropped"}, - - {Out: &out.TxBytes, File: "rx_bytes"}, - {Out: &out.TxPackets, File: "rx_packets"}, - {Out: &out.TxErrors, File: "rx_errors"}, - {Out: &out.TxDropped, File: "rx_dropped"}, - } - for _, netStat := range netStats { - data, err := readSysfsNetworkStats(interfaceName, netStat.File) - if err != nil { - return nil, err - } - *(netStat.Out) = data - } - return out, nil -} - -// Reads the specified statistics available under /sys/class/net//statistics -func readSysfsNetworkStats(ethInterface, statsFile string) (uint64, error) { - data, err := ioutil.ReadFile(filepath.Join("/sys/class/net", ethInterface, "statistics", statsFile)) - if err != nil { - return 0, err - } - return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64) -} - -func Stats(containerDir string, containerMemoryLimit int64, machineMemory int64) (*ResourceStats, error) { - f, err := os.Open(filepath.Join(containerDir, "state.json")) - if err != nil { - return nil, err - } - defer f.Close() - - type network struct { - Type string - HostInterfaceName string - } - - state := struct { - CgroupPaths map[string]string `json:"cgroup_paths"` - Networks []network - }{} - - if err := json.NewDecoder(f).Decode(&state); err != nil { - return nil, err - } - now := time.Now() - - mgr := fs.Manager{Paths: state.CgroupPaths} - cstats, err := mgr.GetStats() - if err != nil { - return nil, err - } - stats := &libcontainer.Stats{CgroupStats: cstats} - // if the container does not have any memory limit specified set the - // limit to the machines memory - memoryLimit := containerMemoryLimit - if memoryLimit == 0 { - memoryLimit = machineMemory - } - for _, iface := range state.Networks { - switch iface.Type { - case "veth": - istats, err := getNetworkInterfaceStats(iface.HostInterfaceName) - if err != nil { - return nil, err - } - stats.Interfaces = append(stats.Interfaces, istats) - } - } - return &ResourceStats{ - Stats: stats, - Read: now, - MemoryLimit: memoryLimit, - }, nil -} diff --git a/daemon/execdriver/driver_linux.go b/daemon/execdriver/driver_linux.go new file mode 100644 index 000000000..1766d64b6 --- /dev/null +++ b/daemon/execdriver/driver_linux.go @@ -0,0 +1,156 @@ +package execdriver + +import ( + "encoding/json" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/docker/docker/daemon/execdriver/native/template" + "github.com/docker/libcontainer" + "github.com/docker/libcontainer/cgroups/fs" + "github.com/docker/libcontainer/configs" +) + +func InitContainer(c *Command) *configs.Config { + container := template.New() + + container.Hostname = getEnv("HOSTNAME", c.ProcessConfig.Env) + container.Cgroups.Name = c.ID + container.Cgroups.AllowedDevices = c.AllowedDevices + container.Devices = c.AutoCreatedDevices + container.Rootfs = c.Rootfs + container.Readonlyfs = c.ReadonlyRootfs + + // check to see if we are running in ramdisk to disable pivot root + container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != "" + + // Default parent cgroup is "docker". Override if required. + if c.CgroupParent != "" { + container.Cgroups.Parent = c.CgroupParent + } + return container +} + +func getEnv(key string, env []string) string { + for _, pair := range env { + parts := strings.Split(pair, "=") + if parts[0] == key { + return parts[1] + } + } + return "" +} + +func SetupCgroups(container *configs.Config, c *Command) error { + if c.Resources != nil { + container.Cgroups.CpuShares = c.Resources.CpuShares + container.Cgroups.Memory = c.Resources.Memory + container.Cgroups.MemoryReservation = c.Resources.Memory + container.Cgroups.MemorySwap = c.Resources.MemorySwap + container.Cgroups.CpusetCpus = c.Resources.CpusetCpus + container.Cgroups.CpusetMems = c.Resources.CpusetMems + container.Cgroups.CpuQuota = c.Resources.CpuQuota + } + + return nil +} + +// Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo. +func getNetworkInterfaceStats(interfaceName string) (*libcontainer.NetworkInterface, error) { + out := &libcontainer.NetworkInterface{Name: interfaceName} + // This can happen if the network runtime information is missing - possible if the + // container was created by an old version of libcontainer. + if interfaceName == "" { + return out, nil + } + type netStatsPair struct { + // Where to write the output. + Out *uint64 + // The network stats file to read. + File string + } + // Ingress for host veth is from the container. Hence tx_bytes stat on the host veth is actually number of bytes received by the container. + netStats := []netStatsPair{ + {Out: &out.RxBytes, File: "tx_bytes"}, + {Out: &out.RxPackets, File: "tx_packets"}, + {Out: &out.RxErrors, File: "tx_errors"}, + {Out: &out.RxDropped, File: "tx_dropped"}, + + {Out: &out.TxBytes, File: "rx_bytes"}, + {Out: &out.TxPackets, File: "rx_packets"}, + {Out: &out.TxErrors, File: "rx_errors"}, + {Out: &out.TxDropped, File: "rx_dropped"}, + } + for _, netStat := range netStats { + data, err := readSysfsNetworkStats(interfaceName, netStat.File) + if err != nil { + return nil, err + } + *(netStat.Out) = data + } + return out, nil +} + +// Reads the specified statistics available under /sys/class/net//statistics +func readSysfsNetworkStats(ethInterface, statsFile string) (uint64, error) { + data, err := ioutil.ReadFile(filepath.Join("/sys/class/net", ethInterface, "statistics", statsFile)) + if err != nil { + return 0, err + } + return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64) +} + +func Stats(containerDir string, containerMemoryLimit int64, machineMemory int64) (*ResourceStats, error) { + f, err := os.Open(filepath.Join(containerDir, "state.json")) + if err != nil { + return nil, err + } + defer f.Close() + + type network struct { + Type string + HostInterfaceName string + } + + state := struct { + CgroupPaths map[string]string `json:"cgroup_paths"` + Networks []network + }{} + + if err := json.NewDecoder(f).Decode(&state); err != nil { + return nil, err + } + now := time.Now() + + mgr := fs.Manager{Paths: state.CgroupPaths} + cstats, err := mgr.GetStats() + if err != nil { + return nil, err + } + stats := &libcontainer.Stats{CgroupStats: cstats} + // if the container does not have any memory limit specified set the + // limit to the machines memory + memoryLimit := containerMemoryLimit + if memoryLimit == 0 { + memoryLimit = machineMemory + } + for _, iface := range state.Networks { + switch iface.Type { + case "veth": + istats, err := getNetworkInterfaceStats(iface.HostInterfaceName) + if err != nil { + return nil, err + } + stats.Interfaces = append(stats.Interfaces, istats) + } + } + return &ResourceStats{ + Stats: stats, + Read: now, + MemoryLimit: memoryLimit, + }, nil +} From 71bfb9367880632fd0dbda5e37e926448473ef46 Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 27 Apr 2015 15:20:44 -0700 Subject: [PATCH 708/999] Windows: Fork execdrivers.go for Windows execdriver Signed-off-by: John Howard --- .../{execdrivers.go => execdrivers_linux.go} | 2 ++ .../execdrivers/execdrivers_windows.go | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) rename daemon/execdriver/execdrivers/{execdrivers.go => execdrivers_linux.go} (97%) create mode 100644 daemon/execdriver/execdrivers/execdrivers_windows.go diff --git a/daemon/execdriver/execdrivers/execdrivers.go b/daemon/execdriver/execdrivers/execdrivers_linux.go similarity index 97% rename from daemon/execdriver/execdrivers/execdrivers.go rename to daemon/execdriver/execdrivers/execdrivers_linux.go index dde0be1f0..89dedc762 100644 --- a/daemon/execdriver/execdrivers/execdrivers.go +++ b/daemon/execdriver/execdrivers/execdrivers_linux.go @@ -1,3 +1,5 @@ +// +build linux + package execdrivers import ( diff --git a/daemon/execdriver/execdrivers/execdrivers_windows.go b/daemon/execdriver/execdrivers/execdrivers_windows.go new file mode 100644 index 000000000..563961fce --- /dev/null +++ b/daemon/execdriver/execdrivers/execdrivers_windows.go @@ -0,0 +1,19 @@ +// +build windows + +package execdrivers + +import ( + "fmt" + + "github.com/docker/docker/daemon/execdriver" + "github.com/docker/docker/daemon/execdriver/windows" + "github.com/docker/docker/pkg/sysinfo" +) + +func NewDriver(name, root, libPath, initPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) { + switch name { + case "windows": + return windows.NewDriver(root, initPath) + } + return nil, fmt.Errorf("unknown exec driver %s", name) +} From 10e2dbf375b1aebe33bce0646a3a95d34c48d4f8 Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 27 Apr 2015 15:53:21 -0700 Subject: [PATCH 709/999] Windows: Factor out LXC Signed-off-by: John Howard --- daemon/execdriver/lxc/driver.go | 2 ++ daemon/execdriver/lxc/info.go | 2 ++ daemon/execdriver/lxc/info_test.go | 2 ++ daemon/execdriver/lxc/init.go | 2 ++ daemon/execdriver/lxc/lxc_init_linux.go | 2 ++ daemon/execdriver/lxc/lxc_init_unsupported.go | 2 +- daemon/execdriver/lxc/lxc_template.go | 2 ++ 7 files changed, 13 insertions(+), 1 deletion(-) diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 15f57bfe0..32e5b43eb 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -1,3 +1,5 @@ +// +build linux + package lxc import ( diff --git a/daemon/execdriver/lxc/info.go b/daemon/execdriver/lxc/info.go index 27b4c5860..279211f32 100644 --- a/daemon/execdriver/lxc/info.go +++ b/daemon/execdriver/lxc/info.go @@ -1,3 +1,5 @@ +// +build linux + package lxc import ( diff --git a/daemon/execdriver/lxc/info_test.go b/daemon/execdriver/lxc/info_test.go index edafc0251..996d56b2a 100644 --- a/daemon/execdriver/lxc/info_test.go +++ b/daemon/execdriver/lxc/info_test.go @@ -1,3 +1,5 @@ +// +build linux + package lxc import ( diff --git a/daemon/execdriver/lxc/init.go b/daemon/execdriver/lxc/init.go index eca1c02e2..a47ece97f 100644 --- a/daemon/execdriver/lxc/init.go +++ b/daemon/execdriver/lxc/init.go @@ -1,3 +1,5 @@ +// +build linux + package lxc import ( diff --git a/daemon/execdriver/lxc/lxc_init_linux.go b/daemon/execdriver/lxc/lxc_init_linux.go index e7bc2b5f3..fb89ac6a0 100644 --- a/daemon/execdriver/lxc/lxc_init_linux.go +++ b/daemon/execdriver/lxc/lxc_init_linux.go @@ -1,3 +1,5 @@ +// +build linux + package lxc import ( diff --git a/daemon/execdriver/lxc/lxc_init_unsupported.go b/daemon/execdriver/lxc/lxc_init_unsupported.go index 97bc8a984..3b7be139b 100644 --- a/daemon/execdriver/lxc/lxc_init_unsupported.go +++ b/daemon/execdriver/lxc/lxc_init_unsupported.go @@ -3,5 +3,5 @@ package lxc func finalizeNamespace(args *InitArgs) error { - panic("Not supported on darwin") + panic("Not supported on this platform") } diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index b3be7f8c5..6b418b26b 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -1,3 +1,5 @@ +// +build linux + package lxc import ( From 68ee5bdf96f1653d73e29329506949a765398a40 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Thu, 30 Apr 2015 14:43:14 -0700 Subject: [PATCH 710/999] Don't wrap 'cp' help too soon. Minor thing but docker cp --help was: Copy files/folders from a PATH on the container to a HOSTDIR on the host running the command. Use '-' to write the data as a tar file to STDOUT. This changes it to: Copy files/folders from a PATH on the container to a HOSTDIR on the host running the command. Use '-' to write the data as a tar file to STDOUT. The \n made the output look funky. Signed-off-by: Doug Davis --- api/client/cp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/client/cp.go b/api/client/cp.go index 392e36292..d195601ba 100644 --- a/api/client/cp.go +++ b/api/client/cp.go @@ -16,7 +16,7 @@ import ( // // Usage: docker cp CONTAINER:PATH HOSTDIR func (cli *DockerCli) CmdCp(args ...string) error { - cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTDIR|-", "Copy files/folders from a PATH on the container to a HOSTDIR on the host\nrunning the command. Use '-' to write the data\nas a tar file to STDOUT.", true) + cmd := cli.Subcmd("cp", "CONTAINER:PATH HOSTDIR|-", "Copy files/folders from a PATH on the container to a HOSTDIR on the host\nrunning the command. Use '-' to write the data as a tar file to STDOUT.", true) cmd.Require(flag.Exact, 2) cmd.ParseFlags(args, true) From 51977a230715be285f8b8076f43bd1e5803d051c Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Thu, 30 Apr 2015 15:21:05 -0700 Subject: [PATCH 711/999] Adding support for GITHUB IGNORES to the engine Dockerfile Signed-off-by: Mary Anthony --- docs/Dockerfile | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index e30d4bbd5..a53048bb7 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -9,7 +9,7 @@ MAINTAINER Sven Dowideit (@SvenDowideit) ENV COMPOSE_BRANCH release ENV SWARM_BRANCH v0.2.0 ENV MACHINE_BRANCH master -ENV DISTRIB_BRANCH release/2.0 +ENV DISTRIB_BRANCH docs # TODO: need the full repo source to get the git version info @@ -61,7 +61,14 @@ ADD https://raw.githubusercontent.com/docker/distribution/${DISTRIB_BRANCH}/docs RUN sed -i.old '1s;^;no_version_dropdown: true;' \ /docs/sources/registry/*.md \ /docs/sources/registry/spec/*.md \ - /docs/sources/registry/spec/auth/*.md + /docs/sources/registry/spec/auth/*.md \ + /docs/sources/registry/storage-drivers/*.md + +RUN sed -i.old -e '/^/g'\ + /docs/sources/registry/*.md \ + /docs/sources/registry/spec/*.md \ + /docs/sources/registry/spec/auth/*.md \ + /docs/sources/registry/storage-drivers/*.md ####################### # Docker Swarm From cbf9a64cb5f69ce07598646ac26be247c7967cbb Mon Sep 17 00:00:00 2001 From: jhowardmsft Date: Thu, 23 Apr 2015 13:45:34 -0700 Subject: [PATCH 712/999] Windows: Change default listener to HTTP Signed-off-by: jhowardmsft --- docker/docker.go | 10 ++++++++-- opts/opts.go | 10 +++++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/docker/docker.go b/docker/docker.go index 1096b840f..698991e05 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "os" + "runtime" "strings" "github.com/Sirupsen/logrus" @@ -62,8 +63,13 @@ func main() { if len(flHosts) == 0 { defaultHost := os.Getenv("DOCKER_HOST") if defaultHost == "" || *flDaemon { - // If we do not have a host, default to unix socket - defaultHost = fmt.Sprintf("unix://%s", opts.DefaultUnixSocket) + if runtime.GOOS != "windows" { + // If we do not have a host, default to unix socket + defaultHost = fmt.Sprintf("unix://%s", opts.DefaultUnixSocket) + } else { + // If we do not have a host, default to TCP socket on Windows + defaultHost = fmt.Sprintf("tcp://%s:%d", opts.DefaultHTTPHost, opts.DefaultHTTPPort) + } } defaultHost, err := opts.ValidateHost(defaultHost) if err != nil { diff --git a/opts/opts.go b/opts/opts.go index d2c32f13c..1db454736 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -14,9 +14,13 @@ import ( ) var ( - alphaRegexp = regexp.MustCompile(`[a-zA-Z]`) - domainRegexp = 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*$`) - DefaultHTTPHost = "127.0.0.1" // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080 + alphaRegexp = regexp.MustCompile(`[a-zA-Z]`) + domainRegexp = 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*$`) + DefaultHTTPHost = "127.0.0.1" // Default HTTP Host used if only port is provided to -H flag e.g. docker -d -H tcp://:8080 + // TODO Windows. DefaultHTTPPort is only used on Windows if a -H parameter + // is not supplied. A better longer term solution would be to use a named + // pipe as the default on the Windows daemon. + DefaultHTTPPort = 2375 // Default HTTP Port DefaultUnixSocket = "/var/run/docker.sock" // Docker daemon by default always listens on the default unix socket ) From 04adaaf1ee1688ff48cb8f541dcb80e965f45080 Mon Sep 17 00:00:00 2001 From: Vivek Goyal Date: Fri, 1 May 2015 09:16:31 -0400 Subject: [PATCH 713/999] devmapper: Disable mount option "discard" by default Right now devicemapper mounts thin device using online discards by default and passes mount option "discard". Generally people discourage usage of online discards as they can be a drain on performance. Instead it is recommended to use fstrim once in a while to reclaim the space. In case of containers, we recommend to keep data volumes separate. So there might not be lot of rm, unlink operations going on and there might not be lot of space being freed by containers. So it might not matter much if we don't reclaim that free space in pool. User can still pass mount option explicitly using dm.mountopt=discard to enable discards if they would like to. So this is more like setting the containers by default for better performance instead of better space efficiency in pool. And user can change the behavior if they don't like default behavior. Reported-by: Mike Snitzer Signed-off-by: Vivek Goyal --- daemon/graphdriver/devmapper/deviceset.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/daemon/graphdriver/devmapper/deviceset.go b/daemon/graphdriver/devmapper/deviceset.go index b5d67fa11..c2a9a44fe 100644 --- a/daemon/graphdriver/devmapper/deviceset.go +++ b/daemon/graphdriver/devmapper/deviceset.go @@ -1366,11 +1366,7 @@ func (devices *DeviceSet) MountDevice(hash, path, mountLabel string) error { options = joinMountOptions(options, devices.mountOptions) options = joinMountOptions(options, label.FormatMountLabel("", mountLabel)) - err = syscall.Mount(info.DevName(), path, fstype, flags, joinMountOptions("discard", options)) - if err != nil && err == syscall.EINVAL { - err = syscall.Mount(info.DevName(), path, fstype, flags, options) - } - if err != nil { + if err := syscall.Mount(info.DevName(), path, fstype, flags, options); err != nil { return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err) } From 79f13d1497b073113f713e903e934dafcf78c42f Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Fri, 1 May 2015 22:26:40 +0800 Subject: [PATCH 714/999] fix docker rm name issue Addresses https://github.com/docker/docker/issues/12308 Signed-off-by: Qiang Huang --- api/client/rm.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/api/client/rm.go b/api/client/rm.go index 1ecc0d657..d99f32e2f 100644 --- a/api/client/rm.go +++ b/api/client/rm.go @@ -3,6 +3,7 @@ package client import ( "fmt" "net/url" + "strings" flag "github.com/docker/docker/pkg/mflag" ) @@ -36,6 +37,7 @@ func (cli *DockerCli) CmdRm(args ...string) error { if name == "" { return fmt.Errorf("Container name cannot be empty") } + name = strings.Trim(name, "/") _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, nil)) if err != nil { From 5eee4058feaa7f06348941d267f4d76178ab3de4 Mon Sep 17 00:00:00 2001 From: Megan Kostick Date: Wed, 29 Apr 2015 12:51:57 -0700 Subject: [PATCH 715/999] Docs adding uninstall instructions Signed-off-by: Megan Kostick --- docs/sources/installation/SUSE.md | 16 +- docs/sources/installation/archlinux.md | 22 ++- docs/sources/installation/centos.md | 37 ++++- docs/sources/installation/cruxlinux.md | 24 ++- docs/sources/installation/debian.md | 36 +++++ docs/sources/installation/fedora.md | 42 ++++- docs/sources/installation/frugalware.md | 20 ++- docs/sources/installation/gentoolinux.md | 18 +++ docs/sources/installation/mac.md | 196 +++++++++++++---------- docs/sources/installation/oracle.md | 36 +++-- docs/sources/installation/rhel.md | 37 ++++- docs/sources/installation/ubuntulinux.md | 93 ++++++----- docs/sources/installation/windows.md | 10 +- 13 files changed, 431 insertions(+), 156 deletions(-) diff --git a/docs/sources/installation/SUSE.md b/docs/sources/installation/SUSE.md index 756ed6b5c..106d4cbe3 100644 --- a/docs/sources/installation/SUSE.md +++ b/docs/sources/installation/SUSE.md @@ -28,7 +28,7 @@ Docker is available in **SUSE Linux Enterprise 12 and later**. Please note that due to its current limitations Docker is able to run only on **64 bit** architecture. -# Installation +## Installation Install the Docker package. @@ -76,6 +76,20 @@ If you need to add an HTTP Proxy, set a different directory or partition for the Docker runtime files, or make other customizations, read our systemd article to learn how to [customize your systemd Docker daemon options](/articles/systemd/). +## Uninstallation + +To uninstall the Docker package: + + $ sudo zypper rm docker + +The above command will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. + ## What's next Continue with the [User Guide](/userguide/). diff --git a/docs/sources/installation/archlinux.md b/docs/sources/installation/archlinux.md index 99849c7aa..570e36c48 100644 --- a/docs/sources/installation/archlinux.md +++ b/docs/sources/installation/archlinux.md @@ -30,13 +30,13 @@ in the packages. The core dependencies are: For the normal package a simple - pacman -S docker + $ sudo pacman -S docker is all that is needed. For the AUR package execute: - yaourt -S docker-git + $ sudo yaourt -S docker-git The instructions here assume **yaourt** is installed. See [Arch User Repository](https://wiki.archlinux.org/index.php/Arch_User_Repository#Installing_packages) @@ -59,3 +59,21 @@ To start on system boot: If you need to add an HTTP Proxy, set a different directory or partition for the Docker runtime files, or make other customizations, read our systemd article to learn how to [customize your systemd Docker daemon options](/articles/systemd/). + +## Uninstallation + +To uninstall the Docker package: + + $ sudo pacman -R docker + +To uninstall the Docker package and dependencies that are no longer needed: + + $ sudo pacman -Rns docker + +The above commands will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. diff --git a/docs/sources/installation/centos.md b/docs/sources/installation/centos.md index 7868f11b0..efebad503 100644 --- a/docs/sources/installation/centos.md +++ b/docs/sources/installation/centos.md @@ -25,7 +25,10 @@ To run Docker on [CentOS-6.5](http://www.centos.org) or later, you will need kernel version 2.6.32-431 or higher as this has specific kernel fixes to allow Docker to run. -## Installing Docker - CentOS-7 +## CentOS-7 + +### Installation + Docker is included by default in the CentOS-Extras repository. To install run the following command: @@ -33,7 +36,23 @@ run the following command: Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon). -## Installing Docker - CentOS-6.5 +### Uninstallation + +To uninstall the Docker package: + + $ sudo yum -y remove docker + +The above command will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. + +## CentOS-6.5 + +### Installation For CentOS-6.5, the Docker package is part of [Extra Packages for Enterprise Linux (EPEL)](https://fedoraproject.org/wiki/EPEL) repository, @@ -57,6 +76,20 @@ Next, let's install the `docker-io` package which will install Docker on our hos Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon). +### Uninstallation + +To uninstall the Docker package: + + $ sudo yum -y remove docker-io + +The above command will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. + ## Manual installation of latest Docker release While using a package is the recommended way of installing Docker, diff --git a/docs/sources/installation/cruxlinux.md b/docs/sources/installation/cruxlinux.md index d474aa52f..e03715009 100644 --- a/docs/sources/installation/cruxlinux.md +++ b/docs/sources/installation/cruxlinux.md @@ -15,9 +15,9 @@ The `docker` port will build and install the latest tagged version of Docker. ## Installation -Assuming you have contrib enabled, update your ports tree and install docker (*as root*): +Assuming you have contrib enabled, update your ports tree and install docker: - # prt-get depinst docker + $ sudo prt-get depinst docker ## Kernel requirements @@ -27,7 +27,7 @@ the necessary modules enabled for the Docker Daemon to function correctly. Please read the `README`: - $ prt-get readme docker + $ sudo prt-get readme docker The `docker` port installs the `contrib/check-config.sh` script provided by the Docker contributors for checking your kernel @@ -39,9 +39,9 @@ To check your Kernel configuration run: ## Starting Docker -There is a rc script created for Docker. To start the Docker service (*as root*): +There is a rc script created for Docker. To start the Docker service: - # /etc/rc.d/docker start + $ sudo /etc/rc.d/docker start To start on system boot: @@ -60,6 +60,20 @@ or use it as part of your `FROM` line in your `Dockerfile(s)`. There are also user contributed [CRUX based image(s)](https://registry.hub.docker.com/repos/crux/) on the Docker Hub. +## Uninstallation + +To uninstall the Docker package: + + $ sudo prt-get remove docker + +The above command will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. + ## Issues If you have any issues please file a bug with the diff --git a/docs/sources/installation/debian.md b/docs/sources/installation/debian.md index da9e5f59b..883f920cd 100644 --- a/docs/sources/installation/debian.md +++ b/docs/sources/installation/debian.md @@ -37,6 +37,24 @@ container runs, it prints an informational message. Then, it exits. > If you want to enable memory and swap accounting see > [this](/installation/ubuntulinux/#memory-and-swap-accounting). +### Uninstallation + +To uninstall the Docker package: + + $ sudo apt-get purge docker-io + +To uninstall the Docker package and dependencies that are no longer needed: + + $ sudo apt-get autoremove --purge docker-io + +The above commands will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. + ## Debian Wheezy/Stable 7.x (64-bit) Docker requires Kernel 3.8+, while Wheezy ships with Kernel 3.2 (for more details @@ -74,6 +92,24 @@ which is officially supported by Docker. > > $ wget -qO- https://get.docker.com/gpg | sudo apt-key add - +### Uninstallation + +To uninstall the Docker package: + + $ sudo apt-get purge lxc-docker + +To uninstall the Docker package and dependencies that are no longer needed: + + $ sudo apt-get autoremove --purge lxc-docker + +The above commands will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. + ## Giving non-root access The `docker` daemon always runs as the `root` user and the `docker` diff --git a/docs/sources/installation/fedora.md b/docs/sources/installation/fedora.md index ed4e8372a..b3f23e451 100644 --- a/docs/sources/installation/fedora.md +++ b/docs/sources/installation/fedora.md @@ -13,19 +13,37 @@ Currently the Fedora project will only support Docker when running on kernels shipped by the distribution. There are kernel changes which will cause issues if one decides to step outside that box and run non-distribution kernel packages. -## Fedora 21 and later installation +## Fedora 21 and later -Install the `docker` package which will install Docker on our host. +### Installation + +Install the Docker package which will install Docker on our host. $ sudo yum -y install docker -To update the `docker` package: +To update the Docker package: $ sudo yum -y update docker Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon). -## Fedora 20 installation +### Uninstallation + +To uninstall the Docker package: + + $ sudo yum -y remove docker + +The above command will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. + +## Fedora 20 + +### Installation For `Fedora 20`, there is a package name conflict with a system tray application and its executable, so the Docker RPM package was called `docker-io`. @@ -36,12 +54,26 @@ package first. $ sudo yum -y remove docker $ sudo yum -y install docker-io -To update the `docker` package: +To update the Docker package: $ sudo yum -y update docker-io Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon). +### Uninstallation + +To uninstall the Docker package: + + $ sudo yum -y remove docker-io + +The above command will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. + ## Starting the Docker daemon Now that it's installed, let's start the Docker daemon. diff --git a/docs/sources/installation/frugalware.md b/docs/sources/installation/frugalware.md index 6b4db23b2..c70028034 100644 --- a/docs/sources/installation/frugalware.md +++ b/docs/sources/installation/frugalware.md @@ -28,7 +28,7 @@ in the packages. The core dependencies are: A simple - pacman -S lxc-docker + $ sudo pacman -S lxc-docker is all that is needed. @@ -48,3 +48,21 @@ To start on system boot: If you need to add an HTTP Proxy, set a different directory or partition for the Docker runtime files, or make other customizations, read our systemd article to learn how to [customize your systemd Docker daemon options](/articles/systemd/). + +## Uninstallation + +To uninstall the Docker package: + + $ sudo pacman -R lxc-docker + +To uninstall the Docker package and dependencies that are no longer needed: + + $ sudo pacman -Rns lxc-docker + +The above commands will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. diff --git a/docs/sources/installation/gentoolinux.md b/docs/sources/installation/gentoolinux.md index 716eab9d8..865e8eb00 100644 --- a/docs/sources/installation/gentoolinux.md +++ b/docs/sources/installation/gentoolinux.md @@ -95,3 +95,21 @@ To start on system boot: If you need to add an HTTP Proxy, set a different directory or partition for the Docker runtime files, or make other customizations, read our systemd article to learn how to [customize your systemd Docker daemon options](/articles/systemd/). + +## Uninstallation + +To uninstall the Docker package: + + $ sudo emerge -cav app-emulation/docker + +To uninstall the Docker package and dependencies that are no longer needed: + + $ sudo emerge -C app-emulation/docker + +The above commands will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. diff --git a/docs/sources/installation/mac.md b/docs/sources/installation/mac.md index 4b157c168..0b4274c2d 100644 --- a/docs/sources/installation/mac.md +++ b/docs/sources/installation/mac.md @@ -2,7 +2,7 @@ page_title: Installation on Mac OS X page_description: Instructions for installing Docker on OS X using boot2docker. page_keywords: Docker, Docker documentation, requirements, boot2docker, VirtualBox, SSH, Linux, OSX, OS X, Mac -# Install Docker on Mac OS X +# Mac OS X You can install Docker using Boot2Docker to run `docker` commands at your command-line. Choose this installation if you are familiar with the command-line or plan to @@ -55,17 +55,17 @@ When you start the `boot2docker` process, the VM is assigned an IP address. Unde practice, work through the exercises on this page. -### Install Boot2Docker +### Installation 1. Go to the [boot2docker/osx-installer ]( -https://github.com/boot2docker/osx-installer/releases/latest) release page. + https://github.com/boot2docker/osx-installer/releases/latest) release page. 4. Download Boot2Docker by clicking `Boot2Docker-x.x.x.pkg` in the "Downloads" -section. + section. 3. Install Boot2Docker by double-clicking the package. - The installer places Boot2Docker in your "Applications" folder. + The installer places Boot2Docker in your "Applications" folder. The installation places the `docker` and `boot2docker` binaries in your `/usr/local/bin` directory. @@ -96,30 +96,32 @@ application: Once the launch completes, you can run `docker` commands. A good way to verify your setup succeeded is to run the `hello-world` container. - $ docker run hello-world - Unable to find image 'hello-world:latest' locally - 511136ea3c5a: Pull complete - 31cbccb51277: Pull complete - e45a5af57b00: Pull complete - hello-world:latest: The image you are pulling has been verified. Important: image verification is a tech preview feature and should not be relied on to provide security. - Status: Downloaded newer image for hello-world:latest - Hello from Docker. - This message shows that your installation appears to be working correctly. + $ docker run hello-world + Unable to find image 'hello-world:latest' locally + 511136ea3c5a: Pull complete + 31cbccb51277: Pull complete + e45a5af57b00: Pull complete + hello-world:latest: The image you are pulling has been verified. + Important: image verification is a tech preview feature and should not be + relied on to provide security. + Status: Downloaded newer image for hello-world:latest + Hello from Docker. + This message shows that your installation appears to be working correctly. - To generate this message, Docker took the following steps: - 1. The Docker client contacted the Docker daemon. - 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. - (Assuming it was not already locally available.) - 3. The Docker daemon created a new container from that image which runs the - executable that produces the output you are currently reading. - 4. The Docker daemon streamed that output to the Docker client, which sent it - to your terminal. + To generate this message, Docker took the following steps: + 1. The Docker client contacted the Docker daemon. + 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. + (Assuming it was not already locally available.) + 3. The Docker daemon created a new container from that image which runs the + executable that produces the output you are currently reading. + 4. The Docker daemon streamed that output to the Docker client, which sent it + to your terminal. - To try something more ambitious, you can run an Ubuntu container with: - $ docker run -it ubuntu bash + To try something more ambitious, you can run an Ubuntu container with: + $ docker run -it ubuntu bash - For more examples and ideas, visit: - http://docs.docker.com/userguide/ + For more examples and ideas, visit: + http://docs.docker.com/userguide/ A more typical way to start and stop `boot2docker` is using the command line. @@ -130,36 +132,36 @@ Initialize and run `boot2docker` from the command line, do the following: 1. Create a new Boot2Docker VM. - $ boot2docker init + $ boot2docker init - This creates a new virtual machine. You only need to run this command once. + This creates a new virtual machine. You only need to run this command once. 2. Start the `boot2docker` VM. - $ boot2docker start + $ boot2docker start 3. Display the environment variables for the Docker client. - $ boot2docker shellinit - Writing /Users/mary/.boot2docker/certs/boot2docker-vm/ca.pem - Writing /Users/mary/.boot2docker/certs/boot2docker-vm/cert.pem - Writing /Users/mary/.boot2docker/certs/boot2docker-vm/key.pem - export DOCKER_HOST=tcp://192.168.59.103:2376 - export DOCKER_CERT_PATH=/Users/mary/.boot2docker/certs/boot2docker-vm - export DOCKER_TLS_VERIFY=1 + $ boot2docker shellinit + Writing /Users/mary/.boot2docker/certs/boot2docker-vm/ca.pem + Writing /Users/mary/.boot2docker/certs/boot2docker-vm/cert.pem + Writing /Users/mary/.boot2docker/certs/boot2docker-vm/key.pem + export DOCKER_HOST=tcp://192.168.59.103:2376 + export DOCKER_CERT_PATH=/Users/mary/.boot2docker/certs/boot2docker-vm + export DOCKER_TLS_VERIFY=1 - The specific paths and address on your machine will be different. + The specific paths and address on your machine will be different. 4. To set the environment variables in your shell do the following: - $ eval "$(boot2docker shellinit)" + $ eval "$(boot2docker shellinit)" - You can also set them manually by using the `export` commands `boot2docker` - returns. + You can also set them manually by using the `export` commands `boot2docker` + returns. 5. Run the `hello-world` container to verify your setup. - $ docker run hello-world + $ docker run hello-world ## Basic Boot2Docker exercises @@ -167,8 +169,8 @@ Initialize and run `boot2docker` from the command line, do the following: At this point, you should have `boot2docker` running and the `docker` client environment initialized. To verify this, run the following commands: - $ boot2docker status - $ docker version + $ boot2docker status + $ docker version Work through this section to try some practical container tasks using `boot2docker` VM. @@ -176,52 +178,52 @@ Work through this section to try some practical container tasks using `boot2dock 1. Start an NGINX container on the DOCKER_HOST. - $ docker run -d -P --name web nginx + $ docker run -d -P --name web nginx - Normally, the `docker run` commands starts a container, runs it, and then - exits. The `-d` flag keeps the container running in the background - after the `docker run` command completes. The `-P` flag publishes exposed ports from the - container to your local host; this lets you access them from your Mac. + Normally, the `docker run` commands starts a container, runs it, and then + exits. The `-d` flag keeps the container running in the background + after the `docker run` command completes. The `-P` flag publishes exposed ports from the + container to your local host; this lets you access them from your Mac. 2. Display your running container with `docker ps` command - CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES - 5fb65ff765e9 nginx:latest "nginx -g 'daemon of 3 minutes ago Up 3 minutes 0.0.0.0:49156->443/tcp, 0.0.0.0:49157->80/tcp web + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 5fb65ff765e9 nginx:latest "nginx -g 'daemon of 3 minutes ago Up 3 minutes 0.0.0.0:49156->443/tcp, 0.0.0.0:49157->80/tcp web - At this point, you can see `nginx` is running as a daemon. + At this point, you can see `nginx` is running as a daemon. 3. View just the container's ports. - $ docker port web - 443/tcp -> 0.0.0.0:49156 - 80/tcp -> 0.0.0.0:49157 + $ docker port web + 443/tcp -> 0.0.0.0:49156 + 80/tcp -> 0.0.0.0:49157 - This tells you that the `web` container's port `80` is mapped to port - `49157` on your Docker host. + This tells you that the `web` container's port `80` is mapped to port + `49157` on your Docker host. 4. Enter the `http://localhost:49157` address (`localhost` is `0.0.0.0`) in your browser: - ![Bad Address](/installation/images/bad_host.png) + ![Bad Address](/installation/images/bad_host.png) - This didn't work. The reason it doesn't work is your `DOCKER_HOST` address is - not the localhost address (0.0.0.0) but is instead the address of the - `boot2docker` VM. + This didn't work. The reason it doesn't work is your `DOCKER_HOST` address is + not the localhost address (0.0.0.0) but is instead the address of the + `boot2docker` VM. 5. Get the address of the `boot2docker` VM. - $ boot2docker ip - 192.168.59.103 + $ boot2docker ip + 192.168.59.103 6. Enter the `http://192.168.59.103:49157` address in your browser: - ![Correct Addressing](/installation/images/good_host.png) + ![Correct Addressing](/installation/images/good_host.png) - Success! + Success! 7. To stop and then remove your running `nginx` container, do the following: - $ docker stop web - $ docker rm web + $ docker stop web + $ docker rm web ### Mount a volume on the container @@ -231,46 +233,46 @@ The next exercise demonstrates how to do this. 1. Change to your user `$HOME` directory. - $ cd $HOME + $ cd $HOME 2. Make a new `site` directory. - $ mkdir site + $ mkdir site 3. Change into the `site` directory. - $ cd site + $ cd site 4. Create a new `index.html` file. - $ echo "my new site" > index.html + $ echo "my new site" > index.html 5. Start a new `nginx` container and replace the `html` folder with your `site` directory. - $ docker run -d -P -v $HOME/site:/usr/share/nginx/html --name mysite nginx + $ docker run -d -P -v $HOME/site:/usr/share/nginx/html --name mysite nginx 6. Get the `mysite` container's port. - $ docker port mysite - 80/tcp -> 0.0.0.0:49166 - 443/tcp -> 0.0.0.0:49165 + $ docker port mysite + 80/tcp -> 0.0.0.0:49166 + 443/tcp -> 0.0.0.0:49165 7. Open the site in a browser: - ![My site page](/installation/images/newsite_view.png) + ![My site page](/installation/images/newsite_view.png) 8. Try adding a page to your `$HOME/site` in real time. - $ echo "This is cool" > cool.html + $ echo "This is cool" > cool.html 9. Open the new page in the browser. - ![Cool page](/installation/images/cool_view.png) + ![Cool page](/installation/images/cool_view.png) 9. Stop and then remove your running `mysite` container. - $ docker stop mysite - $ docker rm mysite + $ docker stop mysite + $ docker rm mysite ## Upgrade Boot2Docker @@ -286,11 +288,11 @@ To upgrade from 1.4.1 or greater, you can do this: 2. Stop the `boot2docker` application. - $ boot2docker stop + $ boot2docker stop 3. Run the upgrade command. - $ boot2docker upgrade + $ boot2docker upgrade ### Use the installer @@ -301,22 +303,46 @@ To upgrade any version of Boot2Docker, do this: 2. Stop the `boot2docker` application. - $ boot2docker stop + $ boot2docker stop 3. Go to the [boot2docker/osx-installer ]( https://github.com/boot2docker/osx-installer/releases/latest) release page. 4. Download Boot2Docker by clicking `Boot2Docker-x.x.x.pkg` in the "Downloads" -section. + section. 2. Install Boot2Docker by double-clicking the package. - The installer places Boot2Docker in your "Applications" folder. + The installer places Boot2Docker in your "Applications" folder. + + +## Uninstallation + +1. Go to the [boot2docker/osx-installer ]( + https://github.com/boot2docker/osx-installer/releases/latest) release page. + +2. Download the source code by clicking `Source code (zip)` or + `Source code (tar.gz)` in the "Downloads" section. + +3. Extract the source code. + +4. Open a terminal on your local machine. + +5. Change to the directory where you extracted the source code: + + $ cd + +6. Make sure the uninstall.sh script is executable: + + $ chmod +x uninstall.sh + +7. Run the uninstall.sh script: + + $ ./uninstall.sh ## Learning more and acknowledgement - Use `boot2docker help` to list the full command line reference. For more information about using SSH or SCP to access the Boot2Docker VM, see the README at [Boot2Docker repository](https://github.com/boot2docker/boot2docker). diff --git a/docs/sources/installation/oracle.md b/docs/sources/installation/oracle.md index e05e664c1..e74decd9b 100644 --- a/docs/sources/installation/oracle.md +++ b/docs/sources/installation/oracle.md @@ -43,35 +43,35 @@ To enable the *addons* repository: `/etc/yum.repos.d/public-yum-ol7.repo` and set `enabled=1` in the `[ol6_addons]` or the `[ol7_addons]` stanza. -## To install Docker: +## Installation 1. Ensure the appropriate *addons* channel or repository has been enabled. 2. Use yum to install the Docker package: - $ sudo yum install docker + $ sudo yum install docker -## To start Docker: +## Starting Docker 1. Now that it's installed, start the Docker daemon: - 1. On Oracle Linux 6: + 1. On Oracle Linux 6: - $ sudo service docker start + $ sudo service docker start - 2. On Oracle Linux 7: + 2. On Oracle Linux 7: - $ sudo systemctl start docker.service + $ sudo systemctl start docker.service 2. If you want the Docker daemon to start automatically at boot: - 1. On Oracle Linux 6: + 1. On Oracle Linux 6: - $ sudo chkconfig docker on + $ sudo chkconfig docker on - 2. On Oracle Linux 7: + 2. On Oracle Linux 7: - $ sudo systemctl enable docker.service + $ sudo systemctl enable docker.service **Done!** @@ -99,6 +99,20 @@ To enable btrfs support on Oracle Linux: You can now continue with the [Docker User Guide](/userguide/). +## Uninstallation + +To uninstall the Docker package: + + $ sudo yum -y remove docker + +The above command will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. + ## Known issues ### Docker unmounts btrfs filesystem on shutdown diff --git a/docs/sources/installation/rhel.md b/docs/sources/installation/rhel.md index b3bd7aa1d..9b1734692 100644 --- a/docs/sources/installation/rhel.md +++ b/docs/sources/installation/rhel.md @@ -16,7 +16,9 @@ running on kernels shipped by the distribution. There are kernel changes which will cause issues if one decides to step outside that box and run non-distribution kernel packages. -## Red Hat Enterprise Linux 7 installation +## Red Hat Enterprise Linux 7 + +### Installation **Red Hat Enterprise Linux 7 (64 bit)** has [shipped with Docker](https://access.redhat.com/site/products/red-hat-enterprise-linux/docker-and-containers). @@ -41,7 +43,21 @@ Portal](https://access.redhat.com/). Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon). -## Red Hat Enterprise Linux 6.6 installation +### Uninstallation + +To uninstall the Docker package: + + $ sudo yum -y remove docker + +The above command will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. + +## Red Hat Enterprise Linux 6.6 You will need **64 bit** [RHEL 6.6](https://access.redhat.com/site/articles/3078#RHEL6) or later, with @@ -66,7 +82,7 @@ non-distro kernel packages. > vulnerabilities and severe bugs (such as those found in kernel 2.6.32) > are fixed. -## Installation +### Installation Firstly, you need to install the EPEL repository. Please follow the [EPEL installation @@ -90,6 +106,20 @@ To update the `docker-io` package Please continue with the [Starting the Docker daemon](#starting-the-docker-daemon). +### Uninstallation + +To uninstall the Docker package: + + $ sudo yum -y remove docker-io + +The above command will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. + ## Starting the Docker daemon Now that it's installed, let's start the Docker daemon. @@ -118,7 +148,6 @@ If you need to add an HTTP Proxy, set a different directory or partition for the Docker runtime files, or make other customizations, read our Systemd article to learn how to [customize your Systemd Docker daemon options](/articles/systemd/). - ## Issues? If you have any issues - please report them directly in the diff --git a/docs/sources/installation/ubuntulinux.md b/docs/sources/installation/ubuntulinux.md index 6c854997f..652edc9fd 100644 --- a/docs/sources/installation/ubuntulinux.md +++ b/docs/sources/installation/ubuntulinux.md @@ -28,8 +28,8 @@ and frequently panic under certain conditions. To check your current kernel version, open a terminal and use `uname -r` to display your kernel version: - $ uname -r - 3.11.0-15-generic + $ uname -r + 3.11.0-15-generic >**Caution** Some Ubuntu OS versions **require a version higher than 3.10** to >run Docker, see the prerequisites on this page that apply to your Ubuntu @@ -72,17 +72,17 @@ To upgrade your kernel and install the additional packages, do the following: 2. Update your package manager. - $ sudo apt-get update + $ sudo apt-get update 3. Install both the required and optional packages. - $ sudo apt-get install linux-image-generic-lts-trusty + $ sudo apt-get install linux-image-generic-lts-trusty - Depending on your environment, you may install more as described in the preceding table. + Depending on your environment, you may install more as described in the preceding table. 4. Reboot your host. - $ sudo reboot + $ sudo reboot 5. After your system reboots, go ahead and [install Docker](#installing-docker-on-ubuntu). @@ -92,7 +92,7 @@ To upgrade your kernel and install the additional packages, do the following: Docker uses AUFS as the default storage backend. If you don't have this prerequisite installed, Docker's installation process adds it. -##Installing Docker on Ubuntu +##Installation Make sure you have installed the prerequisites for your Ubuntu version. Then, install Docker using the following: @@ -101,19 +101,19 @@ install Docker using the following: 2. Verify that you have `wget` installed. - $ which wget + $ which wget - If `wget` isn't installed, install it after updating your manager: + If `wget` isn't installed, install it after updating your manager: - $ sudo apt-get update - $ sudo apt-get install wget + $ sudo apt-get update + $ sudo apt-get install wget 3. Get the latest Docker package. - $ wget -qO- https://get.docker.com/ | sh + $ wget -qO- https://get.docker.com/ | sh - The system prompts you for your `sudo` password. Then, it downloads and - installs Docker and its dependencies. + The system prompts you for your `sudo` password. Then, it downloads and + installs Docker and its dependencies. >**Note**: If your company is behind a filtering proxy, you may find that the >`apt-key` >command fails for the Docker repo during installation. To work around this, @@ -123,9 +123,9 @@ install Docker using the following: 4. Verify `docker` is installed correctly. - $ sudo docker run hello-world + $ sudo docker run hello-world - This command downloads a test image and runs it in a container. + This command downloads a test image and runs it in a container. ## Optional configurations for Docker on Ubuntu @@ -155,19 +155,19 @@ To create the `docker` group and add your user: 1. Log into Ubuntu as a user with `sudo` privileges. - This procedure assumes you log in as the `ubuntu` user. + This procedure assumes you log in as the `ubuntu` user. 3. Create the `docker` group and add your user. - $ sudo usermod -aG docker ubuntu + $ sudo usermod -aG docker ubuntu 3. Log out and log back in. - This ensures your user is running with the correct permissions. + This ensures your user is running with the correct permissions. 4. Verify your work by running `docker` without `sudo`. - $ docker run hello-world + $ docker run hello-world ### Adjust memory and swap accounting @@ -187,13 +187,13 @@ following. 3. Set the `GRUB_CMDLINE_LINUX` value as follows: - GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1" + GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1" 4. Save and close the file. 5. Update GRUB. - $ sudo update-grub + $ sudo update-grub 6. Reboot your system. @@ -216,25 +216,25 @@ To configure UFW and allow incoming connections on the Docker port: 2. Verify that UFW is installed and enabled. - $ sudo ufw status + $ sudo ufw status 3. Open the `/etc/default/ufw` file for editing. - $ sudo nano /etc/default/ufw + $ sudo nano /etc/default/ufw 4. Set the `DEFAULT_FORWARD_POLICY` policy to: - DEFAULT_FORWARD_POLICY="ACCEPT" + DEFAULT_FORWARD_POLICY="ACCEPT" 5. Save and close the file. 6. Reload UFW to use the new setting. - $ sudo ufw reload + $ sudo ufw reload 7. Allow incoming connections on the Docker port. - $ sudo ufw allow 2375/tcp + $ sudo ufw allow 2375/tcp ### Configure a DNS server for use by Docker @@ -262,25 +262,25 @@ To specify a DNS server for use by Docker: 2. Open the `/etc/default/docker` file for editing. - $ sudo nano /etc/default/docker + $ sudo nano /etc/default/docker 3. Add a setting for Docker. - DOCKER_OPTS="--dns 8.8.8.8" + DOCKER_OPTS="--dns 8.8.8.8" Replace `8.8.8.8` with a local DNS server such as `192.168.1.1`. You can also specify multiple DNS servers. Separated them with spaces, for example: - --dns 8.8.8.8 --dns 192.168.1.1 + --dns 8.8.8.8 --dns 192.168.1.1 - >**Warning**: If you're doing this on a laptop which connects to various - >networks, make sure to choose a public DNS server. + >**Warning**: If you're doing this on a laptop which connects to various + >networks, make sure to choose a public DNS server. 4. Save and close the file. 5. Restart the Docker daemon. - $ sudo restart docker + $ sudo restart docker   @@ -291,22 +291,39 @@ NetworkManager (this might slow your network). 1. Open the `/etc/NetworkManager/NetworkManager.conf` file for editing. - $ sudo nano /etc/NetworkManager/NetworkManager.conf + $ sudo nano /etc/NetworkManager/NetworkManager.conf 2. Comment out the `dns=dsnmasq` line: - dns=dnsmasq + dns=dnsmasq 3. Save and close the file. 4. Restart both the NetworkManager and Docker. - $ sudo restart network-manager $ sudo restart docker + $ sudo restart network-manager $ sudo restart docker ## Upgrade Docker -To install the latest version of Docker, use the standard `-N` flag with `wget`: +To install the latest version of Docker with `wget`: - $ wget -qO- https://get.docker.com/ | sh + $ wget -qO- https://get.docker.com/ | sh +## Uninstallation + +To uninstall the Docker package: + + $ sudo apt-get purge lxc-docker + +To uninstall the Docker package and dependencies that are no longer needed: + + $ sudo apt-get autoremove --purge lxc-docker + +The above commands will not remove images, containers, volumes, or user created +configuration files on your host. If you wish to delete all images, containers, +and volumes run the following command: + + $ rm -rf /var/lib/docker + +You must delete the user created configuration files manually. diff --git a/docs/sources/installation/windows.md b/docs/sources/installation/windows.md index fd3cc7eb4..b5a148417 100644 --- a/docs/sources/installation/windows.md +++ b/docs/sources/installation/windows.md @@ -67,7 +67,7 @@ Boot2Docker command requires `ssh.exe` to be in the PATH, therefore we need to include `bin` folder of the Git installation (which has ssh.exe) to the `%PATH%` environment variable by running: - set PATH=%PATH%;"c:\Program Files (x86)\Git\bin" + set PATH=%PATH%;"c:\Program Files (x86)\Git\bin" and then we can run the `boot2docker start` command to start the Boot2Docker VM. (Run `boot2docker init` command if you get an error saying machine does not @@ -81,7 +81,7 @@ to your console window and you are ready to run docker commands such as Launch a PowerShell window, then you need to add `ssh.exe` to your PATH: - $Env:Path = "${Env:Path};c:\Program Files (x86)\Git\bin" + $Env:Path = "${Env:Path};c:\Program Files (x86)\Git\bin" and after running `boot2docker start` command it will print PowerShell commands to set the environment variables to connect Docker running inside VM. Run these @@ -150,6 +150,12 @@ You can do this with - then click: "Save Private Key". - Then use the saved file to login with PuTTY using `docker@127.0.0.1:2022`. +## Uninstallation + +You can uninstall Boot2Docker using Window's standard process for removing programs. +This process does not remove the `docker-install.exe` file. You must delete that file +yourself. + ## References If you have Docker hosts running and if you don't wish to do a From 380b8737523edf1c2575208a14a8673684758692 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 1 May 2015 10:04:24 -0600 Subject: [PATCH 716/999] Only complete repos with "docker pull -a" With this, `docker pull deb` will show all `debian:*` tags, as before, but `docker pull -a deb` will complete directly to just `debian`. :+1: Signed-off-by: Andrew "Tianon" Page --- contrib/completion/bash/docker | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index 5b7a102a6..1aa083514 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -679,6 +679,14 @@ _docker_pull() { *) local counter=$(__docker_pos_first_nonflag) if [ $cword -eq $counter ]; then + for arg in "${COMP_WORDS[@]}"; do + case "$arg" in + --all-tags|-a) + __docker_image_repos + return + ;; + esac + done __docker_image_repos_and_tags fi ;; From d9639409fd54ee6955793474fa9a5bac1e583c77 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Fri, 1 May 2015 20:23:44 +0200 Subject: [PATCH 717/999] Provide a struct to configure cli streaming Signed-off-by: Antonio Murdaca --- api/client/build.go | 8 +++++++- api/client/create.go | 7 ++++++- api/client/events.go | 6 +++++- api/client/export.go | 6 +++++- api/client/import.go | 8 +++++++- api/client/load.go | 7 ++++++- api/client/logs.go | 8 +++++++- api/client/save.go | 9 +++++++-- api/client/utils.go | 18 +++++++++++------- 9 files changed, 61 insertions(+), 16 deletions(-) diff --git a/api/client/build.go b/api/client/build.go index e83de976b..27b06afb7 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -289,7 +289,13 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if context != nil { headers.Set("Content-Type", "application/tar") } - err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), body, cli.out, headers) + sopts := &streamOpts{ + rawTerminal: true, + in: body, + out: cli.out, + headers: headers, + } + err = cli.stream("POST", fmt.Sprintf("/build?%s", v.Encode()), sopts) if jerr, ok := err.(*jsonmessage.JSONError); ok { // If no error code is set, default to 1 if jerr.Code == 0 { diff --git a/api/client/create.go b/api/client/create.go index b0819a05d..f4d6e376b 100644 --- a/api/client/create.go +++ b/api/client/create.go @@ -47,7 +47,12 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error { registryAuthHeader := []string{ base64.URLEncoding.EncodeToString(buf), } - if err = cli.stream("POST", "/images/create?"+v.Encode(), nil, out, map[string][]string{"X-Registry-Auth": registryAuthHeader}); err != nil { + sopts := &streamOpts{ + rawTerminal: true, + out: out, + headers: map[string][]string{"X-Registry-Auth": registryAuthHeader}, + } + if err := cli.stream("POST", "/images/create?"+v.Encode(), sopts); err != nil { return err } return nil diff --git a/api/client/events.go b/api/client/events.go index 2154e0ccd..64ec6e139 100644 --- a/api/client/events.go +++ b/api/client/events.go @@ -63,7 +63,11 @@ func (cli *DockerCli) CmdEvents(args ...string) error { } v.Set("filters", filterJSON) } - if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil { + sopts := &streamOpts{ + rawTerminal: true, + out: cli.out, + } + if err := cli.stream("GET", "/events?"+v.Encode(), sopts); err != nil { return err } return nil diff --git a/api/client/export.go b/api/client/export.go index 1ff46f9b5..42b083473 100644 --- a/api/client/export.go +++ b/api/client/export.go @@ -34,7 +34,11 @@ func (cli *DockerCli) CmdExport(args ...string) error { } image := cmd.Arg(0) - if err := cli.stream("GET", "/containers/"+image+"/export", nil, output, nil); err != nil { + sopts := &streamOpts{ + rawTerminal: true, + out: output, + } + if err := cli.stream("GET", "/containers/"+image+"/export", sopts); err != nil { return err } diff --git a/api/client/import.go b/api/client/import.go index a6cc4cdc7..48c56896a 100644 --- a/api/client/import.go +++ b/api/client/import.go @@ -54,5 +54,11 @@ func (cli *DockerCli) CmdImport(args ...string) error { in = cli.in } - return cli.stream("POST", "/images/create?"+v.Encode(), in, cli.out, nil) + sopts := &streamOpts{ + rawTerminal: true, + in: in, + out: cli.out, + } + + return cli.stream("POST", "/images/create?"+v.Encode(), sopts) } diff --git a/api/client/load.go b/api/client/load.go index 7338c770d..8dd8bb546 100644 --- a/api/client/load.go +++ b/api/client/load.go @@ -29,7 +29,12 @@ func (cli *DockerCli) CmdLoad(args ...string) error { return err } } - if err := cli.stream("POST", "/images/load", input, cli.out, nil); err != nil { + sopts := &streamOpts{ + rawTerminal: true, + in: input, + out: cli.out, + } + if err := cli.stream("POST", "/images/load", sopts); err != nil { return err } return nil diff --git a/api/client/logs.go b/api/client/logs.go index 5e5dd9dd8..171b36da4 100644 --- a/api/client/logs.go +++ b/api/client/logs.go @@ -52,5 +52,11 @@ func (cli *DockerCli) CmdLogs(args ...string) error { } v.Set("tail", *tail) - return cli.streamHelper("GET", "/containers/"+name+"/logs?"+v.Encode(), c.Config.Tty, nil, cli.out, cli.err, nil) + sopts := &streamOpts{ + rawTerminal: c.Config.Tty, + out: cli.out, + err: cli.err, + } + + return cli.stream("GET", "/containers/"+name+"/logs?"+v.Encode(), sopts) } diff --git a/api/client/save.go b/api/client/save.go index 5d9d27615..a04cbcf1e 100644 --- a/api/client/save.go +++ b/api/client/save.go @@ -34,9 +34,14 @@ func (cli *DockerCli) CmdSave(args ...string) error { return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.") } + sopts := &streamOpts{ + rawTerminal: true, + out: output, + } + if len(cmd.Args()) == 1 { image := cmd.Arg(0) - if err := cli.stream("GET", "/images/"+image+"/get", nil, output, nil); err != nil { + if err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil { return err } } else { @@ -44,7 +49,7 @@ func (cli *DockerCli) CmdSave(args ...string) error { for _, arg := range cmd.Args() { v.Add("names", arg) } - if err := cli.stream("GET", "/images/get?"+v.Encode(), nil, output, nil); err != nil { + if err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil { return err } } diff --git a/api/client/utils.go b/api/client/utils.go index eed1163f8..6fb9b256f 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -170,19 +170,23 @@ func (cli *DockerCli) call(method, path string, data interface{}, headers map[st return body, statusCode, err } -func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error { - return cli.streamHelper(method, path, true, in, out, nil, headers) +type streamOpts struct { + rawTerminal bool + in io.Reader + out io.Writer + err io.Writer + headers map[string][]string } -func (cli *DockerCli) streamHelper(method, path string, setRawTerminal bool, in io.Reader, stdout, stderr io.Writer, headers map[string][]string) error { - body, contentType, _, err := cli.clientRequest(method, path, in, headers) +func (cli *DockerCli) stream(method, path string, opts *streamOpts) error { + body, contentType, _, err := cli.clientRequest(method, path, opts.in, opts.headers) if err != nil { return err } - return cli.streamBody(body, contentType, setRawTerminal, stdout, stderr) + return cli.streamBody(body, contentType, opts.rawTerminal, opts.out, opts.err) } -func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, setRawTerminal bool, stdout, stderr io.Writer) error { +func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, rawTerminal bool, stdout, stderr io.Writer) error { defer body.Close() if api.MatchesContentType(contentType, "application/json") { @@ -191,7 +195,7 @@ func (cli *DockerCli) streamBody(body io.ReadCloser, contentType string, setRawT if stdout != nil || stderr != nil { // When TTY is ON, use regular copy var err error - if setRawTerminal { + if rawTerminal { _, err = io.Copy(stdout, body) } else { _, err = stdcopy.StdCopy(stdout, stderr, body) From a05bcd12c44b4daada51267d89fd9ac53812be02 Mon Sep 17 00:00:00 2001 From: Alexander Morozov Date: Fri, 1 May 2015 13:35:54 -0700 Subject: [PATCH 718/999] Fix race in FirewalldInit It was possible that signalHandler won't start because connections is not assigned. Signed-off-by: Alexander Morozov --- pkg/iptables/firewalld.go | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pkg/iptables/firewalld.go b/pkg/iptables/firewalld.go index 308779413..1c0cddb0f 100644 --- a/pkg/iptables/firewalld.go +++ b/pkg/iptables/firewalld.go @@ -2,9 +2,10 @@ package iptables import ( "fmt" + "strings" + "github.com/Sirupsen/logrus" "github.com/godbus/dbus" - "strings" ) type IPV string @@ -40,6 +41,9 @@ func FirewalldInit() { if err != nil { logrus.Errorf("Failed to connect to D-Bus system bus: %s", err) } + if connection != nil { + go signalHandler() + } firewalldRunning = checkRunning() } @@ -76,20 +80,17 @@ func (c *Conn) initConnection() error { c.signal = make(chan *dbus.Signal, 10) c.sysconn.Signal(c.signal) - go signalHandler() return nil } func signalHandler() { - if connection != nil { - for signal := range connection.signal { - if strings.Contains(signal.Name, "NameOwnerChanged") { - firewalldRunning = checkRunning() - dbusConnectionChanged(signal.Body) - } else if strings.Contains(signal.Name, "Reloaded") { - reloaded() - } + for signal := range connection.signal { + if strings.Contains(signal.Name, "NameOwnerChanged") { + firewalldRunning = checkRunning() + dbusConnectionChanged(signal.Body) + } else if strings.Contains(signal.Name, "Reloaded") { + reloaded() } } } From 9f5730e131699caaf6f8f6f941d5942d68295055 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 1 May 2015 14:45:00 -0600 Subject: [PATCH 719/999] Remove hacky "cp .../Dockerfile.build ." in "build-deb" Turns out that `-f` on a file that's in `.dockerignore` actually does work. No idea why it wasn't when I was doing this before, but oh well! :metal: Signed-off-by: Andrew "Tianon" Page --- hack/make/build-deb | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/hack/make/build-deb b/hack/make/build-deb index a5a6d4387..90a2b98b8 100644 --- a/hack/make/build-deb +++ b/hack/make/build-deb @@ -56,15 +56,11 @@ DEST=$1 RUN { echo '$debSource (${debVersion}-0~${suite}) $suite; urgency=low'; echo; echo ' * Version: $VERSION'; echo; echo " -- $debMaintainer $debDate"; } > debian/changelog && cat >&2 debian/changelog RUN dpkg-buildpackage -uc -us EOF - cp -a "$DEST/$version/Dockerfile.build" . # can't use $DEST because it's in .dockerignore... tempImage="docker-temp/build-deb:$version" - ( set -x && docker build -t "$tempImage" -f Dockerfile.build . ) + ( set -x && docker build -t "$tempImage" -f "$DEST/$version/Dockerfile.build" . ) docker run --rm "$tempImage" bash -c 'cd .. && tar -c *_*' | tar -xvC "$DEST/$version" docker rmi "$tempImage" done - # clean up after ourselves - rm -f Dockerfile.build - source "${MAKEDIR}/.integration-daemon-stop" ) 2>&1 | tee -a "$DEST/test.log" From d317b7c89159f9795fa7eb69504191208b3c0b3f Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 1 May 2015 15:03:08 -0600 Subject: [PATCH 720/999] Add "debian:stretch" as another build-deb target Signed-off-by: Andrew "Tianon" Page --- contrib/builder/deb/debian-stretch/Dockerfile | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 contrib/builder/deb/debian-stretch/Dockerfile diff --git a/contrib/builder/deb/debian-stretch/Dockerfile b/contrib/builder/deb/debian-stretch/Dockerfile new file mode 100644 index 000000000..5bf753609 --- /dev/null +++ b/contrib/builder/deb/debian-stretch/Dockerfile @@ -0,0 +1,14 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/deb/generate.sh"! +# + +FROM debian:stretch + +RUN apt-get update && apt-get install -y bash-completion btrfs-tools build-essential curl ca-certificates debhelper dh-systemd git libapparmor-dev libdevmapper-dev libsqlite3-dev --no-install-recommends && rm -rf /var/lib/apt/lists/* + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS apparmor selinux From 576985a1dcd76a9af2c5c483e6f12035a1f47b96 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 1 May 2015 16:01:10 -0600 Subject: [PATCH 721/999] Finally remove our copy of "archive/tar" now that Go 1.4 is the minimum! IT'S ABOUT TIME. :tada: Signed-off-by: Andrew "Tianon" Page --- graph/tags_unit_test.go | 2 +- hack/vendor.sh | 11 - integration-cli/docker_api_containers_test.go | 2 +- integration-cli/docker_cli_push_test.go | 2 +- integration-cli/utils.go | 2 +- pkg/archive/archive.go | 3 +- pkg/archive/archive_test.go | 2 +- pkg/archive/archive_unix.go | 3 +- pkg/archive/archive_windows.go | 3 +- pkg/archive/changes.go | 3 +- pkg/archive/diff.go | 3 +- pkg/archive/diff_test.go | 3 +- pkg/archive/utils_test.go | 3 +- pkg/archive/wrap.go | 2 +- pkg/tarsum/tarsum.go | 3 +- pkg/tarsum/tarsum_test.go | 3 +- pkg/tarsum/versioning.go | 3 +- .../p/go/src/pkg/archive/tar/common.go | 305 ------- .../p/go/src/pkg/archive/tar/example_test.go | 79 -- .../p/go/src/pkg/archive/tar/reader.go | 820 ------------------ .../p/go/src/pkg/archive/tar/reader_test.go | 743 ---------------- .../p/go/src/pkg/archive/tar/stat_atim.go | 20 - .../go/src/pkg/archive/tar/stat_atimespec.go | 20 - .../p/go/src/pkg/archive/tar/stat_unix.go | 32 - .../p/go/src/pkg/archive/tar/tar_test.go | 284 ------ .../p/go/src/pkg/archive/tar/testdata/gnu.tar | Bin 3072 -> 0 bytes .../src/pkg/archive/tar/testdata/nil-uid.tar | Bin 1024 -> 0 bytes .../p/go/src/pkg/archive/tar/testdata/pax.tar | Bin 10240 -> 0 bytes .../go/src/pkg/archive/tar/testdata/small.txt | 1 - .../src/pkg/archive/tar/testdata/small2.txt | 1 - .../archive/tar/testdata/sparse-formats.tar | Bin 17920 -> 0 bytes .../go/src/pkg/archive/tar/testdata/star.tar | Bin 3072 -> 0 bytes .../go/src/pkg/archive/tar/testdata/ustar.tar | Bin 2048 -> 0 bytes .../p/go/src/pkg/archive/tar/testdata/v7.tar | Bin 3584 -> 0 bytes .../archive/tar/testdata/writer-big-long.tar | Bin 4096 -> 0 bytes .../pkg/archive/tar/testdata/writer-big.tar | Bin 4096 -> 0 bytes .../src/pkg/archive/tar/testdata/writer.tar | Bin 3584 -> 0 bytes .../src/pkg/archive/tar/testdata/xattrs.tar | Bin 5120 -> 0 bytes .../p/go/src/pkg/archive/tar/writer.go | 396 --------- .../p/go/src/pkg/archive/tar/writer_test.go | 491 ----------- 40 files changed, 16 insertions(+), 3229 deletions(-) delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/example_test.go delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atim.go delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atimespec.go delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_unix.go delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/tar_test.go delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/gnu.tar delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/nil-uid.tar delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/pax.tar delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small.txt delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small2.txt delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/star.tar delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/ustar.tar delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/v7.tar delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big.tar delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer.tar delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/xattrs.tar delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer.go delete mode 100644 vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go index 0482fa58e..db2d32c7f 100644 --- a/graph/tags_unit_test.go +++ b/graph/tags_unit_test.go @@ -1,6 +1,7 @@ package graph import ( + "archive/tar" "bytes" "io" "os" @@ -12,7 +13,6 @@ import ( _ "github.com/docker/docker/daemon/graphdriver/vfs" // import the vfs driver so it is used in the tests "github.com/docker/docker/image" "github.com/docker/docker/utils" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) const ( diff --git a/hack/vendor.sh b/hack/vendor.sh index 8fed05852..50cff1690 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -59,17 +59,6 @@ clone git github.com/go-fsnotify/fsnotify v1.0.4 clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673 -# get Go tip's archive/tar, for xattr support and improved performance -# TODO after Go 1.4 drops, bump our minimum supported version and drop this vendored dep -if [ "$1" = '--go' ]; then - # Go takes forever and a half to clone, so we only redownload it when explicitly requested via the "--go" flag to this script. - clone hg code.google.com/p/go 1b17b3426e3c - 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 -fi - # get distribution packages clone git github.com/docker/distribution d957768537c5af40e4f4cd96871f7b2bde9e2923 mv src/github.com/docker/distribution/digest tmp-digest diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index e43daed8f..2651bd7ac 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -1,6 +1,7 @@ package main import ( + "archive/tar" "bytes" "encoding/json" "io" @@ -11,7 +12,6 @@ import ( "github.com/docker/docker/api/types" "github.com/docker/docker/pkg/stringid" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "github.com/go-check/check" ) diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go index 69a05ed82..ca971807f 100644 --- a/integration-cli/docker_cli_push_test.go +++ b/integration-cli/docker_cli_push_test.go @@ -1,6 +1,7 @@ package main import ( + "archive/tar" "fmt" "io/ioutil" "os" @@ -8,7 +9,6 @@ import ( "strings" "time" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "github.com/go-check/check" ) diff --git a/integration-cli/utils.go b/integration-cli/utils.go index f0de79ea8..5fb0b6345 100644 --- a/integration-cli/utils.go +++ b/integration-cli/utils.go @@ -1,6 +1,7 @@ package main import ( + "archive/tar" "bytes" "encoding/json" "errors" @@ -17,7 +18,6 @@ import ( "time" "github.com/docker/docker/pkg/stringutils" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) func getExitCode(err error) (int, error) { diff --git a/pkg/archive/archive.go b/pkg/archive/archive.go index 4d8d26008..1579df194 100644 --- a/pkg/archive/archive.go +++ b/pkg/archive/archive.go @@ -1,6 +1,7 @@ package archive import ( + "archive/tar" "bufio" "bytes" "compress/bzip2" @@ -16,8 +17,6 @@ import ( "strings" "syscall" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" - "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/fileutils" "github.com/docker/docker/pkg/pools" diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go index ae9b5a8cd..f24f628c2 100644 --- a/pkg/archive/archive_test.go +++ b/pkg/archive/archive_test.go @@ -1,6 +1,7 @@ package archive import ( + "archive/tar" "bytes" "fmt" "io" @@ -15,7 +16,6 @@ import ( "time" "github.com/docker/docker/pkg/system" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) func TestIsArchiveNilHeader(t *testing.T) { diff --git a/pkg/archive/archive_unix.go b/pkg/archive/archive_unix.go index 82c9a82c1..6dc96a4ed 100644 --- a/pkg/archive/archive_unix.go +++ b/pkg/archive/archive_unix.go @@ -3,11 +3,10 @@ package archive import ( + "archive/tar" "errors" "os" "syscall" - - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) // canonicalTarNameForPath returns platform-specific filepath diff --git a/pkg/archive/archive_windows.go b/pkg/archive/archive_windows.go index 6caef3b73..593346df2 100644 --- a/pkg/archive/archive_windows.go +++ b/pkg/archive/archive_windows.go @@ -3,11 +3,10 @@ package archive import ( + "archive/tar" "fmt" "os" "strings" - - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) // canonicalTarNameForPath returns platform-specific filepath diff --git a/pkg/archive/changes.go b/pkg/archive/changes.go index 06fad8eb4..d03af0fed 100644 --- a/pkg/archive/changes.go +++ b/pkg/archive/changes.go @@ -1,6 +1,7 @@ package archive import ( + "archive/tar" "bytes" "fmt" "io" @@ -11,8 +12,6 @@ import ( "syscall" "time" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" - "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/pools" "github.com/docker/docker/pkg/system" diff --git a/pkg/archive/diff.go b/pkg/archive/diff.go index b5eb63fd4..a8314bc1f 100644 --- a/pkg/archive/diff.go +++ b/pkg/archive/diff.go @@ -1,6 +1,7 @@ package archive import ( + "archive/tar" "fmt" "io" "io/ioutil" @@ -9,8 +10,6 @@ import ( "strings" "syscall" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" - "github.com/docker/docker/pkg/pools" "github.com/docker/docker/pkg/system" ) diff --git a/pkg/archive/diff_test.go b/pkg/archive/diff_test.go index 758c4115d..01ed43728 100644 --- a/pkg/archive/diff_test.go +++ b/pkg/archive/diff_test.go @@ -1,9 +1,8 @@ package archive import ( + "archive/tar" "testing" - - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) func TestApplyLayerInvalidFilenames(t *testing.T) { diff --git a/pkg/archive/utils_test.go b/pkg/archive/utils_test.go index 904802720..2a266c2fd 100644 --- a/pkg/archive/utils_test.go +++ b/pkg/archive/utils_test.go @@ -1,6 +1,7 @@ package archive import ( + "archive/tar" "bytes" "fmt" "io" @@ -8,8 +9,6 @@ import ( "os" "path/filepath" "time" - - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) var testUntarFns = map[string]func(string, io.Reader) error{ diff --git a/pkg/archive/wrap.go b/pkg/archive/wrap.go index b8b60197a..dfb335c0b 100644 --- a/pkg/archive/wrap.go +++ b/pkg/archive/wrap.go @@ -1,8 +1,8 @@ package archive import ( + "archive/tar" "bytes" - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" "io/ioutil" ) diff --git a/pkg/tarsum/tarsum.go b/pkg/tarsum/tarsum.go index 88fcbe4a9..a778bb0b9 100644 --- a/pkg/tarsum/tarsum.go +++ b/pkg/tarsum/tarsum.go @@ -1,6 +1,7 @@ package tarsum import ( + "archive/tar" "bytes" "compress/gzip" "crypto" @@ -11,8 +12,6 @@ import ( "hash" "io" "strings" - - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) const ( diff --git a/pkg/tarsum/tarsum_test.go b/pkg/tarsum/tarsum_test.go index 26f12cc84..968d7c7cf 100644 --- a/pkg/tarsum/tarsum_test.go +++ b/pkg/tarsum/tarsum_test.go @@ -1,6 +1,7 @@ package tarsum import ( + "archive/tar" "bytes" "compress/gzip" "crypto/md5" @@ -14,8 +15,6 @@ import ( "io/ioutil" "os" "testing" - - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) type testLayer struct { diff --git a/pkg/tarsum/versioning.go b/pkg/tarsum/versioning.go index 0ceb5298a..3cdc6ddaa 100644 --- a/pkg/tarsum/versioning.go +++ b/pkg/tarsum/versioning.go @@ -1,12 +1,11 @@ package tarsum import ( + "archive/tar" "errors" "sort" "strconv" "strings" - - "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar" ) // versioning of the TarSum algorithm diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go deleted file mode 100644 index e363aa793..000000000 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/common.go +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package tar implements access to tar archives. -// It aims to cover most of the variations, including those produced -// by GNU and BSD tars. -// -// References: -// http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5 -// http://www.gnu.org/software/tar/manual/html_node/Standard.html -// http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pax.html -package tar - -import ( - "bytes" - "errors" - "fmt" - "os" - "path" - "time" -) - -const ( - blockSize = 512 - - // Types - TypeReg = '0' // regular file - TypeRegA = '\x00' // regular file - TypeLink = '1' // hard link - TypeSymlink = '2' // symbolic link - TypeChar = '3' // character device node - TypeBlock = '4' // block device node - TypeDir = '5' // directory - TypeFifo = '6' // fifo node - TypeCont = '7' // reserved - TypeXHeader = 'x' // extended header - TypeXGlobalHeader = 'g' // global extended header - TypeGNULongName = 'L' // Next file has a long name - TypeGNULongLink = 'K' // Next file symlinks to a file w/ a long name - TypeGNUSparse = 'S' // sparse file -) - -// A Header represents a single header in a tar archive. -// Some fields may not be populated. -type Header struct { - Name string // name of header file entry - Mode int64 // permission and mode bits - Uid int // user id of owner - Gid int // group id of owner - Size int64 // length in bytes - ModTime time.Time // modified time - Typeflag byte // type of header entry - Linkname string // target name of link - Uname string // user name of owner - Gname string // group name of owner - Devmajor int64 // major number of character or block device - Devminor int64 // minor number of character or block device - AccessTime time.Time // access time - ChangeTime time.Time // status change time - Xattrs map[string]string -} - -// File name constants from the tar spec. -const ( - fileNameSize = 100 // Maximum number of bytes in a standard tar name. - fileNamePrefixSize = 155 // Maximum number of ustar extension bytes. -) - -// FileInfo returns an os.FileInfo for the Header. -func (h *Header) FileInfo() os.FileInfo { - return headerFileInfo{h} -} - -// headerFileInfo implements os.FileInfo. -type headerFileInfo struct { - h *Header -} - -func (fi headerFileInfo) Size() int64 { return fi.h.Size } -func (fi headerFileInfo) IsDir() bool { return fi.Mode().IsDir() } -func (fi headerFileInfo) ModTime() time.Time { return fi.h.ModTime } -func (fi headerFileInfo) Sys() interface{} { return fi.h } - -// Name returns the base name of the file. -func (fi headerFileInfo) Name() string { - if fi.IsDir() { - return path.Base(path.Clean(fi.h.Name)) - } - return path.Base(fi.h.Name) -} - -// Mode returns the permission and mode bits for the headerFileInfo. -func (fi headerFileInfo) Mode() (mode os.FileMode) { - // Set file permission bits. - mode = os.FileMode(fi.h.Mode).Perm() - - // Set setuid, setgid and sticky bits. - if fi.h.Mode&c_ISUID != 0 { - // setuid - mode |= os.ModeSetuid - } - if fi.h.Mode&c_ISGID != 0 { - // setgid - mode |= os.ModeSetgid - } - if fi.h.Mode&c_ISVTX != 0 { - // sticky - mode |= os.ModeSticky - } - - // Set file mode bits. - // clear perm, setuid, setgid and sticky bits. - m := os.FileMode(fi.h.Mode) &^ 07777 - if m == c_ISDIR { - // directory - mode |= os.ModeDir - } - if m == c_ISFIFO { - // named pipe (FIFO) - mode |= os.ModeNamedPipe - } - if m == c_ISLNK { - // symbolic link - mode |= os.ModeSymlink - } - if m == c_ISBLK { - // device file - mode |= os.ModeDevice - } - if m == c_ISCHR { - // Unix character device - mode |= os.ModeDevice - mode |= os.ModeCharDevice - } - if m == c_ISSOCK { - // Unix domain socket - mode |= os.ModeSocket - } - - switch fi.h.Typeflag { - case TypeLink, TypeSymlink: - // hard link, symbolic link - mode |= os.ModeSymlink - case TypeChar: - // character device node - mode |= os.ModeDevice - mode |= os.ModeCharDevice - case TypeBlock: - // block device node - mode |= os.ModeDevice - case TypeDir: - // directory - mode |= os.ModeDir - case TypeFifo: - // fifo node - mode |= os.ModeNamedPipe - } - - return mode -} - -// sysStat, if non-nil, populates h from system-dependent fields of fi. -var sysStat func(fi os.FileInfo, h *Header) error - -// Mode constants from the tar spec. -const ( - c_ISUID = 04000 // Set uid - c_ISGID = 02000 // Set gid - c_ISVTX = 01000 // Save text (sticky bit) - c_ISDIR = 040000 // Directory - c_ISFIFO = 010000 // FIFO - c_ISREG = 0100000 // Regular file - c_ISLNK = 0120000 // Symbolic link - c_ISBLK = 060000 // Block special file - c_ISCHR = 020000 // Character special file - c_ISSOCK = 0140000 // Socket -) - -// Keywords for the PAX Extended Header -const ( - paxAtime = "atime" - paxCharset = "charset" - paxComment = "comment" - paxCtime = "ctime" // please note that ctime is not a valid pax header. - paxGid = "gid" - paxGname = "gname" - paxLinkpath = "linkpath" - paxMtime = "mtime" - paxPath = "path" - paxSize = "size" - paxUid = "uid" - paxUname = "uname" - paxXattr = "SCHILY.xattr." - paxNone = "" -) - -// FileInfoHeader creates a partially-populated Header from fi. -// If fi describes a symlink, FileInfoHeader records link as the link target. -// If fi describes a directory, a slash is appended to the name. -// Because os.FileInfo's Name method returns only the base name of -// the file it describes, it may be necessary to modify the Name field -// of the returned header to provide the full path name of the file. -func FileInfoHeader(fi os.FileInfo, link string) (*Header, error) { - if fi == nil { - return nil, errors.New("tar: FileInfo is nil") - } - fm := fi.Mode() - h := &Header{ - Name: fi.Name(), - ModTime: fi.ModTime(), - Mode: int64(fm.Perm()), // or'd with c_IS* constants later - } - switch { - case fm.IsRegular(): - h.Mode |= c_ISREG - h.Typeflag = TypeReg - h.Size = fi.Size() - case fi.IsDir(): - h.Typeflag = TypeDir - h.Mode |= c_ISDIR - h.Name += "/" - case fm&os.ModeSymlink != 0: - h.Typeflag = TypeSymlink - h.Mode |= c_ISLNK - h.Linkname = link - case fm&os.ModeDevice != 0: - if fm&os.ModeCharDevice != 0 { - h.Mode |= c_ISCHR - h.Typeflag = TypeChar - } else { - h.Mode |= c_ISBLK - h.Typeflag = TypeBlock - } - case fm&os.ModeNamedPipe != 0: - h.Typeflag = TypeFifo - h.Mode |= c_ISFIFO - case fm&os.ModeSocket != 0: - h.Mode |= c_ISSOCK - default: - return nil, fmt.Errorf("archive/tar: unknown file mode %v", fm) - } - if fm&os.ModeSetuid != 0 { - h.Mode |= c_ISUID - } - if fm&os.ModeSetgid != 0 { - h.Mode |= c_ISGID - } - if fm&os.ModeSticky != 0 { - h.Mode |= c_ISVTX - } - if sysStat != nil { - return h, sysStat(fi, h) - } - return h, nil -} - -var zeroBlock = make([]byte, blockSize) - -// POSIX specifies a sum of the unsigned byte values, but the Sun tar uses signed byte values. -// We compute and return both. -func checksum(header []byte) (unsigned int64, signed int64) { - for i := 0; i < len(header); i++ { - if i == 148 { - // The chksum field (header[148:156]) is special: it should be treated as space bytes. - unsigned += ' ' * 8 - signed += ' ' * 8 - i += 7 - continue - } - unsigned += int64(header[i]) - signed += int64(int8(header[i])) - } - return -} - -type slicer []byte - -func (sp *slicer) next(n int) (b []byte) { - s := *sp - b, *sp = s[0:n], s[n:] - return -} - -func isASCII(s string) bool { - for _, c := range s { - if c >= 0x80 { - return false - } - } - return true -} - -func toASCII(s string) string { - if isASCII(s) { - return s - } - var buf bytes.Buffer - for _, c := range s { - if c < 0x80 { - buf.WriteByte(byte(c)) - } - } - return buf.String() -} diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/example_test.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/example_test.go deleted file mode 100644 index 351eaa0e6..000000000 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/example_test.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tar_test - -import ( - "archive/tar" - "bytes" - "fmt" - "io" - "log" - "os" -) - -func Example() { - // Create a buffer to write our archive to. - buf := new(bytes.Buffer) - - // Create a new tar archive. - tw := tar.NewWriter(buf) - - // Add some files to the archive. - var files = []struct { - Name, Body string - }{ - {"readme.txt", "This archive contains some text files."}, - {"gopher.txt", "Gopher names:\nGeorge\nGeoffrey\nGonzo"}, - {"todo.txt", "Get animal handling licence."}, - } - for _, file := range files { - hdr := &tar.Header{ - Name: file.Name, - Size: int64(len(file.Body)), - } - if err := tw.WriteHeader(hdr); err != nil { - log.Fatalln(err) - } - if _, err := tw.Write([]byte(file.Body)); err != nil { - log.Fatalln(err) - } - } - // Make sure to check the error on Close. - if err := tw.Close(); err != nil { - log.Fatalln(err) - } - - // Open the tar archive for reading. - r := bytes.NewReader(buf.Bytes()) - tr := tar.NewReader(r) - - // Iterate through the files in the archive. - for { - hdr, err := tr.Next() - if err == io.EOF { - // end of tar archive - break - } - if err != nil { - log.Fatalln(err) - } - fmt.Printf("Contents of %s:\n", hdr.Name) - if _, err := io.Copy(os.Stdout, tr); err != nil { - log.Fatalln(err) - } - fmt.Println() - } - - // Output: - // Contents of readme.txt: - // This archive contains some text files. - // Contents of gopher.txt: - // Gopher names: - // George - // Geoffrey - // Gonzo - // Contents of todo.txt: - // Get animal handling licence. -} diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go deleted file mode 100644 index a27559d0f..000000000 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader.go +++ /dev/null @@ -1,820 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tar - -// TODO(dsymonds): -// - pax extensions - -import ( - "bytes" - "errors" - "io" - "io/ioutil" - "os" - "strconv" - "strings" - "time" -) - -var ( - ErrHeader = errors.New("archive/tar: invalid tar header") -) - -const maxNanoSecondIntSize = 9 - -// A Reader provides sequential access to the contents of a tar archive. -// A tar archive consists of a sequence of files. -// The Next method advances to the next file in the archive (including the first), -// and then it can be treated as an io.Reader to access the file's data. -type Reader struct { - r io.Reader - err error - pad int64 // amount of padding (ignored) after current file entry - curr numBytesReader // reader for current file entry - hdrBuff [blockSize]byte // buffer to use in readHeader -} - -// A numBytesReader is an io.Reader with a numBytes method, returning the number -// of bytes remaining in the underlying encoded data. -type numBytesReader interface { - io.Reader - numBytes() int64 -} - -// A regFileReader is a numBytesReader for reading file data from a tar archive. -type regFileReader struct { - r io.Reader // underlying reader - nb int64 // number of unread bytes for current file entry -} - -// A sparseFileReader is a numBytesReader for reading sparse file data from a tar archive. -type sparseFileReader struct { - rfr *regFileReader // reads the sparse-encoded file data - sp []sparseEntry // the sparse map for the file - pos int64 // keeps track of file position - tot int64 // total size of the file -} - -// Keywords for GNU sparse files in a PAX extended header -const ( - paxGNUSparseNumBlocks = "GNU.sparse.numblocks" - paxGNUSparseOffset = "GNU.sparse.offset" - paxGNUSparseNumBytes = "GNU.sparse.numbytes" - paxGNUSparseMap = "GNU.sparse.map" - paxGNUSparseName = "GNU.sparse.name" - paxGNUSparseMajor = "GNU.sparse.major" - paxGNUSparseMinor = "GNU.sparse.minor" - paxGNUSparseSize = "GNU.sparse.size" - paxGNUSparseRealSize = "GNU.sparse.realsize" -) - -// Keywords for old GNU sparse headers -const ( - oldGNUSparseMainHeaderOffset = 386 - oldGNUSparseMainHeaderIsExtendedOffset = 482 - oldGNUSparseMainHeaderNumEntries = 4 - oldGNUSparseExtendedHeaderIsExtendedOffset = 504 - oldGNUSparseExtendedHeaderNumEntries = 21 - oldGNUSparseOffsetSize = 12 - oldGNUSparseNumBytesSize = 12 -) - -// NewReader creates a new Reader reading from r. -func NewReader(r io.Reader) *Reader { return &Reader{r: r} } - -// Next advances to the next entry in the tar archive. -func (tr *Reader) Next() (*Header, error) { - var hdr *Header - if tr.err == nil { - tr.skipUnread() - } - if tr.err != nil { - return hdr, tr.err - } - hdr = tr.readHeader() - if hdr == nil { - return hdr, tr.err - } - // Check for PAX/GNU header. - switch hdr.Typeflag { - case TypeXHeader: - // PAX extended header - headers, err := parsePAX(tr) - if err != nil { - return nil, err - } - // We actually read the whole file, - // but this skips alignment padding - tr.skipUnread() - hdr = tr.readHeader() - mergePAX(hdr, headers) - - // Check for a PAX format sparse file - sp, err := tr.checkForGNUSparsePAXHeaders(hdr, headers) - if err != nil { - tr.err = err - return nil, err - } - if sp != nil { - // Current file is a PAX format GNU sparse file. - // Set the current file reader to a sparse file reader. - tr.curr = &sparseFileReader{rfr: tr.curr.(*regFileReader), sp: sp, tot: hdr.Size} - } - return hdr, nil - case TypeGNULongName: - // We have a GNU long name header. Its contents are the real file name. - realname, err := ioutil.ReadAll(tr) - if err != nil { - return nil, err - } - hdr, err := tr.Next() - hdr.Name = cString(realname) - return hdr, err - case TypeGNULongLink: - // We have a GNU long link header. - realname, err := ioutil.ReadAll(tr) - if err != nil { - return nil, err - } - hdr, err := tr.Next() - hdr.Linkname = cString(realname) - return hdr, err - } - return hdr, tr.err -} - -// checkForGNUSparsePAXHeaders checks the PAX headers for GNU sparse headers. If they are found, then -// this function reads the sparse map and returns it. Unknown sparse formats are ignored, causing the file to -// be treated as a regular file. -func (tr *Reader) checkForGNUSparsePAXHeaders(hdr *Header, headers map[string]string) ([]sparseEntry, error) { - var sparseFormat string - - // Check for sparse format indicators - major, majorOk := headers[paxGNUSparseMajor] - minor, minorOk := headers[paxGNUSparseMinor] - sparseName, sparseNameOk := headers[paxGNUSparseName] - _, sparseMapOk := headers[paxGNUSparseMap] - sparseSize, sparseSizeOk := headers[paxGNUSparseSize] - sparseRealSize, sparseRealSizeOk := headers[paxGNUSparseRealSize] - - // Identify which, if any, sparse format applies from which PAX headers are set - if majorOk && minorOk { - sparseFormat = major + "." + minor - } else if sparseNameOk && sparseMapOk { - sparseFormat = "0.1" - } else if sparseSizeOk { - sparseFormat = "0.0" - } else { - // Not a PAX format GNU sparse file. - return nil, nil - } - - // Check for unknown sparse format - if sparseFormat != "0.0" && sparseFormat != "0.1" && sparseFormat != "1.0" { - return nil, nil - } - - // Update hdr from GNU sparse PAX headers - if sparseNameOk { - hdr.Name = sparseName - } - if sparseSizeOk { - realSize, err := strconv.ParseInt(sparseSize, 10, 0) - if err != nil { - return nil, ErrHeader - } - hdr.Size = realSize - } else if sparseRealSizeOk { - realSize, err := strconv.ParseInt(sparseRealSize, 10, 0) - if err != nil { - return nil, ErrHeader - } - hdr.Size = realSize - } - - // Set up the sparse map, according to the particular sparse format in use - var sp []sparseEntry - var err error - switch sparseFormat { - case "0.0", "0.1": - sp, err = readGNUSparseMap0x1(headers) - case "1.0": - sp, err = readGNUSparseMap1x0(tr.curr) - } - return sp, err -} - -// mergePAX merges well known headers according to PAX standard. -// In general headers with the same name as those found -// in the header struct overwrite those found in the header -// struct with higher precision or longer values. Esp. useful -// for name and linkname fields. -func mergePAX(hdr *Header, headers map[string]string) error { - for k, v := range headers { - switch k { - case paxPath: - hdr.Name = v - case paxLinkpath: - hdr.Linkname = v - case paxGname: - hdr.Gname = v - case paxUname: - hdr.Uname = v - case paxUid: - uid, err := strconv.ParseInt(v, 10, 0) - if err != nil { - return err - } - hdr.Uid = int(uid) - case paxGid: - gid, err := strconv.ParseInt(v, 10, 0) - if err != nil { - return err - } - hdr.Gid = int(gid) - case paxAtime: - t, err := parsePAXTime(v) - if err != nil { - return err - } - hdr.AccessTime = t - case paxMtime: - t, err := parsePAXTime(v) - if err != nil { - return err - } - hdr.ModTime = t - case paxCtime: - t, err := parsePAXTime(v) - if err != nil { - return err - } - hdr.ChangeTime = t - case paxSize: - size, err := strconv.ParseInt(v, 10, 0) - if err != nil { - return err - } - hdr.Size = int64(size) - default: - if strings.HasPrefix(k, paxXattr) { - if hdr.Xattrs == nil { - hdr.Xattrs = make(map[string]string) - } - hdr.Xattrs[k[len(paxXattr):]] = v - } - } - } - return nil -} - -// parsePAXTime takes a string of the form %d.%d as described in -// the PAX specification. -func parsePAXTime(t string) (time.Time, error) { - buf := []byte(t) - pos := bytes.IndexByte(buf, '.') - var seconds, nanoseconds int64 - var err error - if pos == -1 { - seconds, err = strconv.ParseInt(t, 10, 0) - if err != nil { - return time.Time{}, err - } - } else { - seconds, err = strconv.ParseInt(string(buf[:pos]), 10, 0) - if err != nil { - return time.Time{}, err - } - nano_buf := string(buf[pos+1:]) - // Pad as needed before converting to a decimal. - // For example .030 -> .030000000 -> 30000000 nanoseconds - if len(nano_buf) < maxNanoSecondIntSize { - // Right pad - nano_buf += strings.Repeat("0", maxNanoSecondIntSize-len(nano_buf)) - } else if len(nano_buf) > maxNanoSecondIntSize { - // Right truncate - nano_buf = nano_buf[:maxNanoSecondIntSize] - } - nanoseconds, err = strconv.ParseInt(string(nano_buf), 10, 0) - if err != nil { - return time.Time{}, err - } - } - ts := time.Unix(seconds, nanoseconds) - return ts, nil -} - -// parsePAX parses PAX headers. -// If an extended header (type 'x') is invalid, ErrHeader is returned -func parsePAX(r io.Reader) (map[string]string, error) { - buf, err := ioutil.ReadAll(r) - if err != nil { - return nil, err - } - - // For GNU PAX sparse format 0.0 support. - // This function transforms the sparse format 0.0 headers into sparse format 0.1 headers. - var sparseMap bytes.Buffer - - headers := make(map[string]string) - // Each record is constructed as - // "%d %s=%s\n", length, keyword, value - for len(buf) > 0 { - // or the header was empty to start with. - var sp int - // The size field ends at the first space. - sp = bytes.IndexByte(buf, ' ') - if sp == -1 { - return nil, ErrHeader - } - // Parse the first token as a decimal integer. - n, err := strconv.ParseInt(string(buf[:sp]), 10, 0) - if err != nil { - return nil, ErrHeader - } - // Extract everything between the decimal and the n -1 on the - // beginning to eat the ' ', -1 on the end to skip the newline. - var record []byte - record, buf = buf[sp+1:n-1], buf[n:] - // The first equals is guaranteed to mark the end of the key. - // Everything else is value. - eq := bytes.IndexByte(record, '=') - if eq == -1 { - return nil, ErrHeader - } - key, value := record[:eq], record[eq+1:] - - keyStr := string(key) - if keyStr == paxGNUSparseOffset || keyStr == paxGNUSparseNumBytes { - // GNU sparse format 0.0 special key. Write to sparseMap instead of using the headers map. - sparseMap.Write(value) - sparseMap.Write([]byte{','}) - } else { - // Normal key. Set the value in the headers map. - headers[keyStr] = string(value) - } - } - if sparseMap.Len() != 0 { - // Add sparse info to headers, chopping off the extra comma - sparseMap.Truncate(sparseMap.Len() - 1) - headers[paxGNUSparseMap] = sparseMap.String() - } - return headers, nil -} - -// cString parses bytes as a NUL-terminated C-style string. -// If a NUL byte is not found then the whole slice is returned as a string. -func cString(b []byte) string { - n := 0 - for n < len(b) && b[n] != 0 { - n++ - } - return string(b[0:n]) -} - -func (tr *Reader) octal(b []byte) int64 { - // Check for binary format first. - if len(b) > 0 && b[0]&0x80 != 0 { - var x int64 - for i, c := range b { - if i == 0 { - c &= 0x7f // ignore signal bit in first byte - } - x = x<<8 | int64(c) - } - return x - } - - // Because unused fields are filled with NULs, we need - // to skip leading NULs. Fields may also be padded with - // spaces or NULs. - // So we remove leading and trailing NULs and spaces to - // be sure. - b = bytes.Trim(b, " \x00") - - if len(b) == 0 { - return 0 - } - x, err := strconv.ParseUint(cString(b), 8, 64) - if err != nil { - tr.err = err - } - return int64(x) -} - -// skipUnread skips any unread bytes in the existing file entry, as well as any alignment padding. -func (tr *Reader) skipUnread() { - nr := tr.numBytes() + tr.pad // number of bytes to skip - tr.curr, tr.pad = nil, 0 - if sr, ok := tr.r.(io.Seeker); ok { - if _, err := sr.Seek(nr, os.SEEK_CUR); err == nil { - return - } - } - _, tr.err = io.CopyN(ioutil.Discard, tr.r, nr) -} - -func (tr *Reader) verifyChecksum(header []byte) bool { - if tr.err != nil { - return false - } - - given := tr.octal(header[148:156]) - unsigned, signed := checksum(header) - return given == unsigned || given == signed -} - -func (tr *Reader) readHeader() *Header { - header := tr.hdrBuff[:] - copy(header, zeroBlock) - - if _, tr.err = io.ReadFull(tr.r, header); tr.err != nil { - return nil - } - - // Two blocks of zero bytes marks the end of the archive. - if bytes.Equal(header, zeroBlock[0:blockSize]) { - if _, tr.err = io.ReadFull(tr.r, header); tr.err != nil { - return nil - } - if bytes.Equal(header, zeroBlock[0:blockSize]) { - tr.err = io.EOF - } else { - tr.err = ErrHeader // zero block and then non-zero block - } - return nil - } - - if !tr.verifyChecksum(header) { - tr.err = ErrHeader - return nil - } - - // Unpack - hdr := new(Header) - s := slicer(header) - - hdr.Name = cString(s.next(100)) - hdr.Mode = tr.octal(s.next(8)) - hdr.Uid = int(tr.octal(s.next(8))) - hdr.Gid = int(tr.octal(s.next(8))) - hdr.Size = tr.octal(s.next(12)) - hdr.ModTime = time.Unix(tr.octal(s.next(12)), 0) - s.next(8) // chksum - hdr.Typeflag = s.next(1)[0] - hdr.Linkname = cString(s.next(100)) - - // The remainder of the header depends on the value of magic. - // The original (v7) version of tar had no explicit magic field, - // so its magic bytes, like the rest of the block, are NULs. - magic := string(s.next(8)) // contains version field as well. - var format string - switch { - case magic[:6] == "ustar\x00": // POSIX tar (1003.1-1988) - if string(header[508:512]) == "tar\x00" { - format = "star" - } else { - format = "posix" - } - case magic == "ustar \x00": // old GNU tar - format = "gnu" - } - - switch format { - case "posix", "gnu", "star": - hdr.Uname = cString(s.next(32)) - hdr.Gname = cString(s.next(32)) - devmajor := s.next(8) - devminor := s.next(8) - if hdr.Typeflag == TypeChar || hdr.Typeflag == TypeBlock { - hdr.Devmajor = tr.octal(devmajor) - hdr.Devminor = tr.octal(devminor) - } - var prefix string - switch format { - case "posix", "gnu": - prefix = cString(s.next(155)) - case "star": - prefix = cString(s.next(131)) - hdr.AccessTime = time.Unix(tr.octal(s.next(12)), 0) - hdr.ChangeTime = time.Unix(tr.octal(s.next(12)), 0) - } - if len(prefix) > 0 { - hdr.Name = prefix + "/" + hdr.Name - } - } - - if tr.err != nil { - tr.err = ErrHeader - return nil - } - - // Maximum value of hdr.Size is 64 GB (12 octal digits), - // so there's no risk of int64 overflowing. - nb := int64(hdr.Size) - tr.pad = -nb & (blockSize - 1) // blockSize is a power of two - - // Set the current file reader. - tr.curr = ®FileReader{r: tr.r, nb: nb} - - // Check for old GNU sparse format entry. - if hdr.Typeflag == TypeGNUSparse { - // Get the real size of the file. - hdr.Size = tr.octal(header[483:495]) - - // Read the sparse map. - sp := tr.readOldGNUSparseMap(header) - if tr.err != nil { - return nil - } - // Current file is a GNU sparse file. Update the current file reader. - tr.curr = &sparseFileReader{rfr: tr.curr.(*regFileReader), sp: sp, tot: hdr.Size} - } - - return hdr -} - -// A sparseEntry holds a single entry in a sparse file's sparse map. -// A sparse entry indicates the offset and size in a sparse file of a -// block of data. -type sparseEntry struct { - offset int64 - numBytes int64 -} - -// readOldGNUSparseMap reads the sparse map as stored in the old GNU sparse format. -// The sparse map is stored in the tar header if it's small enough. If it's larger than four entries, -// then one or more extension headers are used to store the rest of the sparse map. -func (tr *Reader) readOldGNUSparseMap(header []byte) []sparseEntry { - isExtended := header[oldGNUSparseMainHeaderIsExtendedOffset] != 0 - spCap := oldGNUSparseMainHeaderNumEntries - if isExtended { - spCap += oldGNUSparseExtendedHeaderNumEntries - } - sp := make([]sparseEntry, 0, spCap) - s := slicer(header[oldGNUSparseMainHeaderOffset:]) - - // Read the four entries from the main tar header - for i := 0; i < oldGNUSparseMainHeaderNumEntries; i++ { - offset := tr.octal(s.next(oldGNUSparseOffsetSize)) - numBytes := tr.octal(s.next(oldGNUSparseNumBytesSize)) - if tr.err != nil { - tr.err = ErrHeader - return nil - } - if offset == 0 && numBytes == 0 { - break - } - sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes}) - } - - for isExtended { - // There are more entries. Read an extension header and parse its entries. - sparseHeader := make([]byte, blockSize) - if _, tr.err = io.ReadFull(tr.r, sparseHeader); tr.err != nil { - return nil - } - isExtended = sparseHeader[oldGNUSparseExtendedHeaderIsExtendedOffset] != 0 - s = slicer(sparseHeader) - for i := 0; i < oldGNUSparseExtendedHeaderNumEntries; i++ { - offset := tr.octal(s.next(oldGNUSparseOffsetSize)) - numBytes := tr.octal(s.next(oldGNUSparseNumBytesSize)) - if tr.err != nil { - tr.err = ErrHeader - return nil - } - if offset == 0 && numBytes == 0 { - break - } - sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes}) - } - } - return sp -} - -// readGNUSparseMap1x0 reads the sparse map as stored in GNU's PAX sparse format version 1.0. -// The sparse map is stored just before the file data and padded out to the nearest block boundary. -func readGNUSparseMap1x0(r io.Reader) ([]sparseEntry, error) { - buf := make([]byte, 2*blockSize) - sparseHeader := buf[:blockSize] - - // readDecimal is a helper function to read a decimal integer from the sparse map - // while making sure to read from the file in blocks of size blockSize - readDecimal := func() (int64, error) { - // Look for newline - nl := bytes.IndexByte(sparseHeader, '\n') - if nl == -1 { - if len(sparseHeader) >= blockSize { - // This is an error - return 0, ErrHeader - } - oldLen := len(sparseHeader) - newLen := oldLen + blockSize - if cap(sparseHeader) < newLen { - // There's more header, but we need to make room for the next block - copy(buf, sparseHeader) - sparseHeader = buf[:newLen] - } else { - // There's more header, and we can just reslice - sparseHeader = sparseHeader[:newLen] - } - - // Now that sparseHeader is large enough, read next block - if _, err := io.ReadFull(r, sparseHeader[oldLen:newLen]); err != nil { - return 0, err - } - - // Look for a newline in the new data - nl = bytes.IndexByte(sparseHeader[oldLen:newLen], '\n') - if nl == -1 { - // This is an error - return 0, ErrHeader - } - nl += oldLen // We want the position from the beginning - } - // Now that we've found a newline, read a number - n, err := strconv.ParseInt(string(sparseHeader[:nl]), 10, 0) - if err != nil { - return 0, ErrHeader - } - - // Update sparseHeader to consume this number - sparseHeader = sparseHeader[nl+1:] - return n, nil - } - - // Read the first block - if _, err := io.ReadFull(r, sparseHeader); err != nil { - return nil, err - } - - // The first line contains the number of entries - numEntries, err := readDecimal() - if err != nil { - return nil, err - } - - // Read all the entries - sp := make([]sparseEntry, 0, numEntries) - for i := int64(0); i < numEntries; i++ { - // Read the offset - offset, err := readDecimal() - if err != nil { - return nil, err - } - // Read numBytes - numBytes, err := readDecimal() - if err != nil { - return nil, err - } - - sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes}) - } - - return sp, nil -} - -// readGNUSparseMap0x1 reads the sparse map as stored in GNU's PAX sparse format version 0.1. -// The sparse map is stored in the PAX headers. -func readGNUSparseMap0x1(headers map[string]string) ([]sparseEntry, error) { - // Get number of entries - numEntriesStr, ok := headers[paxGNUSparseNumBlocks] - if !ok { - return nil, ErrHeader - } - numEntries, err := strconv.ParseInt(numEntriesStr, 10, 0) - if err != nil { - return nil, ErrHeader - } - - sparseMap := strings.Split(headers[paxGNUSparseMap], ",") - - // There should be two numbers in sparseMap for each entry - if int64(len(sparseMap)) != 2*numEntries { - return nil, ErrHeader - } - - // Loop through the entries in the sparse map - sp := make([]sparseEntry, 0, numEntries) - for i := int64(0); i < numEntries; i++ { - offset, err := strconv.ParseInt(sparseMap[2*i], 10, 0) - if err != nil { - return nil, ErrHeader - } - numBytes, err := strconv.ParseInt(sparseMap[2*i+1], 10, 0) - if err != nil { - return nil, ErrHeader - } - sp = append(sp, sparseEntry{offset: offset, numBytes: numBytes}) - } - - return sp, nil -} - -// numBytes returns the number of bytes left to read in the current file's entry -// in the tar archive, or 0 if there is no current file. -func (tr *Reader) numBytes() int64 { - if tr.curr == nil { - // No current file, so no bytes - return 0 - } - return tr.curr.numBytes() -} - -// Read reads from the current entry in the tar archive. -// It returns 0, io.EOF when it reaches the end of that entry, -// until Next is called to advance to the next entry. -func (tr *Reader) Read(b []byte) (n int, err error) { - if tr.curr == nil { - return 0, io.EOF - } - n, err = tr.curr.Read(b) - if err != nil && err != io.EOF { - tr.err = err - } - return -} - -func (rfr *regFileReader) Read(b []byte) (n int, err error) { - if rfr.nb == 0 { - // file consumed - return 0, io.EOF - } - if int64(len(b)) > rfr.nb { - b = b[0:rfr.nb] - } - n, err = rfr.r.Read(b) - rfr.nb -= int64(n) - - if err == io.EOF && rfr.nb > 0 { - err = io.ErrUnexpectedEOF - } - return -} - -// numBytes returns the number of bytes left to read in the file's data in the tar archive. -func (rfr *regFileReader) numBytes() int64 { - return rfr.nb -} - -// readHole reads a sparse file hole ending at offset toOffset -func (sfr *sparseFileReader) readHole(b []byte, toOffset int64) int { - n64 := toOffset - sfr.pos - if n64 > int64(len(b)) { - n64 = int64(len(b)) - } - n := int(n64) - for i := 0; i < n; i++ { - b[i] = 0 - } - sfr.pos += n64 - return n -} - -// Read reads the sparse file data in expanded form. -func (sfr *sparseFileReader) Read(b []byte) (n int, err error) { - if len(sfr.sp) == 0 { - // No more data fragments to read from. - if sfr.pos < sfr.tot { - // We're in the last hole - n = sfr.readHole(b, sfr.tot) - return - } - // Otherwise, we're at the end of the file - return 0, io.EOF - } - if sfr.pos < sfr.sp[0].offset { - // We're in a hole - n = sfr.readHole(b, sfr.sp[0].offset) - return - } - - // We're not in a hole, so we'll read from the next data fragment - posInFragment := sfr.pos - sfr.sp[0].offset - bytesLeft := sfr.sp[0].numBytes - posInFragment - if int64(len(b)) > bytesLeft { - b = b[0:bytesLeft] - } - - n, err = sfr.rfr.Read(b) - sfr.pos += int64(n) - - if int64(n) == bytesLeft { - // We're done with this fragment - sfr.sp = sfr.sp[1:] - } - - if err == io.EOF && sfr.pos < sfr.tot { - // We reached the end of the last fragment's data, but there's a final hole - err = nil - } - return -} - -// numBytes returns the number of bytes left to read in the sparse file's -// sparse-encoded data in the tar archive. -func (sfr *sparseFileReader) numBytes() int64 { - return sfr.rfr.nb -} diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go deleted file mode 100644 index 9601ffe45..000000000 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/reader_test.go +++ /dev/null @@ -1,743 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tar - -import ( - "bytes" - "crypto/md5" - "fmt" - "io" - "io/ioutil" - "os" - "reflect" - "strings" - "testing" - "time" -) - -type untarTest struct { - file string - headers []*Header - cksums []string -} - -var gnuTarTest = &untarTest{ - file: "testdata/gnu.tar", - headers: []*Header{ - { - Name: "small.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 5, - ModTime: time.Unix(1244428340, 0), - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - { - Name: "small2.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 11, - ModTime: time.Unix(1244436044, 0), - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - }, - cksums: []string{ - "e38b27eaccb4391bdec553a7f3ae6b2f", - "c65bd2e50a56a2138bf1716f2fd56fe9", - }, -} - -var sparseTarTest = &untarTest{ - file: "testdata/sparse-formats.tar", - headers: []*Header{ - { - Name: "sparse-gnu", - Mode: 420, - Uid: 1000, - Gid: 1000, - Size: 200, - ModTime: time.Unix(1392395740, 0), - Typeflag: 0x53, - Linkname: "", - Uname: "david", - Gname: "david", - Devmajor: 0, - Devminor: 0, - }, - { - Name: "sparse-posix-0.0", - Mode: 420, - Uid: 1000, - Gid: 1000, - Size: 200, - ModTime: time.Unix(1392342187, 0), - Typeflag: 0x30, - Linkname: "", - Uname: "david", - Gname: "david", - Devmajor: 0, - Devminor: 0, - }, - { - Name: "sparse-posix-0.1", - Mode: 420, - Uid: 1000, - Gid: 1000, - Size: 200, - ModTime: time.Unix(1392340456, 0), - Typeflag: 0x30, - Linkname: "", - Uname: "david", - Gname: "david", - Devmajor: 0, - Devminor: 0, - }, - { - Name: "sparse-posix-1.0", - Mode: 420, - Uid: 1000, - Gid: 1000, - Size: 200, - ModTime: time.Unix(1392337404, 0), - Typeflag: 0x30, - Linkname: "", - Uname: "david", - Gname: "david", - Devmajor: 0, - Devminor: 0, - }, - { - Name: "end", - Mode: 420, - Uid: 1000, - Gid: 1000, - Size: 4, - ModTime: time.Unix(1392398319, 0), - Typeflag: 0x30, - Linkname: "", - Uname: "david", - Gname: "david", - Devmajor: 0, - Devminor: 0, - }, - }, - cksums: []string{ - "6f53234398c2449fe67c1812d993012f", - "6f53234398c2449fe67c1812d993012f", - "6f53234398c2449fe67c1812d993012f", - "6f53234398c2449fe67c1812d993012f", - "b0061974914468de549a2af8ced10316", - }, -} - -var untarTests = []*untarTest{ - gnuTarTest, - sparseTarTest, - { - file: "testdata/star.tar", - headers: []*Header{ - { - Name: "small.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 5, - ModTime: time.Unix(1244592783, 0), - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - AccessTime: time.Unix(1244592783, 0), - ChangeTime: time.Unix(1244592783, 0), - }, - { - Name: "small2.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 11, - ModTime: time.Unix(1244592783, 0), - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - AccessTime: time.Unix(1244592783, 0), - ChangeTime: time.Unix(1244592783, 0), - }, - }, - }, - { - file: "testdata/v7.tar", - headers: []*Header{ - { - Name: "small.txt", - Mode: 0444, - Uid: 73025, - Gid: 5000, - Size: 5, - ModTime: time.Unix(1244593104, 0), - Typeflag: '\x00', - }, - { - Name: "small2.txt", - Mode: 0444, - Uid: 73025, - Gid: 5000, - Size: 11, - ModTime: time.Unix(1244593104, 0), - Typeflag: '\x00', - }, - }, - }, - { - file: "testdata/pax.tar", - headers: []*Header{ - { - Name: "a/123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100", - Mode: 0664, - Uid: 1000, - Gid: 1000, - Uname: "shane", - Gname: "shane", - Size: 7, - ModTime: time.Unix(1350244992, 23960108), - ChangeTime: time.Unix(1350244992, 23960108), - AccessTime: time.Unix(1350244992, 23960108), - Typeflag: TypeReg, - }, - { - Name: "a/b", - Mode: 0777, - Uid: 1000, - Gid: 1000, - Uname: "shane", - Gname: "shane", - Size: 0, - ModTime: time.Unix(1350266320, 910238425), - ChangeTime: time.Unix(1350266320, 910238425), - AccessTime: time.Unix(1350266320, 910238425), - Typeflag: TypeSymlink, - Linkname: "123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100", - }, - }, - }, - { - file: "testdata/nil-uid.tar", // golang.org/issue/5290 - headers: []*Header{ - { - Name: "P1050238.JPG.log", - Mode: 0664, - Uid: 0, - Gid: 0, - Size: 14, - ModTime: time.Unix(1365454838, 0), - Typeflag: TypeReg, - Linkname: "", - Uname: "eyefi", - Gname: "eyefi", - Devmajor: 0, - Devminor: 0, - }, - }, - }, - { - file: "testdata/xattrs.tar", - headers: []*Header{ - { - Name: "small.txt", - Mode: 0644, - Uid: 1000, - Gid: 10, - Size: 5, - ModTime: time.Unix(1386065770, 448252320), - Typeflag: '0', - Uname: "alex", - Gname: "wheel", - AccessTime: time.Unix(1389782991, 419875220), - ChangeTime: time.Unix(1389782956, 794414986), - Xattrs: map[string]string{ - "user.key": "value", - "user.key2": "value2", - // Interestingly, selinux encodes the terminating null inside the xattr - "security.selinux": "unconfined_u:object_r:default_t:s0\x00", - }, - }, - { - Name: "small2.txt", - Mode: 0644, - Uid: 1000, - Gid: 10, - Size: 11, - ModTime: time.Unix(1386065770, 449252304), - Typeflag: '0', - Uname: "alex", - Gname: "wheel", - AccessTime: time.Unix(1389782991, 419875220), - ChangeTime: time.Unix(1386065770, 449252304), - Xattrs: map[string]string{ - "security.selinux": "unconfined_u:object_r:default_t:s0\x00", - }, - }, - }, - }, -} - -func TestReader(t *testing.T) { -testLoop: - for i, test := range untarTests { - f, err := os.Open(test.file) - if err != nil { - t.Errorf("test %d: Unexpected error: %v", i, err) - continue - } - defer f.Close() - tr := NewReader(f) - for j, header := range test.headers { - hdr, err := tr.Next() - if err != nil || hdr == nil { - t.Errorf("test %d, entry %d: Didn't get entry: %v", i, j, err) - f.Close() - continue testLoop - } - if !reflect.DeepEqual(*hdr, *header) { - t.Errorf("test %d, entry %d: Incorrect header:\nhave %+v\nwant %+v", - i, j, *hdr, *header) - } - } - hdr, err := tr.Next() - if err == io.EOF { - continue testLoop - } - if hdr != nil || err != nil { - t.Errorf("test %d: Unexpected entry or error: hdr=%v err=%v", i, hdr, err) - } - } -} - -func TestPartialRead(t *testing.T) { - f, err := os.Open("testdata/gnu.tar") - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer f.Close() - - tr := NewReader(f) - - // Read the first four bytes; Next() should skip the last byte. - hdr, err := tr.Next() - if err != nil || hdr == nil { - t.Fatalf("Didn't get first file: %v", err) - } - buf := make([]byte, 4) - if _, err := io.ReadFull(tr, buf); err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if expected := []byte("Kilt"); !bytes.Equal(buf, expected) { - t.Errorf("Contents = %v, want %v", buf, expected) - } - - // Second file - hdr, err = tr.Next() - if err != nil || hdr == nil { - t.Fatalf("Didn't get second file: %v", err) - } - buf = make([]byte, 6) - if _, err := io.ReadFull(tr, buf); err != nil { - t.Fatalf("Unexpected error: %v", err) - } - if expected := []byte("Google"); !bytes.Equal(buf, expected) { - t.Errorf("Contents = %v, want %v", buf, expected) - } -} - -func TestIncrementalRead(t *testing.T) { - test := gnuTarTest - f, err := os.Open(test.file) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer f.Close() - - tr := NewReader(f) - - headers := test.headers - cksums := test.cksums - nread := 0 - - // loop over all files - for ; ; nread++ { - hdr, err := tr.Next() - if hdr == nil || err == io.EOF { - break - } - - // check the header - if !reflect.DeepEqual(*hdr, *headers[nread]) { - t.Errorf("Incorrect header:\nhave %+v\nwant %+v", - *hdr, headers[nread]) - } - - // read file contents in little chunks EOF, - // checksumming all the way - h := md5.New() - rdbuf := make([]uint8, 8) - for { - nr, err := tr.Read(rdbuf) - if err == io.EOF { - break - } - if err != nil { - t.Errorf("Read: unexpected error %v\n", err) - break - } - h.Write(rdbuf[0:nr]) - } - // verify checksum - have := fmt.Sprintf("%x", h.Sum(nil)) - want := cksums[nread] - if want != have { - t.Errorf("Bad checksum on file %s:\nhave %+v\nwant %+v", hdr.Name, have, want) - } - } - if nread != len(headers) { - t.Errorf("Didn't process all files\nexpected: %d\nprocessed %d\n", len(headers), nread) - } -} - -func TestNonSeekable(t *testing.T) { - test := gnuTarTest - f, err := os.Open(test.file) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer f.Close() - - type readerOnly struct { - io.Reader - } - tr := NewReader(readerOnly{f}) - nread := 0 - - for ; ; nread++ { - _, err := tr.Next() - if err == io.EOF { - break - } - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - } - - if nread != len(test.headers) { - t.Errorf("Didn't process all files\nexpected: %d\nprocessed %d\n", len(test.headers), nread) - } -} - -func TestParsePAXHeader(t *testing.T) { - paxTests := [][3]string{ - {"a", "a=name", "10 a=name\n"}, // Test case involving multiple acceptable lengths - {"a", "a=name", "9 a=name\n"}, // Test case involving multiple acceptable length - {"mtime", "mtime=1350244992.023960108", "30 mtime=1350244992.023960108\n"}} - for _, test := range paxTests { - key, expected, raw := test[0], test[1], test[2] - reader := bytes.NewReader([]byte(raw)) - headers, err := parsePAX(reader) - if err != nil { - t.Errorf("Couldn't parse correctly formatted headers: %v", err) - continue - } - if strings.EqualFold(headers[key], expected) { - t.Errorf("mtime header incorrectly parsed: got %s, wanted %s", headers[key], expected) - continue - } - trailer := make([]byte, 100) - n, err := reader.Read(trailer) - if err != io.EOF || n != 0 { - t.Error("Buffer wasn't consumed") - } - } - badHeader := bytes.NewReader([]byte("3 somelongkey=")) - if _, err := parsePAX(badHeader); err != ErrHeader { - t.Fatal("Unexpected success when parsing bad header") - } -} - -func TestParsePAXTime(t *testing.T) { - // Some valid PAX time values - timestamps := map[string]time.Time{ - "1350244992.023960108": time.Unix(1350244992, 23960108), // The common case - "1350244992.02396010": time.Unix(1350244992, 23960100), // Lower precision value - "1350244992.0239601089": time.Unix(1350244992, 23960108), // Higher precision value - "1350244992": time.Unix(1350244992, 0), // Low precision value - } - for input, expected := range timestamps { - ts, err := parsePAXTime(input) - if err != nil { - t.Fatal(err) - } - if !ts.Equal(expected) { - t.Fatalf("Time parsing failure %s %s", ts, expected) - } - } -} - -func TestMergePAX(t *testing.T) { - hdr := new(Header) - // Test a string, integer, and time based value. - headers := map[string]string{ - "path": "a/b/c", - "uid": "1000", - "mtime": "1350244992.023960108", - } - err := mergePAX(hdr, headers) - if err != nil { - t.Fatal(err) - } - want := &Header{ - Name: "a/b/c", - Uid: 1000, - ModTime: time.Unix(1350244992, 23960108), - } - if !reflect.DeepEqual(hdr, want) { - t.Errorf("incorrect merge: got %+v, want %+v", hdr, want) - } -} - -func TestSparseEndToEnd(t *testing.T) { - test := sparseTarTest - f, err := os.Open(test.file) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer f.Close() - - tr := NewReader(f) - - headers := test.headers - cksums := test.cksums - nread := 0 - - // loop over all files - for ; ; nread++ { - hdr, err := tr.Next() - if hdr == nil || err == io.EOF { - break - } - - // check the header - if !reflect.DeepEqual(*hdr, *headers[nread]) { - t.Errorf("Incorrect header:\nhave %+v\nwant %+v", - *hdr, headers[nread]) - } - - // read and checksum the file data - h := md5.New() - _, err = io.Copy(h, tr) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - - // verify checksum - have := fmt.Sprintf("%x", h.Sum(nil)) - want := cksums[nread] - if want != have { - t.Errorf("Bad checksum on file %s:\nhave %+v\nwant %+v", hdr.Name, have, want) - } - } - if nread != len(headers) { - t.Errorf("Didn't process all files\nexpected: %d\nprocessed %d\n", len(headers), nread) - } -} - -type sparseFileReadTest struct { - sparseData []byte - sparseMap []sparseEntry - realSize int64 - expected []byte -} - -var sparseFileReadTests = []sparseFileReadTest{ - { - sparseData: []byte("abcde"), - sparseMap: []sparseEntry{ - {offset: 0, numBytes: 2}, - {offset: 5, numBytes: 3}, - }, - realSize: 8, - expected: []byte("ab\x00\x00\x00cde"), - }, - { - sparseData: []byte("abcde"), - sparseMap: []sparseEntry{ - {offset: 0, numBytes: 2}, - {offset: 5, numBytes: 3}, - }, - realSize: 10, - expected: []byte("ab\x00\x00\x00cde\x00\x00"), - }, - { - sparseData: []byte("abcde"), - sparseMap: []sparseEntry{ - {offset: 1, numBytes: 3}, - {offset: 6, numBytes: 2}, - }, - realSize: 8, - expected: []byte("\x00abc\x00\x00de"), - }, - { - sparseData: []byte("abcde"), - sparseMap: []sparseEntry{ - {offset: 1, numBytes: 3}, - {offset: 6, numBytes: 2}, - }, - realSize: 10, - expected: []byte("\x00abc\x00\x00de\x00\x00"), - }, - { - sparseData: []byte(""), - sparseMap: nil, - realSize: 2, - expected: []byte("\x00\x00"), - }, -} - -func TestSparseFileReader(t *testing.T) { - for i, test := range sparseFileReadTests { - r := bytes.NewReader(test.sparseData) - nb := int64(r.Len()) - sfr := &sparseFileReader{ - rfr: ®FileReader{r: r, nb: nb}, - sp: test.sparseMap, - pos: 0, - tot: test.realSize, - } - if sfr.numBytes() != nb { - t.Errorf("test %d: Before reading, sfr.numBytes() = %d, want %d", i, sfr.numBytes(), nb) - } - buf, err := ioutil.ReadAll(sfr) - if err != nil { - t.Errorf("test %d: Unexpected error: %v", i, err) - } - if e := test.expected; !bytes.Equal(buf, e) { - t.Errorf("test %d: Contents = %v, want %v", i, buf, e) - } - if sfr.numBytes() != 0 { - t.Errorf("test %d: After draining the reader, numBytes() was nonzero", i) - } - } -} - -func TestSparseIncrementalRead(t *testing.T) { - sparseMap := []sparseEntry{{10, 2}} - sparseData := []byte("Go") - expected := "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Go\x00\x00\x00\x00\x00\x00\x00\x00" - - r := bytes.NewReader(sparseData) - nb := int64(r.Len()) - sfr := &sparseFileReader{ - rfr: ®FileReader{r: r, nb: nb}, - sp: sparseMap, - pos: 0, - tot: int64(len(expected)), - } - - // We'll read the data 6 bytes at a time, with a hole of size 10 at - // the beginning and one of size 8 at the end. - var outputBuf bytes.Buffer - buf := make([]byte, 6) - for { - n, err := sfr.Read(buf) - if err == io.EOF { - break - } - if err != nil { - t.Errorf("Read: unexpected error %v\n", err) - } - if n > 0 { - _, err := outputBuf.Write(buf[:n]) - if err != nil { - t.Errorf("Write: unexpected error %v\n", err) - } - } - } - got := outputBuf.String() - if got != expected { - t.Errorf("Contents = %v, want %v", got, expected) - } -} - -func TestReadGNUSparseMap0x1(t *testing.T) { - headers := map[string]string{ - paxGNUSparseNumBlocks: "4", - paxGNUSparseMap: "0,5,10,5,20,5,30,5", - } - expected := []sparseEntry{ - {offset: 0, numBytes: 5}, - {offset: 10, numBytes: 5}, - {offset: 20, numBytes: 5}, - {offset: 30, numBytes: 5}, - } - - sp, err := readGNUSparseMap0x1(headers) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - if !reflect.DeepEqual(sp, expected) { - t.Errorf("Incorrect sparse map: got %v, wanted %v", sp, expected) - } -} - -func TestReadGNUSparseMap1x0(t *testing.T) { - // This test uses lots of holes so the sparse header takes up more than two blocks - numEntries := 100 - expected := make([]sparseEntry, 0, numEntries) - sparseMap := new(bytes.Buffer) - - fmt.Fprintf(sparseMap, "%d\n", numEntries) - for i := 0; i < numEntries; i++ { - offset := int64(2048 * i) - numBytes := int64(1024) - expected = append(expected, sparseEntry{offset: offset, numBytes: numBytes}) - fmt.Fprintf(sparseMap, "%d\n%d\n", offset, numBytes) - } - - // Make the header the smallest multiple of blockSize that fits the sparseMap - headerBlocks := (sparseMap.Len() + blockSize - 1) / blockSize - bufLen := blockSize * headerBlocks - buf := make([]byte, bufLen) - copy(buf, sparseMap.Bytes()) - - // Get an reader to read the sparse map - r := bytes.NewReader(buf) - - // Read the sparse map - sp, err := readGNUSparseMap1x0(r) - if err != nil { - t.Errorf("Unexpected error: %v", err) - } - if !reflect.DeepEqual(sp, expected) { - t.Errorf("Incorrect sparse map: got %v, wanted %v", sp, expected) - } -} - -func TestUninitializedRead(t *testing.T) { - test := gnuTarTest - f, err := os.Open(test.file) - if err != nil { - t.Fatalf("Unexpected error: %v", err) - } - defer f.Close() - - tr := NewReader(f) - _, err = tr.Read([]byte{}) - if err == nil || err != io.EOF { - t.Errorf("Unexpected error: %v, wanted %v", err, io.EOF) - } - -} diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atim.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atim.go deleted file mode 100644 index cf9cc79c5..000000000 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atim.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build linux dragonfly openbsd solaris - -package tar - -import ( - "syscall" - "time" -) - -func statAtime(st *syscall.Stat_t) time.Time { - return time.Unix(st.Atim.Unix()) -} - -func statCtime(st *syscall.Stat_t) time.Time { - return time.Unix(st.Ctim.Unix()) -} diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atimespec.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atimespec.go deleted file mode 100644 index 6f17dbe30..000000000 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_atimespec.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build darwin freebsd netbsd - -package tar - -import ( - "syscall" - "time" -) - -func statAtime(st *syscall.Stat_t) time.Time { - return time.Unix(st.Atimespec.Unix()) -} - -func statCtime(st *syscall.Stat_t) time.Time { - return time.Unix(st.Ctimespec.Unix()) -} diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_unix.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_unix.go deleted file mode 100644 index cb843db4c..000000000 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/stat_unix.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build linux darwin dragonfly freebsd openbsd netbsd solaris - -package tar - -import ( - "os" - "syscall" -) - -func init() { - sysStat = statUnix -} - -func statUnix(fi os.FileInfo, h *Header) error { - sys, ok := fi.Sys().(*syscall.Stat_t) - if !ok { - return nil - } - h.Uid = int(sys.Uid) - h.Gid = int(sys.Gid) - // TODO(bradfitz): populate username & group. os/user - // doesn't cache LookupId lookups, and lacks group - // lookup functions. - h.AccessTime = statAtime(sys) - h.ChangeTime = statCtime(sys) - // TODO(bradfitz): major/minor device numbers? - return nil -} diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/tar_test.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/tar_test.go deleted file mode 100644 index ed333f3ea..000000000 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/tar_test.go +++ /dev/null @@ -1,284 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tar - -import ( - "bytes" - "io/ioutil" - "os" - "path" - "reflect" - "strings" - "testing" - "time" -) - -func TestFileInfoHeader(t *testing.T) { - fi, err := os.Stat("testdata/small.txt") - if err != nil { - t.Fatal(err) - } - h, err := FileInfoHeader(fi, "") - if err != nil { - t.Fatalf("FileInfoHeader: %v", err) - } - if g, e := h.Name, "small.txt"; g != e { - t.Errorf("Name = %q; want %q", g, e) - } - if g, e := h.Mode, int64(fi.Mode().Perm())|c_ISREG; g != e { - t.Errorf("Mode = %#o; want %#o", g, e) - } - if g, e := h.Size, int64(5); g != e { - t.Errorf("Size = %v; want %v", g, e) - } - if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) { - t.Errorf("ModTime = %v; want %v", g, e) - } - // FileInfoHeader should error when passing nil FileInfo - if _, err := FileInfoHeader(nil, ""); err == nil { - t.Fatalf("Expected error when passing nil to FileInfoHeader") - } -} - -func TestFileInfoHeaderDir(t *testing.T) { - fi, err := os.Stat("testdata") - if err != nil { - t.Fatal(err) - } - h, err := FileInfoHeader(fi, "") - if err != nil { - t.Fatalf("FileInfoHeader: %v", err) - } - if g, e := h.Name, "testdata/"; g != e { - t.Errorf("Name = %q; want %q", g, e) - } - // Ignoring c_ISGID for golang.org/issue/4867 - if g, e := h.Mode&^c_ISGID, int64(fi.Mode().Perm())|c_ISDIR; g != e { - t.Errorf("Mode = %#o; want %#o", g, e) - } - if g, e := h.Size, int64(0); g != e { - t.Errorf("Size = %v; want %v", g, e) - } - if g, e := h.ModTime, fi.ModTime(); !g.Equal(e) { - t.Errorf("ModTime = %v; want %v", g, e) - } -} - -func TestFileInfoHeaderSymlink(t *testing.T) { - h, err := FileInfoHeader(symlink{}, "some-target") - if err != nil { - t.Fatal(err) - } - if g, e := h.Name, "some-symlink"; g != e { - t.Errorf("Name = %q; want %q", g, e) - } - if g, e := h.Linkname, "some-target"; g != e { - t.Errorf("Linkname = %q; want %q", g, e) - } -} - -type symlink struct{} - -func (symlink) Name() string { return "some-symlink" } -func (symlink) Size() int64 { return 0 } -func (symlink) Mode() os.FileMode { return os.ModeSymlink } -func (symlink) ModTime() time.Time { return time.Time{} } -func (symlink) IsDir() bool { return false } -func (symlink) Sys() interface{} { return nil } - -func TestRoundTrip(t *testing.T) { - data := []byte("some file contents") - - var b bytes.Buffer - tw := NewWriter(&b) - hdr := &Header{ - Name: "file.txt", - Uid: 1 << 21, // too big for 8 octal digits - Size: int64(len(data)), - ModTime: time.Now(), - } - // tar only supports second precision. - hdr.ModTime = hdr.ModTime.Add(-time.Duration(hdr.ModTime.Nanosecond()) * time.Nanosecond) - if err := tw.WriteHeader(hdr); err != nil { - t.Fatalf("tw.WriteHeader: %v", err) - } - if _, err := tw.Write(data); err != nil { - t.Fatalf("tw.Write: %v", err) - } - if err := tw.Close(); err != nil { - t.Fatalf("tw.Close: %v", err) - } - - // Read it back. - tr := NewReader(&b) - rHdr, err := tr.Next() - if err != nil { - t.Fatalf("tr.Next: %v", err) - } - if !reflect.DeepEqual(rHdr, hdr) { - t.Errorf("Header mismatch.\n got %+v\nwant %+v", rHdr, hdr) - } - rData, err := ioutil.ReadAll(tr) - if err != nil { - t.Fatalf("Read: %v", err) - } - if !bytes.Equal(rData, data) { - t.Errorf("Data mismatch.\n got %q\nwant %q", rData, data) - } -} - -type headerRoundTripTest struct { - h *Header - fm os.FileMode -} - -func TestHeaderRoundTrip(t *testing.T) { - golden := []headerRoundTripTest{ - // regular file. - { - h: &Header{ - Name: "test.txt", - Mode: 0644 | c_ISREG, - Size: 12, - ModTime: time.Unix(1360600916, 0), - Typeflag: TypeReg, - }, - fm: 0644, - }, - // hard link. - { - h: &Header{ - Name: "hard.txt", - Mode: 0644 | c_ISLNK, - Size: 0, - ModTime: time.Unix(1360600916, 0), - Typeflag: TypeLink, - }, - fm: 0644 | os.ModeSymlink, - }, - // symbolic link. - { - h: &Header{ - Name: "link.txt", - Mode: 0777 | c_ISLNK, - Size: 0, - ModTime: time.Unix(1360600852, 0), - Typeflag: TypeSymlink, - }, - fm: 0777 | os.ModeSymlink, - }, - // character device node. - { - h: &Header{ - Name: "dev/null", - Mode: 0666 | c_ISCHR, - Size: 0, - ModTime: time.Unix(1360578951, 0), - Typeflag: TypeChar, - }, - fm: 0666 | os.ModeDevice | os.ModeCharDevice, - }, - // block device node. - { - h: &Header{ - Name: "dev/sda", - Mode: 0660 | c_ISBLK, - Size: 0, - ModTime: time.Unix(1360578954, 0), - Typeflag: TypeBlock, - }, - fm: 0660 | os.ModeDevice, - }, - // directory. - { - h: &Header{ - Name: "dir/", - Mode: 0755 | c_ISDIR, - Size: 0, - ModTime: time.Unix(1360601116, 0), - Typeflag: TypeDir, - }, - fm: 0755 | os.ModeDir, - }, - // fifo node. - { - h: &Header{ - Name: "dev/initctl", - Mode: 0600 | c_ISFIFO, - Size: 0, - ModTime: time.Unix(1360578949, 0), - Typeflag: TypeFifo, - }, - fm: 0600 | os.ModeNamedPipe, - }, - // setuid. - { - h: &Header{ - Name: "bin/su", - Mode: 0755 | c_ISREG | c_ISUID, - Size: 23232, - ModTime: time.Unix(1355405093, 0), - Typeflag: TypeReg, - }, - fm: 0755 | os.ModeSetuid, - }, - // setguid. - { - h: &Header{ - Name: "group.txt", - Mode: 0750 | c_ISREG | c_ISGID, - Size: 0, - ModTime: time.Unix(1360602346, 0), - Typeflag: TypeReg, - }, - fm: 0750 | os.ModeSetgid, - }, - // sticky. - { - h: &Header{ - Name: "sticky.txt", - Mode: 0600 | c_ISREG | c_ISVTX, - Size: 7, - ModTime: time.Unix(1360602540, 0), - Typeflag: TypeReg, - }, - fm: 0600 | os.ModeSticky, - }, - } - - for i, g := range golden { - fi := g.h.FileInfo() - h2, err := FileInfoHeader(fi, "") - if err != nil { - t.Error(err) - continue - } - if strings.Contains(fi.Name(), "/") { - t.Errorf("FileInfo of %q contains slash: %q", g.h.Name, fi.Name()) - } - name := path.Base(g.h.Name) - if fi.IsDir() { - name += "/" - } - if got, want := h2.Name, name; got != want { - t.Errorf("i=%d: Name: got %v, want %v", i, got, want) - } - if got, want := h2.Size, g.h.Size; got != want { - t.Errorf("i=%d: Size: got %v, want %v", i, got, want) - } - if got, want := h2.Mode, g.h.Mode; got != want { - t.Errorf("i=%d: Mode: got %o, want %o", i, got, want) - } - if got, want := fi.Mode(), g.fm; got != want { - t.Errorf("i=%d: fi.Mode: got %o, want %o", i, got, want) - } - if got, want := h2.ModTime, g.h.ModTime; got != want { - t.Errorf("i=%d: ModTime: got %v, want %v", i, got, want) - } - if sysh, ok := fi.Sys().(*Header); !ok || sysh != g.h { - t.Errorf("i=%d: Sys didn't return original *Header", i) - } - } -} diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/gnu.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/gnu.tar deleted file mode 100644 index fc899dc8dc2ad9952f5c5f67a0c76ca2d87249e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3072 zcmeHH%L>9U5Ztq0(Jv@FdDKtv;8zq|ijXv5BIw^6DOh^2UK)_HbK1>@VQ0c5`qsHR zJrb1zXEcV16&lMRW}rdtKd=NSXg->JkvP|Esp4`g&CK_h+FMmo7oR?iU7RP&svn2t z!9Ke4)upeR_aRYKtT+(g`B!B>fS>t?p7IZ9V9LKWlK+)w+iY|SVQ_tY3I4Ddrx1w) M;($0H4*b6ZFWOBnBLDyZ diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/nil-uid.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/nil-uid.tar deleted file mode 100644 index cc9cfaa33cc5de0a28b4183c1705d801f788c96a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1024 zcmWGAG%z(VGPcn33UJrU$xmmX0WbgpGcywmlR@HOU}(l*Xk=(?U}j`)Zf3?{U78BOoLqCLtvwr=Z5b$i&RT%Er#Y zO+ZjcSVUAHn~8MUp(~vBV+cg7LjpEKCCE6D7E@EwTcMv_>l+&bbg`j1Cv0A776ym5t@+ zSt9MDBFtXbKY&m4pMN0f`l~hhD>#q(-`x$5n+q@eEPmAevA;0XTM8XMYkTvSmQ-t5 zkihVw{(qQ#_JjT})&KMa&-FhG0c8or{CPvw|Jf69WL!B2Wa1KoKYcMW6^2fg(@@ia-%40!5$*6oDd81d2cr_`3;w E2V3|JA^-pY diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small.txt b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small.txt deleted file mode 100644 index b249bfc51..000000000 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small.txt +++ /dev/null @@ -1 +0,0 @@ -Kilts \ No newline at end of file diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small2.txt b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small2.txt deleted file mode 100644 index 394ee3ecd..000000000 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/small2.txt +++ /dev/null @@ -1 +0,0 @@ -Google.com diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar deleted file mode 100644 index 8bd4e74d50f9c8961f80a887ab7d6449e032048b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17920 zcmeHO!BXQ!5M{6a3g^xmb&sU64qQV{sZ?#{1DvdPiv%!*Aw}}_dEEjdi|Nre#)hpG zE{}(KnjXC;wbY|&t*;k1>*dFY-EbwpT`b+-m>u zXfjaoe5W44g1VMFbuvaLV|3acePf?HHj7T34f|}^XTyHz*zDR5hW%jJ$GN!K=dPX7 zuwNSXOT&I?*sl!xm0`a!>{o{UdfWbohf`t0wKm47jd5yYoVY#C#(p&HN5g(h+o$d^ z>C~x6+ovLJp9;gi;Rj^+0U3Tkh98jO2W0pG8Gb;9ACTb()boS>@h8I{`Aj1#H@B=dZfDAvtjWMmW;RoC~7Py0M z`m*5%-1CF}@n^#y*zgB7{DBRBV8b8S@CP>hfen8^_^{DnOAo^z5MmhHr;h_0e!zww zu;B-6_yHS!z=j{N;RkH^0r&ji+3`30fen9P!ynl22R8fx0blw!^!(v@`{T)$#0AMUzUr{%bWEKK}dPBZfAtotM&Q)$6}V4GkAAL?ydIxj}Q`3 zJOATY>UD~$8khO$y?3COY_Ib_T!Mz?cSHC~#(oEVI84ue{e9LR^x69SzvU?x#e`$G z`ReZSkBilxf3HuQYO>v9_2tWYd3#C|uKGRxyy zk#CN0vO|t>vO|t?vO|t@vV)g2dr7mGGDo)W_L8o>q-!tf+DkfmNk=c~=p`M!q@$Pg+)H}y zB|Z0&o_k5py`&p2>BdW1A|f+1N!@lEFX<*ndTZ#%;H1d0PWQ;sPWQ<1PWQ+WPxo*$ zCpU9)GbcB5ax*74^K5XIR5u%)rF*!UXXCT<7;fg-2rW5AHbhJJa5K*aY3VWC%(G!y za*S-8mhRzZo{iMfW4M`TW3}WM*$8{;Fcc4%{&{rCCA9dZs{Iw=Go{iJw}H4J9rlMBkscMKka?4V*dGW zC;x{dmiMq8gvD(vpG{xk(ev}2>9_pg&wuy1`g67#*MIt_+k5+eaQ%mN-{S%QM+!~( zxc)<2*YN+U4#l|sv%B)c7PePszGeL<)ZO)vtHtH=w09GsNfox9d|WQBPwAMB1HKi$ z5#I)1l17qNl4g>25`gi0%mT0gEC34-1PB5I0fGQQfKq@`fKq@`fKq@;fJ%T$fJ%T$ zfLefBfLefBfLeekKolSf5Cw<=%mtVWFc)Ahz+8YvfJT5ufJT5u0A#CaDG)Nzv=opE zMO*%@0IdS81gZg+MP*A>0a;*L*S;zQ^1P%)r9keM))iGXNaa8-mb9xN$g|SAj;op= zlS*1t6=X?iT~QSVc~H`#(jdo4>x!y6$YPQf)rV9dQiVt*BGrggBvO?~WSR`0jpG)F zR$z95<=;=bg;QIfR|BZ{k=7%FW59w-S{C9wpVT}I{Ao4pNVj%vb z{pbH+{)aiAzW>2BZdt7HACK|hLCzZHZZvnf_-l0|IXl~}=T~SgCPR@QPL^Kc(9Lpj zv56@U!e<=Br@-+2fA>qk!2KVorji&Q8CK+X>e0g#)6 z@dUuK3}6apYu09*vX znm!5vu=b8Z0L;v^6bQklmI7jCCS}XN6`)n1l|VJX%uKdX6)-c?y7pBeFf)@Dl>##} ztt+Z(U}h#Qst0CfT31vh!CNoVqM~4CrgcSC7r2GAs4|$DXJmbGf$=ejIaDU{qjK;EfgdABYgg9smFU diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/star.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/star.tar deleted file mode 100644 index 59e2d4e604611eeac3e2a0f3d6f71d2623c50449..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3072 zcmeHHT?)e>4DRzz;R#BjQ;)ERouag*479>@u-$$NbG3!-WyoMlUh?xvNIv}HZD&jy zuA!-C5KZlY0Y@bP833Zfm_JQ2M2yBQ2dTK6K{>VDLBV=D{z>IvVF` zUD#xgUGh?F1AikeIW6NnOIrMRGU4UU`62nAWxyx>^STEhN#m{lQEc_E}GZPaA5N&Q|3Z@N=AbgM*P?o{a$k4#V#K6eR)QrKv#MIE( zgh9c8hHiozU0Pg{SOj!ZaYkZZDqIwk0aTWjhA9jefq29K;yD8YhMfGo^t{B}RQ&;E gz@3MSk&&8{lh1`qc2s;c1V%$(Gz3ONV7P_=0FTf|dgec!sXT^AK{$jKc@-kWdUe(&uh-&e0L+xARj zwLzm>LI~3|1sT#R&XkBIzWbfCPrYEK7fr^Q@7vXO;&pw$QCTT3-?&yO+jq(<{6qS`FS_vP zIBhMBjnmsnS~{|C9LMN8#r!W{zj5l&zcE?^U_t*||1zJ{zqInH{-Zy}2$O|c?WSFx zxn8RtM3-UpAJiW`Z@Zar#$ojz)NjtWBfnULUzD=jj5!>iG>O2k{o(=ZAg=$-urC7q zVm{n!{kK`S@p|Vk`q%aFg#nw)bMB-40yAj*%7=F37m@ziFINBH7pTSD@Cfil^^9T6 zxL-iu+Aq)#ev#CF(l2&S@A^eC<`;^e4{ZQ#s9$Y4r}$iP3;;e3V;a&MNN*s$f%FFc H(;N5+1FUK9 diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar deleted file mode 100644 index 5960ee824784ffeacb976a9c648be41b0281508b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmeIuJqp7x3tu-|!r}ytVByrmfae ipO37m$1T~NWs?FFpa2CZKmiI+fC3bt00k&;vcMnFf)<_t diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer.tar deleted file mode 100644 index e6d816ad0775d56d09242d6f5d1dbe56af310a32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3584 zcmeHIK@P$o5ajGDd_l9j6nKIMUt!cVjT92WM1L@81h#LhDgML6Bon)c?rO_kPgyt^3D0fH9$GJM`O*&4VCw= zv#H)UKC-TtzNwGuV$*%C{bm zsdIMLR{C5VZL^vBE!S4cfUeCYt@>GOiAt%sq7tp|_iN{x5cDreh9ME=K+wOCQm`$x j!znSk-v6Dy)}|V_!f*AilYjI7l|Jj-R%ReG@B;%+QQ}au diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/xattrs.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/xattrs.tar deleted file mode 100644 index 9701950edd1f0dc82858b7117136b37391be0b08..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5120 zcmeHJv2KGf5M|~o_yWg1+khiw>d;i}P^nX=$R$ooYd`|ilD{uBAv6g^kxC>6-(uu< zHg^v_-l5r}td>fyRbC(vfcdOQq}Iq(#u+Ja9X?}Dv(|CCVoJF~09ZgF;2a!G7^%~| zYNYoMUQ-rE=5KzzBJ^EKyr-Mx-NQ4gq%k=v3zee}wOxElT`HH-ei(K*xV|_} zC{$GDvDuoW?o>&odUrVuVHkt_w?IH zW3PV_@V!Jxt@A^i>Yrj(>;K=H?5X8!tJS~MYVd#a^`?|QJKb&Uduf~MfN4M7$J!Lr zF40zZMF!9x{tqJ#0F5+;{2!=)=Knre|G(mAKU`hAc#r>!#{V(9d;sW1hxVv7@B_zF ze)#eKF~#1~>@WTI`#+&4`lkel_5U6!N8h^5vRAE8lqGgr9-Ul!p=H1_U>TS&1K)l2 B)fNB% diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer.go deleted file mode 100644 index dafb2cabf..000000000 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer.go +++ /dev/null @@ -1,396 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tar - -// TODO(dsymonds): -// - catch more errors (no first header, etc.) - -import ( - "bytes" - "errors" - "fmt" - "io" - "os" - "path" - "strconv" - "strings" - "time" -) - -var ( - ErrWriteTooLong = errors.New("archive/tar: write too long") - ErrFieldTooLong = errors.New("archive/tar: header field too long") - ErrWriteAfterClose = errors.New("archive/tar: write after close") - errNameTooLong = errors.New("archive/tar: name too long") - errInvalidHeader = errors.New("archive/tar: header field too long or contains invalid values") -) - -// A Writer provides sequential writing of a tar archive in POSIX.1 format. -// A tar archive consists of a sequence of files. -// Call WriteHeader to begin a new file, and then call Write to supply that file's data, -// writing at most hdr.Size bytes in total. -type Writer struct { - w io.Writer - err error - nb int64 // number of unwritten bytes for current file entry - pad int64 // amount of padding to write after current file entry - closed bool - usedBinary bool // whether the binary numeric field extension was used - preferPax bool // use pax header instead of binary numeric header - hdrBuff [blockSize]byte // buffer to use in writeHeader when writing a regular header - paxHdrBuff [blockSize]byte // buffer to use in writeHeader when writing a pax header -} - -// NewWriter creates a new Writer writing to w. -func NewWriter(w io.Writer) *Writer { return &Writer{w: w} } - -// Flush finishes writing the current file (optional). -func (tw *Writer) Flush() error { - if tw.nb > 0 { - tw.err = fmt.Errorf("archive/tar: missed writing %d bytes", tw.nb) - return tw.err - } - - n := tw.nb + tw.pad - for n > 0 && tw.err == nil { - nr := n - if nr > blockSize { - nr = blockSize - } - var nw int - nw, tw.err = tw.w.Write(zeroBlock[0:nr]) - n -= int64(nw) - } - tw.nb = 0 - tw.pad = 0 - return tw.err -} - -// Write s into b, terminating it with a NUL if there is room. -// If the value is too long for the field and allowPax is true add a paxheader record instead -func (tw *Writer) cString(b []byte, s string, allowPax bool, paxKeyword string, paxHeaders map[string]string) { - needsPaxHeader := allowPax && len(s) > len(b) || !isASCII(s) - if needsPaxHeader { - paxHeaders[paxKeyword] = s - return - } - if len(s) > len(b) { - if tw.err == nil { - tw.err = ErrFieldTooLong - } - return - } - ascii := toASCII(s) - copy(b, ascii) - if len(ascii) < len(b) { - b[len(ascii)] = 0 - } -} - -// Encode x as an octal ASCII string and write it into b with leading zeros. -func (tw *Writer) octal(b []byte, x int64) { - s := strconv.FormatInt(x, 8) - // leading zeros, but leave room for a NUL. - for len(s)+1 < len(b) { - s = "0" + s - } - tw.cString(b, s, false, paxNone, nil) -} - -// Write x into b, either as octal or as binary (GNUtar/star extension). -// If the value is too long for the field and writingPax is enabled both for the field and the add a paxheader record instead -func (tw *Writer) numeric(b []byte, x int64, allowPax bool, paxKeyword string, paxHeaders map[string]string) { - // Try octal first. - s := strconv.FormatInt(x, 8) - if len(s) < len(b) { - tw.octal(b, x) - return - } - - // If it is too long for octal, and pax is preferred, use a pax header - if allowPax && tw.preferPax { - tw.octal(b, 0) - s := strconv.FormatInt(x, 10) - paxHeaders[paxKeyword] = s - return - } - - // Too big: use binary (big-endian). - tw.usedBinary = true - for i := len(b) - 1; x > 0 && i >= 0; i-- { - b[i] = byte(x) - x >>= 8 - } - b[0] |= 0x80 // highest bit indicates binary format -} - -var ( - minTime = time.Unix(0, 0) - // There is room for 11 octal digits (33 bits) of mtime. - maxTime = minTime.Add((1<<33 - 1) * time.Second) -) - -// WriteHeader writes hdr and prepares to accept the file's contents. -// WriteHeader calls Flush if it is not the first header. -// Calling after a Close will return ErrWriteAfterClose. -func (tw *Writer) WriteHeader(hdr *Header) error { - return tw.writeHeader(hdr, true) -} - -// WriteHeader writes hdr and prepares to accept the file's contents. -// WriteHeader calls Flush if it is not the first header. -// Calling after a Close will return ErrWriteAfterClose. -// As this method is called internally by writePax header to allow it to -// suppress writing the pax header. -func (tw *Writer) writeHeader(hdr *Header, allowPax bool) error { - if tw.closed { - return ErrWriteAfterClose - } - if tw.err == nil { - tw.Flush() - } - if tw.err != nil { - return tw.err - } - - // a map to hold pax header records, if any are needed - paxHeaders := make(map[string]string) - - // TODO(shanemhansen): we might want to use PAX headers for - // subsecond time resolution, but for now let's just capture - // too long fields or non ascii characters - - var header []byte - - // We need to select which scratch buffer to use carefully, - // since this method is called recursively to write PAX headers. - // If allowPax is true, this is the non-recursive call, and we will use hdrBuff. - // If allowPax is false, we are being called by writePAXHeader, and hdrBuff is - // already being used by the non-recursive call, so we must use paxHdrBuff. - header = tw.hdrBuff[:] - if !allowPax { - header = tw.paxHdrBuff[:] - } - copy(header, zeroBlock) - s := slicer(header) - - // keep a reference to the filename to allow to overwrite it later if we detect that we can use ustar longnames instead of pax - pathHeaderBytes := s.next(fileNameSize) - - tw.cString(pathHeaderBytes, hdr.Name, true, paxPath, paxHeaders) - - // Handle out of range ModTime carefully. - var modTime int64 - if !hdr.ModTime.Before(minTime) && !hdr.ModTime.After(maxTime) { - modTime = hdr.ModTime.Unix() - } - - tw.octal(s.next(8), hdr.Mode) // 100:108 - tw.numeric(s.next(8), int64(hdr.Uid), true, paxUid, paxHeaders) // 108:116 - tw.numeric(s.next(8), int64(hdr.Gid), true, paxGid, paxHeaders) // 116:124 - tw.numeric(s.next(12), hdr.Size, true, paxSize, paxHeaders) // 124:136 - tw.numeric(s.next(12), modTime, false, paxNone, nil) // 136:148 --- consider using pax for finer granularity - s.next(8) // chksum (148:156) - s.next(1)[0] = hdr.Typeflag // 156:157 - - tw.cString(s.next(100), hdr.Linkname, true, paxLinkpath, paxHeaders) - - copy(s.next(8), []byte("ustar\x0000")) // 257:265 - tw.cString(s.next(32), hdr.Uname, true, paxUname, paxHeaders) // 265:297 - tw.cString(s.next(32), hdr.Gname, true, paxGname, paxHeaders) // 297:329 - tw.numeric(s.next(8), hdr.Devmajor, false, paxNone, nil) // 329:337 - tw.numeric(s.next(8), hdr.Devminor, false, paxNone, nil) // 337:345 - - // keep a reference to the prefix to allow to overwrite it later if we detect that we can use ustar longnames instead of pax - prefixHeaderBytes := s.next(155) - tw.cString(prefixHeaderBytes, "", false, paxNone, nil) // 345:500 prefix - - // Use the GNU magic instead of POSIX magic if we used any GNU extensions. - if tw.usedBinary { - copy(header[257:265], []byte("ustar \x00")) - } - - _, paxPathUsed := paxHeaders[paxPath] - // try to use a ustar header when only the name is too long - if !tw.preferPax && len(paxHeaders) == 1 && paxPathUsed { - suffix := hdr.Name - prefix := "" - if len(hdr.Name) > fileNameSize && isASCII(hdr.Name) { - var err error - prefix, suffix, err = tw.splitUSTARLongName(hdr.Name) - if err == nil { - // ok we can use a ustar long name instead of pax, now correct the fields - - // remove the path field from the pax header. this will suppress the pax header - delete(paxHeaders, paxPath) - - // update the path fields - tw.cString(pathHeaderBytes, suffix, false, paxNone, nil) - tw.cString(prefixHeaderBytes, prefix, false, paxNone, nil) - - // Use the ustar magic if we used ustar long names. - if len(prefix) > 0 && !tw.usedBinary { - copy(header[257:265], []byte("ustar\x00")) - } - } - } - } - - // The chksum field is terminated by a NUL and a space. - // This is different from the other octal fields. - chksum, _ := checksum(header) - tw.octal(header[148:155], chksum) - header[155] = ' ' - - if tw.err != nil { - // problem with header; probably integer too big for a field. - return tw.err - } - - if allowPax { - for k, v := range hdr.Xattrs { - paxHeaders[paxXattr+k] = v - } - } - - if len(paxHeaders) > 0 { - if !allowPax { - return errInvalidHeader - } - if err := tw.writePAXHeader(hdr, paxHeaders); err != nil { - return err - } - } - tw.nb = int64(hdr.Size) - tw.pad = (blockSize - (tw.nb % blockSize)) % blockSize - - _, tw.err = tw.w.Write(header) - return tw.err -} - -// writeUSTARLongName splits a USTAR long name hdr.Name. -// name must be < 256 characters. errNameTooLong is returned -// if hdr.Name can't be split. The splitting heuristic -// is compatible with gnu tar. -func (tw *Writer) splitUSTARLongName(name string) (prefix, suffix string, err error) { - length := len(name) - if length > fileNamePrefixSize+1 { - length = fileNamePrefixSize + 1 - } else if name[length-1] == '/' { - length-- - } - i := strings.LastIndex(name[:length], "/") - // nlen contains the resulting length in the name field. - // plen contains the resulting length in the prefix field. - nlen := len(name) - i - 1 - plen := i - if i <= 0 || nlen > fileNameSize || nlen == 0 || plen > fileNamePrefixSize { - err = errNameTooLong - return - } - prefix, suffix = name[:i], name[i+1:] - return -} - -// writePaxHeader writes an extended pax header to the -// archive. -func (tw *Writer) writePAXHeader(hdr *Header, paxHeaders map[string]string) error { - // Prepare extended header - ext := new(Header) - ext.Typeflag = TypeXHeader - // Setting ModTime is required for reader parsing to - // succeed, and seems harmless enough. - ext.ModTime = hdr.ModTime - // The spec asks that we namespace our pseudo files - // with the current pid. - pid := os.Getpid() - dir, file := path.Split(hdr.Name) - fullName := path.Join(dir, - fmt.Sprintf("PaxHeaders.%d", pid), file) - - ascii := toASCII(fullName) - if len(ascii) > 100 { - ascii = ascii[:100] - } - ext.Name = ascii - // Construct the body - var buf bytes.Buffer - - for k, v := range paxHeaders { - fmt.Fprint(&buf, paxHeader(k+"="+v)) - } - - ext.Size = int64(len(buf.Bytes())) - if err := tw.writeHeader(ext, false); err != nil { - return err - } - if _, err := tw.Write(buf.Bytes()); err != nil { - return err - } - if err := tw.Flush(); err != nil { - return err - } - return nil -} - -// paxHeader formats a single pax record, prefixing it with the appropriate length -func paxHeader(msg string) string { - const padding = 2 // Extra padding for space and newline - size := len(msg) + padding - size += len(strconv.Itoa(size)) - record := fmt.Sprintf("%d %s\n", size, msg) - if len(record) != size { - // Final adjustment if adding size increased - // the number of digits in size - size = len(record) - record = fmt.Sprintf("%d %s\n", size, msg) - } - return record -} - -// Write writes to the current entry in the tar archive. -// Write returns the error ErrWriteTooLong if more than -// hdr.Size bytes are written after WriteHeader. -func (tw *Writer) Write(b []byte) (n int, err error) { - if tw.closed { - err = ErrWriteTooLong - return - } - overwrite := false - if int64(len(b)) > tw.nb { - b = b[0:tw.nb] - overwrite = true - } - n, err = tw.w.Write(b) - tw.nb -= int64(n) - if err == nil && overwrite { - err = ErrWriteTooLong - return - } - tw.err = err - return -} - -// Close closes the tar archive, flushing any unwritten -// data to the underlying writer. -func (tw *Writer) Close() error { - if tw.err != nil || tw.closed { - return tw.err - } - tw.Flush() - tw.closed = true - if tw.err != nil { - return tw.err - } - - // trailer: two zero blocks - for i := 0; i < 2; i++ { - _, tw.err = tw.w.Write(zeroBlock) - if tw.err != nil { - break - } - } - return tw.err -} diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go deleted file mode 100644 index 5e42e322f..000000000 --- a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/writer_test.go +++ /dev/null @@ -1,491 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package tar - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "os" - "reflect" - "strings" - "testing" - "testing/iotest" - "time" -) - -type writerTestEntry struct { - header *Header - contents string -} - -type writerTest struct { - file string // filename of expected output - entries []*writerTestEntry -} - -var writerTests = []*writerTest{ - // The writer test file was produced with this command: - // tar (GNU tar) 1.26 - // ln -s small.txt link.txt - // tar -b 1 --format=ustar -c -f writer.tar small.txt small2.txt link.txt - { - file: "testdata/writer.tar", - entries: []*writerTestEntry{ - { - header: &Header{ - Name: "small.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 5, - ModTime: time.Unix(1246508266, 0), - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - contents: "Kilts", - }, - { - header: &Header{ - Name: "small2.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 11, - ModTime: time.Unix(1245217492, 0), - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - contents: "Google.com\n", - }, - { - header: &Header{ - Name: "link.txt", - Mode: 0777, - Uid: 1000, - Gid: 1000, - Size: 0, - ModTime: time.Unix(1314603082, 0), - Typeflag: '2', - Linkname: "small.txt", - Uname: "strings", - Gname: "strings", - }, - // no contents - }, - }, - }, - // The truncated test file was produced using these commands: - // dd if=/dev/zero bs=1048576 count=16384 > /tmp/16gig.txt - // tar -b 1 -c -f- /tmp/16gig.txt | dd bs=512 count=8 > writer-big.tar - { - file: "testdata/writer-big.tar", - entries: []*writerTestEntry{ - { - header: &Header{ - Name: "tmp/16gig.txt", - Mode: 0640, - Uid: 73025, - Gid: 5000, - Size: 16 << 30, - ModTime: time.Unix(1254699560, 0), - Typeflag: '0', - Uname: "dsymonds", - Gname: "eng", - }, - // fake contents - contents: strings.Repeat("\x00", 4<<10), - }, - }, - }, - // The truncated test file was produced using these commands: - // dd if=/dev/zero bs=1048576 count=16384 > (longname/)*15 /16gig.txt - // tar -b 1 -c -f- (longname/)*15 /16gig.txt | dd bs=512 count=8 > writer-big-long.tar - { - file: "testdata/writer-big-long.tar", - entries: []*writerTestEntry{ - { - header: &Header{ - Name: strings.Repeat("longname/", 15) + "16gig.txt", - Mode: 0644, - Uid: 1000, - Gid: 1000, - Size: 16 << 30, - ModTime: time.Unix(1399583047, 0), - Typeflag: '0', - Uname: "guillaume", - Gname: "guillaume", - }, - // fake contents - contents: strings.Repeat("\x00", 4<<10), - }, - }, - }, - // This file was produced using gnu tar 1.17 - // gnutar -b 4 --format=ustar (longname/)*15 + file.txt - { - file: "testdata/ustar.tar", - entries: []*writerTestEntry{ - { - header: &Header{ - Name: strings.Repeat("longname/", 15) + "file.txt", - Mode: 0644, - Uid: 0765, - Gid: 024, - Size: 06, - ModTime: time.Unix(1360135598, 0), - Typeflag: '0', - Uname: "shane", - Gname: "staff", - }, - contents: "hello\n", - }, - }, - }, -} - -// Render byte array in a two-character hexadecimal string, spaced for easy visual inspection. -func bytestr(offset int, b []byte) string { - const rowLen = 32 - s := fmt.Sprintf("%04x ", offset) - for _, ch := range b { - switch { - case '0' <= ch && ch <= '9', 'A' <= ch && ch <= 'Z', 'a' <= ch && ch <= 'z': - s += fmt.Sprintf(" %c", ch) - default: - s += fmt.Sprintf(" %02x", ch) - } - } - return s -} - -// Render a pseudo-diff between two blocks of bytes. -func bytediff(a []byte, b []byte) string { - const rowLen = 32 - s := fmt.Sprintf("(%d bytes vs. %d bytes)\n", len(a), len(b)) - for offset := 0; len(a)+len(b) > 0; offset += rowLen { - na, nb := rowLen, rowLen - if na > len(a) { - na = len(a) - } - if nb > len(b) { - nb = len(b) - } - sa := bytestr(offset, a[0:na]) - sb := bytestr(offset, b[0:nb]) - if sa != sb { - s += fmt.Sprintf("-%v\n+%v\n", sa, sb) - } - a = a[na:] - b = b[nb:] - } - return s -} - -func TestWriter(t *testing.T) { -testLoop: - for i, test := range writerTests { - expected, err := ioutil.ReadFile(test.file) - if err != nil { - t.Errorf("test %d: Unexpected error: %v", i, err) - continue - } - - buf := new(bytes.Buffer) - tw := NewWriter(iotest.TruncateWriter(buf, 4<<10)) // only catch the first 4 KB - big := false - for j, entry := range test.entries { - big = big || entry.header.Size > 1<<10 - if err := tw.WriteHeader(entry.header); err != nil { - t.Errorf("test %d, entry %d: Failed writing header: %v", i, j, err) - continue testLoop - } - if _, err := io.WriteString(tw, entry.contents); err != nil { - t.Errorf("test %d, entry %d: Failed writing contents: %v", i, j, err) - continue testLoop - } - } - // Only interested in Close failures for the small tests. - if err := tw.Close(); err != nil && !big { - t.Errorf("test %d: Failed closing archive: %v", i, err) - continue testLoop - } - - actual := buf.Bytes() - if !bytes.Equal(expected, actual) { - t.Errorf("test %d: Incorrect result: (-=expected, +=actual)\n%v", - i, bytediff(expected, actual)) - } - if testing.Short() { // The second test is expensive. - break - } - } -} - -func TestPax(t *testing.T) { - // Create an archive with a large name - fileinfo, err := os.Stat("testdata/small.txt") - if err != nil { - t.Fatal(err) - } - hdr, err := FileInfoHeader(fileinfo, "") - if err != nil { - t.Fatalf("os.Stat: %v", err) - } - // Force a PAX long name to be written - longName := strings.Repeat("ab", 100) - contents := strings.Repeat(" ", int(hdr.Size)) - hdr.Name = longName - var buf bytes.Buffer - writer := NewWriter(&buf) - if err := writer.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - if _, err = writer.Write([]byte(contents)); err != nil { - t.Fatal(err) - } - if err := writer.Close(); err != nil { - t.Fatal(err) - } - // Simple test to make sure PAX extensions are in effect - if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) { - t.Fatal("Expected at least one PAX header to be written.") - } - // Test that we can get a long name back out of the archive. - reader := NewReader(&buf) - hdr, err = reader.Next() - if err != nil { - t.Fatal(err) - } - if hdr.Name != longName { - t.Fatal("Couldn't recover long file name") - } -} - -func TestPaxSymlink(t *testing.T) { - // Create an archive with a large linkname - fileinfo, err := os.Stat("testdata/small.txt") - if err != nil { - t.Fatal(err) - } - hdr, err := FileInfoHeader(fileinfo, "") - hdr.Typeflag = TypeSymlink - if err != nil { - t.Fatalf("os.Stat:1 %v", err) - } - // Force a PAX long linkname to be written - longLinkname := strings.Repeat("1234567890/1234567890", 10) - hdr.Linkname = longLinkname - - hdr.Size = 0 - var buf bytes.Buffer - writer := NewWriter(&buf) - if err := writer.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - if err := writer.Close(); err != nil { - t.Fatal(err) - } - // Simple test to make sure PAX extensions are in effect - if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) { - t.Fatal("Expected at least one PAX header to be written.") - } - // Test that we can get a long name back out of the archive. - reader := NewReader(&buf) - hdr, err = reader.Next() - if err != nil { - t.Fatal(err) - } - if hdr.Linkname != longLinkname { - t.Fatal("Couldn't recover long link name") - } -} - -func TestPaxNonAscii(t *testing.T) { - // Create an archive with non ascii. These should trigger a pax header - // because pax headers have a defined utf-8 encoding. - fileinfo, err := os.Stat("testdata/small.txt") - if err != nil { - t.Fatal(err) - } - - hdr, err := FileInfoHeader(fileinfo, "") - if err != nil { - t.Fatalf("os.Stat:1 %v", err) - } - - // some sample data - chineseFilename := "文件名" - chineseGroupname := "組" - chineseUsername := "用戶名" - - hdr.Name = chineseFilename - hdr.Gname = chineseGroupname - hdr.Uname = chineseUsername - - contents := strings.Repeat(" ", int(hdr.Size)) - - var buf bytes.Buffer - writer := NewWriter(&buf) - if err := writer.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - if _, err = writer.Write([]byte(contents)); err != nil { - t.Fatal(err) - } - if err := writer.Close(); err != nil { - t.Fatal(err) - } - // Simple test to make sure PAX extensions are in effect - if !bytes.Contains(buf.Bytes(), []byte("PaxHeaders.")) { - t.Fatal("Expected at least one PAX header to be written.") - } - // Test that we can get a long name back out of the archive. - reader := NewReader(&buf) - hdr, err = reader.Next() - if err != nil { - t.Fatal(err) - } - if hdr.Name != chineseFilename { - t.Fatal("Couldn't recover unicode name") - } - if hdr.Gname != chineseGroupname { - t.Fatal("Couldn't recover unicode group") - } - if hdr.Uname != chineseUsername { - t.Fatal("Couldn't recover unicode user") - } -} - -func TestPaxXattrs(t *testing.T) { - xattrs := map[string]string{ - "user.key": "value", - } - - // Create an archive with an xattr - fileinfo, err := os.Stat("testdata/small.txt") - if err != nil { - t.Fatal(err) - } - hdr, err := FileInfoHeader(fileinfo, "") - if err != nil { - t.Fatalf("os.Stat: %v", err) - } - contents := "Kilts" - hdr.Xattrs = xattrs - var buf bytes.Buffer - writer := NewWriter(&buf) - if err := writer.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - if _, err = writer.Write([]byte(contents)); err != nil { - t.Fatal(err) - } - if err := writer.Close(); err != nil { - t.Fatal(err) - } - // Test that we can get the xattrs back out of the archive. - reader := NewReader(&buf) - hdr, err = reader.Next() - if err != nil { - t.Fatal(err) - } - if !reflect.DeepEqual(hdr.Xattrs, xattrs) { - t.Fatalf("xattrs did not survive round trip: got %+v, want %+v", - hdr.Xattrs, xattrs) - } -} - -func TestPAXHeader(t *testing.T) { - medName := strings.Repeat("CD", 50) - longName := strings.Repeat("AB", 100) - paxTests := [][2]string{ - {paxPath + "=/etc/hosts", "19 path=/etc/hosts\n"}, - {"a=b", "6 a=b\n"}, // Single digit length - {"a=names", "11 a=names\n"}, // Test case involving carries - {paxPath + "=" + longName, fmt.Sprintf("210 path=%s\n", longName)}, - {paxPath + "=" + medName, fmt.Sprintf("110 path=%s\n", medName)}} - - for _, test := range paxTests { - key, expected := test[0], test[1] - if result := paxHeader(key); result != expected { - t.Fatalf("paxHeader: got %s, expected %s", result, expected) - } - } -} - -func TestUSTARLongName(t *testing.T) { - // Create an archive with a path that failed to split with USTAR extension in previous versions. - fileinfo, err := os.Stat("testdata/small.txt") - if err != nil { - t.Fatal(err) - } - hdr, err := FileInfoHeader(fileinfo, "") - hdr.Typeflag = TypeDir - if err != nil { - t.Fatalf("os.Stat:1 %v", err) - } - // Force a PAX long name to be written. The name was taken from a practical example - // that fails and replaced ever char through numbers to anonymize the sample. - longName := "/0000_0000000/00000-000000000/0000_0000000/00000-0000000000000/0000_0000000/00000-0000000-00000000/0000_0000000/00000000/0000_0000000/000/0000_0000000/00000000v00/0000_0000000/000000/0000_0000000/0000000/0000_0000000/00000y-00/0000/0000/00000000/0x000000/" - hdr.Name = longName - - hdr.Size = 0 - var buf bytes.Buffer - writer := NewWriter(&buf) - if err := writer.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - if err := writer.Close(); err != nil { - t.Fatal(err) - } - // Test that we can get a long name back out of the archive. - reader := NewReader(&buf) - hdr, err = reader.Next() - if err != nil { - t.Fatal(err) - } - if hdr.Name != longName { - t.Fatal("Couldn't recover long name") - } -} - -func TestValidTypeflagWithPAXHeader(t *testing.T) { - var buffer bytes.Buffer - tw := NewWriter(&buffer) - - fileName := strings.Repeat("ab", 100) - - hdr := &Header{ - Name: fileName, - Size: 4, - Typeflag: 0, - } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatalf("Failed to write header: %s", err) - } - if _, err := tw.Write([]byte("fooo")); err != nil { - t.Fatalf("Failed to write the file's data: %s", err) - } - tw.Close() - - tr := NewReader(&buffer) - - for { - header, err := tr.Next() - if err == io.EOF { - break - } - if err != nil { - t.Fatalf("Failed to read header: %s", err) - } - if header.Typeflag != 0 { - t.Fatalf("Typeflag should've been 0, found %d", header.Typeflag) - } - } -} From a4af8ad86963a21865af7aff6a742f43a6341ae4 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 1 May 2015 18:28:08 -0600 Subject: [PATCH 722/999] Rename development builds to be in the "docker-dev" repo instead of "docker" See https://github.com/docker-library/docker/issues/2 for some of the context around this; essentially, we're looking to create an official `docker` image that includes the Docker CLI (and the dependencies necessary for easy Docker-in-Docker), but it makes sense to use the `docker` namespace for that image. The `docker-dev` official image is already builds of Docker's own `Dockerfile` (but specifically of releases), so this seemed like a good fit. :+1: Signed-off-by: Andrew "Tianon" Page --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 257fefdfe..d13960229 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,7 @@ DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR)) DOCSPORT := 8000 GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null) -DOCKER_IMAGE := docker$(if $(GIT_BRANCH),:$(GIT_BRANCH)) +DOCKER_IMAGE := docker-dev$(if $(GIT_BRANCH),:$(GIT_BRANCH)) DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH),:$(GIT_BRANCH)) DOCKER_RUN_DOCKER := docker run --rm -it --privileged $(DOCKER_ENVS) $(DOCKER_MOUNT) "$(DOCKER_IMAGE)" From c7cfdb65aa43a4561adb919d170bba5e86d69bee Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sat, 2 May 2015 03:03:35 +0200 Subject: [PATCH 723/999] Refactor server to use daemon as the service layer in controllers Signed-off-by: Antonio Murdaca --- api/server/server.go | 48 ++++++-------------------------------------- daemon/changes.go | 13 ++++++++++++ daemon/copy.go | 16 +++++++++++++++ daemon/pause.go | 18 +++++++++++++++++ daemon/resize.go | 15 ++++++++++---- daemon/unpause.go | 18 +++++++++++++++++ 6 files changed, 82 insertions(+), 46 deletions(-) create mode 100644 daemon/changes.go create mode 100644 daemon/copy.go create mode 100644 daemon/pause.go create mode 100644 daemon/unpause.go diff --git a/api/server/server.go b/api/server/server.go index 3a7975fe6..c43b84fd6 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -306,17 +306,10 @@ func (s *Server) postContainersPause(version version.Version, w http.ResponseWri return err } - name := vars["name"] - cont, err := s.daemon.Get(name) - if err != nil { + if err := s.daemon.ContainerPause(vars["name"]); err != nil { return err } - if err := cont.Pause(); err != nil { - return fmt.Errorf("Cannot pause container %s: %s", name, err) - } - cont.LogEvent("pause") - w.WriteHeader(http.StatusNoContent) return nil @@ -330,17 +323,10 @@ func (s *Server) postContainersUnpause(version version.Version, w http.ResponseW return err } - name := vars["name"] - cont, err := s.daemon.Get(name) - if err != nil { + if err := s.daemon.ContainerUnpause(vars["name"]); err != nil { return err } - if err := cont.Unpause(); err != nil { - return fmt.Errorf("Cannot unpause container %s: %s", name, err) - } - cont.LogEvent("unpause") - w.WriteHeader(http.StatusNoContent) return nil @@ -529,13 +515,7 @@ func (s *Server) getContainersChanges(version version.Version, w http.ResponseWr return fmt.Errorf("Missing parameter") } - name := vars["name"] - cont, err := s.daemon.Get(name) - if err != nil { - return err - } - - changes, err := cont.Changes() + changes, err := s.daemon.ContainerChanges(vars["name"]) if err != nil { return err } @@ -1112,12 +1092,7 @@ func (s *Server) postContainersResize(version version.Version, w http.ResponseWr return err } - cont, err := s.daemon.Get(vars["name"]) - if err != nil { - return err - } - - return cont.Resize(height, width) + return s.daemon.ContainerResize(vars["name"], height, width) } func (s *Server) postContainersAttach(version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { @@ -1371,30 +1346,19 @@ func (s *Server) postContainersCopy(version version.Version, w http.ResponseWrit return fmt.Errorf("Path cannot be empty") } - res := cfg.Resource - - if res[0] == '/' { - res = res[1:] - } - - cont, err := s.daemon.Get(vars["name"]) + data, err := s.daemon.ContainerCopy(vars["name"], cfg.Resource) if err != nil { - logrus.Errorf("%v", err) if strings.Contains(strings.ToLower(err.Error()), "no such id") { w.WriteHeader(http.StatusNotFound) return nil } - } - - data, err := cont.Copy(res) - if err != nil { - logrus.Errorf("%v", err) if os.IsNotExist(err) { return fmt.Errorf("Could not find the file %s in container %s", cfg.Resource, vars["name"]) } return err } defer data.Close() + w.Header().Set("Content-Type", "application/x-tar") if _, err := io.Copy(w, data); err != nil { return err diff --git a/daemon/changes.go b/daemon/changes.go new file mode 100644 index 000000000..55b230b9b --- /dev/null +++ b/daemon/changes.go @@ -0,0 +1,13 @@ +package daemon + +import "github.com/docker/docker/pkg/archive" + +// ContainerChanges returns a list of container fs changes +func (daemon *Daemon) ContainerChanges(name string) ([]archive.Change, error) { + container, err := daemon.Get(name) + if err != nil { + return nil, err + } + + return container.Changes() +} diff --git a/daemon/copy.go b/daemon/copy.go new file mode 100644 index 000000000..dec30d8f3 --- /dev/null +++ b/daemon/copy.go @@ -0,0 +1,16 @@ +package daemon + +import "io" + +func (daemon *Daemon) ContainerCopy(name string, res string) (io.ReadCloser, error) { + container, err := daemon.Get(name) + if err != nil { + return nil, err + } + + if res[0] == '/' { + res = res[1:] + } + + return container.Copy(res) +} diff --git a/daemon/pause.go b/daemon/pause.go new file mode 100644 index 000000000..348f83fc7 --- /dev/null +++ b/daemon/pause.go @@ -0,0 +1,18 @@ +package daemon + +import "fmt" + +// ContainerPause pauses a container +func (daemon *Daemon) ContainerPause(name string) error { + container, err := daemon.Get(name) + if err != nil { + return err + } + + if err := container.Pause(); err != nil { + return fmt.Errorf("Cannot pause container %s: %s", name, err) + } + container.LogEvent("pause") + + return nil +} diff --git a/daemon/resize.go b/daemon/resize.go index 060634b13..f22539466 100644 --- a/daemon/resize.go +++ b/daemon/resize.go @@ -1,12 +1,19 @@ package daemon +func (daemon *Daemon) ContainerResize(name string, height, width int) error { + container, err := daemon.Get(name) + if err != nil { + return err + } + + return container.Resize(height, width) +} + func (daemon *Daemon) ContainerExecResize(name string, height, width int) error { execConfig, err := daemon.getExecConfig(name) if err != nil { return err } - if err := execConfig.Resize(height, width); err != nil { - return err - } - return nil + + return execConfig.Resize(height, width) } diff --git a/daemon/unpause.go b/daemon/unpause.go new file mode 100644 index 000000000..b13c85e19 --- /dev/null +++ b/daemon/unpause.go @@ -0,0 +1,18 @@ +package daemon + +import "fmt" + +// ContainerUnpause unpauses a container +func (daemon *Daemon) ContainerUnpause(name string) error { + container, err := daemon.Get(name) + if err != nil { + return err + } + + if err := container.Unpause(); err != nil { + return fmt.Errorf("Cannot unpause container %s: %s", name, err) + } + container.LogEvent("unpause") + + return nil +} From 4aff563282c4d467575aed006642a22d673d4cb7 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sat, 2 May 2015 15:57:57 +0200 Subject: [PATCH 724/999] Remove unused error return Signed-off-by: Antonio Murdaca --- daemon/execdriver/native/create.go | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index fa53621c4..ddb7b9bdc 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -65,9 +65,7 @@ func (d *driver) createContainer(c *execdriver.Command) (*configs.Config, error) return nil, err } - if err := d.setupLabels(container, c); err != nil { - return nil, err - } + d.setupLabels(container, c) d.setupRlimits(container, c) return container, nil } @@ -254,9 +252,7 @@ func (d *driver) setupMounts(container *configs.Config, c *execdriver.Command) e return nil } -func (d *driver) setupLabels(container *configs.Config, c *execdriver.Command) error { +func (d *driver) setupLabels(container *configs.Config, c *execdriver.Command) { container.ProcessLabel = c.ProcessLabel container.MountLabel = c.MountLabel - - return nil } From aaaa8bab0cad495c842580e35322bee8e73de08f Mon Sep 17 00:00:00 2001 From: Raghuram Devarakonda Date: Sat, 2 May 2015 01:30:35 -0400 Subject: [PATCH 725/999] Adding test for "GET /images/(name)/history" API. Closes #12284. Signed-off-by: Raghuram Devarakonda --- integration-cli/docker_api_images_test.go | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 543182eed..c69308cce 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -121,3 +121,25 @@ func (s *DockerSuite) TestApiImagesDelete(c *check.C) { c.Assert(status, check.Equals, http.StatusOK) c.Assert(err, check.IsNil) } + +func (s *DockerSuite) TestApiImagesHistory(c *check.C) { + testRequires(c, Network) + name := "test-api-images-history" + out, err := buildImage(name, "FROM hello-world\nENV FOO bar", false) + c.Assert(err, check.IsNil) + + defer deleteImages(name) + id := strings.TrimSpace(out) + + status, body, err := sockRequest("GET", "/images/"+id+"/history", nil) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusOK) + + var historydata []types.ImageHistory + if err = json.Unmarshal(body, &historydata); err != nil { + c.Fatalf("Error on unmarshal: %s", err) + } + + c.Assert(len(historydata), check.Not(check.Equals), 0) + c.Assert(historydata[0].Tags[0], check.Equals, "test-api-images-history:latest") +} From b447fef7ecb740bc0f9ece75e10926fc5f121b5c Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 2 May 2015 23:25:57 -0600 Subject: [PATCH 726/999] Update go-patricia to 2.1.0 This includes a fix for the minor v2 API change introduced by https://github.com/tchap/go-patricia/commit/341a37095fea62f400f449e4ec0192702d6e019e. :+1: Signed-off-by: Andrew "Tianon" Page --- hack/vendor.sh | 2 +- pkg/truncindex/truncindex.go | 13 ++-- .../github.com/tchap/go-patricia/README.md | 13 ++-- .../tchap/go-patricia/patricia/children.go | 32 +++++++--- .../tchap/go-patricia/patricia/patricia.go | 59 +++++++++++++++---- .../patricia/patricia_dense_test.go | 2 +- .../patricia/patricia_sparse_test.go | 18 +++--- .../go-patricia/patricia/patricia_test.go | 14 +++++ 8 files changed, 108 insertions(+), 45 deletions(-) diff --git a/hack/vendor.sh b/hack/vendor.sh index 50cff1690..4e8bbefc0 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -45,7 +45,7 @@ clone git github.com/gorilla/context 14f550f51a clone git github.com/gorilla/mux e444e69cbd -clone git github.com/tchap/go-patricia v1.0.1 +clone git github.com/tchap/go-patricia v2.1.0 clone hg code.google.com/p/go.net 84a4013f96e0 diff --git a/pkg/truncindex/truncindex.go b/pkg/truncindex/truncindex.go index 73c7e24fb..9aae5c0d0 100644 --- a/pkg/truncindex/truncindex.go +++ b/pkg/truncindex/truncindex.go @@ -14,12 +14,6 @@ var ( ErrAmbiguousPrefix = errors.New("Multiple IDs found with provided prefix") ) -func init() { - // Change patricia max prefix per node length, - // because our len(ID) always 64 - patricia.MaxPrefixPerNode = 64 -} - // TruncIndex allows the retrieval of string identifiers by any of their unique prefixes. // This is used to retrieve image and container IDs by more convenient shorthand prefixes. type TruncIndex struct { @@ -31,8 +25,11 @@ type TruncIndex struct { // NewTruncIndex creates a new TruncIndex and initializes with a list of IDs func NewTruncIndex(ids []string) (idx *TruncIndex) { idx = &TruncIndex{ - ids: make(map[string]struct{}), - trie: patricia.NewTrie(), + ids: make(map[string]struct{}), + + // Change patricia max prefix per node length, + // because our len(ID) always 64 + trie: patricia.NewTrie(patricia.MaxPrefixPerNode(64)), } for _, id := range ids { idx.addID(id) diff --git a/vendor/src/github.com/tchap/go-patricia/README.md b/vendor/src/github.com/tchap/go-patricia/README.md index 11ee4612d..9d6ebc43a 100644 --- a/vendor/src/github.com/tchap/go-patricia/README.md +++ b/vendor/src/github.com/tchap/go-patricia/README.md @@ -50,9 +50,12 @@ printItem := func(prefix patricia.Prefix, item patricia.Item) error { return nil } -// Create a new tree. +// Create a new default trie (using the default parameter values). trie := NewTrie() +// Create a new custom trie. +trie := NewTrie(MaxPrefixPerNode(16), MaxChildrenPerSparseNode(10)) + // Insert some items. trie.Insert(Prefix("Pepa Novak"), 1) trie.Insert(Prefix("Pepa Sindelar"), 2) @@ -67,12 +70,12 @@ key = Prefix("Karel") fmt.Printf("Anybody called %q here? %v\n", key, trie.MatchSubtree(key)) // Anybody called "Karel" here? true -// Walk the tree. +// Walk the tree in alphabetical order. trie.Visit(printItem) +// "Karel Hynek Macha": 4 +// "Karel Macha": 3 // "Pepa Novak": 1 // "Pepa Sindelar": 2 -// "Karel Macha": 3 -// "Karel Hynek Macha": 4 // Walk a subtree. trie.VisitSubtree(Prefix("Pepa"), printItem) @@ -96,8 +99,8 @@ trie.Delete(Prefix("Karel Macha")) // Walk again. trie.Visit(printItem) -// "Pepa Sindelar": 2 // "Karel Hynek Macha": 10 +// "Pepa Sindelar": 2 // Delete a subtree. trie.DeleteSubtree(Prefix("Pepa")) diff --git a/vendor/src/github.com/tchap/go-patricia/patricia/children.go b/vendor/src/github.com/tchap/go-patricia/patricia/children.go index 07d332633..a204b0c8a 100644 --- a/vendor/src/github.com/tchap/go-patricia/patricia/children.go +++ b/vendor/src/github.com/tchap/go-patricia/patricia/children.go @@ -5,11 +5,7 @@ package patricia -// Max prefix length that is kept in a single trie node. -var MaxPrefixPerNode = 10 - -// Max children to keep in a node in the sparse mode. -const MaxChildrenPerSparseNode = 8 +import "sort" type childList interface { length() int @@ -21,13 +17,28 @@ type childList interface { walk(prefix *Prefix, visitor VisitorFunc) error } -type sparseChildList struct { - children []*Trie +type tries []*Trie + +func (t tries) Len() int { + return len(t) } -func newSparseChildList() childList { +func (t tries) Less(i, j int) bool { + strings := sort.StringSlice{string(t[i].prefix), string(t[j].prefix)} + return strings.Less(0, 1) +} + +func (t tries) Swap(i, j int) { + t[i], t[j] = t[j], t[i] +} + +type sparseChildList struct { + children tries +} + +func newSparseChildList(maxChildrenPerSparseNode int) childList { return &sparseChildList{ - children: make([]*Trie, 0, MaxChildrenPerSparseNode), + children: make(tries, 0, DefaultMaxChildrenPerSparseNode), } } @@ -82,6 +93,9 @@ func (list *sparseChildList) next(b byte) *Trie { } func (list *sparseChildList) walk(prefix *Prefix, visitor VisitorFunc) error { + + sort.Sort(list.children) + for _, child := range list.children { *prefix = append(*prefix, child.prefix...) if child.item != nil { diff --git a/vendor/src/github.com/tchap/go-patricia/patricia/patricia.go b/vendor/src/github.com/tchap/go-patricia/patricia/patricia.go index 8fcbcdf42..a8c3786b6 100644 --- a/vendor/src/github.com/tchap/go-patricia/patricia/patricia.go +++ b/vendor/src/github.com/tchap/go-patricia/patricia/patricia.go @@ -13,6 +13,11 @@ import ( // Trie //------------------------------------------------------------------------------ +const ( + DefaultMaxPrefixPerNode = 10 + DefaultMaxChildrenPerSparseNode = 8 +) + type ( Prefix []byte Item interface{} @@ -27,15 +32,44 @@ type Trie struct { prefix Prefix item Item + maxPrefixPerNode int + maxChildrenPerSparseNode int + children childList } // Public API ------------------------------------------------------------------ +type Option func(*Trie) + // Trie constructor. -func NewTrie() *Trie { - return &Trie{ - children: newSparseChildList(), +func NewTrie(options ...Option) *Trie { + trie := &Trie{} + + for _, opt := range options { + opt(trie) + } + + if trie.maxPrefixPerNode <= 0 { + trie.maxPrefixPerNode = DefaultMaxPrefixPerNode + } + if trie.maxChildrenPerSparseNode <= 0 { + trie.maxChildrenPerSparseNode = DefaultMaxChildrenPerSparseNode + } + + trie.children = newSparseChildList(trie.maxChildrenPerSparseNode) + return trie +} + +func MaxPrefixPerNode(value int) Option { + return func(trie *Trie) { + trie.maxPrefixPerNode = value + } +} + +func MaxChildrenPerSparseNode(value int) Option { + return func(trie *Trie) { + trie.maxChildrenPerSparseNode = value } } @@ -85,7 +119,8 @@ func (trie *Trie) MatchSubtree(key Prefix) (matched bool) { return } -// Visit calls visitor on every node containing a non-nil item. +// Visit calls visitor on every node containing a non-nil item +// in alphabetical order. // // If an error is returned from visitor, the function stops visiting the tree // and returns that error, unless it is a special error - SkipSubtree. In that @@ -233,7 +268,7 @@ func (trie *Trie) DeleteSubtree(prefix Prefix) (deleted bool) { // If we are in the root of the trie, reset the trie. if parent == nil { root.prefix = nil - root.children = newSparseChildList() + root.children = newSparseChildList(trie.maxPrefixPerNode) return true } @@ -257,12 +292,12 @@ func (trie *Trie) put(key Prefix, item Item, replace bool) (inserted bool) { ) if node.prefix == nil { - if len(key) <= MaxPrefixPerNode { + if len(key) <= trie.maxPrefixPerNode { node.prefix = key goto InsertItem } - node.prefix = key[:MaxPrefixPerNode] - key = key[MaxPrefixPerNode:] + node.prefix = key[:trie.maxPrefixPerNode] + key = key[trie.maxPrefixPerNode:] goto AppendChild } @@ -306,14 +341,14 @@ AppendChild: // This loop starts with empty node.prefix that needs to be filled. for len(key) != 0 { child := NewTrie() - if len(key) <= MaxPrefixPerNode { + if len(key) <= trie.maxPrefixPerNode { child.prefix = key node.children = node.children.add(child) node = child goto InsertItem } else { - child.prefix = key[:MaxPrefixPerNode] - key = key[MaxPrefixPerNode:] + child.prefix = key[:trie.maxPrefixPerNode] + key = key[trie.maxPrefixPerNode:] node.children = node.children.add(child) node = child } @@ -344,7 +379,7 @@ func (trie *Trie) compact() *Trie { } // Make sure the combined prefixes fit into a single node. - if len(trie.prefix)+len(child.prefix) > MaxPrefixPerNode { + if len(trie.prefix)+len(child.prefix) > trie.maxPrefixPerNode { return trie } diff --git a/vendor/src/github.com/tchap/go-patricia/patricia/patricia_dense_test.go b/vendor/src/github.com/tchap/go-patricia/patricia/patricia_dense_test.go index 346e9a66c..96089fceb 100644 --- a/vendor/src/github.com/tchap/go-patricia/patricia/patricia_dense_test.go +++ b/vendor/src/github.com/tchap/go-patricia/patricia/patricia_dense_test.go @@ -55,7 +55,7 @@ func TestTrie_InsertDensePreceeding(t *testing.T) { trie := NewTrie() start := byte(70) // create a dense node - for i := byte(0); i <= MaxChildrenPerSparseNode; i++ { + for i := byte(0); i <= DefaultMaxChildrenPerSparseNode; i++ { if !trie.Insert(Prefix([]byte{start + i}), true) { t.Errorf("insert failed, prefix=%v", start+i) } diff --git a/vendor/src/github.com/tchap/go-patricia/patricia/patricia_sparse_test.go b/vendor/src/github.com/tchap/go-patricia/patricia/patricia_sparse_test.go index 27f3c878b..b35c9e2ef 100644 --- a/vendor/src/github.com/tchap/go-patricia/patricia/patricia_sparse_test.go +++ b/vendor/src/github.com/tchap/go-patricia/patricia/patricia_sparse_test.go @@ -300,10 +300,10 @@ func TestTrie_VisitReturnError(t *testing.T) { someErr := errors.New("Something exploded") if err := trie.Visit(func(prefix Prefix, item Item) error { t.Logf("VISITING prefix=%q, item=%v", prefix, item) - if item.(int) == 0 { + if item.(int) == 3 { return someErr } - if item.(int) != 0 { + if item.(int) != 3 { t.Errorf("Unexpected prefix encountered, %q", prefix) } return nil @@ -598,10 +598,10 @@ func ExampleTrie() { // Walk the tree. trie.Visit(printItem) + // "Karel Hynek Macha": 4 + // "Karel Macha": 3 // "Pepa Novak": 1 // "Pepa Sindelar": 2 - // "Karel Macha": 3 - // "Karel Hynek Macha": 4 // Walk a subtree. trie.VisitSubtree(Prefix("Pepa"), printItem) @@ -625,8 +625,8 @@ func ExampleTrie() { // Walk again. trie.Visit(printItem) - // "Pepa Sindelar": 2 // "Karel Hynek Macha": 10 + // "Pepa Sindelar": 2 // Delete a subtree. trie.DeleteSubtree(Prefix("Pepa")) @@ -638,16 +638,16 @@ func ExampleTrie() { // Output: // "Pepa Novak" present? true // Anybody called "Karel" here? true - // "Pepa Novak": 1 - // "Pepa Sindelar": 2 - // "Karel Macha": 3 // "Karel Hynek Macha": 4 + // "Karel Macha": 3 + // "Pepa Novak": 1 + // "Pepa Sindelar": 2 // "Pepa Novak": 1 // "Pepa Sindelar": 2 // "Karel Hynek Macha": 10 // "Karel Hynek Macha": 10 - // "Pepa Sindelar": 2 // "Karel Hynek Macha": 10 + // "Pepa Sindelar": 2 // "Karel Hynek Macha": 10 } diff --git a/vendor/src/github.com/tchap/go-patricia/patricia/patricia_test.go b/vendor/src/github.com/tchap/go-patricia/patricia/patricia_test.go index ce5ae378f..12c441b62 100644 --- a/vendor/src/github.com/tchap/go-patricia/patricia/patricia_test.go +++ b/vendor/src/github.com/tchap/go-patricia/patricia/patricia_test.go @@ -13,6 +13,20 @@ import ( // Tests ----------------------------------------------------------------------- +func TestTrie_ConstructorOptions(t *testing.T) { + trie := NewTrie(MaxPrefixPerNode(16), MaxChildrenPerSparseNode(10)) + + if trie.maxPrefixPerNode != 16 { + t.Errorf("Unexpected trie.maxPrefixPerNode value, expected=%v, got=%v", + 16, trie.maxPrefixPerNode) + } + + if trie.maxChildrenPerSparseNode != 10 { + t.Errorf("Unexpected trie.maxChildrenPerSparseNode value, expected=%v, got=%v", + 10, trie.maxChildrenPerSparseNode) + } +} + func TestTrie_GetNonexistentPrefix(t *testing.T) { trie := NewTrie() From e1ccfabdc5c3163db05327d24c4b2581eeac752c Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sat, 2 May 2015 23:28:15 -0600 Subject: [PATCH 727/999] Update fsnotify to 1.2.0 Signed-off-by: Andrew "Tianon" Page --- hack/vendor.sh | 2 +- .../go-fsnotify/fsnotify/.travis.yml | 8 +- .../github.com/go-fsnotify/fsnotify/AUTHORS | 2 + .../go-fsnotify/fsnotify/CHANGELOG.md | 26 + .../go-fsnotify/fsnotify/CONTRIBUTING.md | 61 ++- .../go-fsnotify/fsnotify/NotUsed.xcworkspace | 0 .../github.com/go-fsnotify/fsnotify/README.md | 42 +- .../go-fsnotify/fsnotify/circle.yml | 26 + .../go-fsnotify/fsnotify/example_test.go | 2 +- .../go-fsnotify/fsnotify/fsnotify.go | 26 +- .../go-fsnotify/fsnotify/inotify.go | 151 ++++-- .../go-fsnotify/fsnotify/inotify_poller.go | 186 +++++++ .../fsnotify/inotify_poller_test.go | 228 +++++++++ .../go-fsnotify/fsnotify/inotify_test.go | 292 +++++++++++ .../go-fsnotify/fsnotify/integration_test.go | 15 + .../github.com/go-fsnotify/fsnotify/kqueue.go | 484 +++++++++--------- 16 files changed, 1206 insertions(+), 345 deletions(-) create mode 100644 vendor/src/github.com/go-fsnotify/fsnotify/NotUsed.xcworkspace create mode 100644 vendor/src/github.com/go-fsnotify/fsnotify/circle.yml create mode 100644 vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller.go create mode 100644 vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller_test.go create mode 100644 vendor/src/github.com/go-fsnotify/fsnotify/inotify_test.go diff --git a/hack/vendor.sh b/hack/vendor.sh index 50cff1690..15846ab97 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -55,7 +55,7 @@ clone git github.com/docker/libtrust 230dfd18c232 clone git github.com/Sirupsen/logrus v0.7.2 -clone git github.com/go-fsnotify/fsnotify v1.0.4 +clone git github.com/go-fsnotify/fsnotify v1.2.0 clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673 diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/.travis.yml b/vendor/src/github.com/go-fsnotify/fsnotify/.travis.yml index f8e76fc66..67467e140 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/.travis.yml +++ b/vendor/src/github.com/go-fsnotify/fsnotify/.travis.yml @@ -1,10 +1,12 @@ +sudo: false language: go go: - - 1.2 - - tip + - 1.4.1 + +before_script: + - FIXED=$(go fmt ./... | wc -l); if [ $FIXED -gt 0 ]; then echo "gofmt - $FIXED file(s) not formatted correctly, please run gofmt to fix this." && exit 1; fi -# not yet https://github.com/travis-ci/travis-ci/issues/2318 os: - linux - osx diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/AUTHORS b/vendor/src/github.com/go-fsnotify/fsnotify/AUTHORS index 306091eda..4e0e8284e 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/AUTHORS +++ b/vendor/src/github.com/go-fsnotify/fsnotify/AUTHORS @@ -18,8 +18,10 @@ Francisco Souza Hari haran John C Barstow Kelvin Fo +Matt Layher Nathan Youngman Paul Hammond +Pieter Droogendijk Pursuit92 Rob Figueiredo Soge Zhang diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/CHANGELOG.md b/vendor/src/github.com/go-fsnotify/fsnotify/CHANGELOG.md index 79f4ddbaa..ea9428a2a 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/CHANGELOG.md +++ b/vendor/src/github.com/go-fsnotify/fsnotify/CHANGELOG.md @@ -1,5 +1,27 @@ # Changelog +## v1.2.0 / 2015-02-08 + +* inotify: use epoll to wake up readEvents [#66](https://github.com/go-fsnotify/fsnotify/pull/66) (thanks @PieterD) +* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/go-fsnotify/fsnotify/pull/63) (thanks @PieterD) +* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/go-fsnotify/fsnotify/issues/59) + +## v1.1.1 / 2015-02-05 + +* inotify: Retry read on EINTR [#61](https://github.com/go-fsnotify/fsnotify/issues/61) (thanks @PieterD) + +## v1.1.0 / 2014-12-12 + +* kqueue: rework internals [#43](https://github.com/go-fsnotify/fsnotify/pull/43) + * add low-level functions + * only need to store flags on directories + * less mutexes [#13](https://github.com/go-fsnotify/fsnotify/issues/13) + * done can be an unbuffered channel + * remove calls to os.NewSyscallError +* More efficient string concatenation for Event.String() [#52](https://github.com/go-fsnotify/fsnotify/pull/52) (thanks @mdlayher) +* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/go-fsnotify/fsnotify/issues/48) +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/go-fsnotify/fsnotify/issues/51) + ## v1.0.4 / 2014-09-07 * kqueue: add dragonfly to the build tags. @@ -69,6 +91,10 @@ * no tests for the current implementation * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195) +## v0.9.3 / 2014-12-31 + +* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/go-fsnotify/fsnotify/issues/51) + ## v0.9.2 / 2014-08-17 * [Backport] Fix missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso) diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/CONTRIBUTING.md b/vendor/src/github.com/go-fsnotify/fsnotify/CONTRIBUTING.md index 2fd0423cc..0f377f341 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/CONTRIBUTING.md +++ b/vendor/src/github.com/go-fsnotify/fsnotify/CONTRIBUTING.md @@ -1,21 +1,34 @@ # Contributing -* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com). - -### Issues +## Issues * Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues). -* Please indicate the platform you are running on. +* Please indicate the platform you are using fsnotify on. +* A code example to reproduce the problem is appreciated. -### Pull Requests +## Pull Requests -A future version of Go will have [fsnotify in the standard library](https://code.google.com/p/go/issues/detail?id=4068), therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so we need you to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). +### Contributor License Agreement + +fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/go-fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). Please indicate that you have signed the CLA in your pull request. -To hack on fsnotify: +### How fsnotify is Developed -1. Install as usual (`go get -u github.com/go-fsnotify/fsnotify`) +* Development is done on feature branches. +* Tests are run on BSD, Linux, OS X and Windows. +* Pull requests are reviewed and [applied to master][am] using [hub][]. + * Maintainers may modify or squash commits rather than asking contributors to. +* To issue a new release, the maintainers will: + * Update the CHANGELOG + * Tag a version, which will become available through gopkg.in. + +### How to Fork + +For smooth sailing, always use the original import path. Installing with `go get` makes this easy. + +1. Install from GitHub (`go get -u github.com/go-fsnotify/fsnotify`) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Ensure everything works and the tests pass (see below) 4. Commit your changes (`git commit -am 'Add some feature'`) @@ -27,15 +40,7 @@ Contribute upstream: 3. Push to the branch (`git push fork my-new-feature`) 4. Create a new Pull Request on GitHub -If other team members need your patch before I merge it: - -1. Install as usual (`go get -u github.com/go-fsnotify/fsnotify`) -2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`) -3. Pull your revisions (`git fetch fork; git checkout -b my-new-feature fork/my-new-feature`) - -Notice: For smooth sailing, always use the original import path. Installing with `go get` makes this easy. - -Note: The maintainers will update the CHANGELOG on your behalf. Please don't modify it in your pull request. +This workflow is [thoroughly explained by Katrina Owen](https://blog.splice.com/contributing-open-source-git-repositories-go/). ### Testing @@ -43,7 +48,7 @@ fsnotify uses build tags to compile different code on Linux, BSD, OS X, and Wind Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on. -To make cross-platform testing easier, I've created a Vagrantfile for Linux and BSD. +To aid in cross-platform testing there is a Vagrantfile for Linux and BSD. * Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/) * Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder. @@ -51,6 +56,22 @@ To make cross-platform testing easier, I've created a Vagrantfile for Linux and * Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd go-fsnotify/fsnotify; go test'`. * When you're done, you will want to halt or destroy the Vagrant boxes. -Notice: fsnotify file system events don't work on shared folders. The tests get around this limitation by using a tmp directory, but it is something to be aware of. +Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory. -Right now I don't have an equivalent solution for Windows and OS X, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads). +Right now there is no equivalent solution for Windows and OS X, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads). + +### Maintainers + +Help maintaining fsnotify is welcome. To be a maintainer: + +* Submit a pull request and sign the CLA as above. +* You must be able to run the test suite on Mac, Windows, Linux and BSD. + +To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][]. + +All code changes should be internal pull requests. + +Releases are tagged using [Semantic Versioning](http://semver.org/). + +[hub]: https://github.com/github/hub +[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/NotUsed.xcworkspace b/vendor/src/github.com/go-fsnotify/fsnotify/NotUsed.xcworkspace new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/README.md b/vendor/src/github.com/go-fsnotify/fsnotify/README.md index 075928426..7a0b24736 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/README.md +++ b/vendor/src/github.com/go-fsnotify/fsnotify/README.md @@ -2,18 +2,20 @@ [![Coverage](http://gocover.io/_badge/github.com/go-fsnotify/fsnotify)](http://gocover.io/github.com/go-fsnotify/fsnotify) [![GoDoc](https://godoc.org/gopkg.in/fsnotify.v1?status.svg)](https://godoc.org/gopkg.in/fsnotify.v1) +Go 1.3+ required. + Cross platform: Windows, Linux, BSD and OS X. |Adapter |OS |Status | |----------|----------|----------| -|inotify |Linux, Android\*|Supported| -|kqueue |BSD, OS X, iOS\*|Supported| -|ReadDirectoryChangesW|Windows|Supported| +|inotify |Linux, Android\*|Supported [![Build Status](https://travis-ci.org/go-fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/go-fsnotify/fsnotify)| +|kqueue |BSD, OS X, iOS\*|Supported [![Circle CI](https://circleci.com/gh/go-fsnotify/fsnotify.svg?style=svg)](https://circleci.com/gh/go-fsnotify/fsnotify)| +|ReadDirectoryChangesW|Windows|Supported [![Build status](https://ci.appveyor.com/api/projects/status/ivwjubaih4r0udeh/branch/master?svg=true)](https://ci.appveyor.com/project/NathanYoungman/fsnotify/branch/master)| |FSEvents |OS X |[Planned](https://github.com/go-fsnotify/fsnotify/issues/11)| |FEN |Solaris 11 |[Planned](https://github.com/go-fsnotify/fsnotify/issues/12)| |fanotify |Linux 2.6.37+ | | +|USN Journals |Windows |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/53)| |Polling |*All* |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/9)| -| |Plan 9 | | \* Android and iOS are untested. @@ -23,31 +25,35 @@ Please see [the documentation](https://godoc.org/gopkg.in/fsnotify.v1) for usage Two major versions of fsnotify exist. -**[fsnotify.v1](https://gopkg.in/fsnotify.v1)** provides [a new API](https://godoc.org/gopkg.in/fsnotify.v1) based on [this design document](http://goo.gl/MrYxyA). You can import v1 with: - -```go -import "gopkg.in/fsnotify.v1" -``` - -\* Refer to the package as fsnotify (without the .v1 suffix). - **[fsnotify.v0](https://gopkg.in/fsnotify.v0)** is API-compatible with [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify). Bugfixes *may* be backported, but I recommend upgrading to v1. ```go import "gopkg.in/fsnotify.v0" ``` +\* Refer to the package as fsnotify (without the .v0 suffix). + +**[fsnotify.v1](https://gopkg.in/fsnotify.v1)** provides [a new API](https://godoc.org/gopkg.in/fsnotify.v1) based on [this design document](http://goo.gl/MrYxyA). You can import v1 with: + +```go +import "gopkg.in/fsnotify.v1" +``` + Further API changes are [planned](https://github.com/go-fsnotify/fsnotify/milestones), but a new major revision will be tagged, so you can depend on the v1 API. +**Master** may have unreleased changes. Use it to test the very latest code or when [contributing][], but don't expect it to remain API-compatible: + +```go +import "github.com/go-fsnotify/fsnotify" +``` + ## Contributing -* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com). -* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues). - -A future version of Go will have [fsnotify in the standard library](https://code.google.com/p/go/issues/detail?id=4068), therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so we need you to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual). - -Please read [CONTRIBUTING](https://github.com/go-fsnotify/fsnotify/blob/master/CONTRIBUTING.md) before opening a pull request. +Please refer to [CONTRIBUTING][] before opening an issue or pull request. ## Example See [example_test.go](https://github.com/go-fsnotify/fsnotify/blob/master/example_test.go). + + +[contributing]: https://github.com/go-fsnotify/fsnotify/blob/master/CONTRIBUTING.md diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/circle.yml b/vendor/src/github.com/go-fsnotify/fsnotify/circle.yml new file mode 100644 index 000000000..204217fb0 --- /dev/null +++ b/vendor/src/github.com/go-fsnotify/fsnotify/circle.yml @@ -0,0 +1,26 @@ +## OS X build (CircleCI iOS beta) + +# Pretend like it's an Xcode project, at least to get it running. +machine: + environment: + XCODE_WORKSPACE: NotUsed.xcworkspace + XCODE_SCHEME: NotUsed + # This is where the go project is actually checked out to: + CIRCLE_BUILD_DIR: $HOME/.go_project/src/github.com/go-fsnotify/fsnotify + +dependencies: + pre: + - brew upgrade go + +test: + override: + - go test ./... + +# Idealized future config, eventually with cross-platform build matrix :-) + +# machine: +# go: +# version: 1.4 +# os: +# - osx +# - linux diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/example_test.go b/vendor/src/github.com/go-fsnotify/fsnotify/example_test.go index 9f2c63f47..306379660 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/example_test.go +++ b/vendor/src/github.com/go-fsnotify/fsnotify/example_test.go @@ -9,7 +9,7 @@ package fsnotify_test import ( "log" - "gopkg.in/fsnotify.v1" + "github.com/go-fsnotify/fsnotify" ) func ExampleNewWatcher() { diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/fsnotify.go b/vendor/src/github.com/go-fsnotify/fsnotify/fsnotify.go index 7b5233f4b..c899ee008 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/fsnotify.go +++ b/vendor/src/github.com/go-fsnotify/fsnotify/fsnotify.go @@ -7,7 +7,10 @@ // Package fsnotify provides a platform-independent interface for file system notifications. package fsnotify -import "fmt" +import ( + "bytes" + "fmt" +) // Event represents a single file system notification. type Event struct { @@ -30,27 +33,30 @@ const ( // String returns a string representation of the event in the form // "file: REMOVE|WRITE|..." func (e Event) String() string { - events := "" + // Use a buffer for efficient string concatenation + var buffer bytes.Buffer if e.Op&Create == Create { - events += "|CREATE" + buffer.WriteString("|CREATE") } if e.Op&Remove == Remove { - events += "|REMOVE" + buffer.WriteString("|REMOVE") } if e.Op&Write == Write { - events += "|WRITE" + buffer.WriteString("|WRITE") } if e.Op&Rename == Rename { - events += "|RENAME" + buffer.WriteString("|RENAME") } if e.Op&Chmod == Chmod { - events += "|CHMOD" + buffer.WriteString("|CHMOD") } - if len(events) > 0 { - events = events[1:] + // If buffer remains empty, return no event names + if buffer.Len() == 0 { + return fmt.Sprintf("%q: ", e.Name) } - return fmt.Sprintf("%q: %s", e.Name, events) + // Return a list of event names, with leading pipe character stripped + return fmt.Sprintf("%q: %s", e.Name, buffer.String()[1:]) } diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/inotify.go b/vendor/src/github.com/go-fsnotify/fsnotify/inotify.go index f5c0aaef0..d7759ec8c 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/inotify.go +++ b/vendor/src/github.com/go-fsnotify/fsnotify/inotify.go @@ -9,6 +9,7 @@ package fsnotify import ( "errors" "fmt" + "io" "os" "path/filepath" "strings" @@ -21,47 +22,66 @@ import ( type Watcher struct { Events chan Event Errors chan error - mu sync.Mutex // Map access - fd int // File descriptor (as returned by the inotify_init() syscall) + mu sync.Mutex // Map access + fd int + poller *fdPoller watches map[string]*watch // Map of inotify watches (key: path) paths map[int]string // Map of watched paths (key: watch descriptor) - done chan bool // Channel for sending a "quit message" to the reader goroutine - isClosed bool // Set to true when Close() is first called + done chan struct{} // Channel for sending a "quit message" to the reader goroutine + doneResp chan struct{} // Channel to respond to Close } // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. func NewWatcher() (*Watcher, error) { + // Create inotify fd fd, errno := syscall.InotifyInit() if fd == -1 { - return nil, os.NewSyscallError("inotify_init", errno) + return nil, errno + } + // Create epoll + poller, err := newFdPoller(fd) + if err != nil { + syscall.Close(fd) + return nil, err } w := &Watcher{ - fd: fd, - watches: make(map[string]*watch), - paths: make(map[int]string), - Events: make(chan Event), - Errors: make(chan error), - done: make(chan bool, 1), + fd: fd, + poller: poller, + watches: make(map[string]*watch), + paths: make(map[int]string), + Events: make(chan Event), + Errors: make(chan error), + done: make(chan struct{}), + doneResp: make(chan struct{}), } go w.readEvents() return w, nil } +func (w *Watcher) isClosed() bool { + select { + case <-w.done: + return true + default: + return false + } +} + // Close removes all watches and closes the events channel. func (w *Watcher) Close() error { - if w.isClosed { + if w.isClosed() { return nil } - w.isClosed = true - // Remove all watches - for name := range w.watches { - w.Remove(name) - } + // Send 'close' signal to goroutine, and set the Watcher to closed. + close(w.done) - // Send "quit" message to the reader goroutine - w.done <- true + // Wake up goroutine + w.poller.wake() + + // Wait for goroutine to close + <-w.doneResp return nil } @@ -69,7 +89,7 @@ func (w *Watcher) Close() error { // Add starts watching the named file or directory (non-recursively). func (w *Watcher) Add(name string) error { name = filepath.Clean(name) - if w.isClosed { + if w.isClosed() { return errors.New("inotify instance already closed") } @@ -88,7 +108,7 @@ func (w *Watcher) Add(name string) error { } wd, errno := syscall.InotifyAddWatch(w.fd, name, flags) if wd == -1 { - return os.NewSyscallError("inotify_add_watch", errno) + return errno } w.mu.Lock() @@ -99,20 +119,33 @@ func (w *Watcher) Add(name string) error { return nil } -// Remove stops watching the the named file or directory (non-recursively). +// Remove stops watching the named file or directory (non-recursively). func (w *Watcher) Remove(name string) error { name = filepath.Clean(name) + + // Fetch the watch. w.mu.Lock() defer w.mu.Unlock() watch, ok := w.watches[name] + + // Remove it from inotify. if !ok { return fmt.Errorf("can't remove non-existent inotify watch for: %s", name) } + // inotify_rm_watch will return EINVAL if the file has been deleted; + // the inotify will already have been removed. + // That means we can safely delete it from our watches, whatever inotify_rm_watch does. + delete(w.watches, name) success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) if success == -1 { - return os.NewSyscallError("inotify_rm_watch", errno) + // TODO: Perhaps it's not helpful to return an error here in every case. + // the only two possible errors are: + // EBADF, which happens when w.fd is not a valid file descriptor of any kind. + // EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor. + // Watch descriptors are invalidated when they are removed explicitly or implicitly; + // explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted. + return errno } - delete(w.watches, name) return nil } @@ -128,35 +161,65 @@ func (w *Watcher) readEvents() { buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events n int // Number of bytes read with read() errno error // Syscall errno + ok bool // For poller.wait ) + defer close(w.doneResp) + defer close(w.Errors) + defer close(w.Events) + defer syscall.Close(w.fd) + defer w.poller.close() + for { - // See if there is a message on the "done" channel - select { - case <-w.done: - syscall.Close(w.fd) - close(w.Events) - close(w.Errors) + // See if we have been closed. + if w.isClosed() { return - default: + } + + ok, errno = w.poller.wait() + if errno != nil { + select { + case w.Errors <- errno: + case <-w.done: + return + } + continue + } + + if !ok { + continue } n, errno = syscall.Read(w.fd, buf[:]) + // If a signal interrupted execution, see if we've been asked to close, and try again. + // http://man7.org/linux/man-pages/man7/signal.7.html : + // "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable" + if errno == syscall.EINTR { + continue + } - // If EOF is received - if n == 0 { - syscall.Close(w.fd) - close(w.Events) - close(w.Errors) + // syscall.Read might have been woken up by Close. If so, we're done. + if w.isClosed() { return } - if n < 0 { - w.Errors <- os.NewSyscallError("read", errno) - continue - } if n < syscall.SizeofInotifyEvent { - w.Errors <- errors.New("inotify: short read in readEvents()") + var err error + if n == 0 { + // If EOF is received. This should really never happen. + err = io.EOF + } else if n < 0 { + // If an error occured while reading. + err = errno + } else { + // Read was too short. + err = errors.New("notify: short read in readEvents()") + } + select { + case w.Errors <- err: + case <-w.done: + return + } continue } @@ -187,7 +250,11 @@ func (w *Watcher) readEvents() { // Send the events that are not ignored on the events channel if !event.ignoreLinux(mask) { - w.Events <- event + select { + case w.Events <- event: + case <-w.done: + return + } } // Move to the next event in the buffer diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller.go b/vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller.go new file mode 100644 index 000000000..3b4178404 --- /dev/null +++ b/vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller.go @@ -0,0 +1,186 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux + +package fsnotify + +import ( + "errors" + "syscall" +) + +type fdPoller struct { + fd int // File descriptor (as returned by the inotify_init() syscall) + epfd int // Epoll file descriptor + pipe [2]int // Pipe for waking up +} + +func emptyPoller(fd int) *fdPoller { + poller := new(fdPoller) + poller.fd = fd + poller.epfd = -1 + poller.pipe[0] = -1 + poller.pipe[1] = -1 + return poller +} + +// Create a new inotify poller. +// This creates an inotify handler, and an epoll handler. +func newFdPoller(fd int) (*fdPoller, error) { + var errno error + poller := emptyPoller(fd) + defer func() { + if errno != nil { + poller.close() + } + }() + poller.fd = fd + + // Create epoll fd + poller.epfd, errno = syscall.EpollCreate(1) + if poller.epfd == -1 { + return nil, errno + } + // Create pipe; pipe[0] is the read end, pipe[1] the write end. + errno = syscall.Pipe2(poller.pipe[:], syscall.O_NONBLOCK) + if errno != nil { + return nil, errno + } + + // Register inotify fd with epoll + event := syscall.EpollEvent{ + Fd: int32(poller.fd), + Events: syscall.EPOLLIN, + } + errno = syscall.EpollCtl(poller.epfd, syscall.EPOLL_CTL_ADD, poller.fd, &event) + if errno != nil { + return nil, errno + } + + // Register pipe fd with epoll + event = syscall.EpollEvent{ + Fd: int32(poller.pipe[0]), + Events: syscall.EPOLLIN, + } + errno = syscall.EpollCtl(poller.epfd, syscall.EPOLL_CTL_ADD, poller.pipe[0], &event) + if errno != nil { + return nil, errno + } + + return poller, nil +} + +// Wait using epoll. +// Returns true if something is ready to be read, +// false if there is not. +func (poller *fdPoller) wait() (bool, error) { + // 3 possible events per fd, and 2 fds, makes a maximum of 6 events. + // I don't know whether epoll_wait returns the number of events returned, + // or the total number of events ready. + // I decided to catch both by making the buffer one larger than the maximum. + events := make([]syscall.EpollEvent, 7) + for { + n, errno := syscall.EpollWait(poller.epfd, events, -1) + if n == -1 { + if errno == syscall.EINTR { + continue + } + return false, errno + } + if n == 0 { + // If there are no events, try again. + continue + } + if n > 6 { + // This should never happen. More events were returned than should be possible. + return false, errors.New("epoll_wait returned more events than I know what to do with") + } + ready := events[:n] + epollhup := false + epollerr := false + epollin := false + for _, event := range ready { + if event.Fd == int32(poller.fd) { + if event.Events&syscall.EPOLLHUP != 0 { + // This should not happen, but if it does, treat it as a wakeup. + epollhup = true + } + if event.Events&syscall.EPOLLERR != 0 { + // If an error is waiting on the file descriptor, we should pretend + // something is ready to read, and let syscall.Read pick up the error. + epollerr = true + } + if event.Events&syscall.EPOLLIN != 0 { + // There is data to read. + epollin = true + } + } + if event.Fd == int32(poller.pipe[0]) { + if event.Events&syscall.EPOLLHUP != 0 { + // Write pipe descriptor was closed, by us. This means we're closing down the + // watcher, and we should wake up. + } + if event.Events&syscall.EPOLLERR != 0 { + // If an error is waiting on the pipe file descriptor. + // This is an absolute mystery, and should never ever happen. + return false, errors.New("Error on the pipe descriptor.") + } + if event.Events&syscall.EPOLLIN != 0 { + // This is a regular wakeup, so we have to clear the buffer. + err := poller.clearWake() + if err != nil { + return false, err + } + } + } + } + + if epollhup || epollerr || epollin { + return true, nil + } + return false, nil + } +} + +// Close the write end of the poller. +func (poller *fdPoller) wake() error { + buf := make([]byte, 1) + n, errno := syscall.Write(poller.pipe[1], buf) + if n == -1 { + if errno == syscall.EAGAIN { + // Buffer is full, poller will wake. + return nil + } + return errno + } + return nil +} + +func (poller *fdPoller) clearWake() error { + // You have to be woken up a LOT in order to get to 100! + buf := make([]byte, 100) + n, errno := syscall.Read(poller.pipe[0], buf) + if n == -1 { + if errno == syscall.EAGAIN { + // Buffer is empty, someone else cleared our wake. + return nil + } + return errno + } + return nil +} + +// Close all poller file descriptors, but not the one passed to it. +func (poller *fdPoller) close() { + if poller.pipe[1] != -1 { + syscall.Close(poller.pipe[1]) + } + if poller.pipe[0] != -1 { + syscall.Close(poller.pipe[0]) + } + if poller.epfd != -1 { + syscall.Close(poller.epfd) + } +} diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller_test.go b/vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller_test.go new file mode 100644 index 000000000..af9f407f8 --- /dev/null +++ b/vendor/src/github.com/go-fsnotify/fsnotify/inotify_poller_test.go @@ -0,0 +1,228 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux + +package fsnotify + +import ( + "syscall" + "testing" + "time" +) + +type testFd [2]int + +func makeTestFd(t *testing.T) testFd { + var tfd testFd + errno := syscall.Pipe(tfd[:]) + if errno != nil { + t.Fatalf("Failed to create pipe: %v", errno) + } + return tfd +} + +func (tfd testFd) fd() int { + return tfd[0] +} + +func (tfd testFd) closeWrite(t *testing.T) { + errno := syscall.Close(tfd[1]) + if errno != nil { + t.Fatalf("Failed to close write end of pipe: %v", errno) + } +} + +func (tfd testFd) put(t *testing.T) { + buf := make([]byte, 10) + _, errno := syscall.Write(tfd[1], buf) + if errno != nil { + t.Fatalf("Failed to write to pipe: %v", errno) + } +} + +func (tfd testFd) get(t *testing.T) { + buf := make([]byte, 10) + _, errno := syscall.Read(tfd[0], buf) + if errno != nil { + t.Fatalf("Failed to read from pipe: %v", errno) + } +} + +func (tfd testFd) close() { + syscall.Close(tfd[1]) + syscall.Close(tfd[0]) +} + +func makePoller(t *testing.T) (testFd, *fdPoller) { + tfd := makeTestFd(t) + poller, err := newFdPoller(tfd.fd()) + if err != nil { + t.Fatalf("Failed to create poller: %v", err) + } + return tfd, poller +} + +func TestPollerWithBadFd(t *testing.T) { + _, err := newFdPoller(-1) + if err != syscall.EBADF { + t.Fatalf("Expected EBADF, got: %v", err) + } +} + +func TestPollerWithData(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + tfd.put(t) + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if !ok { + t.Fatalf("expected poller to return true") + } + tfd.get(t) +} + +func TestPollerWithWakeup(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + err := poller.wake() + if err != nil { + t.Fatalf("wake failed: %v", err) + } + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if ok { + t.Fatalf("expected poller to return false") + } +} + +func TestPollerWithClose(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + tfd.closeWrite(t) + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if !ok { + t.Fatalf("expected poller to return true") + } +} + +func TestPollerWithWakeupAndData(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + tfd.put(t) + err := poller.wake() + if err != nil { + t.Fatalf("wake failed: %v", err) + } + + // both data and wakeup + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if !ok { + t.Fatalf("expected poller to return true") + } + + // data is still in the buffer, wakeup is cleared + ok, err = poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if !ok { + t.Fatalf("expected poller to return true") + } + + tfd.get(t) + // data is gone, only wakeup now + err = poller.wake() + if err != nil { + t.Fatalf("wake failed: %v", err) + } + ok, err = poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + if ok { + t.Fatalf("expected poller to return false") + } +} + +func TestPollerConcurrent(t *testing.T) { + tfd, poller := makePoller(t) + defer tfd.close() + defer poller.close() + + oks := make(chan bool) + live := make(chan bool) + defer close(live) + go func() { + defer close(oks) + for { + ok, err := poller.wait() + if err != nil { + t.Fatalf("poller failed: %v", err) + } + oks <- ok + if !<-live { + return + } + } + }() + + // Try a write + select { + case <-time.After(50 * time.Millisecond): + case <-oks: + t.Fatalf("poller did not wait") + } + tfd.put(t) + if !<-oks { + t.Fatalf("expected true") + } + tfd.get(t) + live <- true + + // Try a wakeup + select { + case <-time.After(50 * time.Millisecond): + case <-oks: + t.Fatalf("poller did not wait") + } + err := poller.wake() + if err != nil { + t.Fatalf("wake failed: %v", err) + } + if <-oks { + t.Fatalf("expected false") + } + live <- true + + // Try a close + select { + case <-time.After(50 * time.Millisecond): + case <-oks: + t.Fatalf("poller did not wait") + } + tfd.closeWrite(t) + if !<-oks { + t.Fatalf("expected true") + } + tfd.get(t) +} diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/inotify_test.go b/vendor/src/github.com/go-fsnotify/fsnotify/inotify_test.go new file mode 100644 index 000000000..035ee8f95 --- /dev/null +++ b/vendor/src/github.com/go-fsnotify/fsnotify/inotify_test.go @@ -0,0 +1,292 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build linux + +package fsnotify + +import ( + "os" + "path/filepath" + "syscall" + "testing" + "time" +) + +func TestInotifyCloseRightAway(t *testing.T) { + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher") + } + + // Close immediately; it won't even reach the first syscall.Read. + w.Close() + + // Wait for the close to complete. + <-time.After(50 * time.Millisecond) + isWatcherReallyClosed(t, w) +} + +func TestInotifyCloseSlightlyLater(t *testing.T) { + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher") + } + + // Wait until readEvents has reached syscall.Read, and Close. + <-time.After(50 * time.Millisecond) + w.Close() + + // Wait for the close to complete. + <-time.After(50 * time.Millisecond) + isWatcherReallyClosed(t, w) +} + +func TestInotifyCloseSlightlyLaterWithWatch(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher") + } + w.Add(testDir) + + // Wait until readEvents has reached syscall.Read, and Close. + <-time.After(50 * time.Millisecond) + w.Close() + + // Wait for the close to complete. + <-time.After(50 * time.Millisecond) + isWatcherReallyClosed(t, w) +} + +func TestInotifyCloseAfterRead(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher") + } + + err = w.Add(testDir) + if err != nil { + t.Fatalf("Failed to add .") + } + + // Generate an event. + os.Create(filepath.Join(testDir, "somethingSOMETHINGsomethingSOMETHING")) + + // Wait for readEvents to read the event, then close the watcher. + <-time.After(50 * time.Millisecond) + w.Close() + + // Wait for the close to complete. + <-time.After(50 * time.Millisecond) + isWatcherReallyClosed(t, w) +} + +func isWatcherReallyClosed(t *testing.T, w *Watcher) { + select { + case err, ok := <-w.Errors: + if ok { + t.Fatalf("w.Errors is not closed; readEvents is still alive after closing (error: %v)", err) + } + default: + t.Fatalf("w.Errors would have blocked; readEvents is still alive!") + } + + select { + case _, ok := <-w.Events: + if ok { + t.Fatalf("w.Events is not closed; readEvents is still alive after closing") + } + default: + t.Fatalf("w.Events would have blocked; readEvents is still alive!") + } +} + +func TestInotifyCloseCreate(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + err = w.Add(testDir) + if err != nil { + t.Fatalf("Failed to add testDir: %v", err) + } + h, err := os.Create(filepath.Join(testDir, "testfile")) + if err != nil { + t.Fatalf("Failed to create file in testdir: %v", err) + } + h.Close() + select { + case _ = <-w.Events: + case err := <-w.Errors: + t.Fatalf("Error from watcher: %v", err) + case <-time.After(50 * time.Millisecond): + t.Fatalf("Took too long to wait for event") + } + + // At this point, we've received one event, so the goroutine is ready. + // It's also blocking on syscall.Read. + // Now we try to swap the file descriptor under its nose. + w.Close() + w, err = NewWatcher() + defer w.Close() + if err != nil { + t.Fatalf("Failed to create second watcher: %v", err) + } + + <-time.After(50 * time.Millisecond) + err = w.Add(testDir) + if err != nil { + t.Fatalf("Error adding testDir again: %v", err) + } +} + +func TestInotifyStress(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + testFile := filepath.Join(testDir, "testfile") + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + killchan := make(chan struct{}) + defer close(killchan) + + err = w.Add(testDir) + if err != nil { + t.Fatalf("Failed to add testDir: %v", err) + } + + proc, err := os.FindProcess(os.Getpid()) + if err != nil { + t.Fatalf("Error finding process: %v", err) + } + + go func() { + for { + select { + case <-time.After(5 * time.Millisecond): + err := proc.Signal(syscall.SIGUSR1) + if err != nil { + t.Fatalf("Signal failed: %v", err) + } + case <-killchan: + return + } + } + }() + + go func() { + for { + select { + case <-time.After(11 * time.Millisecond): + err := w.poller.wake() + if err != nil { + t.Fatalf("Wake failed: %v", err) + } + case <-killchan: + return + } + } + }() + + go func() { + for { + select { + case <-killchan: + return + default: + handle, err := os.Create(testFile) + if err != nil { + t.Fatalf("Create failed: %v", err) + } + handle.Close() + time.Sleep(time.Millisecond) + err = os.Remove(testFile) + if err != nil { + t.Fatalf("Remove failed: %v", err) + } + } + } + }() + + creates := 0 + removes := 0 + after := time.After(5 * time.Second) + for { + select { + case <-after: + if creates-removes > 1 || creates-removes < -1 { + t.Fatalf("Creates and removes should not be off by more than one: %d creates, %d removes", creates, removes) + } + if creates < 50 { + t.Fatalf("Expected at least 50 creates, got %d", creates) + } + return + case err := <-w.Errors: + t.Fatalf("Got an error from watcher: %v", err) + case evt := <-w.Events: + if evt.Name != testFile { + t.Fatalf("Got an event for an unknown file: %s", evt.Name) + } + if evt.Op == Create { + creates++ + } + if evt.Op == Remove { + removes++ + } + } + } +} + +func TestInotifyRemoveTwice(t *testing.T) { + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + testFile := filepath.Join(testDir, "testfile") + + handle, err := os.Create(testFile) + if err != nil { + t.Fatalf("Create failed: %v", err) + } + handle.Close() + + w, err := NewWatcher() + if err != nil { + t.Fatalf("Failed to create watcher: %v", err) + } + defer w.Close() + + err = w.Add(testFile) + if err != nil { + t.Fatalf("Failed to add testFile: %v", err) + } + + err = os.Remove(testFile) + if err != nil { + t.Fatalf("Failed to remove testFile: %v", err) + } + + err = w.Remove(testFile) + if err != syscall.EINVAL { + t.Fatalf("Expected EINVAL from Remove, got: %v", err) + } + + err = w.Remove(testFile) + if err == syscall.EINVAL { + t.Fatalf("Got EINVAL again, watch was not removed") + } +} diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/integration_test.go b/vendor/src/github.com/go-fsnotify/fsnotify/integration_test.go index ad51ab60b..59169c6af 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/integration_test.go +++ b/vendor/src/github.com/go-fsnotify/fsnotify/integration_test.go @@ -1109,6 +1109,21 @@ func TestConcurrentRemovalOfWatch(t *testing.T) { <-removed2 } +func TestClose(t *testing.T) { + // Regression test for #59 bad file descriptor from Close + testDir := tempMkdir(t) + defer os.RemoveAll(testDir) + + watcher := newWatcher(t) + if err := watcher.Add(testDir); err != nil { + t.Fatalf("Expected no error on Add, got %v", err) + } + err := watcher.Close() + if err != nil { + t.Fatalf("Expected no error on Close, got %v.", err) + } +} + func testRename(file1, file2 string) error { switch runtime.GOOS { case "windows", "plan9": diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/kqueue.go b/vendor/src/github.com/go-fsnotify/fsnotify/kqueue.go index 5ef1346c0..265622d20 100644 --- a/vendor/src/github.com/go-fsnotify/fsnotify/kqueue.go +++ b/vendor/src/github.com/go-fsnotify/fsnotify/kqueue.go @@ -14,46 +14,48 @@ import ( "path/filepath" "sync" "syscall" + "time" ) // Watcher watches a set of files, delivering events to a channel. type Watcher struct { - Events chan Event - Errors chan error - mu sync.Mutex // Mutex for the Watcher itself. - kq int // File descriptor (as returned by the kqueue() syscall). - watches map[string]int // Map of watched file descriptors (key: path). - wmut sync.Mutex // Protects access to watches. - enFlags map[string]uint32 // Map of watched files to evfilt note flags used in kqueue. - enmut sync.Mutex // Protects access to enFlags. - paths map[int]string // Map of watched paths (key: watch descriptor). - finfo map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor). - pmut sync.Mutex // Protects access to paths and finfo. - fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events). - femut sync.Mutex // Protects access to fileExists. - externalWatches map[string]bool // Map of watches added by user of the library. - ewmut sync.Mutex // Protects access to externalWatches. - done chan bool // Channel for sending a "quit message" to the reader goroutine - isClosed bool // Set to true when Close() is first called + Events chan Event + Errors chan error + done chan bool // Channel for sending a "quit message" to the reader goroutine + + kq int // File descriptor (as returned by the kqueue() syscall). + + mu sync.Mutex // Protects access to watcher data + watches map[string]int // Map of watched file descriptors (key: path). + externalWatches map[string]bool // Map of watches added by user of the library. + dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue. + paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events. + fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events). + isClosed bool // Set to true when Close() is first called +} + +type pathInfo struct { + name string + isDir bool } // NewWatcher establishes a new watcher with the underlying OS and begins waiting for events. func NewWatcher() (*Watcher, error) { - fd, errno := syscall.Kqueue() - if fd == -1 { - return nil, os.NewSyscallError("kqueue", errno) + kq, err := kqueue() + if err != nil { + return nil, err } + w := &Watcher{ - kq: fd, + kq: kq, watches: make(map[string]int), - enFlags: make(map[string]uint32), - paths: make(map[int]string), - finfo: make(map[int]os.FileInfo), + dirFlags: make(map[string]uint32), + paths: make(map[int]pathInfo), fileExists: make(map[string]bool), externalWatches: make(map[string]bool), Events: make(chan Event), Errors: make(chan error), - done: make(chan bool, 1), + done: make(chan bool), } go w.readEvents() @@ -70,73 +72,68 @@ func (w *Watcher) Close() error { w.isClosed = true w.mu.Unlock() + w.mu.Lock() + ws := w.watches + w.mu.Unlock() + + var err error + for name := range ws { + if e := w.Remove(name); e != nil && err == nil { + err = e + } + } + // Send "quit" message to the reader goroutine: w.done <- true - w.wmut.Lock() - ws := w.watches - w.wmut.Unlock() - for name := range ws { - w.Remove(name) - } return nil } // Add starts watching the named file or directory (non-recursively). func (w *Watcher) Add(name string) error { - w.ewmut.Lock() + w.mu.Lock() w.externalWatches[name] = true - w.ewmut.Unlock() + w.mu.Unlock() return w.addWatch(name, noteAllEvents) } // Remove stops watching the the named file or directory (non-recursively). func (w *Watcher) Remove(name string) error { name = filepath.Clean(name) - w.wmut.Lock() + w.mu.Lock() watchfd, ok := w.watches[name] - w.wmut.Unlock() + w.mu.Unlock() if !ok { return fmt.Errorf("can't remove non-existent kevent watch for: %s", name) } - var kbuf [1]syscall.Kevent_t - watchEntry := &kbuf[0] - syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_DELETE) - entryFlags := watchEntry.Flags - success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil) - if success == -1 { - return os.NewSyscallError("kevent_rm_watch", errno) - } else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR { - return errors.New("kevent rm error") + + const registerRemove = syscall.EV_DELETE + if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil { + return err } + syscall.Close(watchfd) - w.wmut.Lock() + + w.mu.Lock() + isDir := w.paths[watchfd].isDir delete(w.watches, name) - w.wmut.Unlock() - w.enmut.Lock() - delete(w.enFlags, name) - w.enmut.Unlock() - w.pmut.Lock() delete(w.paths, watchfd) - fInfo := w.finfo[watchfd] - delete(w.finfo, watchfd) - w.pmut.Unlock() + delete(w.dirFlags, name) + w.mu.Unlock() // Find all watched paths that are in this directory that are not external. - if fInfo.IsDir() { + if isDir { var pathsToRemove []string - w.pmut.Lock() - for _, wpath := range w.paths { - wdir, _ := filepath.Split(wpath) - if filepath.Clean(wdir) == filepath.Clean(name) { - w.ewmut.Lock() - if !w.externalWatches[wpath] { - pathsToRemove = append(pathsToRemove, wpath) + w.mu.Lock() + for _, path := range w.paths { + wdir, _ := filepath.Split(path.name) + if filepath.Clean(wdir) == name { + if !w.externalWatches[path.name] { + pathsToRemove = append(pathsToRemove, path.name) } - w.ewmut.Unlock() } } - w.pmut.Unlock() + w.mu.Unlock() for _, name := range pathsToRemove { // Since these are internal, not much sense in propagating error // to the user, as that will just confuse them with an error about @@ -148,37 +145,38 @@ func (w *Watcher) Remove(name string) error { return nil } -const ( - // Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) - noteAllEvents = syscall.NOTE_DELETE | syscall.NOTE_WRITE | syscall.NOTE_ATTRIB | syscall.NOTE_RENAME +// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE) +const noteAllEvents = syscall.NOTE_DELETE | syscall.NOTE_WRITE | syscall.NOTE_ATTRIB | syscall.NOTE_RENAME - // Block for 100 ms on each call to kevent - keventWaitTime = 100e6 -) +// keventWaitTime to block on each read from kevent +var keventWaitTime = durationToTimespec(100 * time.Millisecond) -// addWatch adds path to the watched file set. +// addWatch adds name to the watched file set. // The flags are interpreted as described in kevent(2). -func (w *Watcher) addWatch(path string, flags uint32) error { - path = filepath.Clean(path) +func (w *Watcher) addWatch(name string, flags uint32) error { + var isDir bool + // Make ./name and name equivalent + name = filepath.Clean(name) + w.mu.Lock() if w.isClosed { w.mu.Unlock() return errors.New("kevent instance already closed") } + watchfd, alreadyWatching := w.watches[name] + // We already have a watch, but we can still override flags. + if alreadyWatching { + isDir = w.paths[watchfd].isDir + } w.mu.Unlock() - watchDir := false - - w.wmut.Lock() - watchfd, found := w.watches[path] - w.wmut.Unlock() - if !found { - fi, errstat := os.Lstat(path) - if errstat != nil { - return errstat + if !alreadyWatching { + fi, err := os.Lstat(name) + if err != nil { + return err } - // don't watch socket + // Don't watch sockets. if fi.Mode()&os.ModeSocket == os.ModeSocket { return nil } @@ -190,131 +188,96 @@ func (w *Watcher) addWatch(path string, flags uint32) error { // be no file events for broken symlinks. // Hence the returns of nil on errors. if fi.Mode()&os.ModeSymlink == os.ModeSymlink { - path, err := filepath.EvalSymlinks(path) + name, err = filepath.EvalSymlinks(name) if err != nil { return nil } - fi, errstat = os.Lstat(path) - if errstat != nil { + fi, err = os.Lstat(name) + if err != nil { return nil } } - fd, errno := syscall.Open(path, openMode, 0700) - if fd == -1 { - return os.NewSyscallError("Open", errno) + watchfd, err = syscall.Open(name, openMode, 0700) + if watchfd == -1 { + return err } - watchfd = fd - w.wmut.Lock() - w.watches[path] = watchfd - w.wmut.Unlock() - - w.pmut.Lock() - w.paths[watchfd] = path - w.finfo[watchfd] = fi - w.pmut.Unlock() - } - // Watch the directory if it has not been watched before. - w.pmut.Lock() - w.enmut.Lock() - if w.finfo[watchfd].IsDir() && - (flags&syscall.NOTE_WRITE) == syscall.NOTE_WRITE && - (!found || (w.enFlags[path]&syscall.NOTE_WRITE) != syscall.NOTE_WRITE) { - watchDir = true - } - w.enmut.Unlock() - w.pmut.Unlock() - - w.enmut.Lock() - w.enFlags[path] = flags - w.enmut.Unlock() - - var kbuf [1]syscall.Kevent_t - watchEntry := &kbuf[0] - watchEntry.Fflags = flags - syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_ADD|syscall.EV_CLEAR) - entryFlags := watchEntry.Flags - success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil) - if success == -1 { - return errno - } else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR { - return errors.New("kevent add error") + isDir = fi.IsDir() } - if watchDir { - errdir := w.watchDirectoryFiles(path) - if errdir != nil { - return errdir + const registerAdd = syscall.EV_ADD | syscall.EV_CLEAR | syscall.EV_ENABLE + if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil { + syscall.Close(watchfd) + return err + } + + if !alreadyWatching { + w.mu.Lock() + w.watches[name] = watchfd + w.paths[watchfd] = pathInfo{name: name, isDir: isDir} + w.mu.Unlock() + } + + if isDir { + // Watch the directory if it has not been watched before, + // or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles) + w.mu.Lock() + watchDir := (flags&syscall.NOTE_WRITE) == syscall.NOTE_WRITE && + (!alreadyWatching || (w.dirFlags[name]&syscall.NOTE_WRITE) != syscall.NOTE_WRITE) + // Store flags so this watch can be updated later + w.dirFlags[name] = flags + w.mu.Unlock() + + if watchDir { + if err := w.watchDirectoryFiles(name); err != nil { + return err + } } } return nil } -// readEvents reads from the kqueue file descriptor, converts the -// received events into Event objects and sends them via the Events channel +// readEvents reads from kqueue and converts the received kevents into +// Event values that it sends down the Events channel. func (w *Watcher) readEvents() { - var ( - keventbuf [10]syscall.Kevent_t // Event buffer - kevents []syscall.Kevent_t // Received events - twait *syscall.Timespec // Time to block waiting for events - n int // Number of events returned from kevent - errno error // Syscall errno - ) - kevents = keventbuf[0:0] - twait = new(syscall.Timespec) - *twait = syscall.NsecToTimespec(keventWaitTime) + eventBuffer := make([]syscall.Kevent_t, 10) for { // See if there is a message on the "done" channel - var done bool select { - case done = <-w.done: - default: - } - - // If "done" message is received - if done { - errno := syscall.Close(w.kq) - if errno != nil { - w.Errors <- os.NewSyscallError("close", errno) + case <-w.done: + err := syscall.Close(w.kq) + if err != nil { + w.Errors <- err } close(w.Events) close(w.Errors) return + default: } // Get new events - if len(kevents) == 0 { - n, errno = syscall.Kevent(w.kq, nil, keventbuf[:], twait) - - // EINTR is okay, basically the syscall was interrupted before - // timeout expired. - if errno != nil && errno != syscall.EINTR { - w.Errors <- os.NewSyscallError("kevent", errno) - continue - } - - // Received some events - if n > 0 { - kevents = keventbuf[0:n] - } + kevents, err := read(w.kq, eventBuffer, &keventWaitTime) + // EINTR is okay, the syscall was interrupted before timeout expired. + if err != nil && err != syscall.EINTR { + w.Errors <- err + continue } // Flush the events we received to the Events channel for len(kevents) > 0 { - watchEvent := &kevents[0] - mask := uint32(watchEvent.Fflags) - w.pmut.Lock() - name := w.paths[int(watchEvent.Ident)] - fileInfo := w.finfo[int(watchEvent.Ident)] - w.pmut.Unlock() + kevent := &kevents[0] + watchfd := int(kevent.Ident) + mask := uint32(kevent.Fflags) + w.mu.Lock() + path := w.paths[watchfd] + w.mu.Unlock() + event := newEvent(path.name, mask) - event := newEvent(name, mask, false) - - if fileInfo != nil && fileInfo.IsDir() && !(event.Op&Remove == Remove) { - // Double check to make sure the directory exist. This can happen when + if path.isDir && !(event.Op&Remove == Remove) { + // Double check to make sure the directory exists. This can happen when // we do a rm -fr on a recursively watched folders and we receive a // modification event first but the folder has been deleted and later // receive the delete event @@ -324,55 +287,49 @@ func (w *Watcher) readEvents() { } } - if fileInfo != nil && fileInfo.IsDir() && event.Op&Write == Write && !(event.Op&Remove == Remove) { + if event.Op&Rename == Rename || event.Op&Remove == Remove { + w.Remove(event.Name) + w.mu.Lock() + delete(w.fileExists, event.Name) + w.mu.Unlock() + } + + if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) { w.sendDirectoryChangeEvents(event.Name) } else { // Send the event on the Events channel w.Events <- event } - // Move to next event - kevents = kevents[1:] - - if event.Op&Rename == Rename { - w.Remove(event.Name) - w.femut.Lock() - delete(w.fileExists, event.Name) - w.femut.Unlock() - } if event.Op&Remove == Remove { - w.Remove(event.Name) - w.femut.Lock() - delete(w.fileExists, event.Name) - w.femut.Unlock() - - // Look for a file that may have overwritten this - // (ie mv f1 f2 will delete f2 then create f2) + // Look for a file that may have overwritten this. + // For example, mv f1 f2 will delete f2, then create f2. fileDir, _ := filepath.Split(event.Name) fileDir = filepath.Clean(fileDir) - w.wmut.Lock() + w.mu.Lock() _, found := w.watches[fileDir] - w.wmut.Unlock() + w.mu.Unlock() if found { - // make sure the directory exist before we watch for changes. When we + // make sure the directory exists before we watch for changes. When we // do a recursive watch and perform rm -fr, the parent directory might // have gone missing, ignore the missing directory and let the - // upcoming delete event remove the watch form the parent folder - if _, err := os.Lstat(fileDir); !os.IsNotExist(err) { + // upcoming delete event remove the watch from the parent directory. + if _, err := os.Lstat(fileDir); os.IsExist(err) { w.sendDirectoryChangeEvents(fileDir) + // FIXME: should this be for events on files or just isDir? } } } + + // Move to next event + kevents = kevents[1:] } } } // newEvent returns an platform-independent Event based on kqueue Fflags. -func newEvent(name string, mask uint32, create bool) Event { +func newEvent(name string, mask uint32) Event { e := Event{Name: name} - if create { - e.Op |= Create - } if mask&syscall.NOTE_DELETE == syscall.NOTE_DELETE { e.Op |= Remove } @@ -388,6 +345,11 @@ func newEvent(name string, mask uint32, create bool) Event { return e } +func newCreateEvent(name string) Event { + return Event{Name: name, Op: Create} +} + +// watchDirectoryFiles to mimic inotify when adding a watch on a directory func (w *Watcher) watchDirectoryFiles(dirPath string) error { // Get all files files, err := ioutil.ReadDir(dirPath) @@ -395,36 +357,15 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error { return err } - // Search for new files for _, fileInfo := range files { filePath := filepath.Join(dirPath, fileInfo.Name()) - - if fileInfo.IsDir() == false { - // Watch file to mimic linux fsnotify - e := w.addWatch(filePath, noteAllEvents) - if e != nil { - return e - } - } else { - // If the user is currently watching directory - // we want to preserve the flags used - w.enmut.Lock() - currFlags, found := w.enFlags[filePath] - w.enmut.Unlock() - var newFlags uint32 = syscall.NOTE_DELETE - if found { - newFlags |= currFlags - } - - // Linux gives deletes if not explicitly watching - e := w.addWatch(filePath, newFlags) - if e != nil { - return e - } + if err := w.internalWatch(filePath, fileInfo); err != nil { + return err } - w.femut.Lock() + + w.mu.Lock() w.fileExists[filePath] = true - w.femut.Unlock() + w.mu.Unlock() } return nil @@ -432,7 +373,7 @@ func (w *Watcher) watchDirectoryFiles(dirPath string) error { // sendDirectoryEvents searches the directory for newly created files // and sends them over the event channel. This functionality is to have -// the BSD version of fsnotify match linux fsnotify which provides a +// the BSD version of fsnotify match Linux inotify which provides a // create event for files created in a watched directory. func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { // Get all files @@ -444,36 +385,79 @@ func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { // Search for new files for _, fileInfo := range files { filePath := filepath.Join(dirPath, fileInfo.Name()) - w.femut.Lock() + w.mu.Lock() _, doesExist := w.fileExists[filePath] - w.femut.Unlock() + w.mu.Unlock() if !doesExist { - // Send create event (mask=0) - event := newEvent(filePath, 0, true) - w.Events <- event + // Send create event + w.Events <- newCreateEvent(filePath) } - // watchDirectoryFiles (but without doing another ReadDir) - if fileInfo.IsDir() == false { - // Watch file to mimic linux fsnotify - w.addWatch(filePath, noteAllEvents) - } else { - // If the user is currently watching directory - // we want to preserve the flags used - w.enmut.Lock() - currFlags, found := w.enFlags[filePath] - w.enmut.Unlock() - var newFlags uint32 = syscall.NOTE_DELETE - if found { - newFlags |= currFlags - } - - // Linux gives deletes if not explicitly watching - w.addWatch(filePath, newFlags) + // like watchDirectoryFiles (but without doing another ReadDir) + if err := w.internalWatch(filePath, fileInfo); err != nil { + return } - w.femut.Lock() + w.mu.Lock() w.fileExists[filePath] = true - w.femut.Unlock() + w.mu.Unlock() } } + +func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) error { + if fileInfo.IsDir() { + // mimic Linux providing delete events for subdirectories + // but preserve the flags used if currently watching subdirectory + w.mu.Lock() + flags := w.dirFlags[name] + w.mu.Unlock() + + flags |= syscall.NOTE_DELETE + return w.addWatch(name, flags) + } + + // watch file to mimic Linux inotify + return w.addWatch(name, noteAllEvents) +} + +// kqueue creates a new kernel event queue and returns a descriptor. +func kqueue() (kq int, err error) { + kq, err = syscall.Kqueue() + if kq == -1 { + return kq, err + } + return kq, nil +} + +// register events with the queue +func register(kq int, fds []int, flags int, fflags uint32) error { + changes := make([]syscall.Kevent_t, len(fds)) + + for i, fd := range fds { + // SetKevent converts int to the platform-specific types: + syscall.SetKevent(&changes[i], fd, syscall.EVFILT_VNODE, flags) + changes[i].Fflags = fflags + } + + // register the events + success, err := syscall.Kevent(kq, changes, nil, nil) + if success == -1 { + return err + } + return nil +} + +// read retrieves pending events, or waits until an event occurs. +// A timeout of nil blocks indefinitely, while 0 polls the queue. +func read(kq int, events []syscall.Kevent_t, timeout *syscall.Timespec) ([]syscall.Kevent_t, error) { + n, err := syscall.Kevent(kq, nil, events, timeout) + if err != nil { + return nil, err + } + return events[0:n], nil +} + +// durationToTimespec prepares a timeout value +func durationToTimespec(d time.Duration) syscall.Timespec { + return syscall.NsecToTimespec(d.Nanoseconds()) +} From 28547a1a3a594ee24b4cb51a89d9fce630c8c066 Mon Sep 17 00:00:00 2001 From: jmzwcn Date: Fri, 24 Apr 2015 15:02:33 +0800 Subject: [PATCH 728/999] Remove empty line after client.CmdInspect docstring #12706 Signed-off-by: Daniel Zhang Remove empty line after client.CmdInspect docstring fix #12706 Signed-off-by: Daniel Zhang Remove empty line after client.CmdInspect docstring fix #12706 Signed-off-by: Daniel Zhang --- api/client/inspect.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/client/inspect.go b/api/client/inspect.go index db281795c..e040a0b32 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -15,7 +15,6 @@ import ( // CmdInspect displays low-level information on one or more containers or images. // // Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...] - func (cli *DockerCli) CmdInspect(args ...string) error { cmd := cli.Subcmd("inspect", "CONTAINER|IMAGE [CONTAINER|IMAGE...]", "Return low-level information on a container or image", true) tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template") From 28ea4a63d00e9d0e28375adc80d24461193e7a28 Mon Sep 17 00:00:00 2001 From: Daniel Zhang Date: Thu, 30 Apr 2015 13:38:54 +0800 Subject: [PATCH 729/999] `docker images` friendly duration gets unfriendly after a while fix #12852 Signed-off-by: Daniel Zhang --- pkg/units/duration.go | 3 ++- pkg/units/duration_test.go | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/units/duration.go b/pkg/units/duration.go index cd3312149..a31f780e3 100644 --- a/pkg/units/duration.go +++ b/pkg/units/duration.go @@ -26,6 +26,7 @@ func HumanDuration(d time.Duration) string { return fmt.Sprintf("%d weeks", hours/24/7) } else if hours < 24*365*2 { return fmt.Sprintf("%d months", hours/24/30) + } else { + return fmt.Sprintf("%d years", hours/24/365) } - return fmt.Sprintf("%f years", d.Hours()/24/365) } diff --git a/pkg/units/duration_test.go b/pkg/units/duration_test.go index a22947402..fcfb6b7bb 100644 --- a/pkg/units/duration_test.go +++ b/pkg/units/duration_test.go @@ -41,6 +41,6 @@ func TestHumanDuration(t *testing.T) { assertEquals(t, "13 months", HumanDuration(13*month)) assertEquals(t, "23 months", HumanDuration(23*month)) assertEquals(t, "24 months", HumanDuration(24*month)) - assertEquals(t, "2.010959 years", HumanDuration(24*month+2*week)) - assertEquals(t, "3.164384 years", HumanDuration(3*year+2*month)) + assertEquals(t, "2 years", HumanDuration(24*month+2*week)) + assertEquals(t, "3 years", HumanDuration(3*year+2*month)) } From b40a5eeec8a3db6a6488696f2c58bc4acf0d7dbd Mon Sep 17 00:00:00 2001 From: Travis Thieman Date: Sun, 3 May 2015 14:15:17 -0400 Subject: [PATCH 730/999] Remove stale reference to previous SSH instructions Signed-off-by: Travis Thieman --- docs/sources/examples/running_riak_service.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/sources/examples/running_riak_service.md b/docs/sources/examples/running_riak_service.md index 1b14c3a41..7450cd525 100644 --- a/docs/sources/examples/running_riak_service.md +++ b/docs/sources/examples/running_riak_service.md @@ -56,8 +56,7 @@ After that, we modify Riak's configuration: RUN sed -i "s|listener.http.internal = 127.0.0.1:8098|listener.http.internal = 0.0.0.0:8098|" /etc/riak/riak.conf RUN sed -i "s|listener.protobuf.internal = 127.0.0.1:8087|listener.protobuf.internal = 0.0.0.0:8087|" /etc/riak/riak.conf -Then, we expose the Riak Protocol Buffers and HTTP interfaces, along -with SSH: +Then, we expose the Riak Protocol Buffers and HTTP interfaces: # Expose Riak Protocol Buffers and HTTP interfaces EXPOSE 8087 8098 From 5cec69a7b3ea12f9595e017eb27826ff9ad2962b Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 30 Apr 2015 16:23:28 +1000 Subject: [PATCH 731/999] Update the Docker Hub account, org and group documentation and images Signed-off-by: Sven Dowideit --- docs/sources/docker-hub/accounts.md | 45 ++++++++++++++---- docs/sources/docker-hub/hub-images/groups.png | Bin 29958 -> 61631 bytes docs/sources/docker-hub/hub-images/invite.png | Bin 60230 -> 41551 bytes .../hub-images/org-repo-collaborators.png | Bin 0 -> 38785 bytes 4 files changed, 37 insertions(+), 8 deletions(-) create mode 100644 docs/sources/docker-hub/hub-images/org-repo-collaborators.png diff --git a/docs/sources/docker-hub/accounts.md b/docs/sources/docker-hub/accounts.md index 360eb371f..510111f8e 100644 --- a/docs/sources/docker-hub/accounts.md +++ b/docs/sources/docker-hub/accounts.md @@ -34,21 +34,50 @@ page. ## Organizations and groups -Also available on the Docker Hub are organizations and groups that allow -you to collaborate across your organization or team. You can see what -organizations [you belong to and add new organizations]( +A Docker Hub organization contains public and private repositories just like +a user account. Access to push, pull or create these organisation owned repositories +is allocated by defining groups of users and then assigning group rights to +specific repositories. This allows you to distribute limited access +Docker images, and to select which Docker Hub users can publish new images. + +### Creating and viewing organizations + +You can see what organizations [you belong to and add new organizations]( https://hub.docker.com/account/organizations/) from the Account Settings -tab. They are also listed below your user name on your repositories page and in your account profile. +tab. They are also listed below your user name on your repositories page +and in your account profile. ![organizations](/docker-hub/hub-images/orgs.png) -From within your organizations you can create groups that allow you to -further manage who can interact with your repositories. +### Organization groups + +Users in the `Owners` group of an organization can create and modify the +membership of groups. + +Unless they are the organization's `Owner`, users can only see groups of which they +are members. ![groups](/docker-hub/hub-images/groups.png) -You can add or invite users to join groups by clicking on the organization and then clicking the edit button for the group to which you want to add members. Enter a user-name (for current Hub users) or email address (if they are not yet Hub users) for the person you want to invite. They will receive an email invitation to join the group. +### Repository group permissions -![invite members](/docker-hub/hub-images/invite.png) +Use organization groups to manage who can interact with your repositories. + +You need to be a member of the organization's `Owners` group to create a new group, +Hub repository or automated build. As an `Owner`, you then delegate the following +repository access rights to groups: + +- `Read` access allows a user to view, search, and pull a private repository in the + same way as they can a public repository. +- `Write` access users are able to push to non-automated repositories on the Docker + Hub. +- `Admin` access allows the user to modify the repositories "Description", "Collaborators" rights, + "Mark as unlisted", "Public/Private" status and "Delete". + +> **Note**: A User who has not yet verified their email address will only have +> `Read` access to the repository, regardless of the rights their group membership +> gives them. + +![Organization repository collaborators](/docker-hub/hub-images/org-repo-collaborators.png) diff --git a/docs/sources/docker-hub/hub-images/groups.png b/docs/sources/docker-hub/hub-images/groups.png index 0c6430efabb939f19cd45b83b43e04814f171b1e..7b4b1b15314c6e455fb06fb03a40e9fbe43ab70a 100644 GIT binary patch literal 61631 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 literal 29958 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 C)gZtC literal 60230 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%0DV%sGKrLhW5skKIIkAHW0%N;!EKe4?@Z9o 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%0D+wAYMt;|(SAYF{r<5!@q7apxB&O|1_d7>(lrGC{ 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 Date: Mon, 4 May 2015 15:39:40 +0800 Subject: [PATCH 732/999] Verifiy status Codes 404 when no such image Signed-off-by: Yuan Sun --- integration-cli/docker_api_images_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/integration-cli/docker_api_images_test.go b/integration-cli/docker_api_images_test.go index 543182eed..edc7ccbea 100644 --- a/integration-cli/docker_api_images_test.go +++ b/integration-cli/docker_api_images_test.go @@ -117,6 +117,10 @@ func (s *DockerSuite) TestApiImagesDelete(c *check.C) { c.Assert(status, check.Equals, http.StatusConflict) c.Assert(err, check.IsNil) + status, _, err = sockRequest("DELETE", "/images/test:noexist", nil) + c.Assert(status, check.Equals, http.StatusNotFound) //Status Codes:404 – no such image + c.Assert(err, check.IsNil) + status, _, err = sockRequest("DELETE", "/images/test:tag1", nil) c.Assert(status, check.Equals, http.StatusOK) c.Assert(err, check.IsNil) From a4a924e1b6c50f0f02460489259d73468a6c282e Mon Sep 17 00:00:00 2001 From: HuKeping Date: Thu, 26 Feb 2015 19:53:55 +0800 Subject: [PATCH 733/999] Feature: option for disable OOM killer Add cgroup support for disable OOM killer. Signed-off-by: Hu Keping --- api/types/types.go | 1 + daemon/container.go | 15 +++++++------ daemon/create.go | 5 +++++ daemon/execdriver/driver.go | 15 +++++++------ daemon/execdriver/driver_linux.go | 1 + daemon/execdriver/lxc/lxc_template.go | 3 +++ daemon/info.go | 1 + docs/man/docker-create.1.md | 4 ++++ docs/man/docker-run.1.md | 4 ++++ .../reference/api/docker_remote_api_v1.19.md | 3 +++ docs/sources/reference/commandline/cli.md | 2 ++ docs/sources/reference/run.md | 22 +++++++++++++++++++ pkg/sysinfo/sysinfo.go | 7 ++++++ runconfig/hostconfig.go | 1 + runconfig/parse.go | 4 +++- 15 files changed, 73 insertions(+), 15 deletions(-) diff --git a/api/types/types.go b/api/types/types.go index 7c3106546..2f5e085eb 100644 --- a/api/types/types.go +++ b/api/types/types.go @@ -156,6 +156,7 @@ type Info struct { IPv4Forwarding bool Debug bool NFd int + OomKillDisable bool NGoroutines int SystemTime string ExecutionDriver string diff --git a/daemon/container.go b/daemon/container.go index ef4229534..25254d117 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -377,13 +377,14 @@ func populateCommand(c *Container, env []string) error { } resources := &execdriver.Resources{ - Memory: c.hostConfig.Memory, - MemorySwap: c.hostConfig.MemorySwap, - CpuShares: c.hostConfig.CpuShares, - CpusetCpus: c.hostConfig.CpusetCpus, - CpusetMems: c.hostConfig.CpusetMems, - CpuQuota: c.hostConfig.CpuQuota, - Rlimits: rlimits, + Memory: c.hostConfig.Memory, + MemorySwap: c.hostConfig.MemorySwap, + CpuShares: c.hostConfig.CpuShares, + CpusetCpus: c.hostConfig.CpusetCpus, + CpusetMems: c.hostConfig.CpusetMems, + CpuQuota: c.hostConfig.CpuQuota, + Rlimits: rlimits, + OomKillDisable: c.hostConfig.OomKillDisable, } processConfig := execdriver.ProcessConfig{ diff --git a/daemon/create.go b/daemon/create.go index d8addd3a9..8cd3030c2 100644 --- a/daemon/create.go +++ b/daemon/create.go @@ -24,6 +24,11 @@ func (daemon *Daemon) ContainerCreate(name string, config *runconfig.Config, hos return "", warnings, fmt.Errorf("The working directory '%s' is invalid. It needs to be an absolute path.", config.WorkingDir) } + if !daemon.SystemConfig().OomKillDisable { + hostConfig.OomKillDisable = false + return "", warnings, fmt.Errorf("Your kernel does not support oom kill disable.") + } + container, buildWarnings, err := daemon.Create(config, hostConfig, name) if err != nil { if daemon.Graph().IsNotExist(err, config.Image) { diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index df5901ed0..7827baa26 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -100,13 +100,14 @@ type NetworkInterface struct { // TODO Windows: Factor out ulimit.Rlimit type Resources struct { - Memory int64 `json:"memory"` - MemorySwap int64 `json:"memory_swap"` - CpuShares int64 `json:"cpu_shares"` - CpusetCpus string `json:"cpuset_cpus"` - CpusetMems string `json:"cpuset_mems"` - CpuQuota int64 `json:"cpu_quota"` - Rlimits []*ulimit.Rlimit `json:"rlimits"` + Memory int64 `json:"memory"` + MemorySwap int64 `json:"memory_swap"` + CpuShares int64 `json:"cpu_shares"` + CpusetCpus string `json:"cpuset_cpus"` + CpusetMems string `json:"cpuset_mems"` + CpuQuota int64 `json:"cpu_quota"` + Rlimits []*ulimit.Rlimit `json:"rlimits"` + OomKillDisable bool `json:"oom_kill_disable"` } type ResourceStats struct { diff --git a/daemon/execdriver/driver_linux.go b/daemon/execdriver/driver_linux.go index 1766d64b6..cdaa93af3 100644 --- a/daemon/execdriver/driver_linux.go +++ b/daemon/execdriver/driver_linux.go @@ -54,6 +54,7 @@ func SetupCgroups(container *configs.Config, c *Command) error { container.Cgroups.CpusetCpus = c.Resources.CpusetCpus container.Cgroups.CpusetMems = c.Resources.CpusetMems container.Cgroups.CpuQuota = c.Resources.CpuQuota + container.Cgroups.OomKillDisable = c.Resources.OomKillDisable } return nil diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 6b418b26b..3d7b2b499 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -118,6 +118,9 @@ lxc.cgroup.cpuset.mems = {{.Resources.CpusetMems}} {{if .Resources.CpuQuota}} lxc.cgroup.cpu.cfs_quota_us = {{.Resources.CpuQuota}} {{end}} +{{if .Resources.OomKillDisable}} +lxc.cgroup.memory.oom_control = {{.Resources.OomKillDisable}} +{{end}} {{end}} {{if .LxcConfig}} diff --git a/daemon/info.go b/daemon/info.go index df1c0530c..e5ccae80a 100644 --- a/daemon/info.go +++ b/daemon/info.go @@ -68,6 +68,7 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) { IPv4Forwarding: !daemon.SystemConfig().IPv4ForwardingDisabled, Debug: os.Getenv("DEBUG") != "", NFd: fileutils.GetTotalUsedFds(), + OomKillDisable: daemon.SystemConfig().OomKillDisable, NGoroutines: runtime.NumGoroutine(), SystemTime: time.Now().Format(time.RFC3339Nano), ExecutionDriver: daemon.ExecutionDriver().Name(), diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index 7aba222b2..d7bdd5578 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -36,6 +36,7 @@ docker-create - Create a new container [**--mac-address**[=*MAC-ADDRESS*]] [**--name**[=*NAME*]] [**--net**[=*"bridge"*]] +[**--oom-kill-disable**[=*false*]] [**-P**|**--publish-all**[=*false*]] [**-p**|**--publish**[=*[]*]] [**--pid**[=*[]*]] @@ -165,6 +166,9 @@ This value should always larger than **-m**, so you should alway use this with * 'container:': reuses another container network stack 'host': use the host network stack inside the container. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure. +**--oom-kill-disable**=*true*|*false* + Whether to disable OOM Killer for the container or not. + **-P**, **--publish-all**=*true*|*false* Publish all exposed ports to random ports on the host interfaces. The default is *false*. diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index f2ce4b777..ed331089a 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -37,6 +37,7 @@ docker-run - Run a command in a new container [**--mac-address**[=*MAC-ADDRESS*]] [**--name**[=*NAME*]] [**--net**[=*"bridge"*]] +[**--oom-kill-disable**[=*false*]] [**-P**|**--publish-all**[=*false*]] [**-p**|**--publish**[=*[]*]] [**--pid**[=*[]*]] @@ -285,6 +286,9 @@ and foreground Docker containers. 'container:': reuses another container network stack 'host': use the host network stack inside the container. Note: the host mode gives the container full access to local system services such as D-bus and is therefore considered insecure. +**--oom-kill-disable**=*true*|*false* + Whether to disable OOM Killer for the container or not. + **-P**, **--publish-all**=*true*|*false* Publish all exposed ports to random ports on the host interfaces. The default is *false*. diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index cede2e107..6901c2e6b 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -149,6 +149,7 @@ Create a container "CpuShares": 512, "CpusetCpus": "0,1", "CpusetMems": "0,1", + "OomKillDisable": false, "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, "PublishAllPorts": false, "Privileged": false, @@ -194,6 +195,7 @@ Json Parameters: - **Cpuset** - The same as CpusetCpus, but deprecated, please don't use. - **CpusetCpus** - String value containing the cgroups CpusetCpus to use. - **CpusetMems** - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. +- **OomKillDisable** - Boolean value, whether to disable OOM Killer for the container or not. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. - **AttachStderr** - Boolean value, attaches to stderr. @@ -354,6 +356,7 @@ Return low-level information on the container `id` "LxcConf": [], "Memory": 0, "MemorySwap": 0, + "OomKillDisable": false, "NetworkMode": "bridge", "PortBindings": {}, "Privileged": false, diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index c69f0a170..c0c7e9ee5 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -939,6 +939,7 @@ Creates a new container. --mac-address="" Container MAC address (e.g. 92:d0:c6:0a:29:33) --name="" Assign a name to the container --net="bridge" Set the Network mode for the container + --oom-kill-disable=false Whether to disable OOM Killer for the container or not -P, --publish-all=false Publish all exposed ports to random ports -p, --publish=[] Publish a container's port(s) to the host --privileged=false Give extended privileges to this container @@ -1897,6 +1898,7 @@ To remove an image using its digest: --memory-swap="" Total memory (memory + swap), '-1' to disable swap --name="" Assign a name to the container --net="bridge" Set the Network mode for the container + --oom-kill-disable=false Whether to disable OOM Killer for the container or not -P, --publish-all=false Publish all exposed ports to random ports -p, --publish=[] Publish a container's port(s) to the host --pid="" PID namespace to use diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 990faaf6c..8d97ad7ae 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -476,6 +476,7 @@ container: --cpuset-cpus="": CPUs in which to allow execution (0-3, 0,1) --cpuset-mems="": Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. --cpu-quota=0: Limit the CPU CFS (Completely Fair Scheduler) quota + --oom-kill-disable=true|false: Whether to disable OOM Killer for the container or not. ### Memory constraints @@ -552,6 +553,27 @@ would be 2*300M, so processes can use 300M swap memory as well. We set both memory and swap memory, so the processes in the container can use 300M memory and 700M swap memory. +By default, Docker kills processes in a container if an out-of-memory (OOM) +error occurs. To change this behaviour, use the `--oom-kill-disable` option. +Only disable the OOM killer on containers where you have also set the +`-m/--memory` option. If the `-m` flag is not set, this can result in the host +running out of memory and require killing the host's system processes to free +memory. + +Examples: + +The following example limits the memory to 100M and disables the OOM killer for +this container: + + $ docker run -ti -m 100M --oom-kill-disable ubuntu:14.04 /bin/bash + +The following example, illustrates a dangerous way to use the flag: + + $ docker run -ti --oom-kill-disable ubuntu:14.04 /bin/bash + +The container has unlimited memory which can cause the host to run out memory +and require killing system processes to free memory. + ### CPU share constraint By default, all containers get the same proportion of CPU cycles. This proportion diff --git a/pkg/sysinfo/sysinfo.go b/pkg/sysinfo/sysinfo.go index b6087ff8c..e679aabd2 100644 --- a/pkg/sysinfo/sysinfo.go +++ b/pkg/sysinfo/sysinfo.go @@ -18,6 +18,7 @@ type SysInfo struct { CpuCfsQuota bool IPv4ForwardingDisabled bool AppArmor bool + OomKillDisable bool } // New returns a new SysInfo, using the filesystem to detect which features the kernel supports. @@ -36,6 +37,12 @@ func New(quiet bool) *SysInfo { if !sysInfo.SwapLimit && !quiet { logrus.Warn("Your kernel does not support swap memory limit.") } + + _, err = ioutil.ReadFile(path.Join(cgroupMemoryMountpoint, "memory.oom_control")) + sysInfo.OomKillDisable = err == nil + if !sysInfo.OomKillDisable && !quiet { + logrus.Warnf("Your kernel does not support oom control.") + } } if cgroupCpuMountpoint, err := cgroups.FindCgroupMountpoint("cpu"); err != nil { diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index 171671b6e..d634b1ffb 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -168,6 +168,7 @@ type HostConfig struct { CpusetCpus string // CpusetCpus 0-2, 0,1 CpusetMems string // CpusetMems 0-2, 0,1 CpuQuota int64 + OomKillDisable bool // Whether to disable OOM Killer or not Privileged bool PortBindings nat.PortMap Links []string diff --git a/runconfig/parse.go b/runconfig/parse.go index 4ab406980..63eeecc5f 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -53,6 +53,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to random ports") flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep STDIN open even if not attached") flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-TTY") + flOomKillDisable = cmd.Bool([]string{"-oom-kill-disable"}, false, "Disable OOM Killer") flContainerIDFile = cmd.String([]string{"#cidfile", "-cidfile"}, "", "Write the container ID to the file") flEntrypoint = cmd.String([]string{"#entrypoint", "-entrypoint"}, "", "Overwrite the default ENTRYPOINT of the image") flHostname = cmd.String([]string{"h", "-hostname"}, "", "Container host name") @@ -63,7 +64,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") flCpusetCpus = cmd.String([]string{"#-cpuset", "-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") - flCpuQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota") + flCpuQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS quota") flNetMode = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container") flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use") @@ -307,6 +308,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe CpusetCpus: *flCpusetCpus, CpusetMems: *flCpusetMems, CpuQuota: *flCpuQuota, + OomKillDisable: *flOomKillDisable, Privileged: *flPrivileged, PortBindings: portBindings, Links: flLinks.GetAll(), From 1be7a10b89506afdcb80f109f323b6e47d2e466c Mon Sep 17 00:00:00 2001 From: Ma Shimiao Date: Mon, 4 May 2015 20:39:51 +0800 Subject: [PATCH 734/999] cleanup: move container's functions to its file Signed-off-by: Ma Shimiao --- daemon/container.go | 69 ++++++++++++++++++++++++++++++++++++++++++++ daemon/exec.go | 70 --------------------------------------------- 2 files changed, 69 insertions(+), 70 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index ef4229534..0aeb5c1e6 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1557,3 +1557,72 @@ func (c *Container) LogDriverType() string { } return c.hostConfig.LogConfig.Type } + +func (container *Container) GetExecIDs() []string { + return container.execCommands.List() +} + +func (container *Container) Exec(execConfig *execConfig) error { + container.Lock() + defer container.Unlock() + + waitStart := make(chan struct{}) + + callback := func(processConfig *execdriver.ProcessConfig, pid int) { + if processConfig.Tty { + // The callback is called after the process Start() + // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave + // which we close here. + if c, ok := processConfig.Stdout.(io.Closer); ok { + c.Close() + } + } + close(waitStart) + } + + // We use a callback here instead of a goroutine and an chan for + // syncronization purposes + cErr := promise.Go(func() error { return container.monitorExec(execConfig, callback) }) + + // Exec should not return until the process is actually running + select { + case <-waitStart: + case err := <-cErr: + return err + } + + return nil +} + +func (container *Container) monitorExec(execConfig *execConfig, callback execdriver.StartCallback) error { + var ( + err error + exitCode int + ) + + pipes := execdriver.NewPipes(execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdout, execConfig.StreamConfig.stderr, execConfig.OpenStdin) + exitCode, err = container.daemon.Exec(container, execConfig, pipes, callback) + if err != nil { + logrus.Errorf("Error running command in existing container %s: %s", container.ID, err) + } + + logrus.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode) + if execConfig.OpenStdin { + if err := execConfig.StreamConfig.stdin.Close(); err != nil { + logrus.Errorf("Error closing stdin while running in %s: %s", container.ID, err) + } + } + if err := execConfig.StreamConfig.stdout.Clean(); err != nil { + logrus.Errorf("Error closing stdout while running in %s: %s", container.ID, err) + } + if err := execConfig.StreamConfig.stderr.Clean(); err != nil { + logrus.Errorf("Error closing stderr while running in %s: %s", container.ID, err) + } + if execConfig.ProcessConfig.Terminal != nil { + if err := execConfig.ProcessConfig.Terminal.Close(); err != nil { + logrus.Errorf("Error closing terminal while running in container %s: %s", container.ID, err) + } + } + + return err +} diff --git a/daemon/exec.go b/daemon/exec.go index 9aa102690..5febf083a 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -12,7 +12,6 @@ import ( "github.com/docker/docker/daemon/execdriver/lxc" "github.com/docker/docker/pkg/broadcastwriter" "github.com/docker/docker/pkg/ioutils" - "github.com/docker/docker/pkg/promise" "github.com/docker/docker/pkg/stringid" "github.com/docker/docker/runconfig" ) @@ -245,72 +244,3 @@ func (d *Daemon) Exec(c *Container, execConfig *execConfig, pipes *execdriver.Pi return exitStatus, err } - -func (container *Container) GetExecIDs() []string { - return container.execCommands.List() -} - -func (container *Container) Exec(execConfig *execConfig) error { - container.Lock() - defer container.Unlock() - - waitStart := make(chan struct{}) - - callback := func(processConfig *execdriver.ProcessConfig, pid int) { - if processConfig.Tty { - // The callback is called after the process Start() - // so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave - // which we close here. - if c, ok := processConfig.Stdout.(io.Closer); ok { - c.Close() - } - } - close(waitStart) - } - - // We use a callback here instead of a goroutine and an chan for - // syncronization purposes - cErr := promise.Go(func() error { return container.monitorExec(execConfig, callback) }) - - // Exec should not return until the process is actually running - select { - case <-waitStart: - case err := <-cErr: - return err - } - - return nil -} - -func (container *Container) monitorExec(execConfig *execConfig, callback execdriver.StartCallback) error { - var ( - err error - exitCode int - ) - - pipes := execdriver.NewPipes(execConfig.StreamConfig.stdin, execConfig.StreamConfig.stdout, execConfig.StreamConfig.stderr, execConfig.OpenStdin) - exitCode, err = container.daemon.Exec(container, execConfig, pipes, callback) - if err != nil { - logrus.Errorf("Error running command in existing container %s: %s", container.ID, err) - } - - logrus.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode) - if execConfig.OpenStdin { - if err := execConfig.StreamConfig.stdin.Close(); err != nil { - logrus.Errorf("Error closing stdin while running in %s: %s", container.ID, err) - } - } - if err := execConfig.StreamConfig.stdout.Clean(); err != nil { - logrus.Errorf("Error closing stdout while running in %s: %s", container.ID, err) - } - if err := execConfig.StreamConfig.stderr.Clean(); err != nil { - logrus.Errorf("Error closing stderr while running in %s: %s", container.ID, err) - } - if execConfig.ProcessConfig.Terminal != nil { - if err := execConfig.ProcessConfig.Terminal.Close(); err != nil { - logrus.Errorf("Error closing terminal while running in container %s: %s", container.ID, err) - } - } - - return err -} From f3023a93d1a0a96a7312de441a550c758ac0c17d Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Fri, 13 Feb 2015 11:45:04 -0500 Subject: [PATCH 735/999] Allow pulling stats once and disconnecting. Adds a `stream` query param to the stats API which allows API users to only collect one stats entry and disconnect instead of keeping the connection alive to stream more stats. Also adds a `--no-stream` flag to `docker stats` which does the same Signed-off-by: Brian Goff --- api/client/stats.go | 31 ++++++++++++---- api/server/server.go | 2 +- contrib/completion/bash/docker | 2 +- contrib/completion/fish/docker.fish | 3 +- contrib/completion/zsh/_docker | 1 + daemon/stats.go | 5 ++- docs/man/docker-stats.1.md | 3 ++ .../reference/api/docker_remote_api.md | 5 +++ .../reference/api/docker_remote_api_v1.19.md | 4 +++ docs/sources/reference/commandline/cli.md | 1 + integration-cli/docker_cli_stats_test.go | 36 +++++++++++++++++++ 11 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 integration-cli/docker_cli_stats_test.go diff --git a/api/client/stats.go b/api/client/stats.go index b2dd36d68..332b78003 100644 --- a/api/client/stats.go +++ b/api/client/stats.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "io" + "net/url" "sort" "strings" "sync" @@ -27,8 +28,14 @@ type containerStats struct { err error } -func (s *containerStats) Collect(cli *DockerCli) { - stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats", nil, nil) +func (s *containerStats) Collect(cli *DockerCli, streamStats bool) { + v := url.Values{} + if streamStats { + v.Set("stream", "1") + } else { + v.Set("stream", "0") + } + stream, _, err := cli.call("GET", "/containers/"+s.Name+"/stats?"+v.Encode(), nil, nil) if err != nil { s.err = err return @@ -67,6 +74,9 @@ func (s *containerStats) Collect(cli *DockerCli) { previousCPU = v.CpuStats.CpuUsage.TotalUsage previousSystem = v.CpuStats.SystemUsage u <- nil + if !streamStats { + return + } } }() for { @@ -87,6 +97,9 @@ func (s *containerStats) Collect(cli *DockerCli) { return } } + if !streamStats { + return + } } } @@ -112,6 +125,7 @@ func (s *containerStats) Display(w io.Writer) error { // Usage: docker stats CONTAINER [CONTAINER...] func (cli *DockerCli) CmdStats(args ...string) error { cmd := cli.Subcmd("stats", "CONTAINER [CONTAINER...]", "Display a live stream of one or more containers' resource usage statistics", true) + noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result") cmd.Require(flag.Min, 1) cmd.ParseFlags(args, true) @@ -122,14 +136,16 @@ func (cli *DockerCli) CmdStats(args ...string) error { w = tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0) ) printHeader := func() { - io.WriteString(cli.out, "\033[2J") - io.WriteString(cli.out, "\033[H") + if !*noStream { + fmt.Fprint(cli.out, "\033[2J") + fmt.Fprint(cli.out, "\033[H") + } io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O\n") } for _, n := range names { s := &containerStats{Name: n} cStats = append(cStats, s) - go s.Collect(cli) + go s.Collect(cli, !*noStream) } // do a quick pause so that any failed connections for containers that do not exist are able to be // evicted before we display the initial or default values. @@ -149,7 +165,7 @@ func (cli *DockerCli) CmdStats(args ...string) error { printHeader() toRemove := []int{} for i, s := range cStats { - if err := s.Display(w); err != nil { + if err := s.Display(w); err != nil && !*noStream { toRemove = append(toRemove, i) } } @@ -161,6 +177,9 @@ func (cli *DockerCli) CmdStats(args ...string) error { return nil } w.Flush() + if *noStream { + break + } } return nil } diff --git a/api/server/server.go b/api/server/server.go index 61e816265..2485561b9 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -611,7 +611,7 @@ func (s *Server) getContainersStats(eng *engine.Engine, version version.Version, return fmt.Errorf("Missing parameter") } - return s.daemon.ContainerStats(vars["name"], utils.NewWriteFlusher(w)) + return s.daemon.ContainerStats(vars["name"], boolValue(r, "stream"), utils.NewWriteFlusher(w)) } func (s *Server) getContainersLogs(eng *engine.Engine, version version.Version, w http.ResponseWriter, r *http.Request, vars map[string]string) error { diff --git a/contrib/completion/bash/docker b/contrib/completion/bash/docker index f3b833158..f399de177 100755 --- a/contrib/completion/bash/docker +++ b/contrib/completion/bash/docker @@ -1003,7 +1003,7 @@ _docker_start() { _docker_stats() { case "$cur" in -*) - COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) + COMPREPLY=( $( compgen -W "--no-stream --help" -- "$cur" ) ) ;; *) __docker_containers_running diff --git a/contrib/completion/fish/docker.fish b/contrib/completion/fish/docker.fish index d3237588e..39ab9e33b 100644 --- a/contrib/completion/fish/docker.fish +++ b/contrib/completion/fish/docker.fish @@ -16,7 +16,7 @@ function __fish_docker_no_subcommand --description 'Test if docker has yet to be given the subcommand' for i in (commandline -opc) - if contains -- $i attach build commit cp create diff events exec export history images import info inspect kill load login logout logs pause port ps pull push rename restart rm rmi run save search start stop tag top unpause version wait + if contains -- $i attach build commit cp create diff events exec export history images import info inspect kill load login logout logs pause port ps pull push rename restart rm rmi run save search start stop tag top unpause version wait stats return 1 end end @@ -361,6 +361,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from start' -a '(__fish_prin # stats complete -c docker -f -n '__fish_docker_no_subcommand' -a stats -d "Display a live stream of one or more containers' resource usage statistics" complete -c docker -A -f -n '__fish_seen_subcommand_from stats' -l help -d 'Print usage' +complete -c docker -A -f -n '__fish_seen_subcommand_from stats' -l no-stream -d 'Disable streaming stats and only pull the first result' complete -c docker -A -f -n '__fish_seen_subcommand_from stats' -a '(__fish_print_docker_containers running)' -d "Container" # stop diff --git a/contrib/completion/zsh/_docker b/contrib/completion/zsh/_docker index 28398f752..3cff8fbbb 100644 --- a/contrib/completion/zsh/_docker +++ b/contrib/completion/zsh/_docker @@ -326,6 +326,7 @@ __docker_subcommand () { ;; (stats) _arguments \ + '--no-stream[Disable streaming stats and only pull the first result]' \ '*:containers:__docker_runningcontainers' ;; (rm) diff --git a/daemon/stats.go b/daemon/stats.go index a95168d12..c7da91322 100644 --- a/daemon/stats.go +++ b/daemon/stats.go @@ -10,7 +10,7 @@ import ( "github.com/docker/libcontainer/cgroups" ) -func (daemon *Daemon) ContainerStats(name string, out io.Writer) error { +func (daemon *Daemon) ContainerStats(name string, stream bool, out io.Writer) error { updates, err := daemon.SubscribeToContainerStats(name) if err != nil { return err @@ -27,6 +27,9 @@ func (daemon *Daemon) ContainerStats(name string, out io.Writer) error { daemon.UnsubscribeToContainerStats(name, updates) return err } + if !stream { + break + } } return nil } diff --git a/docs/man/docker-stats.1.md b/docs/man/docker-stats.1.md index f6fc3f7f2..4b4858855 100644 --- a/docs/man/docker-stats.1.md +++ b/docs/man/docker-stats.1.md @@ -17,6 +17,9 @@ Display a live stream of one or more containers' resource usage statistics **--help** Print usage statement +**--no-stream**="false" + Disable streaming stats and only pull the first result + # EXAMPLES Run **docker stats** with multiple containers. diff --git a/docs/sources/reference/api/docker_remote_api.md b/docs/sources/reference/api/docker_remote_api.md index d92084f29..676f210eb 100644 --- a/docs/sources/reference/api/docker_remote_api.md +++ b/docs/sources/reference/api/docker_remote_api.md @@ -46,6 +46,11 @@ You can still call an old version of the API using ### What's new +`GET /containers/(id)/stats` + +**New!** +You can now supply a `stream` bool to get only one set of stats and +disconnect ## v1.18 diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index cede2e107..a214341d6 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -644,6 +644,10 @@ This endpoint returns a live stream of a container's resource usage statistics. } } +Query Parameters: + +- **stream** – 1/True/true or 0/False/false, pull stats once then disconnect. Default true + Status Codes: - **200** – no error diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 26659c8ff..159fa8086 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -2377,6 +2377,7 @@ more details on finding shared images from the command line. Display a live stream of one or more containers' resource usage statistics --help=false Print usage + --no-stream=false Disable streaming stats and only pull the first result Running `docker stats` on multiple containers diff --git a/integration-cli/docker_cli_stats_test.go b/integration-cli/docker_cli_stats_test.go new file mode 100644 index 000000000..7664de597 --- /dev/null +++ b/integration-cli/docker_cli_stats_test.go @@ -0,0 +1,36 @@ +package main + +import ( + "os/exec" + "strings" + "time" + + "github.com/go-check/check" +) + +func (s *DockerSuite) TestCliStatsNoStream(c *check.C) { + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "top")) + if err != nil { + c.Fatalf("Error on container creation: %v, output: %s", err, out) + } + id := strings.TrimSpace(out) + if err := waitRun(id); err != nil { + c.Fatalf("error waiting for container to start: %v", err) + } + + statsCmd := exec.Command(dockerBinary, "stats", "--no-stream", id) + chErr := make(chan error) + go func() { + chErr <- statsCmd.Run() + }() + + select { + case err := <-chErr: + if err != nil { + c.Fatalf("Error running stats: %v", err) + } + case <-time.After(2 * time.Second): + statsCmd.Process.Kill() + c.Fatalf("stats did not return immediately when not streaming") + } +} From 49fd83a25e2e6604014de41d4f4099a7bc07a09b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 24 Apr 2015 15:12:45 -0700 Subject: [PATCH 736/999] Use git url fragment to specify reference and dir context. Signed-off-by: David Calavera --- docs/sources/reference/commandline/cli.md | 25 ++++- integration-cli/docker_cli_build_test.go | 29 +++++ pkg/urlutil/git.go | 9 +- pkg/urlutil/git_test.go | 12 +++ utils/git.go | 60 +++++++++-- utils/git_test.go | 125 +++++++++++++++++++++- 6 files changed, 247 insertions(+), 13 deletions(-) diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 26659c8ff..682946432 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -637,13 +637,36 @@ an [*ADD*](/reference/builder/#add) instruction to reference a file in the context. The `URL` parameter can specify the location of a Git repository; -the repository acts as the build context. The system recursively clones the repository +the repository acts as the build context. The system recursively clones the repository and its submodules using a `git clone --depth 1 --recursive` command. This command runs in a temporary directory on your local host. After the command succeeds, the directory is sent to the Docker daemon as the context. Local clones give you the ability to access private repositories using local user credentials, VPN's, and so forth. +Git URLs accept context configuration in their fragment section, separated by a colon `:`. +The first part represents the reference that Git will check out, this can be either +a branch, a tag, or a commit SHA. The second part represents a subdirectory +inside the repository that will be used as a build context. + +For example, run this command to use a directory called `docker` in the branch `container`: + + $ docker build https://github.com/docker/rootfs.git#container:docker + +The following table represents all the valid suffixes with their build contexts: + +Build Syntax Suffix | Commit Used | Build Context Used +--------------------|-------------|------------------- +`myrepo.git` | `refs/heads/master` | `/` +`myrepo.git#mytag` | `refs/tags/mytag` | `/` +`myrepo.git#mybranch` | `refs/heads/mybranch` | `/` +`myrepo.git#abcdef` | `sha1 = abcdef` | `/` +`myrepo.git#:myfolder` | `refs/heads/master` | `/myfolder` +`myrepo.git#master:myfolder` | `refs/heads/master` | `/myfolder` +`myrepo.git#mytag:myfolder` | `refs/tags/mytag` | `/myfolder` +`myrepo.git#mybranch:myfolder` | `refs/heads/mybranch` | `/myfolder` +`myrepo.git#abcdef:myfolder` | `sha1 = abcdef` | `/myfolder` + Instead of specifying a context, you can pass a single Dockerfile in the `URL` or pipe the file in via `STDIN`. To pipe a Dockerfile from `STDIN`: diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 72d15c177..b06e29ae2 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -4221,6 +4221,35 @@ func (s *DockerSuite) TestBuildFromGIT(c *check.C) { } } +func (s *DockerSuite) TestBuildFromGITWithContext(c *check.C) { + name := "testbuildfromgit" + defer deleteImages(name) + git, err := fakeGIT("repo", map[string]string{ + "docker/Dockerfile": `FROM busybox + ADD first /first + RUN [ -f /first ] + MAINTAINER docker`, + "docker/first": "test git data", + }, true) + if err != nil { + c.Fatal(err) + } + defer git.Close() + + u := fmt.Sprintf("%s#master:docker", git.RepoURL) + _, err = buildImageFromPath(name, u, true) + if err != nil { + c.Fatal(err) + } + res, err := inspectField(name, "Author") + if err != nil { + c.Fatal(err) + } + if res != "docker" { + c.Fatalf("Maintainer should be docker, got %s", res) + } +} + func (s *DockerSuite) TestBuildCleanupCmdOnEntrypoint(c *check.C) { name := "testbuildcmdcleanuponentrypoint" defer deleteImages(name) diff --git a/pkg/urlutil/git.go b/pkg/urlutil/git.go index ba88ddf6e..dc4d6662e 100644 --- a/pkg/urlutil/git.go +++ b/pkg/urlutil/git.go @@ -1,6 +1,9 @@ package urlutil -import "strings" +import ( + "regexp" + "strings" +) var ( validPrefixes = []string{ @@ -8,11 +11,13 @@ var ( "github.com/", "git@", } + + urlPathWithFragmentSuffix = regexp.MustCompile(".git(?:#.+)?$") ) // IsGitURL returns true if the provided str is a git repository URL. func IsGitURL(str string) bool { - if IsURL(str) && strings.HasSuffix(str, ".git") { + if IsURL(str) && urlPathWithFragmentSuffix.MatchString(str) { return true } for _, prefix := range validPrefixes { diff --git a/pkg/urlutil/git_test.go b/pkg/urlutil/git_test.go index 01dcea7da..bb89d8b5f 100644 --- a/pkg/urlutil/git_test.go +++ b/pkg/urlutil/git_test.go @@ -9,10 +9,15 @@ var ( "git@bitbucket.org:atlassianlabs/atlassian-docker.git", "https://github.com/docker/docker.git", "http://github.com/docker/docker.git", + "http://github.com/docker/docker.git#branch", + "http://github.com/docker/docker.git#:dir", } incompleteGitUrls = []string{ "github.com/docker/docker", } + invalidGitUrls = []string{ + "http://github.com/docker/docker.git:#branch", + } ) func TestValidGitTransport(t *testing.T) { @@ -35,9 +40,16 @@ func TestIsGIT(t *testing.T) { t.Fatalf("%q should be detected as valid Git url", url) } } + for _, url := range incompleteGitUrls { if IsGitURL(url) == false { t.Fatalf("%q should be detected as valid Git url", url) } } + + for _, url := range invalidGitUrls { + if IsGitURL(url) == true { + t.Fatalf("%q should not be detected as valid Git prefix", url) + } + } } diff --git a/utils/git.go b/utils/git.go index 18e002d18..ce8924d8a 100644 --- a/utils/git.go +++ b/utils/git.go @@ -4,7 +4,10 @@ import ( "fmt" "io/ioutil" "net/http" + "net/url" + "os" "os/exec" + "path/filepath" "strings" "github.com/docker/docker/pkg/urlutil" @@ -19,20 +22,26 @@ func GitClone(remoteURL string) (string, error) { return "", err } - clone := cloneArgs(remoteURL, root) + u, err := url.Parse(remoteURL) + if err != nil { + return "", err + } - if output, err := exec.Command("git", clone...).CombinedOutput(); err != nil { + fragment := u.Fragment + clone := cloneArgs(u, root) + + if output, err := git(clone...); err != nil { return "", fmt.Errorf("Error trying to use git: %s (%s)", err, output) } - return root, nil + return checkoutGit(fragment, root) } -func cloneArgs(remoteURL, root string) []string { +func cloneArgs(remoteURL *url.URL, root string) []string { args := []string{"clone", "--recursive"} - shallow := true + shallow := len(remoteURL.Fragment) == 0 - if strings.HasPrefix(remoteURL, "http") { + if shallow && strings.HasPrefix(remoteURL.Scheme, "http") { res, err := http.Head(fmt.Sprintf("%s/info/refs?service=git-upload-pack", remoteURL)) if err != nil || res.Header.Get("Content-Type") != "application/x-git-upload-pack-advertisement" { shallow = false @@ -43,5 +52,42 @@ func cloneArgs(remoteURL, root string) []string { args = append(args, "--depth", "1") } - return append(args, remoteURL, root) + if remoteURL.Fragment != "" { + remoteURL.Fragment = "" + } + + return append(args, remoteURL.String(), root) +} + +func checkoutGit(fragment, root string) (string, error) { + refAndDir := strings.SplitN(fragment, ":", 2) + + if len(refAndDir[0]) != 0 { + if output, err := gitWithinDir(root, "checkout", refAndDir[0]); err != nil { + return "", fmt.Errorf("Error trying to use git: %s (%s)", err, output) + } + } + + if len(refAndDir) > 1 && len(refAndDir[1]) != 0 { + newCtx := filepath.Join(root, refAndDir[1]) + fi, err := os.Stat(newCtx) + if err != nil { + return "", err + } + if !fi.IsDir() { + return "", fmt.Errorf("Error setting git context, not a directory: %s", newCtx) + } + root = newCtx + } + + return root, nil +} + +func gitWithinDir(dir string, args ...string) ([]byte, error) { + a := []string{"--work-tree", dir, "--git-dir", filepath.Join(dir, ".git")} + return git(append(a, args...)...) +} + +func git(args ...string) ([]byte, error) { + return exec.Command("git", args...).CombinedOutput() } diff --git a/utils/git_test.go b/utils/git_test.go index a82841ae1..10b13e962 100644 --- a/utils/git_test.go +++ b/utils/git_test.go @@ -2,9 +2,12 @@ package utils import ( "fmt" + "io/ioutil" "net/http" "net/http/httptest" "net/url" + "os" + "path/filepath" "reflect" "testing" ) @@ -22,7 +25,7 @@ func TestCloneArgsSmartHttp(t *testing.T) { w.Header().Set("Content-Type", fmt.Sprintf("application/x-%s-advertisement", q)) }) - args := cloneArgs(gitURL, "/tmp") + args := cloneArgs(serverURL, "/tmp") exp := []string{"clone", "--recursive", "--depth", "1", gitURL, "/tmp"} if !reflect.DeepEqual(args, exp) { t.Fatalf("Expected %v, got %v", exp, args) @@ -41,16 +44,132 @@ func TestCloneArgsDumbHttp(t *testing.T) { w.Header().Set("Content-Type", "text/plain") }) - args := cloneArgs(gitURL, "/tmp") + args := cloneArgs(serverURL, "/tmp") exp := []string{"clone", "--recursive", gitURL, "/tmp"} if !reflect.DeepEqual(args, exp) { t.Fatalf("Expected %v, got %v", exp, args) } } + func TestCloneArgsGit(t *testing.T) { - args := cloneArgs("git://github.com/docker/docker", "/tmp") + u, _ := url.Parse("git://github.com/docker/docker") + args := cloneArgs(u, "/tmp") exp := []string{"clone", "--recursive", "--depth", "1", "git://github.com/docker/docker", "/tmp"} if !reflect.DeepEqual(args, exp) { t.Fatalf("Expected %v, got %v", exp, args) } } + +func TestCloneArgsStripFragment(t *testing.T) { + u, _ := url.Parse("git://github.com/docker/docker#test") + args := cloneArgs(u, "/tmp") + exp := []string{"clone", "--recursive", "git://github.com/docker/docker", "/tmp"} + if !reflect.DeepEqual(args, exp) { + t.Fatalf("Expected %v, got %v", exp, args) + } +} + +func TestCheckoutGit(t *testing.T) { + root, err := ioutil.TempDir("", "docker-build-git-checkout") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(root) + + gitDir := filepath.Join(root, "repo") + _, err = git("init", gitDir) + if err != nil { + t.Fatal(err) + } + + if _, err = gitWithinDir(gitDir, "config", "user.email", "test@docker.com"); err != nil { + t.Fatal(err) + } + + if _, err = gitWithinDir(gitDir, "config", "user.name", "Docker test"); err != nil { + t.Fatal(err) + } + + if err = ioutil.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM scratch"), 0644); err != nil { + t.Fatal(err) + } + + subDir := filepath.Join(gitDir, "subdir") + if err = os.Mkdir(subDir, 0755); err != nil { + t.Fatal(err) + } + + if err = ioutil.WriteFile(filepath.Join(subDir, "Dockerfile"), []byte("FROM scratch\nEXPOSE 5000"), 0644); err != nil { + t.Fatal(err) + } + + if _, err = gitWithinDir(gitDir, "add", "-A"); err != nil { + t.Fatal(err) + } + + if _, err = gitWithinDir(gitDir, "commit", "-am", "First commit"); err != nil { + t.Fatal(err) + } + + if _, err = gitWithinDir(gitDir, "checkout", "-b", "test"); err != nil { + t.Fatal(err) + } + + if err = ioutil.WriteFile(filepath.Join(gitDir, "Dockerfile"), []byte("FROM scratch\nEXPOSE 3000"), 0644); err != nil { + t.Fatal(err) + } + + if err = ioutil.WriteFile(filepath.Join(subDir, "Dockerfile"), []byte("FROM busybox\nEXPOSE 5000"), 0644); err != nil { + t.Fatal(err) + } + + if _, err = gitWithinDir(gitDir, "add", "-A"); err != nil { + t.Fatal(err) + } + + if _, err = gitWithinDir(gitDir, "commit", "-am", "Branch commit"); err != nil { + t.Fatal(err) + } + + if _, err = gitWithinDir(gitDir, "checkout", "master"); err != nil { + t.Fatal(err) + } + + cases := []struct { + frag string + exp string + fail bool + }{ + {"", "FROM scratch", false}, + {"master", "FROM scratch", false}, + {":subdir", "FROM scratch\nEXPOSE 5000", false}, + {":nosubdir", "", true}, // missing directory error + {":Dockerfile", "", true}, // not a directory error + {"master:nosubdir", "", true}, + {"master:subdir", "FROM scratch\nEXPOSE 5000", false}, + {"test", "FROM scratch\nEXPOSE 3000", false}, + {"test:", "FROM scratch\nEXPOSE 3000", false}, + {"test:subdir", "FROM busybox\nEXPOSE 5000", false}, + } + + for _, c := range cases { + r, err := checkoutGit(c.frag, gitDir) + + fail := err != nil + if fail != c.fail { + t.Fatalf("Expected %v failure, error was %v\n", c.fail, err) + } + if c.fail { + continue + } + + b, err := ioutil.ReadFile(filepath.Join(r, "Dockerfile")) + if err != nil { + t.Fatal(err) + } + + if string(b) != c.exp { + t.Fatalf("Expected %v, was %v\n", c.exp, string(b)) + } + } +} From 80a895142e7101b44ff71910bb2da994b1cc4f5f Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 4 May 2015 11:02:44 -0600 Subject: [PATCH 737/999] Update libcontainer and make it the source of truth on logrus version To help avoid version mismatches between libcontainer and Docker, this updates libcontainer to be the source of truth for which version of logrus the project is using. This should help avoid potential incompatibilities in the future, too. :+1: Signed-off-by: Andrew "Tianon" Page --- daemon/execdriver/native/init.go | 2 +- hack/vendor.sh | 6 +- .../github.com/Sirupsen/logrus/CHANGELOG.md | 4 + .../src/github.com/Sirupsen/logrus/README.md | 45 +- .../github.com/Sirupsen/logrus/formatter.go | 4 + .../logrus/formatters/logstash/logstash.go | 12 +- .../Sirupsen/logrus/json_formatter.go | 13 +- .../Sirupsen/logrus/text_formatter.go | 9 +- .../docker/libcontainer/apparmor/apparmor.go | 6 +- .../libcontainer/cgroups/fs/apply_raw.go | 49 +- .../docker/libcontainer/cgroups/fs/blkio.go | 26 ++ .../libcontainer/cgroups/fs/blkio_test.go | 124 +++++ .../docker/libcontainer/cgroups/fs/devices.go | 11 + .../libcontainer/cgroups/fs/devices_test.go | 38 +- .../docker/libcontainer/cgroups/fs/hugetlb.go | 29 ++ .../docker/libcontainer/cgroups/fs/memory.go | 1 + .../libcontainer/cgroups/fs/memory_test.go | 2 +- .../cgroups/fs/stats_util_test.go | 2 +- .../docker/libcontainer/cgroups/stats.go | 2 + .../cgroups/systemd/apply_nosystemd.go | 4 - .../cgroups/systemd/apply_systemd.go | 78 +++- .../docker/libcontainer/configs/cgroup.go | 17 + .../docker/libcontainer/configs/config.go | 7 + .../docker/libcontainer/configs/mount.go | 13 + .../docker/libcontainer/configs/namespaces.go | 31 +- .../configs/namespaces_syscall.go | 31 ++ .../configs/namespaces_syscall_unsupported.go | 15 + .../docker/libcontainer/configs/network.go | 4 +- .../docker/libcontainer/console_linux.go | 2 +- .../docker/libcontainer/container.go | 2 +- .../docker/libcontainer/container_linux.go | 22 +- .../docker/libcontainer/devices/devices.go | 2 +- .../github.com/docker/libcontainer/factory.go | 12 +- .../docker/libcontainer/factory_linux.go | 7 +- .../docker/libcontainer/init_linux.go | 21 +- .../libcontainer/integration/exec_test.go | 425 +++++++++++------- .../libcontainer/integration/execin_test.go | 294 ++++++------ .../libcontainer/integration/init_test.go | 2 +- .../libcontainer/integration/utils_test.go | 11 + .../libcontainer/label/label_selinux.go | 14 +- .../libcontainer/label/label_selinux_test.go | 28 ++ .../docker/libcontainer/nsenter/README.md | 27 +- .../libcontainer/nsenter/nsenter_test.go | 2 +- .../docker/libcontainer/nsenter/nsexec.c | 23 +- .../docker/libcontainer/nsinit/README.md | 45 ++ .../docker/libcontainer/nsinit/config.go | 7 + .../docker/libcontainer/nsinit/exec.go | 1 + .../docker/libcontainer/nsinit/init.go | 2 +- .../docker/libcontainer/nsinit/oom.go | 3 +- .../docker/libcontainer/nsinit/pause.go | 3 +- .../docker/libcontainer/nsinit/utils.go | 12 +- .../github.com/docker/libcontainer/process.go | 7 +- .../docker/libcontainer/process_linux.go | 3 + .../docker/libcontainer/rootfs_linux.go | 80 +++- .../libcontainer/standard_init_linux.go | 7 + .../docker/libcontainer/system/setns_linux.go | 4 +- .../libcontainer/system/syscall_linux_64.go | 2 +- .../docker/libcontainer/update-vendor.sh | 2 +- 58 files changed, 1205 insertions(+), 452 deletions(-) create mode 100644 vendor/src/github.com/docker/libcontainer/cgroups/fs/hugetlb.go create mode 100644 vendor/src/github.com/docker/libcontainer/configs/namespaces_syscall.go create mode 100644 vendor/src/github.com/docker/libcontainer/configs/namespaces_syscall_unsupported.go diff --git a/daemon/execdriver/native/init.go b/daemon/execdriver/native/init.go index f57d6cdde..2a6cd26da 100644 --- a/daemon/execdriver/native/init.go +++ b/daemon/execdriver/native/init.go @@ -32,7 +32,7 @@ func initializer() { if err != nil { fatal(err) } - if err := factory.StartInitialization(3); err != nil { + if err := factory.StartInitialization(); err != nil { fatal(err) } diff --git a/hack/vendor.sh b/hack/vendor.sh index de789d4db..68d04f544 100755 --- a/hack/vendor.sh +++ b/hack/vendor.sh @@ -53,8 +53,6 @@ clone hg code.google.com/p/gosqlite 74691fb6f837 clone git github.com/docker/libtrust 230dfd18c232 -clone git github.com/Sirupsen/logrus v0.7.2 - clone git github.com/go-fsnotify/fsnotify v1.2.0 clone git github.com/go-check/check 64131543e7896d5bcc6bd5a76287eb75ea96c673 @@ -69,8 +67,8 @@ mv tmp-digest src/github.com/docker/distribution/digest mkdir -p src/github.com/docker/distribution/registry mv tmp-api src/github.com/docker/distribution/registry/api -clone git github.com/docker/libcontainer bd8ec36106086f72b66e1be85a81202b93503e44 +clone git github.com/docker/libcontainer 6607689b1d06743003a45a722d9fe0bef36b274e # see src/github.com/docker/libcontainer/update-vendor.sh which is the "source of truth" for libcontainer deps (just like this file) rm -rf src/github.com/docker/libcontainer/vendor -eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli' | grep -v 'github.com/Sirupsen/logrus')" +eval "$(grep '^clone ' src/github.com/docker/libcontainer/update-vendor.sh | grep -v 'github.com/codegangsta/cli')" # we exclude "github.com/codegangsta/cli" here because it's only needed for "nsinit", which Docker doesn't include diff --git a/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md b/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md index 566a6fbd9..eb72bff93 100644 --- a/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md +++ b/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.7.3 + +formatter/\*: allow configuration of timestamp layout + # 0.7.2 formatter/text: Add configuration option for time format (#158) diff --git a/vendor/src/github.com/Sirupsen/logrus/README.md b/vendor/src/github.com/Sirupsen/logrus/README.md index bf09541e8..d55f90924 100644 --- a/vendor/src/github.com/Sirupsen/logrus/README.md +++ b/vendor/src/github.com/Sirupsen/logrus/README.md @@ -108,6 +108,16 @@ func main() { "omg": true, "number": 100, }).Fatal("The ice breaks!") + + // A common pattern is to re-use fields between logging statements by re-using + // the logrus.Entry returned from WithFields() + contextLogger := log.WithFields(log.Fields{ + "common": "this is a common field", + "other": "I also should be logged always", + }) + + contextLogger.Info("I'll be logged with common and other field") + contextLogger.Info("Me too") } ``` @@ -189,31 +199,18 @@ func init() { } ``` -* [`github.com/Sirupsen/logrus/hooks/airbrake`](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) - Send errors to an exception tracking service compatible with the Airbrake API. - Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. -* [`github.com/Sirupsen/logrus/hooks/papertrail`](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) - Send errors to the Papertrail hosted logging service via UDP. - -* [`github.com/Sirupsen/logrus/hooks/syslog`](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) - Send errors to remote syslog server. - Uses standard library `log/syslog` behind the scenes. - -* [`github.com/Sirupsen/logrus/hooks/bugsnag`](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) - Send errors to the Bugsnag exception tracking service. - -* [`github.com/nubo/hiprus`](https://github.com/nubo/hiprus) - Send errors to a channel in hipchat. - -* [`github.com/sebest/logrusly`](https://github.com/sebest/logrusly) - Send logs to Loggly (https://www.loggly.com/) - -* [`github.com/johntdyer/slackrus`](https://github.com/johntdyer/slackrus) - Hook for Slack chat. - -* [`github.com/wercker/journalhook`](https://github.com/wercker/journalhook). - Hook for logging to `systemd-journald`. +| Hook | Description | +| ----- | ----------- | +| [Airbrake](https://github.com/Sirupsen/logrus/blob/master/hooks/airbrake/airbrake.go) | Send errors to an exception tracking service compatible with the Airbrake API. Uses [`airbrake-go`](https://github.com/tobi/airbrake-go) behind the scenes. | +| [Papertrail](https://github.com/Sirupsen/logrus/blob/master/hooks/papertrail/papertrail.go) | Send errors to the Papertrail hosted logging service via UDP. | +| [Syslog](https://github.com/Sirupsen/logrus/blob/master/hooks/syslog/syslog.go) | Send errors to remote syslog server. Uses standard library `log/syslog` behind the scenes. | +| [BugSnag](https://github.com/Sirupsen/logrus/blob/master/hooks/bugsnag/bugsnag.go) | Send errors to the Bugsnag exception tracking service. | +| [Hiprus](https://github.com/nubo/hiprus) | Send errors to a channel in hipchat. | +| [Logrusly](https://github.com/sebest/logrusly) | Send logs to [Loggly](https://www.loggly.com/) | +| [Slackrus](https://github.com/johntdyer/slackrus) | Hook for Slack chat. | +| [Journalhook](https://github.com/wercker/journalhook) | Hook for logging to `systemd-journald` | +| [Graylog](https://github.com/gemnasium/logrus-hooks/tree/master/graylog) | Hook for logging to [Graylog](http://graylog2.org/) | #### Level logging diff --git a/vendor/src/github.com/Sirupsen/logrus/formatter.go b/vendor/src/github.com/Sirupsen/logrus/formatter.go index 038ce9fd2..104d689f1 100644 --- a/vendor/src/github.com/Sirupsen/logrus/formatter.go +++ b/vendor/src/github.com/Sirupsen/logrus/formatter.go @@ -1,5 +1,9 @@ package logrus +import "time" + +const DefaultTimestampFormat = time.RFC3339 + // The Formatter interface is used to implement a custom Formatter. It takes an // `Entry`. It exposes all the fields, including the default ones: // diff --git a/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go b/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go index 34b1ccbca..8ea93ddf2 100644 --- a/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go +++ b/vendor/src/github.com/Sirupsen/logrus/formatters/logstash/logstash.go @@ -3,19 +3,27 @@ package logstash import ( "encoding/json" "fmt" + "github.com/Sirupsen/logrus" - "time" ) // Formatter generates json in logstash format. // Logstash site: http://logstash.net/ type LogstashFormatter struct { Type string // if not empty use for logstash type field. + + // TimestampFormat sets the format used for timestamps. + TimestampFormat string } func (f *LogstashFormatter) Format(entry *logrus.Entry) ([]byte, error) { entry.Data["@version"] = 1 - entry.Data["@timestamp"] = entry.Time.Format(time.RFC3339) + + if f.TimestampFormat == "" { + f.TimestampFormat = logrus.DefaultTimestampFormat + } + + entry.Data["@timestamp"] = entry.Time.Format(f.TimestampFormat) // set message field v, ok := entry.Data["message"] diff --git a/vendor/src/github.com/Sirupsen/logrus/json_formatter.go b/vendor/src/github.com/Sirupsen/logrus/json_formatter.go index 5c4c44bbe..dcc4f1d9f 100644 --- a/vendor/src/github.com/Sirupsen/logrus/json_formatter.go +++ b/vendor/src/github.com/Sirupsen/logrus/json_formatter.go @@ -3,10 +3,12 @@ package logrus import ( "encoding/json" "fmt" - "time" ) -type JSONFormatter struct{} +type JSONFormatter struct { + // TimestampFormat sets the format used for marshaling timestamps. + TimestampFormat string +} func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { data := make(Fields, len(entry.Data)+3) @@ -21,7 +23,12 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) { } } prefixFieldClashes(data) - data["time"] = entry.Time.Format(time.RFC3339) + + if f.TimestampFormat == "" { + f.TimestampFormat = DefaultTimestampFormat + } + + data["time"] = entry.Time.Format(f.TimestampFormat) data["msg"] = entry.Message data["level"] = entry.Level.String() diff --git a/vendor/src/github.com/Sirupsen/logrus/text_formatter.go b/vendor/src/github.com/Sirupsen/logrus/text_formatter.go index d3687ba25..612417ff9 100644 --- a/vendor/src/github.com/Sirupsen/logrus/text_formatter.go +++ b/vendor/src/github.com/Sirupsen/logrus/text_formatter.go @@ -18,9 +18,8 @@ const ( ) var ( - baseTimestamp time.Time - isTerminal bool - defaultTimestampFormat = time.RFC3339 + baseTimestamp time.Time + isTerminal bool ) func init() { @@ -47,7 +46,7 @@ type TextFormatter struct { // the time passed since beginning of execution. FullTimestamp bool - // Timestamp format to use for display, if a full timestamp is printed + // TimestampFormat to use for display when a full timestamp is printed TimestampFormat string // The fields are sorted by default for a consistent output. For applications @@ -73,7 +72,7 @@ func (f *TextFormatter) Format(entry *Entry) ([]byte, error) { isColored := (f.ForceColors || isTerminal) && !f.DisableColors if f.TimestampFormat == "" { - f.TimestampFormat = defaultTimestampFormat + f.TimestampFormat = DefaultTimestampFormat } if isColored { f.printColored(b, entry, keys) diff --git a/vendor/src/github.com/docker/libcontainer/apparmor/apparmor.go b/vendor/src/github.com/docker/libcontainer/apparmor/apparmor.go index 3be3294d8..18cedf6a1 100644 --- a/vendor/src/github.com/docker/libcontainer/apparmor/apparmor.go +++ b/vendor/src/github.com/docker/libcontainer/apparmor/apparmor.go @@ -14,8 +14,10 @@ import ( func IsEnabled() bool { if _, err := os.Stat("/sys/kernel/security/apparmor"); err == nil && os.Getenv("container") == "" { - buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled") - return err == nil && len(buf) > 1 && buf[0] == 'Y' + if _, err = os.Stat("/sbin/apparmor_parser"); err == nil { + buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled") + return err == nil && len(buf) > 1 && buf[0] == 'Y' + } } return false } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go index 0a2d76bcd..fa6478b5f 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/apply_raw.go @@ -1,6 +1,8 @@ package fs import ( + "fmt" + "io" "io/ioutil" "os" "path/filepath" @@ -19,6 +21,7 @@ var ( "cpuset": &CpusetGroup{}, "cpuacct": &CpuacctGroup{}, "blkio": &BlkioGroup{}, + "hugetlb": &HugetlbGroup{}, "perf_event": &PerfEventGroup{}, "freezer": &FreezerGroup{}, } @@ -75,10 +78,13 @@ type data struct { } func (m *Manager) Apply(pid int) error { + if m.Cgroups == nil { return nil } + var c = m.Cgroups + d, err := getCgroupData(m.Cgroups, pid) if err != nil { return err @@ -108,6 +114,12 @@ func (m *Manager) Apply(pid int) error { } m.Paths = paths + if paths["cpu"] != "" { + if err := CheckCpushares(paths["cpu"], c.CpuShares); err != nil { + return err + } + } + return nil } @@ -119,19 +131,6 @@ func (m *Manager) GetPaths() map[string]string { return m.Paths } -// Symmetrical public function to update device based cgroups. Also available -// in the systemd implementation. -func ApplyDevices(c *configs.Cgroup, pid int) error { - d, err := getCgroupData(c, pid) - if err != nil { - return err - } - - devices := subsystems["devices"] - - return devices.Apply(d) -} - func (m *Manager) GetStats() (*cgroups.Stats, error) { stats := cgroups.NewStats() for name, path := range m.Paths { @@ -280,3 +279,27 @@ func removePath(p string, err error) error { } return nil } + +func CheckCpushares(path string, c int64) error { + var cpuShares int64 + + fd, err := os.Open(filepath.Join(path, "cpu.shares")) + if err != nil { + return err + } + defer fd.Close() + + _, err = fmt.Fscanf(fd, "%d", &cpuShares) + if err != nil && err != io.EOF { + return err + } + if c != 0 { + if c > cpuShares { + return fmt.Errorf("The maximum allowed cpu-shares is %d", cpuShares) + } else if c < cpuShares { + return fmt.Errorf("The minimum allowed cpu-shares is %d", cpuShares) + } + } + + return nil +} diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/blkio.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/blkio.go index 8e132643b..06f0a3b2c 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/blkio.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/blkio.go @@ -35,6 +35,32 @@ func (s *BlkioGroup) Set(path string, cgroup *configs.Cgroup) error { } } + if cgroup.BlkioWeightDevice != "" { + if err := writeFile(path, "blkio.weight_device", cgroup.BlkioWeightDevice); err != nil { + return err + } + } + if cgroup.BlkioThrottleReadBpsDevice != "" { + if err := writeFile(path, "blkio.throttle.read_bps_device", cgroup.BlkioThrottleReadBpsDevice); err != nil { + return err + } + } + if cgroup.BlkioThrottleWriteBpsDevice != "" { + if err := writeFile(path, "blkio.throttle.write_bps_device", cgroup.BlkioThrottleWriteBpsDevice); err != nil { + return err + } + } + if cgroup.BlkioThrottleReadIOpsDevice != "" { + if err := writeFile(path, "blkio.throttle.read_iops_device", cgroup.BlkioThrottleReadIOpsDevice); err != nil { + return err + } + } + if cgroup.BlkioThrottleWriteIOpsDevice != "" { + if err := writeFile(path, "blkio.throttle.write_iops_device", cgroup.BlkioThrottleWriteIOpsDevice); err != nil { + return err + } + } + return nil } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/blkio_test.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/blkio_test.go index 9ef93fcff..9d0915da3 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/blkio_test.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/blkio_test.go @@ -67,6 +67,8 @@ Total 22061056` 252:0 Async 164 252:0 Total 164 Total 328` + throttleBefore = `8:0 1024` + throttleAfter = `8:0 2048` ) func appendBlkioStatEntry(blkioStatEntries *[]cgroups.BlkioStatEntry, major, minor, value uint64, op string) { @@ -102,6 +104,35 @@ func TestBlkioSetWeight(t *testing.T) { } } +func TestBlkioSetWeightDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + const ( + weightDeviceBefore = "8:0 400" + weightDeviceAfter = "8:0 500" + ) + + helper.writeFileContents(map[string]string{ + "blkio.weight_device": weightDeviceBefore, + }) + + helper.CgroupData.c.BlkioWeightDevice = weightDeviceAfter + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.weight_device") + if err != nil { + t.Fatalf("Failed to parse blkio.weight_device - %s", err) + } + + if value != weightDeviceAfter { + t.Fatal("Got the wrong value, set blkio.weight_device failed.") + } +} + func TestBlkioStats(t *testing.T) { helper := NewCgroupTestUtil("blkio", t) defer helper.cleanup() @@ -442,3 +473,96 @@ func TestNonCFQBlkioStats(t *testing.T) { expectBlkioStatsEquals(t, expectedStats, actualStats.BlkioStats) } + +func TestBlkioSetThrottleReadBpsDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "blkio.throttle.read_bps_device": throttleBefore, + }) + + helper.CgroupData.c.BlkioThrottleReadBpsDevice = throttleAfter + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_bps_device") + if err != nil { + t.Fatalf("Failed to parse blkio.throttle.read_bps_device - %s", err) + } + + if value != throttleAfter { + t.Fatal("Got the wrong value, set blkio.throttle.read_bps_device failed.") + } +} +func TestBlkioSetThrottleWriteBpsDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "blkio.throttle.write_bps_device": throttleBefore, + }) + + helper.CgroupData.c.BlkioThrottleWriteBpsDevice = throttleAfter + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_bps_device") + if err != nil { + t.Fatalf("Failed to parse blkio.throttle.write_bps_device - %s", err) + } + + if value != throttleAfter { + t.Fatal("Got the wrong value, set blkio.throttle.write_bps_device failed.") + } +} +func TestBlkioSetThrottleReadIOpsDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "blkio.throttle.read_iops_device": throttleBefore, + }) + + helper.CgroupData.c.BlkioThrottleReadIOpsDevice = throttleAfter + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.read_iops_device") + if err != nil { + t.Fatalf("Failed to parse blkio.throttle.read_iops_device - %s", err) + } + + if value != throttleAfter { + t.Fatal("Got the wrong value, set blkio.throttle.read_iops_device failed.") + } +} +func TestBlkioSetThrottleWriteIOpsDevice(t *testing.T) { + helper := NewCgroupTestUtil("blkio", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "blkio.throttle.write_iops_device": throttleBefore, + }) + + helper.CgroupData.c.BlkioThrottleWriteIOpsDevice = throttleAfter + blkio := &BlkioGroup{} + if err := blkio.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "blkio.throttle.write_iops_device") + if err != nil { + t.Fatalf("Failed to parse blkio.throttle.write_iops_device - %s", err) + } + + if value != throttleAfter { + t.Fatal("Got the wrong value, set blkio.throttle.write_iops_device failed.") + } +} diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/devices.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/devices.go index 16e00b1c7..be588d67a 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/devices.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/devices.go @@ -32,6 +32,17 @@ func (s *DevicesGroup) Set(path string, cgroup *configs.Cgroup) error { return err } } + return nil + } + + if err := writeFile(path, "devices.allow", "a"); err != nil { + return err + } + + for _, dev := range cgroup.DeniedDevices { + if err := writeFile(path, "devices.deny", dev.CgroupString()); err != nil { + return err + } } return nil diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/devices_test.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/devices_test.go index 18bb12746..f950c1b9c 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/devices_test.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/devices_test.go @@ -17,7 +17,18 @@ var ( FileMode: 0666, }, } - allowedList = "c 1:5 rwm" + allowedList = "c 1:5 rwm" + deniedDevices = []*configs.Device{ + { + Path: "/dev/null", + Type: 'c', + Major: 1, + Minor: 3, + Permissions: "rwm", + FileMode: 0666, + }, + } + deniedList = "c 1:3 rwm" ) func TestDevicesSetAllow(t *testing.T) { @@ -44,3 +55,28 @@ func TestDevicesSetAllow(t *testing.T) { t.Fatal("Got the wrong value, set devices.allow failed.") } } + +func TestDevicesSetDeny(t *testing.T) { + helper := NewCgroupTestUtil("devices", t) + defer helper.cleanup() + + helper.writeFileContents(map[string]string{ + "devices.allow": "a", + }) + + helper.CgroupData.c.AllowAllDevices = true + helper.CgroupData.c.DeniedDevices = deniedDevices + devices := &DevicesGroup{} + if err := devices.Set(helper.CgroupPath, helper.CgroupData.c); err != nil { + t.Fatal(err) + } + + value, err := getCgroupParamString(helper.CgroupPath, "devices.deny") + if err != nil { + t.Fatalf("Failed to parse devices.deny - %s", err) + } + + if value != deniedList { + t.Fatal("Got the wrong value, set devices.deny failed.") + } +} diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/hugetlb.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/hugetlb.go new file mode 100644 index 000000000..8defdd1b9 --- /dev/null +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/hugetlb.go @@ -0,0 +1,29 @@ +package fs + +import ( + "github.com/docker/libcontainer/cgroups" + "github.com/docker/libcontainer/configs" +) + +type HugetlbGroup struct { +} + +func (s *HugetlbGroup) Apply(d *data) error { + // we just want to join this group even though we don't set anything + if _, err := d.join("hugetlb"); err != nil && !cgroups.IsNotFound(err) { + return err + } + return nil +} + +func (s *HugetlbGroup) Set(path string, cgroup *configs.Cgroup) error { + return nil +} + +func (s *HugetlbGroup) Remove(d *data) error { + return removePath(d.path("hugetlb")) +} + +func (s *HugetlbGroup) GetStats(path string, stats *cgroups.Stats) error { + return nil +} diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory.go index b99f81687..d5dbaf657 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory.go @@ -95,6 +95,7 @@ func (s *MemoryGroup) GetStats(path string, stats *cgroups.Stats) error { return fmt.Errorf("failed to parse memory.usage_in_bytes - %v", err) } stats.MemoryStats.Usage = value + stats.MemoryStats.Cache = stats.MemoryStats.Stats["cache"] value, err = getCgroupParamUint(path, "memory.max_usage_in_bytes") if err != nil { return fmt.Errorf("failed to parse memory.max_usage_in_bytes - %v", err) diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory_test.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory_test.go index 1e939c4e8..60edc67a5 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory_test.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/memory_test.go @@ -128,7 +128,7 @@ func TestMemoryStats(t *testing.T) { if err != nil { t.Fatal(err) } - expectedStats := cgroups.MemoryStats{Usage: 2048, MaxUsage: 4096, Failcnt: 100, Stats: map[string]uint64{"cache": 512, "rss": 1024}} + expectedStats := cgroups.MemoryStats{Usage: 2048, Cache: 512, MaxUsage: 4096, Failcnt: 100, Stats: map[string]uint64{"cache": 512, "rss": 1024}} expectMemoryStatEquals(t, expectedStats, actualStats.MemoryStats) } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go b/vendor/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go index c55ba938c..b94f60f99 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/fs/stats_util_test.go @@ -2,9 +2,9 @@ package fs import ( "fmt" - "log" "testing" + log "github.com/Sirupsen/logrus" "github.com/docker/libcontainer/cgroups" ) diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/stats.go b/vendor/src/github.com/docker/libcontainer/cgroups/stats.go index dc5dbb3c2..25c8f199c 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/stats.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/stats.go @@ -33,6 +33,8 @@ type CpuStats struct { type MemoryStats struct { // current res_counter usage for memory Usage uint64 `json:"usage,omitempty"` + // memory used for cache + Cache uint64 `json:"cache,omitempty"` // maximum usage ever recorded. MaxUsage uint64 `json:"max_usage,omitempty"` // TODO(vishh): Export these as stronger types. diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_nosystemd.go b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_nosystemd.go index 95ed4ea7e..9b605b3c0 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_nosystemd.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_nosystemd.go @@ -46,10 +46,6 @@ func (m *Manager) Freeze(state configs.FreezerState) error { return fmt.Errorf("Systemd not supported") } -func ApplyDevices(c *configs.Cgroup, pid int) error { - return fmt.Errorf("Systemd not supported") -} - func Freeze(c *configs.Cgroup, state configs.FreezerState) error { return fmt.Errorf("Systemd not supported") } diff --git a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go index 3609bccae..2ba10cbb3 100644 --- a/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go +++ b/vendor/src/github.com/docker/libcontainer/cgroups/systemd/apply_systemd.go @@ -38,6 +38,7 @@ var subsystems = map[string]subsystem{ "cpuset": &fs.CpusetGroup{}, "cpuacct": &fs.CpuacctGroup{}, "blkio": &fs.BlkioGroup{}, + "hugetlb": &fs.HugetlbGroup{}, "perf_event": &fs.PerfEventGroup{}, "freezer": &fs.FreezerGroup{}, } @@ -216,6 +217,13 @@ func (m *Manager) Apply(pid int) error { return err } + // FIXME: Systemd does have `BlockIODeviceWeight` property, but we got problem + // using that (at least on systemd 208, see https://github.com/docker/libcontainer/pull/354), + // so use fs work around for now. + if err := joinBlkio(c, pid); err != nil { + return err + } + paths := make(map[string]string) for sysname := range subsystems { subsystemPath, err := getSubsystemPath(m.Cgroups, sysname) @@ -228,9 +236,14 @@ func (m *Manager) Apply(pid int) error { } paths[sysname] = subsystemPath } - m.Paths = paths + if paths["cpu"] != "" { + if err := fs.CheckCpushares(paths["cpu"], c.CpuShares); err != nil { + return err + } + } + return nil } @@ -350,7 +363,17 @@ func (m *Manager) GetStats() (*cgroups.Stats, error) { } func (m *Manager) Set(container *configs.Config) error { - panic("not implemented") + for name, path := range m.Paths { + sys, ok := subsystems[name] + if !ok || !cgroups.PathExists(path) { + continue + } + if err := sys.Set(path, container.Cgroups); err != nil { + return err + } + } + + return nil } func getUnitName(c *configs.Cgroup) string { @@ -362,7 +385,7 @@ func getUnitName(c *configs.Cgroup) string { // * Support for wildcards to allow /dev/pts support // // The second is available in more recent systemd as "char-pts", but not in e.g. v208 which is -// in wide use. When both these are availalable we will be able to switch, but need to keep the old +// in wide use. When both these are available we will be able to switch, but need to keep the old // implementation for backwards compat. // // Note: we can't use systemd to set up the initial limits, and then change the cgroup @@ -375,17 +398,7 @@ func joinDevices(c *configs.Cgroup, pid int) error { } devices := subsystems["devices"] - if err := devices.Set(path, c); err != nil { - return err - } - - return nil -} - -// Symmetrical public function to update device based cgroups. Also available -// in the fs implementation. -func ApplyDevices(c *configs.Cgroup, pid int) error { - return joinDevices(c, pid) + return devices.Set(path, c) } func joinMemory(c *configs.Cgroup, pid int) error { @@ -417,3 +430,40 @@ func joinCpuset(c *configs.Cgroup, pid int) error { return s.ApplyDir(path, c, pid) } + +// `BlockIODeviceWeight` property of systemd does not work properly, and systemd +// expects device path instead of major minor numbers, which is also confusing +// for users. So we use fs work around for now. +func joinBlkio(c *configs.Cgroup, pid int) error { + path, err := getSubsystemPath(c, "blkio") + if err != nil { + return err + } + if c.BlkioWeightDevice != "" { + if err := writeFile(path, "blkio.weight_device", c.BlkioWeightDevice); err != nil { + return err + } + } + if c.BlkioThrottleReadBpsDevice != "" { + if err := writeFile(path, "blkio.throttle.read_bps_device", c.BlkioThrottleReadBpsDevice); err != nil { + return err + } + } + if c.BlkioThrottleWriteBpsDevice != "" { + if err := writeFile(path, "blkio.throttle.write_bps_device", c.BlkioThrottleWriteBpsDevice); err != nil { + return err + } + } + if c.BlkioThrottleReadIOpsDevice != "" { + if err := writeFile(path, "blkio.throttle.read_iops_device", c.BlkioThrottleReadIOpsDevice); err != nil { + return err + } + } + if c.BlkioThrottleWriteIOpsDevice != "" { + if err := writeFile(path, "blkio.throttle.write_iops_device", c.BlkioThrottleWriteIOpsDevice); err != nil { + return err + } + } + + return nil +} diff --git a/vendor/src/github.com/docker/libcontainer/configs/cgroup.go b/vendor/src/github.com/docker/libcontainer/configs/cgroup.go index 8bf174c19..8a161fcff 100644 --- a/vendor/src/github.com/docker/libcontainer/configs/cgroup.go +++ b/vendor/src/github.com/docker/libcontainer/configs/cgroup.go @@ -19,6 +19,8 @@ type Cgroup struct { AllowedDevices []*Device `json:"allowed_devices"` + DeniedDevices []*Device `json:"denied_devices"` + // Memory limit (in bytes) Memory int64 `json:"memory"` @@ -43,9 +45,24 @@ type Cgroup struct { // MEM to use CpusetMems string `json:"cpuset_mems"` + // IO read rate limit per cgroup per device, bytes per second. + BlkioThrottleReadBpsDevice string `json:"blkio_throttle_read_bps_device"` + + // IO write rate limit per cgroup per divice, bytes per second. + BlkioThrottleWriteBpsDevice string `json:"blkio_throttle_write_bps_device"` + + // IO read rate limit per cgroup per device, IO per second. + BlkioThrottleReadIOpsDevice string `json:"blkio_throttle_read_iops_device"` + + // IO write rate limit per cgroup per device, IO per second. + BlkioThrottleWriteIOpsDevice string `json:"blkio_throttle_write_iops_device"` + // Specifies per cgroup weight, range is from 10 to 1000. BlkioWeight int64 `json:"blkio_weight"` + // Weight per cgroup per device, can override BlkioWeight. + BlkioWeightDevice string `json:"blkio_weight_device"` + // set the freeze value for the process Freezer FreezerState `json:"freezer"` diff --git a/vendor/src/github.com/docker/libcontainer/configs/config.go b/vendor/src/github.com/docker/libcontainer/configs/config.go index b07f252b5..2c311a0cd 100644 --- a/vendor/src/github.com/docker/libcontainer/configs/config.go +++ b/vendor/src/github.com/docker/libcontainer/configs/config.go @@ -37,6 +37,9 @@ type Config struct { // bind mounts are writtable. Readonlyfs bool `json:"readonlyfs"` + // Privatefs will mount the container's rootfs as private where mount points from the parent will not propogate + Privatefs bool `json:"privatefs"` + // Mounts specify additional source and destination paths that will be mounted inside the container's // rootfs and mount namespace if specified Mounts []*Mount `json:"mounts"` @@ -96,6 +99,10 @@ type Config struct { // ReadonlyPaths specifies paths within the container's rootfs to remount as read-only // so that these files prevent any writes. ReadonlyPaths []string `json:"readonly_paths"` + + // SystemProperties is a map of properties and their values. It is the equivalent of using + // sysctl -w my.property.name value in Linux. + SystemProperties map[string]string `json:"system_properties"` } // Gets the root uid for the process on host which could be non-zero diff --git a/vendor/src/github.com/docker/libcontainer/configs/mount.go b/vendor/src/github.com/docker/libcontainer/configs/mount.go index 7b3dea331..5a69f815e 100644 --- a/vendor/src/github.com/docker/libcontainer/configs/mount.go +++ b/vendor/src/github.com/docker/libcontainer/configs/mount.go @@ -18,4 +18,17 @@ type Mount struct { // Relabel source if set, "z" indicates shared, "Z" indicates unshared. Relabel string `json:"relabel"` + + // Optional Command to be run before Source is mounted. + PremountCmds []Command `json:"premount_cmds"` + + // Optional Command to be run after Source is mounted. + PostmountCmds []Command `json:"postmount_cmds"` +} + +type Command struct { + Path string `json:"path"` + Args []string `json:"args"` + Env []string `json:"env"` + Dir string `json:"dir"` } diff --git a/vendor/src/github.com/docker/libcontainer/configs/namespaces.go b/vendor/src/github.com/docker/libcontainer/configs/namespaces.go index ac6a7fa2c..2c2a9fd20 100644 --- a/vendor/src/github.com/docker/libcontainer/configs/namespaces.go +++ b/vendor/src/github.com/docker/libcontainer/configs/namespaces.go @@ -1,9 +1,6 @@ package configs -import ( - "fmt" - "syscall" -) +import "fmt" type NamespaceType string @@ -34,10 +31,6 @@ type Namespace struct { Path string `json:"path"` } -func (n *Namespace) Syscall() int { - return namespaceInfo[n.Type] -} - func (n *Namespace) GetPath(pid int) string { if n.Path != "" { return n.Path @@ -96,25 +89,3 @@ func (n *Namespaces) index(t NamespaceType) int { func (n *Namespaces) Contains(t NamespaceType) bool { return n.index(t) != -1 } - -var namespaceInfo = map[NamespaceType]int{ - NEWNET: syscall.CLONE_NEWNET, - NEWNS: syscall.CLONE_NEWNS, - NEWUSER: syscall.CLONE_NEWUSER, - NEWIPC: syscall.CLONE_NEWIPC, - NEWUTS: syscall.CLONE_NEWUTS, - NEWPID: syscall.CLONE_NEWPID, -} - -// CloneFlags parses the container's Namespaces options to set the correct -// flags on clone, unshare. This functions returns flags only for new namespaces. -func (n *Namespaces) CloneFlags() uintptr { - var flag int - for _, v := range *n { - if v.Path != "" { - continue - } - flag |= namespaceInfo[v.Type] - } - return uintptr(flag) -} diff --git a/vendor/src/github.com/docker/libcontainer/configs/namespaces_syscall.go b/vendor/src/github.com/docker/libcontainer/configs/namespaces_syscall.go new file mode 100644 index 000000000..c962999ef --- /dev/null +++ b/vendor/src/github.com/docker/libcontainer/configs/namespaces_syscall.go @@ -0,0 +1,31 @@ +// +build linux + +package configs + +import "syscall" + +func (n *Namespace) Syscall() int { + return namespaceInfo[n.Type] +} + +var namespaceInfo = map[NamespaceType]int{ + NEWNET: syscall.CLONE_NEWNET, + NEWNS: syscall.CLONE_NEWNS, + NEWUSER: syscall.CLONE_NEWUSER, + NEWIPC: syscall.CLONE_NEWIPC, + NEWUTS: syscall.CLONE_NEWUTS, + NEWPID: syscall.CLONE_NEWPID, +} + +// CloneFlags parses the container's Namespaces options to set the correct +// flags on clone, unshare. This functions returns flags only for new namespaces. +func (n *Namespaces) CloneFlags() uintptr { + var flag int + for _, v := range *n { + if v.Path != "" { + continue + } + flag |= namespaceInfo[v.Type] + } + return uintptr(flag) +} diff --git a/vendor/src/github.com/docker/libcontainer/configs/namespaces_syscall_unsupported.go b/vendor/src/github.com/docker/libcontainer/configs/namespaces_syscall_unsupported.go new file mode 100644 index 000000000..1bd26bd6e --- /dev/null +++ b/vendor/src/github.com/docker/libcontainer/configs/namespaces_syscall_unsupported.go @@ -0,0 +1,15 @@ +// +build !linux + +package configs + +func (n *Namespace) Syscall() int { + panic("No namespace syscall support") + return 0 +} + +// CloneFlags parses the container's Namespaces options to set the correct +// flags on clone, unshare. This functions returns flags only for new namespaces. +func (n *Namespaces) CloneFlags() uintptr { + panic("No namespace syscall support") + return uintptr(0) +} diff --git a/vendor/src/github.com/docker/libcontainer/configs/network.go b/vendor/src/github.com/docker/libcontainer/configs/network.go index 9d5ed7a65..ccdb228e1 100644 --- a/vendor/src/github.com/docker/libcontainer/configs/network.go +++ b/vendor/src/github.com/docker/libcontainer/configs/network.go @@ -2,7 +2,7 @@ package configs // Network defines configuration for a container's networking stack // -// The network configuration can be omited from a container causing the +// The network configuration can be omitted from a container causing the // container to be setup with the host's networking stack type Network struct { // Type sets the networks type, commonly veth and loopback @@ -53,7 +53,7 @@ type Network struct { // Routes can be specified to create entries in the route table as the container is started // // All of destination, source, and gateway should be either IPv4 or IPv6. -// One of the three options must be present, and ommitted entries will use their +// One of the three options must be present, and omitted entries will use their // IP family default for the route table. For IPv4 for example, setting the // gateway to 1.2.3.4 and the interface to eth0 will set up a standard // destination of 0.0.0.0(or *) when viewed in the route table. diff --git a/vendor/src/github.com/docker/libcontainer/console_linux.go b/vendor/src/github.com/docker/libcontainer/console_linux.go index afdc2976c..a3a0551cf 100644 --- a/vendor/src/github.com/docker/libcontainer/console_linux.go +++ b/vendor/src/github.com/docker/libcontainer/console_linux.go @@ -38,7 +38,7 @@ func newConsole(uid, gid int) (Console, error) { }, nil } -// newConsoleFromPath is an internal fucntion returning an initialzied console for use inside +// newConsoleFromPath is an internal function returning an initialized console for use inside // a container's MNT namespace. func newConsoleFromPath(slavePath string) *linuxConsole { return &linuxConsole{ diff --git a/vendor/src/github.com/docker/libcontainer/container.go b/vendor/src/github.com/docker/libcontainer/container.go index 35bdfd781..a38df8269 100644 --- a/vendor/src/github.com/docker/libcontainer/container.go +++ b/vendor/src/github.com/docker/libcontainer/container.go @@ -67,7 +67,7 @@ type Container interface { // State returns the current container's state information. // // errors: - // Systemerror - System erroor. + // Systemerror - System error. State() (*State, error) // Returns the current config of the container. diff --git a/vendor/src/github.com/docker/libcontainer/container_linux.go b/vendor/src/github.com/docker/libcontainer/container_linux.go index d52610f07..1ffd7d9cb 100644 --- a/vendor/src/github.com/docker/libcontainer/container_linux.go +++ b/vendor/src/github.com/docker/libcontainer/container_linux.go @@ -16,6 +16,8 @@ import ( "github.com/docker/libcontainer/configs" ) +const stdioFdCount = 3 + type linuxContainer struct { id string root string @@ -139,7 +141,8 @@ func (c *linuxContainer) commandTemplate(p *Process, childPipe *os.File) (*exec. if cmd.SysProcAttr == nil { cmd.SysProcAttr = &syscall.SysProcAttr{} } - cmd.ExtraFiles = []*os.File{childPipe} + cmd.ExtraFiles = append(p.ExtraFiles, childPipe) + cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBCONTAINER_INITPIPE=%d", stdioFdCount+len(cmd.ExtraFiles)-1)) // NOTE: when running a container with no PID namespace and the parent process spawning the container is // PID1 the pdeathsig is being delivered to the container's init process by the kernel for some reason // even with the parent still running. @@ -178,11 +181,9 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, fmt.Sprintf("_LIBCONTAINER_INITPID=%d", c.initProcess.pid()), "_LIBCONTAINER_INITTYPE=setns", ) - if p.consolePath != "" { cmd.Env = append(cmd.Env, "_LIBCONTAINER_CONSOLE_PATH="+p.consolePath) } - // TODO: set on container for process management return &setnsProcess{ cmd: cmd, @@ -195,13 +196,14 @@ func (c *linuxContainer) newSetnsProcess(p *Process, cmd *exec.Cmd, parentPipe, func (c *linuxContainer) newInitConfig(process *Process) *initConfig { return &initConfig{ - Config: c.config, - Args: process.Args, - Env: process.Env, - User: process.User, - Cwd: process.Cwd, - Console: process.consolePath, - Capabilities: process.Capabilities, + Config: c.config, + Args: process.Args, + Env: process.Env, + User: process.User, + Cwd: process.Cwd, + Console: process.consolePath, + Capabilities: process.Capabilities, + PassedFilesCount: len(process.ExtraFiles), } } diff --git a/vendor/src/github.com/docker/libcontainer/devices/devices.go b/vendor/src/github.com/docker/libcontainer/devices/devices.go index 537f71aff..7a11eaf11 100644 --- a/vendor/src/github.com/docker/libcontainer/devices/devices.go +++ b/vendor/src/github.com/docker/libcontainer/devices/devices.go @@ -21,7 +21,7 @@ var ( ioutilReadDir = ioutil.ReadDir ) -// Given the path to a device and it's cgroup_permissions(which cannot be easilly queried) look up the information about a linux device and return that information as a Device struct. +// Given the path to a device and it's cgroup_permissions(which cannot be easily queried) look up the information about a linux device and return that information as a Device struct. func DeviceFromPath(path, permissions string) (*configs.Device, error) { fileInfo, err := osLstat(path) if err != nil { diff --git a/vendor/src/github.com/docker/libcontainer/factory.go b/vendor/src/github.com/docker/libcontainer/factory.go index 0c9fa63a3..2b3ff85d8 100644 --- a/vendor/src/github.com/docker/libcontainer/factory.go +++ b/vendor/src/github.com/docker/libcontainer/factory.go @@ -32,15 +32,13 @@ type Factory interface { // System error Load(id string) (Container, error) - // StartInitialization is an internal API to libcontainer used during the rexec of the - // container. pipefd is the fd to the child end of the pipe used to syncronize the - // parent and child process providing state and configuration to the child process and - // returning any errors during the init of the container + // StartInitialization is an internal API to libcontainer used during the reexec of the + // container. // // Errors: - // pipe connection error - // system error - StartInitialization(pipefd uintptr) error + // Pipe connection error + // System error + StartInitialization() error // Type returns info string about factory type (e.g. lxc, libcontainer...) Type() string diff --git a/vendor/src/github.com/docker/libcontainer/factory_linux.go b/vendor/src/github.com/docker/libcontainer/factory_linux.go index a2d3bec78..3cf1c3d25 100644 --- a/vendor/src/github.com/docker/libcontainer/factory_linux.go +++ b/vendor/src/github.com/docker/libcontainer/factory_linux.go @@ -10,6 +10,7 @@ import ( "os/exec" "path/filepath" "regexp" + "strconv" "syscall" "github.com/docker/docker/pkg/mount" @@ -194,7 +195,11 @@ func (l *LinuxFactory) Type() string { // StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state // This is a low level implementation detail of the reexec and should not be consumed externally -func (l *LinuxFactory) StartInitialization(pipefd uintptr) (err error) { +func (l *LinuxFactory) StartInitialization() (err error) { + pipefd, err := strconv.Atoi(os.Getenv("_LIBCONTAINER_INITPIPE")) + if err != nil { + return err + } var ( pipe = os.NewFile(uintptr(pipefd), "pipe") it = initType(os.Getenv("_LIBCONTAINER_INITTYPE")) diff --git a/vendor/src/github.com/docker/libcontainer/init_linux.go b/vendor/src/github.com/docker/libcontainer/init_linux.go index 1786b1ed7..4bbb713d0 100644 --- a/vendor/src/github.com/docker/libcontainer/init_linux.go +++ b/vendor/src/github.com/docker/libcontainer/init_linux.go @@ -40,14 +40,15 @@ type network struct { // initConfig is used for transferring parameters from Exec() to Init() type initConfig struct { - Args []string `json:"args"` - Env []string `json:"env"` - Cwd string `json:"cwd"` - Capabilities []string `json:"capabilities"` - User string `json:"user"` - Config *configs.Config `json:"config"` - Console string `json:"console"` - Networks []*network `json:"network"` + Args []string `json:"args"` + Env []string `json:"env"` + Cwd string `json:"cwd"` + Capabilities []string `json:"capabilities"` + User string `json:"user"` + Config *configs.Config `json:"config"` + Console string `json:"console"` + Networks []*network `json:"network"` + PassedFilesCount int `json:"passed_files_count"` } type initer interface { @@ -95,10 +96,10 @@ func populateProcessEnvironment(env []string) error { // and working dir, and closes any leaked file descriptors // before executing the command inside the namespace func finalizeNamespace(config *initConfig) error { - // Ensure that all non-standard fds we may have accidentally + // Ensure that all unwanted fds we may have accidentally // inherited are marked close-on-exec so they stay out of the // container - if err := utils.CloseExecFrom(3); err != nil { + if err := utils.CloseExecFrom(config.PassedFilesCount + 3); err != nil { return err } diff --git a/vendor/src/github.com/docker/libcontainer/integration/exec_test.go b/vendor/src/github.com/docker/libcontainer/integration/exec_test.go index 12457ba1a..5ee9b9e9e 100644 --- a/vendor/src/github.com/docker/libcontainer/integration/exec_test.go +++ b/vendor/src/github.com/docker/libcontainer/integration/exec_test.go @@ -4,8 +4,10 @@ import ( "bytes" "io/ioutil" "os" + "path/filepath" "strconv" "strings" + "syscall" "testing" "github.com/docker/libcontainer" @@ -29,9 +31,7 @@ func testExecPS(t *testing.T, userns bool) { return } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) if userns { @@ -64,21 +64,15 @@ func TestIPCPrivate(t *testing.T) { } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) l, err := os.Readlink("/proc/1/ns/ipc") - if err != nil { - t.Fatal(err) - } + ok(t, err) config := newTemplateConfig(rootfs) buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") - if err != nil { - t.Fatal(err) - } + ok(t, err) if exitCode != 0 { t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) @@ -95,22 +89,16 @@ func TestIPCHost(t *testing.T) { } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) l, err := os.Readlink("/proc/1/ns/ipc") - if err != nil { - t.Fatal(err) - } + ok(t, err) config := newTemplateConfig(rootfs) config.Namespaces.Remove(configs.NEWIPC) buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") - if err != nil { - t.Fatal(err) - } + ok(t, err) if exitCode != 0 { t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) @@ -127,23 +115,17 @@ func TestIPCJoinPath(t *testing.T) { } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) l, err := os.Readlink("/proc/1/ns/ipc") - if err != nil { - t.Fatal(err) - } + ok(t, err) config := newTemplateConfig(rootfs) config.Namespaces.Add(configs.NEWIPC, "/proc/1/ns/ipc") buffers, exitCode, err := runContainer(config, "", "readlink", "/proc/self/ns/ipc") - if err != nil { - t.Fatal(err) - } + ok(t, err) if exitCode != 0 { t.Fatalf("exit code not 0. code %d stderr %q", exitCode, buffers.Stderr) @@ -160,9 +142,7 @@ func TestIPCBadPath(t *testing.T) { } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) @@ -180,16 +160,12 @@ func TestRlimit(t *testing.T) { } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) out, _, err := runContainer(config, "", "/bin/sh", "-c", "ulimit -n") - if err != nil { - t.Fatal(err) - } + ok(t, err) if limit := strings.TrimSpace(out.Stdout.String()); limit != "1025" { t.Fatalf("expected rlimit to be 1025, got %s", limit) } @@ -208,9 +184,7 @@ func newTestRoot() (string, error) { func waitProcess(p *libcontainer.Process, t *testing.T) { status, err := p.Wait() - if err != nil { - t.Fatal(err) - } + ok(t, err) if !status.Success() { t.Fatal(status) } @@ -221,35 +195,25 @@ func TestEnter(t *testing.T) { return } root, err := newTestRoot() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer os.RemoveAll(root) rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) factory, err := libcontainer.New(root, libcontainer.Cgroupfs) - if err != nil { - t.Fatal(err) - } + ok(t, err) container, err := factory.Create("test", config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() // Execute a first process in the container stdinR, stdinW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + ok(t, err) var stdout, stdout2 bytes.Buffer @@ -262,19 +226,13 @@ func TestEnter(t *testing.T) { err = container.Start(&pconfig) stdinR.Close() defer stdinW.Close() - if err != nil { - t.Fatal(err) - } + ok(t, err) pid, err := pconfig.Pid() - if err != nil { - t.Fatal(err) - } + ok(t, err) // Execute another process in the container stdinR2, stdinW2, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + ok(t, err) pconfig2 := libcontainer.Process{ Env: standardEnvironment, } @@ -285,19 +243,13 @@ func TestEnter(t *testing.T) { err = container.Start(&pconfig2) stdinR2.Close() defer stdinW2.Close() - if err != nil { - t.Fatal(err) - } + ok(t, err) pid2, err := pconfig2.Pid() - if err != nil { - t.Fatal(err) - } + ok(t, err) processes, err := container.Processes() - if err != nil { - t.Fatal(err) - } + ok(t, err) n := 0 for i := range processes { @@ -318,14 +270,10 @@ func TestEnter(t *testing.T) { // Check that both processes live in the same pidns pidns := string(stdout.Bytes()) - if err != nil { - t.Fatal(err) - } + ok(t, err) pidns2 := string(stdout2.Bytes()) - if err != nil { - t.Fatal(err) - } + ok(t, err) if pidns != pidns2 { t.Fatal("The second process isn't in the required pid namespace", pidns, pidns2) @@ -337,28 +285,20 @@ func TestProcessEnv(t *testing.T) { return } root, err := newTestRoot() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer os.RemoveAll(root) rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) factory, err := libcontainer.New(root, libcontainer.Cgroupfs) - if err != nil { - t.Fatal(err) - } + ok(t, err) container, err := factory.Create("test", config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() var stdout bytes.Buffer @@ -374,17 +314,12 @@ func TestProcessEnv(t *testing.T) { Stdout: &stdout, } err = container.Start(&pconfig) - if err != nil { - t.Fatal(err) - } + ok(t, err) // Wait for process waitProcess(&pconfig, t) outputEnv := string(stdout.Bytes()) - if err != nil { - t.Fatal(err) - } // Check that the environment has the key/value pair we added if !strings.Contains(outputEnv, "FOO=BAR") { @@ -402,28 +337,20 @@ func TestProcessCaps(t *testing.T) { return } root, err := newTestRoot() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer os.RemoveAll(root) rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) factory, err := libcontainer.New(root, libcontainer.Cgroupfs) - if err != nil { - t.Fatal(err) - } + ok(t, err) container, err := factory.Create("test", config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() processCaps := append(config.Capabilities, "NET_ADMIN") @@ -437,17 +364,12 @@ func TestProcessCaps(t *testing.T) { Stdout: &stdout, } err = container.Start(&pconfig) - if err != nil { - t.Fatal(err) - } + ok(t, err) // Wait for process waitProcess(&pconfig, t) outputStatus := string(stdout.Bytes()) - if err != nil { - t.Fatal(err) - } lines := strings.Split(outputStatus, "\n") @@ -497,37 +419,28 @@ func testFreeze(t *testing.T, systemd bool) { return } root, err := newTestRoot() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer os.RemoveAll(root) rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) + cgm := libcontainer.Cgroupfs if systemd { - config.Cgroups.Slice = "system.slice" + cgm = libcontainer.SystemdCgroups } - factory, err := libcontainer.New(root, libcontainer.Cgroupfs) - if err != nil { - t.Fatal(err) - } + factory, err := libcontainer.New(root, cgm) + ok(t, err) container, err := factory.Create("test", config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() stdinR, stdinW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + ok(t, err) pconfig := libcontainer.Process{ Args: []string{"cat"}, @@ -537,44 +450,64 @@ func testFreeze(t *testing.T, systemd bool) { err = container.Start(&pconfig) stdinR.Close() defer stdinW.Close() - if err != nil { - t.Fatal(err) - } + ok(t, err) pid, err := pconfig.Pid() - if err != nil { - t.Fatal(err) - } + ok(t, err) process, err := os.FindProcess(pid) - if err != nil { - t.Fatal(err) - } + ok(t, err) - if err := container.Pause(); err != nil { - t.Fatal(err) - } + err = container.Pause() + ok(t, err) state, err := container.Status() - if err != nil { - t.Fatal(err) - } - if err := container.Resume(); err != nil { - t.Fatal(err) - } + ok(t, err) + err = container.Resume() + ok(t, err) if state != libcontainer.Paused { t.Fatal("Unexpected state: ", state) } stdinW.Close() s, err := process.Wait() - if err != nil { - t.Fatal(err) - } + ok(t, err) + if !s.Success() { t.Fatal(s.String()) } } +func TestCpuShares(t *testing.T) { + testCpuShares(t, false) +} + +func TestSystemdCpuShares(t *testing.T) { + if !systemd.UseSystemd() { + t.Skip("Systemd is unsupported") + } + testCpuShares(t, true) +} + +func testCpuShares(t *testing.T, systemd bool) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + if systemd { + config.Cgroups.Slice = "system.slice" + } + config.Cgroups.CpuShares = 1 + + _, _, err = runContainer(config, "", "ps") + if err == nil { + t.Fatalf("runContainer should failed with invalid CpuShares") + } +} + func TestContainerState(t *testing.T) { if testing.Short() { return @@ -648,3 +581,185 @@ func TestContainerState(t *testing.T) { stdinW.Close() p.Wait() } + +func TestPassExtraFiles(t *testing.T) { + if testing.Short() { + return + } + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + + factory, err := libcontainer.New(rootfs, libcontainer.Cgroupfs) + if err != nil { + t.Fatal(err) + } + + container, err := factory.Create("test", config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + var stdout bytes.Buffer + pipeout1, pipein1, err := os.Pipe() + pipeout2, pipein2, err := os.Pipe() + process := libcontainer.Process{ + Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"}, + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + ExtraFiles: []*os.File{pipein1, pipein2}, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&process) + if err != nil { + t.Fatal(err) + } + + waitProcess(&process, t) + + out := string(stdout.Bytes()) + // fd 5 is the directory handle for /proc/$$/fd + if out != "0 1 2 3 4 5" { + t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to init, got '%s'", out) + } + var buf = []byte{0} + _, err = pipeout1.Read(buf) + if err != nil { + t.Fatal(err) + } + out1 := string(buf) + if out1 != "1" { + t.Fatalf("expected first pipe to receive '1', got '%s'", out1) + } + + _, err = pipeout2.Read(buf) + if err != nil { + t.Fatal(err) + } + out2 := string(buf) + if out2 != "2" { + t.Fatalf("expected second pipe to receive '2', got '%s'", out2) + } +} + +func TestMountCmds(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + if err != nil { + t.Fatal(err) + } + defer remove(rootfs) + + tmpDir, err := ioutil.TempDir("", "tmpdir") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmpDir) + + config := newTemplateConfig(rootfs) + config.Mounts = append(config.Mounts, &configs.Mount{ + Source: tmpDir, + Destination: filepath.Join(rootfs, "tmp"), + Device: "bind", + Flags: syscall.MS_BIND | syscall.MS_REC, + PremountCmds: []configs.Command{ + {Path: "touch", Args: []string{filepath.Join(tmpDir, "hello")}}, + {Path: "touch", Args: []string{filepath.Join(tmpDir, "world")}}, + }, + PostmountCmds: []configs.Command{ + {Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "hello"), filepath.Join(rootfs, "tmp", "hello-backup")}}, + {Path: "cp", Args: []string{filepath.Join(rootfs, "tmp", "world"), filepath.Join(rootfs, "tmp", "world-backup")}}, + }, + }) + + factory, err := libcontainer.New(root, libcontainer.Cgroupfs) + if err != nil { + t.Fatal(err) + } + + container, err := factory.Create("test", config) + if err != nil { + t.Fatal(err) + } + defer container.Destroy() + + pconfig := libcontainer.Process{ + Args: []string{"sh", "-c", "env"}, + Env: standardEnvironment, + } + err = container.Start(&pconfig) + if err != nil { + t.Fatal(err) + } + + // Wait for process + waitProcess(&pconfig, t) + + entries, err := ioutil.ReadDir(tmpDir) + if err != nil { + t.Fatal(err) + } + expected := []string{"hello", "hello-backup", "world", "world-backup"} + for i, e := range entries { + if e.Name() != expected[i] { + t.Errorf("Got(%s), expect %s", e.Name(), expected[i]) + } + } +} + +func TestSystemProperties(t *testing.T) { + if testing.Short() { + return + } + root, err := newTestRoot() + ok(t, err) + defer os.RemoveAll(root) + + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + + config := newTemplateConfig(rootfs) + config.SystemProperties = map[string]string{ + "kernel.shmmni": "8192", + } + + factory, err := libcontainer.New(root, libcontainer.Cgroupfs) + ok(t, err) + + container, err := factory.Create("test", config) + ok(t, err) + defer container.Destroy() + + var stdout bytes.Buffer + pconfig := libcontainer.Process{ + Args: []string{"sh", "-c", "cat /proc/sys/kernel/shmmni"}, + Env: standardEnvironment, + Stdin: nil, + Stdout: &stdout, + } + err = container.Start(&pconfig) + ok(t, err) + + // Wait for process + waitProcess(&pconfig, t) + + shmmniOutput := strings.TrimSpace(string(stdout.Bytes())) + if shmmniOutput != "8192" { + t.Fatalf("kernel.shmmni property expected to be 8192, but is %s", shmmniOutput) + } +} diff --git a/vendor/src/github.com/docker/libcontainer/integration/execin_test.go b/vendor/src/github.com/docker/libcontainer/integration/execin_test.go index 252e6e415..f81faf010 100644 --- a/vendor/src/github.com/docker/libcontainer/integration/execin_test.go +++ b/vendor/src/github.com/docker/libcontainer/integration/execin_test.go @@ -16,22 +16,16 @@ func TestExecIn(t *testing.T) { return } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) container, err := newContainer(config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() // Execute a first process in the container stdinR, stdinW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + ok(t, err) process := &libcontainer.Process{ Args: []string{"cat"}, Env: standardEnvironment, @@ -40,9 +34,7 @@ func TestExecIn(t *testing.T) { err = container.Start(process) stdinR.Close() defer stdinW.Close() - if err != nil { - t.Fatal(err) - } + ok(t, err) buffers := newStdBuffers() ps := &libcontainer.Process{ @@ -53,12 +45,9 @@ func TestExecIn(t *testing.T) { Stderr: buffers.Stderr, } err = container.Start(ps) - if err != nil { - t.Fatal(err) - } - if _, err := ps.Wait(); err != nil { - t.Fatal(err) - } + ok(t, err) + _, err = ps.Wait() + ok(t, err) stdinW.Close() if _, err := process.Wait(); err != nil { t.Log(err) @@ -74,21 +63,15 @@ func TestExecInRlimit(t *testing.T) { return } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) container, err := newContainer(config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() stdinR, stdinW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + ok(t, err) process := &libcontainer.Process{ Args: []string{"cat"}, Env: standardEnvironment, @@ -97,9 +80,7 @@ func TestExecInRlimit(t *testing.T) { err = container.Start(process) stdinR.Close() defer stdinW.Close() - if err != nil { - t.Fatal(err) - } + ok(t, err) buffers := newStdBuffers() ps := &libcontainer.Process{ @@ -110,12 +91,9 @@ func TestExecInRlimit(t *testing.T) { Stderr: buffers.Stderr, } err = container.Start(ps) - if err != nil { - t.Fatal(err) - } - if _, err := ps.Wait(); err != nil { - t.Fatal(err) - } + ok(t, err) + _, err = ps.Wait() + ok(t, err) stdinW.Close() if _, err := process.Wait(); err != nil { t.Log(err) @@ -131,22 +109,16 @@ func TestExecInError(t *testing.T) { return } rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } + ok(t, err) defer remove(rootfs) config := newTemplateConfig(rootfs) container, err := newContainer(config) - if err != nil { - t.Fatal(err) - } + ok(t, err) defer container.Destroy() // Execute a first process in the container stdinR, stdinW, err := os.Pipe() - if err != nil { - t.Fatal(err) - } + ok(t, err) process := &libcontainer.Process{ Args: []string{"cat"}, Env: standardEnvironment, @@ -160,9 +132,7 @@ func TestExecInError(t *testing.T) { t.Log(err) } }() - if err != nil { - t.Fatal(err) - } + ok(t, err) unexistent := &libcontainer.Process{ Args: []string{"unexistent"}, @@ -178,6 +148,121 @@ func TestExecInError(t *testing.T) { } func TestExecInTTY(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + container, err := newContainer(config) + ok(t, err) + defer container.Destroy() + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + ok(t, err) + process := &libcontainer.Process{ + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(process) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + var stdout bytes.Buffer + ps := &libcontainer.Process{ + Args: []string{"ps"}, + Env: standardEnvironment, + } + console, err := ps.NewConsole(0) + copy := make(chan struct{}) + go func() { + io.Copy(&stdout, console) + close(copy) + }() + ok(t, err) + err = container.Start(ps) + ok(t, err) + select { + case <-time.After(5 * time.Second): + t.Fatal("Waiting for copy timed out") + case <-copy: + } + _, err = ps.Wait() + ok(t, err) + stdinW.Close() + if _, err := process.Wait(); err != nil { + t.Log(err) + } + out := stdout.String() + if !strings.Contains(out, "cat") || !strings.Contains(string(out), "ps") { + t.Fatalf("unexpected running process, output %q", out) + } +} + +func TestExecInEnvironment(t *testing.T) { + if testing.Short() { + return + } + rootfs, err := newRootfs() + ok(t, err) + defer remove(rootfs) + config := newTemplateConfig(rootfs) + container, err := newContainer(config) + ok(t, err) + defer container.Destroy() + + // Execute a first process in the container + stdinR, stdinW, err := os.Pipe() + ok(t, err) + process := &libcontainer.Process{ + Args: []string{"cat"}, + Env: standardEnvironment, + Stdin: stdinR, + } + err = container.Start(process) + stdinR.Close() + defer stdinW.Close() + ok(t, err) + + buffers := newStdBuffers() + process2 := &libcontainer.Process{ + Args: []string{"env"}, + Env: []string{ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "DEBUG=true", + "DEBUG=false", + "ENV=test", + }, + Stdin: buffers.Stdin, + Stdout: buffers.Stdout, + Stderr: buffers.Stderr, + } + err = container.Start(process2) + ok(t, err) + if _, err := process2.Wait(); err != nil { + out := buffers.Stdout.String() + t.Fatal(err, out) + } + stdinW.Close() + if _, err := process.Wait(); err != nil { + t.Log(err) + } + out := buffers.Stdout.String() + // check execin's process environment + if !strings.Contains(out, "DEBUG=false") || + !strings.Contains(out, "ENV=test") || + !strings.Contains(out, "HOME=/root") || + !strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") || + strings.Contains(out, "DEBUG=true") { + t.Fatalf("unexpected running process, output %q", out) + } +} + +func TestExecinPassExtraFiles(t *testing.T) { if testing.Short() { return } @@ -211,106 +296,45 @@ func TestExecInTTY(t *testing.T) { } var stdout bytes.Buffer - ps := &libcontainer.Process{ - Args: []string{"ps"}, - Env: standardEnvironment, + pipeout1, pipein1, err := os.Pipe() + pipeout2, pipein2, err := os.Pipe() + inprocess := &libcontainer.Process{ + Args: []string{"sh", "-c", "cd /proc/$$/fd; echo -n *; echo -n 1 >3; echo -n 2 >4"}, + Env: []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"}, + ExtraFiles: []*os.File{pipein1, pipein2}, + Stdin: nil, + Stdout: &stdout, } - console, err := ps.NewConsole(0) - copy := make(chan struct{}) - go func() { - io.Copy(&stdout, console) - close(copy) - }() + err = container.Start(inprocess) if err != nil { t.Fatal(err) } - err = container.Start(ps) - if err != nil { - t.Fatal(err) - } - select { - case <-time.After(5 * time.Second): - t.Fatal("Waiting for copy timed out") - case <-copy: - } - if _, err := ps.Wait(); err != nil { - t.Fatal(err) - } + + waitProcess(inprocess, t) stdinW.Close() - if _, err := process.Wait(); err != nil { - t.Log(err) + waitProcess(process, t) + + out := string(stdout.Bytes()) + // fd 5 is the directory handle for /proc/$$/fd + if out != "0 1 2 3 4 5" { + t.Fatalf("expected to have the file descriptors '0 1 2 3 4 5' passed to exec, got '%s'", out) } - out := stdout.String() - if !strings.Contains(out, "cat") || !strings.Contains(string(out), "ps") { - t.Fatalf("unexpected running process, output %q", out) - } -} - -func TestExecInEnvironment(t *testing.T) { - if testing.Short() { - return - } - rootfs, err := newRootfs() - if err != nil { - t.Fatal(err) - } - defer remove(rootfs) - config := newTemplateConfig(rootfs) - container, err := newContainer(config) - if err != nil { - t.Fatal(err) - } - defer container.Destroy() - - // Execute a first process in the container - stdinR, stdinW, err := os.Pipe() + var buf = []byte{0} + _, err = pipeout1.Read(buf) if err != nil { t.Fatal(err) } - process := &libcontainer.Process{ - Args: []string{"cat"}, - Env: standardEnvironment, - Stdin: stdinR, - } - err = container.Start(process) - stdinR.Close() - defer stdinW.Close() - if err != nil { - t.Fatal(err) + out1 := string(buf) + if out1 != "1" { + t.Fatalf("expected first pipe to receive '1', got '%s'", out1) } - buffers := newStdBuffers() - process2 := &libcontainer.Process{ - Args: []string{"env"}, - Env: []string{ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "DEBUG=true", - "DEBUG=false", - "ENV=test", - }, - Stdin: buffers.Stdin, - Stdout: buffers.Stdout, - Stderr: buffers.Stderr, - } - err = container.Start(process2) + _, err = pipeout2.Read(buf) if err != nil { t.Fatal(err) - } - if _, err := process2.Wait(); err != nil { - out := buffers.Stdout.String() - t.Fatal(err, out) - } - stdinW.Close() - if _, err := process.Wait(); err != nil { - t.Log(err) } - out := buffers.Stdout.String() - // check execin's process environment - if !strings.Contains(out, "DEBUG=false") || - !strings.Contains(out, "ENV=test") || - !strings.Contains(out, "HOME=/root") || - !strings.Contains(out, "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin") || - strings.Contains(out, "DEBUG=true") { - t.Fatalf("unexpected running process, output %q", out) + out2 := string(buf) + if out2 != "2" { + t.Fatalf("expected second pipe to receive '2', got '%s'", out2) } } diff --git a/vendor/src/github.com/docker/libcontainer/integration/init_test.go b/vendor/src/github.com/docker/libcontainer/integration/init_test.go index f11834de3..1f75ef525 100644 --- a/vendor/src/github.com/docker/libcontainer/integration/init_test.go +++ b/vendor/src/github.com/docker/libcontainer/integration/init_test.go @@ -21,7 +21,7 @@ func init() { if err != nil { log.Fatalf("unable to initialize for container: %s", err) } - if err := factory.StartInitialization(3); err != nil { + if err := factory.StartInitialization(); err != nil { log.Fatal(err) } } diff --git a/vendor/src/github.com/docker/libcontainer/integration/utils_test.go b/vendor/src/github.com/docker/libcontainer/integration/utils_test.go index cf4596864..263d89d3b 100644 --- a/vendor/src/github.com/docker/libcontainer/integration/utils_test.go +++ b/vendor/src/github.com/docker/libcontainer/integration/utils_test.go @@ -6,8 +6,11 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" + "runtime" "strings" "syscall" + "testing" "github.com/docker/libcontainer" "github.com/docker/libcontainer/configs" @@ -38,6 +41,14 @@ func (b *stdBuffers) String() string { return strings.Join(s, "|") } +// ok fails the test if an err is not nil. +func ok(t testing.TB, err error) { + if err != nil { + _, file, line, _ := runtime.Caller(1) + t.Fatalf("%s:%d: unexpected error: %s\n\n", filepath.Base(file), line, err.Error()) + } +} + // newRootfs creates a new tmp directory and copies the busybox root filesystem func newRootfs() (string, error) { dir, err := ioutil.TempDir("", "") diff --git a/vendor/src/github.com/docker/libcontainer/label/label_selinux.go b/vendor/src/github.com/docker/libcontainer/label/label_selinux.go index 5983031ae..7bc40ddde 100644 --- a/vendor/src/github.com/docker/libcontainer/label/label_selinux.go +++ b/vendor/src/github.com/docker/libcontainer/label/label_selinux.go @@ -101,10 +101,22 @@ func SetFileCreateLabel(fileLabel string) error { // the MCS label should continue to be used. SELinux will use this field // to make sure the content can not be shared by other containes. func Relabel(path string, fileLabel string, relabel string) error { + exclude_path := []string{"/", "/usr", "/etc"} if fileLabel == "" { return nil } - if relabel == "z" { + for _, p := range exclude_path { + if path == p { + return fmt.Errorf("Relabeling of %s is not allowed", path) + } + } + if !strings.ContainsAny(relabel, "zZ") { + return nil + } + if strings.Contains(relabel, "z") && strings.Contains(relabel, "Z") { + return fmt.Errorf("Bad SELinux option z and Z can not be used together") + } + if strings.Contains(relabel, "z") { c := selinux.NewContext(fileLabel) c["level"] = "s0" fileLabel = c.Get() diff --git a/vendor/src/github.com/docker/libcontainer/label/label_selinux_test.go b/vendor/src/github.com/docker/libcontainer/label/label_selinux_test.go index 8629353f2..6ab0c67ca 100644 --- a/vendor/src/github.com/docker/libcontainer/label/label_selinux_test.go +++ b/vendor/src/github.com/docker/libcontainer/label/label_selinux_test.go @@ -87,3 +87,31 @@ func TestDuplicateLabel(t *testing.T) { t.Errorf("DisableSecOpt Failed level incorrect") } } +func TestRelabel(t *testing.T) { + testdir := "/tmp/test" + label := "system_u:system_r:svirt_sandbox_file_t:s0:c1,c2" + if err := Relabel(testdir, "", "z"); err != nil { + t.Fatal("Relabel with no label failed: %v", err) + } + if err := Relabel(testdir, label, ""); err != nil { + t.Fatal("Relabel with no relabel field failed: %v", err) + } + if err := Relabel(testdir, label, "z"); err != nil { + t.Fatal("Relabel shared failed: %v", err) + } + if err := Relabel(testdir, label, "Z"); err != nil { + t.Fatal("Relabel unshared failed: %v", err) + } + if err := Relabel(testdir, label, "zZ"); err == nil { + t.Fatal("Relabel with shared and unshared succeeded") + } + if err := Relabel("/etc", label, "zZ"); err == nil { + t.Fatal("Relabel /etc succeeded") + } + if err := Relabel("/", label, ""); err == nil { + t.Fatal("Relabel / succeeded") + } + if err := Relabel("/usr", label, "Z"); err == nil { + t.Fatal("Relabel /usr succeeded") + } +} diff --git a/vendor/src/github.com/docker/libcontainer/nsenter/README.md b/vendor/src/github.com/docker/libcontainer/nsenter/README.md index ac94cba05..d1a60ef98 100644 --- a/vendor/src/github.com/docker/libcontainer/nsenter/README.md +++ b/vendor/src/github.com/docker/libcontainer/nsenter/README.md @@ -1,6 +1,25 @@ ## nsenter -The `nsenter` package registers a special init constructor that is called before the Go runtime has -a chance to boot. This provides us the ability to `setns` on existing namespaces and avoid the issues -that the Go runtime has with multiple threads. This constructor is only called if this package is -registered, imported, in your go application and the argv 0 is `nsenter`. +The `nsenter` package registers a special init constructor that is called before +the Go runtime has a chance to boot. This provides us the ability to `setns` on +existing namespaces and avoid the issues that the Go runtime has with multiple +threads. This constructor will be called if this package is registered, +imported, in your go application. + +The `nsenter` package will `import "C"` and it uses [cgo](https://golang.org/cmd/cgo/) +package. In cgo, if the import of "C" is immediately preceded by a comment, that comment, +called the preamble, is used as a header when compiling the C parts of the package. +So every time we import package `nsenter`, the C code function `nsexec()` would be +called. And package `nsenter` is now only imported in Docker execdriver, so every time +before we call `execdriver.Exec()`, that C code would run. + +`nsexec()` will first check the environment variable `_LIBCONTAINER_INITPID` +which will give the process of the container that should be joined. Namespaces fd will +be found from `/proc/[pid]/ns` and set by `setns` syscall. + +And then get the pipe number from `_LIBCONTAINER_INITPIPE`, error message could +be transfered through it. If tty is added, `_LIBCONTAINER_CONSOLE_PATH` will +have value and start a console for output. + +Finally, `nsexec()` will clone a child process , exit the parent process and let +the Go runtime take over. diff --git a/vendor/src/github.com/docker/libcontainer/nsenter/nsenter_test.go b/vendor/src/github.com/docker/libcontainer/nsenter/nsenter_test.go index 34e1f5211..db27b8a40 100644 --- a/vendor/src/github.com/docker/libcontainer/nsenter/nsenter_test.go +++ b/vendor/src/github.com/docker/libcontainer/nsenter/nsenter_test.go @@ -24,7 +24,7 @@ func TestNsenterAlivePid(t *testing.T) { Path: os.Args[0], Args: args, ExtraFiles: []*os.File{w}, - Env: []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", os.Getpid())}, + Env: []string{fmt.Sprintf("_LIBCONTAINER_INITPID=%d", os.Getpid()), "_LIBCONTAINER_INITPIPE=3"}, } if err := cmd.Start(); err != nil { diff --git a/vendor/src/github.com/docker/libcontainer/nsenter/nsexec.c b/vendor/src/github.com/docker/libcontainer/nsenter/nsexec.c index e7658f385..d8e45f3cd 100644 --- a/vendor/src/github.com/docker/libcontainer/nsenter/nsexec.c +++ b/vendor/src/github.com/docker/libcontainer/nsenter/nsexec.c @@ -66,7 +66,7 @@ void nsexec() const int num = sizeof(namespaces) / sizeof(char *); jmp_buf env; char buf[PATH_MAX], *val; - int i, tfd, child, len, consolefd = -1; + int i, tfd, child, len, pipenum, consolefd = -1; pid_t pid; char *console; @@ -81,6 +81,19 @@ void nsexec() exit(1); } + val = getenv("_LIBCONTAINER_INITPIPE"); + if (val == NULL) { + pr_perror("Child pipe not found"); + exit(1); + } + + pipenum = atoi(val); + snprintf(buf, sizeof(buf), "%d", pipenum); + if (strcmp(val, buf)) { + pr_perror("Unable to parse _LIBCONTAINER_INITPIPE"); + exit(1); + } + console = getenv("_LIBCONTAINER_CONSOLE_PATH"); if (console != NULL) { consolefd = open(console, O_RDWR); @@ -124,6 +137,8 @@ void nsexec() } if (setjmp(env) == 1) { + // Child + if (setsid() == -1) { pr_perror("setsid failed"); exit(1); @@ -149,7 +164,11 @@ void nsexec() // Finish executing, let the Go runtime take over. return; } + // Parent + // We must fork to actually enter the PID namespace, use CLONE_PARENT + // so the child can have the right parent, and we don't need to forward + // the child's exit code or resend its death signal. child = clone_parent(&env); if (child < 0) { pr_perror("Unable to fork"); @@ -158,7 +177,7 @@ void nsexec() len = snprintf(buf, sizeof(buf), "{ \"pid\" : %d }\n", child); - if (write(3, buf, len) != len) { + if (write(pipenum, buf, len) != len) { pr_perror("Unable to send a child pid"); kill(child, SIGKILL); exit(1); diff --git a/vendor/src/github.com/docker/libcontainer/nsinit/README.md b/vendor/src/github.com/docker/libcontainer/nsinit/README.md index f2e66a866..98bed0e8e 100644 --- a/vendor/src/github.com/docker/libcontainer/nsinit/README.md +++ b/vendor/src/github.com/docker/libcontainer/nsinit/README.md @@ -65,3 +65,48 @@ You can identify if a process is running in a container by looking to see if You may also specify an alternate root directory from where the `container.json` file is read and where the `state.json` file will be saved. + +### How to use? + +Currently nsinit has 9 commands. Type `nsinit -h` to list all of them. +And for every alternative command, you can also use `--help` to get more +detailed help documents. For example, `nsinit config --help`. + +`nsinit` cli application is implemented using [cli.go](https://github.com/codegangsta/cli). +Lots of details are handled in cli.go, so the implementation of `nsinit` itself +is very clean and clear. + +* **config** +It will generate a standard configuration file for a container. By default, it +will generate as the template file in [config.go](https://github.com/docker/libcontainer/blob/master/nsinit/config.go#L192). +It will modify the template if you have specified some configuration by options. +* **exec** +Starts a container and execute a new command inside it. Besides common options, it +has some special options as below. + - `--tty,-t`: allocate a TTY to the container. + - `--config`: you can specify a configuration file. By default, it will use + template configuration. + - `--id`: specify the ID for a container. By default, the id is "nsinit". + - `--user,-u`: set the user, uid, and/or gid for the process. By default the + value is "root". + - `--cwd`: set the current working dir. + - `--env`: set environment variables for the process. +* **init** +It's an internal command that is called inside the container's namespaces to +initialize the namespace and exec the user's process. It should not be called +externally. +* **oom** +Display oom notifications for a container, you should specify container id. +* **pause** +Pause the container's processes, you should specify container id. It will use +cgroup freeze subsystem to help. +* **unpause** +Unpause the container's processes. Same with `pause`. +* **stats** +Display statistics for the container, it will mainly show cgroup and network +statistics. +* **state** +Get the container's current state. You can also read the state from `state.json` + in your container_id folder. +* **help, h** +Shows a list of commands or help for one command. diff --git a/vendor/src/github.com/docker/libcontainer/nsinit/config.go b/vendor/src/github.com/docker/libcontainer/nsinit/config.go index e50bb3c11..1eee9dd92 100644 --- a/vendor/src/github.com/docker/libcontainer/nsinit/config.go +++ b/vendor/src/github.com/docker/libcontainer/nsinit/config.go @@ -43,6 +43,7 @@ var createFlags = []cli.Flag{ cli.StringFlag{Name: "veth-address", Usage: "veth ip address"}, cli.StringFlag{Name: "veth-gateway", Usage: "veth gateway address"}, cli.IntFlag{Name: "veth-mtu", Usage: "veth mtu"}, + cli.BoolFlag{Name: "cgroup", Usage: "mount the cgroup data for the container"}, } var configCommand = cli.Command{ @@ -187,6 +188,12 @@ func modify(config *configs.Config, context *cli.Context) { } config.Networks = append(config.Networks, network) } + if context.Bool("cgroup") { + config.Mounts = append(config.Mounts, &configs.Mount{ + Destination: "/sys/fs/cgroup", + Device: "cgroup", + }) + } } func getTemplate() *configs.Config { diff --git a/vendor/src/github.com/docker/libcontainer/nsinit/exec.go b/vendor/src/github.com/docker/libcontainer/nsinit/exec.go index 9d302aa31..cf40a5951 100644 --- a/vendor/src/github.com/docker/libcontainer/nsinit/exec.go +++ b/vendor/src/github.com/docker/libcontainer/nsinit/exec.go @@ -23,6 +23,7 @@ var execCommand = cli.Command{ Action: execAction, Flags: append([]cli.Flag{ cli.BoolFlag{Name: "tty,t", Usage: "allocate a TTY to the container"}, + cli.BoolFlag{Name: "systemd", Usage: "Use systemd for managing cgroups, if available"}, cli.StringFlag{Name: "id", Value: "nsinit", Usage: "specify the ID for a container"}, cli.StringFlag{Name: "config", Value: "", Usage: "path to the configuration file"}, cli.StringFlag{Name: "user,u", Value: "root", Usage: "set the user, uid, and/or gid for the process"}, diff --git a/vendor/src/github.com/docker/libcontainer/nsinit/init.go b/vendor/src/github.com/docker/libcontainer/nsinit/init.go index 7b2cf1935..c7506a0e9 100644 --- a/vendor/src/github.com/docker/libcontainer/nsinit/init.go +++ b/vendor/src/github.com/docker/libcontainer/nsinit/init.go @@ -20,7 +20,7 @@ var initCommand = cli.Command{ if err != nil { fatal(err) } - if err := factory.StartInitialization(3); err != nil { + if err := factory.StartInitialization(); err != nil { fatal(err) } panic("This line should never been executed") diff --git a/vendor/src/github.com/docker/libcontainer/nsinit/oom.go b/vendor/src/github.com/docker/libcontainer/nsinit/oom.go index a59b75333..412534bcd 100644 --- a/vendor/src/github.com/docker/libcontainer/nsinit/oom.go +++ b/vendor/src/github.com/docker/libcontainer/nsinit/oom.go @@ -1,8 +1,7 @@ package main import ( - "log" - + log "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" ) diff --git a/vendor/src/github.com/docker/libcontainer/nsinit/pause.go b/vendor/src/github.com/docker/libcontainer/nsinit/pause.go index 89af0b6f7..40aace444 100644 --- a/vendor/src/github.com/docker/libcontainer/nsinit/pause.go +++ b/vendor/src/github.com/docker/libcontainer/nsinit/pause.go @@ -1,8 +1,7 @@ package main import ( - "log" - + log "github.com/Sirupsen/logrus" "github.com/codegangsta/cli" ) diff --git a/vendor/src/github.com/docker/libcontainer/nsinit/utils.go b/vendor/src/github.com/docker/libcontainer/nsinit/utils.go index 4deca7664..92f0a9d9e 100644 --- a/vendor/src/github.com/docker/libcontainer/nsinit/utils.go +++ b/vendor/src/github.com/docker/libcontainer/nsinit/utils.go @@ -3,10 +3,12 @@ package main import ( "encoding/json" "fmt" + log "github.com/Sirupsen/logrus" "os" "github.com/codegangsta/cli" "github.com/docker/libcontainer" + "github.com/docker/libcontainer/cgroups/systemd" "github.com/docker/libcontainer/configs" ) @@ -29,7 +31,15 @@ func loadConfig(context *cli.Context) (*configs.Config, error) { } func loadFactory(context *cli.Context) (libcontainer.Factory, error) { - return libcontainer.New(context.GlobalString("root"), libcontainer.Cgroupfs) + cgm := libcontainer.Cgroupfs + if context.Bool("systemd") { + if systemd.UseSystemd() { + cgm = libcontainer.SystemdCgroups + } else { + log.Warn("systemd cgroup flag passed, but systemd support for managing cgroups is not available.") + } + } + return libcontainer.New(context.GlobalString("root"), cgm) } func getContainer(context *cli.Context) (libcontainer.Container, error) { diff --git a/vendor/src/github.com/docker/libcontainer/process.go b/vendor/src/github.com/docker/libcontainer/process.go index 82fcff8c4..7902d08ce 100644 --- a/vendor/src/github.com/docker/libcontainer/process.go +++ b/vendor/src/github.com/docker/libcontainer/process.go @@ -23,7 +23,7 @@ type Process struct { Env []string // User will set the uid and gid of the executing process running inside the container - // local to the contaienr's user and group configuration. + // local to the container's user and group configuration. User string // Cwd will change the processes current working directory inside the container's rootfs. @@ -38,11 +38,14 @@ type Process struct { // Stderr is a pointer to a writer which receives the standard error stream. Stderr io.Writer + // ExtraFiles specifies additional open files to be inherited by the container + ExtraFiles []*os.File + // consolePath is the path to the console allocated to the container. consolePath string // Capabilities specify the capabilities to keep when executing the process inside the container - // All capbilities not specified will be dropped from the processes capability mask + // All capabilities not specified will be dropped from the processes capability mask Capabilities []string ops processOperations diff --git a/vendor/src/github.com/docker/libcontainer/process_linux.go b/vendor/src/github.com/docker/libcontainer/process_linux.go index 1c74b6549..66411a8a9 100644 --- a/vendor/src/github.com/docker/libcontainer/process_linux.go +++ b/vendor/src/github.com/docker/libcontainer/process_linux.go @@ -119,6 +119,9 @@ func (p *setnsProcess) execSetns() error { // terminate sends a SIGKILL to the forked process for the setns routine then waits to // avoid the process becomming a zombie. func (p *setnsProcess) terminate() error { + if p.cmd.Process == nil { + return nil + } err := p.cmd.Process.Kill() if _, werr := p.wait(); err == nil { err = werr diff --git a/vendor/src/github.com/docker/libcontainer/rootfs_linux.go b/vendor/src/github.com/docker/libcontainer/rootfs_linux.go index ab1a9a5fc..d8c61e97a 100644 --- a/vendor/src/github.com/docker/libcontainer/rootfs_linux.go +++ b/vendor/src/github.com/docker/libcontainer/rootfs_linux.go @@ -6,11 +6,14 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" + "path" "path/filepath" "strings" "syscall" "time" + "github.com/docker/libcontainer/cgroups" "github.com/docker/libcontainer/configs" "github.com/docker/libcontainer/label" ) @@ -24,9 +27,20 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) { return newSystemError(err) } for _, m := range config.Mounts { + for _, precmd := range m.PremountCmds { + if err := mountCmd(precmd); err != nil { + return newSystemError(err) + } + } if err := mountToRootfs(m, config.Rootfs, config.MountLabel); err != nil { return newSystemError(err) } + + for _, postcmd := range m.PostmountCmds { + if err := mountCmd(postcmd); err != nil { + return newSystemError(err) + } + } } if err := createDevices(config); err != nil { return newSystemError(err) @@ -62,6 +76,18 @@ func setupRootfs(config *configs.Config, console *linuxConsole) (err error) { return nil } +func mountCmd(cmd configs.Command) error { + + command := exec.Command(cmd.Path, cmd.Args[:]...) + command.Env = cmd.Env + command.Dir = cmd.Dir + if out, err := command.CombinedOutput(); err != nil { + return fmt.Errorf("%#v failed: %s: %v", cmd, string(out), err) + } + + return nil +} + func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { var ( dest = m.Destination @@ -72,11 +98,19 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { } switch m.Device { - case "proc", "mqueue", "sysfs": + case "proc", "sysfs": if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) { return err } return syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), "") + case "mqueue": + if err := os.MkdirAll(dest, 0755); err != nil && !os.IsExist(err) { + return err + } + if err := syscall.Mount(m.Source, dest, m.Device, uintptr(m.Flags), ""); err != nil { + return err + } + return label.SetFileLabel(dest, mountLabel) case "tmpfs": stat, err := os.Stat(dest) if err != nil { @@ -126,6 +160,37 @@ func mountToRootfs(m *configs.Mount, rootfs, mountLabel string) error { return err } } + case "cgroup": + mounts, err := cgroups.GetCgroupMounts() + if err != nil { + return err + } + var binds []*configs.Mount + for _, mm := range mounts { + dir, err := mm.GetThisCgroupDir() + if err != nil { + return err + } + binds = append(binds, &configs.Mount{ + Device: "bind", + Source: filepath.Join(mm.Mountpoint, dir), + Destination: filepath.Join(m.Destination, strings.Join(mm.Subsystems, ",")), + Flags: syscall.MS_BIND | syscall.MS_REC | syscall.MS_RDONLY, + }) + } + tmpfs := &configs.Mount{ + Device: "tmpfs", + Destination: m.Destination, + Flags: syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV, + } + if err := mountToRootfs(tmpfs, rootfs, mountLabel); err != nil { + return err + } + for _, b := range binds { + if err := mountToRootfs(b, rootfs, mountLabel); err != nil { + return err + } + } default: return fmt.Errorf("unknown mount device %q to %q", m.Device, m.Destination) } @@ -240,9 +305,9 @@ func mknodDevice(dest string, node *configs.Device) error { } func prepareRoot(config *configs.Config) error { - flag := syscall.MS_PRIVATE | syscall.MS_REC - if config.NoPivotRoot { - flag = syscall.MS_SLAVE | syscall.MS_REC + flag := syscall.MS_SLAVE | syscall.MS_REC + if config.Privatefs { + flag = syscall.MS_PRIVATE | syscall.MS_REC } if err := syscall.Mount("", "/", "", uintptr(flag), ""); err != nil { return err @@ -355,3 +420,10 @@ func maskFile(path string) error { } return nil } + +// writeSystemProperty writes the value to a path under /proc/sys as determined from the key. +// For e.g. net.ipv4.ip_forward translated to /proc/sys/net/ipv4/ip_forward. +func writeSystemProperty(key, value string) error { + keyPath := strings.Replace(key, ".", "/", -1) + return ioutil.WriteFile(path.Join("/proc/sys", keyPath), []byte(value), 0644) +} diff --git a/vendor/src/github.com/docker/libcontainer/standard_init_linux.go b/vendor/src/github.com/docker/libcontainer/standard_init_linux.go index 282832b56..251c09f69 100644 --- a/vendor/src/github.com/docker/libcontainer/standard_init_linux.go +++ b/vendor/src/github.com/docker/libcontainer/standard_init_linux.go @@ -64,6 +64,13 @@ func (l *linuxStandardInit) Init() error { if err := label.SetProcessLabel(l.config.Config.ProcessLabel); err != nil { return err } + + for key, value := range l.config.Config.SystemProperties { + if err := writeSystemProperty(key, value); err != nil { + return err + } + } + for _, path := range l.config.Config.ReadonlyPaths { if err := remountReadonly(path); err != nil { return err diff --git a/vendor/src/github.com/docker/libcontainer/system/setns_linux.go b/vendor/src/github.com/docker/libcontainer/system/setns_linux.go index 228e6ccd7..a3c4cbb27 100644 --- a/vendor/src/github.com/docker/libcontainer/system/setns_linux.go +++ b/vendor/src/github.com/docker/libcontainer/system/setns_linux.go @@ -12,8 +12,10 @@ import ( // We are declaring the macro here because the SETNS syscall does not exist in th stdlib var setNsMap = map[string]uintptr{ "linux/386": 346, + "linux/arm64": 268, "linux/amd64": 308, - "linux/arm": 374, + "linux/arm": 375, + "linux/ppc": 350, "linux/ppc64": 350, "linux/ppc64le": 350, "linux/s390x": 339, diff --git a/vendor/src/github.com/docker/libcontainer/system/syscall_linux_64.go b/vendor/src/github.com/docker/libcontainer/system/syscall_linux_64.go index 6840c3770..0816bf828 100644 --- a/vendor/src/github.com/docker/libcontainer/system/syscall_linux_64.go +++ b/vendor/src/github.com/docker/libcontainer/system/syscall_linux_64.go @@ -1,4 +1,4 @@ -// +build linux,amd64 linux,ppc64 linux,ppc64le linux,s390x +// +build linux,arm64 linux,amd64 linux,ppc linux,ppc64 linux,ppc64le linux,s390x package system diff --git a/vendor/src/github.com/docker/libcontainer/update-vendor.sh b/vendor/src/github.com/docker/libcontainer/update-vendor.sh index b68f5d461..ab471872b 100755 --- a/vendor/src/github.com/docker/libcontainer/update-vendor.sh +++ b/vendor/src/github.com/docker/libcontainer/update-vendor.sh @@ -43,7 +43,7 @@ clone() { clone git github.com/codegangsta/cli 1.1.0 clone git github.com/coreos/go-systemd v2 clone git github.com/godbus/dbus v2 -clone git github.com/Sirupsen/logrus v0.6.6 +clone git github.com/Sirupsen/logrus v0.7.3 clone git github.com/syndtr/gocapability 8e4cdcb # intentionally not vendoring Docker itself... that'd be a circle :) From 815b472a02dc0f593daee4006ce893fe17236b70 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Mon, 4 May 2015 19:56:10 +0200 Subject: [PATCH 738/999] Add more ioutils tests. Closes #11595 Signed-off-by: Vincent Demeester --- pkg/ioutils/readers_test.go | 125 ++++++++++++++++++++++++++++++++++++ pkg/ioutils/writers_test.go | 24 +++++++ 2 files changed, 149 insertions(+) diff --git a/pkg/ioutils/readers_test.go b/pkg/ioutils/readers_test.go index 0af978e06..b4cbfd95f 100644 --- a/pkg/ioutils/readers_test.go +++ b/pkg/ioutils/readers_test.go @@ -2,11 +2,92 @@ package ioutils import ( "bytes" + "fmt" "io" "io/ioutil" + "strings" "testing" ) +// Implement io.Reader +type errorReader struct{} + +func (r *errorReader) Read(p []byte) (int, error) { + return 0, fmt.Errorf("Error reader always fail.") +} + +func TestReadCloserWrapperClose(t *testing.T) { + reader := strings.NewReader("A string reader") + wrapper := NewReadCloserWrapper(reader, func() error { + return fmt.Errorf("This will be called when closing") + }) + err := wrapper.Close() + if err == nil || !strings.Contains(err.Error(), "This will be called when closing") { + t.Fatalf("readCloserWrapper should have call the anonymous func and thus, fail.") + } +} + +func TestReaderErrWrapperReadOnError(t *testing.T) { + called := false + reader := &errorReader{} + wrapper := NewReaderErrWrapper(reader, func() { + called = true + }) + _, err := wrapper.Read([]byte{}) + if err == nil || !strings.Contains(err.Error(), "Error reader always fail.") { + t.Fatalf("readErrWrapper should returned an error") + } + if !called { + t.Fatalf("readErrWrapper should have call the anonymous function on failure") + } +} + +func TestReaderErrWrapperRead(t *testing.T) { + called := false + reader := strings.NewReader("a string reader.") + wrapper := NewReaderErrWrapper(reader, func() { + called = true // Should not be called + }) + // Read 20 byte (should be ok with the string above) + num, err := wrapper.Read(make([]byte, 20)) + if err != nil { + t.Fatal(err) + } + if num != 16 { + t.Fatalf("readerErrWrapper should have read 16 byte, but read %d", num) + } +} + +func TestNewBufReaderWithDrainbufAndBuffer(t *testing.T) { + reader, writer := io.Pipe() + + drainBuffer := make([]byte, 1024) + buffer := bytes.Buffer{} + bufreader := NewBufReaderWithDrainbufAndBuffer(reader, drainBuffer, &buffer) + + // Write everything down to a Pipe + // Usually, a pipe should block but because of the buffered reader, + // the writes will go through + done := make(chan bool) + go func() { + writer.Write([]byte("hello world")) + writer.Close() + done <- true + }() + + // Drain the reader *after* everything has been written, just to verify + // it is indeed buffering + <-done + + output, err := ioutil.ReadAll(bufreader) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(output, []byte("hello world")) { + t.Error(string(output)) + } +} + func TestBufReader(t *testing.T) { reader, writer := io.Pipe() bufreader := NewBufReader(reader) @@ -33,6 +114,50 @@ func TestBufReader(t *testing.T) { } } +func TestBufReaderCloseWithNonReaderCloser(t *testing.T) { + reader := strings.NewReader("buffer") + bufreader := NewBufReader(reader) + + if err := bufreader.Close(); err != nil { + t.Fatal(err) + } + +} + +// implements io.ReadCloser +type simpleReaderCloser struct{} + +func (r *simpleReaderCloser) Read(p []byte) (n int, err error) { + return 0, nil +} + +func (r *simpleReaderCloser) Close() error { + return nil +} + +func TestBufReaderCloseWithReaderCloser(t *testing.T) { + reader := &simpleReaderCloser{} + bufreader := NewBufReader(reader) + + err := bufreader.Close() + if err != nil { + t.Fatal(err) + } + +} + +func TestHashData(t *testing.T) { + reader := strings.NewReader("hash-me") + actual, err := HashData(reader) + if err != nil { + t.Fatal(err) + } + expected := "sha256:4d11186aed035cc624d553e10db358492c84a7cd6b9670d92123c144930450aa" + if actual != expected { + t.Fatalf("Expecting %s, got %s", expected, actual) + } +} + type repeatedReader struct { readCount int maxReads int diff --git a/pkg/ioutils/writers_test.go b/pkg/ioutils/writers_test.go index 80d7f7f79..564b1cd4f 100644 --- a/pkg/ioutils/writers_test.go +++ b/pkg/ioutils/writers_test.go @@ -6,6 +6,30 @@ import ( "testing" ) +func TestWriteCloserWrapperClose(t *testing.T) { + called := false + writer := bytes.NewBuffer([]byte{}) + wrapper := NewWriteCloserWrapper(writer, func() error { + called = true + return nil + }) + if err := wrapper.Close(); err != nil { + t.Fatal(err) + } + if !called { + t.Fatalf("writeCloserWrapper should have call the anonymous function.") + } +} + +func TestNopWriteCloser(t *testing.T) { + writer := bytes.NewBuffer([]byte{}) + wrapper := NopWriteCloser(writer) + if err := wrapper.Close(); err != nil { + t.Fatal("NopWriteCloser always return nil on Close.") + } + +} + func TestNopWriter(t *testing.T) { nw := &NopWriter{} l, err := nw.Write([]byte{'c'}) From ea6649e701cf04b90fcc4a1abb29337d99b7af70 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Mon, 4 May 2015 20:17:41 +0200 Subject: [PATCH 739/999] Add runcom to maintainers.people Signed-off-by: Antonio Murdaca --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 5c02fa671..5cf0757dd 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -345,6 +345,7 @@ made through a pull request. "icecrime", "jfrazelle", "lk4d4", + "runcom", "tibor", "unclejack", "vbatts", @@ -592,6 +593,11 @@ made through a pull request. Email = "mary.anthony@docker.com" GitHub = "moxiegirl" + [people.runcom] + Name = "Antonio Murdaca" + Email = "me@runcom.ninja" + GitHub = "runcom" + [people.sday] Name = "Stephen Day" Email = "stephen.day@docker.com" From 7d371c0b470334189720840854b2d5acbb1c7909 Mon Sep 17 00:00:00 2001 From: mauriyouth Date: Sat, 2 May 2015 17:29:00 +0200 Subject: [PATCH 740/999] Make /etc/hosts, /etc/resolv.conf, /etc/hostname read only if --read-only is enable Signed-off-by: Antonio Murdaca --- daemon/volumes.go | 6 ++-- integration-cli/docker_cli_run_test.go | 46 +++++++++++++++++++++++++- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/daemon/volumes.go b/daemon/volumes.go index ea117a1e3..49fc81255 100644 --- a/daemon/volumes.go +++ b/daemon/volumes.go @@ -241,13 +241,13 @@ func validMountMode(mode string) bool { func (container *Container) specialMounts() []execdriver.Mount { var mounts []execdriver.Mount if container.ResolvConfPath != "" { - mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: true, Private: true}) + mounts = append(mounts, execdriver.Mount{Source: container.ResolvConfPath, Destination: "/etc/resolv.conf", Writable: !container.hostConfig.ReadonlyRootfs, Private: true}) } if container.HostnamePath != "" { - mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: true, Private: true}) + mounts = append(mounts, execdriver.Mount{Source: container.HostnamePath, Destination: "/etc/hostname", Writable: !container.hostConfig.ReadonlyRootfs, Private: true}) } if container.HostsPath != "" { - mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: true, Private: true}) + mounts = append(mounts, execdriver.Mount{Source: container.HostsPath, Destination: "/etc/hosts", Writable: !container.hostConfig.ReadonlyRootfs, Private: true}) } return mounts } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 0cf5c31ee..cc4c9988a 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2951,7 +2951,15 @@ func (s *DockerSuite) TestRunContainerWithWritableRootfs(c *check.C) { func (s *DockerSuite) TestRunContainerWithReadonlyRootfs(c *check.C) { testRequires(c, NativeExecDriver) - out, err := exec.Command(dockerBinary, "run", "--read-only", "--rm", "busybox", "touch", "/file").CombinedOutput() + for _, f := range []string{"/file", "/etc/hosts", "/etc/resolv.conf", "/etc/hostname"} { + testReadOnlyFile(f, c) + } +} + +func testReadOnlyFile(filename string, c *check.C) { + testRequires(c, NativeExecDriver) + + out, err := exec.Command(dockerBinary, "run", "--read-only", "--rm", "busybox", "touch", filename).CombinedOutput() if err == nil { c.Fatal("expected container to error on run with read only error") } @@ -2961,6 +2969,42 @@ func (s *DockerSuite) TestRunContainerWithReadonlyRootfs(c *check.C) { } } +func (s *DockerSuite) TestRunContainerWithReadonlyEtcHostsAndLinkedContainer(c *check.C) { + testRequires(c, NativeExecDriver) + + _, err := runCommand(exec.Command(dockerBinary, "run", "-d", "--name", "test-etc-hosts-ro-linked", "busybox", "top")) + c.Assert(err, check.IsNil) + + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--read-only", "--link", "test-etc-hosts-ro-linked:testlinked", "busybox", "cat", "/etc/hosts")) + c.Assert(err, check.IsNil) + + if !strings.Contains(string(out), "testlinked") { + c.Fatal("Expected /etc/hosts to be updated even if --read-only enabled") + } +} + +func (s *DockerSuite) TestRunContainerWithReadonlyRootfsWithDnsFlag(c *check.C) { + testRequires(c, NativeExecDriver) + + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--read-only", "--dns", "1.1.1.1", "busybox", "/bin/cat", "/etc/resolv.conf")) + c.Assert(err, check.IsNil) + + if !strings.Contains(string(out), "1.1.1.1") { + c.Fatal("Expected /etc/resolv.conf to be updated even if --read-only enabled and --dns flag used") + } +} + +func (s *DockerSuite) TestRunContainerWithReadonlyRootfsWithAddHostFlag(c *check.C) { + testRequires(c, NativeExecDriver) + + out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--read-only", "--add-host", "testreadonly:127.0.0.1", "busybox", "/bin/cat", "/etc/hosts")) + c.Assert(err, check.IsNil) + + if !strings.Contains(string(out), "testreadonly") { + c.Fatal("Expected /etc/hosts to be updated even if --read-only enabled and --add-host flag used") + } +} + func (s *DockerSuite) TestRunVolumesFromRestartAfterRemoved(c *check.C) { out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "voltest", "-v", "/foo", "busybox")) if err != nil { From 8771cafab65e50d09d3590a7f22758e919b78fe4 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Sun, 3 May 2015 14:54:55 +0200 Subject: [PATCH 741/999] Add tests for API container delete Signed-off-by: Antonio Murdaca --- integration-cli/docker_api_containers_test.go | 110 ++++++++++++++++++ integration-cli/docker_cli_rm_test.go | 11 -- 2 files changed, 110 insertions(+), 11 deletions(-) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index 2651bd7ac..f9da6fbcb 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "io" "net/http" + "os" "os/exec" "strings" "time" @@ -1040,3 +1041,112 @@ func (s *DockerSuite) TestContainerApiCopyContainerNotFound(c *check.C) { c.Assert(err, check.IsNil) c.Assert(status, check.Equals, http.StatusNotFound) } + +func (s *DockerSuite) TestContainerApiDelete(c *check.C) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + + id := strings.TrimSpace(out) + c.Assert(waitRun(id), check.IsNil) + + stopCmd := exec.Command(dockerBinary, "stop", id) + _, err = runCommand(stopCmd) + c.Assert(err, check.IsNil) + + status, _, err := sockRequest("DELETE", "/containers/"+id, nil) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusNoContent) +} + +func (s *DockerSuite) TestContainerApiDeleteNotExist(c *check.C) { + status, body, err := sockRequest("DELETE", "/containers/doesnotexist", nil) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusNotFound) + c.Assert(string(body), check.Matches, "no such id: doesnotexist\n") +} + +func (s *DockerSuite) TestContainerApiDeleteForce(c *check.C) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + + id := strings.TrimSpace(out) + c.Assert(waitRun(id), check.IsNil) + + status, _, err := sockRequest("DELETE", "/containers/"+id+"?force=1", nil) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusNoContent) +} + +func (s *DockerSuite) TestContainerApiDeleteRemoveLinks(c *check.C) { + runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "tlink1", "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + + id := strings.TrimSpace(out) + c.Assert(waitRun(id), check.IsNil) + + runCmd = exec.Command(dockerBinary, "run", "--link", "tlink1:tlink1", "--name", "tlink2", "-d", "busybox", "top") + out, _, err = runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + + id2 := strings.TrimSpace(out) + c.Assert(waitRun(id2), check.IsNil) + + links, err := inspectFieldJSON(id2, "HostConfig.Links") + c.Assert(err, check.IsNil) + + if links != "[\"/tlink1:/tlink2/tlink1\"]" { + c.Fatal("expected to have links between containers") + } + + status, _, err := sockRequest("DELETE", "/containers/tlink2/tlink1?link=1", nil) + c.Assert(err, check.IsNil) + c.Assert(status, check.Equals, http.StatusNoContent) + + linksPostRm, err := inspectFieldJSON(id2, "HostConfig.Links") + c.Assert(err, check.IsNil) + + if linksPostRm != "null" { + c.Fatal("call to api deleteContainer links should have removed the specified links") + } +} + +func (s *DockerSuite) TestContainerApiDeleteConflict(c *check.C) { + runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + + id := strings.TrimSpace(out) + c.Assert(waitRun(id), check.IsNil) + + status, _, err := sockRequest("DELETE", "/containers/"+id, nil) + c.Assert(status, check.Equals, http.StatusConflict) + c.Assert(err, check.IsNil) +} + +func (s *DockerSuite) TestContainerApiDeleteRemoveVolume(c *check.C) { + testRequires(c, SameHostDaemon) + + runCmd := exec.Command(dockerBinary, "run", "-d", "-v", "/testvolume", "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + c.Assert(err, check.IsNil) + + id := strings.TrimSpace(out) + c.Assert(waitRun(id), check.IsNil) + + vol, err := inspectFieldMap(id, "Volumes", "/testvolume") + c.Assert(err, check.IsNil) + + _, err = os.Stat(vol) + c.Assert(err, check.IsNil) + + status, _, err := sockRequest("DELETE", "/containers/"+id+"?v=1&force=1", nil) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) + + if _, err := os.Stat(vol); !os.IsNotExist(err) { + c.Fatalf("expected to get ErrNotExist error, got %v", err) + } +} diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index b8d1b843d..ba4e0e6bf 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -1,7 +1,6 @@ package main import ( - "net/http" "os" "os/exec" "strings" @@ -54,16 +53,6 @@ func (s *DockerSuite) TestRmRunningContainer(c *check.C) { } -func (s *DockerSuite) TestRmRunningContainerCheckError409(c *check.C) { - - createRunningContainer(c, "foo") - - endpoint := "/containers/foo" - status, _, err := sockRequest("DELETE", endpoint, nil) - c.Assert(status, check.Equals, http.StatusConflict) - c.Assert(err, check.IsNil) -} - func (s *DockerSuite) TestRmForceRemoveRunningContainer(c *check.C) { createRunningContainer(c, "foo") From dca9e02b15a3757272c90ec4cf0cc2b052a25fe3 Mon Sep 17 00:00:00 2001 From: wlan0 Date: Mon, 4 May 2015 14:39:48 -0700 Subject: [PATCH 742/999] Add log opts flag to pass in logging options Signed-off-by: wlan0 --- daemon/config.go | 1 + docker/daemon.go | 3 +++ docs/sources/reference/run.md | 4 +++ opts/opts.go | 51 +++++++++++++++++++++++++++++++++++ opts/opts_test.go | 35 ++++++++++++++++++++++++ runconfig/parse.go | 18 ++++++++++++- 6 files changed, 111 insertions(+), 1 deletion(-) diff --git a/daemon/config.go b/daemon/config.go index 5fe01e5c7..cd555cfc8 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -80,6 +80,7 @@ func (config *Config) InstallFlags() { opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers") flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Default driver for container logs") flag.BoolVar(&config.Bridge.EnableUserlandProxy, []string{"-userland-proxy"}, true, "Use userland proxy for loopback traffic") + opts.LogOptsVar(config.LogConfig.Config, []string{"-log-opt"}, "Set log driver options") } func getDefaultNetworkMtu() int { diff --git a/docker/daemon.go b/docker/daemon.go index 55cb090d6..06bdbc0fc 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -32,6 +32,9 @@ var ( ) func init() { + if daemonCfg.LogConfig.Config == nil { + daemonCfg.LogConfig.Config = make(map[string]string) + } daemonCfg.InstallFlags() registryCfg.InstallFlags() } diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 014582802..ff6cfa80b 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -849,6 +849,10 @@ command is not available for this logging driver Journald logging driver for Docker. Writes log messages to journald; the container id will be stored in the journal's `CONTAINER_ID` field. `docker logs` command is not available for this logging driver. For detailed information on working with this logging driver, see [the journald logging driver](reference/logging/journald) reference documentation. +#### Log Opts : + +Logging options for configuring a log driver. The following log options are supported: [none] + ## Overriding Dockerfile image defaults When a developer builds an image from a [*Dockerfile*](/reference/builder) diff --git a/opts/opts.go b/opts/opts.go index 1db454736..380159663 100644 --- a/opts/opts.go +++ b/opts/opts.go @@ -28,6 +28,14 @@ func ListVar(values *[]string, names []string, usage string) { flag.Var(newListOptsRef(values, nil), names, usage) } +func MapVar(values map[string]string, names []string, usage string) { + flag.Var(newMapOpt(values, nil), names, usage) +} + +func LogOptsVar(values map[string]string, names []string, usage string) { + flag.Var(newMapOpt(values, ValidateLogOpts), names, usage) +} + func HostListVar(values *[]string, names []string, usage string) { flag.Var(newListOptsRef(values, ValidateHost), names, usage) } @@ -130,10 +138,53 @@ func (opts *ListOpts) Len() int { return len((*opts.values)) } +//MapOpts type +type MapOpts struct { + values map[string]string + validator ValidatorFctType +} + +func (opts *MapOpts) Set(value string) error { + if opts.validator != nil { + v, err := opts.validator(value) + if err != nil { + return err + } + value = v + } + vals := strings.SplitN(value, "=", 2) + if len(vals) == 1 { + (opts.values)[vals[0]] = "" + } else { + (opts.values)[vals[0]] = vals[1] + } + return nil +} + +func (opts *MapOpts) String() string { + return fmt.Sprintf("%v", map[string]string((opts.values))) +} + +func newMapOpt(values map[string]string, validator ValidatorFctType) *MapOpts { + return &MapOpts{ + values: values, + validator: validator, + } +} + // Validators type ValidatorFctType func(val string) (string, error) type ValidatorFctListType func(val string) ([]string, error) +func ValidateLogOpts(val string) (string, error) { + allowedKeys := map[string]string{} + vals := strings.Split(val, "=") + if allowedKeys[vals[0]] != "" { + return val, nil + } + return "", fmt.Errorf("%s is not a valid log opt", vals[0]) +} + func ValidateAttach(val string) (string, error) { s := strings.ToLower(val) for _, str := range []string{"stdin", "stdout", "stderr"} { diff --git a/opts/opts_test.go b/opts/opts_test.go index 8370926da..dfad430ac 100644 --- a/opts/opts_test.go +++ b/opts/opts_test.go @@ -1,6 +1,7 @@ package opts import ( + "fmt" "strings" "testing" ) @@ -28,6 +29,31 @@ func TestValidateIPAddress(t *testing.T) { } +func TestMapOpts(t *testing.T) { + tmpMap := make(map[string]string) + o := newMapOpt(tmpMap, logOptsValidator) + o.Set("max-size=1") + if o.String() != "map[max-size:1]" { + t.Errorf("%s != [map[max-size:1]", o.String()) + } + + o.Set("max-file=2") + if len(tmpMap) != 2 { + t.Errorf("map length %d != 2", len(tmpMap)) + } + + if tmpMap["max-file"] != "2" { + t.Errorf("max-file = %s != 2", tmpMap["max-file"]) + } + + if tmpMap["max-size"] != "1" { + t.Errorf("max-size = %s != 1", tmpMap["max-size"]) + } + if o.Set("dummy-val=3") == nil { + t.Errorf("validator is not being called") + } +} + func TestValidateMACAddress(t *testing.T) { if _, err := ValidateMACAddress(`92:d0:c6:0a:29:33`); err != nil { t.Fatalf("ValidateMACAddress(`92:d0:c6:0a:29:33`) got %s", err) @@ -152,3 +178,12 @@ func TestValidateExtraHosts(t *testing.T) { } } } + +func logOptsValidator(val string) (string, error) { + allowedKeys := map[string]string{"max-size": "1", "max-file": "2"} + vals := strings.Split(val, "=") + if allowedKeys[vals[0]] != "" { + return val, nil + } + return "", fmt.Errorf("invalid key %s", vals[0]) +} diff --git a/runconfig/parse.go b/runconfig/parse.go index ac5cdbf61..e944a644e 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -47,6 +47,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flCapDrop = opts.NewListOpts(nil) flSecurityOpt = opts.NewListOpts(nil) flLabelsFile = opts.NewListOpts(nil) + flLoggingOpts = opts.NewListOpts(nil) flNetwork = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container") flPrivileged = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container") @@ -95,6 +96,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe cmd.Var(&flCapDrop, []string{"-cap-drop"}, "Drop Linux capabilities") cmd.Var(&flSecurityOpt, []string{"-security-opt"}, "Security Options") cmd.Var(flUlimits, []string{"-ulimit"}, "Ulimit options") + cmd.Var(&flLoggingOpts, []string{"-log-opt"}, "Log driver options") cmd.Require(flag.Min, 1) @@ -283,6 +285,11 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe return nil, nil, cmd, err } + loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll()) + if err != nil { + return nil, nil, cmd, err + } + config := &Config{ Hostname: hostname, Domainname: domainname, @@ -335,7 +342,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe SecurityOpt: flSecurityOpt.GetAll(), ReadonlyRootfs: *flReadonlyRootfs, Ulimits: flUlimits.GetList(), - LogConfig: LogConfig{Type: *flLoggingDriver}, + LogConfig: LogConfig{Type: *flLoggingDriver, Config: loggingOpts}, CgroupParent: *flCgroupParent, } @@ -377,6 +384,15 @@ func convertKVStringsToMap(values []string) map[string]string { return result } +func parseLoggingOpts(loggingDriver string, loggingOpts []string) (map[string]string, error) { + loggingOptsMap := convertKVStringsToMap(loggingOpts) + if loggingDriver == "none" && len(loggingOpts) > 0 { + return map[string]string{}, fmt.Errorf("Invalid logging opts for driver %s", loggingDriver) + } + //TODO - validation step + return loggingOptsMap, nil +} + // ParseRestartPolicy returns the parsed policy or an error indicating what is incorrect func ParseRestartPolicy(policy string) (RestartPolicy, error) { p := RestartPolicy{} From 377ed712d38e75d24ebfe2b5b91455a77bf8b086 Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 4 May 2015 14:56:23 -0700 Subject: [PATCH 743/999] Windows: graph\export.go filepath fixes Signed-off-by: John Howard --- graph/export.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/graph/export.go b/graph/export.go index ae061a8a0..c356a2322 100644 --- a/graph/export.go +++ b/graph/export.go @@ -5,7 +5,7 @@ import ( "io" "io/ioutil" "os" - "path" + "path/filepath" "github.com/Sirupsen/logrus" "github.com/docker/docker/pkg/archive" @@ -83,7 +83,7 @@ func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { } // write repositories, if there is something to write if len(rootRepoMap) > 0 { - f, err := os.OpenFile(path.Join(tempdir, "repositories"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + f, err := os.OpenFile(filepath.Join(tempdir, "repositories"), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) if err != nil { f.Close() return err @@ -115,7 +115,7 @@ func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error { func (s *TagStore) exportImage(name, tempdir string) error { for n := name; n != ""; { // temporary directory - tmpImageDir := path.Join(tempdir, n) + tmpImageDir := filepath.Join(tempdir, n) if err := os.Mkdir(tmpImageDir, os.FileMode(0755)); err != nil { if os.IsExist(err) { return nil @@ -126,12 +126,12 @@ func (s *TagStore) exportImage(name, tempdir string) error { var version = "1.0" var versionBuf = []byte(version) - if err := ioutil.WriteFile(path.Join(tmpImageDir, "VERSION"), versionBuf, os.FileMode(0644)); err != nil { + if err := ioutil.WriteFile(filepath.Join(tmpImageDir, "VERSION"), versionBuf, os.FileMode(0644)); err != nil { return err } // serialize json - json, err := os.Create(path.Join(tmpImageDir, "json")) + json, err := os.Create(filepath.Join(tmpImageDir, "json")) if err != nil { return err } @@ -148,7 +148,7 @@ func (s *TagStore) exportImage(name, tempdir string) error { } // serialize filesystem - fsTar, err := os.Create(path.Join(tmpImageDir, "layer.tar")) + fsTar, err := os.Create(filepath.Join(tmpImageDir, "layer.tar")) if err != nil { return err } From b30f14f06d3a6c289257cf29d2e967ba09900a1e Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 4 May 2015 15:05:54 -0700 Subject: [PATCH 744/999] Windows: First fix for graph\graph.go Signed-off-by: John Howard --- graph/graph.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/graph/graph.go b/graph/graph.go index 9b2d7c2ee..3afe63e01 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -7,7 +7,6 @@ import ( "io" "io/ioutil" "os" - "path" "path/filepath" "runtime" "strings" @@ -23,6 +22,7 @@ import ( "github.com/docker/docker/pkg/progressreader" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/truncindex" "github.com/docker/docker/runconfig" ) @@ -42,7 +42,7 @@ func NewGraph(root string, driver graphdriver.Driver) (*Graph, error) { return nil, err } // Create the root directory if it doesn't exists - if err := os.MkdirAll(root, 0700); err != nil && !os.IsExist(err) { + if err := system.MkdirAll(root, 0700); err != nil && !os.IsExist(err) { return nil, err } @@ -229,8 +229,8 @@ func (graph *Graph) TempLayerArchive(id string, sf *streamformatter.StreamFormat // 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", stringid.GenerateRandomID()) - if err := os.MkdirAll(dir, 0700); err != nil { + dir := filepath.Join(graph.Root, "_tmp", stringid.GenerateRandomID()) + if err := system.MkdirAll(dir, 0700); err != nil { return "", err } return dir, nil @@ -290,28 +290,28 @@ func SetupInitLayer(initLayer string) error { parts := strings.Split(pth, "/") prev := "/" for _, p := range parts[1:] { - prev = path.Join(prev, p) - syscall.Unlink(path.Join(initLayer, prev)) + prev = filepath.Join(prev, p) + syscall.Unlink(filepath.Join(initLayer, prev)) } - if _, err := os.Stat(path.Join(initLayer, pth)); err != nil { + if _, err := os.Stat(filepath.Join(initLayer, pth)); err != nil { if os.IsNotExist(err) { - if err := os.MkdirAll(path.Join(initLayer, path.Dir(pth)), 0755); err != nil { + if err := system.MkdirAll(filepath.Join(initLayer, filepath.Dir(pth)), 0755); err != nil { return err } switch typ { case "dir": - if err := os.MkdirAll(path.Join(initLayer, pth), 0755); err != nil { + if err := system.MkdirAll(filepath.Join(initLayer, pth), 0755); err != nil { return err } case "file": - f, err := os.OpenFile(path.Join(initLayer, pth), os.O_CREATE, 0755) + f, err := os.OpenFile(filepath.Join(initLayer, pth), os.O_CREATE, 0755) if err != nil { return err } f.Close() default: - if err := os.Symlink(typ, path.Join(initLayer, pth)); err != nil { + if err := os.Symlink(typ, filepath.Join(initLayer, pth)); err != nil { return err } } @@ -432,7 +432,7 @@ func (graph *Graph) Heads() (map[string]*image.Image, error) { } func (graph *Graph) ImageRoot(id string) string { - return path.Join(graph.Root, id) + return filepath.Join(graph.Root, id) } func (graph *Graph) Driver() graphdriver.Driver { From bb1ecde1648220871bbf627b8a844fc8501163ba Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 4 May 2015 15:14:39 -0700 Subject: [PATCH 745/999] Windows: Filepath in graph\load.go Signed-off-by: John Howard --- graph/load.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/graph/load.go b/graph/load.go index d978b1ee8..313f5d23a 100644 --- a/graph/load.go +++ b/graph/load.go @@ -7,7 +7,7 @@ import ( "io" "io/ioutil" "os" - "path" + "path/filepath" "github.com/Sirupsen/logrus" "github.com/docker/docker/image" @@ -25,7 +25,7 @@ func (s *TagStore) Load(inTar io.ReadCloser, outStream io.Writer) error { defer os.RemoveAll(tmpImageDir) var ( - repoDir = path.Join(tmpImageDir, "repo") + repoDir = filepath.Join(tmpImageDir, "repo") ) if err := os.Mkdir(repoDir, os.ModeDir); err != nil { @@ -58,7 +58,7 @@ func (s *TagStore) Load(inTar io.ReadCloser, outStream io.Writer) error { } } - reposJSONFile, err := os.Open(path.Join(tmpImageDir, "repo", "repositories")) + reposJSONFile, err := os.Open(filepath.Join(tmpImageDir, "repo", "repositories")) if err != nil { if !os.IsNotExist(err) { return err @@ -87,13 +87,13 @@ func (s *TagStore) recursiveLoad(address, tmpImageDir string) error { if _, err := s.LookupImage(address); err != nil { logrus.Debugf("Loading %s", address) - imageJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", address, "json")) + imageJson, err := ioutil.ReadFile(filepath.Join(tmpImageDir, "repo", address, "json")) if err != nil { logrus.Debugf("Error reading json", err) return err } - layer, err := os.Open(path.Join(tmpImageDir, "repo", address, "layer.tar")) + layer, err := os.Open(filepath.Join(tmpImageDir, "repo", address, "layer.tar")) if err != nil { logrus.Debugf("Error reading embedded tar", err) return err From f42348e18f73d1d775d77ac75bc96466aae56d7c Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Mon, 10 Nov 2014 16:19:16 -0800 Subject: [PATCH 746/999] Add `--userland-proxy` daemon flag The `--userland-proxy` daemon flag makes it possible to rely on hairpin NAT and additional iptables routes instead of userland proxy for port publishing and inter-container communication. Usage of the userland proxy remains the default as hairpin NAT is unsupported by older kernels. Signed-off-by: Arnaud Porterie --- daemon/config.go | 1 + daemon/container.go | 1 + daemon/execdriver/driver.go | 1 + daemon/execdriver/native/create.go | 1 + daemon/network/settings.go | 1 + daemon/networkdriver/bridge/driver.go | 43 +++++++--- daemon/networkdriver/bridge/driver_test.go | 2 +- daemon/networkdriver/portmapper/mapper.go | 31 +++++--- .../networkdriver/portmapper/mapper_test.go | 12 +-- docs/man/docker.1.md | 15 ++-- docs/sources/articles/networking.md | 20 ++++- docs/sources/reference/commandline/cli.md | 3 +- integration-cli/docker_cli_nat_test.go | 78 ++++++++++++++----- integration-cli/docker_cli_run_test.go | 46 ++--------- pkg/iptables/firewalld_test.go | 2 +- pkg/iptables/iptables.go | 9 ++- pkg/iptables/iptables_test.go | 5 +- 17 files changed, 172 insertions(+), 99 deletions(-) diff --git a/daemon/config.go b/daemon/config.go index 43b08531b..5fe01e5c7 100644 --- a/daemon/config.go +++ b/daemon/config.go @@ -79,6 +79,7 @@ func (config *Config) InstallFlags() { config.Ulimits = make(map[string]*ulimit.Ulimit) opts.UlimitMapVar(config.Ulimits, []string{"-default-ulimit"}, "Set default ulimits for containers") flag.StringVar(&config.LogConfig.Type, []string{"-log-driver"}, "json-file", "Default driver for container logs") + flag.BoolVar(&config.Bridge.EnableUserlandProxy, []string{"-userland-proxy"}, true, "Use userland proxy for loopback traffic") } func getDefaultNetworkMtu() int { diff --git a/daemon/container.go b/daemon/container.go index ef4229534..d74ac76dd 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -307,6 +307,7 @@ func populateCommand(c *Container, env []string) error { GlobalIPv6Address: network.GlobalIPv6Address, GlobalIPv6PrefixLen: network.GlobalIPv6PrefixLen, IPv6Gateway: network.IPv6Gateway, + HairpinMode: network.HairpinMode, } } case "container": diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index df5901ed0..28b3ee123 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -96,6 +96,7 @@ type NetworkInterface struct { LinkLocalIPv6Address string `json:"link_local_ipv6"` GlobalIPv6PrefixLen int `json:"global_ipv6_prefix_len"` IPv6Gateway string `json:"ipv6_gateway"` + HairpinMode bool `json:"hairpin_mode"` } // TODO Windows: Factor out ulimit.Rlimit diff --git a/daemon/execdriver/native/create.go b/daemon/execdriver/native/create.go index fa53621c4..61730deb5 100644 --- a/daemon/execdriver/native/create.go +++ b/daemon/execdriver/native/create.go @@ -114,6 +114,7 @@ func (d *driver) createNetwork(container *configs.Config, c *execdriver.Command) Gateway: c.Network.Interface.Gateway, Type: "veth", Bridge: c.Network.Interface.Bridge, + HairpinMode: c.Network.Interface.HairpinMode, } if c.Network.Interface.GlobalIPv6Address != "" { vethNetwork.IPv6Address = fmt.Sprintf("%s/%d", c.Network.Interface.GlobalIPv6Address, c.Network.Interface.GlobalIPv6PrefixLen) diff --git a/daemon/network/settings.go b/daemon/network/settings.go index f3841f09b..91d61160a 100644 --- a/daemon/network/settings.go +++ b/daemon/network/settings.go @@ -15,4 +15,5 @@ type Settings struct { Bridge string PortMapping map[string]map[string]string // Deprecated Ports nat.PortMap + HairpinMode bool } diff --git a/daemon/networkdriver/bridge/driver.go b/daemon/networkdriver/bridge/driver.go index 2fe04d206..a324d6b6e 100644 --- a/daemon/networkdriver/bridge/driver.go +++ b/daemon/networkdriver/bridge/driver.go @@ -8,6 +8,7 @@ import ( "net" "os" "os/exec" + "path/filepath" "strconv" "strings" "sync" @@ -83,6 +84,7 @@ var ( gatewayIPv6 net.IP portMapper *portmapper.PortMapper once sync.Once + hairpinMode bool defaultBindingIP = net.ParseIP("0.0.0.0") currentInterfaces = ifaces{c: make(map[string]*networkInterface)} @@ -100,6 +102,7 @@ type Config struct { EnableIptables bool EnableIpForward bool EnableIpMasq bool + EnableUserlandProxy bool DefaultIp net.IP Iface string IP string @@ -131,6 +134,8 @@ func InitDriver(config *Config) error { defaultBindingIP = config.DefaultIp } + hairpinMode = !config.EnableUserlandProxy + bridgeIface = config.Iface usingDefaultBridge := false if bridgeIface == "" { @@ -243,39 +248,46 @@ func InitDriver(config *Config) error { if config.EnableIpForward { // Enable IPv4 forwarding if err := ioutil.WriteFile("/proc/sys/net/ipv4/ip_forward", []byte{'1', '\n'}, 0644); err != nil { - logrus.Warnf("WARNING: unable to enable IPv4 forwarding: %s\n", err) + logrus.Warnf("Unable to enable IPv4 forwarding: %v", err) } if config.FixedCIDRv6 != "" { // Enable IPv6 forwarding if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/default/forwarding", []byte{'1', '\n'}, 0644); err != nil { - logrus.Warnf("WARNING: unable to enable IPv6 default forwarding: %s\n", err) + logrus.Warnf("Unable to enable IPv6 default forwarding: %v", err) } if err := ioutil.WriteFile("/proc/sys/net/ipv6/conf/all/forwarding", []byte{'1', '\n'}, 0644); err != nil { - logrus.Warnf("WARNING: unable to enable IPv6 all forwarding: %s\n", err) + logrus.Warnf("Unable to enable IPv6 all forwarding: %v", err) } } } + if hairpinMode { + // Enable loopback adresses routing + sysPath := filepath.Join("/proc/sys/net/ipv4/conf", bridgeIface, "route_localnet") + if err := ioutil.WriteFile(sysPath, []byte{'1', '\n'}, 0644); err != nil { + logrus.Warnf("Unable to enable local routing for hairpin mode: %v", err) + } + } + // We can always try removing the iptables if err := iptables.RemoveExistingChain("DOCKER", iptables.Nat); err != nil { return err } if config.EnableIptables { - _, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Nat) + _, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Nat, hairpinMode) if err != nil { return err } // call this on Firewalld reload - iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Nat) }) - - chain, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter) + iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Nat, hairpinMode) }) + chain, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter, hairpinMode) if err != nil { return err } // call this on Firewalld reload - iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Filter) }) + iptables.OnReloaded(func() { iptables.NewChain("DOCKER", bridgeIface, iptables.Filter, hairpinMode) }) portMapper.SetIptablesChain(chain) } @@ -374,6 +386,18 @@ func setupIPTables(addr net.Addr, icc, ipmasq bool) error { } } + // In hairpin mode, masquerade traffic from localhost + if hairpinMode { + masqueradeArgs := []string{"-t", "nat", "-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"} + if !iptables.Exists(iptables.Filter, "POSTROUTING", masqueradeArgs...) { + if output, err := iptables.Raw(append([]string{"-I", "POSTROUTING"}, masqueradeArgs...)...); err != nil { + return fmt.Errorf("Unable to masquerade local traffic: %s", err) + } else if len(output) != 0 { + return fmt.Errorf("Error iptables masquerade local traffic: %s", output) + } + } + } + // Accept all non-intercontainer outgoing packets outgoingArgs := []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"} if !iptables.Exists(iptables.Filter, "FORWARD", outgoingArgs...) { @@ -637,6 +661,7 @@ func Allocate(id, requestedMac, requestedIP, requestedIPv6 string) (*network.Set Bridge: bridgeIface, IPPrefixLen: maskSize, LinkLocalIPv6Address: localIPv6.String(), + HairpinMode: hairpinMode, } if globalIPv6Network != nil { @@ -722,7 +747,7 @@ func AllocatePort(id string, port nat.Port, binding nat.PortBinding) (nat.PortBi return nat.PortBinding{}, err } for i := 0; i < MaxAllocatedPortAttempts; i++ { - if host, err = portMapper.Map(container, ip, hostPort); err == nil { + if host, err = portMapper.Map(container, ip, hostPort, !hairpinMode); err == nil { break } // There is no point in immediately retrying to map an explicitly diff --git a/daemon/networkdriver/bridge/driver_test.go b/daemon/networkdriver/bridge/driver_test.go index d18882e66..c82acb86a 100644 --- a/daemon/networkdriver/bridge/driver_test.go +++ b/daemon/networkdriver/bridge/driver_test.go @@ -177,7 +177,7 @@ func TestLinkContainers(t *testing.T) { } bridgeIface = "lo" - if _, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter); err != nil { + if _, err := iptables.NewChain("DOCKER", bridgeIface, iptables.Filter, false); err != nil { t.Fatal(err) } diff --git a/daemon/networkdriver/portmapper/mapper.go b/daemon/networkdriver/portmapper/mapper.go index 09952ba35..f0c7a507d 100644 --- a/daemon/networkdriver/portmapper/mapper.go +++ b/daemon/networkdriver/portmapper/mapper.go @@ -51,7 +51,7 @@ func (pm *PortMapper) SetIptablesChain(c *iptables.Chain) { pm.chain = c } -func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) { +func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, err error) { pm.lock.Lock() defer pm.lock.Unlock() @@ -59,7 +59,6 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host m *mapping proto string allocatedHostPort int - proxy UserlandProxy ) switch container.(type) { @@ -75,7 +74,9 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host container: container, } - proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port) + if useProxy { + m.userlandProxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port) + } case *net.UDPAddr: proto = "udp" if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil { @@ -88,7 +89,9 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host container: container, } - proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port) + if useProxy { + m.userlandProxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port) + } default: return nil, ErrUnknownBackendAddressType } @@ -112,7 +115,9 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host cleanup := func() error { // need to undo the iptables rules before we return - proxy.Stop() + if m.userlandProxy != nil { + m.userlandProxy.Stop() + } pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort) if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil { return err @@ -121,13 +126,15 @@ func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int) (host return nil } - if err := proxy.Start(); err != nil { - if err := cleanup(); err != nil { - return nil, fmt.Errorf("Error during port allocation cleanup: %v", err) + if m.userlandProxy != nil { + if err := m.userlandProxy.Start(); err != nil { + if err := cleanup(); err != nil { + return nil, fmt.Errorf("Error during port allocation cleanup: %v", err) + } + return nil, err } - return nil, err } - m.userlandProxy = proxy + pm.currentMappings[key] = m return m.host, nil } @@ -154,7 +161,9 @@ func (pm *PortMapper) Unmap(host net.Addr) error { return ErrPortNotMapped } - data.userlandProxy.Stop() + if data.userlandProxy != nil { + data.userlandProxy.Stop() + } delete(pm.currentMappings, key) diff --git a/daemon/networkdriver/portmapper/mapper_test.go b/daemon/networkdriver/portmapper/mapper_test.go index 729fe5607..b908db281 100644 --- a/daemon/networkdriver/portmapper/mapper_test.go +++ b/daemon/networkdriver/portmapper/mapper_test.go @@ -44,22 +44,22 @@ func TestMapPorts(t *testing.T) { return (addr1.Network() == addr2.Network()) && (addr1.String() == addr2.String()) } - if host, err := pm.Map(srcAddr1, dstIp1, 80); err != nil { + if host, err := pm.Map(srcAddr1, dstIp1, 80, true); err != nil { t.Fatalf("Failed to allocate port: %s", err) } else if !addrEqual(dstAddr1, host) { t.Fatalf("Incorrect mapping result: expected %s:%s, got %s:%s", dstAddr1.String(), dstAddr1.Network(), host.String(), host.Network()) } - if _, err := pm.Map(srcAddr1, dstIp1, 80); err == nil { + if _, err := pm.Map(srcAddr1, dstIp1, 80, true); err == nil { t.Fatalf("Port is in use - mapping should have failed") } - if _, err := pm.Map(srcAddr2, dstIp1, 80); err == nil { + if _, err := pm.Map(srcAddr2, dstIp1, 80, true); err == nil { t.Fatalf("Port is in use - mapping should have failed") } - if _, err := pm.Map(srcAddr2, dstIp2, 80); err != nil { + if _, err := pm.Map(srcAddr2, dstIp2, 80, true); err != nil { t.Fatalf("Failed to allocate port: %s", err) } @@ -127,14 +127,14 @@ func TestMapAllPortsSingleInterface(t *testing.T) { for i := 0; i < 10; i++ { start, end := pm.Allocator.Begin, pm.Allocator.End for i := start; i < end; i++ { - if host, err = pm.Map(srcAddr1, dstIp1, 0); err != nil { + if host, err = pm.Map(srcAddr1, dstIp1, 0, true); err != nil { t.Fatal(err) } hosts = append(hosts, host) } - if _, err := pm.Map(srcAddr1, dstIp1, start); err == nil { + if _, err := pm.Map(srcAddr1, dstIp1, start, true); err == nil { t.Fatalf("Port %d should be bound but is not", start) } diff --git a/docs/man/docker.1.md b/docs/man/docker.1.md index 4e7cafe46..8b7b06709 100644 --- a/docs/man/docker.1.md +++ b/docs/man/docker.1.md @@ -53,6 +53,9 @@ To see the man page for a command run **man docker **. **-e**, **--exec-driver**="" Force Docker to use specific exec driver. Default is `native`. +**--exec-opt**=[] + Set exec driver options. See EXEC DRIVER OPTIONS. + **--fixed-cidr**="" IPv4 subnet for fixed IPs (e.g., 10.20.0.0/16); this subnet must be nested in the bridge subnet (which is defined by \-b or \-\-bip) @@ -111,6 +114,9 @@ unix://[/path/to/socket] to use. **-s**, **--storage-driver**="" Force the Docker runtime to use a specific storage driver. +**--selinux-enabled**=*true*|*false* + Enable selinux support. Default is false. SELinux does not presently support the BTRFS storage driver. + **--storage-opt**=[] Set storage driver options. See STORAGE DRIVER OPTIONS. @@ -121,15 +127,12 @@ unix://[/path/to/socket] to use. Use TLS and verify the remote (daemon: verify client, client: verify daemon). Default is false. +**--userland-proxy**=*true*|*false* + Rely on a userland proxy implementation for inter-container and outside-to-container loopback communications. Default is true. + **-v**, **--version**=*true*|*false* Print version information and quit. Default is false. -**--exec-opt**=[] - Set exec driver options. See EXEC DRIVER OPTIONS. - -**--selinux-enabled**=*true*|*false* - Enable selinux support. Default is false. SELinux does not presently support the BTRFS storage driver. - # COMMANDS **attach** Attach to a running container diff --git a/docs/sources/articles/networking.md b/docs/sources/articles/networking.md index 823b450c7..5ee1d7e6b 100644 --- a/docs/sources/articles/networking.md +++ b/docs/sources/articles/networking.md @@ -93,6 +93,9 @@ server when it starts up, and cannot be changed once it is running: * `--mtu=BYTES` — see [Customizing docker0](#docker0) + * `--userland-proxy=true|false` — see + [Binding container ports](#binding-ports) + There are two networking options that can be supplied either at startup or when `docker run` is invoked. When provided at startup, set the default value that `docker run` will later use if the options are not @@ -399,7 +402,7 @@ machine that the Docker server creates when it starts: ... Chain POSTROUTING (policy ACCEPT) target prot opt source destination - MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16 + MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0 ... But if you want containers to accept incoming connections, you will need @@ -452,6 +455,21 @@ address, you can edit your system-wide Docker server settings and add the option `--ip=IP_ADDRESS`. Remember to restart your Docker server after editing this setting. +> **Note**: +> With hairpin NAT enabled (`--userland-proxy=false`), containers port exposure +> is achieved purely through iptables rules, and no attempt to bind the exposed +> port is ever made. This means that nothing prevents shadowing a previously +> listening service outside of Docker through exposing the same port for a +> container. In such conflicting situation, Docker created iptables rules will +> take precedence and route to the container. + +The `--userland-proxy` parameter, true by default, provides a userland +implementation for inter-container and outside-to-container communication. When +disabled, Docker uses both an additional `MASQUERADE` iptable rule and the +`net.ipv4.route_localnet` kernel parameter which allow the host machine to +connect to a local container exposed port through the commonly used loopback +address: this alternative is preferred for performance reason. + Again, this topic is covered without all of these low-level networking details in the [Docker User Guide](/userguide/dockerlinks/) document if you would like to use that as your port redirection reference instead. diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index c69f0a170..7ef5d82eb 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -149,6 +149,7 @@ expect an integer, and they can only be specified once. --default-gateway-v6="" Container default gateway IPv6 address --dns=[] DNS server to use --dns-search=[] DNS search domains to use + --default-ulimit=[] Set default ulimit settings for containers -e, --exec-driver="native" Exec driver to use --fixed-cidr="" IPv4 subnet for fixed IPs --fixed-cidr-v6="" IPv6 subnet for fixed IPs @@ -177,8 +178,8 @@ expect an integer, and they can only be specified once. --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 + --userland-proxy=true Use userland proxy for loopback traffic -v, --version=false Print version information and quit - --default-ulimit=[] Set default ulimit settings for containers. Options with [] may be specified multiple times. diff --git a/integration-cli/docker_cli_nat_test.go b/integration-cli/docker_cli_nat_test.go index 875b6540a..14237042a 100644 --- a/integration-cli/docker_cli_nat_test.go +++ b/integration-cli/docker_cli_nat_test.go @@ -4,14 +4,26 @@ import ( "fmt" "net" "os/exec" + "strconv" "strings" "github.com/go-check/check" ) -func (s *DockerSuite) TestNetworkNat(c *check.C) { - testRequires(c, SameHostDaemon, NativeExecDriver) +func startServerContainer(c *check.C, proto string, port int) string { + cmd := []string{"-d", "-p", fmt.Sprintf("%d:%d", port, port), "busybox", "nc", "-lp", strconv.Itoa(port)} + if proto == "udp" { + cmd = append(cmd, "-u") + } + name := "server" + if err := waitForContainer(name, cmd...); err != nil { + c.Fatalf("Failed to launch server container: %v", err) + } + return name +} + +func getExternalAddress(c *check.C) net.IP { iface, err := net.InterfaceByName("eth0") if err != nil { c.Skip(fmt.Sprintf("Test not running with `make test`. Interface eth0 not found: %v", err)) @@ -27,35 +39,65 @@ func (s *DockerSuite) TestNetworkNat(c *check.C) { c.Fatalf("Error retrieving the up for eth0: %s", err) } - runCmd := exec.Command(dockerBinary, "run", "-dt", "-p", "8080:8080", "busybox", "nc", "-lp", "8080") + return ifaceIP +} + +func getContainerLogs(c *check.C, containerID string) string { + runCmd := exec.Command(dockerBinary, "logs", containerID) out, _, err := runCommandWithOutput(runCmd) if err != nil { c.Fatal(out, err) } + return strings.Trim(out, "\r\n") +} - cleanedContainerID := strings.TrimSpace(out) - - runCmd = exec.Command(dockerBinary, "run", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s 8080", ifaceIP)) - out, _, err = runCommandWithOutput(runCmd) +func getContainerStatus(c *check.C, containerID string) string { + runCmd := exec.Command(dockerBinary, "inspect", "-f", "{{.State.Running}}", containerID) + out, _, err := runCommandWithOutput(runCmd) if err != nil { c.Fatal(out, err) } + return strings.Trim(out, "\r\n") +} - runCmd = exec.Command(dockerBinary, "logs", cleanedContainerID) - out, _, err = runCommandWithOutput(runCmd) - if err != nil { - c.Fatalf("failed to retrieve logs for container: %s, %v", out, err) +func (s *DockerSuite) TestNetworkNat(c *check.C) { + testRequires(c, SameHostDaemon, NativeExecDriver) + defer deleteAllContainers() + + srv := startServerContainer(c, "tcp", 8080) + + // Spawn a new container which connects to the server through the + // interface address. + endpoint := getExternalAddress(c) + runCmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", fmt.Sprintf("echo hello world | nc -w 30 %s 8080", endpoint)) + if out, _, err := runCommandWithOutput(runCmd); err != nil { + c.Fatalf("Failed to connect to server: %v (output: %q)", err, string(out)) + } + + result := getContainerLogs(c, srv) + if expected := "hello world"; result != expected { + c.Fatalf("Unexpected output. Expected: %q, received: %q", expected, result) } +} - out = strings.Trim(out, "\r\n") +func (s *DockerSuite) TestNetworkLocalhostTCPNat(c *check.C) { + testRequires(c, SameHostDaemon, NativeExecDriver) + defer deleteAllContainers() - if expected := "hello world"; out != expected { - c.Fatalf("Unexpected output. Expected: %q, received: %q for iface %s", expected, out, ifaceIP) + srv := startServerContainer(c, "tcp", 8081) + + // Attempt to connect from the host to the listening container. + conn, err := net.Dial("tcp", "localhost:8081") + if err != nil { + c.Fatalf("Failed to connect to container (%v)", err) } + if _, err := conn.Write([]byte("hello world\n")); err != nil { + c.Fatal(err) + } + conn.Close() - killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID) - if out, _, err = runCommandWithOutput(killCmd); err != nil { - c.Fatalf("failed to kill container: %s, %v", out, err) + result := getContainerLogs(c, srv) + if expected := "hello world"; result != expected { + c.Fatalf("Unexpected output. Expected: %q, received: %q", expected, result) } - } diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 0cf5c31ee..beec73dc4 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -2197,49 +2197,19 @@ func (s *DockerSuite) TestRunPortInUse(c *check.C) { testRequires(c, SameHostDaemon) port := "1234" - l, err := net.Listen("tcp", ":"+port) - if err != nil { - c.Fatal(err) - } - defer l.Close() cmd := exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top") out, _, err := runCommandWithOutput(cmd) + if err != nil { + c.Fatalf("Fail to run listening container") + } + + cmd = exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top") + out, _, err = runCommandWithOutput(cmd) if err == nil { c.Fatalf("Binding on used port must fail") } - if !strings.Contains(out, "address already in use") { - c.Fatalf("Out must be about \"address already in use\", got %s", out) - } -} - -// https://github.com/docker/docker/issues/8428 -func (s *DockerSuite) TestRunPortProxy(c *check.C) { - testRequires(c, SameHostDaemon) - - port := "12345" - cmd := exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top") - - out, _, err := runCommandWithOutput(cmd) - if err != nil { - c.Fatalf("Failed to run and bind port %s, output: %s, error: %s", port, out, err) - } - - // connett for 10 times here. This will trigger 10 EPIPES in the child - // process and kill it when it writes to a closed stdout/stderr - for i := 0; i < 10; i++ { - net.Dial("tcp", fmt.Sprintf("0.0.0.0:%s", port)) - } - - listPs := exec.Command("sh", "-c", "ps ax | grep docker") - out, _, err = runCommandWithOutput(listPs) - if err != nil { - c.Errorf("list docker process failed with output %s, error %s", out, err) - } - if strings.Contains(out, "docker ") { - c.Errorf("Unexpected defunct docker process") - } - if !strings.Contains(out, "docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 12345") { - c.Errorf("Failed to find docker-proxy process, got %s", out) + if !strings.Contains(out, "port is already allocated") { + c.Fatalf("Out must be about \"port is already allocated\", got %s", out) } } diff --git a/pkg/iptables/firewalld_test.go b/pkg/iptables/firewalld_test.go index 3896007d6..ff92657b1 100644 --- a/pkg/iptables/firewalld_test.go +++ b/pkg/iptables/firewalld_test.go @@ -14,7 +14,7 @@ func TestReloaded(t *testing.T) { var err error var fwdChain *Chain - fwdChain, err = NewChain("FWD", "lo", Filter) + fwdChain, err = NewChain("FWD", "lo", Filter, false) if err != nil { t.Fatal(err) } diff --git a/pkg/iptables/iptables.go b/pkg/iptables/iptables.go index 9983ec61f..64a45db99 100644 --- a/pkg/iptables/iptables.go +++ b/pkg/iptables/iptables.go @@ -58,7 +58,7 @@ func initCheck() error { return nil } -func NewChain(name, bridge string, table Table) (*Chain, error) { +func NewChain(name, bridge string, table Table, hairpinMode bool) (*Chain, error) { c := &Chain{ Name: name, Bridge: bridge, @@ -90,8 +90,10 @@ func NewChain(name, bridge string, table Table) (*Chain, error) { } output := []string{ "-m", "addrtype", - "--dst-type", "LOCAL", - "!", "--dst", "127.0.0.0/8"} + "--dst-type", "LOCAL"} + if !hairpinMode { + output = append(output, "!", "--dst", "127.0.0.0/8") + } if !Exists(Nat, "OUTPUT", output...) { if err := c.Output(Append, output...); err != nil { return nil, fmt.Errorf("Failed to inject docker in OUTPUT chain: %s", err) @@ -137,7 +139,6 @@ func (c *Chain) Forward(action Action, ip net.IP, port int, proto, destAddr stri "-p", proto, "-d", daddr, "--dport", strconv.Itoa(port), - "!", "-i", c.Bridge, "-j", "DNAT", "--to-destination", net.JoinHostPort(destAddr, strconv.Itoa(destPort))); err != nil { return err diff --git a/pkg/iptables/iptables_test.go b/pkg/iptables/iptables_test.go index ced4262ce..3539bd5cf 100644 --- a/pkg/iptables/iptables_test.go +++ b/pkg/iptables/iptables_test.go @@ -16,12 +16,12 @@ var filterChain *Chain func TestNewChain(t *testing.T) { var err error - natChain, err = NewChain(chainName, "lo", Nat) + natChain, err = NewChain(chainName, "lo", Nat, false) if err != nil { t.Fatal(err) } - filterChain, err = NewChain(chainName, "lo", Filter) + filterChain, err = NewChain(chainName, "lo", Filter, false) if err != nil { t.Fatal(err) } @@ -40,7 +40,6 @@ func TestForward(t *testing.T) { } dnatRule := []string{ - "!", "-i", filterChain.Bridge, "-d", ip.String(), "-p", proto, "--dport", strconv.Itoa(port), From 44de5fecce9dd194fade1b696e9297ac5c985754 Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Tue, 7 Apr 2015 16:53:39 -0700 Subject: [PATCH 747/999] Add DOCKER_USERLANDPROXY test variable Add an convenient way to switch --userland-proxy on and off in integration tests. Signed-off-by: Arnaud Porterie --- hack/make.sh | 1 + hack/make/.integration-daemon-start | 2 ++ integration-cli/docker_utils.go | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+) diff --git a/hack/make.sh b/hack/make.sh index 4226af5bb..953278842 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -198,6 +198,7 @@ test_env() { DEST="$DEST" \ DOCKER_EXECDRIVER="$DOCKER_EXECDRIVER" \ DOCKER_GRAPHDRIVER="$DOCKER_GRAPHDRIVER" \ + DOCKER_USERLANDPROXY="$DOCKER_USERLANDPROXY" \ DOCKER_HOST="$DOCKER_HOST" \ GOPATH="$GOPATH" \ HOME="$DEST/fake-HOME" \ diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start index 57fd52502..937979df3 100644 --- a/hack/make/.integration-daemon-start +++ b/hack/make/.integration-daemon-start @@ -14,6 +14,7 @@ exec 41>&1 42>&2 export DOCKER_GRAPHDRIVER=${DOCKER_GRAPHDRIVER:-vfs} export DOCKER_EXECDRIVER=${DOCKER_EXECDRIVER:-native} +export DOCKER_USERLANDPROXY=${DOCKER_USERLANDPROXY:-true} if [ -z "$DOCKER_TEST_HOST" ]; then export DOCKER_HOST="unix://$(cd "$DEST" && pwd)/docker.sock" # "pwd" tricks to make sure $DEST is an absolute path, not a relative one @@ -23,6 +24,7 @@ if [ -z "$DOCKER_TEST_HOST" ]; then --storage-driver "$DOCKER_GRAPHDRIVER" \ --exec-driver "$DOCKER_EXECDRIVER" \ --pidfile "$DEST/docker.pid" \ + --userland-proxy="$DOCKER_USERLANDPROXY" \ &> "$DEST/docker.log" ) & trap "source '${MAKEDIR}/.integration-daemon-stop'" EXIT # make sure that if the script exits unexpectedly, we stop this daemon we just started diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go index a1a845baf..3500d2f75 100644 --- a/integration-cli/docker_utils.go +++ b/integration-cli/docker_utils.go @@ -37,6 +37,16 @@ type Daemon struct { storageDriver string execDriver string wait chan error + userlandProxy bool +} + +func enableUserlandProxy() bool { + if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" { + if val, err := strconv.ParseBool(env); err != nil { + return val + } + } + return true } // NewDaemon returns a Daemon instance to be used for testing. @@ -58,11 +68,19 @@ func NewDaemon(c *check.C) *Daemon { c.Fatalf("Could not create %s/graph directory", daemonFolder) } + userlandProxy := true + if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" { + if val, err := strconv.ParseBool(env); err != nil { + userlandProxy = val + } + } + return &Daemon{ c: c, folder: daemonFolder, storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"), execDriver: os.Getenv("DOCKER_EXECDRIVER"), + userlandProxy: userlandProxy, } } @@ -79,6 +97,7 @@ func (d *Daemon) Start(arg ...string) error { "--daemon", "--graph", fmt.Sprintf("%s/graph", d.folder), "--pidfile", fmt.Sprintf("%s/docker.pid", d.folder), + fmt.Sprintf("--userland-proxy=%t", d.userlandProxy), } // If we don't explicitly set the log-level or debug flag(-D) then From 3b2c8f69fd7f1ca6a463b9bd2c2e006d9e13fe98 Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 4 May 2015 16:18:23 -0700 Subject: [PATCH 748/999] Windows: Fix filepath vs path in push.go Signed-off-by: John Howard --- graph/push.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/push.go b/graph/push.go index 1b33288d8..e24ee9f54 100644 --- a/graph/push.go +++ b/graph/push.go @@ -7,7 +7,7 @@ import ( "io" "io/ioutil" "os" - "path" + "path/filepath" "sync" "github.com/Sirupsen/logrus" @@ -247,7 +247,7 @@ func (s *TagStore) pushRepository(r *registry.Session, out io.Writer, func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep string, token []string, sf *streamformatter.StreamFormatter) (checksum string, err error) { out = utils.NewWriteFlusher(out) - jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json")) + jsonRaw, err := ioutil.ReadFile(filepath.Join(s.graph.Root, imgID, "json")) if err != nil { return "", fmt.Errorf("Cannot retrieve the path for {%s}: %s", imgID, err) } From 1c3b697d60bc1a5f5197b0c3a432002cf03c8277 Mon Sep 17 00:00:00 2001 From: John Howard Date: Mon, 4 May 2015 16:23:17 -0700 Subject: [PATCH 749/999] Windows: Fix filepath vs path in image.go Signed-off-by: John Howard --- image/image.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/image/image.go b/image/image.go index a34d2b940..e86ed2ff3 100644 --- a/image/image.go +++ b/image/image.go @@ -5,7 +5,7 @@ import ( "fmt" "io/ioutil" "os" - "path" + "path/filepath" "regexp" "strconv" "time" @@ -55,7 +55,7 @@ func LoadImage(root string) (*Image, error) { return nil, err } - if buf, err := ioutil.ReadFile(path.Join(root, "layersize")); err != nil { + if buf, err := ioutil.ReadFile(filepath.Join(root, "layersize")); err != nil { if !os.IsNotExist(err) { return nil, err } @@ -107,21 +107,21 @@ func (img *Image) SetGraph(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 { + if err := ioutil.WriteFile(filepath.Join(root, "layersize"), []byte(strconv.Itoa(int(img.Size))), 0600); err != nil { return fmt.Errorf("Error storing image size in %s/layersize: %s", root, err) } return nil } func (img *Image) SaveCheckSum(root, checksum string) error { - if err := ioutil.WriteFile(path.Join(root, "checksum"), []byte(checksum), 0600); err != nil { + if err := ioutil.WriteFile(filepath.Join(root, "checksum"), []byte(checksum), 0600); err != nil { return fmt.Errorf("Error storing checksum in %s/checksum: %s", root, err) } return nil } func (img *Image) GetCheckSum(root string) (string, error) { - cs, err := ioutil.ReadFile(path.Join(root, "checksum")) + cs, err := ioutil.ReadFile(filepath.Join(root, "checksum")) if err != nil { if os.IsNotExist(err) { return "", nil @@ -132,7 +132,7 @@ func (img *Image) GetCheckSum(root string) (string, error) { } func jsonPath(root string) string { - return path.Join(root, "json") + return filepath.Join(root, "json") } func (img *Image) RawJson() ([]byte, error) { From bba5fd8caa35d5edf8b68e593eafb277394a6989 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Tue, 5 May 2015 00:38:41 +0000 Subject: [PATCH 750/999] spelling fix from Marc MERLIN Signed-off-by: Sven Dowideit --- docs/sources/reference/builder.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/builder.md b/docs/sources/reference/builder.md index 7dbe54923..fb4d5ce1d 100644 --- a/docs/sources/reference/builder.md +++ b/docs/sources/reference/builder.md @@ -892,11 +892,11 @@ consider the following Dockerfile snippet: FROM ubuntu RUN mkdir /myvol - RUN echo "hello world" > /myvol/greating + RUN echo "hello world" > /myvol/greeting VOLUME /myvol This Dockerfile results in an image that causes `docker run`, to -create a new mount point at `/myvol` and copy the `greating` file +create a new mount point at `/myvol` and copy the `greeting` file into the newly created volume. > **Note**: From 6e8aa4e588d11735c70059a478fc0c7857cb382c Mon Sep 17 00:00:00 2001 From: Yuan Sun Date: Tue, 5 May 2015 08:51:13 +0800 Subject: [PATCH 751/999] Verify the no-trunc option for the search operation. Signed-off-by: Yuan Sun --- integration-cli/docker_cli_search_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/integration-cli/docker_cli_search_test.go b/integration-cli/docker_cli_search_test.go index c5ecdd03b..da298a1e0 100644 --- a/integration-cli/docker_cli_search_test.go +++ b/integration-cli/docker_cli_search_test.go @@ -63,6 +63,16 @@ func (s *DockerSuite) TestSearchCmdOptions(c *check.C) { c.Fatalf("failed to search on the central registry: %s, %v", outSearchCmd, err) } + searchCmdNotrunc := exec.Command(dockerBinary, "search", "--no-trunc=true", "busybox") + outSearchCmdNotrunc, _, err := runCommandWithOutput(searchCmdNotrunc) + if err != nil { + c.Fatalf("failed to search on the central registry: %s, %v", outSearchCmdNotrunc, err) + } + + if len(outSearchCmd) > len(outSearchCmdNotrunc) { + c.Fatalf("The no-trunc option can't take effect.") + } + searchCmdautomated := exec.Command(dockerBinary, "search", "--automated=true", "busybox") outSearchCmdautomated, exitCode, err := runCommandWithOutput(searchCmdautomated) //The busybox is a busybox base image, not an AUTOMATED image. if err != nil || exitCode != 0 { From a2c76912e04b087233577b46f96f446c5d13e2de Mon Sep 17 00:00:00 2001 From: Adria Casas Date: Thu, 23 Apr 2015 09:40:23 +0200 Subject: [PATCH 752/999] Rename int64Value to int64ValueOrZero. Signed-off-by: Adria Casas --- api/server/form.go | 2 +- api/server/form_test.go | 4 ++-- api/server/server.go | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/server/form.go b/api/server/form.go index af1cd2075..75584df06 100644 --- a/api/server/form.go +++ b/api/server/form.go @@ -11,7 +11,7 @@ func boolValue(r *http.Request, k string) bool { return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") } -func int64Value(r *http.Request, k string) int64 { +func int64ValueOrZero(r *http.Request, k string) int64 { val, err := strconv.ParseInt(r.FormValue(k), 10, 64) if err != nil { return 0 diff --git a/api/server/form_test.go b/api/server/form_test.go index 5cf6c82c1..caa9f1757 100644 --- a/api/server/form_test.go +++ b/api/server/form_test.go @@ -33,7 +33,7 @@ func TestBoolValue(t *testing.T) { } } -func TestInt64Value(t *testing.T) { +func TestInt64ValueOrZero(t *testing.T) { cases := map[string]int64{ "": 0, "asdf": 0, @@ -47,7 +47,7 @@ func TestInt64Value(t *testing.T) { r, _ := http.NewRequest("POST", "", nil) r.Form = v - a := int64Value(r, "test") + a := int64ValueOrZero(r, "test") if a != e { t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a) } diff --git a/api/server/server.go b/api/server/server.go index d1540acb8..fbc844ad2 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -1295,10 +1295,10 @@ func (s *Server) postBuild(version version.Version, w http.ResponseWriter, r *ht buildConfig.ForceRemove = boolValue(r, "forcerm") buildConfig.AuthConfig = authConfig buildConfig.ConfigFile = configFile - buildConfig.MemorySwap = int64Value(r, "memswap") - buildConfig.Memory = int64Value(r, "memory") - buildConfig.CpuShares = int64Value(r, "cpushares") - buildConfig.CpuQuota = int64Value(r, "cpuquota") + buildConfig.MemorySwap = int64ValueOrZero(r, "memswap") + buildConfig.Memory = int64ValueOrZero(r, "memory") + buildConfig.CpuShares = int64ValueOrZero(r, "cpushares") + buildConfig.CpuQuota = int64ValueOrZero(r, "cpuquota") buildConfig.CpuSetCpus = r.FormValue("cpusetcpus") buildConfig.CpuSetMems = r.FormValue("cpusetmems") From c7ca73c8309a49893fa54d0c604733f4c7b3cb4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sankar=20=E0=AE=9A=E0=AE=99=E0=AF=8D=E0=AE=95=E0=AE=B0?= =?UTF-8?q?=E0=AF=8D?= Date: Tue, 5 May 2015 13:17:33 +0530 Subject: [PATCH 753/999] Added information about mailing lists MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sankar சங்கர் --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 5603a55a7..4bb249eea 100644 --- a/README.md +++ b/README.md @@ -246,3 +246,9 @@ Docker on a Mac If you know of another project underway that should be listed here, please help us keep this list up-to-date by submitting a PR. + +Mailing Lists +============= + +* [Google group for Docker Users](https://groups.google.com/forum/#!forum/docker-user) +* [Google group for Docker development discussions](https://groups.google.com/forum/#!forum/docker-dev) From 5a6db4fd44c0e05c8b185174a9aa380c9cc4f719 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Tue, 5 May 2015 15:59:17 +0800 Subject: [PATCH 754/999] a few cleanups for client output Signed-off-by: Qiang Huang --- api/client/cli.go | 3 +-- api/client/help.go | 4 +--- api/client/inspect.go | 1 - api/client/kill.go | 9 ++++++--- api/client/pause.go | 9 ++++++--- api/client/restart.go | 9 ++++++--- api/client/rm.go | 9 ++++++--- api/client/rmi.go | 11 +++++++---- api/client/start.go | 6 +++++- api/client/stop.go | 9 ++++++--- api/client/unpause.go | 9 ++++++--- api/client/wait.go | 9 ++++++--- integration-cli/docker_cli_rm_test.go | 4 ++-- 13 files changed, 58 insertions(+), 34 deletions(-) diff --git a/api/client/cli.go b/api/client/cli.go index 600d4cc5a..7d9610078 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -90,8 +90,7 @@ func (cli *DockerCli) Cmd(args ...string) error { if len(args) > 0 { method, exists := cli.getMethod(args[0]) if !exists { - fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0]) - os.Exit(1) + return fmt.Errorf("docker: '%s' is not a docker command. See 'docker --help'.", args[0]) } return method(args[1:]...) } diff --git a/api/client/help.go b/api/client/help.go index e95387967..5dee9ba8a 100644 --- a/api/client/help.go +++ b/api/client/help.go @@ -2,7 +2,6 @@ package client import ( "fmt" - "os" flag "github.com/docker/docker/pkg/mflag" ) @@ -23,8 +22,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { if len(args) > 0 { method, exists := cli.getMethod(args[0]) if !exists { - fmt.Fprintf(cli.err, "docker: '%s' is not a docker command. See 'docker --help'.\n", args[0]) - os.Exit(1) + return fmt.Errorf("docker: '%s' is not a docker command. See 'docker --help'.", args[0]) } else { method("--help") return nil diff --git a/api/client/inspect.go b/api/client/inspect.go index d60d47ad3..8d9c08610 100644 --- a/api/client/inspect.go +++ b/api/client/inspect.go @@ -26,7 +26,6 @@ func (cli *DockerCli) CmdInspect(args ...string) error { if *tmplStr != "" { var err error if tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr); err != nil { - fmt.Fprintf(cli.err, "Template parsing error: %v\n", err) return StatusError{StatusCode: 64, Status: "Template parsing error: " + err.Error()} } diff --git a/api/client/kill.go b/api/client/kill.go index 7ad1e5613..becff3b7e 100644 --- a/api/client/kill.go +++ b/api/client/kill.go @@ -16,14 +16,17 @@ func (cli *DockerCli) CmdKill(args ...string) error { cmd.ParseFlags(args, true) - var encounteredError error + var errNames []string for _, name := range cmd.Args() { if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", name, *signal), nil, nil)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to kill one or more containers") + errNames = append(errNames, name) } else { fmt.Fprintf(cli.out, "%s\n", name) } } - return encounteredError + if len(errNames) > 0 { + return fmt.Errorf("Error: failed to kill containers: %v", errNames) + } + return nil } diff --git a/api/client/pause.go b/api/client/pause.go index 6c807410b..2f8f3c83f 100644 --- a/api/client/pause.go +++ b/api/client/pause.go @@ -14,14 +14,17 @@ func (cli *DockerCli) CmdPause(args ...string) error { cmd.Require(flag.Min, 1) cmd.ParseFlags(args, false) - var encounteredError error + var errNames []string for _, name := range cmd.Args() { if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/pause", name), nil, nil)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to pause container named %s", name) + errNames = append(errNames, name) } else { fmt.Fprintf(cli.out, "%s\n", name) } } - return encounteredError + if len(errNames) > 0 { + return fmt.Errorf("Error: failed to pause containers: %v", errNames) + } + return nil } diff --git a/api/client/restart.go b/api/client/restart.go index 41b10676b..c769fb6d2 100644 --- a/api/client/restart.go +++ b/api/client/restart.go @@ -21,15 +21,18 @@ func (cli *DockerCli) CmdRestart(args ...string) error { v := url.Values{} v.Set("t", strconv.Itoa(*nSeconds)) - var encounteredError error + var errNames []string for _, name := range cmd.Args() { _, _, err := readBody(cli.call("POST", "/containers/"+name+"/restart?"+v.Encode(), nil, nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to restart one or more containers") + errNames = append(errNames, name) } else { fmt.Fprintf(cli.out, "%s\n", name) } } - return encounteredError + if len(errNames) > 0 { + return fmt.Errorf("Error: failed to restart containers: %v", errNames) + } + return nil } diff --git a/api/client/rm.go b/api/client/rm.go index d99f32e2f..e6f3aeaeb 100644 --- a/api/client/rm.go +++ b/api/client/rm.go @@ -32,7 +32,7 @@ func (cli *DockerCli) CmdRm(args ...string) error { val.Set("force", "1") } - var encounteredError error + var errNames []string for _, name := range cmd.Args() { if name == "" { return fmt.Errorf("Container name cannot be empty") @@ -42,10 +42,13 @@ func (cli *DockerCli) CmdRm(args ...string) error { _, _, err := readBody(cli.call("DELETE", "/containers/"+name+"?"+val.Encode(), nil, nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to remove one or more containers") + errNames = append(errNames, name) } else { fmt.Fprintf(cli.out, "%s\n", name) } } - return encounteredError + if len(errNames) > 0 { + return fmt.Errorf("Error: failed to remove containers: %v", errNames) + } + return nil } diff --git a/api/client/rmi.go b/api/client/rmi.go index a8590dc82..36f2036d1 100644 --- a/api/client/rmi.go +++ b/api/client/rmi.go @@ -29,17 +29,17 @@ func (cli *DockerCli) CmdRmi(args ...string) error { v.Set("noprune", "1") } - var encounteredError error + var errNames []string for _, name := range cmd.Args() { rdr, _, err := cli.call("DELETE", "/images/"+name+"?"+v.Encode(), nil, nil) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to remove one or more images") + errNames = append(errNames, name) } else { dels := []types.ImageDelete{} if err := json.NewDecoder(rdr).Decode(&dels); err != nil { fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to remove one or more images") + errNames = append(errNames, name) continue } @@ -52,5 +52,8 @@ func (cli *DockerCli) CmdRmi(args ...string) error { } } } - return encounteredError + if len(errNames) > 0 { + return fmt.Errorf("Error: failed to remove images: %v", errNames) + } + return nil } diff --git a/api/client/start.go b/api/client/start.go index b290524ca..55f307f52 100644 --- a/api/client/start.go +++ b/api/client/start.go @@ -120,6 +120,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { } var encounteredError error + var errNames []string for _, name := range cmd.Args() { _, _, err := readBody(cli.call("POST", "/containers/"+name+"/start", nil, nil)) if err != nil { @@ -127,7 +128,7 @@ func (cli *DockerCli) CmdStart(args ...string) error { // attach and openStdin is false means it could be starting multiple containers // when a container start failed, show the error message and start next fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to start one or more containers") + errNames = append(errNames, name) } else { encounteredError = err } @@ -138,6 +139,9 @@ func (cli *DockerCli) CmdStart(args ...string) error { } } + if len(errNames) > 0 { + encounteredError = fmt.Errorf("Error: failed to start containers: %v", errNames) + } if encounteredError != nil { return encounteredError } diff --git a/api/client/stop.go b/api/client/stop.go index 08a1f5ba1..9551911ff 100644 --- a/api/client/stop.go +++ b/api/client/stop.go @@ -23,15 +23,18 @@ func (cli *DockerCli) CmdStop(args ...string) error { v := url.Values{} v.Set("t", strconv.Itoa(*nSeconds)) - var encounteredError error + var errNames []string for _, name := range cmd.Args() { _, _, err := readBody(cli.call("POST", "/containers/"+name+"/stop?"+v.Encode(), nil, nil)) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to stop one or more containers") + errNames = append(errNames, name) } else { fmt.Fprintf(cli.out, "%s\n", name) } } - return encounteredError + if len(errNames) > 0 { + return fmt.Errorf("Error: failed to stop containers: %v", errNames) + } + return nil } diff --git a/api/client/unpause.go b/api/client/unpause.go index bcecb4633..dceeb23af 100644 --- a/api/client/unpause.go +++ b/api/client/unpause.go @@ -14,14 +14,17 @@ func (cli *DockerCli) CmdUnpause(args ...string) error { cmd.Require(flag.Min, 1) cmd.ParseFlags(args, false) - var encounteredError error + var errNames []string for _, name := range cmd.Args() { if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/unpause", name), nil, nil)); err != nil { fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to unpause container named %s", name) + errNames = append(errNames, name) } else { fmt.Fprintf(cli.out, "%s\n", name) } } - return encounteredError + if len(errNames) > 0 { + return fmt.Errorf("Error: failed to unpause containers: %v", errNames) + } + return nil } diff --git a/api/client/wait.go b/api/client/wait.go index 8f34b2452..bfec19e24 100644 --- a/api/client/wait.go +++ b/api/client/wait.go @@ -17,15 +17,18 @@ func (cli *DockerCli) CmdWait(args ...string) error { cmd.ParseFlags(args, true) - var encounteredError error + var errNames []string for _, name := range cmd.Args() { status, err := waitForExit(cli, name) if err != nil { fmt.Fprintf(cli.err, "%s\n", err) - encounteredError = fmt.Errorf("Error: failed to wait one or more containers") + errNames = append(errNames, name) } else { fmt.Fprintf(cli.out, "%d\n", status) } } - return encounteredError + if len(errNames) > 0 { + return fmt.Errorf("Error: failed to wait containers: %v", errNames) + } + return nil } diff --git a/integration-cli/docker_cli_rm_test.go b/integration-cli/docker_cli_rm_test.go index ba4e0e6bf..f5884dc0f 100644 --- a/integration-cli/docker_cli_rm_test.go +++ b/integration-cli/docker_cli_rm_test.go @@ -105,8 +105,8 @@ func (s *DockerSuite) TestRmContainerOrphaning(c *check.C) { func (s *DockerSuite) TestRmInvalidContainer(c *check.C) { if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "rm", "unknown")); err == nil { c.Fatal("Expected error on rm unknown container, got none") - } else if !strings.Contains(out, "failed to remove one or more containers") { - c.Fatalf("Expected output to contain 'failed to remove one or more containers', got %q", out) + } else if !strings.Contains(out, "failed to remove containers") { + c.Fatalf("Expected output to contain 'failed to remove containers', got %q", out) } } From 101f982059589cac0c8891832ac3f8069291d63a Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Tue, 5 May 2015 19:21:01 +0800 Subject: [PATCH 755/999] Refactor the code of checking conflict option with netmode. Signed-off-by: Lei Jitang --- runconfig/parse.go | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/runconfig/parse.go b/runconfig/parse.go index 63eeecc5f..82d0870cd 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -15,9 +15,8 @@ import ( var ( ErrConflictContainerNetworkAndLinks = fmt.Errorf("Conflicting options: --net=container can't be used with links. This would result in undefined behavior.") - ErrConflictContainerNetworkAndDns = fmt.Errorf("Conflicting options: --net=container can't be used with --dns. This configuration is invalid.") + ErrConflictNetworkAndDns = fmt.Errorf("Conflicting options: --dns and the network mode (--net).") ErrConflictNetworkHostname = fmt.Errorf("Conflicting options: -h and the network mode (--net)") - ErrConflictHostNetworkAndDns = fmt.Errorf("Conflicting options: --net=host can't be used with --dns. This configuration is invalid.") ErrConflictHostNetworkAndLinks = fmt.Errorf("Conflicting options: --net=host can't be used with links. This would result in undefined behavior.") ) @@ -112,24 +111,25 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe attachStderr = flAttach.Get("stderr") ) - if *flNetMode != "bridge" && *flNetMode != "none" && *flHostname != "" { + netMode, err := parseNetMode(*flNetMode) + if err != nil { + return nil, nil, cmd, fmt.Errorf("--net: invalid net mode: %v", err) + } + + if (netMode.IsHost() || netMode.IsContainer()) && *flHostname != "" { return nil, nil, cmd, ErrConflictNetworkHostname } - if *flNetMode == "host" && flLinks.Len() > 0 { + if netMode.IsHost() && flLinks.Len() > 0 { return nil, nil, cmd, ErrConflictHostNetworkAndLinks } - if strings.HasPrefix(*flNetMode, "container") && flLinks.Len() > 0 { + if netMode.IsContainer() && flLinks.Len() > 0 { return nil, nil, cmd, ErrConflictContainerNetworkAndLinks } - if *flNetMode == "host" && flDns.Len() > 0 { - return nil, nil, cmd, ErrConflictHostNetworkAndDns - } - - if strings.HasPrefix(*flNetMode, "container") && flDns.Len() > 0 { - return nil, nil, cmd, ErrConflictContainerNetworkAndDns + if (netMode.IsHost() || netMode.IsContainer()) && flDns.Len() > 0 { + return nil, nil, cmd, ErrConflictNetworkAndDns } // If neither -d or -a are set, attach to everything by default @@ -266,11 +266,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe return nil, nil, cmd, fmt.Errorf("--pid: invalid PID mode") } - netMode, err := parseNetMode(*flNetMode) - if err != nil { - return nil, nil, cmd, fmt.Errorf("--net: invalid net mode: %v", err) - } - restartPolicy, err := ParseRestartPolicy(*flRestartPolicy) if err != nil { return nil, nil, cmd, err From 0e08e9aca14a4ca7142fa4649983302d93b55dab Mon Sep 17 00:00:00 2001 From: Lei Jitang Date: Tue, 5 May 2015 19:27:07 +0800 Subject: [PATCH 756/999] Add support --net=container with --mac-address,--add-host error out Signed-off-by: Lei Jitang --- daemon/container.go | 2 +- docs/sources/reference/run.md | 7 +++++-- integration-cli/docker_cli_run_test.go | 27 ++++++++++++++++++++++++++ runconfig/hostconfig.go | 4 ++++ runconfig/parse.go | 23 ++++++++++++++++------ 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/daemon/container.go b/daemon/container.go index 5c7d3a4e5..756ad9497 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -1107,7 +1107,7 @@ func (container *Container) setupContainerDns() error { return err } - if config.NetworkMode != "host" { + if config.NetworkMode.IsBridge() || config.NetworkMode.IsNone() { // check configurations for any container/daemon dns settings if len(config.Dns) > 0 || len(daemon.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(daemon.config.DnsSearch) > 0 { var ( diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 60a180584..43983f3d6 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -282,7 +282,8 @@ With the networking mode set to `host` a container will share the host's network stack and all interfaces from the host will be available to the container. The container's hostname will match the hostname on the host system. Publishing ports and linking to other containers will not work -when sharing the host's network stack. +when sharing the host's network stack. Note that `--add-host` `--hostname` +`--dns` `--dns-search` and `--mac-address` is invalid in `host` netmode. Compared to the default `bridge` mode, the `host` mode gives *significantly* better networking performance since it uses the host's native networking stack @@ -298,7 +299,9 @@ or a High Performance Web Server. With the networking mode set to `container` a container will share the network stack of another container. The other container's name must be -provided in the format of `--net container:`. +provided in the format of `--net container:`. Note that `--add-host` +`--hostname` `--dns` `--dns-search` and `--mac-address` is invalid +in `container` netmode. Example running a Redis container with Redis binding to `localhost` then running the `redis-cli` command and connecting to the Redis server over the diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index 0cf5c31ee..e828d0c50 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -371,6 +371,33 @@ func (s *DockerSuite) TestRunLinkToContainerNetMode(c *check.C) { } } +func (s *DockerSuite) TestRunContainerNetModeWithDnsMacHosts(c *check.C) { + cmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "busybox", "top") + out, _, err := runCommandWithOutput(cmd) + if err != nil { + c.Fatalf("failed to run container: %v, output: %q", err, out) + } + + cmd = exec.Command(dockerBinary, "run", "--dns", "1.2.3.4", "--net=container:parent", "busybox") + out, _, err = runCommandWithOutput(cmd) + if err == nil || !strings.Contains(out, "Conflicting options: --dns and the network mode") { + c.Fatalf("run --net=container with --dns should error out") + } + + cmd = exec.Command(dockerBinary, "run", "--mac-address", "92:d0:c6:0a:29:33", "--net=container:parent", "busybox") + out, _, err = runCommandWithOutput(cmd) + if err == nil || !strings.Contains(out, "--mac-address and the network mode") { + c.Fatalf("run --net=container with --mac-address should error out") + } + + cmd = exec.Command(dockerBinary, "run", "--add-host", "test:192.168.2.109", "--net=container:parent", "busybox") + out, _, err = runCommandWithOutput(cmd) + if err == nil || !strings.Contains(out, "--add-host and the network mode") { + c.Fatalf("run --net=container with --add-host should error out") + } + +} + func (s *DockerSuite) TestRunModeNetContainerHostname(c *check.C) { testRequires(c, ExecSupport) cmd := exec.Command(dockerBinary, "run", "-i", "-d", "--name", "parent", "busybox", "top") diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index d634b1ffb..3a91744af 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -21,6 +21,10 @@ func (n NetworkMode) IsPrivate() bool { return !(n.IsHost() || n.IsContainer() || n.IsNone()) } +func (n NetworkMode) IsBridge() bool { + return n == "bridge" +} + func (n NetworkMode) IsHost() bool { return n == "host" } diff --git a/runconfig/parse.go b/runconfig/parse.go index 82d0870cd..1fcf521ee 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -18,6 +18,8 @@ var ( ErrConflictNetworkAndDns = fmt.Errorf("Conflicting options: --dns and the network mode (--net).") ErrConflictNetworkHostname = fmt.Errorf("Conflicting options: -h and the network mode (--net)") ErrConflictHostNetworkAndLinks = fmt.Errorf("Conflicting options: --net=host can't be used with links. This would result in undefined behavior.") + ErrConflictContainerNetworkAndMac = fmt.Errorf("Conflicting options: --mac-address and the network mode (--net).") + ErrConflictNetworkHosts = fmt.Errorf("Conflicting options: --add-host and the network mode (--net).") ) func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSet, error) { @@ -99,12 +101,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe return nil, nil, cmd, err } - // Validate input params starting with the input mac address - if *flMacAddress != "" { - if _, err := opts.ValidateMACAddress(*flMacAddress); err != nil { - return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress) - } - } var ( attachStdin = flAttach.Get("stdin") attachStdout = flAttach.Get("stdout") @@ -132,6 +128,21 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe return nil, nil, cmd, ErrConflictNetworkAndDns } + if (netMode.IsContainer() || netMode.IsHost()) && flExtraHosts.Len() > 0 { + return nil, nil, cmd, ErrConflictNetworkHosts + } + + if (netMode.IsContainer() || netMode.IsHost()) && *flMacAddress != "" { + return nil, nil, cmd, ErrConflictContainerNetworkAndMac + } + + // Validate the input mac address + if *flMacAddress != "" { + if _, err := opts.ValidateMACAddress(*flMacAddress); err != nil { + return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress) + } + } + // If neither -d or -a are set, attach to everything by default if flAttach.Len() == 0 { attachStdout = true From 3efc083d4a785b2c7b1c1f862133167d5618d216 Mon Sep 17 00:00:00 2001 From: Peter Salvatore Date: Tue, 5 May 2015 11:03:53 -0400 Subject: [PATCH 757/999] Remove blanket "supported" language. Signed-off-by: Peter Salvatore --- docs/sources/docker-hub/official_repos.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/docker-hub/official_repos.md b/docs/sources/docker-hub/official_repos.md index eb73b4bc2..98c33c643 100644 --- a/docs/sources/docker-hub/official_repos.md +++ b/docs/sources/docker-hub/official_repos.md @@ -5,8 +5,8 @@ page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub # Official Repositories on Docker Hub The Docker [Official Repositories](http://registry.hub.docker.com/official) are -a curated set of Docker repositories that are promoted on Docker Hub and -supported by Docker, Inc. They are designed to: +a curated set of Docker repositories that are promoted on Docker Hub. They are +designed to: * Provide essential base OS repositories (for example, [`ubuntu`](https://registry.hub.docker.com/_/ubuntu/), From 5e563d170815ce3111eb66b44cfd252c80d8f34c Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 5 May 2015 10:11:59 -0600 Subject: [PATCH 758/999] Replace "docker-core" with "docker-engine" in "build-deb" Signed-off-by: Andrew "Tianon" Page --- hack/make/.build-deb/control | 4 ++-- hack/make/.build-deb/docker-core.install | 8 ++++---- hack/make/.build-deb/rules | 12 ++++++------ 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/hack/make/.build-deb/control b/hack/make/.build-deb/control index 03caae834..ac6541a73 100644 --- a/hack/make/.build-deb/control +++ b/hack/make/.build-deb/control @@ -1,10 +1,10 @@ -Source: docker-core +Source: docker-engine Maintainer: Docker Homepage: https://dockerproject.com Vcs-Browser: https://github.com/docker/docker Vcs-Git: git://github.com/docker/docker.git -Package: docker-core +Package: docker-engine Architecture: linux-any Depends: iptables, ${misc:Depends}, ${perl:Depends}, ${shlibs:Depends} Recommends: aufs-tools, diff --git a/hack/make/.build-deb/docker-core.install b/hack/make/.build-deb/docker-core.install index c3f4eb146..76b05ad04 100644 --- a/hack/make/.build-deb/docker-core.install +++ b/hack/make/.build-deb/docker-core.install @@ -1,10 +1,10 @@ #contrib/syntax/vim/doc/* /usr/share/vim/vimfiles/doc/ #contrib/syntax/vim/ftdetect/* /usr/share/vim/vimfiles/ftdetect/ #contrib/syntax/vim/syntax/* /usr/share/vim/vimfiles/syntax/ -contrib/*-integration usr/share/docker-core/contrib/ -contrib/check-config.sh usr/share/docker-core/contrib/ +contrib/*-integration usr/share/docker-engine/contrib/ +contrib/check-config.sh usr/share/docker-engine/contrib/ contrib/completion/zsh/_docker usr/share/zsh/vendor-completions/ contrib/init/systemd/docker.service lib/systemd/system/ contrib/init/systemd/docker.socket lib/systemd/system/ -contrib/mk* usr/share/docker-core/contrib/ -contrib/nuke-graph-directory.sh usr/share/docker-core/contrib/ +contrib/mk* usr/share/docker-engine/contrib/ +contrib/nuke-graph-directory.sh usr/share/docker-engine/contrib/ diff --git a/hack/make/.build-deb/rules b/hack/make/.build-deb/rules index 3369f4fc5..22dab31d1 100755 --- a/hack/make/.build-deb/rules +++ b/hack/make/.build-deb/rules @@ -4,7 +4,7 @@ VERSION = $(shell cat VERSION) override_dh_gencontrol: # if we're on Ubuntu, we need to Recommends: apparmor - echo 'apparmor:Recommends=$(shell dpkg-vendor --is Ubuntu && echo apparmor)' >> debian/docker-core.substvars + echo 'apparmor:Recommends=$(shell dpkg-vendor --is Ubuntu && echo apparmor)' >> debian/docker-engine.substvars dh_gencontrol override_dh_auto_build: @@ -19,13 +19,13 @@ override_dh_strip: # also, Go has lots of problems with stripping, so just don't override_dh_auto_install: - mkdir -p debian/docker-core/usr/bin - cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/docker)" debian/docker-core/usr/bin/docker - mkdir -p debian/docker-core/usr/libexec/docker - cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/dockerinit)" debian/docker-core/usr/libexec/docker/dockerinit + mkdir -p debian/docker-engine/usr/bin + cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/docker)" debian/docker-engine/usr/bin/docker + mkdir -p debian/docker-engine/usr/libexec/docker + cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/dockerinit)" debian/docker-engine/usr/libexec/docker/dockerinit override_dh_installinit: - # use "docker" as our service name, not "docker-core" + # use "docker" as our service name, not "docker-engine" dh_installinit --name=docker override_dh_installudev: From 878dcb89f38e8eb7bb07ccd4a4e5ce622252ff30 Mon Sep 17 00:00:00 2001 From: Patrick Devine Date: Tue, 31 Mar 2015 13:58:17 -0700 Subject: [PATCH 759/999] Make a docker-in-docker dynamic binary and add RPM target This change adds a new docker-in-docker dynamic binary make target which builds a centos container for creating the dynamically linked binary. To use it, you first must create the static binary and then call the dind-dynbinary target. You can call it like: $ hack/make.sh binary dind-dynbinary rpm This would then package the dynamic binary into the rpm after having created it in the centos build container. Unfortunately with this approach you can't create the rpms and the debs with the same command. They have to be created separately otherwise the wrong version (static vs. dynamic) gets packaged. Various RPM fixes including: - Adding missing RPM dependencies. - Add sysconfig configuration files to the RPM. - Add an epoch to silence the fpm warning. - Remove unnecessary empty package. Signed-off-by: Patrick Devine Signed-off-by: Chad Metcalf --- Dockerfile | 1 + Dockerfile.centos | 36 ++++++ hack/make.sh | 2 +- hack/make/.integration-daemon-start | 2 +- hack/make/dind-dynbinary | 23 ++++ hack/make/rpm | 193 ++++++++++++++++++++++++++++ 6 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 Dockerfile.centos create mode 100644 hack/make/dind-dynbinary create mode 100644 hack/make/rpm diff --git a/Dockerfile b/Dockerfile index 2b49fc1e0..0c85a8633 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,6 +47,7 @@ RUN apt-get update && apt-get install -y \ python-pip \ python-websocket \ reprepro \ + rpm \ ruby1.9.1 \ ruby1.9.1-dev \ s3cmd=1.1.0* \ diff --git a/Dockerfile.centos b/Dockerfile.centos new file mode 100644 index 000000000..0d11f2f2f --- /dev/null +++ b/Dockerfile.centos @@ -0,0 +1,36 @@ +# This file creates a CentOS docker container which can be used to create the Docker +# RPMs, however it shouldn't be called directly. +# + +FROM centos:7.0.1406 +MAINTAINER Patrick Devine (@pdev110) + +RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 + +# Packaged dependencies +RUN yum groupinstall -y "Development Tools" + +RUN yum install -y \ + btrfs-progs-devel \ + device-mapper-devel \ + glibc-static \ + libselinux-devel \ + sqlite-devel + +VOLUME /go + +ENV LXC_VERSION 1.0.7 +ENV GO_VERSION 1.4.2 +ENV PATH /go/bin:/usr/local/go/bin:$PATH +ENV GOPATH /go:/go/src/github.com/docker/docker/vendor +ENV GOFMT_VERSION 1.3.3 + +# Add an unprivileged user to be used for tests which need it +RUN groupadd -r docker +RUN useradd --create-home --gid docker unprivilegeduser + +WORKDIR /go/src/github.com/docker/docker +ENV DOCKER_BUILDTAGS selinux btrfs_noversion + +# Wrap all commands in the "docker-in-docker" script to allow nested containers +#ENTRYPOINT ["hack/dind"] diff --git a/hack/make.sh b/hack/make.sh index 699bedb37..96ca5902b 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -274,7 +274,7 @@ main() { # We want this to fail if the bundles already exist and cannot be removed. # This is to avoid mixing bundles from different versions of the code. mkdir -p bundles - if [ -e "bundles/$VERSION" ]; then + if [ -e "bundles/$VERSION" ] && [ -z ${KEEPBUNDLE} ]; then echo "bundles/$VERSION already exists. Removing." rm -fr "bundles/$VERSION" && mkdir "bundles/$VERSION" || exit 1 echo diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start index 57fd52502..0a889ae7a 100644 --- a/hack/make/.integration-daemon-start +++ b/hack/make/.integration-daemon-start @@ -2,7 +2,7 @@ # see test-integration-cli for example usage of this script -export PATH="$DEST/../binary:$DEST/../dynbinary:$DEST/../gccgo:$DEST/../dyngccgo:$PATH" +export PATH="$DEST/../dynbinary:$DEST/../binary:$DEST/../gccgo:$DEST/../dyngccgo:$PATH" if ! command -v docker &> /dev/null; then echo >&2 'error: binary or dynbinary must be run before .integration-daemon-start' diff --git a/hack/make/dind-dynbinary b/hack/make/dind-dynbinary new file mode 100644 index 000000000..a577ca076 --- /dev/null +++ b/hack/make/dind-dynbinary @@ -0,0 +1,23 @@ +#!/bin/bash + +DEST=$1 +DOCKERBIN=$DEST/../binary/docker +BUILDDIR=$DEST/../dynbinary/ +RPM_PATH=$DEST/../rpm/ + +build_rpm() { + if [ ! -x $DOCKERBIN ]; then + echo "No docker binary was found to execute. This step requires 'binary' to be run first." + exit 1 + fi + + $DOCKERBIN -d 2>/dev/null & + $DOCKERBIN build -t centos-build -f Dockerfile.centos ./ + $DOCKERBIN run -it --rm --privileged -v /go:/go -v /usr/local:/usr/local -e "KEEPBUNDLE=true" --name centos-build-container centos-build hack/make.sh dynbinary + + # turn off the docker daemon + $DOCKERBIN rmi centos-build + cat /var/run/docker.pid | xargs kill +} + +build_rpm diff --git a/hack/make/rpm b/hack/make/rpm new file mode 100644 index 000000000..6340f7913 --- /dev/null +++ b/hack/make/rpm @@ -0,0 +1,193 @@ +#!/bin/bash + +DEST=$1 +PACKAGE_NAME=${PACKAGE_NAME:-docker-engine} + +# XXX - The package version in CentOS gets messed up and inserts a '~' +# (including the single quote) if we use the same package +# version scheme as the deb packages. This doesn't work with +# rpmbuild. +PKGVERSION="${VERSION}" +# if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better +if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then + GIT_UNIX="$(git log -1 --pretty='%at')" + GIT_DATE="$(date --date "@$GIT_UNIX" +'%Y%m%d.%H%M%S')" + GIT_COMMIT="$(git log -1 --pretty='%h')" + GIT_VERSION="git${GIT_DATE}.0.${GIT_COMMIT}" + # GIT_VERSION is now something like 'git20150128.112847.0.17e840a' + PKGVERSION="$PKGVERSION~$GIT_VERSION" +fi + +# $ dpkg --compare-versions 1.5.0 gt 1.5.0~rc1 && echo true || echo false +# true +# $ dpkg --compare-versions 1.5.0~rc1 gt 1.5.0~git20150128.112847.17e840a && echo true || echo false +# true +# $ dpkg --compare-versions 1.5.0~git20150128.112847.17e840a gt 1.5.0~dev~git20150128.112847.17e840a && echo true || echo false +# true + +# ie, 1.5.0 > 1.5.0~rc1 > 1.5.0~git20150128.112847.17e840a > 1.5.0~dev~git20150128.112847.17e840a + +PACKAGE_ARCHITECTURE=`uname -i` + +PACKAGE_URL="http://www.docker.com/" +PACKAGE_MAINTAINER="support@docker.com" +PACKAGE_DESCRIPTION="Linux container runtime +Docker complements LXC with a high-level API which operates at the process +level. It runs unix processes with strong guarantees of isolation and +repeatability across servers. +Docker is a great building block for automating distributed systems: +large-scale web deployments, database clusters, continuous deployment systems, +private PaaS, service-oriented architectures, etc." +PACKAGE_LICENSE="Apache-2.0" + +# bundle the RPM using FPM -- we may want to change this to rpmbuild at some point +bundle_rpm() { + DIR=$DEST/build + + # Include our udev rules + mkdir -p $DIR/etc/udev/rules.d + cp contrib/udev/80-docker.rules $DIR/etc/udev/rules.d/ + + mkdir -p $DIR/usr/lib/systemd/system + cp contrib/init/systemd/docker.{service,socket} $DIR/usr/lib/systemd/system + + cat > $DIR/usr/lib/systemd/system/docker.service <<'EOF' +[Unit] +Description=Docker Application Container Engine +Documentation=http://docs.docker.com +After=network.target docker.socket +Requires=docker.socket + +[Service] +Type=notify +EnvironmentFile=-/etc/sysconfig/docker +EnvironmentFile=-/etc/sysconfig/docker-storage +ExecStart=/usr/bin/docker -d $OPTIONS $DOCKER_STORAGE_OPTIONS +LimitNOFILE=1048576 +LimitNPROC=1048576 +MountFlags=private + +[Install] +WantedBy=multi-user.target +EOF + + mkdir -p $DIR/etc/sysconfig + cat > $DIR/etc/sysconfig/docker <<'EOF' +# /etc/sysconfig/docker + +# Modify these options if you want to change the way the docker daemon runs +OPTIONS=--selinux-enabled -H fd:// + +# Location used for temporary files, such as those created by +# docker load and build operations. Default is /var/lib/docker/tmp +# Can be overriden by setting the following environment variable. +# DOCKER_TMPDIR=/var/tmp +EOF + +cat > $DIR/etc/sysconfig/docker-storage <<'EOF' +# By default, Docker uses a loopback-mounted sparse file in +# /var/lib/docker. The loopback makes it slower, and there are some +# restrictive defaults, such as 100GB max storage. + +# If your installation did not set a custom storage for Docker, you +# may do it below. + +# Example: Use a custom pair of raw logical volumes (one for metadata, +# one for data). +# DOCKER_STORAGE_OPTIONS = --storage-opt dm.metadatadev=/dev/mylogvol/my-docker-metadata --storage-opt dm.datadev=/dev/mylogvol/my-docker-data + +DOCKER_STORAGE_OPTIONS= +EOF + + # 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/ + + # Include contributed man pages + docs/man/md2man-all.sh -q + manRoot="$DIR/usr/share/man" + mkdir -p "$manRoot" + for manDir in docs/man/man?; do + manBase="$(basename "$manDir")" # "man1" + for manFile in "$manDir"/*; do + manName="$(basename "$manFile")" # "docker-build.1" + mkdir -p "$manRoot/$manBase" + gzip -c "$manFile" > "$manRoot/$manBase/$manName.gz" + done + done + + # Copy the binary + # This will fail if the dynbinary bundle hasn't been built + mkdir -p $DIR/usr/bin + cp $DEST/../dynbinary/docker-$VERSION $DIR/usr/bin/docker + cp $DEST/../dynbinary/dockerinit-$VERSION $DIR/usr/bin/dockerinit + + # Generate postinst/prerm/postrm scripts + cat > $DEST/postinst <<'EOF' +EOF + + cat > $DEST/preinst <<'EOF' +if ! getent group docker > /dev/null; then + groupadd --system docker +fi +EOF + + cat > $DEST/prerm <<'EOF' +EOF + + cat > $DEST/postrm <<'EOF' +## In case this system is running systemd, we make systemd reload the unit files +## to pick up changes. +#if [ -d /run/systemd/system ] ; then +# systemctl --system daemon-reload > /dev/null || true +#fi +EOF + + chmod +x $DEST/postinst $DEST/prerm $DEST/postrm $DEST/preinst + + ( + # switch directories so we create *.deb in the right folder + cd $DEST + + # create PACKAGE_NAME-VERSION package + fpm -s dir -C $DIR \ + --name $PACKAGE_NAME-$VERSION --version "$PKGVERSION" \ + --epoch 7 \ + --before-install $DEST/preinst \ + --after-install $DEST/postinst \ + --before-remove $DEST/prerm \ + --after-remove $DEST/postrm \ + --architecture "$PACKAGE_ARCHITECTURE" \ + --prefix / \ + --depends iptables \ + --depends xz \ + --depends "systemd >= 208-20" \ + --depends "device-mapper-libs >= 7:1.02.90-1" \ + --depends "device-mapper-event-libs >= 7:1.02.90-1" \ + --depends libselinux \ + --depends libsepol \ + --depends sqlite \ + --description "$PACKAGE_DESCRIPTION" \ + --maintainer "$PACKAGE_MAINTAINER" \ + --conflicts docker \ + --conflicts docker-io \ + --conflicts lxc-docker-virtual-package \ + --conflicts lxc-docker \ + --url "$PACKAGE_URL" \ + --license "$PACKAGE_LICENSE" \ + --config-files etc/sysconfig \ + --config-files etc/udev/rules.d/80-docker.rules \ + --rpm-compression gzip \ + -t rpm . + ) + + # clean up after ourselves so we have a clean output directory + rm $DEST/postinst $DEST/prerm $DEST/postrm $DEST/preinst + rm -r $DIR +} + +bundle_rpm From 8c9d67921a44e4f189373d8dc10fc8946039918d Mon Sep 17 00:00:00 2001 From: Mary Anthony Date: Tue, 5 May 2015 14:33:31 -0700 Subject: [PATCH 760/999] Updating branch for machine docs Signed-off-by: Mary Anthony --- docs/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Dockerfile b/docs/Dockerfile index a53048bb7..8ece2b09e 100644 --- a/docs/Dockerfile +++ b/docs/Dockerfile @@ -8,7 +8,7 @@ MAINTAINER Sven Dowideit (@SvenDowideit) # sub project ENV COMPOSE_BRANCH release ENV SWARM_BRANCH v0.2.0 -ENV MACHINE_BRANCH master +ENV MACHINE_BRANCH docs ENV DISTRIB_BRANCH docs From 08b7f30fcd050244026098673b19700485308b5a Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Tue, 5 May 2015 14:27:42 -0700 Subject: [PATCH 761/999] Fix issue where build steps are duplicated in the output This fixes an issue where the build output for the "Steps" would look like: ``` Step 1: RUN echo hi echo hi ``` instead of ``` Step 1: RUN echo hi ``` Also, I noticed that there were no checks to make sure invalid Dockerfile cmd flags were caught on cmds that didn't use cmd flags at all. They would have been caught on the cmds that had flags, but cmds that didn't bother to add a new code for flags would have just ignored them. So, I added checks to each cmd to flag it. Added testcases for issues. Signed-off-by: Doug Davis --- builder/dispatchers.go | 56 ++++++++++++++++++++++++ builder/evaluator.go | 11 ++++- integration-cli/docker_cli_build_test.go | 35 +++++++++++++++ 3 files changed, 101 insertions(+), 1 deletion(-) diff --git a/builder/dispatchers.go b/builder/dispatchers.go index 195d18305..7d1dab640 100644 --- a/builder/dispatchers.go +++ b/builder/dispatchers.go @@ -47,6 +47,10 @@ func env(b *Builder, args []string, attributes map[string]bool, original string) return fmt.Errorf("Bad input to ENV, too many args") } + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + // TODO/FIXME/NOT USED // Just here to show how to use the builder flags stuff within the // context of a builder command. Will remove once we actually add @@ -97,6 +101,10 @@ func maintainer(b *Builder, args []string, attributes map[string]bool, original return fmt.Errorf("MAINTAINER requires exactly one argument") } + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + b.maintainer = args[0] return b.commit("", b.Config.Cmd, fmt.Sprintf("MAINTAINER %s", b.maintainer)) } @@ -114,6 +122,10 @@ func label(b *Builder, args []string, attributes map[string]bool, original strin return fmt.Errorf("Bad input to LABEL, too many args") } + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + commitStr := "LABEL" if b.Config.Labels == nil { @@ -142,6 +154,10 @@ func add(b *Builder, args []string, attributes map[string]bool, original string) return fmt.Errorf("ADD requires at least two arguments") } + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + return b.runContextCommand(args, true, true, "ADD") } @@ -154,6 +170,10 @@ func dispatchCopy(b *Builder, args []string, attributes map[string]bool, origina return fmt.Errorf("COPY requires at least two arguments") } + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + return b.runContextCommand(args, false, false, "COPY") } @@ -166,6 +186,10 @@ func from(b *Builder, args []string, attributes map[string]bool, original string return fmt.Errorf("FROM requires one argument") } + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + name := args[0] if name == NoBaseImageSpecifier { @@ -210,6 +234,10 @@ func onbuild(b *Builder, args []string, attributes map[string]bool, original str return fmt.Errorf("ONBUILD requires at least one argument") } + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + triggerInstruction := strings.ToUpper(strings.TrimSpace(args[0])) switch triggerInstruction { case "ONBUILD": @@ -233,6 +261,10 @@ func workdir(b *Builder, args []string, attributes map[string]bool, original str return fmt.Errorf("WORKDIR requires exactly one argument") } + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + workdir := args[0] if !filepath.IsAbs(workdir) { @@ -258,6 +290,10 @@ func run(b *Builder, args []string, attributes map[string]bool, original string) return fmt.Errorf("Please provide a source image with `from` prior to run") } + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + args = handleJsonArgs(args, attributes) if !attributes["json"] { @@ -317,6 +353,10 @@ func run(b *Builder, args []string, attributes map[string]bool, original string) // Argument handling is the same as RUN. // func cmd(b *Builder, args []string, attributes map[string]bool, original string) error { + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + cmdSlice := handleJsonArgs(args, attributes) if !attributes["json"] { @@ -345,6 +385,10 @@ func cmd(b *Builder, args []string, attributes map[string]bool, original string) // is initialized at NewBuilder time instead of through argument parsing. // func entrypoint(b *Builder, args []string, attributes map[string]bool, original string) error { + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + parsed := handleJsonArgs(args, attributes) switch { @@ -384,6 +428,10 @@ func expose(b *Builder, args []string, attributes map[string]bool, original stri return fmt.Errorf("EXPOSE requires at least one argument") } + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + if b.Config.ExposedPorts == nil { b.Config.ExposedPorts = make(nat.PortSet) } @@ -428,6 +476,10 @@ func user(b *Builder, args []string, attributes map[string]bool, original string return fmt.Errorf("USER requires exactly one argument") } + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + b.Config.User = args[0] return b.commit("", b.Config.Cmd, fmt.Sprintf("USER %v", args)) } @@ -441,6 +493,10 @@ func volume(b *Builder, args []string, attributes map[string]bool, original stri return fmt.Errorf("VOLUME requires at least one argument") } + if err := b.BuilderFlags.Parse(); err != nil { + return err + } + if b.Config.Volumes == nil { b.Config.Volumes = map[string]struct{}{} } diff --git a/builder/evaluator.go b/builder/evaluator.go index cd8bff1ce..bdcc6b29a 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -280,7 +280,11 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error { original := ast.Original flags := ast.Flags strs := []string{} - msg := fmt.Sprintf("Step %d : %s", stepN, original) + msg := fmt.Sprintf("Step %d : %s", stepN, strings.ToUpper(cmd)) + + if len(ast.Flags) > 0 { + msg += " " + strings.Join(ast.Flags, " ") + } if cmd == "onbuild" { if ast.Next == nil { @@ -289,6 +293,11 @@ func (b *Builder) dispatch(stepN int, ast *parser.Node) error { ast = ast.Next.Children[0] strs = append(strs, ast.Value) msg += " " + ast.Value + + if len(ast.Flags) > 0 { + msg += " " + strings.Join(ast.Flags, " ") + } + } // count the number of nodes that we are going to traverse first diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 20a317246..5247a11ec 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5349,3 +5349,38 @@ RUN cat /proc/self/cgroup c.Fatalf("unexpected failure when running container with --cgroup-parent option - %s\n%v", string(out), err) } } + +func (s *DockerSuite) TestBuildNoDupOutput(c *check.C) { + // Check to make sure our build output prints the Dockerfile cmd + // property - there was a bug that caused it to be duplicated on the + // Step X line + name := "testbuildnodupoutput" + + _, out, err := buildImageWithOut(name, ` + FROM busybox + RUN env`, false) + if err != nil { + c.Fatalf("Build should have worked: %q", err) + } + + exp := "\nStep 1 : RUN env\n" + if !strings.Contains(out, exp) { + c.Fatalf("Bad output\nGot:%s\n\nExpected to contain:%s\n", out, exp) + } +} + +func (s *DockerSuite) TestBuildBadCmdFlag(c *check.C) { + name := "testbuildbadcmdflag" + + _, out, err := buildImageWithOut(name, ` + FROM busybox + MAINTAINER --boo joe@example.com`, false) + if err == nil { + c.Fatal("Build should have failed") + } + + exp := `"Unknown flag: boo"` + if !strings.Contains(out, exp) { + c.Fatalf("Bad output\nGot:%s\n\nExpected to contain:%s\n", out, exp) + } +} From 18beb5561140aaa950f00391a87bb332fb2b6aea Mon Sep 17 00:00:00 2001 From: Jessica Frazelle Date: Thu, 30 Apr 2015 15:30:42 -0700 Subject: [PATCH 762/999] Add rpm for centos-6, centos-7, fedora-20, fedora-21 Signed-off-by: Jessica Frazelle --- Dockerfile | 1 - Dockerfile.centos | 36 ----- contrib/builder/rpm/README.md | 5 + contrib/builder/rpm/build.sh | 10 ++ contrib/builder/rpm/centos-6/Dockerfile | 15 ++ contrib/builder/rpm/centos-7/Dockerfile | 15 ++ contrib/builder/rpm/fedora-20/Dockerfile | 15 ++ contrib/builder/rpm/fedora-21/Dockerfile | 15 ++ contrib/builder/rpm/generate.sh | 73 +++++++++ hack/make.sh | 2 +- hack/make/.build-rpm/docker-engine.spec | 180 +++++++++++++++++++++ hack/make/.integration-daemon-start | 2 +- hack/make/build-rpm | 73 +++++++++ hack/make/dind-dynbinary | 23 --- hack/make/rpm | 193 ----------------------- 15 files changed, 403 insertions(+), 255 deletions(-) delete mode 100644 Dockerfile.centos create mode 100644 contrib/builder/rpm/README.md create mode 100755 contrib/builder/rpm/build.sh create mode 100644 contrib/builder/rpm/centos-6/Dockerfile create mode 100644 contrib/builder/rpm/centos-7/Dockerfile create mode 100644 contrib/builder/rpm/fedora-20/Dockerfile create mode 100644 contrib/builder/rpm/fedora-21/Dockerfile create mode 100755 contrib/builder/rpm/generate.sh create mode 100644 hack/make/.build-rpm/docker-engine.spec create mode 100644 hack/make/build-rpm delete mode 100644 hack/make/dind-dynbinary delete mode 100644 hack/make/rpm diff --git a/Dockerfile b/Dockerfile index 0c85a8633..2b49fc1e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,7 +47,6 @@ RUN apt-get update && apt-get install -y \ python-pip \ python-websocket \ reprepro \ - rpm \ ruby1.9.1 \ ruby1.9.1-dev \ s3cmd=1.1.0* \ diff --git a/Dockerfile.centos b/Dockerfile.centos deleted file mode 100644 index 0d11f2f2f..000000000 --- a/Dockerfile.centos +++ /dev/null @@ -1,36 +0,0 @@ -# This file creates a CentOS docker container which can be used to create the Docker -# RPMs, however it shouldn't be called directly. -# - -FROM centos:7.0.1406 -MAINTAINER Patrick Devine (@pdev110) - -RUN rpm --import /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7 - -# Packaged dependencies -RUN yum groupinstall -y "Development Tools" - -RUN yum install -y \ - btrfs-progs-devel \ - device-mapper-devel \ - glibc-static \ - libselinux-devel \ - sqlite-devel - -VOLUME /go - -ENV LXC_VERSION 1.0.7 -ENV GO_VERSION 1.4.2 -ENV PATH /go/bin:/usr/local/go/bin:$PATH -ENV GOPATH /go:/go/src/github.com/docker/docker/vendor -ENV GOFMT_VERSION 1.3.3 - -# Add an unprivileged user to be used for tests which need it -RUN groupadd -r docker -RUN useradd --create-home --gid docker unprivilegeduser - -WORKDIR /go/src/github.com/docker/docker -ENV DOCKER_BUILDTAGS selinux btrfs_noversion - -# Wrap all commands in the "docker-in-docker" script to allow nested containers -#ENTRYPOINT ["hack/dind"] diff --git a/contrib/builder/rpm/README.md b/contrib/builder/rpm/README.md new file mode 100644 index 000000000..153fbceb6 --- /dev/null +++ b/contrib/builder/rpm/README.md @@ -0,0 +1,5 @@ +# `dockercore/builder-rpm` + +This image's tags contain the dependencies for building Docker `.rpm`s for each of the RPM-based platforms Docker targets. + +To add new tags, see [`contrib/builder/rpm` in https://github.com/docker/docker](https://github.com/docker/docker/tree/master/contrib/builder/rpm), specifically the `generate.sh` script, whose usage is described in a comment at the top of the file. diff --git a/contrib/builder/rpm/build.sh b/contrib/builder/rpm/build.sh new file mode 100755 index 000000000..558f7ee0d --- /dev/null +++ b/contrib/builder/rpm/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash +set -e + +cd "$(dirname "$(readlink -f "$BASH_SOURCE")")" + +set -x +./generate.sh +for d in */; do + docker build -t "dockercore/builder-rpm:$(basename "$d")" "$d" +done diff --git a/contrib/builder/rpm/centos-6/Dockerfile b/contrib/builder/rpm/centos-6/Dockerfile new file mode 100644 index 000000000..d24814227 --- /dev/null +++ b/contrib/builder/rpm/centos-6/Dockerfile @@ -0,0 +1,15 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/rpm/generate.sh"! +# + +FROM centos:6 + +RUN yum groupinstall -y "Development Tools" +RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libselinux-devel sqlite-devel tar + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS selinux exclude_graphdriver_btrfs diff --git a/contrib/builder/rpm/centos-7/Dockerfile b/contrib/builder/rpm/centos-7/Dockerfile new file mode 100644 index 000000000..a58c9f58b --- /dev/null +++ b/contrib/builder/rpm/centos-7/Dockerfile @@ -0,0 +1,15 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/rpm/generate.sh"! +# + +FROM centos:7 + +RUN yum groupinstall -y "Development Tools" +RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libselinux-devel sqlite-devel tar + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS selinux diff --git a/contrib/builder/rpm/fedora-20/Dockerfile b/contrib/builder/rpm/fedora-20/Dockerfile new file mode 100644 index 000000000..f5642cdf0 --- /dev/null +++ b/contrib/builder/rpm/fedora-20/Dockerfile @@ -0,0 +1,15 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/rpm/generate.sh"! +# + +FROM fedora:20 + +RUN yum install -y @development-tools fedora-packager +RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libselinux-devel sqlite-devel tar + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS selinux diff --git a/contrib/builder/rpm/fedora-21/Dockerfile b/contrib/builder/rpm/fedora-21/Dockerfile new file mode 100644 index 000000000..18e7837c7 --- /dev/null +++ b/contrib/builder/rpm/fedora-21/Dockerfile @@ -0,0 +1,15 @@ +# +# THIS FILE IS AUTOGENERATED; SEE "contrib/builder/rpm/generate.sh"! +# + +FROM fedora:21 + +RUN yum install -y @development-tools fedora-packager +RUN yum install -y btrfs-progs-devel device-mapper-devel glibc-static libselinux-devel sqlite-devel tar + +ENV GO_VERSION 1.4.2 +RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local +ENV PATH $PATH:/usr/local/go/bin + +ENV AUTO_GOPATH 1 +ENV DOCKER_BUILDTAGS selinux diff --git a/contrib/builder/rpm/generate.sh b/contrib/builder/rpm/generate.sh new file mode 100755 index 000000000..7bfd06f67 --- /dev/null +++ b/contrib/builder/rpm/generate.sh @@ -0,0 +1,73 @@ +#!/bin/bash +set -e + +# usage: ./generate.sh [versions] +# ie: ./generate.sh +# to update all Dockerfiles in this directory +# or: ./generate.sh +# to only update fedora-20/Dockerfile +# or: ./generate.sh fedora-newversion +# to create a new folder and a Dockerfile within it + +cd "$(dirname "$(readlink -f "$BASH_SOURCE")")" + +versions=( "$@" ) +if [ ${#versions[@]} -eq 0 ]; then + versions=( */ ) +fi +versions=( "${versions[@]%/}" ) + +for version in "${versions[@]}"; do + distro="${version%-*}" + suite="${version##*-}" + from="${distro}:${suite}" + + mkdir -p "$version" + echo "$version -> FROM $from" + cat > "$version/Dockerfile" <<-EOF + # + # THIS FILE IS AUTOGENERATED; SEE "contrib/builder/rpm/generate.sh"! + # + + FROM $from + EOF + + echo >> "$version/Dockerfile" + + case "$from" in + centos:*) + # get "Development Tools" packages dependencies + echo 'RUN yum groupinstall -y "Development Tools"' >> "$version/Dockerfile" + ;; + *) + echo 'RUN yum install -y @development-tools fedora-packager' >> "$version/Dockerfile" + ;; + esac + + # this list is sorted alphabetically; please keep it that way + packages=( + btrfs-progs-devel # for "btrfs/ioctl.h" (and "version.h" if possible) + device-mapper-devel # for "libdevmapper.h" + glibc-static + libselinux-devel # for "libselinux.so" + sqlite-devel # for "sqlite3.h" + tar # older versions of dev-tools don't have tar + ) + echo "RUN yum install -y ${packages[*]}" >> "$version/Dockerfile" + + echo >> "$version/Dockerfile" + + awk '$1 == "ENV" && $2 == "GO_VERSION" { print; exit }' ../../../Dockerfile >> "$version/Dockerfile" + echo 'RUN curl -fsSL "https://storage.googleapis.com/golang/go${GO_VERSION}.linux-amd64.tar.gz" | tar xvzC /usr/local' >> "$version/Dockerfile" + echo 'ENV PATH $PATH:/usr/local/go/bin' >> "$version/Dockerfile" + + echo >> "$version/Dockerfile" + + echo 'ENV AUTO_GOPATH 1' >> "$version/Dockerfile" + + if [ "$from" == "centos:6" ]; then + echo 'ENV DOCKER_BUILDTAGS selinux exclude_graphdriver_btrfs' >> "$version/Dockerfile" + else + echo 'ENV DOCKER_BUILDTAGS selinux' >> "$version/Dockerfile" + fi +done diff --git a/hack/make.sh b/hack/make.sh index 96ca5902b..699bedb37 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -274,7 +274,7 @@ main() { # We want this to fail if the bundles already exist and cannot be removed. # This is to avoid mixing bundles from different versions of the code. mkdir -p bundles - if [ -e "bundles/$VERSION" ] && [ -z ${KEEPBUNDLE} ]; then + if [ -e "bundles/$VERSION" ]; then echo "bundles/$VERSION already exists. Removing." rm -fr "bundles/$VERSION" && mkdir "bundles/$VERSION" || exit 1 echo diff --git a/hack/make/.build-rpm/docker-engine.spec b/hack/make/.build-rpm/docker-engine.spec new file mode 100644 index 000000000..1bb654df8 --- /dev/null +++ b/hack/make/.build-rpm/docker-engine.spec @@ -0,0 +1,180 @@ +Name: docker-engine +Version: %{_version} +Release: %{_release}%{?dist} +Summary: The open-source application container engine + +License: ASL 2.0 +Source: %{name}.tar.gz + +URL: https://dockerproject.com +Vendor: Docker +Packager: Docker + +# docker builds in a checksum of dockerinit into docker, +# # so stripping the binaries breaks docker +%global __os_install_post %{_rpmconfigdir}/brp-compress +%global debug_package %{nil} + +# is_systemd conditional +%if 0%{?fedora} >= 21 || 0%{?centos} >= 7 || 0%{?rhel} >= 7 +%global is_systemd 1 +%endif + +# required packages for build +# most are already in the container (see contrib/builder/rpm/generate.sh) +# only require systemd on those systems +%if 0%{?is_systemd} +BuildRequires: pkgconfig(systemd) +Requires: systemd-units +%else +Requires(post): chkconfig +Requires(preun): chkconfig +# This is for /sbin/service +Requires(preun): initscripts +%endif + +# required packages on install +Requires: /bin/sh +Requires: iptables +Requires: libc.so.6 +Requires: libcgroup +Requires: libpthread.so.0 +Requires: libsqlite3.so.0 +Requires: tar +Requires: xz +%if 0%{?fedora} >= 21 +# Resolves: rhbz#1165615 +Requires: device-mapper-libs >= 1.02.90-1 +%endif + +# conflicting packages +Conflicts: docker +Conflicts: docker-io + +%description +Docker is an open source project to pack, ship and run any application as a +lightweight container + +Docker containers are both hardware-agnostic and platform-agnostic. This means +they can run anywhere, from your laptop to the largest EC2 compute instance and +everything in between - and they don't require you to use a particular +language, framework or packaging system. That makes them great building blocks +for deploying and scaling web apps, databases, and backend services without +depending on a particular stack or provider. + +%prep +%if 0%{?centos} <= 6 +%setup -n %{name} +%else +%autosetup -n %{name} +%endif + +%build +./hack/make.sh dynbinary +# ./docs/man/md2man-all.sh runs outside the build container (if at all), since we don't have go-md2man here + +%check +./bundles/%{_origversion}/dynbinary/docker -v + +%install +# install binary +install -d $RPM_BUILD_ROOT/%{_bindir} +install -p -m 755 bundles/%{_origversion}/dynbinary/docker-%{_origversion} $RPM_BUILD_ROOT/%{_bindir}/docker + +# install dockerinit +install -d $RPM_BUILD_ROOT/%{_libexecdir}/docker +install -p -m 755 bundles/%{_origversion}/dynbinary/dockerinit-%{_origversion} $RPM_BUILD_ROOT/%{_libexecdir}/docker/dockerinit + +# install udev rules +install -d $RPM_BUILD_ROOT/%{_sysconfdir}/udev/rules.d +install -p -m 755 contrib/udev/80-docker.rules $RPM_BUILD_ROOT/%{_sysconfdir}/udev/rules.d/80-docker.rules + +# add init scripts +install -d $RPM_BUILD_ROOT/etc/sysconfig +install -d $RPM_BUILD_ROOT/%{_initddir} + + +%if 0%{?is_systemd} +install -d $RPM_BUILD_ROOT/%{_unitdir} +install -p -m 644 contrib/init/systemd/docker.service $RPM_BUILD_ROOT/%{_unitdir}/docker.service +install -p -m 644 contrib/init/systemd/docker.socket $RPM_BUILD_ROOT/%{_unitdir}/docker.socket +%endif + +install -p -m 644 contrib/init/sysvinit-redhat/docker.sysconfig $RPM_BUILD_ROOT/etc/sysconfig/docker +install -p -m 755 contrib/init/sysvinit-redhat/docker $RPM_BUILD_ROOT/%{_initddir}/docker + +# add bash completions +install -d $RPM_BUILD_ROOT/usr/share/bash-completion/completions +install -d $RPM_BUILD_ROOT/usr/share/zsh/vendor-completions +install -d $RPM_BUILD_ROOT/usr/share/fish/completions +install -p -m 644 contrib/completion/bash/docker $RPM_BUILD_ROOT/usr/share/bash-completion/completions/docker +install -p -m 644 contrib/completion/zsh/_docker $RPM_BUILD_ROOT/usr/share/zsh/vendor-completions/_docker +install -p -m 644 contrib/completion/fish/docker.fish $RPM_BUILD_ROOT/usr/share/fish/completions/docker.fish + +# install manpages +install -d %{buildroot}%{_mandir}/man1 +install -p -m 644 docs/man/man1/*.1 $RPM_BUILD_ROOT/%{_mandir}/man1 +install -d %{buildroot}%{_mandir}/man5 +install -p -m 644 docs/man/man5/*.5 $RPM_BUILD_ROOT/%{_mandir}/man5 + +# add vimfiles +install -d $RPM_BUILD_ROOT/usr/share/vim/vimfiles/doc +install -d $RPM_BUILD_ROOT/usr/share/vim/vimfiles/ftdetect +install -d $RPM_BUILD_ROOT/usr/share/vim/vimfiles/syntax +install -p -m 644 contrib/syntax/vim/doc/dockerfile.txt $RPM_BUILD_ROOT/usr/share/vim/vimfiles/doc/dockerfile.txt +install -p -m 644 contrib/syntax/vim/ftdetect/dockerfile.vim $RPM_BUILD_ROOT/usr/share/vim/vimfiles/ftdetect/dockerfile.vim +install -p -m 644 contrib/syntax/vim/syntax/dockerfile.vim $RPM_BUILD_ROOT/usr/share/vim/vimfiles/syntax/dockerfile.vim + + +# list files owned by the package here +%files +/%{_bindir}/docker +/%{_libexecdir}/docker/dockerinit +/%{_sysconfdir}/udev/rules.d/80-docker.rules +%if 0%{?is_systemd} +/%{_unitdir}/docker.service +/%{_unitdir}/docker.socket +%endif +/etc/sysconfig/docker +/%{_initddir}/docker +/usr/share/bash-completion/completions/docker +/usr/share/zsh/vendor-completions/_docker +/usr/share/fish/completions/docker.fish +%doc +/%{_mandir}/man1/* +/%{_mandir}/man5/* +/usr/share/vim/vimfiles/doc/dockerfile.txt +/usr/share/vim/vimfiles/ftdetect/dockerfile.vim +/usr/share/vim/vimfiles/syntax/dockerfile.vim + +%post +%if 0%{?is_systemd} +%systemd_post docker +%else +# This adds the proper /etc/rc*.d links for the script +/sbin/chkconfig --add docker +%endif +if ! getent group docker > /dev/null; then + groupadd --system docker +fi + +%preun +%if 0%{?is_systemd} +%systemd_preun docker +%else +if [ $1 -eq 0 ] ; then + /sbin/service docker stop >/dev/null 2>&1 + /sbin/chkconfig --del docker +fi +%endif + +%postun +%if 0%{?is_systemd} +%systemd_postun_with_restart docker +%else +if [ "$1" -ge "1" ] ; then + /sbin/service docker condrestart >/dev/null 2>&1 || : +fi +%endif + +%changelog diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start index 0a889ae7a..57fd52502 100644 --- a/hack/make/.integration-daemon-start +++ b/hack/make/.integration-daemon-start @@ -2,7 +2,7 @@ # see test-integration-cli for example usage of this script -export PATH="$DEST/../dynbinary:$DEST/../binary:$DEST/../gccgo:$DEST/../dyngccgo:$PATH" +export PATH="$DEST/../binary:$DEST/../dynbinary:$DEST/../gccgo:$DEST/../dyngccgo:$PATH" if ! command -v docker &> /dev/null; then echo >&2 'error: binary or dynbinary must be run before .integration-daemon-start' diff --git a/hack/make/build-rpm b/hack/make/build-rpm new file mode 100644 index 000000000..0f3ff6d00 --- /dev/null +++ b/hack/make/build-rpm @@ -0,0 +1,73 @@ +#!/bin/bash +set -e + +DEST=$1 + +# subshell so that we can export PATH without breaking other things +( + source "$(dirname "$BASH_SOURCE")/.integration-daemon-start" + + # TODO consider using frozen images for the dockercore/builder-rpm tags + + rpmName=docker-engine + rpmVersion="${VERSION%%-*}" + rpmRelease=1 + + # rpmRelease versioning is as follows + # Docker 1.7.0: version=1.7.0, release=1 + # Docker 1.7.0-rc1: version=1.7.0, release=0.1.rc1 + # Docker 1.7.0-dev nightly: version=1.7.0, release=0.0.YYYYMMDD.HHMMSS.gitHASH + + # if we have a "-rc*" suffix, set appropriate release + if [[ "$VERSION" == *-rc* ]]; then + rcVersion=${VERSION#*-rc} + rpmRelease="0.${rcVersion}.rc${rcVersion}" + fi + + # if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better + if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then + gitUnix="$(git log -1 --pretty='%at')" + gitDate="$(date --date "@$gitUnix" +'%Y%m%d.%H%M%S')" + gitCommit="$(git log -1 --pretty='%h')" + gitVersion="${gitDate}.git${gitCommit}" + # gitVersion is now something like '20150128.112847.17e840a' + rpmRelease="0.0.$gitVersion" + fi + + rpmPackager="$(awk -F ': ' '$1 == "Packager" { print $2; exit }' hack/make/.build-rpm/${rpmName}.spec)" + rpmDate="$(date +'%a %b %d %Y')" + + # if go-md2man is available, pre-generate the man pages + ./docs/man/md2man-all.sh -q || true + # TODO decide if it's worth getting go-md2man in _each_ builder environment to avoid this + + # TODO add a configurable knob for _which_ rpms to build so we don't have to modify the file or build all of them every time we need to test + for dir in contrib/builder/rpm/*/; do + version="$(basename "$dir")" + suite="${version##*-}" + + image="dockercore/builder-rpm:$version" + if ! docker inspect "$image" &> /dev/null; then + ( set -x && docker build -t "$image" "$dir" ) + fi + + mkdir -p "$DEST/$version" + cat > "$DEST/$version/Dockerfile.build" <<-EOF + FROM $image + COPY . /usr/src/${rpmName} + RUN mkdir -p /root/rpmbuild/SOURCES + WORKDIR /root/rpmbuild + RUN ln -sfv /usr/src/${rpmName}/hack/make/.build-rpm SPECS + RUN tar -cz -C /usr/src -f /root/rpmbuild/SOURCES/${rpmName}.tar.gz ${rpmName} + WORKDIR /root/rpmbuild/SPECS + RUN { echo '* $rpmDate $rpmPackager $rpmVersion-$rpmRelease'; echo '* Version: $VERSION'; } >> ${rpmName}.spec && tail >&2 ${rpmName}.spec + RUN rpmbuild -ba --define '_release $rpmRelease' --define '_version $rpmVersion' --define '_origversion $VERSION' ${rpmName}.spec + EOF + tempImage="docker-temp/build-rpm:$version" + ( set -x && docker build -t "$tempImage" -f $DEST/$version/Dockerfile.build . ) + docker run --rm "$tempImage" bash -c 'cd /root/rpmbuild && tar -c *RPMS' | tar -xvC "$DEST/$version" + docker rmi "$tempImage" + done + + source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop" +) 2>&1 | tee -a $DEST/test.log diff --git a/hack/make/dind-dynbinary b/hack/make/dind-dynbinary deleted file mode 100644 index a577ca076..000000000 --- a/hack/make/dind-dynbinary +++ /dev/null @@ -1,23 +0,0 @@ -#!/bin/bash - -DEST=$1 -DOCKERBIN=$DEST/../binary/docker -BUILDDIR=$DEST/../dynbinary/ -RPM_PATH=$DEST/../rpm/ - -build_rpm() { - if [ ! -x $DOCKERBIN ]; then - echo "No docker binary was found to execute. This step requires 'binary' to be run first." - exit 1 - fi - - $DOCKERBIN -d 2>/dev/null & - $DOCKERBIN build -t centos-build -f Dockerfile.centos ./ - $DOCKERBIN run -it --rm --privileged -v /go:/go -v /usr/local:/usr/local -e "KEEPBUNDLE=true" --name centos-build-container centos-build hack/make.sh dynbinary - - # turn off the docker daemon - $DOCKERBIN rmi centos-build - cat /var/run/docker.pid | xargs kill -} - -build_rpm diff --git a/hack/make/rpm b/hack/make/rpm deleted file mode 100644 index 6340f7913..000000000 --- a/hack/make/rpm +++ /dev/null @@ -1,193 +0,0 @@ -#!/bin/bash - -DEST=$1 -PACKAGE_NAME=${PACKAGE_NAME:-docker-engine} - -# XXX - The package version in CentOS gets messed up and inserts a '~' -# (including the single quote) if we use the same package -# version scheme as the deb packages. This doesn't work with -# rpmbuild. -PKGVERSION="${VERSION}" -# if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better -if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then - GIT_UNIX="$(git log -1 --pretty='%at')" - GIT_DATE="$(date --date "@$GIT_UNIX" +'%Y%m%d.%H%M%S')" - GIT_COMMIT="$(git log -1 --pretty='%h')" - GIT_VERSION="git${GIT_DATE}.0.${GIT_COMMIT}" - # GIT_VERSION is now something like 'git20150128.112847.0.17e840a' - PKGVERSION="$PKGVERSION~$GIT_VERSION" -fi - -# $ dpkg --compare-versions 1.5.0 gt 1.5.0~rc1 && echo true || echo false -# true -# $ dpkg --compare-versions 1.5.0~rc1 gt 1.5.0~git20150128.112847.17e840a && echo true || echo false -# true -# $ dpkg --compare-versions 1.5.0~git20150128.112847.17e840a gt 1.5.0~dev~git20150128.112847.17e840a && echo true || echo false -# true - -# ie, 1.5.0 > 1.5.0~rc1 > 1.5.0~git20150128.112847.17e840a > 1.5.0~dev~git20150128.112847.17e840a - -PACKAGE_ARCHITECTURE=`uname -i` - -PACKAGE_URL="http://www.docker.com/" -PACKAGE_MAINTAINER="support@docker.com" -PACKAGE_DESCRIPTION="Linux container runtime -Docker complements LXC with a high-level API which operates at the process -level. It runs unix processes with strong guarantees of isolation and -repeatability across servers. -Docker is a great building block for automating distributed systems: -large-scale web deployments, database clusters, continuous deployment systems, -private PaaS, service-oriented architectures, etc." -PACKAGE_LICENSE="Apache-2.0" - -# bundle the RPM using FPM -- we may want to change this to rpmbuild at some point -bundle_rpm() { - DIR=$DEST/build - - # Include our udev rules - mkdir -p $DIR/etc/udev/rules.d - cp contrib/udev/80-docker.rules $DIR/etc/udev/rules.d/ - - mkdir -p $DIR/usr/lib/systemd/system - cp contrib/init/systemd/docker.{service,socket} $DIR/usr/lib/systemd/system - - cat > $DIR/usr/lib/systemd/system/docker.service <<'EOF' -[Unit] -Description=Docker Application Container Engine -Documentation=http://docs.docker.com -After=network.target docker.socket -Requires=docker.socket - -[Service] -Type=notify -EnvironmentFile=-/etc/sysconfig/docker -EnvironmentFile=-/etc/sysconfig/docker-storage -ExecStart=/usr/bin/docker -d $OPTIONS $DOCKER_STORAGE_OPTIONS -LimitNOFILE=1048576 -LimitNPROC=1048576 -MountFlags=private - -[Install] -WantedBy=multi-user.target -EOF - - mkdir -p $DIR/etc/sysconfig - cat > $DIR/etc/sysconfig/docker <<'EOF' -# /etc/sysconfig/docker - -# Modify these options if you want to change the way the docker daemon runs -OPTIONS=--selinux-enabled -H fd:// - -# Location used for temporary files, such as those created by -# docker load and build operations. Default is /var/lib/docker/tmp -# Can be overriden by setting the following environment variable. -# DOCKER_TMPDIR=/var/tmp -EOF - -cat > $DIR/etc/sysconfig/docker-storage <<'EOF' -# By default, Docker uses a loopback-mounted sparse file in -# /var/lib/docker. The loopback makes it slower, and there are some -# restrictive defaults, such as 100GB max storage. - -# If your installation did not set a custom storage for Docker, you -# may do it below. - -# Example: Use a custom pair of raw logical volumes (one for metadata, -# one for data). -# DOCKER_STORAGE_OPTIONS = --storage-opt dm.metadatadev=/dev/mylogvol/my-docker-metadata --storage-opt dm.datadev=/dev/mylogvol/my-docker-data - -DOCKER_STORAGE_OPTIONS= -EOF - - # 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/ - - # Include contributed man pages - docs/man/md2man-all.sh -q - manRoot="$DIR/usr/share/man" - mkdir -p "$manRoot" - for manDir in docs/man/man?; do - manBase="$(basename "$manDir")" # "man1" - for manFile in "$manDir"/*; do - manName="$(basename "$manFile")" # "docker-build.1" - mkdir -p "$manRoot/$manBase" - gzip -c "$manFile" > "$manRoot/$manBase/$manName.gz" - done - done - - # Copy the binary - # This will fail if the dynbinary bundle hasn't been built - mkdir -p $DIR/usr/bin - cp $DEST/../dynbinary/docker-$VERSION $DIR/usr/bin/docker - cp $DEST/../dynbinary/dockerinit-$VERSION $DIR/usr/bin/dockerinit - - # Generate postinst/prerm/postrm scripts - cat > $DEST/postinst <<'EOF' -EOF - - cat > $DEST/preinst <<'EOF' -if ! getent group docker > /dev/null; then - groupadd --system docker -fi -EOF - - cat > $DEST/prerm <<'EOF' -EOF - - cat > $DEST/postrm <<'EOF' -## In case this system is running systemd, we make systemd reload the unit files -## to pick up changes. -#if [ -d /run/systemd/system ] ; then -# systemctl --system daemon-reload > /dev/null || true -#fi -EOF - - chmod +x $DEST/postinst $DEST/prerm $DEST/postrm $DEST/preinst - - ( - # switch directories so we create *.deb in the right folder - cd $DEST - - # create PACKAGE_NAME-VERSION package - fpm -s dir -C $DIR \ - --name $PACKAGE_NAME-$VERSION --version "$PKGVERSION" \ - --epoch 7 \ - --before-install $DEST/preinst \ - --after-install $DEST/postinst \ - --before-remove $DEST/prerm \ - --after-remove $DEST/postrm \ - --architecture "$PACKAGE_ARCHITECTURE" \ - --prefix / \ - --depends iptables \ - --depends xz \ - --depends "systemd >= 208-20" \ - --depends "device-mapper-libs >= 7:1.02.90-1" \ - --depends "device-mapper-event-libs >= 7:1.02.90-1" \ - --depends libselinux \ - --depends libsepol \ - --depends sqlite \ - --description "$PACKAGE_DESCRIPTION" \ - --maintainer "$PACKAGE_MAINTAINER" \ - --conflicts docker \ - --conflicts docker-io \ - --conflicts lxc-docker-virtual-package \ - --conflicts lxc-docker \ - --url "$PACKAGE_URL" \ - --license "$PACKAGE_LICENSE" \ - --config-files etc/sysconfig \ - --config-files etc/udev/rules.d/80-docker.rules \ - --rpm-compression gzip \ - -t rpm . - ) - - # clean up after ourselves so we have a clean output directory - rm $DEST/postinst $DEST/prerm $DEST/postrm $DEST/preinst - rm -r $DIR -} - -bundle_rpm From 54662eae10923a0aca03ffddc1a30b7d25431c79 Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Tue, 5 May 2015 13:00:20 -0700 Subject: [PATCH 763/999] Fix RUN's error msg when it fails When RUN returns with a non-zero return code it prints the command that was executed as a Go []string: ``` INFO[0000] The command &{[/bin/sh -c noop a1 a2]} returned a non-zero code: 127 ``` instead it should look like this: ``` INFO[0000] The command "/bin/sh -c noop a1 a2" returned a non-zero code: 127 ``` Signed-off-by: Doug Davis --- builder/internals.go | 2 +- integration-cli/docker_cli_build_test.go | 17 +++++++++++++++++ runconfig/config.go | 5 +++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/builder/internals.go b/builder/internals.go index 452180f90..93c8ef802 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -619,7 +619,7 @@ func (b *Builder) run(c *daemon.Container) error { // Wait for it to finish if ret, _ := c.WaitStop(-1 * time.Second); ret != 0 { return &jsonmessage.JSONError{ - Message: fmt.Sprintf("The command %v returned a non-zero code: %d", b.Config.Cmd, ret), + Message: fmt.Sprintf("The command %q returned a non-zero code: %d", b.Config.Cmd.ToString(), ret), Code: ret, } } diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go index 5247a11ec..2656f52d8 100644 --- a/integration-cli/docker_cli_build_test.go +++ b/integration-cli/docker_cli_build_test.go @@ -5384,3 +5384,20 @@ func (s *DockerSuite) TestBuildBadCmdFlag(c *check.C) { c.Fatalf("Bad output\nGot:%s\n\nExpected to contain:%s\n", out, exp) } } + +func (s *DockerSuite) TestBuildRUNErrMsg(c *check.C) { + // Test to make sure the bad command is quoted with just "s and + // not as a Go []string + name := "testbuildbadrunerrmsg" + _, out, err := buildImageWithOut(name, ` + FROM busybox + RUN badEXE a1 a2`, false) + if err == nil { + c.Fatal("Should have failed to build") + } + + exp := `The command \"/bin/sh -c badEXE a1 a2\" returned a non-zero code: 127"` + if !strings.Contains(out, exp) { + c.Fatalf("RUN doesn't have the correct output:\nGot:%s\nExpected:%s", out, exp) + } +} diff --git a/runconfig/config.go b/runconfig/config.go index 844958be2..8778d2612 100644 --- a/runconfig/config.go +++ b/runconfig/config.go @@ -3,6 +3,7 @@ package runconfig import ( "encoding/json" "io" + "strings" "github.com/docker/docker/nat" ) @@ -59,6 +60,10 @@ type Command struct { parts []string } +func (e *Command) ToString() string { + return strings.Join(e.parts, " ") +} + func (e *Command) MarshalJSON() ([]byte, error) { if e == nil { return []byte{}, nil From d2c4ee37c6a4114b33a915b7dae6de70e27e7965 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Wed, 6 May 2015 10:18:01 -0400 Subject: [PATCH 764/999] Fix LXC stop signals `lxc-stop` does not support sending arbitrary signals. By default, `lxc-stop -n ` would send `SIGPWR`. The lxc driver was always sending `lxc-stop -n -k`, which always sends `SIGKILL`. In this case `lxc-start` returns an exit code of `0`, regardless of what the container actually exited with. Because of this we must send signals directly to the process when we can. Also need to set quiet mode on `lxc-start` otherwise it reports an error on `stderr` when the container exits cleanly (ie, we didn't SIGKILL it), this error is picked up in the container logs... and isn't really an error. Also cleaned up some potential races for waitblocked test. Signed-off-by: Brian Goff --- daemon/execdriver/lxc/driver.go | 15 +++++++++---- integration-cli/docker_cli_wait_test.go | 28 ++++++++++++++++++------- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/daemon/execdriver/lxc/driver.go b/daemon/execdriver/lxc/driver.go index 32e5b43eb..49db60874 100644 --- a/daemon/execdriver/lxc/driver.go +++ b/daemon/execdriver/lxc/driver.go @@ -127,6 +127,7 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba "lxc-start", "-n", c.ID, "-f", configPath, + "-q", } // From lxc>=1.1 the default behavior is to daemonize containers after start @@ -278,19 +279,20 @@ func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallba oomKillNotification, err := notifyOnOOM(cgroupPaths) <-waitLock + exitCode := getExitCode(c) if err == nil { _, oomKill = <-oomKillNotification - logrus.Debugf("oomKill error %s waitErr %s", oomKill, waitErr) + logrus.Debugf("oomKill error: %v, waitErr: %v", oomKill, waitErr) } else { logrus.Warnf("Your kernel does not support OOM notifications: %s", err) } // check oom error - exitCode := getExitCode(c) if oomKill { exitCode = 137 } + return execdriver.ExitStatus{ExitCode: exitCode, OOMKilled: oomKill}, waitErr } @@ -468,7 +470,11 @@ func getExitCode(c *execdriver.Command) int { } func (d *driver) Kill(c *execdriver.Command, sig int) error { - return KillLxc(c.ID, sig) + if sig == 9 || c.ProcessConfig.Process == nil { + return KillLxc(c.ID, sig) + } + + return c.ProcessConfig.Process.Signal(syscall.Signal(sig)) } func (d *driver) Pause(c *execdriver.Command) error { @@ -528,7 +534,8 @@ func KillLxc(id string, sig int) error { if err == nil { output, err = exec.Command("lxc-kill", "-n", id, strconv.Itoa(sig)).CombinedOutput() } else { - output, err = exec.Command("lxc-stop", "-k", "-n", id, strconv.Itoa(sig)).CombinedOutput() + // lxc-stop does not take arbitrary signals like lxc-kill does + output, err = exec.Command("lxc-stop", "-k", "-n", id).CombinedOutput() } if err != nil { return fmt.Errorf("Err: %s Output: %s", err, output) diff --git a/integration-cli/docker_cli_wait_test.go b/integration-cli/docker_cli_wait_test.go index 21f04faf0..b7fb3fe95 100644 --- a/integration-cli/docker_cli_wait_test.go +++ b/integration-cli/docker_cli_wait_test.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "os/exec" "strings" "time" @@ -44,7 +45,7 @@ func (s *DockerSuite) TestWaitNonBlockedExitZero(c *check.C) { // blocking wait with 0 exit code func (s *DockerSuite) TestWaitBlockedExitZero(c *check.C) { - out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "trap 'exit 0' SIGTERM; while true; do sleep 0.01; done") + out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "trap 'exit 0' TERM; while true; do sleep 0.01; done") containerID := strings.TrimSpace(out) if err := waitRun(containerID); err != nil { @@ -107,7 +108,7 @@ func (s *DockerSuite) TestWaitNonBlockedExitRandom(c *check.C) { // blocking wait with random exit code func (s *DockerSuite) TestWaitBlockedExitRandom(c *check.C) { - out, _ := dockerCmd(c, "run", "-d", "busybox", "sh", "-c", "trap 'exit 99' SIGTERM; while true; do sleep 0.01; done") + out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "trap 'exit 99' TERM; while true; do sleep 0.01; done") containerID := strings.TrimSpace(out) if err := waitRun(containerID); err != nil { c.Fatal(err) @@ -116,21 +117,34 @@ func (s *DockerSuite) TestWaitBlockedExitRandom(c *check.C) { c.Fatal(err) } - chWait := make(chan string) + chWait := make(chan error) + waitCmd := exec.Command(dockerBinary, "wait", containerID) + waitCmdOut := bytes.NewBuffer(nil) + waitCmd.Stdout = waitCmdOut + if err := waitCmd.Start(); err != nil { + c.Fatal(err) + } + go func() { - out, _, _ := runCommandWithOutput(exec.Command(dockerBinary, "wait", containerID)) - chWait <- out + chWait <- waitCmd.Wait() }() - time.Sleep(100 * time.Millisecond) dockerCmd(c, "stop", containerID) select { - case status := <-chWait: + case err := <-chWait: + if err != nil { + c.Fatal(err) + } + status, err := waitCmdOut.ReadString('\n') + if err != nil { + c.Fatal(err) + } if strings.TrimSpace(status) != "99" { c.Fatalf("expected exit 99, got %s", status) } case <-time.After(2 * time.Second): + waitCmd.Process.Kill() c.Fatal("timeout waiting for `docker wait` to exit") } } From 76bc44fb912c66498b8e6e714ce4aabd9eaef8dc Mon Sep 17 00:00:00 2001 From: Matt Bentley Date: Wed, 6 May 2015 10:37:29 -0400 Subject: [PATCH 765/999] Added nanorc for Dockerfiles Signed-off-by: Matt Bentley --- contrib/syntax/nano/Dockerfile.nanorc | 26 ++++++++++++++++++++++ contrib/syntax/nano/README.md | 32 +++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 contrib/syntax/nano/Dockerfile.nanorc create mode 100644 contrib/syntax/nano/README.md diff --git a/contrib/syntax/nano/Dockerfile.nanorc b/contrib/syntax/nano/Dockerfile.nanorc new file mode 100644 index 000000000..80e56dfb3 --- /dev/null +++ b/contrib/syntax/nano/Dockerfile.nanorc @@ -0,0 +1,26 @@ +## Syntax highlighting for Dockerfiles +syntax "Dockerfile" "Dockerfile[^/]*$" + +## Keywords +icolor red "^(FROM|MAINTAINER|RUN|CMD|LABEL|EXPOSE|ENV|ADD|COPY|ENTRYPOINT|VOLUME|USER|WORKDIR|ONBUILD)[[:space:]]" + +## Brackets & parenthesis +color brightgreen "(\(|\)|\[|\])" + +## Double ampersand +color brightmagenta "&&" + +## Comments +icolor cyan "^[[:space:]]*#.*$" + +## Blank space at EOL +color ,green "[[:space:]]+$" + +## Strings, single-quoted +color brightwhite "'([^']|(\\'))*'" "%[qw]\{[^}]*\}" "%[qw]\([^)]*\)" "%[qw]<[^>]*>" "%[qw]\[[^]]*\]" "%[qw]\$[^$]*\$" "%[qw]\^[^^]*\^" "%[qw]![^!]*!" + +## Strings, double-quoted +color brightwhite ""([^"]|(\\"))*"" "%[QW]?\{[^}]*\}" "%[QW]?\([^)]*\)" "%[QW]?<[^>]*>" "%[QW]?\[[^]]*\]" "%[QW]?\$[^$]*\$" "%[QW]?\^[^^]*\^" "%[QW]?![^!]*!" + +## Single and double quotes +color brightyellow "('|\")" diff --git a/contrib/syntax/nano/README.md b/contrib/syntax/nano/README.md new file mode 100644 index 000000000..5985208b0 --- /dev/null +++ b/contrib/syntax/nano/README.md @@ -0,0 +1,32 @@ +Dockerfile.nanorc +================= + +Dockerfile syntax highlighting for nano + +Single User Installation +------------------------ +1. Create a nano syntax directory in your home directory: + * `mkdir -p ~/.nano/syntax` + +2. Copy `Dockerfile.nanorc` to` ~/.nano/syntax/` + * `cp Dockerfile.nanorc ~/.nano/syntax/` + +3. Add the following to your `~/.nanorc` to tell nano where to find the `Dockerfile.nanorc` file + ``` +## Dockerfile files +include "~/.nano/syntax/Dockerfile.nanorc" + ``` + +System Wide Installation +------------------------ +1. Create a nano syntax directory: + * `mkdir /usr/local/share/nano` + +2. Copy `Dockerfile.nanorc` to `/usr/local/share/nano` + * `cp Dockerfile.nanorc /usr/local/share/nano/` + +3. Add the following to your `/etc/nanorc`: + ``` +## Dockerfile files +include "/usr/local/share/nano/Dockerfile.nanorc" + ``` From 7c574b9e9d62f14c8d73ea358a557a771dcd8d4d Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Wed, 29 Apr 2015 13:48:30 -0400 Subject: [PATCH 766/999] Move ChunkedEncoding from integration Signed-off-by: Brian Goff --- integration-cli/docker_api_containers_test.go | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index f9da6fbcb..e4e3a4e9d 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -6,6 +6,7 @@ import ( "encoding/json" "io" "net/http" + "net/http/httputil" "os" "os/exec" "strings" @@ -1150,3 +1151,54 @@ func (s *DockerSuite) TestContainerApiDeleteRemoveVolume(c *check.C) { c.Fatalf("expected to get ErrNotExist error, got %v", err) } } + +// Regression test for https://github.com/docker/docker/issues/6231 +func (s *DockerSuite) TestContainersApiChunkedEncoding(c *check.C) { + out, _ := dockerCmd(c, "create", "-v", "/foo", "busybox", "true") + id := strings.TrimSpace(out) + + conn, err := sockConn(time.Duration(10 * time.Second)) + if err != nil { + c.Fatal(err) + } + client := httputil.NewClientConn(conn, nil) + defer client.Close() + + bindCfg := strings.NewReader(`{"Binds": ["/tmp:/foo"]}`) + req, err := http.NewRequest("POST", "/containers/"+id+"/start", bindCfg) + if err != nil { + c.Fatal(err) + } + req.Header.Set("Content-Type", "application/json") + // This is a cheat to make the http request do chunked encoding + // Otherwise (just setting the Content-Encoding to chunked) net/http will overwrite + // https://golang.org/src/pkg/net/http/request.go?s=11980:12172 + req.ContentLength = -1 + + resp, err := client.Do(req) + if err != nil { + c.Fatalf("error starting container with chunked encoding: %v", err) + } + resp.Body.Close() + if resp.StatusCode != 204 { + c.Fatalf("expected status code 204, got %d", resp.StatusCode) + } + + out, err = inspectFieldJSON(id, "HostConfig.Binds") + if err != nil { + c.Fatal(err) + } + + var binds []string + if err := json.NewDecoder(strings.NewReader(out)).Decode(&binds); err != nil { + c.Fatal(err) + } + if len(binds) != 1 { + c.Fatalf("got unexpected binds: %v", binds) + } + + expected := "/tmp:/foo" + if binds[0] != expected { + c.Fatalf("got incorrect bind spec, wanted %s, got: %s", expected, binds[0]) + } +} From ca8fa6e46d9eb93e652e4045e418e41d635c6b51 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 4 May 2015 13:58:21 -0600 Subject: [PATCH 767/999] Allow download-frozen-image.sh to work on user images too To account for "/" not working in filenames, we replace it with "_" for our temporary files (that exist only to emulate Bash 4's associative arrays in Bash 3). Signed-off-by: Andrew "Tianon" Page --- contrib/download-frozen-image.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/contrib/download-frozen-image.sh b/contrib/download-frozen-image.sh index 8a2bb5012..29d7ff59f 100755 --- a/contrib/download-frozen-image.sh +++ b/contrib/download-frozen-image.sh @@ -42,6 +42,8 @@ while [ $# -gt 0 ]; do [ "$tag" != "$imageTag" ] || tag='latest' tag="${tag%@*}" + imageFile="${image//\//_}" # "/" can't be in filenames :) + token="$(curl -sSL -o /dev/null -D- -H 'X-Docker-Token: true' "https://index.docker.io/v1/repositories/$image/images" | tr -d '\r' | awk -F ': *' '$1 == "X-Docker-Token" { print $2 }')" if [ -z "$imageId" ]; then @@ -60,12 +62,12 @@ while [ $# -gt 0 ]; do ancestry=( ${ancestryJson//[\[\] \"]/} ) unset IFS - if [ -s "$dir/tags-$image.tmp" ]; then - echo -n ', ' >> "$dir/tags-$image.tmp" + if [ -s "$dir/tags-$imageFile.tmp" ]; then + echo -n ', ' >> "$dir/tags-$imageFile.tmp" else images=( "${images[@]}" "$image" ) fi - echo -n '"'"$tag"'": "'"$imageId"'"' >> "$dir/tags-$image.tmp" + echo -n '"'"$tag"'": "'"$imageId"'"' >> "$dir/tags-$imageFile.tmp" echo "Downloading '$imageTag' (${#ancestry[@]} layers)..." for imageId in "${ancestry[@]}"; do @@ -90,10 +92,12 @@ done echo -n '{' > "$dir/repositories" firstImage=1 for image in "${images[@]}"; do + imageFile="${image//\//_}" # "/" can't be in filenames :) + [ "$firstImage" ] || echo -n ',' >> "$dir/repositories" firstImage= echo -n $'\n\t' >> "$dir/repositories" - echo -n '"'"$image"'": { '"$(cat "$dir/tags-$image.tmp")"' }' >> "$dir/repositories" + echo -n '"'"$image"'": { '"$(cat "$dir/tags-$imageFile.tmp")"' }' >> "$dir/repositories" done echo -n $'\n}\n' >> "$dir/repositories" From 72a0272a62f881fefb8dea5986b865fea36113ab Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Wed, 6 May 2015 20:56:12 +0200 Subject: [PATCH 768/999] Fix typo in the api remote reference for links Signed-off-by: Antonio Murdaca --- docs/sources/reference/api/docker_remote_api_v1.15.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.16.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.17.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.18.md | 4 ++-- docs/sources/reference/api/docker_remote_api_v1.19.md | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/sources/reference/api/docker_remote_api_v1.15.md b/docs/sources/reference/api/docker_remote_api_v1.15.md index 8fcf8cb18..e4fe5074d 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.15.md +++ b/docs/sources/reference/api/docker_remote_api_v1.15.md @@ -207,8 +207,8 @@ Json Parameters: volume for the container), `host_path:container_path` (to bind-mount a host path into the container), or `host_path:container_path:ro` (to make the bind-mount read-only inside the container). - - **Links** - A list of links for the container. Each link entry should be of - of the form "container_name:alias". + - **Links** - A list of links for the container. Each link entry should be + in the form of "container_name:alias". - **LxcConf** - LXC specific configurations. These configurations will only work when using the `lxc` execution driver. - **PortBindings** - A map of exposed container ports and the host port they diff --git a/docs/sources/reference/api/docker_remote_api_v1.16.md b/docs/sources/reference/api/docker_remote_api_v1.16.md index 9c6159b9d..df8e5be13 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.16.md +++ b/docs/sources/reference/api/docker_remote_api_v1.16.md @@ -207,8 +207,8 @@ Json Parameters: volume for the container), `host_path:container_path` (to bind-mount a host path into the container), or `host_path:container_path:ro` (to make the bind-mount read-only inside the container). - - **Links** - A list of links for the container. Each link entry should be of - of the form "container_name:alias". + - **Links** - A list of links for the container. Each link entry should be + in the form of "container_name:alias". - **LxcConf** - LXC specific configurations. These configurations will only work when using the `lxc` execution driver. - **PortBindings** - A map of exposed container ports and the host port they diff --git a/docs/sources/reference/api/docker_remote_api_v1.17.md b/docs/sources/reference/api/docker_remote_api_v1.17.md index 80f4fccf0..d8ef81c0f 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.17.md +++ b/docs/sources/reference/api/docker_remote_api_v1.17.md @@ -207,8 +207,8 @@ Json Parameters: volume for the container), `host_path:container_path` (to bind-mount a host path into the container), or `host_path:container_path:ro` (to make the bind-mount read-only inside the container). - - **Links** - A list of links for the container. Each link entry should be of - of the form "container_name:alias". + - **Links** - A list of links for the container. Each link entry should be + in the form of "container_name:alias". - **LxcConf** - LXC specific configurations. These configurations will only work when using the `lxc` execution driver. - **PortBindings** - A map of exposed container ports and the host port they diff --git a/docs/sources/reference/api/docker_remote_api_v1.18.md b/docs/sources/reference/api/docker_remote_api_v1.18.md index a91ca8417..71ab41769 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.18.md +++ b/docs/sources/reference/api/docker_remote_api_v1.18.md @@ -218,8 +218,8 @@ Json Parameters: volume for the container), `host_path:container_path` (to bind-mount a host path into the container), or `host_path:container_path:ro` (to make the bind-mount read-only inside the container). - - **Links** - A list of links for the container. Each link entry should be of - of the form `container_name:alias`. + - **Links** - A list of links for the container. Each link entry should be + in the form of `container_name:alias`. - **LxcConf** - LXC specific configurations. These configurations will only work when using the `lxc` execution driver. - **PortBindings** - A map of exposed container ports and the host port they diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 1a321fe73..f2f71245e 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -222,8 +222,8 @@ Json Parameters: volume for the container), `host_path:container_path` (to bind-mount a host path into the container), or `host_path:container_path:ro` (to make the bind-mount read-only inside the container). - - **Links** - A list of links for the container. Each link entry should be of - of the form `container_name:alias`. + - **Links** - A list of links for the container. Each link entry should be + in the form of `container_name:alias`. - **LxcConf** - LXC specific configurations. These configurations will only work when using the `lxc` execution driver. - **PortBindings** - A map of exposed container ports and the host port they From 589de35651ce8c91a2f01be2a5c99274d548d9ae Mon Sep 17 00:00:00 2001 From: Anthony Baire Date: Fri, 24 Apr 2015 00:08:41 +0200 Subject: [PATCH 769/999] Logs with follow=1 immediately send HTTP response Signed-off-by: Anthony Baire Signed-off-by: Arnaud Porterie --- daemon/logs.go | 5 +++++ integration-cli/docker_api_logs_test.go | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/daemon/logs.go b/daemon/logs.go index 79d4044bb..ae3e99731 100644 --- a/daemon/logs.go +++ b/daemon/logs.go @@ -129,6 +129,11 @@ func (daemon *Daemon) ContainerLogs(name string, config *ContainerLogsConfig) er errors := make(chan error, 2) wg := sync.WaitGroup{} + // write an empty chunk of data (this is to ensure that the + // HTTP Response is sent immediatly, even if the container has + // not yet produced any data) + outStream.Write(nil) + if config.UseStdout { wg.Add(1) stdoutPipe := container.StdoutLogPipe() diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go index f9284494d..d77ddef30 100644 --- a/integration-cli/docker_api_logs_test.go +++ b/integration-cli/docker_api_logs_test.go @@ -60,3 +60,25 @@ func (s *DockerSuite) TestLogsApiNoStdoutNorStderr(c *check.C) { c.Fatalf("Expected %s, got %s", expected, string(body[:])) } } + +// Regression test for #12704 +func (s *DockerSuite) TestLogsApiFollowEmptyOutput(c *check.C) { + defer deleteAllContainers() + name := "logs_test" + t0 := time.Now() + runCmd := exec.Command(dockerBinary, "run", "-d", "-t", "--name", name, "busybox", "sleep", "10") + if out, _, err := runCommandWithOutput(runCmd); err != nil { + c.Fatal(out, err) + } + + _, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/logs?follow=1&stdout=1&stderr=1&tail=all", name), bytes.NewBuffer(nil), "") + t1 := time.Now() + body.Close() + if err != nil { + c.Fatal(err) + } + elapsed := t1.Sub(t0).Seconds() + if elapsed > 5.0 { + c.Fatalf("HTTP response was not immediate (elapsed %.1fs)", elapsed) + } +} From e35b025aa61e7d8db04a9973967b7109f742593a Mon Sep 17 00:00:00 2001 From: jhowardmsft Date: Fri, 24 Apr 2015 11:12:56 -0700 Subject: [PATCH 770/999] Windows: Split ContainerExecCreate Signed-off-by: John Howard --- daemon/exec.go | 6 +++--- daemon/exec_linux.go | 18 ++++++++++++++++++ daemon/exec_windows.go | 9 +++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 daemon/exec_linux.go create mode 100644 daemon/exec_windows.go diff --git a/daemon/exec.go b/daemon/exec.go index 5febf083a..fc4e8eab4 100644 --- a/daemon/exec.go +++ b/daemon/exec.go @@ -9,7 +9,6 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/daemon/execdriver" - "github.com/docker/docker/daemon/execdriver/lxc" "github.com/docker/docker/pkg/broadcastwriter" "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/stringid" @@ -111,8 +110,9 @@ func (d *Daemon) getActiveContainer(name string) (*Container, error) { func (d *Daemon) ContainerExecCreate(config *runconfig.ExecConfig) (string, error) { - if strings.HasPrefix(d.execDriver.Name(), lxc.DriverName) { - return "", lxc.ErrExec + // Not all drivers support Exec (LXC for example) + if err := checkExecSupport(d.execDriver.Name()); err != nil { + return "", err } container, err := d.getActiveContainer(config.Container) diff --git a/daemon/exec_linux.go b/daemon/exec_linux.go new file mode 100644 index 000000000..a36032632 --- /dev/null +++ b/daemon/exec_linux.go @@ -0,0 +1,18 @@ +// +build linux + +package daemon + +import ( + "strings" + + "github.com/docker/docker/daemon/execdriver/lxc" +) + +// checkExecSupport returns an error if the exec driver does not support exec, +// or nil if it is supported. +func checkExecSupport(drivername string) error { + if strings.HasPrefix(drivername, lxc.DriverName) { + return lxc.ErrExec + } + return nil +} diff --git a/daemon/exec_windows.go b/daemon/exec_windows.go new file mode 100644 index 000000000..d6f244e6d --- /dev/null +++ b/daemon/exec_windows.go @@ -0,0 +1,9 @@ +// +build windows + +package daemon + +// checkExecSupport returns an error if the exec driver does not support exec, +// or nil if it is supported. +func checkExecSupport(DriverName string) error { + return nil +} From 596e91638cbe8b583dff4fdfc5a52e9d3840b20f Mon Sep 17 00:00:00 2001 From: Arnaud Porterie Date: Wed, 6 May 2015 14:35:10 -0700 Subject: [PATCH 771/999] Remove unused Dockerfile instruction INSERT Signed-off-by: Arnaud Porterie --- builder/command/command.go | 2 -- builder/dispatchers.go | 5 ----- builder/evaluator.go | 1 - builder/parser/parser.go | 1 - 4 files changed, 9 deletions(-) diff --git a/builder/command/command.go b/builder/command/command.go index 16544f026..8e5d98032 100644 --- a/builder/command/command.go +++ b/builder/command/command.go @@ -16,7 +16,6 @@ const ( Expose = "expose" Volume = "volume" User = "user" - Insert = "insert" ) // Commands is list of all Dockerfile commands @@ -35,5 +34,4 @@ var Commands = map[string]struct{}{ Expose: {}, Volume: {}, User: {}, - Insert: {}, } diff --git a/builder/dispatchers.go b/builder/dispatchers.go index 7d1dab640..4d4c12396 100644 --- a/builder/dispatchers.go +++ b/builder/dispatchers.go @@ -512,8 +512,3 @@ func volume(b *Builder, args []string, attributes map[string]bool, original stri } return nil } - -// INSERT is no longer accepted, but we still parse it. -func insert(b *Builder, args []string, attributes map[string]bool, original string) error { - return fmt.Errorf("INSERT has been deprecated. Please use ADD instead") -} diff --git a/builder/evaluator.go b/builder/evaluator.go index bdcc6b29a..49e4a7648 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -71,7 +71,6 @@ func init() { command.Expose: expose, command.Volume: volume, command.User: user, - command.Insert: insert, } } diff --git a/builder/parser/parser.go b/builder/parser/parser.go index f68c710c0..2260cd527 100644 --- a/builder/parser/parser.go +++ b/builder/parser/parser.go @@ -61,7 +61,6 @@ func init() { command.Entrypoint: parseMaybeJSON, command.Expose: parseStringsWhitespaceDelimited, command.Volume: parseMaybeJSONToList, - command.Insert: parseIgnore, } } From 74121a42118750e560e772a3fee33e9d7bd903d0 Mon Sep 17 00:00:00 2001 From: Antonio Murdaca Date: Thu, 7 May 2015 01:49:16 +0200 Subject: [PATCH 772/999] Do not check and return strconv.Atoi error in api container restart, regression Signed-off-by: Antonio Murdaca --- api/server/server.go | 5 +---- integration-cli/docker_api_containers_test.go | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/api/server/server.go b/api/server/server.go index ab236f876..e1fcc74e8 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -917,10 +917,7 @@ func (s *Server) postContainersRestart(version version.Version, w http.ResponseW return fmt.Errorf("Missing parameter") } - timeout, err := strconv.Atoi(r.Form.Get("t")) - if err != nil { - return err - } + timeout, _ := strconv.Atoi(r.Form.Get("t")) if err := s.daemon.ContainerRestart(vars["name"], timeout); err != nil { return err diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go index e4e3a4e9d..11956108a 100644 --- a/integration-cli/docker_api_containers_test.go +++ b/integration-cli/docker_api_containers_test.go @@ -899,6 +899,25 @@ func (s *DockerSuite) TestContainerApiRestart(c *check.C) { } } +func (s *DockerSuite) TestContainerApiRestartNotimeoutParam(c *check.C) { + name := "test-api-restart-no-timeout-param" + runCmd := exec.Command(dockerBinary, "run", "-di", "--name", name, "busybox", "top") + out, _, err := runCommandWithOutput(runCmd) + if err != nil { + c.Fatalf("Error on container creation: %v, output: %q", err, out) + } + id := strings.TrimSpace(out) + c.Assert(waitRun(id), check.IsNil) + + status, _, err := sockRequest("POST", "/containers/"+name+"/restart", nil) + c.Assert(status, check.Equals, http.StatusNoContent) + c.Assert(err, check.IsNil) + + if err := waitInspect(name, "{{ .State.Restarting }} {{ .State.Running }}", "false true", 5); err != nil { + c.Fatal(err) + } +} + func (s *DockerSuite) TestContainerApiStart(c *check.C) { name := "testing-start" config := map[string]interface{}{ From 867eed8f3586c81b32dc9f85208692e9e1c9909a Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 6 May 2015 17:39:10 -0600 Subject: [PATCH 773/999] Fix build-deb This fixes the part of #12996 that I forgot. :angel: This also fixes a minor path issue (there's no `libexec` in Debian), and fixes a minor bug with the `debVersion` parsing. Signed-off-by: Andrew "Tianon" Page --- ...ker-core.bash-completion => docker-engine.bash-completion} | 0 ...ocker-core.docker.default => docker-engine.docker.default} | 0 .../{docker-core.docker.init => docker-engine.docker.init} | 0 ...ocker-core.docker.upstart => docker-engine.docker.upstart} | 0 .../.build-deb/{docker-core.install => docker-engine.install} | 0 .../{docker-core.manpages => docker-engine.manpages} | 0 .../{docker-core.postinst => docker-engine.postinst} | 0 hack/make/.build-deb/{docker-core.udev => docker-engine.udev} | 0 hack/make/.build-deb/rules | 4 ++-- hack/make/build-deb | 2 +- 10 files changed, 3 insertions(+), 3 deletions(-) rename hack/make/.build-deb/{docker-core.bash-completion => docker-engine.bash-completion} (100%) rename hack/make/.build-deb/{docker-core.docker.default => docker-engine.docker.default} (100%) rename hack/make/.build-deb/{docker-core.docker.init => docker-engine.docker.init} (100%) rename hack/make/.build-deb/{docker-core.docker.upstart => docker-engine.docker.upstart} (100%) rename hack/make/.build-deb/{docker-core.install => docker-engine.install} (100%) rename hack/make/.build-deb/{docker-core.manpages => docker-engine.manpages} (100%) rename hack/make/.build-deb/{docker-core.postinst => docker-engine.postinst} (100%) rename hack/make/.build-deb/{docker-core.udev => docker-engine.udev} (100%) diff --git a/hack/make/.build-deb/docker-core.bash-completion b/hack/make/.build-deb/docker-engine.bash-completion similarity index 100% rename from hack/make/.build-deb/docker-core.bash-completion rename to hack/make/.build-deb/docker-engine.bash-completion diff --git a/hack/make/.build-deb/docker-core.docker.default b/hack/make/.build-deb/docker-engine.docker.default similarity index 100% rename from hack/make/.build-deb/docker-core.docker.default rename to hack/make/.build-deb/docker-engine.docker.default diff --git a/hack/make/.build-deb/docker-core.docker.init b/hack/make/.build-deb/docker-engine.docker.init similarity index 100% rename from hack/make/.build-deb/docker-core.docker.init rename to hack/make/.build-deb/docker-engine.docker.init diff --git a/hack/make/.build-deb/docker-core.docker.upstart b/hack/make/.build-deb/docker-engine.docker.upstart similarity index 100% rename from hack/make/.build-deb/docker-core.docker.upstart rename to hack/make/.build-deb/docker-engine.docker.upstart diff --git a/hack/make/.build-deb/docker-core.install b/hack/make/.build-deb/docker-engine.install similarity index 100% rename from hack/make/.build-deb/docker-core.install rename to hack/make/.build-deb/docker-engine.install diff --git a/hack/make/.build-deb/docker-core.manpages b/hack/make/.build-deb/docker-engine.manpages similarity index 100% rename from hack/make/.build-deb/docker-core.manpages rename to hack/make/.build-deb/docker-engine.manpages diff --git a/hack/make/.build-deb/docker-core.postinst b/hack/make/.build-deb/docker-engine.postinst similarity index 100% rename from hack/make/.build-deb/docker-core.postinst rename to hack/make/.build-deb/docker-engine.postinst diff --git a/hack/make/.build-deb/docker-core.udev b/hack/make/.build-deb/docker-engine.udev similarity index 100% rename from hack/make/.build-deb/docker-core.udev rename to hack/make/.build-deb/docker-engine.udev diff --git a/hack/make/.build-deb/rules b/hack/make/.build-deb/rules index 22dab31d1..fe19b729e 100755 --- a/hack/make/.build-deb/rules +++ b/hack/make/.build-deb/rules @@ -21,8 +21,8 @@ override_dh_strip: override_dh_auto_install: mkdir -p debian/docker-engine/usr/bin cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/docker)" debian/docker-engine/usr/bin/docker - mkdir -p debian/docker-engine/usr/libexec/docker - cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/dockerinit)" debian/docker-engine/usr/libexec/docker/dockerinit + mkdir -p debian/docker-engine/usr/lib/docker + cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/dockerinit)" debian/docker-engine/usr/lib/docker/dockerinit override_dh_installinit: # use "docker" as our service name, not "docker-engine" diff --git a/hack/make/build-deb b/hack/make/build-deb index 90a2b98b8..a347f0e85 100644 --- a/hack/make/build-deb +++ b/hack/make/build-deb @@ -9,7 +9,7 @@ DEST=$1 # TODO consider using frozen images for the dockercore/builder-deb tags - debVersion="${VERSION//-/'~'}" + debVersion="${VERSION//-/~}" # if we have a "-dev" suffix or have change in Git, let's make this package version more complex so it works better if [[ "$VERSION" == *-dev ]] || [ -n "$(git status --porcelain)" ]; then gitUnix="$(git log -1 --pretty='%at')" From 9da3a848abea56dff960baa8428dc073b70ec1de Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 4 May 2015 05:25:47 +0000 Subject: [PATCH 774/999] Add a userguide to cover the uses of Hub before creating new repositories Signed-off-by: Sven Dowideit --- docs/mkdocs.yml | 3 +- .../docker-hub/hub-images/dashboard.png | Bin 0 -> 140981 bytes docs/sources/docker-hub/hub-images/groups.png | Bin 61631 -> 70648 bytes docs/sources/docker-hub/hub-images/hub.png | Bin 27837 -> 68579 bytes docs/sources/docker-hub/hub-images/invite.png | Bin 41551 -> 40459 bytes docs/sources/docker-hub/hub-images/orgs.png | Bin 36206 -> 45997 bytes docs/sources/docker-hub/hub-images/repos.png | Bin 33179 -> 63242 bytes docs/sources/docker-hub/index.md | 21 +++-- docs/sources/docker-hub/repos.md | 88 ++++++++---------- docs/sources/docker-hub/userguide.md | 57 ++++++++++++ 10 files changed, 113 insertions(+), 56 deletions(-) create mode 100644 docs/sources/docker-hub/hub-images/dashboard.png create mode 100644 docs/sources/docker-hub/userguide.md diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 7438af582..1fc19dc96 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -75,7 +75,8 @@ pages: # Docker Hub docs: - ['docker-hub/index.md', 'Docker Hub', 'Docker Hub' ] - ['docker-hub/accounts.md', 'Docker Hub', 'Accounts'] -- ['docker-hub/repos.md', 'Docker Hub', 'Repositories'] +- ['docker-hub/userguide.md', 'Docker Hub', 'User Guide'] +- ['docker-hub/repos.md', 'Docker Hub', 'Your Repositories'] - ['docker-hub/builds.md', 'Docker Hub', 'Automated Builds'] - ['docker-hub/official_repos.md', 'Docker Hub', 'Official Repositories'] diff --git a/docs/sources/docker-hub/hub-images/dashboard.png b/docs/sources/docker-hub/hub-images/dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..594c5d14571d4a61a7380f27a8227ce00b5b12ca GIT binary patch literal 140981 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
    u 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 diff --git a/docs/sources/docker-hub/index.md b/docs/sources/docker-hub/index.md index 3651497e2..db6694d3d 100644 --- a/docs/sources/docker-hub/index.md +++ b/docs/sources/docker-hub/index.md @@ -4,20 +4,29 @@ page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub # Docker Hub +The [Docker Hub](https://hub.docker.com) provides a cloud-based platform service +for distributed applications, including container image distribution and change +management, user and team collaboration, and lifecycle workflow automation. + ![DockerHub](/docker-hub/hub-images/hub.png) -## [Accounts](accounts/) +## [Finding and pulling images](./userguide.md) -[Learn how to create](accounts/) a [Docker Hub](https://hub.docker.com) +Find out how to [use the Docker Hub](./userguide.md) to find and pull Docker +images to run or build upon. + +## [Accounts](./accounts.md) + +[Learn how to create](./accounts.md) a Docker Hub account and manage your organizations and groups. -## [Repositories](repos/) +## [Your Repositories](./repos.md) Find out how to share your Docker images in [Docker Hub -repositories](repos/) and how to store and manage private images. +repositories](./repos.md) and how to store and manage private images. -## [Automated builds](builds/) +## [Automated builds](./builds.md) Learn how to automate your build and deploy pipeline with [Automated -Builds](builds/) +Builds](./builds.md) diff --git a/docs/sources/docker-hub/repos.md b/docs/sources/docker-hub/repos.md index a48040fb5..67cf0431e 100644 --- a/docs/sources/docker-hub/repos.md +++ b/docs/sources/docker-hub/repos.md @@ -1,42 +1,37 @@ -page_title: Repositories and images on Docker Hub -page_description: Repositories and images on Docker Hub +page_title: Your Repositories on Docker Hub +page_description: Your Repositories on Docker Hub page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, webhooks, docs, documentation -# Repositories and images on Docker Hub +# Your Hub repositories + +Docker Hub repositories make it possible for you to share images with co-workers, +customers or the Docker community at large. If you're building your images internally, +either on your own Docker daemon, or using your own Continuous integration services, +you can push them to a Docker Hub repository that you add to your Docker Hub user or +organization account. + +Alternativly, if the source code for your Docker image is on GitHub or Bitbucket, +you can use an "Automated build" repository, which is built by the Docker Hub +services. See the [automated builds documentation](./builds.md) to read about +the extra functionality provided by those services. ![repositories](/docker-hub/hub-images/repos.png) -## Searching for repositories and images - -You can `search` for all the publicly available repositories and images using -Docker. - - $ docker search ubuntu - -This will show you a list of the currently available repositories on the -Docker Hub which match the provided keyword. - -If a repository is private it won't be listed on the repository search -results. To see repository statuses, you can look at your [profile -page](https://hub.docker.com) on [Docker Hub](https://hub.docker.com). - -## Repositories - Your Docker Hub repositories have a number of useful features. -### Stars +## Stars Your repositories can be starred and you can star repositories in return. Stars are a way to show that you like a repository. They are also an easy way of bookmarking your favorites. -### Comments +## Comments You can interact with other members of the Docker community and maintainers by leaving comments on repositories. If you find any comments that are not appropriate, you can flag them for review. -### Collaborators and their role +## Collaborators and their role A collaborator is someone you want to give access to a private repository. Once designated, they can `push` and `pull` to your @@ -48,24 +43,9 @@ private to public. > A collaborator cannot add other collaborators. Only the owner of > the repository has administrative access. -You can also collaborate on Docker Hub with organizations and groups. -You can read more about that [here](accounts/). - -## Official Repositories - -The Docker Hub contains a number of [Official -Repositories](http://registry.hub.docker.com/official). These are -certified repositories from vendors and contributors to Docker. They -contain Docker images from vendors like Canonical, Oracle, and Red Hat -that you can use to build applications and services. - -If you use Official Repositories you know you're using a supported, -optimized and up-to-date image to power your applications. - -> **Note:** -> If you would like to contribute an Official Repository for your -> organization, see [Official Repositories on Docker -> Hub](/docker-hub/official_repos) for more information. +You can also assign more granular collaborator rights ("Read", "Write", or "Admin") +on Docker Hub by using organizations and groups. For more information +see the [accounts documentation](accounts/). ## Private repositories @@ -100,8 +80,15 @@ Hub](https://registry.hub.docker.com/plans/) plan. ## Webhooks -You can configure webhooks for your repositories on the Repository -Settings page. A webhook is called only after a successful `push` is +A webhook is an HTTP call-back triggered by a specific event. +You can use a Hub repository webhook to notify people, services, and other +applications after a new image is pushed to your repository (this also happens +for Automated builds). For example, you can trigger an automated test or +deployment to happen as soon as the image is available. + +To get started adding webhooks, go to the desired repository in the Hub, +and click "Webhooks" under the "Settings" box. +A webhook is called only after a successful `push` is made. The webhook calls are HTTP POST requests with a JSON payload similar to the example shown below. @@ -137,13 +124,9 @@ similar to the example shown below. } ``` -Webhooks allow you to notify people, services and other applications of -new updates to your images and repositories. To get started adding webhooks, -go to the desired repository in the Hub, and click "Webhooks" under the "Settings" -box. + -> **Note:** For testing, you can try an HTTP request tool like -> [requestb.in](http://requestb.in/). +For testing, you can try an HTTP request tool like [requestb.in](http://requestb.in/). > **Note**: The Docker Hub servers are currently in the IP range > `162.242.195.64 - 162.242.195.127`, so you can restrict your webhooks to @@ -161,7 +144,7 @@ in your chain. The first webhook in a chain will be called after a successful push. Subsequent URLs will be contacted after the callback has been validated. -#### Validating a callback +### Validating a callback In order to validate a callback in a webhook chain, you need to @@ -195,3 +178,10 @@ The following parameters are recognized in callback data: "context": "Continuous integration by Acme CI", "target_url": "http://ci.acme.com/results/afd339c1c3d27" } + +## Mark as unlisted + +By marking a repository as unlisted, you can create a publically pullable repository +which will not be in the Hub or commandline search. This allows you to have a limited +release, but does not restrict access to anyone that is told, or guesses the repository +name. diff --git a/docs/sources/docker-hub/userguide.md b/docs/sources/docker-hub/userguide.md new file mode 100644 index 000000000..7ace5f358 --- /dev/null +++ b/docs/sources/docker-hub/userguide.md @@ -0,0 +1,57 @@ +page_title: Docker Hub user guide +page_description: Docker Hub user guide +page_keywords: Docker, docker, registry, Docker Hub, docs, documentation + +# Using the Docker Hub + +Docker Hub is used to find and pull Docker images to run or build upon, and to +distribute and build images for other users to use. + +![your profile](/docker-hub/hub-images/dashboard.png) + +## Finding repositories and images + +There are two ways you can search for public repositories and images available +on the Docker Hub. You can use the "Search" tool on the Docker Hub website, or +you can `search` for all the repositories and images using the Docker commandline +tool: + + $ docker search ubuntu + +Both will show you a list of the currently available public repositories on the +Docker Hub which match the provided keyword. + +If a repository is private or marked as unlisted, it won't be in the repository +search results. To see all the repositories you have access to and their statuses, +you can look at your profile page on [Docker Hub](https://hub.docker.com). + +## Pulling, running and building images + +You can find more information on [working with Docker images](../userguide/dockerimages.md). + +## Official Repositories + +The Docker Hub contains a number of [Official +Repositories](http://registry.hub.docker.com/official). These are +certified repositories from vendors and contributors to Docker. They +contain Docker images from vendors like Canonical, Oracle, and Red Hat +that you can use to build applications and services. + +If you use Official Repositories you know you're using an optimized and +up-to-date image to power your applications. + +> **Note:** +> If you would like to contribute an Official Repository for your +> organization, see [Official Repositories on Docker +> Hub](/docker-hub/official_repos) for more information. + +## Building and shipping your own repositories and images + +The Docker Hub provides you and your team with a place to build and ship Docker images. + +Collections of Docker images are managed using repositories - + +You can configure two types of repositories to manage on the Docker Hub: +[Repositories](./repos.md), which allow you to push images to the Hub from your local Docker daemon, +and [Automated Builds](./builds.md), which allow you to configure GitHub or Bitbucket to +trigger the Hub to rebuild repositories when changes are made to the repository. From ceae5f54b3abe805b3323476dafb00595b064ed2 Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Thu, 7 May 2015 10:11:02 +0800 Subject: [PATCH 775/999] update recommended kernel in checkKernel We already changed docs: https://github.com/docker/docker/pull/10652 Should change code as well. Signed-off-by: Qiang Huang --- daemon/daemon.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 130fcc46f..0f692c7c6 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1139,14 +1139,14 @@ func checkKernel() error { // test for specific functionalities. // Unfortunately we can't test for the feature "does not cause a kernel panic" // without actually causing a kernel panic, so we need this workaround until - // the circumstances of pre-3.8 crashes are clearer. + // the circumstances of pre-3.10 crashes are clearer. // For details see https://github.com/docker/docker/issues/407 if k, err := kernel.GetKernelVersion(); err != nil { logrus.Warnf("%s", err) } else { - if kernel.CompareKernelVersion(k, &kernel.KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0}) < 0 { + if kernel.CompareKernelVersion(k, &kernel.KernelVersionInfo{Kernel: 3, Major: 10, Minor: 0}) < 0 { if os.Getenv("DOCKER_NOWARN_KERNEL_VERSION") == "" { - logrus.Warnf("You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.8.0.", k.String()) + logrus.Warnf("You are running linux kernel version %s, which might be unstable running docker. Please upgrade your kernel to 3.10.0.", k.String()) } } } From 44cd599e29451647492b3a5341ba23252a69ca27 Mon Sep 17 00:00:00 2001 From: Brian Goff Date: Mon, 4 May 2015 13:49:28 -0400 Subject: [PATCH 776/999] Cleanup container reg for lxc special case The lxc code here is doing the exact same thing on calling execdriver.Terminate, so let's just use that. Also removes some dead comments originally introduced 50144aeb42283848db730b936d6b5b6332ec6565 but no longer relevant since we have restart policies. Signed-off-by: Brian Goff --- daemon/daemon.go | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/daemon/daemon.go b/daemon/daemon.go index 130fcc46f..26dbd50e3 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -22,7 +22,6 @@ import ( "github.com/docker/docker/daemon/events" "github.com/docker/docker/daemon/execdriver" "github.com/docker/docker/daemon/execdriver/execdrivers" - "github.com/docker/docker/daemon/execdriver/lxc" "github.com/docker/docker/daemon/graphdriver" _ "github.com/docker/docker/daemon/graphdriver/vfs" "github.com/docker/docker/daemon/network" @@ -208,25 +207,16 @@ func (daemon *Daemon) register(container *Container, updateSuffixarray bool) err container.registerVolumes() - // FIXME: if the container is supposed to be running but is not, auto restart it? - // if so, then we need to restart monitor and init a new lock - // If the container is supposed to be running, make sure of it if container.IsRunning() { logrus.Debugf("killing old running container %s", container.ID) container.SetStopped(&execdriver.ExitStatus{ExitCode: 0}) - // We only have to handle this for lxc because the other drivers will ensure that - // no processes are left when docker dies - if container.ExecDriver == "" || strings.Contains(container.ExecDriver, "lxc") { - lxc.KillLxc(container.ID, 9) - } else { - // use the current driver and ensure that the container is dead x.x - cmd := &execdriver.Command{ - ID: container.ID, - } - daemon.execDriver.Terminate(cmd) + // use the current driver and ensure that the container is dead x.x + cmd := &execdriver.Command{ + ID: container.ID, } + daemon.execDriver.Terminate(cmd) if err := container.Unmount(); err != nil { logrus.Debugf("unmount error %s", err) From f133f11a7d25e6262558dd733afaa95ddd1c7aee Mon Sep 17 00:00:00 2001 From: Qiang Huang Date: Thu, 7 May 2015 11:55:58 +0800 Subject: [PATCH 777/999] add blkio.weight support We can use this to control block IO weight of a container. Signed-off-by: Qiang Huang --- daemon/container.go | 1 + daemon/daemon.go | 3 +++ daemon/execdriver/driver.go | 1 + daemon/execdriver/driver_linux.go | 1 + daemon/execdriver/lxc/lxc_template.go | 3 +++ docs/man/docker-create.1.md | 4 +++ docs/man/docker-run.1.md | 4 +++ .../reference/api/docker_remote_api_v1.19.md | 3 +++ docs/sources/reference/commandline/cli.md | 2 ++ docs/sources/reference/run.md | 25 +++++++++++++++++++ integration-cli/docker_cli_run_test.go | 14 +++++++++++ runconfig/hostconfig.go | 3 ++- runconfig/parse.go | 2 ++ 13 files changed, 65 insertions(+), 1 deletion(-) diff --git a/daemon/container.go b/daemon/container.go index 5c7d3a4e5..b1e306086 100644 --- a/daemon/container.go +++ b/daemon/container.go @@ -383,6 +383,7 @@ func populateCommand(c *Container, env []string) error { CpusetCpus: c.hostConfig.CpusetCpus, CpusetMems: c.hostConfig.CpusetMems, CpuQuota: c.hostConfig.CpuQuota, + BlkioWeight: c.hostConfig.BlkioWeight, Rlimits: rlimits, OomKillDisable: c.hostConfig.OomKillDisable, } diff --git a/daemon/daemon.go b/daemon/daemon.go index 130fcc46f..03d6c5960 100644 --- a/daemon/daemon.go +++ b/daemon/daemon.go @@ -1184,6 +1184,9 @@ func (daemon *Daemon) verifyHostConfig(hostConfig *runconfig.HostConfig) ([]stri warnings = append(warnings, "Your kernel does not support CPU cfs quota. Quota discarded.") hostConfig.CpuQuota = 0 } + if hostConfig.BlkioWeight > 0 && (hostConfig.BlkioWeight < 10 || hostConfig.BlkioWeight > 1000) { + return warnings, fmt.Errorf("Range of blkio weight is from 10 to 1000.") + } return warnings, nil } diff --git a/daemon/execdriver/driver.go b/daemon/execdriver/driver.go index 7827baa26..8a0035036 100644 --- a/daemon/execdriver/driver.go +++ b/daemon/execdriver/driver.go @@ -106,6 +106,7 @@ type Resources struct { CpusetCpus string `json:"cpuset_cpus"` CpusetMems string `json:"cpuset_mems"` CpuQuota int64 `json:"cpu_quota"` + BlkioWeight int64 `json:"blkio_weight"` Rlimits []*ulimit.Rlimit `json:"rlimits"` OomKillDisable bool `json:"oom_kill_disable"` } diff --git a/daemon/execdriver/driver_linux.go b/daemon/execdriver/driver_linux.go index cdaa93af3..f3bbca3e5 100644 --- a/daemon/execdriver/driver_linux.go +++ b/daemon/execdriver/driver_linux.go @@ -54,6 +54,7 @@ func SetupCgroups(container *configs.Config, c *Command) error { container.Cgroups.CpusetCpus = c.Resources.CpusetCpus container.Cgroups.CpusetMems = c.Resources.CpusetMems container.Cgroups.CpuQuota = c.Resources.CpuQuota + container.Cgroups.BlkioWeight = c.Resources.BlkioWeight container.Cgroups.OomKillDisable = c.Resources.OomKillDisable } diff --git a/daemon/execdriver/lxc/lxc_template.go b/daemon/execdriver/lxc/lxc_template.go index 3d7b2b499..55c05498c 100644 --- a/daemon/execdriver/lxc/lxc_template.go +++ b/daemon/execdriver/lxc/lxc_template.go @@ -118,6 +118,9 @@ lxc.cgroup.cpuset.mems = {{.Resources.CpusetMems}} {{if .Resources.CpuQuota}} lxc.cgroup.cpu.cfs_quota_us = {{.Resources.CpuQuota}} {{end}} +{{if .Resources.BlkioWeight}} +lxc.cgroup.blkio.weight = {{.Resources.BlkioWeight}} +{{end}} {{if .Resources.OomKillDisable}} lxc.cgroup.memory.oom_control = {{.Resources.OomKillDisable}} {{end}} diff --git a/docs/man/docker-create.1.md b/docs/man/docker-create.1.md index d7bdd5578..98d2359ec 100644 --- a/docs/man/docker-create.1.md +++ b/docs/man/docker-create.1.md @@ -8,6 +8,7 @@ docker-create - Create a new container **docker create** [**-a**|**--attach**[=*[]*]] [**--add-host**[=*[]*]] +[**--blkio-weight**[=*[BLKIO-WEIGHT]*]] [**-c**|**--cpu-shares**[=*0*]] [**--cap-add**[=*[]*]] [**--cap-drop**[=*[]*]] @@ -59,6 +60,9 @@ IMAGE [COMMAND] [ARG...] **--add-host**=[] Add a custom host-to-IP mapping (host:ip) +**--blkio-weight**=0 + Block IO weight (relative weight) accepts a weight value between 10 and 1000. + **-c**, **--cpu-shares**=0 CPU shares (relative weight) diff --git a/docs/man/docker-run.1.md b/docs/man/docker-run.1.md index ed331089a..c4f70fa38 100644 --- a/docs/man/docker-run.1.md +++ b/docs/man/docker-run.1.md @@ -8,6 +8,7 @@ docker-run - Run a command in a new container **docker run** [**-a**|**--attach**[=*[]*]] [**--add-host**[=*[]*]] +[**--blkio-weight**[=*[BLKIO-WEIGHT]*]] [**-c**|**--cpu-shares**[=*0*]] [**--cap-add**[=*[]*]] [**--cap-drop**[=*[]*]] @@ -86,6 +87,9 @@ each of stdin, stdout, and stderr. Add a line to /etc/hosts. The format is hostname:ip. The **--add-host** option can be set multiple times. +**--blkio-weight**=0 + Block IO weight (relative weight) accepts a weight value between 10 and 1000. + **-c**, **--cpu-shares**=0 CPU shares (relative weight) diff --git a/docs/sources/reference/api/docker_remote_api_v1.19.md b/docs/sources/reference/api/docker_remote_api_v1.19.md index 1a321fe73..2f40cb6fa 100644 --- a/docs/sources/reference/api/docker_remote_api_v1.19.md +++ b/docs/sources/reference/api/docker_remote_api_v1.19.md @@ -149,6 +149,7 @@ Create a container "CpuShares": 512, "CpusetCpus": "0,1", "CpusetMems": "0,1", + "BlkioWeight": 300, "OomKillDisable": false, "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] }, "PublishAllPorts": false, @@ -195,6 +196,7 @@ Json Parameters: - **Cpuset** - The same as CpusetCpus, but deprecated, please don't use. - **CpusetCpus** - String value containing the cgroups CpusetCpus to use. - **CpusetMems** - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. +- **BlkioWeight** - Block IO weight (relative weight) accepts a weight value between 10 and 1000. - **OomKillDisable** - Boolean value, whether to disable OOM Killer for the container or not. - **AttachStdin** - Boolean value, attaches to stdin. - **AttachStdout** - Boolean value, attaches to stdout. @@ -341,6 +343,7 @@ Return low-level information on the container `id` "ExecIDs": null, "HostConfig": { "Binds": null, + "BlkioWeight": 0, "CapAdd": null, "CapDrop": null, "ContainerIDFile": "", diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 5a9273952..60c258f88 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -941,6 +941,7 @@ Creates a new container. -a, --attach=[] Attach to STDIN, STDOUT or STDERR --add-host=[] Add a custom host-to-IP mapping (host:ip) + --blkio-weight=0 Block IO weight (relative weight) -c, --cpu-shares=0 CPU shares (relative weight) --cap-add=[] Add Linux capabilities --cap-drop=[] Drop Linux capabilities @@ -1898,6 +1899,7 @@ To remove an image using its digest: -a, --attach=[] Attach to STDIN, STDOUT or STDERR --add-host=[] Add a custom host-to-IP mapping (host:ip) + --blkio-weight=0 Block IO weight (relative weight) -c, --cpu-shares=0 CPU shares (relative weight) --cap-add=[] Add Linux capabilities --cap-drop=[] Drop Linux capabilities diff --git a/docs/sources/reference/run.md b/docs/sources/reference/run.md index 6d9c314dd..bcbbe4a9a 100644 --- a/docs/sources/reference/run.md +++ b/docs/sources/reference/run.md @@ -483,6 +483,7 @@ container: --cpuset-cpus="": CPUs in which to allow execution (0-3, 0,1) --cpuset-mems="": Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems. --cpu-quota=0: Limit the CPU CFS (Completely Fair Scheduler) quota + --blkio-weight=0: Block IO weight (relative weight) accepts a weight value between 10 and 1000. --oom-kill-disable=true|false: Whether to disable OOM Killer for the container or not. ### Memory constraints @@ -654,6 +655,30 @@ Linux Scheduler used by the kernel. Set this value to 50000 to limit the contain to 50% of a CPU resource. For multiple CPUs, adjust the `--cpu-quota` as necessary. For more information, see the [CFS documentation on bandwidth limiting](https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt). +### Block IO bandwidth (Blkio) constraint + +By default, all containers get the same proportion of block IO bandwidth +(blkio). This proportion is 500. To modify this proportion, change the +container's blkio weight relative to the weighting of all other running +containers using the `--blkio-weight` flag. + +The `--blkio-weight` flag can set the weighting to a value between 10 to 1000. +For example, the commands below create two containers with different blkio +weight: + + $ docker run -ti --name c1 --blkio-weight 300 ubuntu:14.04 /bin/bash + $ docker run -ti --name c2 --blkio-weight 600 ubuntu:14.04 /bin/bash + +If you do block IO in the two containers at the same time, by, for example: + + $ time dd if=/mnt/zerofile of=test.out bs=1M count=1024 oflag=direct + +You'll find that the proportion of time is the same as the proportion of blkio +weights of the two containers. + +> **Note:** The blkio weight setting is only available for direct IO. Buffered IO +> is not currently supported. + ## Runtime privilege, Linux capabilities, and LXC configuration --cap-add: Add Linux capabilities diff --git a/integration-cli/docker_cli_run_test.go b/integration-cli/docker_cli_run_test.go index cc4c9988a..1eee6711e 100644 --- a/integration-cli/docker_cli_run_test.go +++ b/integration-cli/docker_cli_run_test.go @@ -1158,6 +1158,20 @@ func (s *DockerSuite) TestRunWithCpusetMems(c *check.C) { } } +func (s *DockerSuite) TestRunWithBlkioWeight(c *check.C) { + cmd := exec.Command(dockerBinary, "run", "--blkio-weight", "300", "busybox", "true") + if code, err := runCommand(cmd); err != nil || code != 0 { + c.Fatalf("container should run successfully with blkio-weight of 300: %s", err) + } +} + +func (s *DockerSuite) TestRunWithBlkioInvalidWeight(c *check.C) { + cmd := exec.Command(dockerBinary, "run", "--blkio-weight", "5", "busybox", "true") + if _, err := runCommand(cmd); err == nil { + c.Fatalf("run with invalid blkio-weight should failed") + } +} + func (s *DockerSuite) TestRunDeviceNumbers(c *check.C) { cmd := exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "ls -l /dev/null") out, _, err := runCommandWithOutput(cmd) diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go index d634b1ffb..5c44de229 100644 --- a/runconfig/hostconfig.go +++ b/runconfig/hostconfig.go @@ -168,7 +168,8 @@ type HostConfig struct { CpusetCpus string // CpusetCpus 0-2, 0,1 CpusetMems string // CpusetMems 0-2, 0,1 CpuQuota int64 - OomKillDisable bool // Whether to disable OOM Killer or not + BlkioWeight int64 // Block IO weight (relative weight vs. other containers) + OomKillDisable bool // Whether to disable OOM Killer or not Privileged bool PortBindings nat.PortMap Links []string diff --git a/runconfig/parse.go b/runconfig/parse.go index 63eeecc5f..9d94ce9bf 100644 --- a/runconfig/parse.go +++ b/runconfig/parse.go @@ -65,6 +65,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe flCpusetCpus = cmd.String([]string{"#-cpuset", "-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)") flCpusetMems = cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)") flCpuQuota = cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS quota") + flBlkioWeight = cmd.Int64([]string{"-blkio-weight"}, 0, "Block IO (relative weight), between 10 and 1000") flNetMode = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container") flMacAddress = cmd.String([]string{"-mac-address"}, "", "Container MAC address (e.g. 92:d0:c6:0a:29:33)") flIpcMode = cmd.String([]string{"-ipc"}, "", "IPC namespace to use") @@ -308,6 +309,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe CpusetCpus: *flCpusetCpus, CpusetMems: *flCpusetMems, CpuQuota: *flCpuQuota, + BlkioWeight: *flBlkioWeight, OomKillDisable: *flOomKillDisable, Privileged: *flPrivileged, PortBindings: portBindings, From 6e8cfd63fcbf6d64b475b387cc66689582cc81c1 Mon Sep 17 00:00:00 2001 From: Jiri Popelka Date: Thu, 7 May 2015 10:04:58 +0200 Subject: [PATCH 778/999] Fix firewalld callback. It needs to be called with same args as the one 4 lines above. Signed-off-by: Jiri Popelka --- links/links.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/links/links.go b/links/links.go index 935bff4ae..58fec95f5 100644 --- a/links/links.go +++ b/links/links.go @@ -145,7 +145,7 @@ func (l *Link) Enable() error { return err } // call this on Firewalld reload - iptables.OnReloaded(func() { l.toggle("-I", false) }) + iptables.OnReloaded(func() { l.toggle("-A", false) }) l.IsEnabled = true return nil } From 59bfee2fa4f178660fa4cb2a9d18c924e86595a7 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Thu, 7 May 2015 20:38:46 +1000 Subject: [PATCH 779/999] DHE documentation update Signed-off-by: Sven Dowideit --- .../docker-hub-enterprise/adminguide.md | 2 +- .../assets/admin-logs.png | Bin 161230 -> 104525 bytes .../assets/admin-metrics.png | Bin 74062 -> 51440 bytes .../admin-settings-authentication-basic.png | Bin 23600 -> 21283 bytes .../admin-settings-authentication-ldap.png | Bin 25353 -> 22486 bytes .../assets/admin-settings-authentication.png | Bin 13472 -> 12119 bytes .../assets/admin-settings-http-unlicensed.png | Bin 21971 -> 19784 bytes .../assets/admin-settings-http.png | Bin 20618 -> 18574 bytes .../assets/admin-settings-license.png | Bin 14936 -> 13512 bytes .../assets/admin-settings-security.png | Bin 21807 -> 19364 bytes .../assets/admin-settings-storage.png | Bin 41579 -> 33367 bytes .../assets/admin-settings.png | Bin 26041 -> 18998 bytes .../assets/console-pull.png | Bin 35398 -> 31544 bytes .../assets/console-push.png | Bin 49729 -> 34326 bytes .../assets/jenkins-plugins.png | Bin 45860 -> 40960 bytes .../assets/jenkins-ui.png | Bin 42805 -> 30240 bytes .../docker-hub-enterprise/configuration.md | 93 ++++++++---- docs/sources/docker-hub-enterprise/index.md | 6 +- docs/sources/docker-hub-enterprise/install.md | 3 +- .../docker-hub-enterprise/quick-start.md | 28 +++- docs/sources/docker-hub-enterprise/support.md | 4 +- .../docker-hub-enterprise/userguide.md | 132 +++++++++--------- 22 files changed, 168 insertions(+), 100 deletions(-) diff --git a/docs/sources/docker-hub-enterprise/adminguide.md b/docs/sources/docker-hub-enterprise/adminguide.md index d47104167..66f099df4 100644 --- a/docs/sources/docker-hub-enterprise/adminguide.md +++ b/docs/sources/docker-hub-enterprise/adminguide.md @@ -53,7 +53,7 @@ following information: * Error logs * Crash logs -## Emergency access to the DHE admin web interface +## Emergency access to DHE If your authenticated or public access to the DHE web interface has stopped working, but your DHE admin container is still running, you can add an diff --git a/docs/sources/docker-hub-enterprise/assets/admin-logs.png b/docs/sources/docker-hub-enterprise/assets/admin-logs.png index 76f0d19a80ce96bf2e9995a6076a0c559d7235bf..3221cc54daed5e5ed8ec425b24175396f18c210e 100644 GIT binary patch literal 104525 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 L4@wlD{{24yIVXZH literal 0 HcmV?d00001 diff --git a/docs/sources/docker-hub/hub-images/groups.png b/docs/sources/docker-hub/hub-images/groups.png index 7b4b1b15314c6e455fb06fb03a40e9fbe43ab70a..23dbbfcff43eb481d19a95ad2ffe2410b731db9f 100644 GIT binary patch literal 70648 zcmb5V1zc2L*Y}NL&>hk(4N}q|jUe4EA}tI()PNF#G@^8ebTy>(T~z@agB$||1qJ)HqO2wg3hE9D%42l2 zhrkif8^<`{&m(Io6)6;y%4p0R^T)vN2dqM(Q*qM(pEr8a4Z0VmL%6b)QaP%sJq{C|Lwocatn_~f0kf-K7Y zpMRMx`Ee)@2=-sgN@;t}?WH}{Cwkq{3IcQQuU~WdU);9NUvnKSnyONmOq+nGv8_nu zA`)F0zu;GgqRSCIVhp9HeTeZj=QpiJ3?<_xX*4oA-!CB_fiJ+X9nrss=z@@{|C~Ha`{#r-g!#|4bV2HH^uL#6 z!3h7BkS;((N4Cz$`)+@Xu9>LwsG$Al{8`$6RqOUpK zg7#8TDkN)&Q z41p|rQ9woa#S;5CMp*Bw55Mi6C{@(oRwK|378H}=7)*+t12p{wI71qfhtLZ53H7?2SX5|Wc+C?vcQ7ZQ3#cj(@{nxjua zOdO2n?Ch+gqa)@x0e1J=vw%~GinMyo;D+6&jAsd`WyYoBDEu8`{`i}l8z&}u1_tw; zjqbq3P>3Gt;Cv9n>U|v4H^(pF6 z1sj`!E$<#5&*m*}Qe>3g5%tPt=%ClLLPS{arlT2Q%xDgOmXDI0jH5%f*l$H+Q#Jn+ z7<-T+bcQIIiN~p;TrU?Ql&<5yoSA)K@c#Cs_9?M;q42qh*GTi*TK^_7<9@zvRW;nz>00r0e{XVp zoX!3H$m&>klcb$w5qMi@^`^L5TFce}RZMcM3WQl!X2z>+PtgBfP z=i~$i1_pO3BXMGg)E?|yYfWBon$I2|f+(BLOJP}u`Y_i{Q1^Q7*4SKrA!2`jU&u8z zHMOLqWN&|;Zc0v0ZsNk6j)8r;W1~4G!CzC!%F4skNw%ab=_COX0BvgGH-nRw88SQO z>HFOe3*MdFDWJU`ZWgAGy2*f(LnKe`y-(7dGbldr6&9EKFWF^khqhjCQV2b_kjQAB zd#j~we8YD*S%!QZTJ8ZYF;2S_sI8^HV?q=UC&KFMYSZefGkEQn%;0_qHBbc7=9u)- zl&KyR?FK>A_wt`}TwfPl|6vMctPukxUjyLc8FLj=_GKgM+;TEmI9ns+g4e1_X2|(A>9iZ*YC&Z!kUIx%Q}k`|F-k{Cv{C^=xS~?wm^Exf_k(+ZB=q z!L>ERoWaWm=aZMsAUnr9NQSx3HFCv5(UXzjrUy#Xe2x@F`0<(_C7%&r_-S{s?LSt& zJ3hB5N#?arH&YL_gP1irGAs$c6o^a_p`y7L-(X;sRyDm-5|;_0&7gUc=+bvyG9f|? z9-lp}jmyOH+wyd4CZpB4fqQGaurrdwRlpBWBX+FLTxRr`;@+n$2I- zb~qsgS&F%JW%U}#X8Md0*}BVXbuCY26V7YfGI=u8x<7jxdDPU@Wn*Mrg~&AZpT?Y` z65&8grv7)qPEHYPAt0nJfhhnwD{+)H>YGo z*kkyuq@3`~=w>>x7yNJNU|Y_5TM#{s1~y*4=4H{a2nXI-&Y^F8J9Y}BdWF8RuS>l! zH86xa3g(B(4lwqU_|8@Bu_84(FXhdrbDZp!F;^MFM1=)ZWT~~2=*j6paBDV+Y@0)T z=t1mRd4`xpxD(}XLCKrbf^}Pbt)=TFP^_2n5&qzDm=7UN*e-!x`9x!J67p^YGpe9% z4F83sLu55U4_QhgRsBetqpdMz=X+iZz?}4Q)2>qL9X^m%R8k^18Sbr*{PKnDo3J`O z!xG}ZDqd2$Z|z8QyFlMy2KK*-fnP-^+&*@#z_A8_5C}x{ld@wrH8nME?((WCUKURm z7v39aR#sL%qKD&|q~QRX+M74Bo)#84xdvUoO@mhQVt;+Ku9w1qmW8NyIa!`U>tv@?beoZR>?Fsq@u4!s)26dh@5nAvF^wLd@sjp^`$0+{(NC(YO|jP+7b(s0%g* zP7jU!sBwERy96s)DJ%Z{?NJ=Q8Bsw1teC}^IEieCLS3blh4+1-83KRMiUZ{PLe;L3 zO2k>S48cN_`Y<~^r)b$`+2;$q#OX5zD~)y%1_WMd_8qqNBD?nEx}ycJn5Go`nv<7^ zDH#Lp(_L6+S7GFyts;W~hs}BKnMoREQ~+%4GG|mk23$GOIaWhK`G_`K60mbbP4jsk zX=2TpnXlf6=2UpyzWpo|MFc@??d_2zVMW8ZFVs?8nXZ0uWg&6>LtmG#enR0=E~XDm6e6I1DkPqpWAQNi;w*3XU#`-rY09nw2iZEkzBLH zE>Rb3VI>W@+P($_6_$&^e9q3j;cNSg`A*R@~r^b|(0-w8ENQ+kq zDlwzkC9)G#pWQr6HI>+w^Hz)ptyGQJ{6LdNWKX!kffJZGHm)`!nTeq z8+xoD1&{rqNJ*;iEc|DV@VA&jLTp3lQzy2$>OJtIE&%6qTm%Q`Gio*T^^wp?(h?X~?We-Bg8} z@t$djGv2=Dc#@S=TS`T>811^yi${r*|5P>W_wV=mwRw4#nwlVgYbB+yJOU{xDah(1 zH3`Ya))rbT>55P}Be%k}zS8XUG^t%#vx4l;FU*im9~zpBO=I|C3u*wvyYD}Xi|hd- zJ~<^2fd(P9fIC+crL7_$66nXA^+}1Bk8BB9c%?VEOjsqRCa0ApAFVN@Vj)ONO}YIZ zNIFdz&wL!}$kQ~_)^a^bv5>?!TEPywT#yr-u(p|fCD`lNv)WH%LX1hNZCv|lOF-K+ zpra!Xx(%#b^e$7Sl$GaU8LbV7clZ{HZBe&BR;PUz;+={~?43&_Zl!(t4Dl}WkJ2=-|+;hinfuvZIpdb<~FYocosr1LvvTMoYm3~R6uc_u6zBPw20 z%7a@F@BFGJ!vl}o9Z}scAZ9bMa%52sq6kyZ-Rd)DDunH{rKm~1q_(22(dPj!8Ufv* z-}cAQm%@hm$DnWDzOqTSPjYJsoSE#XcGo_p5qt*H(SkeQ;JvR38tKG_b zm`R8d+7{^szrwiZ8f*{9TP6ll1YKPcC>+KOtCUWWJ#a-Y`wr3`hdtM0OczWHZsT*u z503Isd=kga)hF|x{jVPd$H(gxOkJ$Y8%3TcziX}UXOH<5^li!6vex~qH+r^^Pf@tO z!lrn8C5e5?{d}KvyO~d8_iMD7f_1*zZtUfU*1-g;EviH9$$nd6qLb{pyZq~6F);G( zbn&+MDWpGnj{2I^OEx4tD(dsMwV+S1`I(th3T7dpnZZHqQuEPLE!VUfpyb6=a=#V=9%EjP$!I zihlOF(rbE2@BUU-PKVg>r_+LD>s48dMWv3d!K{>PxIuV=GPM=LS85E*h=WMr?$hh0 zo?Rx>wgvo|%F^G2tB%N(TyCPT1UYVIVM-&sf;ya)9w!!=f6%xG}r*0?$4 z*ZFc-ey+9Rev-m8nCIL?Ny~f>TGjHpRb#-oLADGbFY56NvjbQ-o1?x|@_!uw+;nD2CiL*HI+UDwuK) z_Hijkd69LxL9fI#2%%%-1TuS%J!OdH7OcIO{w2=?WFKZ4h%LiF%q;pj%YhH zWj3g^O<5X(V7&gWqfLKT>e~fx)7iTI_PC$R(^MlAo@q(X{>4LG&+ z#HP%PEb&!pn;*tM(BQV&jh=HH&F!{u*@O5`SVEQwuQn?b+!`Vxu#}bi&(F{2>p^Qk zKvd|Gda^m3pO^P~Go_{F_7$@1-67VQNm9Q=yDpBSAZQ)z=C+E|(J{dU*H<*E>u4Dn z8{XxH-|_7Fye{-?(>{%3or!pLwwx5!r*wpGy9x`BIpVW=jcVOBSlHN@r|xl<0l3|6 z7K997(h_5KTO}{KJjPqpZRn*CXqJt~Dzl_KTn?m+0L|J^PfWsZv2*kC)|=-SN6l6p z!y>+bD(Ckqdd)pl%XW^4M-*O90iM{m=;0S6*`991n(Xpgiow}K%Sm~=r=1HN*pZQF zohct5+>C5>VP44p>ZI08zk^26&y^IkR)C zmTt?ZRqly2y>~{uR5i_daJXid(=h3sIz(?XAjj#E`mWQ^syDAgRGAe{`j@b<9XCsl>IUzyqPXedq3^ZrTY==<`K3uOtvHLIaGg(*|m6fzZa z$ydlCs>%a3r{#wYR@+`>T~*UY7SeB}>SCU-Hhzo3NZ6zoc2Qlla)8?VwPSUihVgXq zH1~TLDq0|ogG-fJprjus!NJ|s6k%lP{!gR6pE(Fg3F=G!#apPtjlEHZqTu{n>-0v? zqjyS581Gw}nnc*yG3)l%*O{hPBZ0sf2qR@=WW2n*K71%iBrrWK3^%KFl24O>c+*09 z!qfF4M! zVzO;rXDmHq65kKGS!mhnPj@83RZvnBTzzsgJW8{C-BGY~?fW%x_lK)KjUoA#_3ck) zj!9PD!jFe48Z1JztD`zKwVj~n6=B;aq_=lV6RCld;J3}L(KF}ajhHb;8ZxSLGvX78 zO!I!Od(1Z29gFxXH?R1?+e+1IB&%c!Uhq=Sx8!iQ;}WkQJ*uY#zHT~MP6E1c4U*$8 z40%;}WLle}lRagKYaAM=U>oFtlEMum@1sBJM<*z8Rf@bYHgn~#`B_0*W?$Q(n>4rg z>50uoym-*izPRa6eG@`>gkPMTl49}h9daMyUjXZWT1#=jqmzZOPSx4=0r6N`${e{* zQ$!@aJduZI3c03KyFkFL-YR1kOJ-PoeX&GyScjh0(M#-EcV)MICf5&h1G_)&Sj?2S z(=?Jt#Y_vP2-ehL`>^6h!_=I@{gaUgF}lbgSnpdZE-tR2#l^*`sj0rcorOkM_4nHu z!*2x{7R>!b+RYW$sb`XXIs#vvWp8iV<=LgMG6+R5a4Jo#{s$EMCGc75ffl!m%bNN4tnQ^QKMxHL=0s}a7QQpmvgLh9;9&y0x9h}hu)2udt9Hg>1U8F{#W%B1Ny& z-cJ~M7H2K>dU>^H-}Vhs)8DC1RubkP%zz9Hb)SFNMs&u4VC>JMzIj(=q{0B{q*P!I zD@`ut!4T(@K3o;}_wY9;X*%K;-{iNU$)uE5_(E&THO~DWL@dd%LZ8WnoZtU>+eMxO z!ecoe%8>*M3kx4#buH*k&IE$Xn)amw>oipv+9$(7#?df; z9kVJ;3C|yymt~*7wDU>fqvE7%ysb>HOtLJ`6Ke7rFe+varm3y_z6%Y?Yl#~QTN?dt zL+6_-PD<=&gXF@xTcotQ{2`{kH(|sXTtN9yK;&>UFSxH8Lo9L-BpZT`h?Q1%JjTx zoOft6Ugs zcYD9g!RR#YcAyK(T_+{}f+Cvp3)yseVq!=?Xytxai0uWSL1XL~MKhC#g+cF!*lG%I z(eETwLwxG6mnO%f<Sg+nNL8H@Wn(t#e zI%5(3qr;uCvCil(t~KI*_yfboiI|!B0=cWfS7#pUbylf)(g_czAZ+6uwD^K%Q=nDYyDez?sW=TVaoty{N+=T*$oZ)!)6KlX2M^9G2@Jw1hfiwX+reFbeNJqmJj zbL(|5?UV}T&wp=c$JR-O{jqQ5?wT|E3le9wf5)sE{f{=t+j^Qfk9!k+dp0&_mVpB) zXz+L8k!Yw0!%_G<#AvhR;cba}(%&CmI1=&_4_0@+)oH_1G@NfsICl@-G7%eFG>(vLQ&k)0X1m z;);rxo)}~qNxfZi6f?;{LnJoIa`BN&sE%ecX5r6agMRDb6BAJEy`yA9c-qy6W~|55vYhoApT?fLdvjjs7Hm;~TBadL9LeEE`|o*v+SfSUzGj@;b1#dx3O zl>g#av@95IHK+UkFTcf11O#_;$LW{8wx5^&1paA@w~mXSKtlq4Poh%hiQ$4089OVx z!)&r>bQ2fM_R7FA2&_ba)b8Z=YXSg^_z%r`*d@YhH8eCr@kmMabac9SpC@M`;s%DK z`Ek8N*vi8ibxtj*0?kTir)TCQntSr|TGmFsSAgq`Zr-lcm2W+}b1VF1eeICS5XXPM zS;Cx)aOD_cnElo<&mavSR88}8y{RcmCjWz^)!_pD@y918Kfi}YLZ4dXib};$}l-ap_M3B@OMwCf;&eL&UP7*i( zfkr*$?^8qGy?59iN)t^=N^-G}-eTbA*LG`J6BjdeYr%x{6njQH`F3t_*pBfn1X`CZ z_IgVh-K%+9IZ&l(E)&=bCa#hs*#Go--?vNQ5A!8;sp{C!RQ3rW@`Ki`1e)aI-=%-C zoOb0Scc6s;?4Zh(b~Vb8`pnSvx%Y*iFPi}7RduwY3RXaO7Y3YX#E0rS3 zK0z^4p6&%Qi<7@Nhtw`wdafAys2z=p@}Sb(SI;(A?>^v@@E5;I+b4l1wXW}0N8U9Y zEte@jDCJ%2@%eBD1b-M`)P&zhA2x?U16kf?Tndff7(2?Ho#sSI8a7i!hExnR`|;Jy z^byCl3aw@~W4yeFx<9R$ddaAYans)6Fmn5&?mRMTsk~(F#F7e?upY{lsG-U8Yj5wu zt=`YI>?p-3+U)f}%^@^uQ0=L_a>{l7`{8=Gt6L;0JiY#ET{y*boNs=cdSq!f@5a5V zzi8-D&z-?N9!^&@DMH^9`Od_(xY?bKMl28!6@AOq8<-50uOqIz?dlVM_x*U8tfuqa z^AR0BWAG#RlE$dF+8A<^?w(!F*K;3o{GU-By-`e1ka_$H=e}`!%b#=MGYEOF zyGLC=JSYD0EK8toYzdv-)YoVdfg8+}_u1!7k7@>=)!aarcelpT7eGNYdEoN+Iq&vC;;t)hKSE*}c6DOqk8fw!N%r4CLKinO3V|wc7l`>)~HMjgi+G zrvrE^IpVsG2cn zW&5K&d=B}^wm#R!CZ3kO&wFsTznj!|wCz>0P!Vpa248&XltHDc&V_;o&wEmW4;4Q5 ztWT}iAXXr?3By)MeE=+^r?Y*o@>PS{jBv?PGb+ABN{jl^3mcmc$+dQ<9p?L1@l!oR zslf+7*DSt%y*8vZnd_f^x6igl1xUEDXkU?T@ns4ABPL+Pp0QQKWBi(g()PNi9IF1= zIpNV8FRh7BA&sNDq0p(X-{*0t<$j{>W0V}DV|3NG9h_5&+HN~J7Pgc4mdjBH{_*W5 zz=KtJ2M3Nk+_{tmUeeMX`}6fo&VX=4a4SNe+uTfvpDT4ppt!>`Bm>+P&Yc9fFQ3QChzuOqcw!R1*!JZv8Wb%E=IialCDw{Z9(t7Oc^3rrxpXMZ@c?R z=vcz`$o91!t&F`}<+RF2}UI#l;$baFA_AwG5wn4rpzqe z3q}ULETR#%I775e`Z&A*3|awHrw=Q;V=KhklAa&Fv$Ak~YBm%uth)K$J&&*8VuSb0 zkBZI(0uJ&N-8KjQ(>Vt-BNC#r6>3r%=(Z}Yy3CT56oSqG(WMxt80J0#X9E|d@-L9 zqMC*uTL|Pf?LeQ*Y6A8yh~U$wPm`0AtMV*mh%pt7=Iy<`lEOm#@llo!M#jc(fJ7=> z(Wfmk!DA{fYIXE$mnY;Nk3SYvW;dIdy7y=Iz?FjE+ppAxsM@TN#|0!J4ulHnagr%R zwKTQ3r@_ISLYQp!_?OJ$e8CRDawdJHyf|4^=fT3wA;!-@>il*L!)&v&MT$FsLGLHN z=YXIz6{H>~3}2GGm*%Cq^|am2b@}w3kCYKj9z(RnWc|1SbLx8D$EjrFLFYW_#^q;9 z$UDF+w?3mJ-y45yZQ_kBDk=zS^U$*-rXMh_R5d@y45o__9-6O-t z$2NMXGI)N6?VY0^p7E)K0RGfpQ?}yA@H|d~mX0nfFHi1ZH^wtKg^-?+iRsIg_3!!a z&Q7s1);c*duv7&%p#QOF=k{$vvyV|Bwn(ON;!s<6g-I~x%E1{@k$h7qRUA^H~4 zNKF<4sl-l3Zm?9~=BXI&!wg*5&hds&)(`eh2Vns)y$L~1(Y3qkyyqA>1I#IDI+r4q z!g9yCvSxQtK5KgbU@Yp>bHnm)wBtT!!qu~$-r4ezv8PzQ89p|_aCTuY!oQnqc^Sd- zc%4#uYLqcf!j8&amOr<3yEz(kJoxzmk->w-vnN_499sBv93mthKFgh~!G~*gI4%%L zJBRS-7q-DRqBvACp6aq7Og8$L)CfU@Acc)tse?mr#53gT)<U$NHIJ6lKF_9FBX z67~-|nz~%%&s-o17#O2b`ez#>k{r^NjnMYce~lw-dU{&nRKe6VT^bBM)hhW90~h{E zW$5R)PAt;oZ8Vb#Zr)1!Zj_mc2xhV*6~9%TGp!KYUEQY8G|^&)lhBl3h8PGp7|fVD zHEa>5c!F6RYz>01zkeJ*FPj;;3$XTd3lKqXGKP5w(B8;)oe$h+}64 zRD5SwmOyJ@eNYXyIX6cSt=rGekiN`fH+O;b)L#$I-2!l8xVGWryB5j}KLOTpIx#6J z*E|Ki_19j{a~Yg=yX=e3Zmn$BFGxS!mK~4;3o}hF)QTXZs$^3A9Yu<4Dj|6cwk@Yy zMbb6?r?un1jrQGe@dB^Avbv}Bs5cq%o2zyPkH+6Fv8`S{3O*p zh?n50{C+{lq(Wy5+LZX?uL4FHiKBwhgZKCK1!XFy9k|$&uCxe#En)+pz`115z_)KY ziAEVGO(dG?+R0w*!w1bhNv;Wul3ZMzl78J}^yx^4f5LziOPOG_QpT|uoQ0jrw18A)lR-r}ZdkFpS#p|zI8I@pnegM*w* z^*+^LCR(i5mg~Rgp`Lx!`B(h)?#1S+@q?Rh?$)SZGxT?_aeYCPG!$2;I7W zwaD0>a#B)#nV1q_L8!V1jDS7;i{HG@I<}!+m`<4U_BxjjYY)$-{*HZjS*zG^ZDgb- zhRpbC2ZQYzHqkDyv21+$kf8Ic2Nnn#Sj-){!hU=|y1Fkjev$6Omx1N6su-O3Zcj8< zi_X}5PxLnh4Y8FRQS>K?CK%XlF%4VZeB>t6L%g+|=C}!|W9x6-g=_NeFV(5lVPfZq6UoWW*+B#5)%Nc2& z7h)!~3i}zF|3X!m952tPMD!v3m}sA^0Dj2vt0*9r1!5*uI3WQ6auR&CmM`s;=jeI$ z=J9_>Bj8}hd61U%F!jq&8aD^gfZf3~l53RsaV%!xIwO$SJi?coKeGvtE%gR-jmD>^ z{R2aRnebNoqBrVW(3FH$>GSM0P_GQ7NsGclTSY0T*5aF#82`i{#T>9YU?fc#R%!ayv@C}Wo_QR^aZS{oVXtoM+iDNI zLN3iL+A@W#i+a)s%ij$M?Ml>sPq3wo)hE3D{I zA#4jmyr`D|Gl(lwZ*5S~-Za$KV%rmHQD36ZOY-;iRf5r^?2Qm}K@_+5Z6>-J>-(e_ zUldYZ5A;sLm1vUR4!2YyimEsLnBFp~MxX|g-Z5h>m4H6S`;*apztRSI8*Yw?Db*P ztZx)WTsQgn_vih9S%1?GCG)XLbCNF`A@FezWRG? zxCVs4{Bj>7?wj*#uQ7O-}}yxQS&-m69@5v?!hw{R(NI%wC$SHZCZppLW-u+VwKmSvWw1dZ8ObR6*0EE(T-CBA=!Vq^Ps7cf${eind8V*n6MNMJglaR_fMguZ- z@)50hBpQ+`( z_VAK6%~l_4Yfw)vz;G0O0p;ZF+sqhuo~ww{XZF6nH+a0R+e(?f%}N$pC+HN`vok>I zsruQE4HC@x6Diy%2neyI-V$CF#^$=~8r)S+lbl%vlSH+Euk+W|Vg`n!OPprd%NM{T z_1=gtu&)-%?a@QT+Oo1*`^KxqK+BrZBo@G- z%+H@sO-`Z*k{)oGrX(k8YHH#=7#%@D13n0u8+7((b2-KDHj)xMC+g16n?B1IG_M)wKV8*NiC*2~f}X#p`ZwyLu%k@RI7^eY=)M@rPcD`o+P{9aQ%YKU<_~qe zUqkjkCLnS)Nygi_%BpNMSebspo{!mgf1>n97+k5zySee*f=&c-i7cD10P>(`Tza~x zktV|K@=Cv9(F+$C`KhRbP?S+UW+_WZf;H~8^*Tv|r|AiH*xTD5FiFeh1McBj z{`Hua*y`w`$MB;cb%5LG?;fA!uk;cZ$BRh1OaTb4p(nl0yRKXcrTZi$fhy zeJ$O+!#JAsfGyw1N!0}S__xjOmhrp&^Xs#xFh2kO1a~aIriu4+<{_60@2JU+LjjZG zxCDCXeD`%!t42X3S;BqgIFPw?>)=l)X*EkQVLU*^+BAPePC^n7xHuytv~th3unS5{ zRRAo>upA6!h)qmPv_2-i87eD_>1{~8$sVif8!+q$U6Okdao6Z6 zC;3#yEw|G4cDn|R|5TO*lhiQ^2=3|ZM7xiQjO66pPy3`VVI?my3Ox<0UFFd&DJ?8) z|D26U^V%I(44>j(_(2^`GZ|l0RMheFCmp=8vGJ=f5M>7}jgHdQd*MH&8%zBPx=Ije{$$K8p6OrxK$bZJ@zFn8{{(oB;6L3%|F3W6!Hj=~?JTWM_MeLWu#Eru zrcd{G`xYdh6aSAl|M$v&^BACFUB!QZf~|58QsTzV?SiPz&63~k$As%jb|YpH3maPz z`(F$}Hw+36!phTHXpBgGJo)_zu%QcRCmoJqFf%5{@SC@aMBC8R6@U{+)!q|*y4d}R zxtBY%3HWsrby+(u0Gq)~=z;wO7qicUbQ17%O*)pq=_o;Z@6#26tSRYAn*Lx7;I=x7 zl+lX0vS25VXq)Nz=7TDB2!KPt4IPFJoP-`mF#IVE+zQaTzB{`4B-6w{)mWwcsS_v+ z+?8pp7d3vm^PjSR00uCI#aw?{Kn@Xp1giP)rxBp=pVruUhZ#EfqJXl01`X%}P$STS zI^63aP)(A|I8(y-W-LVQkdBl#QDRBKDb)!JmVb=!Hwx~&aO5f+_mAWeo z=93WVf-=vN_`gqKm;p#&%%@7*ES;ngg$x0{o5bj-iEZ<3hb3Yoo`jlG*JN;+5GQc-=8*%dkmIsY;>jGXuV2Gf}|#D|>~_SDJGA?jb)A_bngt3z~kI(iQKC z`PF!e?tF*n2P6}#t>j3*|2wvKB^vsg$bWK=6H?Q&DvEMT76!(=GqLCAMMa=Z`X>hN z0t#@wd6Rf)yA-2LSzu{Pm=s{^0ogccF&oHi7Aw{jG&yFXO@MV5d$JM!JVK1Puen&{ zTF9%%H3!3bycPdE!aLu}$nJ07De8A{s<6zdrkvO*Cm~@v)y{c9iUi>r2)1z*L7k})wiUmu&W11E0I`%>z^gmR zD_l)j2>grSSM&EA>#B?DS z8~Fp9xVS%}KeMt&s1UWYEyzLqXt1#xo(M(9=aOX#cS?-$<;NkB)lx_S@DvB2*#JWQ zU0BqLT0$}~=%@33Zzq}9PN^c*CW--uQ@p)fB*|6h($#a*m)uA~>epZ+>T!XzI_WKW zs+5&kB`L(lU>Tak>dEf08O&JUPcYU}Ui z*ToILzx6ge3cVamUEmqF^2-$14N40PpPsBW0@FtUIhTQx7sHzzsV&|zH<1(BWAmy9 z>3~YiEBu~|{+pg|g+{QX*& z=$-Wy*N&(f3h=U5mwq9N|0bQL2U?=T{orWJ%2^&09~Fhu2>ZFX9-Bb^!OQ-JU$Obp zb90DlG>wLwn2!bGZ=J1cVELVBv!N9Ap%x!#Rs0(jV!XP_E5354XzId?nftfS#9Kf` zNCa&M;Qf-!L|l6EAV1f9H6zzE}D@C(EruF8oo`Kaq%lkc*@r@tGpD&G#hR* zVeN_r;NsYuz(G=91No9Bwo{XpeCiq$lWfP{HL)gGKRcaa1scyqBoR#)ZRN>yTprrI z$?fla+_N-hlojHn*(q9puf6RCK+pOoK$XB};e2gN<^mb+`xW>Jy-m%Zz|$B;P=OTs2Nbsa_o?r_b)D4F1#e+l1nC@8`J|N1@8q zhtRu>!40!COBRp=pdG+DTHr+=cDH<`jLeX=7&@n<>=(VmQQcGL+`G^Iv)Zcsc0^p0 z5PH3RZQ#{=X2X$kPOUw4(Y;~y_*&9@lNR25J(LE#+uS@eVk{87tqH{5j<@R#e@;uw zei+~++?rhDTLWn~%GhM5c85;&pZ{EsMl{qr;EQgYDuu{EGm|U6U#QXRPPGfU*xnKz{ijc zr!9FcGUoXrJ;Prv;Hmav9v8O3#w}HL|Mfs*wWA^RHDJxD3y)dxS5y#qg++7#4AV?+ zgA0B`5(43TmBzNaS@Gy5 z&7XBk0!G4H=i;BzXY_KG_YTxOS64R{0YW_>A?pNM{?pG<)j; z71pE6=2F33Jq^F1S}bnBI$ZCXQ3dM<2SGzeO|^n@-=A6)W@$L{t^wRfL2dL0>ZlL3 z92`ihYc;ObBhMI_n2NG#04qX_0wHeh0fnW;jkzMjjSL3vfY@w<0i+n98wLVoN=*um zJ#^NcAjtt87ylStX19g@mXIL4D)t9c06063IKk+qp(@1c4dV z-cRADdeMNfERF!vJ~A}Z*DR8TcXh^E)LLbT?YfQb?ld_-J3C`F6;yRpu(hu(_Pumf zH}_4rIe}CIAQQ3*Hf-9m0%#F{H_~WwMEvE0peP$Xt-AfMqP3Uj2YTgRmq?KC$hGSO zUYV!wCLzFXw?;wd--v0N86RPrR_j9=!KDl#7aOFhvB^bWk$44{gKK_{#EO1Qo>|F4 zpb=_+sE%H6+08{&+EX!^icD%CMV?`2GPycovcxmG05KUkcwg;1t!GVER(l~v^-LT- zCijN2@UQ{x%tT8SWeWi?<5!h=B;T`UMs5feakF=P<$4bF?9bajGDVVfo6YKZ>3Xzs(19!`6K08SqLi&A3qNijSDL=bZzvm@R7l)$3C5;a6*Pvhz2JdcD^2 zuY;wRoD`B=da0QQK-2pQ0YXMMnpMA{&}R_TL3AE&4)UJfly889beh`iT;FzdwUWJ$ z_)s6(sME>L3u2VX{84uBE%2nd)| zkAl3bv(4)O;Ks%SO+V}GDL}Br&8TB7F4?&NrQH0aw<{eHD6f(gu zi|B&bnJHo3ZKZb`@2jhAQf1+6dlwaNBzHnN_MwyEjp2dGRs;}ZcXPaRR|m1mlP z^dIB*1h9_vOE-KLKWp_7aLuFc{c4zoCq>FYrBF0cu9=|>uZzZ`8O*4UTC%GE#k`)? zBzz4YT}2Mva5CTKbbvOznBISIZuHofCn@i3j*Z}8gluL3{$NRyEaAtG?G80jBARf4 z(?@s`x7l^D%WGpmjP$L+YPiRnccB2=o_-fWmNk_=_aJpGE~u<+si|A}XdRlkQw$&6 zH8P~D;)@b~O~<7nh}Xj-rs3=BKV7JHkr6vpG;*PKo_3Y74z18S(!iR-zt(v|<^%3| zJ&JaBb!<|tCUl*@aNqiD@^vAwOGFCpU*TjVv%831x|?j|+?Gf0Iox?HAkJ5a9;J)@ z!E_A#Tg%TEh>RkEAnPeT5IAAAPp{Ktv8a>oS|8B0SS!F+f&ptHll;QwKgC~#Kwcddc4)|G5-&1ZypZi`@fCr-J%j&NkS!r zP=xH2P}U-387li8Vk|RMDj|DJ)~ReU_MKrSDcjihePrKluxx$o<~uIu%>&hvbo*L7d)SiyP9h{>g+@y+&>f0<+(%)qgj-HbFau*1?CkS0HZ&Qe61+LfPHsL{O0|R#4 zT=JHzZB2%rFO>JX`T>67)*d&M1NEX?`B(RU)um{(-JCt(Mo#+u`zh(@B3dK>14CS{ z!?}!Nr3w3}t*CF&bQ5D9xXLW|`UAKM_c^}!1jHxHOG*ShVfJeQUDLG_uh`-8T4>g?RrW43OwtHxs$S^2}#~KN?$)-}^>*aRKnqX4O}*yZ1Ym6*emNaug^^ z7y(j+1=~opt@aJ8VQ7+@;2lxMkRlo6p=4}cDq<=l}q5v(Xhned%NM@ z_taNaWBqC<;~RzJ(2$r!ztT@dP74!&7;_)_vi}6&#!V#!1iJ9h^EQKhUERYoGOH48 zH;#9VY1qzKxjgcIRk{tTBYL`cZqtzq1;ewUR(LhDuXfk0@W0W4qdpJF4~OaO&L(kp z4kw?b)|!}-JJY=_kG{d?!$v%jkcAR*$Xn8caO}4r|#X?t83X#RVQL z_#*^6E`%^XDFs0MRitlb35p65^6e|0qB$F)PKDd(YO~a2%>L^e$Cif_t^*?;IM^Ve zZc$--CxvWQxRNmO_{PI_OTB(iqJKiTKulFjkBkZ5R5JNBt}@QTWXm0NvV!|C1a6eaK8~X zmE{{Zl^T0R4>T)xskCMMck7+vF{CSPR18)BGwh|0srwLGLHEwk#prkK#xquGsc$f( z(IgU20(pIshegj46eHM;@O5q7EnJPsQHNuG7}IxOw)LgA21qTO^s=Hn)PRu?bIy0P zWt?nSKHVysyV1d+zIf(tXf)sT4H}`J=Xr#- zAH0OT>F+6*P)TNZ_H+>zT-A&$$DUM^Jfh^&$i90a+xhA+P&=#-EGcl^{x~4}wYC&5 z_34iw!}c@i)?a}nJT0Tvswy1q@9enKrbsP(vsZlqYtFMAoj)pz{PDliO^9vR_>lDa z=P^KS_hu)RrCj$~8K9|ifTHXvKL2l8%zq0$8;X|{r}kR4C)(`kQ;Is1Y!Pyn|F4gV{c`CaSa|0|UJM|q>@u3Y%;5Y@{gKSd zf*#<3fb`d}`~O2DjP3!c476uY`va25pOy{Zp08__*}F05fWkY(|2*h_-#MEHJl>y< znU?D(9my;QR6VZ-^N{x}9JK}LTsFFS32tm^z7oxjX33L!D~^M-%}UKDz1fzPE2Laj#P>Oh?v0MJJ259C2q@ z0S#=B)Drrai2Qt^#1-K0vFz+;RIC@B3K)=3#}E3g4+HDsc29=j!mpWnk3*&hf9;OV zH}MM+S0iGPt126Vu^G%j{sWr}SSz3CdQJ6et*glK?quv&HopP8ef>+Jhts)-&-u;p z{uKES*c+-H(t>KRH|l{VjTgi#cHC{)+g`e6ywe5y(1BYnwyoW}IpT?{n)L8VkfI7v z*!s}^lZhPX0t7{usGfT>4IH}kdzBThu&5`-Yr+zG>|j`&aMVtXZr)Agm-M&OP=%&jooB%G4UdnhfIm-Yl&?MYRL#Th1bj-qL zZXG-)0F9#TAqjqibl~b0Sxh?p16s`tJc{lPlT(2*Nx-w}w#rQ&@uGHpyUCgG=6hI| zti{-q&^F)){0#KJ$nD2ZPppHKYcyz1*)y90kqHeFh7N#?A1g6EaeP-tIRc~zAT?Ve zUG@}|MBiXdr(O{^$1>@+YG_1zd#Vq;vBzZ6;E)mxfox?Xg75(8}dcu#k-bGiiDq19URF8N(6 z(K&o^dckw9JtZO{LU`4^^mlaS=ZOa}b{@5~1&YOELl((<1_l z?33S^X4!!u{~te$hyA`45b%9tuXmy#R}ViNoB~!Dc;JBWOtSS@{?GEYfcyC9cA&F8 z96ER~t}LUB^&(f5#HQDXs%OfjlYzCi_>TC*dz6U78)A_2^{0ZmJw~7DcU~o@t{P>p zGr@zY^j{MXZY_!(;N1xSjuCIn5?%5)f$-N@UIp{c%~>wuMQF~_8*6@^flnVn(S0Q9 z=6UfOhtg~L>uwPEJdaZTr9d;P*Hk?#mQP_`yjAdoCPJm6% zFr5x<$7^3)nD)nSLSE&st|G4vUGyqaMHgAFA3O*~Q*!E4ETEl3usRZm@2G_UXtcf$ zw}aqx-22FuAiL@29A&`apq58J_UA*_?o8d)G-(l0`ZU!4cz8r%dsiwgQaS1I&%Zx-J-z+zJRYbH@~x{o!}15SFc#SLb_91aSHm!GKN^1u$Z7LfebtLMK{*?$Yp4D3S44wG-0P2MkFR9e=Scl%IprjUWO zc@72SmfBYtwFVBpzcw-njV?TeRiQJT!eyyq?&!&|w_w@v^`MM*cHTO|Tg4JeFzJLc z&BXP30*fyg1Khx@Vec*RHaLG@FUbmXLS%bO3F5`vp)L0!&?p(`kHYLH|3JCbgs6za zxb{lsthscKIGc};cKzBgavXC4a0c+06J6{Qk<`Xoho>L6lw`8zK}2w*27FGqcw@L# z;f4tMLT#m@q$r>N&wYT}{CQkZ4ag|V+Mgyc=C(9H`g@iE3U$YcE51oxg4u2k85k$$ zauQF)c_|$Yo@g=Dx8yPprDyHtMXT@Q@`6yoaXklv#;8T)Z})p}qA2yRbx>BX@thrQ zVVQQ$44dUmxtnansQI7PUfl`60nG8o_SHTk*A4BX{hXsv(nYyfReY+uy8Itd*o1?7 zTMUjbSNC?Lp@@fp(JG72Yqv3|4c*s8H1M>OTUd;j|kPEo2vfwNNg!tkn zhCQ8dQb2kOu%G9poGIR*)wboa-IC+yRoa?qMAfa@v=RfmU-Ea2Rz<)85#^=OYNcDu zFj9&FFcx;~vUc?P)U=Jt}r!9K>k=}#1l|Kp;e4WK|xKiuiQBP@zETI>R2*{O+Xy_tU z5Pw(q9T0DiG9;BLAb%P)zZ+u#9HS&83A@?VNzO6HtBhJ@tf5{jn)GQHg5wiDjM&j; z`=@%LtgSL1U=786W;*P4Gt)|s@Jg`V-t&%=oaZ7w)kSv!-}J>YF#9}Kr077V)R1Lg zv2jw@NnQW_L5^Ezd%U$duDqoQ^EU7F@S#DTf6Pn?biWcprGWD684m6hX|8pvTdM&cc>*5H zr>cro9yz@2D{$QtSj>rX`kHw?3%+#j04+W6qmy=v zx3Nm)7SL3ps3>rZgj5IE1g$0*l3+Jc{7lT70?fL&^@S!?P&$z?UU};R92{O^kSI0} zfi{((jEvf}ECMvh*l6Xp;M2asZKSw#^b*z7zXKFKJir!dnXD^WHPsppvs`C149hV8 zg7L`8QjoX}3XZ3fPt!RN6y*cC$?cIT%30Orx;${p+Fu9U!J#7{UV(eV>eMGEeN%Pa z05>Yv%7HfpH^u|%HbQ~MwM-ckmU6YWW;4PIwVFA5j+cF|JS|+`+o;`hVj-w-{_$N< zMBDm0U?{=3z&#hHbxcMJ{Xhesb@g#($ur0U>qmY@GLj+3L3uv%D*-`CR5H~v8m_7w zLR;KfI>^@nsaDW203{aSbhE1CE-Eb}rQSV+CQZ$o$U$zQn!CpxlG|aW>eI|fkbk<4 z!;${d0$5B2-mpAd;PE`ZmlObwYjAi+1V-~2r5T4`Ho#~NY+_2>@|I%&Nj(93Nwp+a z`h}?Ny8}O5farc$$l|GI_GTT|WZV`{<=wFzfEWg+LE-AAhF~oS2|xU?B_+5)+gUm4 zg^mZ)ST5dojf`Itoq#$%Jp?7!5YBArm}>9<)#?mu5P%-SAK+NX$lAJv)A^&)b8rNo zVIRd6;G7N}jUqfCDSlo-3*)%kV~&g^i7i^TjY2`8PjC9dS4(pqI0M%H<6*2_Q&#HA z@}kII!37BVRhSObvfCD`z#ntmzLNU#<3HA$D!o>%zrfC>VmYof5 z%31FDPTmpA_cw#}Mrx!3OdtI8@-qDZ_SxviHWn3n-hEWgBy-itq2KdMZ&Y$D?jc*V0G8C7nEX*(rtP??@n|F4P z7mYTIDalCh+NQ!E|3GfB)1G&E&jh*&^pdiWE{`QY8lUr1E*&S$#>Yo3bv@4QsJgP` zZ~y26Y=c!{y9W3x{qcq3N1R`O0!8AWZm2KeW??BbMtutu`r00spW{>MPDOWFOWdvc zQsnS}5->XZ3f~F#_V5?(cn@`MLKIJ(KZCr&uC0l?H39aMoB_MEYo5(L^}PNis}v^V ze{rvqdpBuLwgWk43Jquh^R_(2^GFHHGRX_I=|y=}X*g+=Gvq;LI$`jqWYxDXwIw+j zR&Foy$AAz?yr_~Z^3Bad||1Pn>zX?@qKY$ZAWjTJpgk=L=#%Rj7^XkK-it-ad&f&tDy_j^|2k z-Vq;sFid?E`Lnl0eR(E`rN~ll9xWkH@-{H@H-^GYXoD<8m)>@aP8L;toG8#E(Stig zv+ktdjdC_Mw9;|XFgKy8tng<+AEL%!soPN*5P4V*JFR#_Sj4wHOU8QGISm|hJbc<}Q} zNpA?kAgRiGou0nM(|trEu(=o7opE>mX0Q31z{PX;A-G*tAf-Sz_{Z4Nu)F|V<#?=d z|0r7#J;m$n0Ulm_0VZ(nJtKZ?nb9#0e`y$pKfa3qZ5$W|&k)s^BS46HnIe6mHqXR6j--;g&4ZT#$d3#|?9~QVaAZ2%@ z<)-6Re1|75(w|a=&>|{9+m-G$X-;YK8CR4hyvf`~5bTb+GJ*!G3i4R+u%cU+c5q`Q z^wXa%3$ah>L1K*pYzV#~3whaZB_Ws4*?BbXv0xE&zO&<2t^Vam>_UL%QM3LGF>z#=8G<1vhu;WdQJ-Q1f<{(_*IbJ%g-snleN6A}gAZxLxQ zhc>*69Dl)d_s()?LqB%-_-)MoY3iJ339%neAM6}gy$H|GSXC?u7}*ZSk+#Ma{45nq z?69>gwnK+f0uoHLjO{r)HlK^}J9S>0;vJ}0Y=)_DA_u9H z3986ip9Vj_4wwoLw*<@G2H32EoceZbGn7eB63@@#DyG#$)+c%KXRwN8l8#be)VW}t zkm}?wtsZfNlWpA?E8WcO1xeHXPRXb+JM&Y;k=A3&5_rZu^0PYQSA}Spz|72g_O2N! z)}u^-`Cu~Q6WtMNjSCvs7HGLEm*e}83mkwcLdqxwJexiotc+p`cTO3^!{d21^Xy1*&?E_5@UirLFBoBXHEx)ZfHBd4v4ZwSR^mh zX!CNOMr>EbR};{^&+qwIN1DtTh$~&g_Zb_fjP^Sq@Y82Fu$k1woL#q`C3V|Va}xbV zRjET~@Ux`-P^gNiYB&{XU5W|X2y&JE19$7JA=<=YbDB+$uMI}i1ayy?BC;- z5l)V-*AuR}tX(O|Nkm&hQmzGd9Y(KH%BHg}D6pz1F898daknEekulrtwq23!k(2(y9a%tNyy&4ac(%UcxyDK za@q53g+JlhZnl$$?N_A!Sa)UziQuN3mdWU@SQ?_)+U1s~mr@g6kANQKbCpYLQ%W#i zh}OTgH_bSuqnz-B7mPkbVILEH`qm(3MCIns5tZj%j?nwoN8DGuXW_Hd6o1)>WK5Gy z;JmxZI*+FrEu5e5B5?f?$ET@c$@%%~Py&ytj*EInB3=RSlG%|mS$cVA@ILX{Y}%03Bv{4-(yQ=x`-_m49(K6G<(z}$E^zmAYDNx|o_#dQbQy-Sax zcQoj676{ciAv1`9s6@mu5A_$Dy$%&^W-}R4^FsK2ibXKX_|7dpt&EAj&MsP4n5KR+ zbcNrWEAy_!z#O_d^)4&3R138gx-?~+j>XGG?WTl7955H;rSgg~SlEEL^h0ufJ`bWP z)m^X_%a6#3KfvCB&Y*L@Ue3p|;Q2lghR3O`q@FP{Jz&j_2&rYGz!;5rbEQ=t&eICF zFe+6rY!A(M`^(gC{SHFnNUi)2>u(`tW39faXqwgw%T*8;3i9&lmTiJMe2U$V{2ocx zHkREG%gb|8thK=fX(ZhD%`(5gw1GmMsc=PCy2Vtl&`WgMDcoIe)KXJ^*W@mlmGkT1 z0fF-R-MzyiOjAM##S$)2VBJ2!Z#`o8nXe9wP33&5rW}$x7?ZW3NeXm)K-KJMNnNg- z7CR-%HT_1z;@gmxaTI|mRU3nHQcQj(8HkoL;uindSN+ot6=t`Ix8A~-r(=C=fBp^| z&i#<{PI&|Fvih2!7B9*7H}#fy zYS(tYcq$iXY9_db0qK{N$32RGiu}iJe!PmDAE`PGq`XJc+?$M**D&TL(p+*2i5DMu zkV$bE#<*}Zqqby&@B#t*hJ%DICSr?acI#Dz%ko?pQv+W2DX4uRUU|p>FRzuiHCnPu z0fjTIh=Jr}cR`3uw;cwTJDMn1Mti6se%~x`9ERXPg!^Z3&>L4PEh!C6>b*AeBGG0e z3d8d9HeOzDh?N~Icz>9ep-cv*+R<}YlTeEieJ^s(ESL|ogX#$IGAuVAAC#96QRQV= zFUiSuEJPEC%kK^dG@E){dN|ZeSqYEc8}nEK(@TwbLa-#RW0T@c%^LY??L=VC`bHOb zu@}yE3u(g6oJVBLpx@+n#C=_iFMmC3ix*YXM}hc}?MKE`$#R?76z%7l!JYI_cEjM- z)E~t3dZ&GXwDak=IN2@cXM~95=gXF&6v}WI>jY&9m~nkaZJI9UXus=uVT}yepdJN0 z744o*In8jiq2&px>YIf74$knB>Em_Q38GGr8WSp(gbC3CajigRWAeE{rvG4LO!RG4 zNHYqB=nq{ANpehqm>xA772+#&MmKi$&ch4AR81=%*T`tKmW*=3e z{{f~8c)pn@yxwwInc08}m3|SpP`eq7ter%{gcNQuZ~XZSt|sV97&FsLm=M01EsAhn z27I);fZr$o3C|Di(!Q5w6quNhZO{dkOP}`s5#u??^wLfcRPx9-cy_O{sSKXJ|L2b; zezlO^y?ghYxjptr$K(Z-o8n*E6u&}SZ8+?>GjwiRLvZi+&Fs-& zazUBCvwSHj>3MRkx33S_FGLeh?v^i2c;5l(BmhTc7w26O*EU6ZB7afL?57 z>#W=5-N(eVw}ydlWL@uaPDb@p>&XI5G|MD~ zVGCy~c)~xIv?anVzOQ4;EKepylLEtJ0(*wn=X%Ea`NE7!?nqb=+?&Ji7HWfIgU!7* zM+a#cL7adrP$0yk`~gtO!Hc#h20pr}HO7bBp%_^A`L?}iM@q+*F% zXyxYBQ&`uk2}FsJzxrjxyBJcXPiAeJm5zNcw#`JS@dESqwkF=`*NkKTe&kLwj*F;` zb*Y$Vo`N)x56G65=2Ydmtg!8C?CoLDD>DkjwlrJln;))RCJ*DnDqy&V`xZqHmq^ow zj~q(MkvcJmC4rHA6D*Q|5fw>bqkHmrp8+rTP*)Tp^CA;0#|G0`O-(eFvf)q-}ebR47G`*-IV<)N917N^AwKdh}BLq8yd&U6_gK*{JWX>r?Xg31-_w2gW( zw_Aqbnp8gds8Q&)F16Z-tP4E?X*QN8jHTuX3iLA@zN?K^m?xMdH@2OeU%LqbUpyk* z!4qD^J{YP@-1;s=kBmiVJw+G|ytQnPsw&P;6m1lw)k+gWh>@R%xzTt{ixvo=vHQRUcgs=D`o8Qy&wJJjaKKEd*d0_X^abY9dzQ@%!6q}+uT^bTW{t)@xV3@ z%%Z7bQC3f}|1?#!-E7lfcA1s>`vB((Q~HmQVFgMnbRDnDtCCRsgXYs3*I~hsvK}fA zM9$?SJ!VbImzAWkn_o5{p(eaeoOAP_#ac8cPbL17*5v+j?=9HO0rhsruRWf9r`6PL z@^~%zd24#L4#Ee2y-0AXvcY$KTP1;Ep)3GSa)fpCToC=irmdb|-B#;hqC(nqu!{vKf{g7W@VnmkRqDx92Vf z16)7qfCEU4%?zvKs}I6QpL;#B_DYt%W!Wc*xg&&X7wkjR+suD+65~916}I** z#irggrL3|@uhBC{63)q;89L`{(S97nv6BQL|JyY0$klJT{RIMffnaV+*yY8)ViWw6 zhsWJ8jdtD;7G6*O0*&R>5Zwh&C)x}Z45|b2e8t&edO)`%t&Fh06*?neCe-*n7Fw(=M(B^{mYu)Fnmu4{zt*tBrd!)Dyh zC)ZrC#DV-zTL361u>AfZubz799Cd}#P+-ztK|J;W*MyBS=p7x4@HQi9$#x8Jd*7I; zwnjNKg8CF5rH~K=#!X*xM;4mgXmRX5X1f~K&kIeh(!+4#^L>5SIU-|=ej}tV<=aM( zSmWO)dFCw<&k9fB<#HM8cL&OzK9nTgBT63`g^rcuh478e3Y}AGQRi0O=eT?+me-N8 zF3@}Pzm|d}(%XnEeXA!os!}VJ@xl3PCLY)Bq*AMv0@wvKm$_Dn?|>qX(c0&Kb$9M%*#RXg4$2S3j&(IN+!}J+9*gbdC2N{%4$O|L z;DOgCy(&=2zYPQ_o~Puoz6C=~Z)m}pQHa#>q*mJu(0ZBW{I{V_YH@_x;Nr#VaS|M= z;J0mD(%&RA^L-jN;kT|Mz2vFn4#%K*LwDVTA_?K{^!V@|Y`cl*$Uva+<94NYS4Xy8 z&}ts8Yhhd{bvK32+h>zsY_-jdtDGfVk{DLefn0Z|UZjor&p#pZfCU~&rrTu^Q=rYB zL4Q_A02$;z0m#6#2fxOn_E67-zaU``9Ra`ua1Z#j2Uqrxz_dazD-%-_w0QrEYkxoO z;VAIWL;L<9k3XOOqNsl{)E?n99`c%*G7W4w6f z$Oi+r?GRBO&9WE1mGyn6g11WTVHlM*AYOAazkAbMIQRXj12kkuR=NDj6>-{zvH~E3 z9?n~=tTih8!1M1T(t2Qs2mN}Zft#PSM^E_t2Q1#ksH~8A%gt+SRpC}?rZ(NLe^F3=)`yQo_Li47;OyRv?F^`Yg4rv1zYU&SSv==rS#|KO zYf?p`ZnE;v!O8GQv4(bw<0p8sRC?dfKOQpOeS4ZQaa``0+*x$OhJH^zE?0CxR^i-A~?We$96`HD-_pa6&sTQ4^ABZIG)?d;c0=s&5>07 ze1YCMHL^=;HqW*Q8QUlRnF!y^B?HEI|EJcxnQnrBK?G7+XkV@U7Ug?Xp}>-&btsBz zTc&BNJN`P($tLu?UQ65!F31qm^uN#+n zHM2ElL2^K1=l$?QK(cws;wBP8+zLUj;rq9tNX5r@k$N%p@lpwJjXR*AFj!_ZZ4w~O zG@;h>%)Vs%*p02hkwM}zoE!>cCey!8`$yJU|F}T|Rn~m-xDXbj+%DztcCjlkuQwrE zn{xZjZ_QA_I1O)Y_DPp@lM0SW^;>@BH;XDOWuKtd_^8K6TS#0bZ&vRvC71-g3!Gfx z`=*|hn6!oC9v;rkY>$t9!%KFRaq!b}LM5Jz^|_sS?3^u3cqOsCKe&I}TWW3{wU0^S z9LNR|335%>z$)2jytdVr$DVu#5CwlMd39ouBkAMS`l_9~*a`mnl?L__9)#ti+;|hL z3;L=jzk&vK@`vG)%~#4B`YV=iFa)t*;)xVIU)$N)u{-md)2fswk%EJoJl2FaSBK~2 z9ilzCZqq*~7g38$sq2&;@T|Z(Yv`WcBx6)IE6X3NTE%J?rTqSE38jbjsp{mC`#^Hj z2>asFT=za|+6iCmu7A*NvWVoqd}a9=MK+JerRKGKwaWJ5^78q)Q@gCFgTXQRF@VY_ ze})mJ*Id?RL~M5A9e5%#@`vAXL+lfkzbV( z(9&G;uz2uouPAK>oqTbV?sm}eS}`&=9vo&eG>h#3<8%a0`)8P$A%dSVSHn7)_|&zO zQG`Vm6>W;ZPLcoxrip_NBz7ah&VZZ~Na|fj$2YG}`vc%T3`yaA?0=}d3_3_X|0vC} z!u-KVFG$_f)VX%(7h*+TE$zfCH6B7&BGUGwuGelT32%7>l2cRr;s{y;{!LFwXG`5$ z+AXT@M8I&m*iS!M5CvJdTE?^k1aO8lA%(CqR=@=2n>}eD-mms| zwT`6encc!NbRusDo|lWjo$CjAXW9BVL#cfIDCM=8Ajq89O0u$J>)nL$&Ot959A)*x zs=LdFpQ}LmRJ4Buv`|)M1^;P(2)zFD(}q0(l=LFU(b4+Mf*0-Q47U;>x!;*aAgsE# zh4}Ei3JMBkdZT4^!%~G)Eco>gd(-^&EH!SE8%$mqURky99JfsQs=OXdLZb2GUDJYs z{Bb*Q6Q~>G7MGZ?=zzpjp>v$}2i=;_$SVy0tW_Qph?$V5vFmV7TTEZ@S+hN2`Kp2n zaJfz-*)5;~`eN6y|C~#tO46*Y&uK=a>R!5dNQD2`Rr66s7Vz;>o24q^# z^^oCbiPqM3oCtHe%Ur?=dS`gcqI48ugH|vqn6Y?n~R>uAiD&vyLp5 z;F=z_n*J1m*#~6=r73bIpj}aJAogA8)Nz@TqND||Ixtb3NX7^s<=@_OJ%Fe)zLN7< z!4n4zjJj@jpW!U9=#l8}>iW%h{+WyeWgVwtU`gj)WJ!h0hFV7(xiMMQ5fCoOmi^v{&O0n6E@ z;0k@#1H4XVLP*4+@rolp_Y%8A3AF63AvkbukcEL*5fw? zrmb$*OH*`l0nPmjA!Sq=LD8UR#~kn5AbRPx#inJOY02{1!uOqCfH4fbdC5yf?SbZ{ zWRnh3$$-bSkNEUy`0U)%*+Rh^IgQy#$97kN`oJ^+XapsvERU8FhtbO%i{sqH5avpb zPM_f`3($+i?aq9(ae3U!5<~bLv?r?oGNn?~F}7v#6I<;#y-E_}&NAByCz)hIF}|CIJiYU7^>MjkC@@Iwv5tkfie0l4|}!hGa3a4s*2o7u!KyMs(NqoIp? z>f~90)S;x*QxMK67%9JX!W^0qsws$%bN0>Ip!r)R<4spR88&{?)b5GHTq%XL zPQac4z3VF@F4p_U5g6Be$;>F|dF%@-OIXe{K`%46v%Nljz&JRS1^p>t)^vNg@Eak* zvo6?#TdWD2p83+!}V z?Xkq*`mOm#bddxlE!{(al)4tl*fgZ31zKV6rsy79o7`KRS$^6DP;y$DL$+Zvy=s$~ z9q;gSakcXsW8{f!KD`(4I{fNi0Eslmpy>VYzOL%_%~2Ohoqf-HUx%2fon)BF=uW|2 zm-X@d6R|)d@@>d|@>w>F6m{gbrM#t&BzxhxY9Hs2mf~5g$p8>j=g>O<^&+)Reg`Pd zYCcT}phVe>(BY}*fR+R&h{mTxOG+nMJopP1GB3nU186#@k&rj6?30ZM7z-I;cOZ*P zggK9T;4o>z11L13MUdS7^bezXXxm6?xXBLro#wXwiQ89Fd)&9O17?gY6IxPU;ZRAb zCQh*im|~ppzI~HBeF6}&sLn1BcLHVF+ElhDf7Qfn#)N_nv?kYt<}lCxC82eEy3ZeS z&gHTxWo0t@pVEw5=bA%@*H}^c2LH&l`OL!@W>f)6)n9s><}LCyHQTni?l7Qg#NPBR ztmActQ~UTERfS2Z4eT@Jj;67K#&CWrj#NDx4}z#wmQ=G1`BmCNvYgL{V=Z7CI8r`buU#y@ z>bfkk)XUO+oanm&iMD!vOi`HKn(St(U>k3^OvPiPrmFxq{U3LhdE0ou4kVXE5W>s?=HsVQig&(e?@=Q_uNL3Mz{h=Ull9h$v=)He zfYUxjE^D{S?Mfbjk3Z<4O=M88<7V0gdRG6zvXHw!!xT*#d^1o%v;^sN3+) zvi*H!qTIObAsHLcro#%6b+H!jd) zjPkX1Y%h_5VBB85*CH-Ca{7V*#S3>(q%mUx=d{>2N?)alvoN02gw=M`@si+(Oz7cC ze?0VvISr1Nn2kH;u?++HpU@z|vjl%`{D^pqSesvE(^|g2Si@u;ZN;J0vc&jgq+_+= z%(y0p2kE3der3sRpmI;uy3<4sS-w)6zNERimn=VFZ$mEO@o+pCPK7ZNoDWDYY+D+q zAe^i02s=ueKQ!rxZZ&1r!_9ZHcs($&`q|{RYMdAAe{s91?9QPfGL}WQ7VF3?ad1ym z0*r6UVH&6%maV@)yj*M^P|qEq*=+bOip%~-9ai)LG7W?Lq5{kYjP=qcaZ-$uwDw>8 z@IN&3J9o^U$bJhzi@$0z(3RO{|LV$7gnt$1|7y$s2hU8?Qxmw1+3jkSJRKLWG|G{i z*%sZ0$Q@}?-~xAu1*{J_SD#w@;;rV~?fMS@@thsfvnch0x5SlZ$>2MF3VwU*W;A{Y z=qh`x7yFq!ZnZYVy>lP}`$w67@xr^j{G4@E7nseeW=Xl@{p0O_u56$MuFQb(IX)93 zn_2jZUXu3)m?kIkl)iYj4KXZ~XRIG%_mnhz_|%lU!S3tG7o$tdJJVs?o;-kS)wo*A zJllLzmG4@Slhd1<-ireeswtj1QZlxT4SCZ`gLIoZRC&O}eeGUPVN;2cG8{@8zZe-j z6u}aH)5TiFB%Vv5@w%w2o~^Gler)Q7DtvN$s)saOO_8=x2D}0*|6^JS*A+J4d_@?W zZRe*l&q>;8p7AK$Ab*1loFCFumnnC#M$ zNwtX#H98y)ggg6B>k6~w8^*cuJ%_kxox_;yDo}@A*K5!-WEs&$DtK@I0}8ERMVY%; z+?X$RjPVoJeQYRNF#zwOJ+mT0+t*}n0&#)8FH-XYhc<&(OA}M}uk8LsdTAqFn+5~9&?_^~sSpi}!oiMKA9@tzG&xm={8(_yYfC*Mh?oys0_=R|E9P&cma~@jAQel$z zlU7mh0YEk}g#LFdqK{E?iEjgWf|&W@B$zua?i%u>k+ z9%c8Ow33qQOe395)Nk4S8dN{9=S0bZ}8H^QEYAsB!qosdqs5G#4ZZ)SM%LKe2jBSO00gK-QT%5 z$wfAX825B_MQ;p&GA!x+GGQ{IWPQ%&hT!^Z`eXvA_Bxh+u1xQM@lE^!<)l5yXf1cv zpHMf#SPkdTh`~!1#RwBjBD|k_amxqc$DXz1%883+NqF+M0&Uw#X#=o0Q6p2eJ{DK_UTu3Oflr@{TEHW8>go>1+?_gD;@I z?EfHQe{-d;kQTm%z+5SzRd!z_l;=3n;04TC0ecnp?m~>gf za^+<){*7kE+s34}DIN}D(4jw9Aaz6&%B9YOJY-Y((c6V`2?0C}re+|+?@4tpv##T} zSW^Uh6k%pWgZp}>j!WcjG3&MxaN%-y6ao{(nOhM4Io z@o)yFjB9RRXyzJ6C{A1^v$3}JW_!H?zKFD^L|WX_2H;6(zA!_3)pvGfn3VF0Id~-8 z&B@I;?ctG-pm$v0-HlBjRk#xPH63;pPX09GZntk;{6hTd z5eeV&%ABX-k8E(R|Bo&lE+d>=6_s^?gRC^vb~K^Pr|pnNMcHsnUzu0;B%(Y2%xOeR z(CF(sF7c&Q>qY@n%s`*)w*_J2{1$s45}gabUvpusAg|07MaUJvk#GMQd>~UaL1|~M zrlh3A5JqK{@&$2)#WY3QokJDKPtmFPm(TbVW>)+NxYmLHIl85 zi>bP##uYFa2$#WRv&SEO7K-U8lo{#3y%`Opp2!vP1zKO!sUyx$m&KcB%inIsLyy!M~xgr#FL1Kdm6~&!6D< z-+%l+Bj(@W_+KMtFW{IIm&h1l#Z{oFE3y{+Xo)Hbd_FojcNJ)K>zKM)K9%b>eO_*2 zBwql%ByxFdHr-Yb%VBzkC7Y4J&j$LN7Iv-*?2^r}P!hELXcW-g`g<^Avk7Ga=w0AD zBHYtw7ZhrQV9q79dJBu1j2&+s9jlTLxFn?bK?=Gv4C(O>-joXimV~_$JN-%-5M%16XCht+l?l6mmr^Ir zSK9~fq;yy)_6!YG(I0P0*yd7LoEigDO8TiUz9uR+4fSk2NcVG|I?)1K7J%I}?{_BU;K zSgZrLrtoa&@VjwxexNyiRNiJT8t}Wg`kk5FKL4)( zH$(=-3rTmtao$$s_3U&bqEy@3> z2uf=z$0ofTtKyn%*{bGy^|$!Jt0(Yw7>0Y7?Sk9T!sPxey6dj~{T1d}wDsnQD7=6p zUci=-$F8!HZt{{fbNn3Yk14kd)7{5alP9~0$EoCdHu^CE^OgjA+l>Q%Yf-k z#?{Tv_-KTdg{a)Uar`tDsRmcA$MM0hs)E<8t0*%ur53@AT||D1vV*ccDB4@1^u+K5 zr{f(E@9l#eml`WGMI^ZILpIcP| zF2ua(Ipi%&>nv;5@;D(t$Z{ z)aF3~*yVlLf!uU=1L6mH2Bj6*hj_t59LeL(_OVwG>I6@(tk1E4DvD(DAcxLecM~U- zZw=$;-V#hPfc+Y!g^Rtz@-8h8!Pi?8JFn1IY;4JI#~G6eOTT0Zu-{>JbeDE{mq)rP z0oUj~0D*x4ZE-euDI-$Q?Z&e@+k5Lzgw*^1<@A~_%_QnH)rntw8t=eDz#M68xA?5n zORU8db&dHqHeEkuLkaG;GIkjbhdsAJ+xvxXl}DWH9$E?fkhyv^`a}G5 zt^VVF1^ine9k>nM1gWI?N?wimgK9uKmieQM?(Y^twLtyzQMNMIgA z<;AG#4sWv?$AM~QIxu>vM0_?5*7q#HT;2G{FWc$+)NOK~p#uq2ZU>aJ2m}<$gNag* z&#S#mMWOKJW|!xVn9NkLft0fP=~r=@@&>IpPFqgKwRxicHqFMZi_|uq4R8pjU^2|W+`&5 zFDwL4NJI)uhH2tTVS9GW9}u=D-?GD}(efhMWU{DHWXjY|b@UJb{WaJ3tWH*!)qn;M zSo?y|_6`aK+*UDT_(IbQhVq~kHHC2fwy;Qq=4DyAPB&*#_IeL$LaJa}vdLqZ7hb%M zevq`}F1amaV6^+B2KU|z4M@z`R713x?k@%{-3DLky=&j5yRt~86Kl(}eSd7L77O2Z z@#UZwaRSe&16tL(i15K;xAuBFkYcuEBfwI^GZxOz@Ez7%X^7t63X}-T6Lerg!4FDA zPa!r!?<=s$mYwqnj3rQD6-f)nVaw@?kIQV5Hmem6hbf{uNLE=LEmM1N{W@+wFUQfr zKH+45xH=BXb;)W{US2_9sn34$*P{i0erogBi2J}Cb#RCUU|dOPP5m7%0NFmYiV0)M znj~Aj)D?L(giiKr%PI#{JVA2jAm2jbh>jm56Ondxp@+@;hk}GAo@-C7v+_*xG!}g7WO@yfWNvV2u0-?actL zS87$nIQ>;I%lKJ$w+*KQ*`q_Q{Ote_wug z&VQ&a^me@pmtxDZx0g;Nj2-m%4|y?ugS^46A>oz${0!gsFg}IGt|#+e>)Iw&n$NL8 z!DaldP0h6*y;mYuQ7Fn(2Zr>^s7Pw<2?4Nku)G0df%xJgcWIcO@zKj)7CCFJH+ zGwI%+rX0_odrgJ^f%`Sb$@y6wP{VOiD5n? zdvYC4e&Nqw=cD6K^5o!v;vTLGk^44`}dvnN6nL=W`>T>?moIRn0;ld5|EV_ z+LPP0!8P6o4+g6l4fJkRrA!pQpMP`O4I3n2bH=eH1GS)`mm23Csd=xTLkk^UOISNH z)SF7vkg&LKdyLDjt5T63+D~=NO{h{F7PV^ z66&mqPncLBiEvl*Wj1*~WioNS>8vGVLaOLKc=x3HeZ;hNy^~#M#LIdLV;Kw#nQbDw zozzWiSzWR|m*IX&+}!AtK!k+3W-Rj|)g(sUwfp4ImXYor|Deb%@(GfSk!Xuot1NDJ>eE8C z+~UV-%B=9w^^JNsU9A>TO`NE;!u>I3&n&7u(#4Oc$ z(D^s#v3}|vG9J@Ex#WzaHxc1Y4*T8i{Av176JUkhC+mn8WY)hjUS0F;9CnNv7rP=J z*VuJL__l_wW(w+UfsHjTcYFYO?EfX2(l~~86Js^GPQ8m(m@Cp*RA-o zYbiN79pqJZUvTsz<)f8Z25fmEKdg`N4fl`dtE=j!_9eLaJ_+%z)qSwMM1iN^frzHDgsgign)?j5?Tn5K*oYd zmo8nYLMT!~fRJFJ_Zn&vrPqWWLj7&f^T+qzbI(2J{Lb@x?sNauN0WR$d#}CL`~6yL zhy8`11j~Q6b+jjwWAyyrE(IPvNU3utx==!PSUOx)pzDsHXDH=L-YAT=^@N9=pjXjs%B3 z{5sEZz3$pF%8}ENVoWpql4sxj=UjmoMGDKW6ANI6_4IY=IH7y6f~W+~JF$6i+baAM z++^G3Rm9MaG}T~j=kqpM=$n~1Q?joYh)f|z}$w)>*Ea-z=qc@YGCQvJf!IdbRk&FyU_r2?u zGdiR63UiF@WQtdrdC{;VzwvadS&7eg#VwR9&Y-YTh*g&Ab;64<`@R|`esLy2-ghzB zeX-H)!DYR%(ra`2`l-9(pwI=HR`***pa5izd#f}C01wFDxMHa>2MqO&20dUN=2;%T zP^xfa4BZr?3{NQ7k&fC)IF|`_p$bgvbVil$He2KLGulpvqb7#+MNDf?GS%e!N23zT%LG<)g;F^(0<1So@T1 z3oCd7PB^gt)^eABD0Kgo`2FA1^!{J;kNr^gQyTn#7CQcOC)U4;iT~jT{}faH^@D%@ zi~r0Sw{U2gcoYaQygxcEenD^iH%{1p_xtYuA^3X#Jw@n$;z7YOC=Fh$^l+=+LBgdC zhr{Cnf#`q2MFB8bz}9#ft+Dm!M^dzg#Y#Zkx-Ye8U+?mgUZTicw*6A&4@6b0y**F|!9*Bhz~3$Wt>;0$<|LU2Y@-Wu z750XJv0FVwl0IH?DLemsvX@2G3&-J~WT1<@v{@p~EvgUHfS{kdXKJ-Y*q8~D&4IXt zNTB&Wl=GH0;a0y}-()$E0ROb3W69SPR5zOSQZ0$T7~=eNvc#dQ?0U}b5UDHH{A)XuWRhk2fRJ@5 z!|_3z=(Sno;X};@K;abTj7|V`0V+2AI{o0ufTnY4?fi@28&BcR59TQy)KprNE;|o) zxHjl)vOEI81LP8Pr_LoafP(AU%LepzAJu$VAHi=6T>F;p3I@xYh36&oK|y-P4Pps_AY{QApEV*`buVSQ5K zxN+J1B0JjGefD7X-U{CN2OW2n=T?gs00BW)=yut>gwGy7NZ;cJpG>y~?JR9OZh6Pw z_%%QEJX+C|@0DtbK{`;-`&*Ox*lA8qaI|C5?o^R?Geq0fBy9%Mv7k-%Vofb?H#Gjf zO`e;SH_;@Zj-Exf(i@D*TIpQcSZV?<~)>5@(@kfU^zo*#_m3i%yKbRwMm1`9w~ z;;9e&BANxdXgceJKBRB%hL7#W0Gb{1)&4VR+1X2!F=1#9VB7ftcF@q-EF7IWTCAHj ztI~T;;UJqmk0Udl@oPZ%Okt>wZXp7eIXVy2iQ?I^jR)y{GSX%b2z=eZd0Y$N#!wm_ zu4RfgbSeu#SfZf?v;?m2E`&vTcOpY zcq?#xvtqrbKrACgJJQmNRy-S=0We(q5>8)aqn5+$%qojIN?vk%IoS8Kmww>Rm$Cb+ z=K)fS1GN+CR|eNFe=$5UPC0#?1w+@;4L-9$3D}&~B2z_4fF!I|9Od)*iwG zdQr@jbS!+HVATYW$*P3NC1sM6CE~UDLGj9dGO-FD?c+K zVp=)~)79Po&^Ow( z4WQ^~M5>N0h(e|NmKK{M^Suz7yL*zJuoe~u=(hCvMW7EKuN>k6;xB7QZV5Iw$de~} zf#EyaFdb|PUN>`%4P8HM2)YQb){0ShT-HB$czTQ+lG35b>Y5BQzF7eDdAU&%hz|OD z6}e|NY~=K5?wqU|*u4v0q+npLViRYZixWQYk`TXnx$M`eNEC*RX!9oF2$-PiSWd_ps++6x%d?J7ea z%63&>zW|8*&Bs`eu#mu6*W)u`;=F3w9a(!_?gE8jH#>I?dY+9D8bpLmirYqcIX6L! zse;Q{&vDgXdUmAr*c6~f{!ts;2Eaaz2fuQ^Z1Yk9_!_|cSoo7|lwbG$xX$39#;FZ3eo$nb2N5XHN2v!^# z?M8xiw&eA8zP#ggH<(ml6<_4eRpPe%G`4X`Z^fsUTl=0!e zoH`!gT#08$WY}2EYV$u251?K6gkxBovMFU-Czz!TSKX%Yp9UuNG}~XHW+0bH2f0O6 z@_uqaL=Bmjb7k=#xstgk^IIj8f@!ab_i{tl3gV$5K%*=bLJz56amTA7t5 z{B4G04D&IA(B2u^**pIsccF9pT}4Ko=}=FPzXtA=AxC>d^-N^~NEVZTLzrHz{uIN} zo)oGgmM$L1vBJ?_V}4I39fS`?KTQvLbduqcJiT6EHd9&A`S)ha}j-Oxb?FH`I+~0@6 zHnlI|F$&dz^m4?-U_~$h9hjRtWb@<&hua;p6>!67xq)yAt z4I1Y*p6eH1^<~5#OV%I(c)B$bPCj zGY*u+N)t>*j$dhhB+1mt8wi(RokLDxF3;5@2ULPK z(UU({=+4=$rQ}qx9}3={?+Eg$2IO8*0YXr4EcgRZX#6byeilRjZaD&;<9@FHujR=9 zZuzwL!QhMhhaW6vMCy~dRka7dy?m*@G`g=EHZa?5{>>p9?i|SB1D7bV;W-Pl#7u}O zfNclAVZmdZa2%|Po*K|QrAwEpKqJ>>+A|JOnmMP%)m<&XeeV;!>P*=m0AF1;S~|6BxB4LFQJ!VHqT9^nDPpvGN~ zq{VUp0C2=n^FV)=s|zBE*r4P4SjxIM-MuLWyYF`>{4*&>bFz_r`|vFvo7=JAF6XBW z2WO|_= z5(31BNCyn zyA0SiZiV_q1aAqL4B}oZ3We zG!31JD&cQeqfPHzGh^Rs(uCsBai76i1qh6;S&8c#=tv)VfNlZ!?cctQH%Vjtq!f-7 z6~V$%An(r~KkQ2?TwLlbc-6-S)bP$FUy#PwFr{uDgCLTBISv+yr+Bw_e2A8(X<9pS zespbHJv>JMz&qKNqnpFQU``0qBb9`mby=w|RL;Ps&Q8tq+#r1WA<-*NvP8p`${2ID64!ctRytS1p=TL?h#bSY zskvxRNM9Fdl?R5H?ah@+udF=N>*ci1Uk6WFkBxz{!BZVXp6x{&+U65%{{^p)OhA-? z&7RVZ=0L2T7_8+_WzFP%ek_G3+wjXBTQBt&of*OcKaX3K)~jUWVGdKu1#V}Rfo4(a z?2USB35#4ss{1z=3`;b9FEJoB)LIPWg509`pBGrpeH-cIH~$8tk}nCnMEzFcuFnkR zrHSKl$=^R(Wt$_MC|}3-wu2}=v1i++%9AngI(kpezzY;L6A2IBmVNmuDI`-^ji!}a zhR@l81Ozm=0I0M1%gmb}qC&ub9UBWyWPn;$Q?pe{N{T&(J(K-lDIVWv*cWpyhGRTtYBQu4 zLwkE`t58FWNJB$oYg2P;dwaEkaVM2pr_J>v{8xXd+fd7y4@t|L8(CTUGMm#`e{v&l zpv)~RYFk~)k_wggdmHe=>C;R_o8_xe%qH6?bu0Pnhut9w_jNNHcZS?od<)mKG9qIr zLx*!RTzQT#;nuSFB}C}DzZ@*}GsoGniDuSc&d$zdzxnD|I9uLPQt{Y}Wx$ue2Ubi> zj8uhpt@k0;z@hj<-Z>YF0>ZsTOcW4fA>Vh!cUsL`E-dnBh?u9sS1?gD7IA!cdw5-C z_p^0}SXT8e$|m?Ww;q)u*KUjdS`W?I!mQB`isnk{tgGDj7}~C8pcrcu6q2!~Nq3HH zrBhq^`1r)d>uN6QKXQD0=a9aB#(f5nN!6cYXMaOgIDTC6=1P`PhtaRAyPpPHw1j_? zYE;|yRayR?BIiDlbUTHznOM@E6o2aRR#AOrg|v|Mp@ECz+z*^{1!in>8Fr9W&oUf7 zbPR>P!3vAe1I>cTqcgXDuJKQ=0s^dB^H~On+Fsna6gxY+H^N^%3f70p_i49kOq`H3 zFHPCz556E~(z4aPrLwc>u={>>M~tMj%-kjUH0}0_dG}4dh%+Q^L#w5_(`DMiq>>XK zzmJ=7JkR(zXqg_ylzsubQV?1@eQ9;vH@~TXoE0(K>rqE}*94EjQmV~diTnoe)uU_V7!;vlIg|IR>X?;si2807BmuQAtu>;PX=XKK~J;6PN8 z=T%V!p$Ywsu#nR9dM4x8@`{W}`AldkwAEYSi&t1{UNvXcQgAqiMX^)anuum7NzP}} zOxLqh3%r(*&3xOQh!7jcpJmXhR6{wSoMh4L~+SjBI^kZGVm zY-|%!ei*)LD|DZsNA|!A5PT{@J8tc+4laC*pudpxVn3w;S&!>1Ilr6Yo0btJp`|)8 zad*O+E{m9(>V%o8X@pSG7PVLvgvokB;Z7;5dOKXTdOKpReaCW!Y zZr#z7eZ)nfWR9KDWQIL)$0@&BDI3Sir)4{LBT|@<;Bd3Io6vkGo^8H{1zNYdzG_ zvL_cs*IZ8YXK-$Bb4Zt%2M%Qrw=#M1e8#KGw0t_9CZGk~=4_@5W?hxuW`me;n39s$ z;?wX7OiA)a+IJp5MW#Bqx|4x%N?e*1(etxYVb5mJm&&xF@{G zWYoS&-^?u+S3ycI#F8>|a74}S3irwLpzD86O!R}pl!!-8^3V`BM5H9eX6{;h+}1(g zZcAzT`fJKLs*p`Z3(Mi-XM))=bMxhXz z-gMZEOi#pFauDrQSbfrrE$dON%D~b@`z7n0&sLvPTU6F#=7%|uMpNsesZZo)F-QgY zLs!bhnWtGF1gGfd=9S$JHI}tkx{V*@-8Y9W(-^m8Z+YBQn*Ga%OplTSx$dYId)>LS z_14zhEsArDQn@gw8N*BENh2?# zF7Rt*s`|WTxSCxm+o05h4!Z9y$5Rp!-A<6F0|hrc%)0-yrJl9!K1=^_9FeFL#${tD)f9$NGJ`oO;cc{W;CH3w3uzyq#y=<{*uCggKuJs}k^cQG&cmXHU^v zJ5w|}ottjdN5yI&FZfNVY+9vWz?7mUCwGqRUbI**2&S78V>QbXmic*J*d=BWpDMw- zA|aUXPI(01@ubSF7dDj_2N^MpFF1$3^B~TFiix$u=8ae)%2kNhhpAdy`&*CIS)F^y zqre_z@b*n|qTAS1(RQQy0pXJaA_}%yiO%WfXG%r-yq)6l@i!3OZ#DFyZ5$lmUF}lm zrgtWM>pMHH!{MkmVOLpi>s+;?Ujo6LC(a>MLk0>&BoC~ZFT<8z1yB?Hgt7-j(7yB0 z=3=Z(jC1^IZs_Nhu^a6L1$E@IEHY|(e%=b~TTp_HZN8R_6Xhw0;lbun%uQ2Fy5H!F z4(XX--W9v5TTu#f2YIi@#9y#GvL!t7(3(D!2ZF|pW4MQS@i&Kz1->S8KEE`6MJvCP z8uSR_{T3@&r*Ue!>rZR*Gq%YVHs|x017rTXmSqXiWBqRZj_)PDsIgmMk5Z^yPx6oA za+p#MCrjX=2)i&>=lctf_{y8pDE3bj9Y%LSr0hRX0q!@TYWyqKMx5)LtYx)c+tv< zmXX?k=dVgQg}T{>BXw#Cc>01))_Ts`D;WSH$&(23az@U2F$dRbCsZ z4B35maBil$!Q|x}=N-G*c06wOOgK7tL$GP{JLSKf*sp?1Cs8mc@~NS*hndw@&eGeK z+>ef`_X-3h2m-gTonqPYHw&+>KP^D&ua8{7+%N8HKRF<(u_$s3dE*#WG~7d9tzX7} zhP#%Gxxmz2)g zYR?Y8R*TOL((;8v!d_pEC!Nxf<{_>|eUvvco+R@suLzy}<5sbk<(Kh6Ua{m_*$2`0 zkIq4Fur07C&}fV@c?kdtUJBhj0%A zHB@Px*Nrbp7hfcwz0#I@gRMwSlzeUd79Xo@?xe#I5ACC5@$^DLQ_tI(gNstAt6fU? ziq^!fPVLe%^g`NnUiFN&pWm!{R11#wN0}MSJW1MHzkKBM+tsZfAi*?Wrq&Rm}@f*dAhy{C`!EgK? zUiUlW)y?+GzxwL_WHZPpy!u72((|c%p1OJ@T)o&_BQjM&?xVMP-qv4eEBm`&pG|H3 zf*m^{Us=7W5{3C7wTe({wR&c1g%bC04SDWtciSHm0fLmEVb zTSWug1s^nt(&Ei=ym(}_?MeJu}Q;;SD!LzEn-v}$_#ap((0n$sbRuoFkVU?Yj_ zfhqO#hQ<#!M~fjC<#8qr!>XsTdhh0&`^{FY22Z?3Gi)mu{=C-@9y~a@)vH|LRx2j4 zHp=e6kEwF0>x$uY<{Vrurc=i71A{rZZYgtD5Ex`vj>mn~Z_4vhya3o)}MQ*U4& z6c)&t7!W0^W#zKBTs#*eUY2_u8$B3%IH%72$qB0VP+9pWSE6D^WhX6Hq~*O_gUN-* z#?w#G!}>C+yTrG(@HsODrEVYBP+FZXBUJU7;USB)V&C@&!WQS5;UF~Lad%3)Qf&Jh zk+!orX70ym-QA+EsBHPnq+;|Ee4mCKQ-83rxmnM`Iw3p~2~V7?{VX(4>Q*pc(-|u7 zbT;WZ+MLvxmtTU-OfPw0$W!9Z)TdZs@g}fp*U@H$DcNP%8BoRVVE_`eaH&lSNNK~Mb|2m&PAI0BN)JkD<3+3%)yCZSuT@|&Le|O}13g>W%H-{<*FMI4 zzMmN5FFM;i95VhZ)WE`U#O7Jl_xsJ6#$gT*4wf1C!#VKev+c2G;jEL@Uv9)bH#$k=?{t##Y zPH8(mZ!oJpF-8%i^}RKAD6n&Oz_^Enc`aY2D4}+q$0XwW8x@vUq=yI|t80NVeFf&J ziV%FInWz)5S-97UE;rTuhg=(Vp`V9RlNI%Jpm^JDzQs4sa=f*40Tac)*cC?BGoqO+_0TbE`pxeY_!Uq@abCC{SX^@iyB+Mp4ZZmPZSn-ZRQ2Uq1o zItN4;^n&TR7F)+4vBF@{8xQU)lq<;H5)xQ=(^vK~zj=FAMe(uS8|zH-?>L8MX{bEb{2}WHZ;PL*|JUd^oM-yV%9w zMX@*jnqj=G9WL$M(nc8+>Kl)VMlh4xM1|waY%ApBRByC*n)B-2P#p=5Nh|9$R`ett z8%k1&h2M&bg}`yAn~T-_|L!fOc4^gj+^u{|^fH`lQ^KojX9gE@$V^{X&73o_vEWsL zBr_(D9L{R#J0n8vr0|-}*9(!sxQs8KYQtmm76M<}*)eRVr%G3sx+NuItQod#zLRqb z`(ddWt!?P3+KX3hRv0;ZsR*}P?zWs)@W2OP^WWiE5%MWfuF^cb}XO zI(E|>)8FSAKzW6L@3hAv&4P@VU11-b9IBu;#oCt9dIqoWgyC7P+RJI@3-!JiT;Yth z_JSsRP>Sn)5cf0m5JBON0jUz5c=5V)`4*}=aw+Tn?!5G7A)RMyU?MrLcq@+()a9d} z8pW8OA5}6i)ivNfx+600pB8dwBx+2vwQD0$1|IZ@lIp2jDHLS*wAAQ47gv%5#?`{u z--oS;uy)aSAZJ<0wcT&^j$~ZEd`gT$8=6h~p*QAxaRmP8s$1uPQ!=b;!=2-BjkESR z$Niyu_41JsR!`<1J36I$3aSsY&x;=20gwY+a-zUU$nTV$@67x38FaV?#o@7if%3|? zjyGQXm#JwWuJ8FErC!6jetxDCM%Av-u^^Bv{Ri9%F z+*}Y3?!k25C^lB#>`+UY(~Fui<{lqD&&YqhX#ZeA&I10{nUMC$8>StS$P6f~OPop; zl^Yn^l4DWHYrpLBef|0sw^SFs9Uwg@PgHgw-MXW;5c*pD7R?Xk5!PC&+RT=1L*-5$ zPm{~MTN(Jka1!P0n&Z>1@vtmoPDK$sH;sFz)K48d5|*vq*WHJv<==F#DpV~Q=Rhhnz(FSOa1K&-?~nZ=*(O2vbpKjdNu!@Ja($UX^|BA-8r}B zv4Pv8*^?>}45^4UoQ@X4LUied*y7>8E=+cEGoGkSL*%T52A^!{=1V6R3Nh>q{lMKM zbe9FM%6pzUJ%=vciF!%d%%}L~4OeAKrA5->v-y*c?H*W$ZA{s3(S7kM$%@_7)o^Xv zgD9Kgqd^&jkw7iK+j_h z57Li{pw+WF4Rk(f$T;3tLO(oPlAFKnW!M&yDWsWqsfY7*3+!Xvy3@RB#dWKhm?8z{ zYbgab?o4~wK7_sP|ehf*q)f_~k&3F(n3if=ZUL6Wq6N7S^+ zL!ES_o-e3RK>AO%oXL*;hIw-)S|zvYECeO5_Wpsr^B}K-a#x38KTvU?9xeZGq`fHNfsX_i+{m+jz09k zoNJ=nz_6c2?&?h2_*<0A8)WjQNJ28us%n?LEj5{M!r-`oB@KpjP=@#;A-^qNqUDq_ z`U4}5NwoE{vjtX(3Kya~&~aIb+6lzJ<}8x^TT^)L5(3_cf?2K4+d+MxYXaj>+tPM#C`xzS|WZNb$bGXn-Z@UXq}6M6sH@yrp>loF|e_&&x}l&J#<1z zHFqb36s)eFFD0l-|9lB?|JQsSdML>=Q<`&jIDeMtH2&+!k*uSxk>1e819=+552FlQ z6f5oQf(v2VZy_kOE%brWY};jnVr}>5af*XD(Uwf<;Y{IASy$?Cd|oKuj1t8wfBD_a zEF|SZ!L4_>8)>96fV=<~J1Ule@QsUdAVfV4Eiy1Lvx~n<3rDPFB9f@kZXaDTtiQv( z*aY!cuXiafvy{YV1OMCL12m?YEHQh6pDdP-D9K$dvkO{!q*jTn@seJ5hM#nIJYF1{ z#4Kyhm}Hx2we`g7`umh!|E;VJC8Z`Sn4ZL$o@D?s!W}LUsL&&a51-#_cDnRQFM&Zh2szV~sFynv+0?upZZn_bG-xpNR$o*@L@(>y zEiMs9S^dE`CbUvQ8J`lrB(HLYL`M;tphL}Km&+20-Z>fc=Vaxd^$#$KdWNqQV%s0| zrW#n=B;l-#gXpM0Nxn-aiX}Z3?swk3xY}Ppwn-Fj$i4FAy>wTmd=u_g4N3VkUa?6; zW_a~(bi20!C7ymVX2xBMcnnf#X7zYq0@Ai4v7Kbw%p5p2lC)S8&dCx!Fc79_v^NKgrpmY*AU-(0O-)lsV{Ri3^VxZYkg!tdQu+R2^+gVP0oiN-|JgD`Awh|d4*Ucm6LaC>8V=J z`@D4j6N>@vPr^8^#Yxc%;MxKOf$buHv^J=i7$?TXdtYslp>-G6S?P_hA$o^Gh2|a} zbtEU>H1}=-{9gL)z)aVQ%=?-1brJz544kuALw!%7c?~!FIF1>Fso|VCbE_UYJkQU) zda3zpvQRp)DUE2(QZeE5P?>YgrO(dW8sf@4Jn%R~NCbmOB!&RudA}V8sk+PZU8?L) znk%>Tu0MJ)D*W7>K;DoD<2BYGqbYGzL&!JGnoAI?{1F4+DJakV9j_aA!hb?2ydVi` z2RC&Dx52wR1eVxUB-`bu#}5zaj?X@twbS`-l`?7@ZBdfj;%@mq-6Cdm!$-UOfmpXL zrex{&&0^cQu6xXp^VRoNHtB7D7Lq3_v!tyYpz7*2jDD8i;)tr+{+Nyh{mD(-`|p=l zdjrR7Pu06K*_-dIWdRnrrcbI~5IQm*^m5}cL$5;Mx>X>`(`H_y=@>HaM6rCO)(|sU z<(jY;w#LsXlu>4)(tJ@S_~=mUY8&~`1mA_&*~q?Wzpbc10lwv6qO|lY<7VI1rerc? z!?ECak>Kw|qQ{XpLYL&(C*;PND(wno+5`HuAxxG|XHO@!n@Sy^QVVo*5)u+Jp2F?y zB1nz~=Xd5*!C-zM0xBX{qGM$ro*Z4eqdk@xE0C^zj)8U7%5$Rj_QVXoJ1!*Rt`d*K zR{BjAm8PRkyFXDB{OWjgF7#x6yq+!2tuJU=m79;Z&j&9qP&&{*HQj%DE;DE?913oU zZu%(NTrdOCibD-(@E8YV-~i-`tThoJ^e zUCi9Il@$*pQll9LgXt1dm3Q!p<&tT}(!`2A4@vp+pPbv-3({Mp1I611cWfA-}dyP7wc`Kd@T>h5{e zHwcC|QM$=p)b7rK-p+yHVd1!D)xzM{;Gyc=I19|q5&E7mz0V+mp{FB8R`UfLWyIW_ z@8pZ=a}TyMjt{>?f4I3kCM7hW3H z=e89+R)Sr{=dTt=Z7K-eQPiLakAH4!a+!R!FmFXlbXSrd-+Vjn->wrYv_2poL>nvOn$t7piwS`Gnm{Ny_hdVp3Xky~pKD*ECV^m<|GH*H~ z$^4E+NSbdz8gEgWK&EcIsYp21=_te_Kw$IqMFr1Z8SWebl?cSjUHyA@AMq{iMRi}R zdYmkahF)fY7|u9m1uZ;!DGOn}?K|u!#FP|Tk!)X+&_LdlvmlTqrZCM9_xA2U=WiA#s|_JXgLF*QFwKR0)_QM5H)M=-{wJ;mzgj{zf_ zHl`OYYB?(>>kd_x6x(UT>dm|-MdJl4NK&~4GG*T%rd25zXASp0LRMu7yE6-l8#*K) ziF|OvgSb%DG6#E8X#)_=-3R&Hi) z|9lRcHhd=eAwD0bXpt66e))$<`$Mn!RT&fg(Dzq%*4E1SwUTU|onuMQ3=-tN9-c)>Ywg(E7dmCLvALJC zXa2-{z!U#EmTtZ^wQH}Gp1C^wTmB?zv8CjUb?eq&(OV_$_P(=FnuLTzE*5)+C3-c* z?&rW4KXc#7!)NDVOKV!*>Tw=e#?%=ZZ}srJlI*l}z0RSbOvgNxVwq-NRQ#x6MKf6% zxqOn%A+uXLzQj8}0Nj`ZL%hD@4Ymzqf(se-a1s%ae_X4=@xveP1-~!L&e@+S9jJLi zN%-R`H8uHz)OV;!yOx*r$ZtAJX%uyGV5*5p^(F;Q&NJ>E82Aj{=^nMm+^j^*8iK~0 zSsPr4y}gs3hi7oAub?X2&N;TwH?%PSOLJu}rMS7?Y}$I@TM_X}N0D+%rhJchJ90?8 zzE|Dx_(>CCC1-q=a-9;5Cfe|*9f9);s-_)N*^P-M|CS%Pn5kE4-JjwYA$X%`2zjh(#5+^n9Xu=JCO@D_`qL zHT7A8-MLQ*t~sIiLDqE`6R`1D{olwE57g+W^sD+ZPA;60XO|yBwcPR*KBlp>I0N342l>C>lQ0Fk!O530}>7Cu^k zKGp?EUGrF#mbqn}K`%AQb-;i7dMmrtjy$VnlGo^dv$0sbsZ{&{-VD?7WHrJ`DE#d- z&&}RuS5stD<5R{OE_i1t(TYJl_uaYLk+g>AQ(PV{N$cUaw+n6a!%P@YXOpvcmg1^i z-UDuNpC0)I&|Dn;Wx+jq_b6q+TR0H=R=Ne!P7_96OrFh_ z)eG)lk-NGnQ|4zUO1pC>7_>!17eJFLP@b!@X?m}zZb zaQppYv6Wh|M|Rcv77equ9WS<%cH_!B#ZJ6%lSn;Hu-g}Ov~~j_=aAVsRb|5pZo?wJ zW=s49l#>ZlMP;)SzdO&F){Wy+#!7uR&bLvYXh&O?eyeR~phpvT8N1u+_A|~qQ$I15 z4*+A?+}KEn(Zt3HBT5E#nhG&eqpQ{1^VOJJtNy8zb?f7=2onMFV@C#}UkyNBD#X6* z=QuoQwK>Rzz*8CDKMwURuTEl?G1W@085I84k5gsc4w%gjU^eTJB=JG|6G~*QkCKwo zR_*?=zO;QTDmAVe-1AIn+#&Yu?tJGg-2Prq^Ifeb#c`f_!$CLu(=n9+hn;pzoU}1G zKlAoG94XN+rzHQ_+^YeF*`#51$u)sphpaQa>FKnxjo}840xk4rE54+pm<9bgFDovM zWOiDUTysYu9Yx0JKEjXS5M>kvCXRO zA%&)N+Yhrj4U7i)4EeLPk1|V--&sD#Cl|gLPc`>m~47yQAG*SSA-^WX4dAb;(LHn6{da`+!|!T)(6dH=uJ+phapIQ$>{U=SOB z#EF0T;GeYh&+q^97yr9GulpC9{okL*kAE_mx|bU7#ovD)*xqeIs{s6f`^?YQn704F zJvk( zr*{*!iXjDN<6_{QvQF;V)(c)64w94Juhgj99T6*fBUhN;T#C>Wb8^>o25-LZ`L*0lsqNH5#2JXWG(a)uP75QYLC74*?t zYm%6eCD+AN;auX!yeGijVu+cpHO2_dY^!I^ogT`=mud`PWLFo&i!RgY@*etZ-~0;LPKulu$l-j0 z9^beO%HyJTW_+`u=8o5QaCXsIAEpqU9cdZ0#p?HJfd7_pK^V8BRs_b+K5jx2-+#9# z%KBKPo2Qei9Ji!&)h^oyB6Y&8$htkE6jQYnOYXC2<=E^|Oo~rW^n$psnhX#m@R%Mg zpDDdMP}_9a>I}x`mrt^Gn|$^nx6a@Nw7#1ge}>=?)sFPdmNci4a+;~VxBJR&`^A?G zXGS&HgJYY#rz*BSU)|nRs_Z1+A9F)N@J4MdVrRhwGWI8Oo|D{*!v49cc{#CCucdAm z{c*Lvo$a{o1PTQS_kp~4@#4ur+UxVv&MuB={qOQiX6r(4uWI&G8yTDGWVRtC`H`)C zQBYIC)(;h)+wvISUq1DN$1^a0U&Y95cizfS_w(#;*Dz86ezjl3l<`|(C6?{e@w@gf zhT=>X#uOIDCO6mHN#^BV_g9dAc_bN`!R>6m&s~Dd7A5&7R~5` z4|fbF%DzcP{!?1j+o|d3(heQW;X;r$RqO2O-zQoVfs0n-Q-lS|skj0Xxwaw!1QwTy~>cix$BckwWJj(k30$_9KO3=!B0oUdvr4pw7eR z(Hf}l)b#q=iyAUwcb4bDRCGl-imSEd2>@&8&PZ_aj-nAj8LpGI; z?ac8*_EWsaX&*TXhHl!Cx1!`yE9iu(9OFtl3hd&U81L&=S95CDN@5FR$)&)il?o<1 zCEY@O=8qE#SD|qx8jK`=n#ngrI=ZiIvBa0QyZGZ6UfXr#HMoP=-p{PFW#))Rm=Aa} zT_-l-(v|zIp>O0-J~&8z90?&Mx|&L>+(>DQR1nSa1FJh?x=YHYN62Qj@RY-fj8pgU zlKV33H3bK8S>uMJ0Dwp4jMWq~~MwWA&r`#)d z=P1yEen-(D*1ZjZ)r&`HpCa1AG=T_Rt>jbD2*M7C8q(ec1zwyVJIQvmqBE9bC1h!CPQ+p^7jgA1_K69;}#OukE~p5euDZRrkyDam_>D0c~(qu0;U)+@oGr}`rW zj#+a^Bo)>Cy)l@@{+&?W1OB_c@dJ30KRbI7;be>?_TW}?*#TZ>)$B6s)|^b(~l zMMX?W-vm}-L3q)>JQmIoWHYa=;0+zUi!!_&;WaW=QK>FbOqboHbDrXjwGlD*l~vM1 z_4*XGHi^V)isX;H|7U7lfogrvW|$$n=?9>Il=l%Ot9^UN^Tv~doZ8iNQg_##8B41% z_B<*4DKTh$hb%rLr07QvrWUKfEL|V>;F-h3m?+)TgZWy$78dahgc};!>{_hSovSr> zfc1YjA((Kr5bTWD^Kd_3GTH!2@VX&c{YW@Cpk~cSEh~+2$@j&rS!0iq{`nRwEIp0^ za)4BTAXEegcnw{qw;5H=%;08o0>VX%EQOt_9KVud5=GKXRF%(Fw*Ba)oA7%n7r-vV0(Hv-E!XHYC_&rRs2mNaoMZV(i%0zRMHkbWD z7TnF~)FO`?vdoPVr6d@3$D8I$2MlW6=g;q-`X>1Jz-~$^ z@z8zgA;2R%@St%xg{9oq_;16#%O^G|_}3X2;Hl{^q2x7c5&+)S!bLFz@Cg!RaE}zP z9W~19la!#0e5?Fi=h`)QXoL=oO%ROIvB9Aavqen&>eV4CGPK$&HJV`E+u?acjIXrj zGG^wZ))XzLo4`8K8t0&JkEUa1WhjhvFhXLV=s7=0upL28OxqQj+Kf-YbfDQ4U|ww; zzy=!jT+!FI;P_9hV!Sg_ahdJ{;Q%C|0rViCia4gV{fGX5A;>!NBQxu70mx`>O<&-OleAO8jv_#S2b?-g4q#pvC! zrSU%Nwlnw_{1WTSzwSlJvCS6)j*P3TD}d>OQ!B6Mb8&O0 z3kgISW=&3DA%0HFzP4WLAaM!-H5W*>bDEhTUwNI%M?TrL_YQl6dRaRlZ6=)(cZcM$ z&w{-)&CTUE9o5`*$IL?KNwaY?#D*i*w$MbyijgR)x(1ifwHM{_R05qYO&TNp4OJr&G44aQ?Wmep!aSWB`5< zBxgRB9_O0AeS2q3LcLPUwfX8mJz#md2i(+3qULwX-6Yd_;lEhV7Dk*bY9!wuhQaN! z;~6vE^Em-w4;(~~o3^xwRZZ%E42_WQy7s?%`wpn4mbGm?9`V>f1*L<>h6soVNCy=a zkrI(6{Rm1gA|-(Yu!0ncbg5B#5fJGCf+$65q<11UgpvROLI_Fz30}SDuJzw<-Sz$7 z_pjwz>dv0MXWl*YzR&y2Gcy(mL21;@jHp!*Id7a=*`K7CT3u>LImWBykD!_jPnhc9#*Xk|!oqzR(Ht}h3N1>2HFvgX8(^1o3)O94WgrKLZClXaM>G8hA+ zK^SQ%|7a+6x>YV=checm3K(I0#jokf7k12(t~I->lToCKhv~4qrP%eW%h@35*SdUD zUu&MeqH^KP6#@HP;>TtWFBVSb*OL6965xFEgIf9r(ei)y7XM1kfUV(Gm6Z|WpI?Ao zH!|*+=QJn52XiI(*`u1;tV6$k058eT z%|%yvt*or1J3XG7nF*Pmm;ez{L_}kxx7rkSSM=DU8(`gFaC?2DHGh$~i=>Nptr2IC zt>cRRi+?mit$KX?-eDO^lJjxjoBMJiBaa-`tV|10J^dgy+UP}EOG`_AV=5{ge@joc zkzlyCIXCCn5v+Qe$L)t_{!-;bo*6KnkLn0^WP3fE{{Fpq(H+HQ(c~2_j^IZv9UZZ{ zZyhbm&1w)>(qLzGqcAxV1q+j7g{BpH1UQKvDW!L}B%(*rC#}cu)VW*W@OX_TDj8pB zS)(W;Bcpt4)1Q`*z(=U;;??i8oJ@)nw>ZbO#_ecokdzyn&}y*=1o1S^z<(&DKW^EYVSr-lu}`uVb4X5{@s>5__eZ( z;C)vO4Gkd>S)4Nd|Q2Av~J%c$dhNfDRe0H{6uJUq6DK=#WyayB6XjD?iYDp;)ISZ5AEB;^^2!c4*dMI<6vw}dg$Mf&PwN4*YpVQ z#a|OXk>!d^IbT zMPlyT9hk_tcuolV^WhO;O!eno+JVDqSywZJe4`;4+IK1U<^J;?|4U^$N ztFGKBGl^__j&WL z0f%0_dUfJikLD>I_P(u-);%qvMc;;o&b=^jaBwg(>ceAptTogMN2j^Hu8_)Rl>QtN zCkYD+h_}ICb#!#*q~CT432m;Xuhz%L9$VXeqmleu{r)pqLRP<;ayF)APfO0`=H@V} zW@-Io$Ms`=XDnJt9POUtNgg-5=;s9@SDWhh0)zws&~_c1>&Z@^JjPD z6yqiDR{GRt6;CDm_$Dq|pnYjQ*mF7WElYb>fHWD91GbKM#M)44M2w;AW@6h|pQVhU zT@I`S?nwjr->Wj+IS~Nwr9cMiV6t#q+1Ocpz1DuT5nxpZKe#d0NCT+3O)6mPYp9sM zP&$Ae^3***aVs}sOaGn2p>Y`2n8@S2w~CTiUPN9uCOP)MO9KD_l9ERopEwG*uAX5o zLjypphSJ-lE*y9nUZuJzc}5#xs*YzGVfl+$ZB_y)Z$1a){Ki_Wpo(j4X%RY`b#3#2 zpRey-a>x*eBDaa7sGjol^t3ffK|vukBi*f7I1yF{7KH$4plbT@>U|x+721n-8qTqq z*7Q13Z%CV@0eo!&z=4hZiR6U*?EFL-_!U46=H}OA)SO)V)(I&vKYaRBu9!bXRc|=8 z7HK!Vo}?=6jVUZIDfJ{DXIGtiBO5AV)8oJWJfz+&|*#&&=%=ziVv0j z&?%Ui#?U^ukg*~&ahCdWbWq|h+9z0^Gvl<-q8gOpK~@3Z38Y&*uEpWEG+i9&O~Rd@ z8}zHwm0SU>k&dA33-#p-l$mr>N!bCVGsk(-4Zn9aGwS4v#+No+vC=z%s5YzA!TI4;EO)DBqpeIUz^Ck?A0ddUyjf&%xoiqX?1R4t2);Juq#DDz`tS##&3 z8V5$NVe-^`cd|CmIW+}$cVDJ=_>M(ItLEqDCs@58AL?J>lm#p0Px`rq54h;*>FMf* zZzwdH+||_7Y=*5%>ezl2-NZKn10+Q84psT4uXWuIeE+oQjI_O}jqM_*ki@Y_ieL|5 zsV#*M2?EIMI36qta^!}mXUiNyV*?&hSGto(VdcwVsdUgP&Qc_Yf;BE1{t|kWPjMopmvg3P(E~$SylTjsUUU7UvR7^~(dWW8t zHOd;>)o{7-Oqnpg@XT-agg^;E2i3bng_JKktpC=IJi_-Tp_S_vAoV?wn`zG<^p*nV zha+4kTGis5U-WJ9YiCH&atoL^Dhn2kt%<3y!3sw;jYfsMK+bhVdCXVPf#SNSpSgw8jg;v6o*B85$-bgd0Q+~;*bMQV=&it3s|aL|I^Z*0I#^o2Tdi%PC< z0g7<9Q`q%sEn#(p?@IFWdgbKe`s~q1=DWTCViDVC#R)pWn4zX&JD>DCh5J0%fW7lb zlmxF2Gil)D*!d7>pfz*iEJy$rps97PGH0@i|gPjvq_!j-M#F zmB9x$dbT<2U0_kw*VT1GMQ(^%szDKYY04AI0m6*gPr?kog_sH43G_uP+4F($>cHXGVb-!TPw3utjxGP{rII z7}cMkAnk~|G$ZK<<4uf=&RA4Jo`=s06~+INAeI)FiOF|&B)_X| zvh*}GHjb0O=Ivc3|J)bzT&DcWRy3bhLYjDZ0jA)Oe?k^LSs9rT6^AQaVV<2F1pb+Z z1dwp8`FtDZ>ke&tBdolBYERKa`sUjDy0>V5x_@N2nanNUon32{4WajMD)ato5-$%= z#mTH|bvOGrUt_ZG+;11;UWl^g!I48BHEPm5my0tpt}ABC6kSmypYtmU=km6uH-vKY z9)Ym^1m4*o`H~TlzR699QXLl|UXqp7%u>F->mU+s!&n}`J@rwWQ?2r+qvf9)=yu7l zts&SPu2d`k9SBDP?5(7F@oaJ!m#F@oJEL-HJ6Xgm~nmM7x8G%h!g3`Ll$qB<+oJSfN0^>aK8M-oS zrD$x5iWr|R8jS)r-OSXkH1GHewsK)?TPr}(IPvj@O2B2sKTQF=&j|C}%if;Ehi~qX z0^x=HND&ulmqkAMvsS5q4YoKW zOPB+BH?4lZJbYdJx=H{hn-bwg9hokD!!-hcG4|9!Q~ zf5L3Am{XR55KN+N(+mHFjO0I;+5GE^8O(KJO=XB;z|w}1%VHmqQ3LtQi8(M7bRq3a zoBS87`;CZD9wK*T@Z0R;Ctq^cU*Wob{rc{sg@2@;2r<4)eDeG_+o7q*FhX2-qVmW7Hjb6^7oXppA@shhm0%2L5l*+E-aXYK9*EX#lU!ioi$L|YLA zO$Ltubn;gyFw|oN1;V12Uz0w$oxpMI+`;_^l^y*5;kw}VuY0y{XC0)s@*iIS&t@IO z`_CfVFJn1D5Aj#{`)9AOBe8XNmfQ z2vxI`U!3k|Jn|!*e-Yn zNO7nh5fw(hGAqk0zvi7qC97_(4jS7w?4yP8cs;^EtH&EPP=1(fn2moY{%8=gehV(_ zqdbFCcHK%O1#FBsxs{Om?Zi=+x%sACC&`NBIMQ)kVxxAFfKLsagmLbeaT%7ZTHhoTw(CNw4pbA?mpLJo z9s2Akq8nSGZxkx22mC(zcgIN-MC(w67E@@AUi_CBhv=Lla+fY;Dr8i7Ta8Icb}>s< zR8r2HV=kKgwT51k!pBsS1$1|8E!8?9Yc^46WO&n24rM5DAH&|TU9uwr)B&P|Lkg-1 zW%%RLAU!=jGpfGqwSM2TiyY#!@!|9!XR*P-MnphKWfsN1SPg3XesK!nR@5PitvMuJ zJERw>8p~U?vA*Mxgj)d%v8R1wv{_ z(YI3L<~LoU#xnctoN;zDGBT*wj?T^kAQxi}!P}Hx@K1x&pNm^4_R}b^n`+#ZFI^X3 zSaGp>iBuN(@jLMSEvhf7g-+_v(7rBn4tveTCSQRF!M1Xu$n@DFo66QOG_wvIVZ$dZ z^vJFwiEborVo+VQNEJe93?E->0Kb;;UGGQAHOr^PqbxyncyK##GQ?zAp z1~O%J`7O3&5NyEs%(glvg%M|I!!BGH35%CU^GK!l_M)%a#!OFLygXk=spN(vZOn^t=4&HS=q$50-x z{5or}Bq-!VfVC^|DoamM%k>MJCIfJrw=a!S;uY*FZK>>Wz8GPJP5J{t^w>orqpZ-b zubN`5Lh`S^@POV-P0n% zP+Rx+&2)q@G50CqZLU|M5X;lCcNx9q{uM_`3NWg30b9{ImHXd2U<_6xl3MSC%DE{_ zXXHt&r~oplKV73(nxX_HQPn##^48YY0v5XbW|JxR-KgW|hS@D37*DU6Bw@|~;!~t` zN2>?UU4AF9y}g=yqkzLVO7Z=WLIyjy`#oyyw8HKQNSt zCLDtK1*Uw7-k`fHP*u%I*Qjk$DB(yAV|*gxa2;@Gm8nkHkAArHa!TzYYLUFzmOvT% zJS|p?-BFH2y}7zIisV+M%)PIjNv?383a%q&wNWxT<-!HrDfA-#ZmUGU2T1=)x$px> z>>J+wtZt=1@;gKu#F*juU2BW4W12cx-ID2RJ+y`7)=jyj;$P~ju94oUy2Jb_U>5wk zJ3OkCXzVL=ff=|wVro`j+N!x~c?LK=jZlh!KP{Rd8B08`#oU>&zT&h?;}zWu$yikM z3iU>QK0jJYYf=#(RaotSpP2QiEz8M8`_Ao`q^D6H-@S)B3>7!CxZplla`$anLBSdV zz&P0AB6ZY_@9=Pl3{u-oDmD|Hc_w5lD48&#vhFwkLr=}_tZskf(kpHGe^iBU?evrdT$~9 zp~>Z7f3gngn5GdQwket$B(!^uzucKl*HoXHR@#9F<0HXoZmtlqL?w3eo~et z$8>U@Y4;&L<2WW65Bze#To*XC0_9v$jo9-YF+=lFBBivJ7d!>0Ad5@hRl2`gQM)!9 z-~$4Z@8|{MJ5j5+MFll*!uSsJdT>5G3pwoCj_`j&4*#ncm`u|$^mk~#IX4pYsiZ+Z zU);rWV=TWZe~JY(8j?sSn;_Z*gh^0=vFGC8gKrVZvFGk&WM`W?aRr}K=O_<QkZm>o_ykzuV3 zot4|5zX+{5z;~?mfPkzwoX`Pt0Nt;A<4r_IML7`tY<%r(#h$iqFt0jBxwGgLEI|SJgr)_wJAqH5eH6{>VaTgI1{VOp|9dS1TAd@KyTr zrj(X^{Oru#MOnW+baRYV-@Ex3mCaFwQ)L@p_zG$;O*-X`f!3R(8n$R*<>;zi%Z}A5`CB0cDq`%yz#D-br}sHh}dZeaXO&=H0ap9q1{u;L%+{+MV1mA*M z_HA96AjB8W5HI_p-CO%sJs;eQ__A%f*HnKm)DR%;R&B~25L1=^}pVqGWeUrYDZxr$-xZZrSvSan#t6| zZLp?|$G;8we}jNu1s=`BKZQ*-oYC{-x&r3MRxI5Ioom-!590kU&wH>U8)gZ_jyDsXk2 z?&`f)P9Mq014A41@=sJDoh|&luQxBO+agNHqWqo>@gj(Vyp(UzUnyX$T$kbChm+<> zZAfa2M)(MQDZ1cUkX2`?U9}vk(abVQMGnB8aqfoluqtLyAE`$o)+dWP;xkOH0bm<| zR`fx^V-s4{>L1u4Y;Shn(SQXr6G{p(x)mZE(>(n>%8bYHp{q2{|VVAE~2AoG`fTP+&tO8hJFH*(O(1PBT=*Fz+e+=Tz(neT37{!%TMq?GJ=^}9J zUWQl(wnkA*X<3-lV~q9|zcf7TMP&3>kEIaQ=b_n!{6Q$7Vd$>? zDK%*#+9CP%maOo7&xQngMH*1BfbP;p9V7L10zY|#Goz~Yl3%IpnawbsUYh|I6HI?! zZ$lEy7l4q6R?y?Px@+8ckCKZK$nW8SjMlXp{kN0~Lf;1Olav$;g)lLKKvDzdJJniL zRgz^%^b)nGEFB0+z_p6<jv%!4{t49P-L(|HLxsdq z<{~N4n6?}_tJGC^>!iCO4Tz(oVh3eQaeiA&}t(wN5BUR#wYUH-o!tx6D`v`*# zn>gaKBZwi|Ns2B2O`uvSB36jgmwLb#^-e}x3sd;Er0VIf4lHR zOAa?bKM!_>2lA9MmtmmtsrCl6r&Hg1$77t!GPaFcMVG~o?WYz0w(VRny*)h(qgBu^ zfd?Z9V#DIc!prMf*o*GBlf^=*;n{^%tAUPXYirBU?$0qHv0y{0*skrOx&}r@t|Jxh zy@ltDc)-$hHZXKo=zy!&FZP=6GsJ7ZSH3h378wB^iZ%!)39$t?RG`+Jd)Nh*G+2IX zg?<3g<gJWi0UoCZ?M@xFw0%_^bfi zu2H}hFq<(C4bsMc_jsm+Ml?sBmXc1khhz;*W}IEi(2C3CT(6`AV>2Pt8r@1tv<$lt z@E2bxeG3CcX)#X-ofA3v#xkwvlyg-a;S}O%5WAqF{}v4Fx!_kE5)uODJSHY4R>wR8 zRZVMOO@6nr6JXn}G0N1($7fz88HGYMHga)srRyymCIT^(s`tWO7Z(>Bn@Le)epz9` z+6S$%o~oD+A1?F;5DDw3Ve5rRupKk23bDY;48Z~@G8fJO^ZDa2eus&b^A7fXu&clt zv1>plk$A~vbDDJg$Qyf+OXFA^J<|CJj0Qa1SQbro4T$YX@tP_>T-KWXYs!D$!wd`GnWT8WG-N2N)vzaKhAhfleCGt)){94`&L z+Z`<67!$0t@goQbcVwm%aZq7H`YR=KhI45!Q@_3(LpUbP&&smm%rPV|Y}H{kbaKricYbDsv`%~0n0^4Ny*E#kzJLGz zVx^mm2@AZ9vEWhGtGn7M_OoTkq`g*%+&3R@c9ziAE-ZrTeYmC`TZ_wJ2Hp4Rer-}5oC zoi$U}8N`u}H>HRJW!agsAye2TQ42RR;K<@BA>4(<5|QkvD?efaP}qPCww zY6*b0dqs3e%+!{CPjlL2zmM&YQGr zP)l!}LzSvMPZi3?0q8h4!$eLrdG+;Kc9t45hP(!o;yuQ~u^c8CH8rPj8zEM;YGes1 z^DbD*f|+sm*EMk*_HuPO&`0_WIu5;h!)PcX72W6WSM%;V2?!Az5N&Iyj3B)^2U`$J z;B?Rtr0WxcoM%c?NI^(}cEB#N zAQnW;1f80TR`Z>F;Q%NtCQhcauGGr9f zSNy;LSApCf(@1a&Z*mF2arVqt4~_b$)!XzV>{lR;22 ze*$2P6Mb>wCP$PGc?r#j$z~;DOU}o-wUUlnr~(?ttc-M44VIk^n*@0#jP?c6rDfRL z3$le?TC5)69wH>Hz-R!;deE2;mOJqhi?pqtwt{Hq$s^L&5GbIT$M00<5XiN+cSbnX zX@KvpMP&@O^pX%8nqc(c1m)$KQP<d+783eVwoSa~e9S+CgTiVc9VF)ad;lC~y zKEkx{x#rE~1MHIe0bt#%Kkc4lH$?SWBLwt46fHJ;-JL_H@}ovANE7@4r6QsxebNFx zbPMDteX3x%8?P+{xG?_&E=`Vr31V-n}iom2Fj{nq>>MQrp=4Wa%*;=`rybugP- z>oPD~urYPf(1OFfb&%jTLO;?@0g$4*##44QO(j_snRWW~>3n2rGiEd=H#fJWM5ac{ zKU3-YjjBSE;xn&u-CEh}*_F1@_vr)@kVNO{T17+eL(yRX#pQ$e*9WE$I@7HD)(jOp zQ*<-90T4O76LZT zvLYCCc0s{;FNBW|HSi$+i9T*gOM&r}UIZHW4Kr|7#sK%~(Y|pBLHo{q@ z3dz#Ve+QQT0I+h@p5|*(S&41eb}u^Il(hi%`ZZK^Y~#hhp_6x>uu(jz6SuVHbLbm*M0E?XJ2K3I1fN-TIGCVDGzCE zv%G+jv_QV><>lpkBF2~RQq(JyCT9`+AApLP4|+|*>0H7TG@WIkOUh@a;eFEt2Subm z8+EKlc?-~h14T9fpnY|vrx4G?A8bMX!fk0lPhQ4DOJTzZE5kkz>ZxlZ)7?AX^c9-9 zoSrX6{ytbU&LJRw2*ukqdaCkDSGUqLayfK&C-p&}l%maVN)43 z_l31`<0}Q#`|aBY_sPby8>y+OAVyxp*pad7n5Hw=1Qu*tPGxX%D~`1o@Vj!yzrl`9=>cj>U<8rAWt(Mvq$k4*tDlhNXy|MIi6=2WZs!opWBRIzq{6+dU;eWU z@`qmFZz<(}`vOax@h`9ao6O*Es)Qf9ga1DluCVAnmZASCuh^b1VeI|U;2$;ouUJ5! zsI`jg?d_)l$qAUW*ny#zZQX9T$7BaxIcWc7kkk?SALuQ0)UhNG=fj>dXNFgjt>RoH zsY5_E$5PJS@n^|Zfa+pQ9-5nMdcXzkNZQmsS`@Q?B z?vtJm7#P?f;Ny&h0F*op6s!Oz z1S2_VNf_wgzh7;I2|x+5gN&v#3=9g+-_I+Uv|>0&`4Evukx!eWXD1498LD=GHHWAS*!J44}m z6M8Y%4t_SW>$eT#a1u26nZxQR!{S(!yX_N|@a~WP`#t66eU}KQ-}Z&+cKeSXU3C;= zi})09b1?^)sX&crVeH1Ij}MQ=;MJRG!Z7OO+nR9c8$IiaIuYx#w$j@dpV>X*a}Oah z$$!W8AH!)DOsv0$3MTHqGZwtqf6o6)LDN+#YiP{5N2ftRrf1*sU57P;OJgNb%umG7 zs%m>qzonvBf3pA7z4v7KK_qsDgon^+!S?0BxQHVz;@_czB{seu=cU*{oIDUbb<(fP z@KP+G{GYan-}R-339Q%&wg1|?FyJ6yBShm{AzLQHKnQ*nHxRsF(F>yyBgKS(p_Ypk zKBbv2rEK^=YVL7(2xZ9?jG9)h47rD+I5c-z1bhxpP`u79#<7|Cf&H&hO&|O&h`K^k z!xvnC!<7t4ilt_vU;jQ>p-%EY76Qd?eGJhW1)gX3hkW$<0_&KSY2eh!n1_gg$^Cnp z$AK{M-BgxMCjVtrOTBEoThQOP(a{qdKe;gABJgmA_^FXFAbJx@_BqZ)e)i&x+^LIs z)c9%=^xLOWgK%z9Ecl!vjAiMKSl7D4hc6_fM&h!lbsNPNQd*>Oh&p8U9@7fVI>=YX z!T;`?BsRW3G7zU{>t*UXDeY1O~)|iu+?ez~eoW2|EQYjpC zX=FVA#*+K{55ccM2xyV??|?%+d~?(2^74|;^O8D0KmX^?p9ZCk_Vo)kaEORFSXg56 z@~AmEIg5r)7eT*R8~9i3GVD6h6q6B?m$v-ZHZBdEf=6KN;wt8p<8BGXg!pu_w9tsE zUL0Q4G>{p2HJGI+yb1aH7$?*B^z;VMm*?%hy)CyEb^=s_7%7lhO$Uxt%S!Bu9UUuc zoUr!|CMl_clM}~}A3qiYFGmo?5+>5&wj@fa5eSjl!yRf%QyMYPkO|0bXckpvzIb4| zHH6cr!`DoF_$snDqt-xH^%sx$?-)R>?AD}2j#(Qi9+G!&$mq^M{V zlc5lnhIF@-o#sG^vd899iS(~1P( z5)vLhRc$IKPn+O&{Pi?w2I-WU-t>HudAceU5OXUIwi0#g)qAj?+VMz_8He)m)p(8_ z9P5+1YC-8z&$nkfBwk{N#*Nja$ScLzoj}%ll}#5zZX$lYQ={23=9CW3@QRxq921`J z>Ze8ypI$31e@E?%Z8aL!B}28tCre0e5_BS%smF?W7xa3w9KCiB+>j_+e9PG~+%lVz zwEVKIe=pb2JJL+DF{Io09669O&Y%+hApbb570&ZaW;BfB+tgnmyuBt(n!T~qaZ7Xh zhIwZwQ!e$UfuCvRy8@+}k+!R0FI60Ssc&@b3XM+EGL3Pgr4fRJxhY#t-PqPhDl6Q> zp}mjTIM6S^a0+B*uuQvQHufuKE8CkBmUdPS zBY})?6j(f=p8~ozW6bZKWg;8q#=j#u>}@$TEjZj!TVJlM>Pd{MO=45+&$5NFR>!LD zzDgJfW^6@=cf(JQ#jQG&L6$P-KRp+C+V%M1-?qbo(GEprq2upUTwQIA6a0MWRGXI} zAcnUoVM8sApiku8Y9dp?`v{Yznlv4~JApEy)}R-q-?yFWH(NL0i~V{4?9Bu+>nf#%H7 zT{v@q`?RDxMxKZ#ZIx^QDQVr*fg{A5XRf!`KW@fY>KdyS~>z{L`kMfbA!)$JD zM#RMQnbq8D?X@N8XpJSlvFKp?;@`@*RL--Xg+5kL`)m_8G- zazHz1(+x8(j}U}P2ExNp3=TJ_!GguKVaGD5^j;eXp4s;m1zB~kI~a1O((`(-5&1y8 zDz)V2(a3CGk#`8HLh^d??gMvXO>yB z-)<>pnWvT+nTKAqI?@!X1u~y)IdCrX)9Y9s)vqN9AYQ7w?6_6vmqOnq$x(_MQ#Xk0 zIdHJXPr1cPuYQx`cxS2~`&K*F*}aV*6U2S`$4x#(DDAC;-ez5eU6Z(Yr&_@^@#t|Z z*x`{Myz~v3m?OnuXEli9Xee&pdCBF))0(6H+nF3>4Qz09#(G=*RoHFMi7Y~g1RfrK zdv{ljNPN_SYtu!bN`r}-ntJ?~7;LE;V?akg8xvFX;Gm4SxHzB3`LB-DB362$7%3-b zXIU8;Wke43DwN5-f~k{&`P1J_?_yue5#$@X@)@G$fOdA3RrpOes&L_@KjUE?k+Q9x zYrUXH-YsD52rzz5105B>y(MI1~hmWOve(q;v13+k2$qW{Q8v*vh6{N z(Pn2XQ)WzZ(;1H){4ZxMLFigKHTQaXU>m>N6P)PjqBnd}^LN+mir3Z{5A_%WORe-V zKe47#uN?3{f7V|g5&z}D2+1N*ER40x%eyfc-_Ad#zw&z-p_`IvvPBn!;TxL_pOTR! z9b6J@vmVst2>Uthozj4h?!%oZCfxEpK2545tH<-Egan%O^GtWRM#B4V*&(UF*=$m` zGvg=E#AI%kvoC~eSwb1p(_C8DK53rbuz1SGo^4}aVn@WCR0UwOZ3oN0dh*x*Xdpk8 zD4FK^EO28uzFonO*~m(+_sOpBLjTTzh@p6D0j|2{el_3Op`bdIWKBklTp zbP723a*?cNPV=sdK)_W#_bZqkw5X|9HTG?{!did5V49st2%+g{kF9l+Am8P<=e#NP zr@Em13`d5oe}<(ti%ZUB9=%2e7l02i*yhHH{m zBtu;b4kF4+%h9IGRN-2xG_{Y}?{3k}*b!+WWhGQmi)!qYT+j=r?NMNiqi2b=>D!@z z>ug4lIXGiy$P%oSHTXA6H z8})3t8Nx70+0$_&`~n>mj1!G1RPnqtD5c98xQFAAp$@p08vEx=LG8r%ya^^MH2Xvg zM5~-my+IlL38`nJJ2fZwulSuvqeM$ncp$>Nu1Z%a6NI4bSk)2O-6!+c9Z0i9qby_?0D`|ID3ah;(# z9WpO-vWD|W*cI9u^o@tvq>0vj1EGN(b*oGG#P8d$BW*2ac)d~Ifal2 zJe_dzwDNqvEQ=x#Bq?i$gN7rw1}9WO<);cbd> z>U;UR?K(#1ve+$8khfah;@sD`leK%u8Dfwx1LJ!&SA(96F|;Z%NsUpTY6zQJ z!VluZ`PMVK5a-(7u^>>-6D>=|4Kh}348~TN1qx#ggpF`L>`u8n4r8xhK7_X^q#O zNz!-Cis9*F9RRnIA+s zP?CTlWYK{9cAJ&?w11iP500v8JH>^UFhdqY0Mr!J$uB>oZ_CP%S z+urf;9Mh(~CY#5$?rJ5$>Ni6wMDbZ-khZF#*2V!7ak+TtA!7;^#(VkyPSVFeFn4N|U{iGf;LC0Yl`{7CL5QyqZ?3waRvF z>Z68V0QL>j?zV1fonAUv0wGXO=IRs8<5#_d9PjL)?lh8Q*0rYUb26fMEl-3Zz{DUz zhQd&jpj9`#Q?oZG7Iv`OpBM#TfV-k^d@$lr2nZfHsS`9Kwa<|jKI3uQM=D(z?y~Z) zX$j?>Wil#tWuBsiriQPs!f!i({9fG*G}(qZ%E5#_g6t<>ibS)IOwi`|oA>~$==}@=f*U#xV3jyHRCX{Fu`Ykz zx$rE}4e+lsXRtH>m8oY`LhD!K_+wY`SC~nAkEi zLA7{QZI3nk`2c!1c`R~GQ#vW4M4#wC?>=-b6Vh&?Tg5Yits zHD%t#xqJ$or|uW2`l;Z*Ggnu;jd3DZ__7nRTG~7^>l-yWFN#U?|&9$ z`|NT`a*u`h#@OOBxFu$%PBZJRziLyXm+S`w@9J3R_?pB9>6jf0za}AC9ld>9+4PKv zle=VdCi~M1-TA=Ad5C{frNrfew-d8yw(~FAfp!y*4s(rUCwzaxM}msN`GaR8sWK6- zS;#X74pw@^+xY}n60Ly@{nQ?7e-iLc3iL9$!R@N8XZYzV=9nOL@LQcSbb~8RzdZ2S zr=5yhXU|Fif4dv7ZHsv3J%z|@c+y3m?gavnQ9T0dB4`sL*VorSO})7Jp`vxQQ;qEDAGM&8s`=FpV_%zS_J3y$F=U9_dH&a-*xhY22$v^#8j zH7#i_lKY?mvU$nR6-;=nVDnDr8iE9?vM;3y3#;!h>SfKL+&ql8aQ8I%b!oSM-UK!6 zoPI0%s+l^az-`X<1;WFkgf>!isn;pz?8%PM8HyOUAYMB&cx5+-WmPQ1@@;9W%c8Ni z|9j;}?!1qCVYU0H(rVzH?6p$bqmJWw{^7+1PEBpgQ}JIJrHLqaM($X&*3|45tLBuj z6|=N!O}ZKpnV7%3OFI6fmo_)PU<0f0`eTfCadwJwu~g+UAjIVLlWlV0jNXVL;a?w7 zRV3w&KDyBlxXY>NSs<29E1|;LEYDXkwBE5yRXbI>*$yz5bvMQm%D-r%x>@53a6!Vx zb|qE~dcKRh*$)|9m%68Bhyd%{(uHFm_x-VMo_dY%i8QCF27>#TSMWqdozeZgn}P7u z<$EiUlR`2^knpyzTDhzfDpsgDtG&X`-CF8xTs z5JA7Hr)WrF7D}usjM_me{He`N14&0DyNK( z`)l3bj7lh?l|Ki_e8ck`RfJlaX*luaX+w=T^!x${8J9TPRtR|vN1*J#o55FZmHSFK zLq)Z*ks)ml?_rr?)@qoqXeLF=+O;9<&LXDHZs_{LoR`tB>{V|lo7>KLY~!(I{dc<< zcS%|(6eJYL@TKAq0ps`rAG+1Qf8hc9Ybm(oH{RrylcW9-LWq_;pU&wwEy=UwjdsYDc4PTbXtBQYbrD)o-25mn!wY1$9T%=70>I|^>fkMcNWyc%e z`r@1%4-I#D4KUZj&+~`t6U06=4sIL*HiGZ&N!M3=w?( zjL2arXZxqcwwCk)RU%E7qI8Ry}*| zr<3k;a2)iTX}iOolET33>;s-%!!DbD*EhKi&3)Dj(&7yPI(%tOP0f4?d;*w};oT@`#`5;d7VPla8px-+0VZGM~yeO>|#rzqR9C;2(D|N0_8LSXGC|3 zykSXHfBgt@4_o*iQCNy9k`gcLllAL>K9gP(E*+=30G+7}1e5re`{KRVnW36__Jdt% z(?Qlp`QJ%=elo9_Y@8@N3d)&{Zs)od3V1QFy1x#!f!gurH~wlScRU2XrPFzO);i*d zXJpETW-WJvpwsa}pb+NVPCTrl{FSV-9 zFFI~-HOltX;xl7bF=;z>bd^2pW}CLLUmu5lC!{b9Vlb=MIUBolcCMB`)3J4Fi|4hv z_9Ly_*+~U%b{T&5CRLvH&6_v$jEsV1RH>a=V!oqNnTm8DKYo1KLxC+s9MjLmX*zxv zEAaB>#R5BhnhUKGhVM@{!uWAqE98qdI@B239qNZEEUjQPF;;Vxt2||{xLqB^E%fpz z=5l!U3_0&GoKsnAbH<(Jbiu)XOC)1^Mkzgv7dH0kWJ z)Q#p-&x-+DD86VZM8-zYxr5Mv2$DKGf_QjKHM8@stQcCIrl+)?w9V5YwAeoonFztt7uB~TdAPQ`)#>Dyz;S8 z-jHj5o@mP5nn8hdIH31Tk(^${fxp0DQ~TPalRNe6?$S_vs(#NX)&ATuLCGEqwWurH zD!M%QMr*9EYKu;z_G)dO6undp1@`eNX+C9Nf0rade_o9?e-(WE33f7SFwtrN5%zv% zZ6mTNiokQ`>9O!=Ip5&=a3*EcEJM*g9J5_)Lze{~Jv^9JA?^FmpCMfaod+&PJn7GM zA?#g3#jcK>H{tAKBPfIZMD}P*Z@3mT zywnfZ<}&$SQL0^~4PDJ1d@cCe;}--{(l_f3Dgb4uWb<14NL7Y`TS@xzCN!fT28{E* z`d%H#%$TEtZRXR-Az-wnyY((X!tr*RgC%#)^F;n9mO7dmsTI$US;qL=gk9PRTN-J) z0TU-z<^oCvcPMvXsowW~a`_(m9BRs^d-@Gi;2o>~k) z5;CU>H?hjjT3{1T7b<=c+CaXRtv)vDYYx6T(a+vu#IGSBfWa;IiuB7?gQOzZ5-R(% zkdG377|Dr2W_<`G%+;qodzrf*;rBB)k7-UYwD$vbs_(Lu%Dt~qvo)~Tn`Y#J)2pPo zYq8!UxE-}?eQpNWn{Nl3hmuaiK=8;;KaBiKML~3PI~FhaG4^XqoWYxVug6IK0Za>S zLIz&VrmEOP%awA$GDPYha@?rpWAif4y>KU(SU=4A{^z5j^cW_)|pLrLxa_LG@&T;ct{{Hik>qtjuriBK@uHvb%*k$z2N~2SKeb6 z`P&&9`IIlml`%l2(mw9a9rB^^gMtL99j}s4UBaB7P+%P!xpyi~@KD6S$qg-UROC1& zBLQ0UlLD+=3vH}uzInAF)G?Yzt}Zh|4K3hH$cNl6K?9$rp-<^DC{c(yN>1B-wiP92 z-oKsY0r2T33;6iV0f6?hzKi1J)hL#C5qxObpp+_bFAmxUwV|+rSo7 z%l$D(fuf?uIVTBUvoR>Er&p7XQBS%lsX{p4nhA0UOB2#(rU+;=#Zv zl+1W06l*dfWStIL{xG+#L1s?~PGPOTxJ!G+6>R+q-ciKwL#Nx|L9xHGP@HOXIPP-; zwZrU{^rJ__BD4}t7@fy*>)WB_xVm6Myz16x*7OQ7|ASj=!_bHyU^5qN3IMGDIJY*O znx4R~9||73GnLlYFYFr9Q&I>HXG-?S^#Px4!q2<`Wn4Jc-k|r2we6#amjngX#Qzlw zpmLV*s+z;&A9_ z8|iZfY?wGW5*{9WKodhk-~hT-n^0l2i_*jv+y#RGJ4Yu~T?w_nv)RKkv*91KPRDRi zFu^3nerG{MD&KQXL1vXK`Q>|PXiMy-qj#sl6*FAigk({dLPdYo@>De$DK=Fx@geDB zFE3N53dVm%?rpi{Q~@p?-seI^QYJhUmgEtA91A3Zg3&@Nm@5F;0)$!>_?Hd~J_7^8 zVv~9k%A~c$*OxKvWxH>gc77mclZ91dAMZPA!OL%aT1EqA9Gh1j^z|(fm1#1usJqPq zhPAP1g{sf=S_5HNcH96gSJFSNI4%@k5OChcFVvF&ye%A)_E$DxIQ!1yjAk24W2I*UdTUL=}(ceLc7@X-V(xv!|so(vk_$M&$-n|L> zcm3ZexU}Yg*3xNvi~XCA+wXtB+5uhxILbM!h~EFZ%m0tN{%=(If3BJ=>}+fPWT{kt zlO*$Yjz@M<@o#v^4@O?3!vgg@j%U7qtxaa+{!btNH^NT-mpGLFje`I0s!gpataWJb zSy*U+z=Z@lrO#SgpJZi`rE+;I7HxQff`cO?A}DBS!vMuY`kz3VAI>gA(@1!@xRtI) zYJPVch`>HdPEU_)XJ-f4e96<|NabPy3R6t%6<~$=K%Z}IR$Ehpf?kt;#De)W`{BeEMuwf6zNuKpDh z1Ok~H&6Z{GIld0=3g|QH0&pcj)thwqG8F+$4XvB7pr)sfh>ypvw_EvSW>yfJht!pa z)}DY*wJ|fd8?UD`%G82)u|CWp;6mP=f|sQ4=?#(bw|-*_ZO))`$E~V)cT38^V080` zMhCi&+g|~0`4+b)GMT&=@6T_0`NOtUU@@syP0g1S5MLI@4y!5U|B8UPS*;>MMSwm6 zt4r=g#J}As&@}c``9>WU7!vew|E%R@o#EkOi-l?$+5}nYujP6<%!Cjb6g;S_VDOs0 z(k#^p@*5<)n$^Y5=0Tn(2c?>TTYpl`?6JJ1Kku-O($Bksp9z+KXSt^zhYxBrgx z*{%~)b$D%sdJc7eKeqHK3prP;UuWiam#tN^?9gVRPpPW_b4=_U)rWF?>jGx|!h;P6 zZ$U*^w&PVS!~vE$K#jPyWL{o6LCt-^Xb%}ep7AG34O7jo)}&vX{=nBYCJ?R^jyvwq zMW4;b6P^UOby+VAJh9V7;AfLbYWs&OkR3ShviX^!ACY{=O&v@@JTsL~7kJqr)nkw8 zy)|4dF(xFD9zC zN528G*(hz<;>Kb;yIVbFSReu60eNhXP3?Jmz2DOQ(C@vq-&h?6JN2w5)T2FXNBlks zrSs9h=iCp)p72j;#XG-?t#5IJoT3R}ISNYZw60)8_NoEyJD!@^omZE<{-Bfw9?q7| zgO&0d;S@)G-g3bss)fF%N-sguW>HB{su|xq@pAnPZNlk*DD^^E%xZ|oWZG1VcWvkZWD^+jniuM+n3ZkTWfS`BEi$DKN=|-yBB&o6r*kB zOl?PQ=823L%->PBCQ3_9l~qysA^HS3i$P|b|M2Wb9UT&8t=eCvBS|{mCv{x*s~Jb- zEwS_S8lBqK`z2y7Eh~UDT|yu;@?WScBloq*6I4*Q{rc|io4%%noEpz2s2U_oOe82nr zBzm-ew=M2h%5fX5u9tuNXZf31iQ??Z{D%R25t<{R8~GrpdxP^*7$+n3bDbrZ{~FM| z*q<5eGq>iGM{Tx0GmSlm>%sYR(`c)h`%C3@BbF~V%)>ToQvLmW&9$+jQ;FFa($G}d z5{w^vFeFVrH;36CmYy>Rm2`t1o~S*mIz9D9XG4~(BE6fuP!O!W&DOM1ko3LNv4l{o zfokGCEt_BJEeXpSM?!cO6a+k+gvLhu8QmRf$-uwYDguL8vg#Yjiy_dtLBY`eat7h$IElO|UZ&=P9)&)!6AQSI z^ltt*DYg!+UcQ$1xxp!D7ftRu8DOJ0)KT{dS_s#wyvfYT%gjMwI4acng>0z+Zmq4i zP3ycou^A52dm*YMNfKw-=k;K9=RoM6n zsJ>Y*_a-w{JNs5pgd6`d%hg=A*i)d>$nt9zf)wd<{^StGjTXMS(#q{ho9j1BO@$x7 zrMR|QyPETebXfF|!O~7w8(*7 z-WItNcUxfl6aLbj^C&U($#G|(b!Nx2pR3H*(@&=*)s@R+wSApuRg3=b4iZ+I+t?t{Mem`CWAt(^_!iiK9Jddy2I9b($Wp{Heog_*x)NLAyspHwGAY?7!oFy4JC_ z1~;uv4D9m$udx&*9d3YnzY~nA(kh!SQI7)xEc`LA;6JUc-JY%acQx?eF7>s(To5_A zjt!c2FG+GmxH2X~2C{=ZE`D^<=7gG*js|}Jvg*|fNMq3EBInMl=KJL$(4zNcvFck6(D zw%B|57KKoB$CXS+8{y|?wfx-4^s~kl;xriX3PXpAu(~y@zNIHhhm%^>t~;HE4SAlj zm3}j9&!dZ%Rp6ekXgy?S1IoiG)mD2=i38Yh+id?siF z4o_fB=2MLG{6f7QPRPv$tABH(Bx68gFAK&+@=Kzs>+T;H^r;}^c4Y+2%54m2 zxJ@VSBH53!852;KG)KSZknr&Fvw60ISy976L$Thz-8o~gwNkzk7PP3+0Cvk1TwJ&< zD|*GA3?9}bQBhIcW9wfH05SoyNSoQl`=fLjXFK`ltko)`1e;~>BfHZMUF%AfexjbZ z+z{)lMXrtJKjd;&;_5u#Lm{oRX-U)j$CP#TC?!0&9}|*rm6JAg{FnmQ>XdSv;LZ4K zmqNPG-zN$JTPOrp)=M7Bvb>0%`wwktBiL`>q94;S;ztNhg^?S*O!EtA#t*hyezlec z+Y*lv6&a_G*ISL+exYja?>>G%aBj0>xzxhwUZyi9Z9R7Zt~+WilrWO$UlR;Lgv}xr zUJjr?G3|Q|Mw=9P!A?<8;H9-b1 zZb?2>iM}80kYa)EyZu1pEvw)3!?p>%Q^w<2?W&~?{^c>EJF`@$DUlrVN0F8GacSns z+gyYufPwGZk$`)Fb?~?rle=Kl>YB6*3gS{dzC0h+!0-wQ1H7t+(A>u? z6Kyde_tk|1(Cr`ffd4o4RI*1*Ms!@pkAmF3-WHO?pE08+{@aTE{YNKASfvvNVGExz zWLzQr76#2S-6boTS#Bq0NFPE5n}m<`bN;*IiS}gOM=sZ`G_1ONS0euuZ#`4~6_s7l z$M^2$bR1`W;qP55okf-$C^PGX99L4HQk-GF&EE+bZy}z@CVR)`)6>OyYqxL^YpdU0 z>w)k~TbsrE*658-w7t9^nnKy5NCo1w~%?l8Qh#*d09ykvlB? zcMA$~9Ch$tLv^w>O+jtqJY^NFF`}pQ;2-noOwgqs`KA`)w#C|fTVdxu&M16w2Ewzp zu@zDHi_OL#sO9cnm(AaoFintyjJ@Z-)Kw%t`R0+M9RJ$Y@e?YNu4o}QyKhlt@HHn9 z>~~#fLGh`zA`Z8{8@ym%t0`HDsTDFljeJM@YQ81r`2E)C-f`LT)ua`2U~c+gsn@s6 zLF%pjViL2ukj~Y!d9Qj^_EHf`3A*#K)+QhBTi0D--T-tw#3MDk%{%_m3a@KfD}>S| z$(izD4qm@PxtNFudZY(9^j8}=1nYl91PKUmVS^3VkNzk~yn=cJ@0o)2ZkjjmE7IdD z4ZD-(BTwfLmd8!)O|?~UdEM+KLI^8qChDAh`BH}Z#VG_6bo1Gl$IIBUJPO@AoZ0-3 zCt*I3&MVN`q`)vbT0VPN?Au#@PTx_Jv6%dZR2mYd!LW9N<6W$Dn@(iVFDdz%PJo9) zkDJcniv`;OIC|T?n7ITkIFOR*4#_uH3YO)8-^}zA)dU zl{Yk&aih=Cm~d?qb#Ia1Mq)N6O;vFzmf2DQ9i#n{A>F6VX!p!)mQgMR4b^&To1`8} z56LDCH=Oon68&FQ ztS#@;`={$t$C`CS6NaF33HPSTg;<4I8thnaeO$Svzr8^-n=KL~yxARa@!qf%@i==F=3DaK61)6y{%*a*HddWk zY>oe;(e1~ru$)A}?Kk6fFio^$TwKwN!KLlvt=abQ z$JXZ!c{6{Hu=~Zl@0A=0qOrbdqH3JNYZshu7Ip}#YF=B%398WHgu>BvQj~L@21Cf@ zEqiI{e&luSk*s||o?XFhn0=GqRYM{zO+LDb&!e;D5)n9{i+TTi?&OM0uxhxLLhpFB z;9<9~-H_^3)Eh@mdy)K3b@*0Z>lVQs`hvEWI_Q6U*jvA2tvEVw17j@UGj!+bOvvM6 zmv0*4O3+ipb)>mhQ%~FzV0NJqzS?5-@d&U{EnPK z#s>{GLP63OReG0sM$r4*P*jK;F4IxGO!&9fzsCQSH?$9VymQWL~~Sd>Z%q4m>d~9gOcwHr3AhuR%^HAKYPI(4uGEOs4(su&3%LS`7yWtXP>D+ zpl9_dWub(Zt=9RFmFP9=w7a+;KcRh0?$*N>KaX3*MJxBgk4cb&?8dDX`T|hqhh(-B z3_wC_b*_lIe*K~VD4J;PeLpf60V;r805;jFB>F6=sYytiFhouuOol<5fE?Z<2E6g( z%?ZSv#kgTf>BxkZdfPmDZJIjts8m|B#fp{gmH8Nv+6(Tu9%J!a=AO%YyB#w{fxt_f zNptJf7oZ^88|P_hwc-!)al z@VVB3l&{~%X?LzDIV4$rmD!{IR)rScS?m?>@uRr2Z00X$Dt9m|ynX!KQMO9U4>;skC0lT3p*C2}ME zVo!x!8T%!7y<~3A-o7mrL>De6@iN_ysaJki2g31?U-2H7IF z*uUGK*SPrjHGt3iFMv}619*v$P!`DXo0wSjV$w%5a19=Le91~qmi|9spnt`0!2IB` zU3_YNwZn?<-M)Q&#o-5O(LNwC?(=-|#)De3){i~!&q+$q&?EhYd=VL^zj@JsgNBQ1 z8rVYuF!X=O9fCR4fp`xTyJl$ zvYy@;z+Imx*%=P3{^TR)TPC-2o%~MF{6}A`2JWm&xi~oD@9sQE&=G+z_PQm16h1PawL%M10W!AX|;c~^AI`~kzf_ldGaeOzYDk?8dPZj zx(djijF!snz+@ii+qD zXUj^c{)2^_7=T>MAxRAljeLha6c29ruCxMu?#!OQeG4S8j)_$ktgEX6-j4oGt)8^q ze_RW|_IyZcXo1|^7-frGU;Z^#vOECDE%DjoRQS*3*w}v`XE_~kTH{B^xayE$gn!b_ zaPc@VYS8$#~C1<+?063C^tgRahRsZHOH$_0tzYQ(` z;X2xDls{0ebpJ%Ah5Zw#2pv;gxc|Vj|2qGD#J}TzqhMQ{4S2_hC+u%SdUYwcczFp` zyxH$4nBJ$>o=SMQ>|gg-fY5!c32(uuLIoYjri2Aa_Ia`oWohR=fzOID_83!;gO! z#A9VO>l|-S6#u0^AG6Hu1*5x3`V)3_u}2JacZHJG=8cs3fMM8=(Bi&|BYM7}iZNaT zr4U$Q|A|P;Vq~q-SzQmxQ-!A5py|B)VD%`4QkdCXv&tv^mqxP~L{{U_rW0@UQG_n=dp)_QewKPmXDAG0Cn#KdjAO%)efJ^l%d( z)yo=5YvODytb42IGg5Qg0s7N#tZ{xoSE_l=(IX*h+`FF&&2IC?D5?I$MqUd1xWIpY z7vK&Gtq)Lkd5s1q5P;Z;tu4TT?Tevq)2q7T9{y zTYpNGXl=mB2>Q4fFh6X`m9>i@TgCH^V>fdhen?|oid6%ycD!$y(ToPuh*qRzvqTuV<|nKUEJOo_(#dC&l79;^ZmI)BUn? z{r)dpZzv+~V11pjzdt@q8r)6r05d)j!-B4SzGrIV>Y22F^IWyuv>`PfO(@T9OSxk| zpru|vl*r&nb#bDhu1Jzw(oi|3ftHcS*75r3_5RdEwP#^?wz;R-d*kOTzv0oc+B|G8 zk&xR^8dLcNhos)Vp%naBy56@=qOzOG#ABSEr$Qjhq3wQmy4xPyccuC+!L-!QKCO)7 z>6w;8v)F+R1 z6VAcyrCL9t`I(a>3txxPndb+Zxyig{>&b)&m9~vLLnlV24iruvl1~2ZhF%Z%*T;^h#G{j# z(rw(fk_mmlvL&Ire2Vy|_zyhG)tlyv$t>Gksz&ijk zof2@^V?rMEJtF*2zs9%4dFW2%Z+t@2&K*44&yU;3#WeJT8~+DwZvhn7)@=)iK#<_> zP4FZ*1h)`8cp$jDyEhUbIDz00q_IEibvIH&-fv$;ew)f|3UJMZxe6Z z_{6r)FiTF}i*5bX^O`-ZGE~A8-6jkeeCw;i>&LK^crVw9{j1E2*QV-xVo|~Ujh^M+ z1GdzLscBS&!fWa5t5KVFAq@Ew`W4G?=c5VdBmt*{Po>0tHlzgNB8s-9%5kdBU5j=3 z(^O-Y9b2qyk(1iF!yg@iJ_WTqbzmYf$g0ZGxi-l)Rqb zI#+=Uc_i2-gpZ<2X7nBz0WJy!zXscH(7z6zSlrJ9q?@^lGiN)#{q_8XNZrro9oL^< zv)GY;6CzrDiBx;M8v?Tcm9vWh{Oo-Vj6}wfa_~FJt*edi-hE8=E(T!``YI3VhdB*YC2Ir;P;saUUPYL+omMP z?+uqb)4~naZUvxnuvdlqgeU!k_P6d$#YeNzCx51uYXZ;lg)P*1(GP83GlQ@n+8VqP zd{5IGoR+uC93ExF?0&8E5~gR>VOxNkfzg?pAT~RX^h=+XSRz73{m=*Hj>lV{eu>3< zk1p`{A`MB#`MB*e&r!z&IYB(L7Pwz@`6yO1Uv zn;e2Ywv}8%dR`_z*B^~N0(UMa+1AF`OS^5IYM2> z*-LJf2&lL<2a_w%>2&JlQvp;x@Z}ifMIiJm@~M;&30wCl{gzsX#u_JLhh2!V!b!u3 zw{``PoDF(L>wTKNu%jihoSY{6!7k;6NrQA!MMy11g@z}7#p}(Z`yW5P=O`!njfdzBIaJ_s>iGUS;fz3jN>#78$X*(Zoc-uXD5&xO63X z05A3QO=vLovaH9UkfjO`U+l9AsS6z#V(Kbur|TbuVHE1>>#LZ*_g*wy1xHxRBZo8Ak6(i^E)=qFW8j|AqK zf@Kb4IvYtWR?Kw-C27y}GOzcG;$NqJ41A{P|oW&<;?{Y#9I z^M>tXZt1bleMpu{0&x#4-*6a+@|KkdxI8q=hHG;q01X9o@(OsBY zoI#ByePv&DHQ?flC)3yzzyaDGbepJays^FlI+~_H7d2z7Xi0J(?0~<9XdQNy40UB* z&0HB2PfAKdU&nt<`;0d~&t@vcBh&ptpSU+#^3w#pKJMDb8s-k_yyier}9<#FNEEDI*IlwZUv?<%?Z#{skr zBHk$i!*JA6KgcFJSYn@-S5IPb-HnEGuk$(6nJ*x(ReOJ!@*73C0BhHAU8WF5IozL% zZFpIqdy)`~_FK`T&~KAEYn% zWQ`p%E}MAzTZ=3@ikMeWac?|sVs1X-VJ{uJyy&wzT=RAt43?M=HQozul_a{ma_r)& zaDy$R)R5TiJ(dBltX9>b8%*D5wst+6#_y4f?yg<_UHh*b!%>M6Tbc#NLsB?WqRm?N zburg;!|99dwiy`9a4E-gqs!>Nb?jCUTbDB_H&4TiSTg6kG1E{`cR)uI!SDv@#jMK# zx!jc1ev*J4U>F1kds+=nFt?R3g5;tWa6MV;*U3E9c~vF1OJs7d)gW-NYO9H1E9s7T z)=EgrE*y93n;qB|L%VER+my*RJE#?FXs?AF=$tO)WYBWdiNj|bq4}?8TseOxgsk==cT`-i3aHfx-4Z)p;%LpbLX8pAVO9^3g3 zt_(+ksKi)bIIej3DxE)4;c|-W#l(4}yCL3i=}d9{rU;y+`HT>QH`hKlKAZeqnmT00 z!a9qLn@TsFW-V9;EhJ5@VW$RJ=A+4EWcDRUXJXG076_=kZc1g;xoZv3o^{)K{qz0c zk#)=g=#@on-p}J!{8=m(w)fwISPT*QVQT=KiV0r3(z!{b6B_@)t$Ea1B6Lf6*zz zrr?TEhQL~%2J6%{eI$W)^WHaMxU5sp;(5d zwCQkLb*Abp?r3=>1yn1DFhdd;$sCSGZiG0^hL$ESts)xo7<5Pz##7-fY9;dw8;v_f z5iWwlUXI7d137J04?awLFHfbgH&o`F#T=690I>-8{4Vg7H5`u(#;()G@c+HMp!w@sKjw(x)oS}< z+;jab=kAaGxjE7E>LL4rcmdOJYmYayb@(m{EANz)l0qqA24PiD^Rumz_`}JwBM#*prv^VYCwPV=)vd2SxFCoZ zu6~fleUTqNZq`Oc7>7z zd-(eXOc(JY^(P(I=7?8ndBg0iA5CCw=~YCEiTYl)l5urxvwKO{XL|ZGJ5__ zvtDM9TJ7*X$8D+_MBPhL74wzb6jmVW zva`)l)^S;_Hvfbl!9c*i!+b%wOqN*u-wA(ou%Y1K~xN5{vxTM}x&Z?)JHNRY!K^K?`Mj#y~eJmi>v+B_=tP``Pbo&&E&@tk9e zrsDU8W+KC%-fC-xKGRP)+L9d~YD;K) z3eDo)+gCD|%r-?P`9fmAH>0Tiwju1)vD;oBcfZ(=CEl$c>SI>X8kanN{qp9nN1aIS zyRw!y-o3Px3>a}U)eft;cMO4DH`Ut-5k$xsg_KmGZU&)#ttQJrS*R4n;jm`hxRu=rdW+tW2zjf#oO~@ z@NY*tJ2BD`6hdg*+qUzz8EQQ#uJCi+{VSNZ=deZg2YPN#R!lz_n$qjN+*x1 zV^N@Ogq7`8nKcF_G54C9^me*bdKY@&CG%7|%wO=!>4qje%R}6jdV9E~GZ)Bh>Lsu% z%FpeT`f+UgKDtM=rO`cNJujNAve0KlR*u@%1x_!CBpviL0JIBUy><%% z$wIfbLB3r}OON${JR7?3Q({;=>%RW*ZNrd?+nZV=Dw3wG#KxURTH=ffja=e;N3)wa()Fwdc>{pMu{eJ&f8OWlZ~;CC)+{J>EZ{)KNE zt9}q}P+eu#*BTqrt5E8EOHfXIk8H7OKlmNa<&L|&92Q?YQ|0ED3jnb`!2QgFo0jP> zm`#(5dB7zK20mXoAph9*y+i}pQ{SwNC8Hc*jY{MGond1_Sa#r-q9?D8Hbt^i+$YbK zr3vYHV|EWtA_58Qm1jJa2Rz>_4fwe=x;f$x%4^z3ZwXPa^_paNOPmdQX?| z!d}{8V+FHi`Vj9oL3#E3Rp!fUsc5RUJzefO{%7_uAoUNy^%ypP0SqC(CjrY5o_7Y6}V*Oi`g*&NLL069&rmXc5Hy(&r7Bz%2wUh1Y#ab$&aNgLhWr99Z1Hs!bN$T~|HRo5FBz8!OYvStcZ#@2!R?NODKTP{(oBoYBs8*nhJkNsnCKvM(ILr8$Y{*kc7-_XkBchh&1$ zjA}6*!IO9M(_f$CUyr7W3snY8iz=DQ{|>AGi& z5jaO1HV^)|GM|hq3Sq8{q4y)Cxp9?Dc4`4d7I6DoWGT(zU0Tx`*PFTen54Bw#T;?4Z{pDm1%|n24m)F4uP;-IKR6 zRj_}V%20&|UbLN<>xc<$K2m*unn)&1Z_)YegE`mNAfujMqr<>kc^aXrjAVArtRHJ~ z$82oudxlMpk&^}O;TrE-9VNaFZE)->hB^f|cPOewjo1gtB@fS);5Ukk*_xaxiLxm` zIrXlrY#CU;Q>~L~*jW}pt|Rl2N8RALtO}WW_XhfBMWxwWGg^XTYC}$o@tvYDtBZ>`@2f%m{bRQ1H-+zJx~lZPTeHOpuToi!@T(}#XH)B7SbhJ^t6x9jmPu!_7++I4hVMxv%{RZ{^ClE&s?X=e zL$Fh|{o1KOdo+-TAl5l`gRf9>EfYdC-*6f1fNr?k zcG{8JOJ|*R>37EB{lPOB%P~>=h}d5+B!wU6x+vV75MNcUyP+mG8X@n zy!L_hoH#vB^sdyH)dR|XW<-{%PaemX*C6w_1CP z{M^H*qp!<@)MD`X=f_-~&X7G1#SH~q9Hgnix)L3zu$H6mMvm|z07c5#-L7AYf59c! z0ybMH5{Xhg%3~kniAAfT{{;HRGZrR zJP&CGsh$j=Dtqj~$r!or3B}a0KuWDTK{MkWQ1MRRVf{oIA|u2}IrepbJ1JH_vRB7I z?>N}B5Z4u5MB&p5T~*|aOh0POqpQoFtvXzNke0GC{Q#dA9OAwyN2P1Q*FpS9^tIJA z+9ShemdVS!UKz8Et{a;R;^-m=wk8i_hrXZYa2cFS|I)Kd7GaIGH}->h+hM~c&-@uGkLGYD*=YyZ*Sx;*) zvCk#+_hkO?x`3gkofXVEX_;C0++AgBA?O0h_ok_WEHoQdj3FcPAry z<+!oOb1Xo2%4mD*Xg#_}SofFH(eh*0ir_Qvo9T-?11RDBS&oU$sN43=rM@H?BsuFC zY;-nvFEji==a9%8R$%`M{dxzp=XC`}I3tH)kAT9$mm^Ek=q=|9Gv|xAASgwocWdVSK;tn+F)l~7@2XwGLeNJtsK>s{dbXG3q(k}#Yrd)0J; zng@ew+D`eRMLsNqqXF;-3xZhQ*luE}Amv%{AKEf4xx3iZP5_XGPK=b%?F~6jv(u^B z9e7Ev)z(Lkd1JPOB{PJYV41XBBySW4e#xU&oL8WW7!bR~}_iTm4;K?-%ua<7J$k$+GDFMx1%_T*#%cgv)K4N;yYj868*BTr&%uXRn$X?`;31 zLq^D&OnSUg7J*u$Hc3<@P)MV5xuLVvbkH!ytg)wu670^SBR-r1gLCrrE?v6roL@*! zHGGZ1{QSwOgd>&$A>Em}<%q0H2U7TuA5`Vb)TPcnD(1A$?C=#Xh-Ig`T}*KqDRkG+ zu5NPYD7PA#ydVlSK2#_@F0lLv*A3i@^hh{hIWO7=Q|@0ZB3xWhZf~(6ymfmwcER1Xycybb3IzH#7oxg4i*uN+^9It`*?L};|^XnDy$365W=kd zm4*qp4caKL=M!a#y0e=b{+h-dUXW=JO_lf=oUE3uw#q5|tu~m2xmdsh?SOPYzkKfo z0m^HL&z8SWjC-M--Hu%#uY1Hx%j^zu`Jz*Yw{E8_0q4=sB0T0wluyfenO~sL$rfh8 zigwnccEI9G5LJJok`jDMikz}Kep7}dID8PMEwQPh_(JWS?8R@Fb2BWhnH0Uj3LO?# zJ=G&0%miERPH9$KuU00~4|Wx%wQ%oi z=~d=eLqf+;1ud49RSxZvGe+{0UB2BP0a^C2*8~ppdF`?;E}B_5MAn+~EeaX23 z5E5%X5}zk*8+g?AOGls*ly*G6G4%C@29{n#cvEUsM5Rh^2b&?%ZR$QG*u1wU6G&%!;Ek5}7wf;BtC>Gi9YUB+Fj=8VO* zaA7vdq1B@;5$8(}%hK+LHo^5H!eC#nn&M2bVCZRM_(BOZ_$sJ*)JD%`->k3MexSnm z-7mX_sOxrxZXWkrK%@7<33!{a4zg?{U?N!}bUhkwS$_88?eG;DnSG_z(M~PES7ZGM)zVKEeu-K^2U!_Ze zj=iAm{Rb7E&ok$WAo`?IeYyvnWmBU^HlmG!sOpd&CMs|V>QSCT`9;HRzcQw3%*0?| z@AgZ!sS7Ii6<^jZcQ}EiZ~FAVcwgK%;TbK{O>yqQK%nYiJ)Vl(<0hW-<0-Y9WBT61 zW0@0u;V$D1ql+8N@*v>K{UW!!^=rz93Er`$q>4fL^Dl*y%gqD$nF=MhGB7Qy* zyt-%s3&^F}r{6t$t1+=Q%-hs&1j!t8FQ(H_b|$}pfj+OyC%iA7W6T`@MmWAm-#M|l z0+YT2ed(_EMY#{RvtV)=Ts6k+qQT+2vpPBfDpQa_(J;U)^0i6 zxzyE`J2NvAl7$qM#w!?xBnvA%=PYdzhZs-x?kKo8+#Y2+dt<^YqW*CZa<_Vf1i0e- z^?W0v>@#1RF~EZ@8}auBNp6i^Af=_kbdjun*$9%5-8F_TJRl*@Ykp*l?1=PC&9P0; zke88>SDdS3hzw>PBJp$}m;k-u`y_P|WEfVmqQif8R$}k$+_*O^=d`>FV+!FVI5#oC z2#!5AqzFHI?HAPFH+@P(m@jam_Rl!N{sTQn&rb)yktGYoH3jILv_-QumkpqKo+v`d z7a8j%6~%I1)T8;Pkhf1_z(ALp)ME_8*9QuQ(C#;TQ6{x!M{so8i?f6Osx;J;k$ieA zZ#oS%E{+$3wwMeTO}-`hs~h1M{I4d$zu*7c1|ZQ^1+k0YLe2l`RdCn29?r2HJjz>! z@VAU(I%nxLxc>dgt-j%?K3qJYo@1gtG}5g`Ibb2z=T4D+iFjpD$4v*Cl)L>8t#e`H z;NZYLBb;NGvUy{n&M8&c7f>-<7*r=$X*G?(&(D8Uw+%>5O)afxcx03N2c+E~Kz@0> z;DqVw>iUE@zVxfv1$tdCx;erAmcM6a{(XiTDaF!?iU!IiamTxHm=XB%`TN`d52=lR z+wA{KoulW?M}`Z!f9QmjL5tsnH>Cdo1izJdo4K;1G-(k$L9_TueOcsGv^ENi=%ar=6g;%7{0 zMVKLwzYC2>WNhPABTHNj>GA0%jhA!kkH=L!Ijbm2OWYxrJHvZ({(Q3och43@v~{HM z{`@-cJQ zramW2S3MZ&1H*mR16D}S*DMGpPw4TD0uBu&0yTl#0w{ouKCr0d8=JzGHtU$T&!t}; z8tmd{b5O=gV3mzaxn6b-en3Q6i4cs$xFSsGR0yG(nO!O$p2!}xFR_0WVb=qSfDs{`oSoML-Pj%L@^xn_&ioa6@&|A6*X+}~0j z0U~C13Gr&0UUA;SaidI@zyMBi6drUQ&d=!#aJpQmX*xTdZW{!mQpf!hnb-DQo?bY6Yft#*1v0jYO*)F)@T`PI2JWuv=)ri%Xx zr?QH})LGP0fOLJym#>&^TE~>{lbQCp^Q-s815T*%~>1 zeCc1vCYU-G2rWA;D)V*VftMUK?hlnvO`jPk$Xq1?Tj(Um2Xk{A4|`Z@ zKTSQ@JJeYuzf5k{=MdJ7ZIh3waOVYqR4SgH8g{jw!Tu${OV#KY<>Kn6G$L#*ist`N zW0rhs1+303Y*Tls`_?~OanD305y-*%E^JvgdAQ5&chUYC>sMIKJlyyAoTRlyuaY6s;>>HE7K+sJ2wYCdzfFQ5)rs~avNs6H-dQk zTV>&oMtxuHwrFq$!!n)=>ct36tnsX_Cr4pTsaA9dTQu^URS!!8x@9mh<&`RsXP% z2W?pe{;|V^*)V|xE2bwBmLY)u4rLp4*w24hocD@`;dc2PD7QY?(c|1sWIG##E!30H zSI2kZa5l|lXumNP)!mK_Z2(<@HlfjlD+wVWx6p~YYQgL(N zE=lRSuea&Dpmf30PE2U2OON{5izi!-=?{Vrqhm-x> z7~L4Xun%~CF6Bub2zUMo`Hp6p1d)NQSX-*bqko5UTP6(VnQh3AS&YaG9GV&G^nqrU zdyso6FbO|>eD=7w&vJcu%|q7zvvkzpr}zOB@%bG@{ewO^p)aBfhrxX=^2ug2{o!m? z^T-%XmPckNHamGF4<2;<1|g%RmOMznVAr7XY}eJdiJjb9RNIFcl+sA7lBm8vG|KV} zwAU6c`0ZO9HOxdz^CRS9IqXoj^-bZHl?d;aKyX5+W9iUoS3D`ejGzb#M(91&K6RT= zFRj()2Sn=%u{PY!yB?c$TM>S5DY1<%M}v_#qUU;=LyZ&qW1^(99n+*yLH%S0ewJ?w z{2~*NK3P34L0VweF~Iyu7fTy)2}C2<_|&t`a{vi5yOrw*e`Yz2=Rjt5=9;#eOz5?D zxHQ`fo~_9L83Ms=PxkF4QL~g|Vv&RmnPL~@1Nl@dfU;{k(iD~m&F&lOQ`bYUhk}&{ zt@1Sn6ltLr=9Tb5PA`YmhEOC5s1J?-jHyE9n77LpF`ZpOTxEz&N&#@)z4fPxgx4XV z6V%>a8*`6_@d$4oq}^*ph~D`{rEL1n0dEC{6vsiY?;nW{>M5X#NJkDQ^d~*SuLo-A zw~e1s-&MLUM$aM`q}N8*uCZLG}FD&nmDu$p}{i|)6rcXqb| zXbo3*Q=qT80#T{dS2w~dmL7|v!S9#Z9odDcgS&Ue@$FA@LCOHFX=}5@W1|~4>rQFs zbF{n6XZ_A<8 zj*K2Nck}7D1lIhfC;^`(XLx*h8@!B&NH^?Plf3=4XXC2^*WUX!7E+GofkKOb7+5?s z89poCXJF{(3TZ60jTfEl~WG$aXjBe8b4GiJ1p+8w@^Zw!}E6Sk2hO8m+Y;TFVCx(9a{3D zFjx9Ag0-MBZQP!4`J?jIX`Io1nX3rhMFBl-kQKM<{Rfz@S!aL|8^0+s=G$#Zv|)&@ zV}t+P4<3Z$N{Qfka(Zm$#0su4+4=+X0CzcD#pRVW zM~U6Gb9MWRc3i=9&Q0MVAw5O3Fy^Czd$t*J_x9B*qq&ccl2uB`2z#EdMXPyZjGYwp zJHaX+VlY<(eMojiOMDl9+{sS|3Hs;t=$^wFD4UKlzfE@?tw4SheM>BEM%tKpN<(hG zo3cD+Q_}q?2QuqQHCBy(cX{hct_GV!VjCDvuTi6ZRb%))J47K$d~ac?-4tq&dM9C| z&3PR3wWZ%8MMJdUTu()Al+U_R`}s%r)o`VR)5K~D zwb#5zJO+#|mBxfW(i)iIcV4+h5dU%SXCg}(=N_OTHzhC^&d^gsR-Rb}VM$eeeMT?IAs1Wd+ugPafjoZ;Ko z_wq5(jmVcMcrzF-@O7Q$CPu*1+nsM!>96*ZglvkEZ-^jtA z9Ff^X(r8;KQybA?<4TjOzTd-a$mM0FbIlaQOZtYK58p|Xz3Vj6cq6W+^zSR!;3Fug zed-XsIJ344psW9gS|QDNMQc%5g*5VqDzQ>)gF4Fg`l4`UEMfd|wr-=|HQ-or>RvqK4Eu?d;bcA*Y^?Zs6S)CsYc{kZf`4bw;!kd&7LT|ImI~pVr=*d51$yB0YHbySrqie6t(x8%;w7!26ml%| z61#a}HYRNm8R#Tr)(@t&GUM7UNIbolT%3(#?;V?;;^^><($%u6Pp9-$34}SLAtCCE zH<_6j-YBGsk(3-Itp~}2POdK5cBcjyQ*(@r|BxRQYcgWSUZz}{(6gPrg40zzABJwC zZ|j$D$?KnuNTQq_MP%<<7yLfjnF}Z#LCGme>Wcwl#)6OWj?#;rZPZKlJ7#ACi|@cc zdrUPfq)o#Aw)B{*2o})4JBr#`kHmn%&8f7_SjQ(J3qHAnN$|ZION|V0+RI=1;_WK~ zmtvE%#Q(~jBj^$NXo!yO$zrI&$9*qjPsySTKyiKEjp6Fd^rj3jEY|7gBDIsdGy-ImzQ+H4vTD1Z!vQNjDx@+0NK(hOTKX%?XUF=c zUR>m2R3ArnUV6sZ1KB`aiB5XnQd3po$;@D57gX0tj`V`QLenJ_wj@rlO&Y%Y9vhYT&Nj+-f~j>#KEcxG6YIy1&hUS4h@^aio=3a>DjoJZf`=cjb%@2 z3XcVU6n+;M>lmV=;7@-vc_m$PnMYL@k)?U!YR>#c{6EHD*c@&Gjm?o6zb^5NBv)X_ zhP&B2IE(~OB+S!Ss{vwj4^~g1AG&T$?y4siw;PRvbhvrVEi8V9mwxGBa7b)rZ(IF$ zCK4RNwl1xsB?a(l%MKsuT9&2ADs539za^F1MG)rfLU-lY!>wO>3af38kDcA}J!~nF z#_XdWzeKR_j|{U@2MwHepzjn{|7n-`JseY+ppp)p>3Ln7 zCs*%D0=6da3gIhQR`r)AYy&LVr#Lm}Z=vdc53>FY%>Q^`!vGoGEaPzampL8tjI6_F z(n*tBQy=Ttho^`95d)?xsGS7)RXbLpgN{^Y);TwHC0BL0Kn}3K6l`|bE z&D_}B{4;SHcG_a~_Ap`U;b+?*Xru2O3b_6p7hEu&PGLO7BZ{f}xti zf|PeYCw9F`Y=f_dD~}L+`MFzRakm zj7ADYq{Gaz#_!aFtH7`n73q{RA~*cj;P#If5CvxBkRx0#ubwY0YL&E=v$~+liq*qVFd(e_H#*c;Cf7!5=$_d zNt(W-rg(N%Q&IdJF%WSbZsdVC!K|3xJL|Z!Z4r>IFx{_`G0z%w%L{kSVIG2+gj-qJus4_$luxK9V<{N{ zK2Zm2B(Vfw{9qjjBf5MgP-fP){kW*Y>Z7Dq!h;3Z!S;w}A*F9^#16ne(BXvPI3H0AUs;`3|F1 zcATts-oVWpt6cSA8Q5^$pqqqgtfWk$Q5o)!tgi7JX*ll_@ zBdnxo;v}Gq%^Rd16c~g{#v5A&nM@RxUo0K_Z^n1UeF!KUQE5%_kFmb=d4lC&Ez)Dc zi!f3`5w}HnO5Oc>!N}j|@n)h(xG4?7kzjO@(n^@TFa3}f8mGZAd-Kpqbwv_&DrNXq zZ-7kdu#9{_?^DkR3}i}z;*m!ge=qGaE!>S3uoP9ENws)&_4whFDf1n4w&9$$;5Yp8 z-Lha>-Bz>+9kB|{^U*K%v^iBa5#%I#Mp-u!JMsszi%~O0qfW&saQP`el0tN#%gk$2%WNM_8icvJO6|zAg^F<>09@*tyHJBDpTs z8;UOg1f!}|SUePIvugek@#tpJf+@#SB>X_Bv}iE-mkSVUzJU$U7Hl!4b9jh**T_>l z%!0-|+z7o5uZ8x+^4;n8t<-RIO+WZtMgouyGFwPNmUM(wuWHb=*5lSeKK z*9*@|LFmmha6@EUa(+K%yVZxUC>&4Q!fPG9)62$%I=7qOfuvhzM&n83pDu4}A)zQH zB(~eSk5F^4GR{HYm0p^$z!+Z3-y9 z7}ZMHzdYZjz1gfN8GF2?t4Z!o4`dh;_|~D7$cACP4TL%hehV+jJeA?D*b4-_>Nn!n zZ7(-#$~=c@Hh>|QZtm7AOrJ61ADZZo%vxitSQG9^`_pG+PD8xy7(v!sU7geR>oO(H zS1`HAp&HON#wG=GwjD#k($tla&A6}qIyqEgea))&>cz2*iI1Hm+|I_5F9cd^On*OK zQ`}mc4cX}zSuRr$-&k~~jd~Q-xV`n{9Vr7{6?5tGmqw}S*_a$3xz5ei&n}PlS|MSK zOuL0xbA@l$@A1HzR0&I9?1CfDac;=88skOP+HtxOdaT9;+cuLVK!33Lo<1t=p1aZU zx_SI@F#nzR-CZHCV%Np^zWeRZrT~~~hT(7rpRUKl<&f#o!_4l0e6#YQ4&$<7*Y(l8 zTNd=%dG5GA;4_QIarR`>!j1I$W6UUSpc;p&Cpd}Vl^vW$82oz{Z8 z=5$J**XHrQ`gsw*dtpg>crzqwfdl(-&+ajLCtNvdso3p6`2j1O>Ka({9@P0MmZ>4m>1E0N!4`IV!OZ8ZipcaLax9PPxpy6$?m}63 zTEq3&?6@e`augvR+XqYzWc#d;wM^Xw(M9Kj#UU&HYQ=*k8Q(j=?VOf$3LHxRD`Wxx z*E#3<+=oaFvk!AVj_~k%`mnx*#2Qk}uLkssnhBgg=T1-G*hMcr__0f~bQM~ds5jP@ zm!NyTb!cX0u&Tl=bb?ocX1p4RcE0^BQypTZOT3d~XNDh6kkBNv=n_!?T%W8Bb#bn| zJxIK*Cs%%gnjJ!`c0*hX_UXMDJ z#yT(L3^T0}hQFKmJg%rFdE9Dy)mR_9*q}#i0B?Z8;FzCZ)Q(C%wux`w#`D&aq_l{~ z2XB=J!w1=CXMAZ=ys;$e$M3hF-Ykwv!)CRRqlmq^$4-rI#;!)n=hU`(<)zh(zPCcX z#t9`5s`F(&t&r}6abL0VA+<~^G{U@s)eBwlwKb}DYS)=t3I>e~kb)Jj@#^3ptJOnz zkVR3zkah={uBOpRKP$8R>?Gab#JhSot?%NMANnyj8lXx+T*z}FMpErqq+BF2&LD>E z<;5o7?k=C${F07)=kvqO%?MJC$n;N2G*0pInF zTyz{>VN}TO?YCo1hiJ|yPV}W;TP@jzo^-8M=eh571S`^*kG!v*t&BeQ=c!-jo;wz5 zHoKIPOQv7jN~8~sVV9gXU5eTq0~4&8FG{@ZhS@521^W(wt4gX>Q)N4qd)Ldyl84IC zQ3-~vKbx8Y4D&;9Z`A{JUm|ImKP*b5dpI)<3qh21y;q5Ua6`jDB@G~S;k!3X^VMVX zvl0qsq)kZtZwQc(7jPiy7#QH{l!p@A?cHv`+BT-(`)#WR*8AtGJ291%7FtQ~@dEK0 zaggS&l^6*)v7XkQh4QF}xYo@~H6g_QA+MVEoH0M4!?YLPcf!0DWeUNu`n~?mt3W5j zL~m93AAPmQ0ut-~nca%Ascq6o^GV(xAC&fr3`N8_WD*5>hI{Ng6Pt(1rJpI+-So$L z+zQ)v)=r!Db`q9)tyu*apnTy2vzG;iCoY_CoiAOEqS@K<=f7osOer~!cjz9;EzV9% z{zcSU1-&<6F6K}05%vUyd8u5G?U@aOU9CC%OW!ZpTiDF@$;c@ltvkPaMj`xN0HQQ!~0}W%Wu{a__bZdLgev2*FV-`qSIgev= z!=mGp$xIoI2p-N4#2|}R2%^oQ&BWwXbg%Kdr&Y>vfep4E2YE9zq#KUn4?CO|sgWDG z--c94Z(tg47pv0A6Y(F+UEa&EoSuQXtmivWyg!Mgv)8nsl2UB84A~?_i<@}hr*EIn zS>4SpPf2&T3dQ6Txlohz7{4uDcQ}IxlpHN|cFX_hIY_1-otzYR(Se`+$5$Nb4PsJ)dnD*%dZxQ}?02CM1L zE3|U4QboybtYaF_KJf)tMD$o&F`N@610(Z;TN@>{a*)^N1v z?KEiA5k2&Y{r+%OoG%0Z_%2VGTmu#m`71R?wlxPY>W;SVR+{PY-eazZu%#+HQOAa)}rR32L5;;T+i)3tM{FBC{=LkL? z@{rCyeN^MAz!Jai7l=2WP$%=p^{fxPoN9^mRG#3kkE}NhjK}i7PPufkKnzomLB9zm zO(7t~kT*$s)jWjnBcpON$<8zJ<)lU6>|8Z8Z&>w`TO9O+T>DA4MRV+09W0-5_%a^x zB5`ca3#l(s@^()ViK#ilGa1tQD6+~jVQ1;lH!#BH3HOI%#^1V>UO@`NPnm46PM+#Shp$BwoF0RaWdcS`C z`al-_3+)HcG~|bIhhz+ZLi8yFYj4{haVUj)B6?35!@iJxCu(YFWXS!_kS#WaQT>s3 z5moP&*f`aRj{Jb&OqyvLysZ+Kuztss2RW#cs>LMDwK$G2y?to4-0yRuh=?pBL=i1# zvvC`8d37~8^LsFn)nKmDldpO#S>9ay{rG#!uPeS6!DM~|6~S5X-7Ku|kbY%lO!)Zt z!M&H^uXZHfij3p;U}UxO!{sn@b#6mL=y*RYX<>BXL26=QUJP$!r71SOuC?csh0NQ=&;%Rfliv%PuMZ&XQBq&2<^Gpe zg}|<@dR)>7@UmEwwk*gxghB$_EtU&`c ztRvk1T$%b`=co$JVvjB2Wz=C59fE&uDUIwFSQi-Ukw%&J2lsA*#8UkPo(|ovR5y|k zwaCAXM<(#tUz)Tii3YRYW;{_jRN%%+_l2KP|r$h z`ENdK+-??s8vJA&k4@=nU!XOw>4bLS*A7mT$~5}@u{lLW6_a>MdmLaecy14xLW!bC z>_;Jyu(h?&lc%Q)h%dQHCG=~hC|wd6GGo>N8T`i)8Zu<4E>be&H(jI@$~vFOl!fcC z6%;kcdnA=XLRJjmZJaOfRKBt0DAW2@h_V?fD<{2MUF`nAB<`#$wEoMpF3TC-H+?sH zc$?$LKh!ZT2u9{h;Y0hH%vG00n#9yyi7DEPS(zwH%b=G8`!^Pp2RyiOoX%1a0?!g0-q{>@9{<4X?CgACi}^2o&b+x}iK!ad~|6sxhet(O8 zi{Zo{VkwNR4ypvj>nnYZ&!N%@lo$1`OBmq7W3|G3&^;ha7{-$C{d05zGfb8)gRYp9 zctkE7+`DE^j@%7u;k7ya*x8*$Gg14W(K`34alF)fE5g7YX(}G4^@bV#j26TH9c8hP z`EAcraDql1micOTWAzJxAx^D-FB!O&96=UtF}&jxei@=WlqHQ@_}iu!It)4{MwEJk z*9X11#51-E(&)Ovf}-`tya*K)@OlnroEF+DBij#V%q!!;>nlZNL3{(6N4#jrytBF+ zB?era*AkWAzD#iXlChQ4)R9mG`+Nzf0h3C60olT>`hVjU9*M-L#^Q)14AAf2+Z&Ii zEvC@#4~7b+p7iI|qhc*Zo}(@K!UIBRfyg55g8xYP=8y%VU33elk`ZYft->-0i#pu+ z5W&FZxL(ubgkM7Z=A8x_Aw`W+;u%NUvZqU1ls?FB76$(}pALQE_%7$qSJq+pJFy}} z0x*Moe<+P)VaHdbfr#G);-QoGT-o;!VX-5hN)sznrS+u0Dc2Lnz*~e`?7Z=iVHZPB zgoZ@tonwVV_WR{5*++y--&HB|u^d9tf1M%(87s=>RO2IAUnH@xtSriC`C$)3an=|A z1-b;~eS$%-$0Is&n6oxMB04l=*}*>K#edkws;TM|19c$Ir$l}KBX9*d$uM#-o&wE| zO*l8}Fd0KUNLvW8+>PVzSfoKeIAa{k{|BwSo;ufeb97{ZM*(Cgcz(Bs=F=y#I>Q*| zt;$1C$bwsz3*KkmPk+RFEW%{yBn=D%boj^GL_VR`+}NyrU@lz1e`bj8t0MHxOlr@j z#SQBxhPH2Zy}edlT5<3pMq>l#p9av!p9MszdBSJ^8Yv1GZhRS#m7_FHtBo@p-eKE7 zQ~cDwspWb%nOc1hJ7(>2FeXRbwtv8Hd+pq~-Ewb0)150C-rxmnS@Sn`a~pFbGEv9| z8qkjmr3gn~tO$98s%(&DgY{A%MYJHK55ePAiJP97pAl{|Cka9&%BpLErAIEHDTf{> zX=%YA7IDe*xbm7rKUrI zC8uTQyM-DwW2ZBF`k5+=g2>ah%(K8v%Ok z)w#JTBEHfqMHL(X>9vwe7MsJREct~2Bbhkl96OihtG60St4_#y6}OltisDQ^o4_F{ z*$JG(HBrtMPDm0}p$b$mjT_88Y}L2@qC$N3(DP6`vx9va`2^d7HM@Z;JR0o5Z%U$p z06#I3i*cMbA2Ci%Pq8=A$!-NXY~2rBIeggi!{}aHP6&jGlgb4;_r_v=gVnuP4<0)B z{m3>_Ldj;A?$Mh0WS-aU8!Up}3|(8EappUwC<~zkW|=$Y_*omK`VyfVv@c@mb#n6U z9{`^0|L{HxBU+!l2AO}dN2aHeUD9J1f<3FOE_q={iY)|s?@Q`8x&ZT(%M@5eOPpaOtg6iP*(kF=-`j#y#T zELp)ynIF*cb0+cRKs=#+3v`&7;*KMFeLZI*sdi+XdlW_>;x==d-y~IU>G1dvlZo9! zXI9?VMSf|Bk=0F+8s4WIX)nEtAZm|AFfgTcuTo}WDDue5wwJ6PFmJP2+&UKE5tuF${xv^2Ga zO1$|Tn1b7Ob%n@v=#@(UR;Rz4sQd+~L(J-a|103s8;QX|tKJ-tah|$yAVPC?I6qbO zlU6@zeNhp$G`JBKWzTG% zeYz(bgp*`;UGHf7vpR|;{;T-?Yhr9ec(HYlUwdQ9=_LFG+o)Bb|4YGv8o^rx)YO>(j)0W*v?c+Aq?#h#~$vCY3Z#*#)jfWXxl9Z*R{_B?{ z#F8qzP;QIW*|A7ZONYyEw773J-9S%f?mso#$rhW;J8c&qewiK8z zksb*&~5fb=CZ}UGs3?O*jRJ)%oJ8NPWZ`_*O}PAOmi;Ep=gwpwBJ;=t5kQ&CnG#k_%(U4O2p%*=^J)fvWT@@Ao~y=P9E z>FfTEk;eD8Dc!r4|9poUE~Q5Ip@vh|X|lz(W-3*GpDyjT;QV#{D5)rgr1Y1$7- zPUc;ZptNR?h-yS9;>sq8j(vv2W6|9M#($HtT6Ow}-lGnW%}NK~U`{wqvQ3gV7C1D$V9^@gYu>#d3S z>@M`BDcN5v2y=(TO-4s4n>`74J?ao6k$(bi*H8?s$R+2R8wxYuNv?2aMp2PNs(;?-VWPnBoKRZ1*XNF;LEPLk4r zSsuw)`#YkfpvC-6`9SY_j=1$rHWpdKMWH4`Fg{_#n{L1f)>&>eQWh1PNhGLNU3`G6 z-K0|D`pRL&acP2(l|2TZyu?t0w-5SUS9ksL=uVRNa29R(Y%Lg$ z1XM!*u*7@3AP{12^cj*hZ!CEz(0yit-;&MXd*x>S`J;gOV7kE5QB*oeAYoi~Wyn&< z&8wos^2UA^H}Xt?XDX+Y$mU8KV3l*hf6yTf6gi@8I665p0=eI>P#e#vP#GTE?#`3F zysD}gV)~AOW{M32M19eva11;_44@iIodK5rI}k=DhVY+F9FK)&-onzd{Jf%ckKf&L z{jgEw0Iogt4eme+2@mc-LqK`M9L{Ze@35hI75APClezpSx)`WWVSt@1(yc$y03&5+ z)=IOpyMDC~oN%aL4p(_8A?P`e_&MXf*Pow?BbwZy;P!j_F}%@r%ePB5M2$GZcOzZ= zWy*uk4x|`pJgUVPvq_r!Dly5ge572}N+bo9g z!Y$Qh=eunE2=Zi%EsT-a^I9k0Y#i`--`Uv>TW_!<39!YE?_N~v4YRi9+m75;^By;> zE7_@)uBTi~+#3R-#uZHr`%`q2uxMnBy&63L$e(^4csjj6FyCCFC#hF?s)s|!f$X6o zEjLl1Rpt-~>m@qj2lHf#vM|q{n)Um+=wR;=s$83z-Drx7y5xAd2ccOPyWmA0JIFco zFY=5Bu8B9ol?D3{HmQ}QC|JL)J-~FakRpp{F;jGrhJR$3Bt?9KxveT%h^Uk;-&17O z!7k$qFVhXZ@|4F4Sf?tVKkvWRUl0wPHs&3L2c|%@QwQQ3;5L$5P_&4G9W4%`Lg=3} zSekkn*i%|OT=n-stBcY_TQMg`*}qbH8Nm`c8(%^|J%ot4Py&`m-yRC;SE$9!V-o2%pmqn!yn|IDa?@G6WRNRDUPOCE~e0$f&k zfdkvXhWCO96qxxPNd1l07YA}>0wWf320J6)P#7=?59Sv!_^rEb+4G$9yk!sma~4Wb zG{jN@jrtB@v1{~gg;v5ZXIkle8l3a3i=aPaI~^Biuy!CX30A%j2A)K9c)u5CBzHTV zmU2EBeuCk8myWldNY%sG#R4^%L!Gi|EHChB8^M74p(pm#A}Hp>08bAeRuLOT7PifP zO`0ke|I4d;#>t@?gn`GkKNJ&Q!q{IW8y*v-jCk5y?b6D#F$islQp*j-L$nI{>g3~M zvVnqq=JA%^D3(e3NAUcu6$W7cdZ5Ai6{)~vABbbrW8G#7J9-L`Q_1}-sl;7%rroyp zZ$r|TKzH#6K0fut;x)P5-wtNWE6=dRVCAKLEL+nDapmb7uyAkQSn1YTl00a??sG0G z2>sVXW4JNkrqQI;l^PU+P$o}@OZ{0@k~m7#lCsxt*`*hAexTn@VBhQ@7!}tR4(cA& zN$Gu7vq^e%g(7ymyq%!RtM=(tQYFOo94($*^T7`u>p7hM1U& zh$vza1jCo=_Jcuw@1txP2(o_Agrk-tat0c~MFB9IF!2@su|_alnX}pP2;V{p^{~qVf>|iE!uN+Axg3MfS1TX*SeHEC zjw)JwuC}8eg@v12h52kwkKEUX=DqA6p|B7$<@G*PmG~w%VdStGdOV%Hr1xQD2j^mn z#tWNh>doKk`|sYNe`-h<9}SKk;k#kB!NilYf&hIn5(T;WD+X_J2ffMA2YrcmyoF^2 zh6-Trbt!3)Pv7Q}6oq_sZ*V1zhHS!M?s@qJupcu!RJ-VI=L~V?UOsS8{U;Yd4!OAm zzuqOd1xy+(p{y$8`)#5xEGZ5OZIMe-C1>zDfqO24oK03P#SI^(-p4d3k?Pw8%o0ma zNdH;(k6%{^M<8KnEGpTJ*!;1Mu>6iI1TB(&u8<1bHyIx*Kk(!BS22Y4lGRxnsN+sg zPvcAchi2_^gq*%_-Kw!MCBywi0xUOf-r^0Q>Gh+;;NXC^Rgp!B4Z*71M$uZDko34; zaruY7BH-_Ty^KEx`IshA#2^d}578DEk9*BR6p7{tPvX?3Q$xd7+F&T zSl8S=(cc%IGfe;MZ6j!#R_SUwj&mlaq_nHBs6)PQaGtg53|d(Bjo1^7RES-;D9oAy zA&ve$Mj*)=I5bhw2Ltxaqh8_<47U^VaEPLVCt?5@7uBFB=441^eYM|9 z!-Y6Q<00Ggjl6fLTZ{}he^ZY-$nGgF!I)+X$>U)s1=tM~jdI*@ zvrqQ6O`a;Y(@`73mz0z^>R{n@^BcVD8X)H?u(W!g?$)tR;?KRGG;0JrIecLSW2l!8 z9yCZSc&`VKrZJVpj6;|y%5uR!J?uygKNdJ;3 zWA87KYGug!=I*72MuVlubGm{HI`Tuwz}U1={B+;#WH#a@9Yq)wm#E_9^m{iF0|T~u zSS$$GF{tAuvG9K|V(*O-Z79@bd~Ew*Da*K5B~{nbl4h*yic_2yEuJyQeQP`sj>1KS zjPPXh2hZ0J?dP~r_2XrJRE(T4u>Z|#yeJchkFY&<>rshRDv+iOqga95O!y!ILoob7 z`aVaX$F2M*7OJBkl5;@yyw(7yI)lxei7)Y=xMtWnh*by1%H1KDOT8MDF9R z_j5ihu*ub{(@RwF2g?^tX|m{_&CLW5-7G9D@KQ`(ptB96_DuzEq4JhT%s{-;!WXWf z8KOl5G&k(9PhB1Ttd>PX^aX`Q6u8cRjz`ZH-|A)waU^nYIuJjrVi6>BZR-1AU_e8% zc5rkey#r~15d9xdgM_{TFUJS{E6?so4cYK47W1mBV@XeGaiyo?RZrVb+5s8ZajoX8 zcuCIR{q3+vG7g+;y4s%6<_Cu$5EWX5XjW7d+`oy9M0xF4tIYZpc~B+rs@-a%eo+VS z{=nqBv*;mGC!0(#G8+!wOsQu7*C$NB-RIX5C<361Q1aa>4x`uIMAI0qG@%O zp5K4f3^Wm^2|OWVYgZ78Meux&CMT$5J#F=&@n!M#CHU>KA-Pr2`1JC^I^Nm@$8Llv z1`GWT1j{-}hU`HGtCo|_dBR|&?uay*FF*SMkU;~s(#|M2(VKv8*Er?pwBXM+<(YNq$j* z@FE?)17x8xWHNo>M8i*L{2j{jLb>R#Vc33wSD%v@W9W1 za<~p{foHWYg(n?Q>icgkT9v<-ytV9C)iF~{3-C?|KLx3I0@Wut>fPn|;N(=pc z$}@YK5wLDle#SpGpZLs-7-BPO?p%JK3~2IQ@Wkj>fUAMym9hUG|J1VhzkK5V-?#mj za?Ag}^zDECZCecn#Ym!Cep8E!#d5E!%6VW_RaF6(MwZYwfLj3xmkY?)zXx#g@9{rV z{o|F>f#d(+mj8oW|2G;4HMb9iareuCAK3o6e7(#P?#~%%Jn>))jCAAIL1eQjzJGgj zo}XKD^uVCr23Bsb3Fj+Mk~YGvfNU8G}bvA}%4;zn6rJSG47YZ6#SK$xKE0I`f_ z5Ay1A%|W4z_rN~)8>+E9FZW;%_npsW+GpI$`?LVnUuW*!*5tBRCC=0l{mme?Y>xf)iE8JOA4oLPZRi%bkm^&qpD3m`Rejg;-Pj_3~^)hOC z4gU2a>v~<`ayhi*7I_k=J5FQnFt-Hryww@wEY9#&zCHB13Q zSwd^vsp=hZeQHsa|D8P90OYrNa3i)!Md8Tbipy?q2Wv=c41eb4@*Z&ARqBL!r@>i&S=Vy0h*< z8#IlDa58O{)%`(=NvkdTFxt}f zyq}-#0ybTzJF8`PG$iQ)6NR$PYp2ULv$Ju>0bl zQ@h8>_SHvmR6m&HhG<2-Ula<=jYM`SjAVK20&P);%M$spxx{Us zDed)N#B>f}Z066iIW5xdcvc?|U4|goHXGOOhm&fzA4(RzzG;rFT_A};nwk}TbvRk* z)S+T`Tu{(XD)d;tuKk<(3#>?uM&ov{eEJ>1FvpU_`NEQ<4xtn2eSZTtf8TGWq>EMT zls=Yho=|ZZ7N&mP+sh!3Yr#^nVZW+fPyAkA;nmnr;sA26yOt+D(tqejO%@y74nxYT z&Mr){B5i)Ew@ByDm`4#${VWuTQAw`T59^nI0Co7YaL}Odu+Z<_siSIal6HpiaM_qM zW4_UjhY^pw{Si+6>002jLo@!n*XB_}uMG9_^;Z;_GrnyDp+X)FCFH}q_cG7#1qx9kvLy`rW)0`-^tAbo3e@1u3W9%k$9x>2)R78Ns8AU zm2PvS4l~wIpRGmB*!7pL>TQprPRH)r_0Gnh?_y2h>%2CV0a{K^U7KCkvk&fg{VUV@ ziYBw3miE=D$p%gE;A=7Y~`h{pofIY3m!LuHzMZ)$4IV#Qdz^}^A&b<|=m zZ6mcj0^dWxjczyRB+u2EnKLCNsZtL+Jbi@0m}qoycXByyBQ+p9vXQd7?cURh=j6sk z>{B-~Q-d4qTZnmW0(IvD=eyZrZL{OXm?YMS3IJrly@d)BS;UeNa@{&b_j&+%4r+T{<66Kv_gD08lMSWKpA5y9d+wYDKuUz=GW^ zZ?lp$5?XmfviBTY8bP_xjcj#5tWo;+hpq8O?*-~a{5KNp*HgS)*7H*1^Y%PfiTFQ^ zRw%L9N8!==xb74u@UW?(wWO+3-HB%#_jyC)*Zo@_p>%9 zJku>z&wTuz=Wp+ZSP;L9Kgi_c{ZsA+k7{t6Rg8gnWfnRz<>C`@%Ek2&HRB_n9B*Wk z2CzBg`E`5eUndEW+xsk&p91U#_j>%dlI{Q9ed51n-~V4Y@!xznVYSg`MnEfxduccL z5332vnIA_@#TOIjKSbHJy|(ssM#jms{i6@=Yc1|O+48J3M=8~WBo0`toeDQyk2S91 zd|x*ZczwZRg374g{8dJt?aT5~rT&^oi3{EEt121YRk~=4wBETMwqCpTTsd+5&(jA8 z`s59{PJ6=KA!v@=Qd8QR5_yoAH!OI_Ou$j2$(gE&hynHLduZW+5} zKp6PtG-fiQ1|^_D6^Z%l7|yEUv}#yV^(hsgoSSMcA`Ff-Tw!*ZfOP{2C2O79Z$40? z>DHz`Kx%xxND7!ev+3t!KpEw{SUab`9e#a4SFcvvxvhqsHS}QTdHZ;Garq`Qps()| zwE8_4;W%EG;=KYI65&b?Rv+z$z2oJ9&|dQ6?Y*iN zs~J6g`Lm$r(m#Pxzf8l@_cmVLn(`LwW#x>&>mdJkg9Cw{I-yX;VUUAZMX?eXr+8INM?SH?DG-R>NEkBzD9lqlg} zSLS0o7JCAZPL_|ACcBqb;KJgyh6?dW*}>zCNpC!(D#`{uH#-I|lHTJF$b9Yl_S10u zS4l3=o&l1eQYQ3hAw*|Amd%JVa(0#mmGkEVW_+#CyQVQglf+>)=0~1Uv_t)i*y8!e zEi<;ww`XZM2};%0XiN&)V6PiDy2)=V#f*{c+`eJ(W>{ncb=6T|MJ^na%Cn<3o{Q;9BlZ)-`UcnN#(FCm$c$2t=L!*ZA$kLSL zas*=6_sXBo^3Z;xriX`WoW`?nE?+51Va-Kv!%vk?K#q4SODCHB-PMm<^okF92Jr9Z z{SPz7Nd`4aES$U)y)3q#lGS6=TD_`foHuSZZ<^b5(^&!UCp=UKe46C~v*~YNCDU7Q zW51cRiEQerYEzb+`{&C)Uk-$iR+~Pd1Uc=^mpUJ;;^GdLq7w0~kdF?xotq=TMg)pG zk_gdB(lHFgIv9;CPgL1cI?}A6NpvgP{W?k5ng~BEvAuzVfM`(sgO9j&e{eE2)+e;D zGShu87a-1qlwcAqZMO0da!=vNsam|${PC(3a_`jo%F-GVRL zTs{oi*le7dvuUGq#^+ff0Xre*r1S-qY&_vJvASXVn00A5gy+E25@V$mB0-Q)_bZiQQ zf`C{`cI4Ig&v|s{*B|$+f8O|C zFV&Y!y4TET=Gs|`F$F2&MGmzO;Mw#J9xup`I} z3EBJj^c6n_B|Rn+KJi|byj(%7Q0i2Bek6!p8*;cB=@_r`yym0)^_*^#ro06ZL2 zSFY8jL6G9fFaNj8KDLd!=ABS`Q|sCLGYF1^@KQ)aDql-Zmh%^`A{K<4TURU>GDlYt zi}Xcmorv2Vg5|F>era>2Tv<6bLQ!j|JRG1h7Ab*-S<{w>PD|rBiZRs?!&n0EK)6bJun9W zs(j#Ar)#bkHG7e;3|gEzjKy8n%&S?ieT&v!e&egu`~Fy|DF^T7^S(~nRM`_Xli{H2 zO#z3j6dlu~c8r5}E^dzG5WP}nj&kQ(?j)G$cNR}fTzRNp?WK}HCh)~ylR-wv&%X%SwYKQS#6528^~ zN|8{I9mkj2GNl$v`R>Lf?SlkYR8!pzcNuA7rZNrsJrPa9FA|fc4PaGdo8^n+pA;>v ziKpSb&8C2VDjH`>ttt;WQj;!u|FE<{ihC5DmbJONd(f8a7s|;I*!PnLwR|;3IwJ|C z4^-Y|dcXLcZqBk}a=Lu^_ijPEikhi!Tvh%%a_@8exeU;H*^shKh_Y-lMi8p*@xw_p zr&#W&Zq_HJLe~@O{n)8#l~TJC#OZq2vO|@$=9(je_8cs;mBn|BL#nK5L+adv7lhBH zZAYaO3wcO%sW*-S&)HI72|#vk*a9;+1((qmqKtg}m%A?<@8zj)Oms2Z$z&jJJO2Lu zrL47r)wm3!_EXEuh`n^FnoZHh-O!zKEv}HW66Cl?Y57AXZUbdwKes%;z4y zlWyR4{5UQ3rk7|lfeZ8t9e^+?35@s=VvQ&eC4zi*GWFxMTVlXe@{l}ma6o|p{_@d8 z{%6Ae59ELq)Cf8Jn)L9=O>up_TBd?TYinyvjsguu`zo{KxeLD?Z_qQck&)5Ro&y~_ zyE1W9Pzu}-?_W9dKCIe!ya>2!d3hP1kTASCV$>*>Lc77H^YIvP>+bC4E5unhSl@f5n|>0XCj$DczJm*fo-+S$5MG}l70SpZb!t%&BOHkylER6 zO@&k{qnMX_wffRFEL>*|Jf zFU~%EP=|v65qkjQK$VUACFbG^g=N$@_YEaVO>w!A{qR8q{sEXVa(!ydEG_ z)#8y|^RiLyOaaZ}krPlXnkP+;hY%G76?Mp}et6rC7BhtA^&OyHc8zZ(KrI4*K;$W- zc`-4V#F#V(0L#DvY>AmZzDrPRp6qxj3S84;DX`&YXkKP&UM^Fh`8HM=pPE`SHKjsH zO+CElki5v3CO3cNR1NM~w@K`Ouc044K4UMtbq@g$i4@~E89Zs`)%A7c0wrVJc9+RC z3th#!%@?Kw>G}Ejp^*_4H@Bvk*w}5SX2beLoDk8e+1dPy-SLCN`RenBCnK17?m9=- zw)$s|n)-TrT3X4Zq$GY0g&9Z)gmK)~cmstu4YZz4TZvW~$}ZjI-2ju;)=rmHM1B_L zqHBtIrL2`La7!|eeV(12-N;b2Bc_!kK%`e{1$FNq9Ze6$Qac_m@c={$=w8RbG05H^ zDnCf&m}+9PUf~a1gae5Ft*>(gWq9Zq{u&}V-s^?V{6n;)j zD9NVrP7@-N*L`IeBoytos;8%>7B@6Bq@$z5x0T7pw0iVrZ(g?g9A7F;fvIS0OpS&b z=LqqkiyR(GLIPkSU|M0}qppJPSTI8dj+}~r{`>#~0|N{uFsccvHl@q^z-Xy=qQd&c z!*kU(z8yjD@cXv}n z_5qFnx>=2BakOKET=fpc(b2w9t7_f?c*)S{=yZvCwb;ly?xLer&0uR3Ay3yE=zk~K z;OU)*(LtX}m_uPN89lwUa=skrt`s|6`^;~&QjzU`zIa=H$)WM__i#NdVrt>x+<$!{OkQ5bG zY1`$%m{~su=9v~#0>C20YXQ9_5o=xjaYAaU$Gny~77te`#m|yA5STcx6De@@WEK!j)V1;)_~6B( zwvsMAW$?Q$!EXd_6)6&xJpPVENZ!6JY<5zms;3kH>Wga@YVc+9= zot6F~0B-gFy|~Di`5UWLngd@|NogXO$?8my06ag)G%>J#9kG5#?bfogveJH5c3b1F z4W4=a9*swP01QFL^xxZAxw*O5Z&FHdR^}SV!=)@Nqz6US;rdz1`gxP;fA6zJJ6CnB zv!LPrWy{P@=pVZNoSd|0Ip+a#QXDzMI^4mev`4O{S<8Yx%$7eph5#{OWK;fI@RR-S z*qNU2KLSp#WoYkP0w6LJXgJIg8tL(*R(UPFNYNEoKpat~$P=t975A~OYUciv4>h*a zSadgnnK-r0(-<`+2X~_7<$v)QU4okVqW(+4fzC^Vc#Q5*O7WHRaFNQf=STX3>px#U zOza709F)!+Pxg&j7TtUAMg499O!C*WYfl?(1p^h*K?Vht4qP2r-xi zlg6Y5tOa%D^0I(hw|dhs9XFPOr?hy->G6Y(7W~%f8WHPK`iZf9H_jWaw&SO!2Zyyg zF;4H>K(EJLwx^2}zNv|+0+M~s=@X(|qLP{4*F2eN2j(^2Jv?SL7Y&u0;${~mvk$VG zH9O{3h#ZDb_wiRqai*_2uz?l^>boL+&x}+VSP|oBZ%>g0)Kaqjwd#!#2&}PbPlG*i zcdUE38^uKbxhdzeTy-xfH@j4`=meK=YV1iM zp;WmOv-Ng|y=cI}RBBwfwZ2|k8!`&FY&GjG$q>N<{!;d#{p%AH%lZ>^dHbDDgbKAE zdrt%Y{_%qPBqJ}^nSKh1GFf8e2IX_V)zu8;5ftq zAw#Gw0!g8=&7D4XY*%^0jn}>7eb)Py*Y!NdxAP|;_0)xlErkx8XQDw7h*ki|kxS#H z%%7wU>dx%!6uxN4j`#&~FGUS9uzR1mCMfXPO(DhW2C$&7YOQ7u;KChmOiZ7Jk36dF zO_uwcFWrnoa2W^MEYjQrRi4hl_si{8D%(xVB5w>DsyVYsb(O2m;{+LX1IkcaZ#oqW z>)lV7j_?I#buVZhu$QuP?-&>A9B&DqG76sE<^LFu3SjF16lOkHd<57s5gD*3DdGkr z1x;#GEd8xRm+$4v+*Dm&h^>|=t(v{69)fsg^OUZJ zcHB0Vl#RGI=i0}xRxr(#zPxRnP9B%UO>@cJ34nfADQYd zHoIy?jKaNrzn?#>JGm>Ikhis3YHSv75}4DkmT+K zA^Wr7j6F1fgjY4QbydDe=hHUFT@h%`kYvQ>Grf*TBBR^YOVyi(!T%tKi&iGYq5Y0x2I~aU^NxzC#V5+Ch?%lCi`#T^h7b+=NoHveO+MK9M9s zOaQJ@uQnRowo6*X7mARoWYx%;+*4rzO%*EVkHq6?`dEuWAdh1XvKG`pN4TiJUol ze}hg~0lW_6v`7O8Ia$!NY02uQAl6cozNv4G02q1Tf>pid-Q67kAUHK;06-L%oJ^N6 zocn>)W_}GXTlGh5ObnG!4B6j?zs}N9BOu$sb~UXERn59I9RH1uOcL8C;20VhV9{g? z$Bmmiwy5k6y^#adPJ z4xd~BImgh^l_xs1jaWxBTKmUku#KXh8-&bod zGM&sbwXyj{NG1gQh>@X>Vx>>8kB6O&&E@A`{f+smzlY*zmA?VN6g6lxv`BH#v3VIE z1o|<#7dL7&3FviB&wGbgl2TF{iXKR`gf#R6OVL+Mo>;~-6%mV4(RHR!ejm^7NK&`of6!XU(D1Obo!z-1 z*udOydflcVCr1MF`4(yE*vpYQofD=0H{bcvT9JZLQ`1~#6*c=#a!0a=6%bA^Jrt-P;gthmxk3n;{`nZ9I zvC7ZV?vtR8@9Z1A@+VE@8Z3!q$O%!4D=L1#!T)Pq#RBG=G3p0>6@`ew3U+pOPGGQL z{Di9wyVh;OO;9SGlB%Bt7ufL4hr?_imzJ~uSbbF$TF1e$274m?{a;HCCz;(ilIKfc zErF(@;_u*N)3cQzwi)%+B(|OdviA$_8UQ5$Q#EG9%EhU9IW>IoRwo7!wN~vNTr*l`SlVCdmnyOB2<-C#twVz!QIwPO zZNzo`uRRpa5(NOcgoK2A00sjl^U&s5ctH=o_H=le#mrFgyA@|FjONy}W?Q3)0I@NSN~{OGq_?n%ZYdaP0nL zH9H!q1eRvg=#++9w@973VA;L-&kb1F69Afh-WjrO8CMDY7XXq9t82>kVFC8hQE5gg zSf8%vU$J5n8~h&f=F8$2xYeH@k^kie-L9c?6R^SSyBbvI8eg3f|6lLn>&gF)WAT6G z(*Gp~sLwwh)&H>r^nazD|J7{@-1R?j)Hcj>0|YC}1xgE80p8pWif_ioSkRECLCC;; zYHtD90et`4y8ppr{`W5ZU+S(81$8|{N3Da2GT7^JY;JL>dEc=0R$*n;g(?G;<5;2o z##DeM;sNz9m)LYK-A$p^@;lO&9O>NDnC(s#Y!_A)yJyX4-#^sLnfN8R13zRT{_7F{ z%R*)CWjhJAPIrLa@!9n3#;a^r=oqik>vVwFQmghOc!dtkteFYswEoj^O111=@cAxK zNNQiG~mlVLTjHMG6X!? zbvn)R(lNUI&!<_RHOlha?)t+gms{B&S$_9tAxJqW)Jw$|(dIHyq_YO3VEiO;x-KV{%po-}J;qW@&rX-($D zIdzce{HJNPv{+@fnBejdhY)ZuYbAvdS?|5vdM3!hJzPb#yRF{`2){@ky$nlg6P_7n zL_Tl7O!D}W`e^E`iL^eO1s!{uwGyXoMpNXDyQ;Li6p5ZZxg{)6?zyC_AnhEi@I^ox zo?m_##jWLkam$ zYw=FEKUYw33pd6NF?93C)5g5s?%MhmA1kSg(kE?6k~$8vdb1Q;z8)# z;^uaXHT&KH6qeKVwOZ}gE-IfIXV=?%KE24EJl4pgJ?}H$f0$@Kv6yZ)($)*~dRUUL z;Rt)Bo?6x}mryxeRsS4C=@+z;rJr!0rTp?2FRzQ4i`N_R8{c#O`R&UMIReIi;qFB6 ze@`@BIXRIwDRvA}C_}8KPO}r|b^h4zvIevxAKj!SGoj?pd89?~oz0gSZ}m)PuO@Y1 zsan``_am2DyVo)Gy_ez;O1m=P>9#SzNidC$+>h;k#?VDO_ErQB>x4hZ=}gqH2{h^F z63<8y9fWw#b-7+_TUjdyIliQGaXLqO3*hWG9J<}3dKSoQQPS0nxW5SR;vIBOJ3gLw zbCQ7Wxb$Y%{dmF7&N2f)fNhoy`g!XlW9CIW-8rp1U;gE;J?hKHpA>1o%XY>MV1K*= zjC!2h<@^(d;fci*otb0u?)8I9K~4)Z#liFbnhwU-K=T14Sa_97QrM=ydg_2Gc;xLE_RLU7!EAnb;7TEF-F{#7?BYT5>6&Q1 zFvD{HSF4bl-5nN$-{HoyqRm>UzZ&yW4ZG{QMQuse@919a}Cd!R%ZijgpV) z{D8_3fG)dRmGh)b1iiCq-%k6`NU`|>iPp;QavK;V49sJKGKWuTT;->KT2bg#k4u%d zlaD6OBi!~h??ebjp1$LI>twxgG!NX_yztT$TFxlf2a zd^G|~+lr4gfCt=FX>hLId6qJP^IWtHbjOF>EVYj(uCG?rfOy@o8LFAEABv0C^mD`0}4NekYLnXA_ z{|S}cvDK;99$ak`Ox50_2vq;_j2NHJbF+?!EOm1g^?4odo%ZH-j}7Ph|EIPy0f(|* z|F}|$Qg4eAp||wfvSo(srLv4HS!-;SFlMaTickp^no<%%vW;yd#*!@+vhSlTLov3o zW-IIee$sp1^PcydbDit|KmWP7x*F!0-~686@4mnH{rxoT@>6_jNf!rozF-dUi_Ec1o zG0`J-tj?mBD`sz)V|xc!)-g?5&3uxNoO;i0$BIhCt<1-?rA&k@Ni#~*8&}$Ig?75V zbL4=6Djcd_uHr9g?U{sFqjv?*`!R-}ipm!T0|eXzWPf40As%s5v5;=;IcJ^Ux?%Ip zouCWePtnJ?*dT{SYhQvk4m|SdYpUb_UoMaXkX&itVD==VvHG>59NbNoYy}Ci=fN`Q z707oEhr#7Gf*ge!)2&G(7*IP%bcV;sxmy-)p39}XmSDsn2v>glQ)_BDf6^IlDkZnIb5eM~3|akQzz zP>0lIl33oeSddFGXS-uoCyu9ojdKgDF(z1MPc%DwDm5_}Mx^G^E3b9T-u+6ZnIwK~ z6HoK$2L0e*;Nol7Ua#3%WS)l&DmF|tHBXCHTMC6dPgdnnZH9n!W?Xob8s!#;3X!FNs8Y`Nmq|0Lm%Kx=ol4u!!@ z@g4Up?#wwERmx%TE&xBKAeLf7lrCv}QL>59)|_)vyxUj1(454B-HU5Ug4U#r=NXeI zcT%E5*36)1hP_>>YfSwv;q#@ZMYS482kjpdhV8K>c6ehcqeQ&dG)d{3emwHsd@pa!69EhDuVpUSEqGpPNS1Z<}_953?+tP&7TQG^A zjR$zsy5rE!Z;glV$&HeSc_{Dzz!YIU{w-W?H-6al;-kf)b?x68Wv*?mx z_Uf^Sl*F{y_Q-1v8p@49ZF>z$ZHucd*fHcI-K*-9m}`PMdNC!Px}t4VLcu(GqiKq; zNxoi`ad$jFMQf?!o`|74Ni`@|KJ_@QyF+&|VcbY^I^Hm z$`h|2VqG+OFUIU_Vm6oDD%J4asu-N64OT(m%{nkMr8V5&-!kUn;4j#Mg!bHUa;r%# zJMb|MadJFsa>O@xLlR|3cCrNBYNF zxa*bnL=zUO5};@@KP4-<`0Et5v-?gU7Kt(A_79H)sX1Kce@57s%zUY$5=#Z8C;4(* zG~tLa#F8R~lt?l$XLYhM(=Sk|)vFq=cg|z^v8;P*hG|{~anB>?x1*We_kUCW+Ldv7 zih~(74lJ}`M?pS(#oNn|@~tYU<~9FaR4r|7TY6z6#@A7D#0X`o`-f_8^b6NEetjxv zKwYmyxa91?jV7(v&y5gxRu!rXO+2)@R}_}C`v$epSd&sUES2;miA53nW*4+dWW`hG0MY5V4Dn|?@(W>aO7$9t)H{LHVP{*@y)!2DFEIRLJ5EFVX zS#N1=MBJx~9#hJ|&Z$v~uNm+yrJO~+ZOb)h^~N3vYxPC^i0n+k_T!4FPQ@8xV?3dQ z7sJj-ad({|wv9JNUX!%Q9gkWnxOHOsV|OJH6Uw&bYeJvpGc|kXZT)BeRopvYErpE! zFx2Jw_wVIgICi~YqEwB_87A#}cTYjWKyfTdN6%QFQe7-!(8HDgO5bg~IhVK`s!h7) z7}zfi_a_&!`OM$o@-1&6HxEOI5+ga$+GoEJg$)lv>9+`7wK@1Ea6FMEi%O?3KXR%uCvovsRmJsd0EQ6L6Je7HtbTD#zuu{O38R7w)aUt zZ=b>x$XfIClIIIr48s+??iH)y6}8ma+LN_juqGYMb8#pxx-`?vh0za^-Ac(1xZ&B! z!x=42=-tRZb&y=^lKh(vOFk>o5EK_J?a8Fk%)wCsR00#K688X_`TSLVQy+%+FGxL& zh!9F{NvW-c#y+nDEIv5ko4PO190zaB(8D7S4B9>i%j{458hgb|B}(OeSBk#Nf*B?& zD+`E4EA+7QQ`VO3-4BD)!(he(uM)no4M zgTp>F)HoRHOO|yRXjFkv36aAo{@b#To4b4D#EQo2kzX6}PENY9J_{*FrKAi^O>4bw zuf6pjR1Nn~nESfozfv_YW0*61>w1&EOn+~sQj^sAU*J%9_f2RYnI|86DzU$1+k^8h zxLc1vNV~4Yw;clTpOBgZHaTpn2Y0Ob9a1^x^TI9%R06T$AfoZv>7ywhH1Ws!mV;H} z@s5thUS9IsA-Z*i%nn1%y}bcp0bCJkHHr16Qmo@C8QnvT2rXgQ07eW~i106)KL9~9 z<{q`KezQNcm!TgFzCHBq!Er>W8z68_9c1%Y8IZ2{9!yme0fR@%rv8I(}Iy|L5RI!(i0{`Xh=a-}Ih42OETsHQ?l`F6G^!3A@Jv##ZbB766IiO3LJo!lXe8dfG}t~-3OgD6a?iFv`jvvmV3@#CNlZ>g zoE~szV6YF>Pl10T2kW9fMp2}+$1&hU-$!csH`+o=BaKD|a}=7{UmMqQ3;8iX3(2uQ zawDYYszXvLW`HiV45JEf0Of@rsEw$us*=o;!OjQA0$BsLO=IvrDddtt@XtoT?*$L= z5N%)1N^rZDK`25|)kVn=Qx1c)eH#z5*1IH(g4U|x@REWOdaNX-*0|p&-sM_Buz|AJfsrq7J2t$PlhJY zUN9{+%y5!m;r|Vu$#7nCWx)AD*p_7COE}HopY&$C52IKfKMY{GSY*S_F>1O)300Q!oi9Lhc9w^J=ci_x#nay?47HXNAC-0ASOevc!m0l zsiX&Y%+}vziodfKd(TunJfv1Jwi9m_JU?yS5My6|2zPz2{Or4{LFla=WA1g&Wc9U^ zTNJ3Lh$qv2zT*Zr509vLA8k}V>;+^Yh>F%Nb<2f6pDiw_IuiOgCWagwywzorlW+h< zlM2qet@10pV*2?Fvx^9!z|T4J=#WF-Dthy;mI% zRz4QfItM!)*%TqK`QgS%Ywu08b}tXHwimZ{brh7SE)KR0WhC)hoKJ) z+{a}}`DnYodHRuB0)}BqUS7Kbv1ubZx2@@;OfHAhE59Dx`1aOw%K=>G8Esz!wp|F{=Os;F!EsD%edgirys`B8Ql&_~*^*i&TXj-+Qor!5K>^BKc zP{|-n6G0P$Y3}X4(B)*!Zb5aj+K}aREFHi(AP>QntlCd+*=&`ycy7~!yU~7)-@1TTL0UXvbf)bF6IJb}@A0!29&iy|)m$Pai676kmi4g0nzySU1gaa2|VH_9#u);j;-RGyR zibDh%soJeDkj2K?hM;<)Bq%wtH(6nw;E+kz`_k&A}ih(I_Ho!GW) zp*tPhy?F!hqH5hTxr`)8JrbOc8wT}lY?2EycdYMo+f*FcED}T2jOLzgp=SxzN^5qV^IsNA78aye7}@L#DEITW_=Qh>^@1>Fv?!jK#c zBnjGt#S)3ixFStK4Q6KMg)c$ku3z4>x^$}ZUvg(hv*g^3Ht<1ZTh}#%QWP?PWb`K{ z#k8D@iDz3N$PF|gEq#^58tZIZYWjIlG6T64I)|9J4*AJNLv?Z@TF$N7&JcxVG7}32 znF5l~>b$#uGACPxIfhxBp6kfvIWwa7!}iF``^n5xR9BbKOTfX~+r46akPS zBhzljv!9bw7mrT`u5+7jDvEE4~B}&;rDK+$ai#``e@5I4E zrr(4tb{hf0cUGS>6AIaJJmXjKUpC_^g7>{fAdg=-L3@&;s6zag1`h_aJ5eQY={2C7 zj7t{`wW6f8Ktm3={sJP^P7C#0y3CwpeZ<3uWz3L1SnuvR!Nh6>914g`K$fiqrPfCS z83WrGfCgB+V&+98m!sXb0 zVIiTj7|gv-M{PDJftJl3DEG?LZMN!G$ryYUpg`E$^7!#?4h{|>MMdxTGrO|Zp?)V& ze%C<2CfDv!AmF54;|B%@Yx<_1c1wonT5phI$a`g76whTO&ni@SsGiHwM(+SDHAM&} zN4B8It@kN}!?$l${-(v~bb5qe2Is}vr{5HT`&-=Gk-g%--nAc@Y#EZsf zyF>Z2Yf`!$WCUVlO2YY}dh2v7jot*VDN-;cD)17=^ZT|*y+x-+4l&?xDx z725pD5#=8%xmeg$Wg7eYFC*0Ugg}R#bX~N3IBWmf)UFtT>V`952&E8AsVsi*oXctm z+OhXk_(!LG^52UjbuPg3K)7Nsn2Kl2J7C}cGwNisooe2h_|7CWY2O!bnH^c3+@;(n zP-wIv6pgUXf_Fln3mGfLn=WGUuxEl#vcqPy&^Jr}l&J_+=tz=IthNX2BSbzC$~T*; z5}yAgPEP1@;W6(+)F<% z&nK5f`7IxOP^gQw4$>}9TiRRl-~uMsrhD@6)`0kpCm@=b_(z+-*8d^OkbnNMk@kQ6 z(Z4D5{|i5YsYV9)Cp_f8@ex2W!{fi?{Hz$2UAPkk$8&nbwSVgeYq-4O>Fw6Xm!rRb zNoU!_+y!mo8&PK@IQ8Z~xqMAXy8k-l$0bUJOzDtTv=>Vn-;@V^5r+@@rcR*lQEFF; rKG5>8j*!P690&a8-^|8?t1G_5&+?(SD}1$(*J+&AK1Ei!aO-~n(ePgN diff --git a/docs/sources/docker-hub/hub-images/hub.png b/docs/sources/docker-hub/hub-images/hub.png index 16840e0547b74b92bda72b678dd85ed51601c56a..489f730f96ec0403f4eb12c834e6b13b42908a06 100644 GIT binary patch literal 68579 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 literal 27837 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%0A%-BiCnMnNl*PI*^;Mv2h0EsMsHS?^GWw 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 ayq{0>=Wpnec<2UhfSDUx8&M5CNdEvT$72Kl literal 41551 zcmb@tWmFtZ*Dg##kOU9z7Tn!K5-eD9{%a$gjy= zL)N2#B67>mAQuUHTU%2*7vL`ff`pT)p^K>rmAj>j1(l4PqKXN-DGCAt6@r}P`_CQ= zhs&NmpQqd5XQPMn^so=l-%!7sLwg+@fK*dYtMR9-T;`{CCA;tUX@cVJ2{oqAI=jZx zAB&|9vFcudy70AMBoziwdCyf<(3{Z0N?OI~c@#dl5xn$+`fjP4AN!4DtoiMvdzZBz zTzTRAv&BvJ6w+Y-^HX)~_5X2?gT(glsm>vH;JLzCG-Sj>=2Ejw@mbY97h^t>-&o-W z)4_f|9DMwoG2i}fvYHndH5PZ$i2?}HK|jZ{e)fyf{Ik8%J~Bd;WJ>7`T0H39I$O2G zE1ycI`{&S?Ny`)A-E)uA`=G?xh<<}M6W-Nxk28-JB6Q?tJr1)R97F>?l1idxP?U5! zyJi9bTgbo7+Veu!E59K<$k9A8Ws+7DVX5yD< z57~>`t+-WHcMLv%MAtO$x+wo5GpDL1RX-g$xUu!?m`gX7NY2ny46_cUdR*4DC81=~ z`DCR;40i6C-$y*nK&u!+1+@29wkvncY{a<1>fLZiiKsSKwD32j)#NJevXO5)X;DZe zzq#M53xP@$DA#kt#De=zY(rvI9E7Qb1N|J=-h?fl^1JZZM9-qxm2;%;eE>BIBYFO9 zCsHYar&z$tjv=$52Fz#61cU>|@4G8+Z2U1Xp%zUku4HS=#>dA;L|T?+%TN0A=TAv#>DPpW zG9DiFuMU0Cw4Zc0&pjMG5wPl)^p+^>SA7c>Bl;2T5IvfoR8d+W-hWQlUA!0h3SsxO z_|45%*ca?aVe`3d|Mb{S*7xtPUZJ5ead7l*oEdNuags!(4sW+1IAew(hIPKTv}A%^ zAJNj&hhAQ~{QC9lbHI-L7&o!691GXbN2hOboX$j2uG4RV@0f+Srang2#anozV#cIc zfRU=l-)WmLsw)ZbC&cB{)C8ycw*PL>3wjD8f}2cZIt`?gl@uBGUlA4(NJ&acZZ^k* z!SardTof@VIpg2DjoLTPR?j@1Ul2t}i&9|el@kJ`(^h%;`Jtscb@8#W@662dU!9Xq z?E#Od$AMW_zW#XgNh=b0U)N5T0%~2i7&L7|(`A!8vemwKW?&-=)kwgJ7?+J2BnPb= zonaf+te$yUSfE~DAlY*py+B7sB#r*;a0fnMawcv&*dE8ReDKqySE?hduB86y3C6O@ zs4>@v9FNEINbjTo{d3!}Y*%YD|H0MObya%~>a~US89C@#%3w%4;Pc#TZcX5fUb{LT zi!mw%`h-{4q+@#orH}YEON~ZomHfEuSRh4Y0zJOqR^Kmu17Fo_=n<#cIdU4MxDT3q z(=P7bY7itF3XnQ@t&Y6!*Ug|96{EDv>DlVB;#E^SFPP8H=*nStUCgDuRsVxyz ztme&ZnK?V+7st%pN|U2UyigWWstd!AiN4Tk=oQ${p8x3P^d{vxa-*~P^?tL#xtHUj z@o$Ivd6z%m`DCm)8~4xVjPI1>)D4=h>^Pp_#7pd$noS;t&d&tQ+;R%@Gv~0;!VUQ+ zaz2bky)I)iISMQ8XDjN)F!=tfcbBEX%-}K&dR087KHnz&%{<+~3bo}Zna z&DqqxE3ay76pkhrk(QHtX;(*DJs&xHfZ-y<%E7@PAfUO7+N3tdO+wYR+HJgBCPHX9$clHvgtwy)>a56B7@wV8S&k(#8mv?6kG-=((hyFMIytS^-2hE zF@p$o#sUcC3^nwFlG?wuZ?t0!J<-I>gCp8Zp2vhKfW!KOK6$Xhzz|l(or|rL%31z+ zYX&UZe&j0|fwL8nL62E4(%H*#<*qM6CTu#DuQ*PZ4{>i^`o&kr1%<^p4VxN|cIMRb z><>7e?jP6;6bI{Fv-^{=)FnFTd`wr`C5W&Rj2VSwCDp+L&7ZvQJe}fNk_s4BMhA1u z9VEhb$cHvaoje{MTDpwaIP}e-H5VSR0T&yLtdwWssiQNLE?36Z+V12+Sdt^rzySC? zB7#(GrO$$Q9q>~S>l-?{@f;bf5)GDj+1)HOG+zq~89O>U1U*g-vPpl6efTTIBvM~Ynok0|C^%4< z4+NP@-qm%wu-yhn2pVJP#}0cWe~~V$>YxulmMEk>jQALwgUnx)3wZXiQO0x`8Q9%7ONGy_pJW0>40xpW% z=HB5!2fzJt2q6(zES-bj4vC7k6s1m~@rN;;>ETifTvT-b<6F1^28hGB162fa%rI}F zl+l}j6-~=yO+cysJW0p%D#e}0%!NNqE5&j05dX07k_|r2QQhC{h9iQ&XxJSzZ(^pn z9`XgkhR2a5Z8k&R+(?P};La}ag)4ff#0FX4Q9$|O<}^IJ$sb8s0TKz6RoOitq1AnM z<75)>oL|3JBHKW#KA~q}0k>;DeAm1|Db4QQxUxJFYz)4HL?W#$$3n|Cw&tYuJWffF z%}o=8?n&%^JaVCY9XxNx#}k->$D>sD5B=z+4`93K8yeX5C-Cp;2;HitA2sbafA7k3 z#pgCvS2vPr+Ap5Z1w2Vmd1_C`mn)1#n_Y?qI|4#eMdN-hM!nW=O{Dvb&X;^kErKcc zW;?cC=(b5R)-Wf`g``gFFvG)d3fhmv6*b1kvSMz9Ba4Fm3#CZq|7W|~X87uYKh#Y7LRDnrAK{4{9%N$}ba9eu;mYB-Huko`%Bw!Qr=c*V&M z!Eg@3JnoaP9#8fr-2Nok9pCKYMBg?F_>+)yXK^U3DBa zyU~LHiiEruM1PktD3LckB6Y_9dQqO7qe0Pm9VVq%0(-#dkv#bztQyh^B zp8a@7hg(|yBb0sTJ0J2kLk*jLMCSQREld+7qufVYzsEi`=7NWeB@CelXCtO^v2zwZ zu@|5HhAtP)E8Y4iL9Ntbb@tAj-UGbQKW@l9e8s+jXER)mM>^2ZQily^o4Ja_ULMW! zgT);I-Pjtp=C(?W=$zt;d0)b1&pW(2*XAo7&-)daXDEt?zXWDO#7S^n{zVR^Zi(Mn zyP@>s*%y9Mp78rpc~2KNcHF0`_@xkc>;}WPHyk6j<{3x%PXl*8*K4};iJP3TG(J4s zUJU;`k%abHDW^^!y9(c~@)KW7zjNURV9+~Za4dP;a3MIhGFwS#G^kUhC_Nr**tz|w zi}{>*ue9d8_9S&tJtiZyPVtEOAm0^6=yw(*w+d<>2vsDXtN5Dn#`z$1i2eLAeOQXe z`72~w_)Khm{U$vfV|se}^XJbmIzF8u5CLJXq>K!TG|^?9#+^hW+&7~8gNcdBs*tuK z%6MU6Au4(W^^U`sm-ndZ%BVCk;xg)ax}fA}5|t1xlvp z_=*sTTqnyhYUPD(6<&;sYRM)ef`aWxp*XjqY8+rTmUB^b?RF_~~QP z%^*D&(rZnsq5N>ef;>qY#xeGmC#oq+@0kEZ`WPAd__=r1zpDwe{%~MT^0^p$LwcnR zP|^8<=X>rznTsAS`!jU*x;kfHEdkD-Ss1e0RpIlipn-v<+L#o;gXNz-tjwHk`wex7 z)*tpbULx$eve?`8tvyoK!i}bdjRX;G3T7hq+{*8L)ow7xu|{2m~N=5 zHI3TPAZxyF*T@d$(6A5Ml#4u=Rfl9b-@2GxhmS3#o%9m`L+c?U8=LLa4}8&EDFp>| z02Bq5eSJEeN}N5QWf8>=?698HQ*+4@M?}yB8cb@5V6pMzFf}(&;u#S+5yY1i*Z}Pw8`qo z)0ys~2u}60MwvOHA4Gd1^MW$SlnNx>SMHI&AkDEYI<*zFC%7vsmC+Q0&>_BS-E%A4 z{(C!Nw}8Y-borHb!iF#^x)Ax})K_IB%rW>*g%z&_Rxx=awf2Ov3(B8kZN+Pnb_c{g zUFmileF`+To{vttED40%cwbZfXc4?bn`VlLh{|7CL;YidB%+}fDz@(9!`u|Lf_t)A znuZRwh28!;0fKkGBQxuO#XsUe+pHrQ}J+*g;D2-PCXoWG%H za;L=_8Zw-eWRaKMW-#Waej6o2q1T=-Zf9~-Tn&QlvmRer_?&=HRTpZI1VCOMkq-dgb;2m#5T;i-q{J9j(=Mw9}?nBqtv&J&}4@3Sf80H;O8hSb{apX7j)lTFt z{+!@vU-+=yw|OQyZgk~$zNYI9BT@OwCk~sT1+%~r2-}}A*W(YFaO04WELdotis|R& z<>lu7IkSe2E8levnQOUW-$ys5qou!}9DuARG3;Cz*dPS*}&J+#Ma1w*n4fV3KDE|X zMR$KJh4v)PdpDXj@R2MS$JYJ{JZUmbIJcJ^HxtAmV3+6Zq?SWa>J^s20a+Nhb<7z# zd0rvI>y0I2qU+RI<0 z9tcWbU4%3~s}t`HU1XVWq6@1*gO3~nQ?>t@!-~Z%#M&Em!|d9=>v1S8zH=BYYvair z`QnMTvwdOVo<#eYjKS`J`R!(tCKIh#bl*U2OkLi_?Fm*>->$M}lNOq8!gPwP&OtBO z;2KNao5LqPY(q`il3!CS#f{IAJ&kK`viX^ zEKFI&Aa#dg4Tn^#g`*0~Ij5#&+r$3%$s_5bYlq^e?VEZPhR<#ukmDaGDZs-$hvF3v zd!d#w!wh#PF+A+~XS}dLe9_Cv);NB#9}F{?M(sM){kcEV3yuw+iVTNHR|eQ^?)(*O z?b3=fSRd~coJeeq>2ds@coJ9<^sCq&uu7iX2Rmt$buR7scPO`8d5&%Q-q{X_cjZ;< z8CYm@7n?uSPhKtjE(@YBC(Qv;atGZWJ#bN?7Crs8a}P&>KfgSe?T$$%f*c}V7A#JV zz1kL88%c|;@fAWND9g7@+0B&{sb8)?bSrto?REY+UkT$LJcl(Pa5}Gt(o&u|u>8qx z?Y;z0+h;0$IXa1CfaOZcwKkIMZR%}3k)HnjTM_q@ov}C?e)~bw@dG$L075Xs5&#Uc zv+PLdUlFWX=`@VriC7AXVpp%C=j0>T^Imi1=R=RIJPpZXDGH)M%;!mUdx&V!=J zP%v5lqLVk7OB0#-+N7QNBL+^wWInD0%J#qQq4)f(>swbzKs?Ak8n~&G8ap4oVY>wY zkO5si-|Z7Jh1M)@j+#2(W(oEkhP?_m&rSrQqyrBl08E(M=VqdEa|br-UQZ*!oIw-) zc5(%7m|a{{XpFx%1q~ujLxO_BK4$x|aBwWmtv6=~L7S2l@*kcExL49faVa(nlLiMM`h8V&Q5Mq)6;yfKDIwA zE-Nb=m;Ksa3Tl5p2tZc1rr*DRTmC6qy?c=8t=nxQWEByNloPn_z?`eLTR$F3&i^t> zF2-NUTX2|bd70YQT<6zb&xlrO3I;VWr*uX;RQW?vJvA9>&m`WX4U*k{HAU%l7_?=m zb?lv!U>!Vj$5vYXaaJnsZ8kqjpFlv_3Ah&OAG;d7s)==D&~)vphj6&L4Q)-djIam7=FZ#i;s0V-OTj9h4d&^-y88S;x>FO z^Iq3>Jz@{;+qJwyb+()_tPdisa(Ah)@;>^D@A?&0Q1lfYpPg`a!M{F6Oa*q7IDAK+ z!(RDZ!>&HJ+&n8_woqi zj$NhW4WbJt?)8gpXV)2WRyqFw^YWuX02Xt4tc&iKXLPBoUa)yG3^zIWGmPA@ z_wa90uYi0QYP)X|rX7?wmIrA)Hbe57JxrGbJcbI+9xS&-LG_ds7(1b)zKZXc?GGE5 zkwqSfE%&^YM!v5m7p%@OC-h57Ng)BbPX>%c9MP)mR-K=NTfd5mm>v8zPcK*RbUteg z*}eXT!K>06n?63l+xC3b#*gG(E9H4zZ>Zy@zOvIImfiYySfx;v;UYQf$8e{L{1=0(TM7HZIYi)BGLpQScVv1P&AK9 zJ8Zva_wG`sLO?RMGbUxqPwSf~I4I@4cpukSS?VF~b_sWGl|Jpg4ria~uSI$my5$*1 ze|)z`=&IF|_a7GsY3pKf3LG z1A!BIc$}#nKh8WX7MwIVw=dLKbAgBJbs^xhM=Wx;re(nlC2nqRS$X+R15O~Z$_XA0 z)L@5O=Zwqh=~2LLPI}EQ@=W~aUSZl3esNJ96mc?ON;rXzEJsk9>%Mr$XKkirxL0VN zCa^h4KlvSWHi-iAoPNHIp|xzH%|m9o`2=IihfJp+T{K!qEsKK?1k{ES~d$;zSvP8)TccaHJ~n2ZAv zQ5=LbfTRpAcsO)$>y@rGSb;uWi6IP&GCw<;iqk{d*H?@nB2V?^J$&qhZQl_`z?^-% zK}z4T{gJROS-``TRx*l!Wb6z187z;L*oSsF!W zJp^W?=YQpetau#Kvd^*&h2!OkdnH5N)_IyvA>cHi0~GEjY4Rfnjn;l(C{Tc17d~hd z1kK#Q-f%u9wA}WTavE^of2um!JoRX)U#38BJ3P7Gf}p82+)i_Vs&5$cRf_G&A~8c* zi~D6;`UmqZ%Lxge0iJB93dpaep9{~_+faCKCzmbTsx;9-=QeM-C(kP9@{vx|5S5 zaU>`jIdO&9XyY0@EjbeN4wr(;zO^a-HX@Q*>g01iK^9{lbp4g znw_xsRDb@deD#--jAN(e=TDXh!(4L^1QNH$MgLLt$v~bV zGaiD7kg+ipAVYa~cL!v9465f(y;=fYrKO~PSAZSq|9Tj~yBJi`QT>G!tphC1TFuw9 zmyy5rS~N9pzVB>wa6|e;Liv_LJGbmK`?&c;H|tjnkjqnGD0HVDZpchH)b(O|3etJ3 ze_JIa9vl{iOF#FLm2birT35qxy+tY3*VknMRz*}FNZZCH=P%+lDvWXSf8GxETlRR| zfA6CXlDuxMz|Ei!Gnrinn;H5$jXK6;DEHShUt9+fUqln4$WUop{inlC zu2hH6xjkImfvjrps)u{Dde?W{08<{L`JZ+v10D5-o_^g$I6N{kSEtS{Fnj89t+Tze z76ohhIBV8`6Pu7QY}pPVaAOk_B~46nU){0tWDRrxj3hcTyKJ;kzbkZY`HM_i>lW3h z&D(b;h1k+`QWFC7iJ8~f6hxWQF5g1vnj%iUnhiMbHNVbU&@8R473rNVaWRId_N%&} z-9*7pDEU_k!Bu|^wJADNXRpu9!V(@8)ooO{>ehtFrWKkk!+6Js0rI1W2?HF+$k^Di zY1zcgtjMADSq6z~#qYLNJPNF7b}W$y7EgOP*IO~Cbn-lPylKITw8;8qy&`funo@dz zkK3apZIAU3F>aLMdGq#p({$vpji_e}wBkSHeV!@`z!$&p867JsEu|9`6~)ff_AXI9 zT_tLNHe|r5^68TVFv`;>$RRDN2POa2F?>sZ+n%zU@lyNA@_d-981?TXNRF^6N~p>I zWb&Yjtlh@{k2d1vD?HND)^r#{?Gsa*9(QamnMP% z>#7UWC|>r+B0NPJ~P+B^F`-tpGyJC)KN+o}W!O5z=67 zrH=ad0_ao?$Ts8azrECFIX&=r_DcKj0UDZuwEq-;`@iiOE&l2xzJ2?<@4G8X9Qc^@ zb>QPahJ?^za(I5bn$)=mkFKZ%h#jUrVT)WBHIG1Q2cC3G!K;tLBTubE>+i-JAfr1U zhy~bKfWas$Rs97Gl%ii?i1sctl+YV$^61TU>3vFb$V?(c=S#%tuGi_`beb;E>y+<$ zRRzdw0JquWys34+^*ZYnENUCOF!EaQWefj_foqLb<-SKVz)u(`^x|R+EZgKM$Q4j7 zwTf1f@R3QUkq~*MJG);3)z&r5pk}l6mIz;~`fh|1iYyO@>0BQ*B?r1=F$}dvY3Y0e zbA=a5WL24;iA)j9J~KmqDe(>RNbgk;{Je&2&@-W9OMlIcFT<_sUSoE8io#97A7#>( zP^IpYoXpjL#7j(!;fth?1X=6&jm(LeJjiP#k2|&<9jTJ2{pCE{J&=bpdSe&Euoucq zA3lVWCx#lsDAF+&Sr#>3!rauxIm66PG(TgJeDFDvb`ys^>j_up^f?xVLPBkR+Vo2^ zKf^4g@fYfPD;$I4oJDgHuRWmWk!9v>RZ~-Dw&m>OiBlOP+WoDe^-VNZpH6e!4~+ps zc*F%0Rzqf>^A4BKHt!)4N2PT*97H#en=_OYOh{MkwTuxU>`#_?xuaHy`Fq2m*qUN!M2iNzM*u9nE# z;Ww|rVypZ!)2uu{1$33MygRuRn+dFsQ}F84F!RE~VtE}A3=-yNz@9WZ;*-FJK*7^7D4N*7l8iK%quV|5>+ zc7lfL?)zS=)6L4wH(aEtwLGcp%hqhIw5!tqY~@-A2v(s=z`#Q@41YD*Q75tW)2G?= z?hp+5>Km2cg{}7TiT>;A)?$;wJ0r>tP}kWnxh~D)T)Q!Y`+N!$|MyI}6a_ZUl!uxy z^S4TSD;bG)BLxr-6jvnESf8KvYA0u1c@LSiJgw^%qtue7F|>vj!m4a~26a_sX08lx zge*+2L9B{N zx89=3z7cbE{jHfGuVomX*vAXOKb{z-gIRe#mj16PTfkHKA`{zijb|yhg=M?<3jCG%X~?hmI*$3 zSQVCU8A0_hZDFqm$DTk4=Evrr7t8ZTgWTiO#L{yUK@Jq+mS2+RTqf9C5)50@q;q{) zwwjbO3tA4W4ymH6vQv`s1otFBA1|E4y6KH9v@xYTr*OUtjgdIxX{wK1PUK7pRF8}# zTFTTEV2<>OEBt1ZG^MQe=R<#3LZ6 zvR&k#Dc2wU!DoHDnV|g7_k56JzR{Hh2?^=5re<-i7Vg%VDhQ#_RkSdY5G>E*0nUpwGoxM!h!~S!q`V7%E4*RlJI@7hQrUJxp`Sfnuhp}UOrR&s5!DSj%im}^ z``umi_G`Q6tN2xdygv+YlTSS_n}{NN+Eft7sIPI(_JuirGzIj2J01qW*&CnEEX8npQ{+5xE7?gcj-JBZfL8u!k&jX zQ~XNBRv_MxYj7q*(G!+T!Zku!mb4nD!5s1YM$1n11_PFX6&p9FOJrmGh3(K>)`}cu z`ypL4h4^A@anD&IOGDesXwUVAS5%&`()a#3=VE1aV}#!<5=VlxiCd@eK^yO7Ey2uu zn%8c&+thpK-qh4(@R#zpJw3iyoa>=VbZqgVm}|1{UsZ;aGqnPq?}s9y=M`={Ng8UD zB^duzdE}7c*Oqg3>7%8#M&;q-AS<(sMWH)R+(qDDeJvPVVXptymwRHQPukhpnTegf z2T*MAl5hbaN|Zu06SsBm4tfC~B!ulNuQLx(0NBN*rTtz=sNPrbqZclAIZ#p`5^$GJ zzl?k%kR@2Z9>1h1f7($;MX_l9G)j)g*uZ z)gA>=kfh{M)N|5Zw zkH2JMC_tqe0q^8BfnXlERd7g%s&hHcw9~3j&;$ z2tBkdYxbZUNPIa6?#@>;?CtFV^(@&5*wu-Qm5%+N)iedJ{t@C|daCVxq$@|T{;iQP zNW;~Y4<8>t>Ui)xtn}MA)L%I{h|36%rRq=ETmDd^N^D|cIS&uPcR8*ATn3)vGQHG3 zT=6b!F44d6%f&W+N{zSP-@4)^_afcNS^v_}remW=Zn(I(5*8M_azRGCTUTB$F)-)= z^=xMtmN730X-#Bcx%V&eZ?2#_w`BkNq_50a!9C-xdmHjr*VSIr(zW;^&uFiYgVH>< z+Cp~*5R#feHC8Ml@?fQMdiZ3`gSn;|d3|Oov+M-h#bRpy z1g2RmhUQ)Qry45`8S8euI9Yh%hE1{T<~F!D_LNP~uz>Ae0DOUU0VTfSE=dyE64;_# zpU~pqbO(#BzTIJ&pyT2$LZk_e~%U{quOh$lUBRhxDL~Z2mm~;~vqx*HU zXXRK5OP;3UHe?3VBKnRMi;Tzh5W={tyAm&N^1&I&^Ug>ypZdY!Q?MP?AZE7^mpUhcx)CvET@3fkVgNNv< z^;UsL`a@LvK+VGTHWVl;QTC+up1INTlSph7;>)O+VIWs$(b*bm@&dTdV!vXsu?Dy&zkhA=%(<=O^ zQ0fbtKT+CjBFr=F3}*90-++OX6h@%~wM7=v(Uc!^i7WCP`{#}wI8&mM-ubaNS#Q*z zPUuOlgWo=F>)fr?Q>Ywt8FCwTz5vPfW%eN^w8}_(&n!|g8FR*gUSd_Nwc29A_C{_a zybO9IO%{lx3rJek8EjlU7>mu925+j79rsEImG()9$f`6(>r-}5ReT4)IB%~krAI7p zVaxE>>eu|9Cnw!Me_5_mHEoX=+(1T@2mzl(D&@%qXe3pSSD2u`8Byui=$M~@C~y*Z zlK`nX0}>bfps1)w=}7-<%?cHxo#Il@M^|>WHHJ`qBl_E$ZUSBjC$h8p-|n{sVYtwx z0T|}C2+o$z!#4QgD(Xh-YKtOzs%f!i1tDRt3z9hd-iB;hS>cWF)P1RlWlpDrmIrh= zu%gE1ksm?B9y$mRN~GfPfaPy9FIZYJA_f?y8RaM?yn-cP(219MlgAM)la%8+t{px%bjh- zlNGA+^%7=K3SJwX;`IQCtj!+9jh=7%ol~5GUd^hvgl5*7jc# zArOw(+Fwk=tQ}wLvXhHGC2!k~I_4kR>JvXY5Cp-req>?@3bgiwCLK5?yql|mOF$2g z$rt>Ut6g8f^nbkHDuiBSyeDw5+d3ddTZ>+uJ*a@+Q+VwO0GL(?EB%as4KSfa+&R1s z{s@pdfR=cBF)q~H7ztXvb%JTZOCsd7i3)T8u&86m%-JBZBfr#uJSmn8v&JR{++L1Y{dM)1~lAR=!hPgA$VGX|6?YMEQtsxJ*VS(OdnA zT+2mAGk=9zab?N|aDwy2$rIw8=@L!I|4vn_J)<;lqO3+r`H@K=mH+miC% zG}o-RBCb)}z7ZV@JttTVY#|$HPyJ8t+!t-9LKF;d(}gZiY^@#|^|cOqohPm}3|kJ~ zcAUA^*pO8r80}rWbDqRfRbPsEYW4oHb`#TcaR!~!eAgS(I_$_c^M2ADi1)-jqVz6}bIe79uV`6>a@2L~(-D7VLj^oVVf~0!huiVvAguY6K02xu$ zF{`ZDSZrN(O)9**tOP(aS}|`!Cg>0VBqmifHN*N%&L68V1D|mM9EGQ+CmtT&3*=Ei zRvPs@4Uj#tvX(fDq{hklTjLze9PS9N3O^7RUkfTLD3cCle{+I>YSK2?v)^V-TVs8= zQ3zf1orK9Go|<-^*AL9yACrnU`SA4CwTxE7AHJ0)K{z*seNuBU(U!fs>RMLJ=8&&` z5~65b?LE`27s`YaAG{I`*gF*UyeGrdf!E*Ahm>Fz>YbwMi|)g8;9z(T&9?Ntf`au! zh{~U&fpxtjscmcCk?a01@zBDEd)Q3EL>BhG&(_4+hP~}dZFDuQsmnwi|8@Arx0g0`(fh+l zM^Ly%EoE9fGVGiQWdQBksNwLH&Ek#PbF#(Smj`1j!3P^mK+GJ@qtVu!mZ@S}o;E?#MpNmKN{}3L7TOBt1C>=!D7F+x2ygm9ly(lNX{VW{5 z$D}JQ-l!soL3H%o*9)rknzoxsQX&Yn7Dv(XhBzN($I6Fm-4&qH5*~C ziD$xCK5AU_gXM3D+VDQ?r$!KGEoENu7huims`j*M_;#Q*wc(9Ny>Iz&+c+~)=bK&5 zL>Mzx(17^E{-@=yB0K@Gps+EnOc0b({biTRpL^o-6^;(<1Zj)>J4spj$G4Bip!X{x z{>C?8*VwU`UGP(wf~NI>yp7o+*W3q7m@iKN{4JA6k8~98`*IzT%;efxCdHedVq0l* zfu4)b>Eui~`L8QqJkH^c^WbCFnP)9jK2Eg<(96Sbcrv4r6HD;geeM#-E?gE{cj^%A<7%3J}K3gf727sMM=F&G<8LOy@Rp$i_dk}e_4cg;$M{#=Yc7gQYBrb# z?ESpWi$z7a?X^^U2g_PaH;Z*e-$&i6haf?u7Mqky?ZlJ51$zHx?hC70aY2g;elJC~ z0wE;q?5j91W&nIFhG)f>j6@$~aj|*SmKMx(4ul?c*+(g-IPhyFUQR}TU z*q$_|?=Pbh%^7xVa}I?+OGBn4yL&}^w^aUw=75jps=ey`v$U5XA-C^aerw%PyAoS= zA#nA#{MNstUMcY_6a!M>BMj!vQY@mP>8GcsZNBR4DQJMO*vg8Ti;F7|R`NhS1hwV# z!219{sIM0U-a+7a!6Y$=otr!U^wj?S=u;$@uA>28R&#Z5`SNWeP4qnlE|1GieR^E3 zRMu~CquF0O^XC@isYf7=#+YXkw1GG-t^4xl`d+~tL%-+jn{Z&YAyZ^duisv3s9~|0 zK<%G~^-_o>ItsD3)3rbT8HN!kdvG4yu( zUS-7Sv6W)eXiLx;%KuoBhm9*LR#Uxr($-8aHiDUi^Nu-A%)Rna)Bh$)*ke+8H)SEt z*UA+3&ST$TD8%0I*6#aB3S`PJb>RHv;5S!pO&BY!cn4QCjo7vE3>;A>0 zN`uk>E}3g|wWboh+8qx%URD;xV7|>PUH!QbLA5yr^Kh#LF{`0<+fH}u2S zkZ?v;_+;K+U>XU8xP1bQZVqrWs6b{^l#G;ATx#mK$-Os#J?)koks49>eQzC3D~_IE zwGW0vl+6GE26!ESd;#2t_YWB)xtZ=XBaL*jSjUl+l+@MJV^j%x&i47~Jx4%-cJ)`p z*qs|PTL91~H60yNRF(F?BB3XO(p9Q$`^WFm(fI($ zRrD|Ya_5G^2FOi-E$4I-ON9-oG&$d&1-bR#)vidT*H$Fy+OM>q-VrSAPfs-e%N{`Y zv!{7YYNmWEEdRse01+={aOsEw?mssdA|wWK{r_wkV;^&3zb4BPn!Q3(rv?o2&F9ms z(NAiCh}g-Atq0+M{l`deSh*nGmS*@dva_ZURfx;KS(v)N=%AE}rYQYwn-bQ4^CS3w zBu92Cw$OC{R_|1%^4|^kccJTlb3^~(oPbIH|9ipi|MLa^Rnh-;!T*nUig#9Jk{QtP zX8WSp?;XRn-Pj9q{KInON~{CFcFu1F7OUX>gDyrE{_M^Y`CW1gO)D0BK7UHzbMZD4 zb2UG5i10WH-y^cKf0#XzaZatRT1~vkv4fH7^3B(cQ2N(q3e_o_kGKWczK1V21SDSU z>(}qgV8BH0W)i`!j06kw3YaoG&=jK0nD|YlTniQjil}N}qXA_^xZ^hdh37jjnWm z`?+lK-Lg8?Y75)+O;0LLfuVhdjn~ma zhSlpgU*pQB5giRXTSuH&+zuG1o* zn)ZhM7QO4Dy=n1Wa$Y=&6zab7`9eTgk;Kjabi`HGaz@{?bo*(0C-Ip|>xsmZLzev= z%Q3iD28D!RsVabLc6bwG^GB}S8!FzkECM*2^ zn)eHX#WP>~?Q+d_jx4PK`g9vFTc(qk)UKhQKcm?3Qigq#?+kJ0|9G&kw2HM|dFyQz z5xTo(%R}9^4;z9NBm8AnltVL(ZQo=vS%VxMzp!lDaL3m>Gm{#O(9?5qV$djM7NuOtjH^PWyu$fjS3Yz3LfIbU#$lh zdYY{D8YnOz%Pb}nN3Wx%er3Qhd~HT;GN$bVpCg@bL+1aLf9+M$J74=p$hkc+D*lcU>lM9YvbYig=5cTfXS5#5 zTe>>5Q5q!i^%z`aFTUl+$_G)CEq5C)i$}>Cha?;P@-<{=3C?t&fu$|vt(%#-N zyo4=HDZMQVZXr{e6OTTX9a8g>geX}UF*AjYep)W|5h3Ig3pR{KIDUcD=C`7Ji3{G8hjZ{7}8x~aA9(dDMxizyt@G9CH-7Ga|8hk8)y3IW8dY!UvBI9$l@amwOGnw0F+EFyi zH`7C3qRDf)@IEOebLUnZ)3NpHE19Z@T>kfS|Jqqbe96BA2e*pfJz z$N!7Aw+xGG+15rAAh;8PHWH+hU;%BbM;=;wRH_6Esh`nUwnjy;j3J33e=S&tceMn zsYDy+>-OH{dq|5}`BzC-wK)c2w>oUvH-_9~8k?0Xi6*_gUTP-uESt88Re6kTAtaY^ zZ6e;r@{yYdQzWq!pa833*BxC1U>A(UlG5n=+U+R;*jTN%v^gv+JgAmmFUH8|ZT}B^ zutv4Y_vs|h4ekPPng=!AiB4#+_t8(IfM(P_JR1gGYyPWhU;3pGde_wF7m?s-orO1S z1wOm0%rU2^x~YQMGFJ(oYOv)sTB;>W(fopKc;9(cUrE*$$W*)+9BGV$Ok`<%NlB5m zR`Tq}?77S16xF_S=w5h=*o&#nReFqd`W5YvM_6)8cN>TJ>gneyX_e3Z`9Jt*DtQ*d zH@-o1Te;$_rqUbC&v}ONzK+ITjW+~VxONcg(}8Jp&yZb~OqLtk32~4SUQ01!Vlk+A zr1q+F@}N!lc?8w(qiD`Isi_t%O&ikFGGpkxRYY6Cyn#(h64T5}b3eT<$lr~hnz&|i zr+f|Ob$dsOAvBVh%|mzWT&86`eacI?fc3iB_?Mu!5k2qPE4D^1@COois3U`E`y;zW zu2BOEpYSOycK_*(G26M82r7avD2;GBy|$s{m2VUydu`};j8jphmE*U5BmJ1@F0QzW zhx{wn5z;YJma9od>_OD50DZ@HVBA`N+#@KZ$WbfJJg@lM_QOq@f`Z@$j1<0LpO;yU z^>K$1FYc;c`AA9?4Zi&Bp|>8SW!1jX&qxn-wZOni7Il~m*aR?P@?I1(D0&i$t6_9f zTaLU&1m)3l(vjWW-4YrUL#ZrA+q!t?QPVl}dpJn1Og@6T!g3Xtj3E#L(2K`y#qVsG+vM!a^i?3~K6Q%@|JrLtm@+x6=Ryr@ z`<0+mDmZ@#{luxWB(=YN_}Bctxfj=>(Q6DGXr(LEnjXm3US zjX@vboQJo(yxldBghB|^C`DP3U5**FbnB)TmW0~H;6@z!4{2Eu8#*#*Im1rIHNCIN zz6K)o|B_T;Y^p;1U!KI!wvdy^VS^Ng9weA}E9J+nh_2F|ng+l^GOtjA6$Sz9u`A@v zLiHhdbRPk*)Zs40f~(?TMeq#;$B~vAtP?gNnf;!I8#3lJqK6nrzchUpX{X-(7|%62 z7607-A!P|>`Hm1hJbZqL{H-VR`Ab!Wn1l!${bQzoozU}d1z!Ap5L!J!U>j5n>9mLXt7@m1e z&s%g-MaVr7$v|o8(=tyzdQnvp z)sO7WsIlHE$Nuu75~U@e2f(sjF*A(#moN75g402M-@j05m>FKyw1z|`*^fllI3KLrP8u7>z8&?!gn~jVZEO5SgOeTa6$Yy{d&P=BkAAC6s^GeK$bb82bdX0*0tR!h=u zUv`5VSwdAkS>q7RF&wsM^@vIBl@f^f*n5A8K06T$t5F_FqEc98i^Q)kJf5vy7Ifye z))qI|wM4SBQ|o#psFl%J6!taq@GIU4+f93mLe?vxgb%=Q4}_xx%VosFSN6_aSc#QV zB%ho2NQHGAq-AL~O%#{h1rCHup}gz z^PGK&Ehgt|n-&^qyJ|XS3%L2wrJT_ujDtgtq_s_?rNHBY`o*e#6W!y`Q8j^?iHb`N~ftplnYc_y*~e&xt9YXrt665#!KspnzO``9d$qra&n5{SPB7x zy7@sss__%8rn^C#lQ3m-BqFTvcQ&lMwezgw`K6K6cyC-d*r!u37H?4B`T>XwhfNIB zak^{Y`=(-x=-dJ$J2{Vdugt`x>pZ$yw&pcNe`Gsy^yu z(Qad5K>+p#9SQ5%AlR^w{H8`U3h(`=ppHn`*=HUaFtx^$Ql3{jb@rdWoG`mQ;T8SN z&_%hqu|#E^OkYdx;RV_3qK_W@GUie;VIVK_F8ZZ$#bX5qys~ zW+?S}mjQjb^6#;U`ognITSz!`J@uOSzH&fEH4Wq#upA{yeu{8X!ZjjTf%rG4rX1P| zz@6xv&KvR!ZtAse=({FLg($}lO77`lmr5bbTa}+XTfCzSvrImmO7GE>1f~`@yi67ZQ|=-5x*wtp zM$lH$Mvq)VU%K{!wGTE_%${?9!RsaQ?ljpp4Wlx_U;`NBBWto;g~6OJV!xb7vOWeO zH?lWB6;0X_O5iqJJG<4j8;dAw!u1G@prt$HfeBQb)8(J3{=RrNt~A^o&0em9wlN-6 zz8~oieyrT7=XOEN0aUKuZ;-#K%i{MM-nrZ9%q2d_3!FTNodXW_nRqMOFiPN|NRl*? z_RmL>;4hDG?=)qBjjy)zGMm5sDB>DmKWw!L$I?z+JtfXiP{C|&-7s&h=n*&5W5>l! zdm_x;XI}ekW?m2w?_Rk3<_&DNDl0%KU(?+2fWuQ;YJ`sUgLi@lD#0H52Z{S7?*O>W zpT4h2(SU{}Ov!Sic32$kGlEf1Ih|sm%ZQY(SU6QBs?HoB)dZ#&_9PBZ*&=c?rfAc^ zYIt3B{4~VGw?{!ov{#01Zt-Oy3k}q!<++n@v5of;Esf!u4iFqu$tIooh{s5)fl-?S&jU8uan5_!F(OXbOXy_Qv=)%pC27zz>R!`JGGv9BfSR1MN{< zZWckkWo^lyrB&=(rJvQfbc|x0hC)NKQf)kz#&vYz5FL&eyumAop%B|O^a`{*FjSdK zS@3jeKI5%3!Vy89=mMX#doz5`yRFAw6)v$VTydCG)`q#WAqifK?x@){%@4Mz8Ik1% zHuLhCPa{%^d26@D0^dK-0Az{)Eo(SnE~HjOK3W9h50GL0f5gskBxt6V@LSy0FZm&XiH^aliU` zZcH1R2-j#p>d7NW-A4O$?~RuZev`cP{sbIVu!6*p?U&uowfzoYM?`-rzc%U^Q8}zO z^h@YTT{*HN-28~v#KM^I95zV?wifIQL$DN9Jp6pTkf~QcXI+so?RnZdnDot~q+fpq z<>FebN@a|ZbSfzPc;x)nfG}ypzzgmJ(VDJ5H7jA3)vJp<2eIsJEwA_Q`!j6jED79- zu3=uVrYd|fgkb5bUgDqvyCcEFv!*tt6Wz}?w4KvtCvD*Vj@5oFd5;L!`Vh|aWBVSn zbC6C1?EqkzJtm#)`EFH?|5#AcYvH#$y|0~Lz#FlLGetM|w%8UhuBRxeLS(o0#?fyb zUm?Px+gl_S(s$!bYdxwkV$mj+A3tTfJ;IsAI`Y%fI9Kt3PUptWpFWdw9LnW7T4U zJafOPTj0NzH?EQ7h!lscaE_~Vf{)c!Z|gLEwrR_(o8LznsB9)s*vh(EfYnQntsW$Czp&mS?`o$$YN`-Gu6XAFPjQ-(A;8o0yd*UqvX^ zT<0!$?YvD>Z=qrj>QLMC#`U%UDhbp#!|nsaSXyX1lzaFuT z`tYNIp~x0QPs|VcBwQhT;)}b~!`}@hdYBoXUV>1+92)D%j%}#iTa}k<=`ypOn7rjHu9EL z(dfBQE;CWCWbTaS>qo74Wc5RfGD55!c}P)trv&;LBk9exS~r_G!&gX@i3F zg$+Z2!yCR6FP2<2r@Yj?9TSbezyb73+AWOhQb$9|7TM#hI|qr7X(H1*rx@Soh*mYs zA3*)Ov_(mUz6C4v=aFapKd}H3!JxOBTL}m8jNHaLJNmSq80kVd!~2)$cdx;xxm%}u z{XGlcWw(E~8G`nf4(*^#RM^j(SMX0}HmQFWu=YMb5rGmCHo0A9d#jP<6|&&ffM?sV z8cIYQ<)*xuid@k2%%XmI7^iHp?z7}&U*GI)Z$)Sc)f3m$<*zGiB-F7o(OiOuP+VX3 zT+i&mQ7kHz)ziN`Ffv!Jd#*R1y5bR;y{4xyF+A8Rj@*rak2W>cRLoI%+OJ5Ud8kT^SZ03$0zStd&y1g`)Jc7}-ZgXtoEsFUl5Wm5LygX!Y zJNkkSBnF8h-59bPHG1c}eUKo0hvdLP*$EVi~^a%K;46CxFrpHW^lNH|qRK07*id)sdx zY&X?Q|Bw`pW=k3v7-SXWm<%OwIFtps{Ul;gv%&ws7kfGb+6`++9m04>5zD7qeqz-x zf8w`}K8jH@OXfy|1uyp`^{L5OwKn}LkOS!m^-1CJJ|G8tf*Af zlkTA+M8Z`vECdT`d@mpD_CU$JdpDbx@Av0V|G%=rKh^$Q+W3$?{rUWp-2Q7``5z1X zTlV|U9TfjH`~9bi|Ck>CQ{a{oxwt=0WiR8;cBGzR&`1G|ff}XIb4xfwdWCRLpJw&9 zxC)I0@h9|7`RQ7Fi5Ji&wT*8N?e7_!genuCZH|8Kym0~sQP*9^Iz&^S>=b%Q{9OP* zWa?P*wKLc@=Myjt~a**d#-&>#B-Q$mJeA9uq(qUfbxCZ&w%d6wh}fA4!@jwR)4g4W%l zcAEa$FMJrbHf~l2IA;=Zo*nOP{xjCXN;NZ_>hYv_XPrrnf1;c}LCsety?!0Nxew5^ z@_~>Nktbw@i*#djzPD$e-{dyG{UpZy_=vKq9_sk5lT&m(uQX#lC;F@+j-99Y+s}tQ z>mIXwo$xFoWEc0c{6#P0D;)h;m64=o-}$Jc5UuhckFrY8RAE@0(Gky9lPjprq?#Z> z%h;su;M{^|ZhYmhmSO&~uAfq#c9UQtRw{H*91~-9PMpWUbXb8UL*wT9%6JnyciHI! zvDud*B)SJJh_8vMV&RMrO<;ejK5C=;>e~ySZ z=Y+C049nPBPDi1Xa7l(sNlNpo9K3rt3guk?@Dp#mLY}l#vTk^c4rW_(+IX8YM}ts| zD>FYXICHU60sT2Fnzoj2S_izRsHN8S1y49d3BR<3+FB%qe;T2!MtpoG)67j@lOzfU z&ap6!#E&cik3Bbd);-c?BxV`CF7I_KV!=F1U3DAhbp&@KY1~yUHf6E$tPfM~>Z;zr zyKH)JA>cm8gimW}LWt>Q{x3H*tu8aKfR>;;zB47z&kG43E@@2u*(Sb^cC}r+hb*<&D_`}Dcz{J z=&l|0ul?)Uo_#wQvFH^B=7>P;FBsR+?<%|k$qv3F8>P}>J&~uRE!ydxif~U9{XYPR z|iL`6E~rFVLhyeXtcQxF5UPL?^6DXbqBV&MKTjSR`jDm~RpSf?Yj@g$;@ zs^eivIf9dq?%W&j;ir^8<{zVKsd!qR%gk-fXkvuAClnrKuYvWnjqj%L`|vZ1K1*egXk8_T-iezlXao3$6xUqx7RELZe>dHz+~6DKxJS#?DK zmHkw?@*f}7!*as5ixe%8MnnXG*&i>zG*`EIH4g!TL{SQ2A4bMHAAu-F3+S|b4I-_i zPb@7BqZ={5)>YI7`S{qwtcQVC7j;QB5fc=+U$U!xK=RE4-$INgw{r#bpU?-8>SYo{ z3ft&+lyj7jrY4crC5f*U_R7B3w@?J|))SE%&~vw5%d2xenX!;sv`g|uMfM9+Se+1W zF^F@FVS2LfRrqpg5%xrIaI{Wg`S+(wq7>4U-5x?P+xtZ&iMzYsmNH(xUM82(jhl3! zx(|{~nLJ)}>AKbfV=T~zy|i^Ww*!Z!R|Mb^Ge0{LyK}k`F7O?s$SbG*9*YC-nzK94 z>NIpF6hom|M7P}-*W|!3@i*9jX{(aCv_QNb&`aIM$uyH#6{{7^x^AV`ekKlHe%6pY zUzBEvS9kDDx4}TPQgc2<`q-$|p^)`}_GOw@178mfDkVwa1@&kk6hR9vopWJu7R`qcI5N5i;5A zqc`9WQUOWm;52vK2_^pHg{b&KDCqJT7unje!Nd%5IBoU18apJ3l{x z6jmp#$|$=onw2hPwQLa7O7Kzr9jDDs4 zN+%@zVG=cB9Ex?0vrrV&Cep9-%r+y^pfF>9n2GGhgyJTmbOUXf4#|iA_7hc3EY|s0+M&rep`s z)!0B~=h~kL>8Gxvb06*W`$NH&lC6a;FGg`Ty^n8hZS%h>R2R`*;Ym*x6jbgiS$12*FRbS_RO_848y}qgXat$TB>ua2h_3_7~h){3>MO2 zoJ+ob-0NcB#d@9gHKar6u}Y(do09jB;L&FSTDq6>WbaJ0${Lb%&MpmRV@z{DNSMV( zG$YLE4?LZH6kHx=^J257j)pP96sQRPa=YaK1gW(|G?}opP3DNvmkNiez+@%(9BQ8F z#Khf_kv#s{8rPUC3we2KlHq!vacQ&lI!7PKH0eF){Am2+*$ICPQ#Z%WiO+$F2JCSQ zhT6bId+yJI^wFX;4}DUZjL3*4MCJ3^ZyZ0YTd(d*X7Et%97U?2^)}K?MY-ub=e+JLTUgoRCS8|U-2)@v#R{&c|vYW_mYLAI^REG5^s3CpC6rr}5E+~NIR#XPrWwP zEZ1ck_D=*4oe%EIxBukC993|wX}N%#_x9NOPCa5CHCEK(H-cspY&;u{ zV_d3FO_T2`0|`;t2b!8tQ0Ht&{wLke)NaIZ3Y$3LGWtm5@9Hp0Cq!;DDhkmSnP>2dO zwI5qA0-{j@254S*u$$?qAzjJbsNz?A&pwBuV}{V|6kcD9&D!nY7jAtYtA0`0aHZ@i zr@r^g3b92q>M4g`HW7E9z;Vdk#vh7OoZd=X#v!!1@E^{NdEDH>o(Opk~gjD9Hh8ng#%dVZ^=@aTIcQs@nt{nh?%nb>eW}Gea_usG= z4S}3J*YaZxEUaywxRVS4GkfNz45mnaUA-N;C^t13s3B$gH;%A5>x5EI>hiG^d&17! z>e-3KFMAHDtj6vaXb6cIVjp##i=0=myJt6hxMsvtZZxd4no7*E@22gO-g0t@!&|uB zWm=Hh1i>|I(Zt2&acT#s7 zJ0CD!6_gxf#d}$alvN*NSTp_)Kx!EoCd0f>yB##d=39nV@lW@*!xNGet2&W zH_u6baCi3=YdEEE-h?{>a6Bl;dtXU-+I$ZDG9`~&>gvnXKsc?4ZtJrU8kQtMIulzJ zJI}SoTRqxyXp?er^Iv3ReMRiAN!GHtTHN-j*p#Z+VP#O;hStkBOJN5R%@6FP`0U{N zgyW=F;c4LIzWxjxiJX;6W)^_y-5}j*ND&PXB<~A?tInIk6nNI={(y^H&+_}Ya zQ=0(|z7qm_-0S2UkOgO@6zz_PxWF1;Na$mX+m}pfHPoeaN16g_77j)vksAF5wJTo> z!@a7+?nn$bT{ynPaV`&b+5)SMJ@xK0Cd>pZ_^R(e$53vSyDlQ19h_dxFOqf-8SDoy zk+R7Gr3ZS4h;4t*g|6mK1zdS>yb4BhbGB=qzc4szchuRNjmT0qC|Nj}xifG+T8|$b z`#lyu;9#e3yv}=Q#d8!<5_$1!F#W`^FswwAQ(!kSVe6)$4JT=6ruo^gc_$LhM1a$* zt6bb1)5x`vj$qyU5XZ1J3NvkdI>f>!V>4QSe;z^ktj(aU|WV#}~ zeMAHUx#EW4y_KJ3M19vHDL;m+FXmS_{RwX2-2&{pQ>Jc~-+ifn^>#HMzaQ;q+dWJX z;(Xp(qZ8$-$E*;rWV7hB^vO7!jc&|gW!6iHao*_{W8}j_ah2)94!lr2G>U;DKo#7 z$ZT06`8n{OIa>)j+F1(N963f@J!3ET@%ntVFZ`ji%S{sLSx%)>MfjS-OYmLs-ljC# z+K$_t9?hFkiI&-wAcG(nz3wZYq0K2|2_c`v9sDQfX;qcq?=19P%b)*9qv>yk&Xm|^ z`!&}&myx70Z$7szKYmKJ!g(z4Q6zsO^c(w{CyV3WwnyC{M4P9?(Q40dA{`g9g~RV8 z=Nuh|5f=!=7ij+-(O5The|XJRvj-{ntFP%bL9J|>W9l%UTd>utzWn||YZRKBxcoY1 zMAC^6{8?n%QaEMUY^Je)ZMCmwv@*5C8@nc#E#q%M+q&7{Gor7%6yk}KV8m=LtrIPG z@l&b3yfq%DRRV7ry!U3`5;SQB;*)duk3>OH?xy%TB6Jw15l}|)o?|R8AK#ep9)}0x+%a6sI~Fu?YPOXwJhqYH ziBQEXN_Ee#4MQGWsczM>(At2$Sj3fTWO`(A}3@J+?<+gExC;QIW`d6M)_Lk~Z z6OWYPDphRYJ)uPa`Fr8KO%*DT_BeRY_+HBzFFY>|{!Q2_`7JT$B7%2gBDuKFn=CmO2%bdDh2kjwzH3 z(=hqjB05z8Hs5!=RG<1GuWaPN3$ht0&q|2hON`=n!N{Kixi}|Wo?l!#YT7q=PzwBX zPw_}4dp#Xt<=o*ovxxBP#y-zbebYSyN2W$G7Wl)ncj(AA4IeefZwl$e<*hnTC75ljFkIJu2`>yp40WUcM zPL2Q!&>XZcSw5#7)P&!i@F-&Gj%!eK7$%mGFE#rs&n+5|nh?UlrTP%qK3Z?mzPzK< zvk0M4+4%S7U6;wDvliNeJe?QwHog70Gj3 zJg3Qcq@+ind1|CiZGw&v+t5AHG^S)9YRyS`Q*ARby?0nu()4BgH>^<xH=J4qOkS0^cv zWMr_Tr9-N$@t79pQt~>A3IRoB996VLdxcvx?9{aTHY+CPdI%}?Z#iQeLg!e^!bG#Y z!IzNl;}@)c&&)P%OoUG$8HrApiFO@xW~>yo@qq2EHzKgTNhFHW27UDM{=AiHZvY~9 zKzD&lw)>{Fbr9Fc@4Xxi`7biu#4CNfZ;D=ssEtD-cD5mA@)T#=xoQ(mH5cDIb>OMH zV+S~$Y3h%u-o=V(&zjK|z{e9~zhRM4_jaa<9Z30&R@^L7KSi)>F;|xqhOX~)F4<{Q z*V;*dTne3jQ&Y8BC_6gjs+QhX_XN@)7N6~DsrF`Nt|=SfpnrkVa=5WneBorrJgXR6 z^F1TLeGt!GfR}T*+2_pO&g!PvBc=Yp$i2()SC+TQ+OLQOwV;7eY}HE(6KY>PV>^0X zO0QzjyK?|>CoDGkwq;;wj*rF=#9sB1-ucp_`Oskh>k_H(Y4t(2u0IdeSIC{0N0Swh z>i~p^p}H@M$=w%fCmmJu@j4IW7)BqI2#k(QD?PsH3D5y>sq9EeAuQwSVaw6WFq z2#((H8RBjhI;62mvDx-bx{+mNqcAD!XbYXe%1X9S7i+6?%UxUw_t41cLVytVL@wrK zOe6-~70i4I<@rs?q&bj#onc|+ZlN5YN zO@Ri~F$Oa4Y@YT^%}q`=y7U3TiFu{jf0OcK3%3npi$f6sR-VXP(8Ick&VW4IW`q#S z2k)fdOF=kZEON4OSk82Nm|goEM;>#{QiVt5fE*hm3E^(?%vl(Q%#}tI$T{u1hJ}sOW~xK=dS3H zKL*cm$~_Kt*CHjw3Gl*fn|cjyl*60$S|+Q0j^(h14O>$;U|!^T^<9}K5uM~u+{8}D z5l?t6-XS8al3&GGp48I;YtxD?XU;|izk7(+%+s;WG9oWOrIU|2G&}z~7;0gy@s``) zCZ|u^`8_>7ZEDexT$zIn*w3W8ewii>v6?XAZax>xWJQHWlC&g*&-KfMTyp*Nwq{Qw z8YMm*e z9NVl09vDZxH4DVlFTEokPSk&D7&3qHs;8e-Pd&r8NI?6JqzE@e??w&!lmR-}hnw`6rV3p*LI~)*~KNlRuPxg00uBs2w4I;T1h&miw3qnIG>x=m`KY)MIRyp91kM9gg}}8U za&i9t%91y5YFKx_ z-Sc4l58LFE&@=o&=m}a&=h_KPx6X9ieY-&BP}Nf~^Jc78;Z>)y=N?o_W~kGlLcQ(b zb*B&5DY!MBtyj=gcXb_|5x3O9S^GEO;}>tnh&E$S3*5tvzBN&o?QO((^Zg}Q9DSb^{3oCw&*AOyqIG&WJlY!=mR z3+5n$sezZi>Vd3lYoh=DDSI@Ytqri@`bn6PL$G zy^*o+7ZVgj|EPoX4}b8<0`^T8Js#}Y9>c@)Rn8oDwAv>$gc7}*H2L6~fnqW{$~6hE1BE#=?i%t&voqUGUM^ipjS*nIDmybl~YISw#xkZD^brVWYj}SW@3H!p3Uk)avNB#{b&!l(JOM zWBa%tTx8NwozOM}>vC7t8=<7y+%t+B^j``|z&gr( zpE>8D)~X*Iy}`$8JgzUb+Bu)(x$lN)$myV@HeuKnuT9TjW{}3_`=BA+s-u2o8C9)y z;}AKCf1o?Rr~f9btL$KoxUIuAAH%?tvZ-{0v|-6&lA$NEe!z6$t-d}x+U+Hk9)MQ^ z!{vxQ>Uk5Uuy9Q*2z4I%tWED@PiuYz zC6b8QSv3kLFr6>#%Lh&Vj(&_*xD$Ggb9|m~6}B+yq*AJ0Xfl&Zxs%m~IIH5pMIFit zCtXxjO(qIw-}^|hSU;Mux0HLzX1rz3Zb*ZCjn|3%v$Pnk$T#F0VKXWt(sXRMF#z;@ zB^xNiU213!-&`)N&BVglIg-rSGv<)nIxg(%GHCyPLGt^xto~RDA9f2FKz)h-W8vm$ zK*CX2azjW}Y#J8~!G2|nAyy(g2nXCr`R# zI#UMUuQH&VhDyP!eCJaG7wqMugj`S27l^eR&v&7*C1hoFdi!s0!`e%^2S zDBlN_j;+-J)@!aTo#Up1d4JsN;ARroL@T;dROOylUhEk*6=ylrv`(zTY2R=lHYXj$ zD7oE?%N)M`95n4!=5td0Ec}rdyE&y2wp^$zFCi)DNsy3*YMO~x!q3fpYvnw3Hf249 z?@qqpT}>zN&ldSA1Pw&o_V^h;>Nm_i0LQr4?ZrQn=q}N;G`4@>fIsL%KE*T|6G52A zFfE1-8O<~pGNVPYx2?g-`$k=xNcL%^sRquYl20SQi~Uqkg8Q%P%?^Cuw@+~TtUFtl zEIUOeNSqP$aEw7`gh)S}Kv-guIA&>=b8?~u08G{);C_qsOb1ME=M3;EQ!$nGj$U5e zC8!&Uc3@8QgK;{_3XDxtQW^K5AaC%w^C{!OZX*UUlxizj63S^glbUn=&7wt%k;nu_ ze_?nTS2&fhJz>fsrQq-b>Vtkb*dZN(c2EMuFP4l>U6VwRVNb3dkLNUoP*~LZZ?KuD9=Zlw4#aVws0|!a8Vbd%8r_-S=ly`xrxi!03J$#t# zkzv^?duG;E0hXH8C^f;n^XcxP*=vbD!9wP)uNFBD$~N|#d-JOZY~M9@b>9h~ zM}%cbu2Xa#3J-rSv>F`SL#Wk*=}tb4?NPsk3sxO?ai6v%NIHU~tacYC_?skGCwXi` zgX!*F#8L!#o_Tw-^k51jmlg+SK_-18@~~$2mHvc9b$-Y!{by~QPu4Y%G7G(LzCCTk zxN{aOj5Eq+b_b_=yC;n86u!_o4B9!6%Z@wmf{T*aDWvfLnS^9|Kaj}tG=Y>vx?6;U z56Fb$Nuk#j?2g3YHlX*ev&u2@GnHX##37Pe4P`oHFKsO8rV0-0Y>q4`ZXMP0D%UR} zvIRY#gwQc*th-(_kbG3OWXPM1FWV?BvwCq0sVp7gohw~)o7q>wQGaM@v;hA|y9K>s z6Ff1sCB-k~c1`?v*>qMAs^t_H7rNLfPwh+e^~O-2I{*2`w(GngYTHuii^tMKsYcUh zAuwu|43r+kDsw}CdVDLo1U0+0u-HA#g{;1~V!1xx1 z)4qAyHU7mmP3s7wLSA%ih$CF*V}#R6#3wY*SASy6ni_d8$$ml4W1r#3SlF`A`lhI5 zs`s2pWkDef3xTi`Po3UjaN(~?s?H!dxniF+PjpPm&d+%*cJ>s+%XA!xy))TDoP;Eo6w?kf6Epn;actwM4}>`Y=lOr^YQEPe z!RUJV+geh@1wDoiho=?2o!zElBYW-uQM~sg{(DP|O6Bg#bXGVP$jrsG1_(>+sobqAS$KpdR)tAp4v8+EU z7h+XURI5AdHOPqP|HJ~VDowxjKyL?>qiBgB>x-aA`P0Yd-9$bLOiGr0{+MvFbZ@mj z$$m2xDzF3?Cyi9K`XoXBvdIQbu54fdk4XREf zB20!|^4J@gGtK^@yX9|4n@$8TU9BnK8@j{3OUKYl@jC@v38+i{E{9!a-%_X`zKMDM z)CvRfgT6O|0dbXr{4#(}iaz{nx3%%dpRZ6^9)9I8eYnPm$~;^RM;@;9ehCj(vR@BZ zssFcP*&~Zv>4?`FFII#B!gCK{NBP)@R}QBmG5zbk5Ul_NRX2K3Pie+=v=&(o)o;6b zom^M@K?}H9)<~wp^>vlG)l>#`y+n8J`EG1vQaKXct$X!!C)r$c&RTy3xn3{jaafvi zn`pg>zY6$_HdV8>0AIeon+|ZXZ{0)XH&{A)_biEa%82_-EkMSYD6G8u zh;EAua-0sX|4o#M{f6L_xMCR@fUeuDRDL z)~om1z>O=9aHL5oj@{9EJ`NPO2cE=fJ!~X)FU(#K@iC+R=y)Qi-0VS}crdWGxU|mg zAz5@rYvK=K8=1Y7Vu;FRY;<8&mD<#HAcGL7LPESpR5TY zyX$U|`q#z%s?cR~<=d;oA{dlyCz>lBemnTiEiwLanXVl-7CBpG-F_(ia#wozea-n5 zlO}w@d$rhy2)4B|=Hj9o#cqF!>)Pj<@2R0F_nH0xVOR*uGpF|CGk8%}`K_r{N^bKU z_mRU!m{eqxaDE(|rN^M_R@m-{0t)MGZxgJ8OHVjLp|y0CJM~L8juuWu>fPUHGJ zZ}Ym#$V15fO>ABm5c&PyLTGGY_}1tnW^XetcOMqGP`r=YT3ud7fn4#f?HW%~GPzW8-TW;QYw)CFvJG@Td3t&_8ATx;IlBX* zf%Y(^|DNmrZN_4&|J%g=|5)t*p8u`gL6FncX?!>p>(|vSSF8QvqLJ+m+hHz3|CFWM%7kmf!=6X=A>K<0k^y}cIGJYInL_F;{K+KSi*1p%u7`R6!!c|d z?KyZHFJ?Ycs%?C-DZL;CTtb?N??f(AvVH_RMm`(k8dI-HIpo9EIi>MaLh5|fJ41)D z&#J3}Awt{LPpc>U?27~NZnrQ*JH0{JUu`(zN2p)Y_{NoIK)t(N+&IuY>oMtx=YEphbgLsX-!4AhQ>QJiFOHbsWlATyd;IZg=T39n*Ea}$@ z&BfH(D5{nlArmya-jws#IU0Sm)xVv{OP8apke-&A@5?4t#$_gd&`@r5uRPp@w%Y|O z0L{A742>ALEen&+&r-klE(Vbh(Wlo^P|;-Fyv5us$p{2>Dx`Fjr5e7lF3~dcuv@_MZ)PU4wcaEz-jp=(@?_oIUo?qKeYwkb z(R;#$wwL~XrI>RD`L#WBA@08Om*82h5|Yfxne1-b*?GG;@>1Y?J*M5UQzyLzHpU8z zfiY}q@~_LUlr>%P=6}OZu;_(Oh7Iw5;5`){wwNkqv>uSSX#DUtvfQz*IZ~y2m+YI0u$N->Y1UFx-a#v(A-cSajPu zT}5X6+GiM?x!wV53#nSe!v>d0WZBmB={PIb9`2Xr!9(Tm^xvirM>jSD7SwrO80=w{ z63S9bwYJ~9e{@7gSA&VNeT18wmUPPw#2{Yy-kE?VW5~yczeSkrx&Tt334KcX2x=OU! zJxt_`+1{x0XoR0EPW0#ZG=3lZ_)S&7g4HpW%H(V`$HQcZboZHXwQT%p136e)%r@@CxfSE<-jo=?k3RxM3|>g3MWB z8ZKbUHx=kigSVB2$_626@`P;g3;lxuwK%|NNzlv(r|mPSNp`u#=3Nof)sMrCiL4%N zMDttHQ9?}=%RtGZctb1imOJm*1V_Hla@7psS6MpFk+Us*zkXM-aCkN-pZ7!8pks{t z*@v`r`H6c-RY2iOYQb-Dhb-qlf_-a8hU=pY*u9wVyi(sz@EEq(Sm{{iuBa&&Fr z+q7YVre;8w`VoA@uFHS#rK+ZEWMPoqY8#o^nV>Ctj8Z#F75>^%b7=*h+_h!T7I)17 zaK~vXnsg1}dn4*WXH<#e&NSRCp)P&-R&HFv+zHz;9w4>m^i|#etFh~jYT{eBfCUv0 z1!*D#1w^Dt?;uS;ngW6tdKKxtgn$TwG|_-`L5e`=MM4La-diXkkVpwV6bYTc8}+yD zTdw==dUyWFKQlRV&YAC=y}$j@A;<#cBQvr?~-q1nS`h}9Y%SZ)XZ!5-;wn6-zzFy z9tjd{D}2~wycaJrn^Gu!zwiaaj8s&UKl)aQPAFRIp+@*CMQ;-6OB72|_B3vO=N!?X z*;#j?Wt1goNolxK#4>n7Emm*u-VW9126&nQJsA$Jbj*gvZU#8jy1ZT@H2S4|g`VuH8=s-XLp4OH7x z@2oa}x&Olh{3DdBJ!@sHE-hjtGrVH^W*hx*gF*M8fZ?s{gOT?_A@PNC+?^)W_M8Ff z;*o*X=U%d9$awL9cqbG+Gbm>(Ey1VU{p)yS*{j%6W4FynCK4;}Gu%$-Qdj&X%Fi45 zN2oM2%H(rvF`%ww?##&#n#Ow=hFnEZ~O+u&~*FU~v%=j=py)-x$Bm*cn*i7}za z$}|shA1Chf)_1$-^J}s%rzlU*lu&;eTz}NTZ46b~Ia(RpZBskkG+aK_+LfK%uKL_W zmz8c-U~_BBzHF{=w?|B1JmdZ>LoChkh=m?xv?vDe*e6+CA44o`+x@cTEB+WvxCU~n z4fDwJl&72(0&*KZO?wkU!MuGb z-$Eie;=WOm+#_S;p_A$(tnCr;jMG|_#gVH5I?>4%%PwVxo~&-t?-9Bi8BD6`;CVN* zWQhD2Xok5?AA(#{OY^`BjOS0qZEd*Xm1-6r)Zwj`17u${cs-(lcx)`R zfirBtS1idQI0zd$o|Zx;sSx*Wa~O>oE<3z>5Tf}YTfikz&jhonSnjr^ch-JkEPVdj ztR)5NgQ2o$nZtn|m07|$wXWOn(@O}xDK(}?;DmvKjo8J$DOhQZPbQi!P7;;_J8<-O zq>vR>0=~Ba)6zX1y(ZxN+|aA*?updQQ3DvdM5s7!bHv7L>l#>6;3GJ1v%ctlb+xKo zOLuY-q4axVnLl9%H6JqY=g0_$$lcHQqIs8|KWF!=Pjk~l&;_qFxyvTlcf_V*{Dpes zkvYt~8xI0iuwBIm<>3s+EEl5c*qUb~=y|QtD`JEe*F{L7@~t5MqmemU@~Wx)~);JG`$G9(sH;jeVlyJCYwMSQ4{80Xlf5N8s)0 zz;>r(u~mJ=9G`RKQ{$54I;Y0PU2h$P707novRrw98!Q!_!*YdtfD~oKkqB{CxZwK4 z0v(?^p*Q(qdpqP#(k7!rp;g3shE{Vuf8K#~mYy2Rcm|%Q-E}f}4-=6sfv+<(I*lrC zU{nLBP#;HeUHEW}f|^5v3Z_FQYuU145H}$VJW) z2)vfn48wCje44)#f@ck!kG{HHkMA72vO_h#0jY)ygsrx0X}yJv1YS94QVnRjji_?r zjPo16fl2ieKNS{J^E||HSSuM79$D0EH;N9cKOEq4IW7t_V@ca(Gh!-U707fj=uH8a z=2^PWf$l%w@F)?rGKmX>VcNI&i$9vmy!8c9nAA(8$hV(?snXC5t)d_2dv)W|Aa;Td zcG^EaOpj7kce30NK9hr$2_H79ygYViuB7*h1$*oMUR3}_=El*L-6|2JgY_*HyllG{ z$>Sa1XzGjiY?!PMxb4SWYBe7pO&?T;K2-at<8@+;q0&5x1XPEnULbd2>4-$_e}6#Z zPI+}qwWBcW)qZE-mNpE)&ui*ozUkxbn9h>k`p(|68^2C}>C#i9dP=Syo>6`Im-;bm z@hcksWR$;>oj+Q}pTy@cE#sfW=P$|QKVSAIjryy#fAo<*nbAM3eSE~A!RF;y&-231 z%7b$iG5$!`$W8&!Szh*^K6^Q2KmBIox|N_*ME>F7p??pX*VMb6uicH&8uG!Fhpq04 zU!#yP4liXF7x--LcWAFnbDl-dC(h;P;GYVTc{`Du!jk5KKx1bu99A*c+k@CKmY!sR z$9!JqL4234R{&=?`d4Us$faNKhW;M+@Qk}6+vEnA`QOzW7Bi1#Swl0k*k*ORL0FEq z{X~re?<)@Yw2F!fl-m>dWf3v~M%|G;o0u%N?wvqaO=x9%Qf&GFmeXIDn;WK;D)t^Q z!2zt20CSp#oiXtI;-cQOXD@<*Nkqc5cn^oH0}ahN0X@nUSJ2RqwwMu=?t}3Q*O4d2 zLyvaL0}k91_`vOX8G#Q04`DB_lVNi^WE+jzi^o4zdZFuC^yyz>0m~L}Z8V4aM?HQ0 zYd{FKc64|gF6UMH@4PT5G46`sN>?*6VL5Z2l1)@}6nsHiu=F|5;R-L_lt32tc9WJj z4lav22b?7OxIUX!Y7mZC5tjFaPNuun0c5+A+g?qYP-*cEAKkceJuD`*m8#i4IW`1s zBnB1JA=AOQQEgw6AdG$cJ$}1hh$Fwau@_ZKN!?p}3NewozRl99pphKn>XFX^k#Ie? zv8*u}O+7B?GIg>ej()p&G+x$pl)Tre!zkzsBi@H0O8~nNz}ePnqRayDK-%o$$|&>3 zKO+k!0cJ)HuoPLFtcsePd=_Oh2t#f;*>nxq#wALoo(8USWA;a@ji4Kj368-fD82o} zUh$eE`LV})yIw zBs{s#$w%04l>K@FWC^mF2=T3O4T9JXSb=wC8wgX1Cp+Jv^Ujgx;werYyJbh?KUl`v zPNT)s!Osofaa5(Iq+B-*Jg{Mf5+0ur^5o0y23P@i<9xtjl#%l1cM5^c{khofUg3d9 zy1JI%fDn*s;B(pT9kbA^@g|WaIW%jh4h!lQ_5%m|R=A{e|1ew>v{{ich)wI(O5O_R z2fg8hTT}IzO@Bq0%b?`Jol}C)TN9h=nv6dECr-KUcaJ8R&PI-x1*`(EDvS=MvlOIE!?ExQMQ&Vlg znHF$416)!3YzpJHq@N9MWL+ug-nw83u_yVshk78#VFWjF zN*z0WVkSfAfYj_}LeCyj&E~@Om@+WKVYC-o#f1Hi3@~#X&MSfDioC(07k1h8rC-hL z3TR3Lp`YebQSWIn-}xRePNQXD=otRoa|g4_?Nv@nW|Ea(QjnD;qz0HQ1qB?tW##8J zu`;?xwFy60UwKEPGwT`VHZp5(O`ei$b}Y1=E*(5$=VXfkVdom)iviq@JKcxUCz7ih zY)`+3Ee5v5qjcOCGQtC1?*@D=5&9&3U-aAbd%j6W=|DQ$)5vQ7V@`EdWlv8L;?W+t z`Ob=Hz%Gh@$)AS32_m@&l?^`aGwZJuXw?RUSU~-hY*}I?=z<{u z+?6Hol6CFVn(PA6m8Q9qj}%e4X1G10>f?&V>vb8)#$s`apbB66bz*cC{&T&{2rTJ9N} zmU`f{fS&SpYAGd})U$OWQHeu|`u(+=9O=z_8~ZBGeyfwhkh{?B^&hitar+%~#2UvR z!G3l^GkGm{!1q2_7@Bk_;n-{J%{A~B8iQaQEL-B}p`kS4eMifBj=|ITbmc^sh0hGw zPZEq8137YQ2l;PGsTz}d!gC*;9EeejMJSJg-k@PpY0Cm1HovxgJ|*9n8Nos&DY5Qv4{d3E@LyI`j+HC)G7IDM<`X{cMy6GEQL4FtpJ=%fI2iF2(M- zRkfuBv`0unPWC#QoAgp-2)!ZzcRz^u(KJ7pe5Uyz%o7Qu#%L1XwHL$oIokut~CbW z*dF9@f-eAy_yrf1c&U5<&=znmh%2h7wdO!&r(#1IK$}+3SP@ZhuyIR%Ezym^Rt@~B zN>M)3wR>!6Xm|B3DWq^!hW`H+u>Vz=@|2hov%j`8A~3)6Z6@98ZYkDXkds#13D;1$ zk0wOHGvwm2JB~uD(n|1*K*62rfT#@Cw=7J@d=H;ZQ3DSLhzD=+mpq5aaz7mr6w{eW zd*&+rF#-ofty(F!Uu)U^;CDZD7d=+;0;0!Yi zO8rR@$`r2vNbesF0Pe>UTer2X=l>IhAY=7!B#*1qWfCl4<}G!+U=dR3%bq6=H~Hi~ zU@GTuWAJ+~@J0q6w5T(*nQR?8)AZ6=`b6_tFvlsffPUuos#Nw3^yLk?Rd zLqqGQDOrx39H9+Z>xD7w2jt|f^Rjg?9(O9E-_IT%!!IEnjFhcENc5tL-8jmR@1~`# z5j@9;30E*F=!6u$Q-SapeIZ!=yt^mB=xnM6imNV6h;GU|bB1wWi@kY#C90O_H$NBD#v1X<{KC zBV~dfsUS|YUKdS+yC)lWsep*~1s1mFl;rD-yjF#P8!np7`|l*#!_vrbCQA=QGw4{8 z>R$KZ?8K%P7(lkBjKb9)wzn(el7!Em_d@k-n33OJ%V5I3c5ZcR3yp%iG(6H8C!WHy zD`a5A0^7ZB6uKYg@x`{Y7sEY>z-k0Gg3L5jqQVxPO!=Kehp48cfaxdfN!UDr@kTQx zPPeqQ%h$-#rd%OEP2=ub(Jco4w#@uKVu zlHl^Q8qQFOFVK5dYbR)J7XbW!X6u8E8Qgl36s5?$tEEo^-)(=&_DRFe`WhS z-1?;C((S!hq7E7=Lp90=Y_i`jeY-eLNCcXxoQj?agrC`ew%7 zFuT*;QWFRi$vRa=$H&LP=izhkq9Ts2?vcu&h z8alc*zs5P<*2mta8xnvoByZ^Ng67BB`Ixs09ayzr+h4SY_D!kDOnrbP6#Uv@bCZ%7 zOXTLR0VLYjk$Cw8!KG$Y929h{c3_}u=uET~${vA5L`0Oc!8n$${Mu`qgyW?00TvGU z+2rTvf2ylX0rpmz&uG8F=*U}ue$K7kes@RRQ@><;80i5RxVLAfAv3M C)gZtC diff --git a/docs/sources/docker-hub/hub-images/orgs.png b/docs/sources/docker-hub/hub-images/orgs.png index 604ed95a091e298442534d2169de57d2560dae5a..6987cd3b4e5822810143ca0a5c93ec7968914f1d 100644 GIT binary patch literal 45997 zcmbrm1ymee(=G}zJTQ2I5AGoZ3j~)0cXxMpcLtZ>?hxEv0t^x~1otplaCbXRzW4k7 z|37Elb?#kvv1YNmd%Ab++SRqI>Us8r%1Vo&A-_e2gM&l+@>y6O4(|CZ@F7Ng2Anaw z=8gvby)Y4w5`crNjz+oDdkGxFJIIR(!c~kCZUYCT2A}1n;NaY;;NW}%;oxq7Q@(%T z;GEvU!R_e6!F^1IgTu2;Z;|5xE+E-{R&#)ZL&19bz{91c;{zvO=}U+S!#zIzWw#f7 zg@dcf{UR)&=sLfbf#8b$!>^&;V&&r&_sU$y@7Bk?+OfH<`t~8FwDuGuJ%R86E=CDW z|A7=r6EX}$j4#iGF$4tN;vGi0w%&>7$-y4;1x0MeOz}l1&e~=8PR|@wR9sZ196Oa1 zwC)RT12F^<$pT}%wwKFv2+h=Y;`Xoa2?Oi^pF~ zcw9<5Z&Nh>^#HImi2C2HzfVRKNe4^+Z9Ux~LL5rM88q}wc?G%%{#QrGWWwg|xJ6{# z)puAxpX)zB$BmVN_aTvu=~WaRW$Mj_-%R54-jzR8J4xk4qHTP{$MSoUP$mje`q9VW zi=kY-{f~9-@k<1G61#bPk#ykV8|=D<2J`y)va+(PtE-c}{_V$Vh3+s|8hj!3c*{*K*?!#ni zAoMubmqqu71db0EgG{Wo(M@_=_FaOPFE;E^wu+~3FF_q8IWEiaA80K1F7_9$w5s_D z$@Ff}#B=z_kZJwHEBNlEGI>RMi27VIy8 zJQwcGf)i;=19yyaC^g>^+#Yz9whwU@9unFTxUkYSW!LbDXjaawaoDbQztqss@bK`U zvA4En61l66NsiOA+WPG{^ofq0mX>W|fp79AzDEl0aC39hj~T|wrvWm<*lVU%g=(m)L;nPdlg@!apdg}NbY!Ljm&i!;%xrolCQ-OK zHBJ9w&M4$3EgYoE6>3^@-p`LV@`)_=_HNu=pKfn&X!MK*w8|eRc5(ts^X`VJrPEfE*zlBJ_Lt4eLQC&8&u-wA99xk`cb8Za-26m~TuHoY?IFS;>*FGfjRb$)+9A)nVgN8z(!g&^Qlp<_rdvmqaGMR1O_bp|$ zFcrJYgC$mzQQhXRU%#I8!}au06|d>0eQN?E2oUir6)j^hy_nL~&n~T9)67P=37z~$1@gU0IOyQN`<txH#QF!Fo{FYcOK)v;-Wekst4`+(pNf<-YZ9D zasoZ|*Fna#PTp)m69*kp?9G5H!UhhKe=$>wBq znqDN_ov&zLt%X;PsyAWxlPmvt?|hrdeL|G-&=+E`*v_U}?{x^TG$rUd9E(*H!v+z% z3t{z{Lnb6h^f32SS{0#hDiK)}-c038?nfDwBb?nCS%5MZ)V1B+ZIr(Ev5Ub~FahJm zbki&(RcI?rNlMS@sMeXI)!>_~6W|ACy^_YziCr4EI|kb(onUuixUOxr_No}7b?DDr z;#e*BOK0@iQ=FXo_*`a>Ac5rPz}6pztQYM-ID2tXdWTRaqyiQ8rq_}npY@kw*t#jt zLS)jRm6+QRQWwP4#J*C!O`H%vSw&%CVH##q7)Qkk{0m4}7^XoHO(Pl-(smq|EEFLG za_LrDB<({-by+mMEXsJdd%H-8>2dR@t70__xU;1tq6Fz4rP0yRM(ZUyIy!$zF)^{8 z{L<3W!^TIY7cOFqxI^URgwuWzJwya zVnato6{b)%wXjH=w&NhGNq4f13X^K_l6j8usP9ya1kU`b^8q4E*!yKMlm)3aR{I&b zK(d}q2MS1LXdsZ%fQ(m=NxHecZ)}s!>sxe4lW|r z;0oWh<%X|SDORWfc&G}OiSpT6KH#KYtC5#z$U{Z$p~VpLcj?ayDV0N8VrG+S+HrqG zRU&fTe+|kt!h2gzXHc~hO(hi&t4qv<62Rjz4r(T!20K(={H|tC5u#A#_38o}68<8w zkURb!M9)L__8bg)s0Za?Xd4!bL!}HWLId2=IkLmVV~sc|@?MeCeNWExxVKV6Sa3N@ zmWRgDULis3qjLQxzq^G##vY;IxGxp(;D9^KifBP%@4IhVJ{9BMyet+5ygL~^*}dws zSe)4KEcVwnzKSlrfyfc^(2?ff#lnQ!(K{1O+qFhUX4y->N#P#)28M?f+5CpyrrNtX zJ2&9J7pZSzig{IFxzi-1^IYpgQ&bJ2d49zauU_dhi-AImj?VE*1Dl`^3jGY~bUQ873_5}= z{yYKg-JI{9?6!McdU|>WH$h1FJSb_|YTI(fodTdU21P^)ziJVJAf<)CI;Mmcc*Hf1 zTQI}n-C&(pg*DDM^$?eL!W5n+t09rRl+xxexT~^h>$8pdENn~Ax zm2%i9Bvxl2%pcT)##vycMTOqM77z#PbTyAdmr@yNpx5j56j*R9)^i4a^ThKR0 z3n6XyBB>6(^mi}0AoOEJL*-Bsg()_+_X59LTY_KVxyT$l-3@aMP^I*~+3!(H&MK<$ zC9{%(a55;Ipg1q6*7IJ}lS$Wf5g>y#;py#9Ny#dS)8hUwjTC< zb994ek3Qz~W@8AS>&bBLxdw4dOzCaN#8#M_FvJRkf2=X8v5t45Teh>;=0C=Mh!Ij} zzEF|-3&Dj6TD$w_m=PhW?UVOTI*x@RHD-LO*U#R=*icVVtWBIOUWmxuP^*@t%hEx( zSpn5I`I*_-Uuo^{Tl5TQ{#>yL1CB4+%F;47cYdDKz`(!?_2}fpT!p5+vhwiybW3kX zpUa!_WDr=!=}Suha}DQ( zHSLE6@2D(4V!SOsi;FozY(_G~lPDb4BJEH%t3R<(_6*X}^PcW5fIV(#A)trf-UOyN z7~RTRX)T@Fw2?P-qfTDHaH!)=5S2|DKjwK&C!kw|A+gg|bn@4&zmeAN@IL9_bopTq z;-qBMDr;ZcWID)B7Dh*_GTX8~*5R^#*R&m0|GCtqU%g`C!p(itfcUbn7{d2>=^{d( zx6ROVO(&up$_0*Mz~J~DL_fcN*xR^*w;=tPU*7WGZklI!NUN9%l2;SaOWE*gLtb6G zqV7W;I7$h&7ytECOT|HW(Qz}CH@W|%w-=RgTUnc6Sv{}WifFPQ!{+@fho;Lo6rgtZpz`wpDnc$}Tfeu6o$s;0)}t@QC}Bm+ znwYU!4owV+5b@3LWK2w>#SlC^JP8oT9yt?UVp39gqfH5a9hI~DpUF8FYH4)T=;6_! zkRTzS0?62n<$1iw&{|yF*9q254Xdi;({HAs=+U7>7hSXD&KKKib8qIjqBv}3CZ}>< zA!hM9%ponKesw8y|(uDjP!I_AsneC@oVKzL1u!`gsvlp77rJG zKJ5vTnQu_VU4_@MH&R9pSLfWBuV*xew~;uqZy;+A)DLaA`$4}X-q$(|V;`rvXbDq? z)N8oh?J)O<4-VQ21(iRAWr3Sv<;Y=S7laL&oE{w?=x!yVi zvpPMMEa^x17x5k+R#lZ0bI~K3meMq}Ig!Zqb|n6PN`uyX8;xR4mkDCL2P z(_&~Ws6OG@jf-jaecK_o{-bx|6L8GBMI*9kKA~(RI-Aj1%wUjP6gF8iDoi*L zRPO6JXr<(o*kU?L6x~b1trLYUCR9mKGz9XF4mM~q9Oy7qXlEpI zd3Wc@RoGM#?p8FiDkTNDls309A#X36U#Mg-x#Q5W_D4D8g8;206=gIMw$X{okuSVz z?RwXVV@h&33GrZ0^~4(=Gb}K#dmoS37I(=}5mBYIMvc5%;L&Yy(+e`#sj|}zoVAwF z8hx6TWK&b5*iVH-`e2*73br-7_YDxf^hEcI9}79|rUs#*q3rDJ?(H2_RT!*5HWaK! zNJt1J_~sz5`zX%ZvwIpmz*;WVbSwA>K(I(hy|msw{NKeNrK z)~&{lg2l;yJIn}ey87s}Tm&Pz&b=^>-q@Xgpbh?`Vn%YuET^M$`1>r|CZUNL_8qEb zxzMUZsAujTyuFNqA@FTUFIFyi`xKv5j;9Ri6OI>-VPWzFo6$kshQ4O(PU>3*Xhg_Z z0Kzu3$FrEzNd_7_qmRe#Y_h?Uw@I$9C)HZnnAKTx7$`r|DnsI2tl)UTM8+JD5$fvT zz&iY5lytfE+lz7MhW@=a&shiK(H;V*Ntz2HsPh1JadGu+@O$DZURImy8&)3@jyc(E zzp1FDpYEI=rxO!Zc;ul(Hp||6`Mtn`V#&-|=zKTTY`(xm7-!zHaX=~TdC8)Ig@lOb z<95oD$K<`P4aeX)dy~iZ(TmT}h$QSPI?DB;D9Gp1*Q209CbgsGqxoP0y^6kpLC(^~ z#s)1d?a`)2qjhL~GMCSTr>Ll?larI3ot>#^F}}WLokRFz#L)t$9Zc8+95vLR!fLYK zlL{o(Cwe2J!aQ7DZ!AbA6^4dv7MtYej$IY_`F%rmmayQMI3BuUL#&wfKRVBYvH*vn zyUGi%Cnl}!pN;MbQgM=s9TR(tV>co50Dowxz#69jaXOTotWsd(6QvD%h^(Eu5Ik+PsaPPyTB3KESqc z0Gk5hs)|)lb*4ee zHV-W*--=Ih9{CmOdel`~F2Pf_1$_*I`C)y#eXjX89F~VGiYN_X=mC;gbX-sf(swjs z9_rIx4r55As*;Kd9ww%Koyc7*xMJ_F-Tc-X3+%lUM+>qUDvVbgxH&7J*CAr%Eo>i} z(`&622X#7MW?~;VDBtDk3%|S#iPdQtcc#>=b}G_yXoWlG-<2xLk;CJ&ok5p)Xt=Pg zugLMKsX-UHdtP+(fPL(6{L;%2tW{qPl=m!VDl&Mz=BKAGV7=inTG!6>obX{Ce^9&+ z7P&Y$I98!-Y;3k?wI*ZQO}1p5oU^^X1*{;1*%xtMJCn3iM?t`g;OfdL80I!k$DBpd zD;*-((_A5t^;IW6E>lZQDN^IJwg3wQp?uUB_E#p}Y~ty_d2dd=ur`6WILEhP)koRF z%$*$aT)4!ltxh4cJDDaLX?}aCqzIpi%SwR46Wc+}ERo{jAxYFHPguUC*zv__7N^BB ziMH#@%Xg|TohRoIgXusBP1m6dY50GhKWniK!Z~o@ly2$}ZuYPs9Z`)^aPHWhw*uky-~sCwu^gF8%v#kdbRFAa zai;HJuZm*@=OdG77&NwxT^~EFKV*%1C@&6tu!iSLM`@3M((B6}+=J(w*!)}>x-c*1 z*s@kM2HT<)z3?tDT}3h4wO~h&X;x{k^?z?(WF@xX+6Q5I7=VlagU*DR@2$Akm)MVvu!~`Z4N} zt(R*NmgG~UrJy*h-CJt~duu_LR4`w*xhU(s=^r->~lP zUK1-TAgpG1MvAYv+|wh}wxZp!yL{G@D!cFnB}qxKK2pSayRTf2BtnU|jLYiBVAFd1 z`Wfm#qo*qC2Rh?f2#eU>Uhio zp1od9PHc)Ajux>*&;p17Fa6M@mQ1D1qW8c}WHfbjI=jh2SDuB-fdZP&)x0Ws58kUl zk?-RnK3B)UbXApT7tzzv_M#HLMG=$Avhe82Dad=w&pXYCRWRvf)w>BR=iVo*5C^T) zJRbdda~d4|HbjZwlDMqI`{xqL0~)Q^Fmog^hx5g=tCnKIRA!jf`PWzNw#R&Bi#pm0 zGh=FVjm&4&wo&fF5)BRuD`iMV^-B#$S?V5JDwq3wh;`M}31?-Rs;=gu?6sKyp1^`| z{P}=H$J1%5k5TIR?I&8928c!dysxuxUK4&ehB+}Yv9`SYvun`<{HXPSAT%_zf`S5| zjtm$#P(`m%2!oVMFrT@_6fXwN*h9%z8Zrgu78W|Zznua_$Lcz4c&4gMg^ zfZshHOdUN|9dBm_J%&WONIPW}y|uC&=q>|Lp!aZohT$Mo5iCw#FE6h(7_5XlHB9Mn zMsak}#%Gp+msk4?8xJ??bIVPb$9qI!LQP4Us{;X-q~Tu-R-X!CAH5vJ*f~Bl-t9HX zG%j@OwBXCD&yciFm>T>ArwsXHlai=gMmn?p+y6LB|UXiHX6dLxue(NIu& z8o-!MP4=kvC+iT-Sy24L{__JBg)meVH{&E9qi72ioV#r;rnG0jQg2I_MG5i%qd_El zj(p_^z&Y=>0o3R?5z{Cdd*l1+36_OO7PzduxcGZ;t{RaX+V?I!01)as{zEu>n$OJ= z^vb5AEk^Zdc-do9#{n(x>{~~x`-)=+<7#8{M~=>kvGRJ-@ns=a*?|z zf?YKE{0TV&N^i?on4jLjcIaNxU` zBipuQQwr}t7~-#xe{i-Z6!3qC5dnDc3C#O{aUt*@81X-M2>$O#qZOTx=m13hk8S~V{;MSm-Tw4nR|8+h zJ!uu&&K>=qUVu*Z{3xF6MkIY%=${^eTmSz)IvBCMyquAdaU?Jq4H-2I4Taz|0}v90 zf-L{`_h_oN_UZWfkqA#WH$Kz-2RFTiBj+d^F=ax+6rh3uV|R0N^YioL<>l?@=zxcZ_alP1edbcWzP>gxF}WNhF9qi16@w8@ z0+8B{|B76=F>%3l;PrAEcBKNu$D$77EXtvQ0X~?FjO=aXs|Lo5g@pwI3Ekx@s7f_E z4GkhZAkN_6pes4qa{;J|^#YPno9)P)t&*Vl4hlIVZz@-(c9BQh_C)rKPK%wz=wQ8# znzWZ)ey_(@V}^ygC8;$&-ADZ$a?ncK;k#U^vB5$J)}k2TF^jR&Z^WQ#vox^hDo{WJ z|4#_?dV3&`GpMqp!rSK8@M>OPftTE&BN`g611{>VuBE2@$(mI9a5|6TE7I($Bc%mr zD?Ufd;~PdGwTH&ZDBBcQd?WIGGe5aVh?A8Z`*nd2oEe`;oR&Pi0{Un8WE3#Kih#&U z{PDs@EN>jLX*=9AAmY%3Vdoj<#?Y@~Il6$<{Mgi5_(DvSk57L%P$uyMJNsY|cQkRx zX$Ggy_Hm)4h|&~-^`_SR0V4D4xP&*h*>R;#&l1dvhc>gwum-@YMY0c9-^$S+8h@}}Z@ zcTm#vhVZYq&$FYiI@XGGT@fs0!Yko(R-mIS5+ptiKt(@;57(xk?fU3AsbXdgP=XD zy!NbIrE_iWO_!;@Xb4Hte2SbP9_0yUB5h%F|Dil*-&6E2p@l7a)*>gN=XG5ebtb49U5_f z`4@Z+bD@%4@&2PKrIk^WnGNLl4*e>5SW#WWT5DBG3*MYw6{<$FY%iTkYLhC-FolB< zx5`x43Xc_D#Wc%mst3#DFC59O6*6fb;9(ZFz^@?|F=-zcZeXWx4}7alvYg$)!XIAQ z`;BuD>5+L~uLXogU*5y6ND9=u!>RIU;?pH5qlYh7qT zDB}4&)YZ)FTM4(gtZYnlG&%)0IeA`AjzB}|T5U%MFYrbKDcHh-#-Apja#rlBkOSdc ze(ozIG*1fs*(Gju-ilQuwD&u8@ZGe@sL;~yk=7_;KF1vfVL}}}*BYXB2{7-9`KR2P zgoy@7!lyc-pFQmCLb@FR4cOt>9yYS~(868F;wKsqv7b zur%wfW{w?_WZ`}DgyNOJ9l9fZ(Z-`XogR-M?%Sn)OKQ)W7x1{{E4h0SOwtH?+C;2f z=Ka?B!Nusqjq^WKATv`lLM90lQ`{Aw4Fl~auF{$<0-UyZGq@*-~OfOy4d+2^u<~c6? zYav>gNWo4~@#TEFFuoCYHD|^p_1G{oLmV})+3I_S2pMTMI#}@chM*A_vl(OFGPlhd zEPpbW^#a`H;I^LbfhJcarfly5TQ5Ea0r(if=(->`4zJ_5^74vR8|Rp?CIfjrvvy+Z z9Qnv1m&T=Tf>aG$H>wtsq^S?=Hb|2FaXPa%%aZipra2O(IJBi`ZyKXsZN|=kpPkUx z?+@7&B;9plz0K(!ByWq;q`1?qHqlo%A_B|MlQ*RX=XFvot$-fw`Na#k(zyda^sT-yhLlc`PNuM9LgP4uGCRQnHD*JT!i@bQN{R zvq7W`cP<2n5}`qq`zWyjB-~3K&T0etGPcD)2E@K zpgBw1i`}7s5mWYZLA<5rw;1NnfW0$E)FoeSzmICVF@$hJv7c2h^VQeF{S4}!4_?I! z&>NE-12PCC0#1y~a|>?1&+FHL{IvHN7N7cL{k%JO)cD zUlyLMo)6e-<^2RrO{xXLigkN@KH)*~x*IT}d|3b3{+j!i^P02(K|1(dt>#+hoCmr| z$SgR#wB2E_x}Pr->F7tfud@V5=iuwzNkL2&(WQVnX|j{pmW?U+HJo|WC-1{-4_Sc( zO-d0$79XpXPb~LiUv$z>K6W-dza47kpA_;o*b12t=9yjcnZ3fd0;z;utuTiOPA0lh z=g0?+#J2vrggG4as{DD|zY-?8r0ZfzWuB9|h+K`#2T!<_ zmek|zG&n={4TI~d=b|wZOi}k>D7l=q|J>WU)Q@EC(*Ieyyj&wQ1Byh#z@teH}s$qKGdn61yY{6s0d_9#I+VDgJH*GP3D#P_UE>)(~tk!w9 zjoAK)PoNO)5~v&&Vc0uhQB<+=I2-0lfT$>&xu|8i+WeAKve^Sdzi-miENoJ55=?dY zoVHKMMR<6q4tis8^Gt?&I^u^$=IXh?+A2EH??@acqbROLdR2Kl^4P)lT}OyHOU0T$ zDuEh{$UCTwfe#rcxTInYQ|wh9MCkSWju}UuB;@MV`h@voTjss(o@BrVVRWUXE< zS(=*#`4fb35gnU?E7I@L(Ipz+WcoL^gFS z)LEjh&>p8}iQ~x#9#{mrK#bsiPu189``brpp^E4x<^QxN`TG zkMoT-(>vW4=0{iW5*BFR%+Z0yelWMd_MG?X@>*Win)XY|L4g! zA!(fW3#Zc3jf>$DLmQzX{S>-*s!luIY%AG_bFY&!G;FWddxz6K34a{ds@UQ_-%`F@ zmTqN^)aV$R>leoVFT@s$6vmH=t!nvGN|2#)hOIbpOtnQE**!^EbB;cbRUh+f%Q)M{ z{SQ{9F3mPijNuFaJo9US+7J(pswLp|=sG3uvz+s}+WdsR^84raqtAHEk>aWwJ*hfZ zyId4yUU;tyMR>R?e;=+||624>8a$U_sN?AZziY&!*&e4GX^-OcUnGPK!AkzC{e9A^Ix z&0yfyG{HejEn=^nmo1}UaNs=CvG`6Vgd}PpvDA1aoVnOa_EoTz1jy&o+?C&(GGB_g z;m_GKt1yNN<%?n4YjE%f+zH18XZPQbZ`Q z2h!hbFxa#TCH*C-cyhd^>MVYN>#>`O?#NEdcpA|Yseo500UD#H;kDnvjQ>c^%}n`; zfY@{KA*|u1t7)iU1Bcy&NyoP3YjRwyhAehc{O10O^T@C5x4Qd{OCMrhL9^mhzagP= zh(VtV=IQ>j3;p&|q(F`9x@(n%%Pq)Wh{jb`^+O_*sxXt&1?QMvgwPql&E>TeN=xW? zw0GXV)tR$2=%vI=!OsfPQIlJr3S0Cc({U=RBx98Cj zibZv{cundipNmi{k7YrS78!P>-l_OG`PZ1OKUt8{=SZ7|(tHc!6i29a_!YUr=CQNX zc3Bl*YV}0mWi{kHOT|BLsh6X!SM|_^qtp|JIj>jz&E<`5$VKi7yAO?|PFAeuKi*~Y zzT47n(`36aj#plJK_L1@AtOA!!|S_Oy2DvY)7?#>aZx1VuWuaD@&@cp#jmK@Qw>NjalMCOB~#ioZHX&HJI{^4O21fVnfHG z@$*XBAyL~Uys*viCc1IdQbR)^b-Hp`0k9bH>Hz$N-M1n(ic>UxgWfN5vHW8QSRTj3 zc#NpOw)7c~|1xu*?HUDt(BY^o6C`mA0 z46!kh>N4R|MGPY?jMB*+MP$ZJX(k?p=$=J*CSFx!CLW$1@D^#n{U{5}j#6)y#@PyL zvHJ|MGQvI6cc+y@<77athDk2dD?enRN?*!T7i$KqP_EDUU6u93tjfO?oKHki8`&X0 zTqpD|2r*rL3C>F+4ng3f9B0QB*tGID_>zoIOR-KwxBufIk)362gEa zBn>48gCoM4PW&WRJ8H}{=M32R_;jLZD|YmkBLldc`&i!Xp&)x4oG_BXMi?2zq_s}a zLlrA}=^(9~`FYN-I|M|X)hj3F(t+%9j**rP>%h7LVxTi!M3ggmEkZdGrtms4BEWnX z+p2HhqmyD%6XI&!^}W54!0Gt8%YD_ljRm?2!P&GcSVZ#-^9`F<#okgPMA8oWHEIa)2KkV%@oB3SKXDoCu{?A8RVI)-in zYp8z^;C^)%fi~{hSPU<_+{K3$-isZmN7RGAHnM{F_Fa{D?C2E=U3NkInuov{E(z-dAsjSt-Y3}zA}kw=IeA1MD2SX$Khyi znWcnHJFFiTSZoH=4DHSliy%4~Cth3TuCljBK{Zn98-{}+Y(RxZho_A8)w9R76u9&t z*Sgo6@oGvdl%Nn5Aai9t7LSXzM3!jHRF9bN25JLAL5IjcpiKD?1U$vA9dFM`3DZ(} zD4E_!dHLrLia5qi#U#^&17PcxDgi?w(T4(9dyF ze0>7ywVXWvoKh7Gt`Ik$s$+Xc)E^NBABJFVbaN2}*2fY5tZuaAuD_!W7gbVq~; zM`zWofu$LiVmw(dtN5^jG%^|!VY2_6Y@+z2KygydOdbOthcAC?VgLRMhLY&*z&jYo zAINblxdLq`T?sgM~HQTojOU_jh9doJY#eCI@0~2o^063qqY*UX?{gNGYb&?YkA-?lZsZ zpNHVD*~rn-(YEdv`bCis_gc8r+M^7EjyE@PIWMp28>`FFY5a#!{05O4(& zGN|8YV_Iv<7nWPwl+yY-Gz@o&Y&~eHfZjd^x5o}DlBzMP6OlgeHLM&#?tQdwX5TGN{)r_{_B?>eq#P7s}g%}&%a`^ zG}BaA?{`NI{-~eSpQJcu?Lq>N8)ji$ed>lN2;mx>7JYPg7*Tdi$bsC3s~DTttFJDW zuwv^}msC_RQBXil!(BOqHR~Wl)C@22G7g*zM0gg3*AW$D6?gV_tgN<39TBX(y0)X< zN35Z#(8A*8TW*@5imq)c9vsQPA+veTNrQv*^d)tJUuKpQd1!<+w_z}(Q9d=I;2B6*3qAt%uxo zU$?lLDYAVP3*#(7pk~&7Fg$-nL3@FiCU&r5f4XiO9H0*6a+>z^=rnj2mcx3Cb1CHT z_6$t?_AOoTr{m+}F6uJ%VGlPqN=nM@`4G`Wt5vCen_^t%d8pa{X&aRq5){QARCGA|BVe0&Vo(|B9T|!H&xh)*dwHRnFB%Z+FmMMt>g^6 zyDFXm>I1yZIC7LzGI9f|v&o0gb)dkO#vuQm9Ipk?d$dMigJM)fM47qIIjNZp3C9UVQja(jcwv+K^!;9;63;}Wq zbUEW&m5b7!r9({mDGQLqWJU%$)(+B=y@Y#D| zK6MVvoJ#_(d)W4sd*sA#-)y<|C;|Jg9(MMR{=6uFL7_#OEzWp$-Al|Lm(UPHf9(<= zWI!%K*vB2Oa34-ghm;mvNPwJlbFp_L&4mYmwT{sFUPp%M!2mp(8u<+XrF#8@0LeR= zso00G@M^@I&DGUazyvuP3k#`w*x4B@O-*SS86zPOfD4cO`gLN4ys+hxcMgF0eiie- zIU3Ca>S?V@ZX;a2np zq^WAi$@!s))PBr`+3I53Q9UEemjFk#(kA#TJVH282t+(}Lpf>wJCg!8lToqt|723s z)x~q*gmD6Ugnuc49>?|2)n67JNb!AKU#0|^=n#WH@8BRp?!qBV_$}sZ z4@c+|Zywh3@^spGNPX=nU!NwivuR(Z_3aShw4jcR@gnnDg z*1^ouvaF&aYGA-?h3vOj$Rh2V^x?e}-)8~(0o{3fLPOnso+M|$cL7qtudGOzdpmLC z)URK^037(v`2=zMg`A+QqO7j0rL60)4X%+MLFzTHscuTVI|Io}~LLF<9EQbt5AaL;DRN^uMN}JBu16m&Hr{o4y5 z0isXlQ?t@KuuU!LMGvylxD5~eo0q-*Jqrq=S`Ze+WjHm{f6nVnTFZqRA`9z07J zOIk8Lt(^GbzxpLWXwg(tOG!^B1h&M_l#&VxhYy&o|5exv zf&!l>t_@4@rQQuGlOQ3h`h4y()zqwwE^(Vv|!B#i%+6^L%Xm z4Wx=)TLE9+JZJ7Ikp{3rj@@y0$oosh6vLci6cLLKo7@xA zdOE5a3)woZo0~G5R@Wg&r4K8)V~s0E=4OW=i$j8(#hFUJg@7PnL&hRvxR3QxY^98L z54ZkSz~V3`3kCc{PD65I%ewy?$*GoC4#IckMAV%;-bPwNc^Jn& znYG&k?QoV+?;xjBjO?;o8KcV;<3A+!GVCZ&q*pNfqu$NR?!6|qQHRR7g}`>3gz!jj zk^=O-Ydx8z^Gl-8LV#aB$Oe7`frn?&$JbA&+pqe=Uu_v;K$^_sO3-i~dgC$}iO5+4 z3%BTIN<3$VD`?5gngNo&fDzlXHF^e;(m+UMi=)ab=WwYmOoC|AX2Xu9dSC<3diB@6UT++_ zypV-P9t_DsJxWCu>3NAxi@drqPbK0NoM}CN%0pX)b;(IdvzwZdgT6kwIm36M zSEU-3@r?akMhQA8RW+<2A7dC@Lj9#Mbjd)Wdc8@kO7s27!P|B2#DLBEDY z3`|w>8Z2ckOrG56!_#!e3Y}Nv3L-)NIKzhRh(FN~t_gGpqAa9_p!!-gkh~DPc{+`C zndjThL<024J-jBODW{2I>4#Um4;b)ulEWYqZ@1Sn&GmO-W-*;}-Xd64WulEsZDlra zu{1Ke!AB{HV~wn5Kz0Td8)8{r(?B|0RQjjIk%*;c0)Iz0Dk>^=1SR#jko-j>=;eZ3 zzI+))K}{W?41r9&e}92Op`+c8mFRZ8q?A;{uU`mFRF;xOYR@|aJMV{mwo9;#a8$H1 zJTRGO@o7y$vMAUl17L>U#0~56%lbn?@pwZ+j?_-grwR&jS;o`gfor;JgBJ= z$<-JeALnrL`vw}s*}l>bE+D z^{9kVfF!b)t#@$Vf5ZlQAdK8Cm0ovfZT2qOg3h6mxbj6;7WyqwWr9{Cd_gn-S4A zXsE-%!C|;Pa)s+HDd}V_{)3kSJJy-gX@J70cw(XnfzqEptm zM%;H8W->o3?h^4z*Yh~QNW)iul}oq&b!{E0VaXnE2%nBmiT2h#FA(1Q>c82=bE}mD zk?7ZpPBfxH>UH%$8pO!6$8DC8BzJ5lvrd8j?4cc(tONuEaPjf_KQsUF=hrVIUj9MM z{{EAC(1Z8=50GDNG!iiFJ20Uq_QJpV3qLER`-9dc92}y+bvqf*$Kv|)alM`bS2j(x zPkJ5b7HG?@wxT>)lGl`TC1D8Sr4+TVd&T2AZK8+$U_}WJ7K(gQC#^O{Ktn9AM{a8S zbGHQguEG!U6UYH>o$Ak}M1`ogoC-q1-0+MN4*rKDJqKN&D-kh2;(Apjd1!syzmvvl z(hIaF7S4jD!fZs%kE{o(suRTWChLY@g69un4#XBtB1e5_?4HQvw+HbIUD_o75Ti12 zCtWSAkpV32i`hSa-t^E&J17z*zOboq@$#zsY9Zn2S-WY`po`0`nR^=-`~zlhI2YGp zh66#IasEBBEK<=T(y{yEFrG6m0-i`} z4?$89+-<@J@;TE~W&G1GMzARKbzQVZbGPp8iyZSFY?>pGN@mhJVcRM>1m`@*3oI_Z zG!#6$10Mof3-k_d3T#_L8nC|Yzjp8D zB&mKGk9w6cv{I-uH&v9XQ~4r$fcf6NH{365de$cc$t>Gk)T=I3Rqf2qi_7sOZRpc8Z~(`gYy{^u+(7!ni|oG^dm3pF1lvK%0s1C8V^ zWfnF*mi}PsUu!V7&YXW1WkCE;RZ}x6G?c-qr}m|)+i_s*F)Q-BinizKst-u|rRt6T zEXV|W4)EXbg$WByy?k~WZTx_Ng&!&i*KCG=sORolo}*AaIdwrctjQF`>XrhQV-F3M z2_`OXbJnVk5GAR9*m@AabtiZJSEF>b%N_>C4wsyB7yBsZT$L6c`S}g1HM7@Nf4VJ? zbs=x^Me1iW_$!qcIz^x#g$VmQvv?0a%qMO27K(N=(NzW2Av=JWpSwEk4rvt3XT1*l zUuKhM^$MtGRsw!tom`gup!O zX^j@DV1CvovC{MitUaz3^SLbx8lOG*RYN|hZ)gzA|a)=Eosydstq`Cik^D0*Q`}-4i z%1Li?j7GaVhg+pW+nYtfc0xef@=*p7+IGMmY9pvbtDnlr$#n-4emNSMrvY}}dsEQS z3l?WLK^9e2surTxhDUJ0LY6&%5Hs_f7VbZkBQz+~4OEGbT)MvSS$l{a_=J#x&eq+f z7)7Ly7+-1W4RPxMp$cJsDCkVSu4)dqLn0zDd3v_-GLuQcgv_0lMrN*bq`pdo)*mle9@ukn? z)54;nx-Rx6Gf56qbHt!r>3ai8mtkv zGV5Wg1A5;UQ#%=NT=2z3rKrbhkvB3gD>vH&v-uw-sZ-Z^Mm@%t27&ta_3K^fN@u#k z8f%LjsfQ0-H8di_HxhR8mU77~@tr`wSQ+hOA*;z>0vybx=ofV2j&%dR=FS^vH{Y#u z%?SW>PgO<5?LP8tPAG5^4k4>jz5WaL-C6uUv>L6rSX$<~guDQ4vy1V)wW^B|GWA z!}@|DUB@7aP!!J@8O;Vo!(ZZ-I|K59Z2en>bJcq)na8|F9hH=n@*nvDGnG|4Q!&_n zsG1=mB;*Hm>F*;yK6Qqmls6BVPj&X+c_h9j#Lr*0K)SxN5*stRt3+Y6vF1gLLL%QT z_32XbNi!ic1}b&`mDJK6-#4lNGhks1yf3YDtI={kET;C|G%PnM zDM|AwwQNrV*&ww8>eCIcfQ_#xm*@4 z=IlOoBj>nWxvcd5&8FJuI~+$_`w^$t3Fslu?9-aEvO#!MOIKH2{$fZHwMjv~bWKi< z!tV2>&#+HbRaI6Ck`7Mw$_v&$hR)2%ik+Mr9$w2UVVXd4_5~WQpg%PY}43 zV@;{ha8+z;oE}NIp4c(=kvIz17I{+lX6*fKRNZr@bG88<@iSluu5l7|gMDIZ?VF#j z*lm1q(bw0{-yynggIP~C1h@%}TB{_byiUBP@C?x6C&Zq&dDYrug>NqdA#blNq$oPS zA6(2|S1^4)@=B^P2)}o-!*U9)WG_?F)-W%fjF)gi3Syzfi*?)h+ar^waX*cazm3GgfMx3;p`FQdNR zUbSNtj3NT8Y-$wdlimwWfIUuH)|2ZJvUO}iu}43oPwvha-`bitU}pG0gl+Iv=ckn@ z=%9?Rd#Zgnv9!WWQ+p>$dzLc(mO#9*{RlOY-{|N;NiF-M+>BLmQj3$LG=WHWmK3Px z)^hAL&H>42bev>`g*Az|eU~oGMNXeJ=tt2pUOec!(;IcT)y#UT(2l88HR$XK0&eF!JR?MW4y=}SA8pVJen{yYP=?lFn z$3XzuPamO@zASiqJ8{7Rv(tfh&)7qcNaNV}V6tiVcDCHEp7Q;F4~^$aO1r$iZK|DM z%MPUg#>wH~4C?xVa*MHy`YCHDJdK=BI=SZtQL}={^@Eme$&U_<6!}-0k33Laqt|Lp zDUO@9s21X@nBl*!>VJ){6Eq6!SFFgqRWQf@whRc~mGs9k zyHFOj*2xu|#xouYY}4@@Kp-%Cm&yx)#g1G}L{WItZ?8m%k=PdIgSD{K?}LN88P%(c z!$R8)-oCAh@pYY37mulBy|*TzT6MsXpshy&?)YF28VvjtsqS)j@;Bo@aOu{{qagCx z=0@G5eGFm?DPcNH<0Um|_gSKFw2si5wknAUmGlNwbvXi8_M>;7tlh8X6i}gG~ag2g7Gzds6-t@7$vD2kt}*;s?AWlKJSshZoOy z8}Xa&ty`PjRpyQqlFaH@sz2@1RDn9hax&xf&IBb`V?EYK1dkYnN8` zCQy7;jOuMQB}>C**U1jlHRM5$W{aqaQ1-nHh7Ghg@*nuH@&*g#C*5ADCimPSC6yB- zO+5b1mztF&*Ye0g=YGXEPXySx=ypoB>8o6GSSM?EAFw1I=r`FM1xn>%eF@BC2vMC% z>!8|ct8RaSl85W6PENSWQEVw|TnCo|Z{KZEAIo#czT0IU2>I!II)I#}7x_=G-1b4| zJWub%-kjUEg|6MwIs<%j@7#yN6_wV-qbnsGl%XlGJ*)q?zn5kQ;CryZzzFNI4FMu$ z1%*2V@4nbCO|q{uNk~YDcHk0E@P6;NsRI^V?fRBz`EYnkjz{{}Vf~e0FOyNBAnNgu;y&kh;))4g2Bqo1mRfQve=-F9E-PvSuYlY6J= z;eYwC(>zBy7oi!ydCRxYpw7AdhoG+N`@BM&bmn<=&<^3%CCiecU?;)4qq$4I6bfUf z8$1FN@}+jg6t-X<-Z3HKkzC6BkTE%BoRJIfGb_FniEBQ~opWXakeP9(ZOj9K>v?Gm zENmirxEBm-IQ-Ey%#Hs=9)n^l_+=WGiZa$Xq2f~e(a?c5h??_Q!NC6TbMtUR5dt5c)Dqda z5#hH3g*LhMU-5$5B}%<<$d8f&n+$zcL0r5-nRgV(n*B0rA1v>#E$5pcLz=UXFAaK)ZY@r$-4{t4@AZrtaBI<0t`|#n*d}}K8 zs?1E;x_7EUruT(IoJkJIGrohootry<;ZtqKlaGKHon|ioIz$i*wn#!m_UYP}F0kN_ z{>5z5Gsa}@hF=jp6HJJ^bH8n;_do(Q@wf|w)wjKwzMT;(lvh=iPs-eDYLSyLS8c5R zuPnNb@y&TB?dI>-gXeQ9Y!>`@1#WFRD0Fa#aHqQ%BeN2@Co2(?l?x4*#r5ihTIB?v z@mQC8mYdt_E%q@H?SXI)kU0J+j}?i zyH3N8AF#dVfwc_nKYfwxSm%)h8i88sOUc3RSD+r7N1DtzHKddR6;D0D5M6+I0}mXx zd;F9Mb>|5V*FsNNky45Xumz{PIianfZ@Ejk64*~cT|HtvWHsh(m7!DGpCWsC0|Q1i z+NxVK1UGLka}q_#tEi~BIG%I9n(ln7dQ(B0!4UVZM_z@Wx%ksx)D*fBe%_}a@-xV* zZ!AQJyAtYPLbf=YN&*-rt*g6QTbCNAaix!$%@7zMw~f z_3pzVixHD63<+e&HB2e^xF%|0=o#uv<3 zJKoi7^8}P6O5WKMxXjRR)d$DAPBw1`%Dh^?LABlY&BJok#y&2tevIO$X~+Uht!Vno z*x(B7)|%V*nl!7)O!K6M4geT;tbKV^5j2;Q3ukK}DNBqUtl3APAak6$udDA6>wW5u zmU+q6W_n)JyWL_FL31aM&1d|na-~bJ@R3p%j31o>ew%j$pHS_-(9&Wm*erm12Npdw;Onv3-6s;X-hJR@Rq*0B;|u9VtmkHy#Cj6R=yHM$>=ZCTsvY-%`|y;esLM zONqJS<}l(=1le2g zqGt59m9~~9LGy9vR;}ho11lNkI=}+pT~ln*HDwYfE8V+4RYCiujlZInE2;|&yj_4d zstD)tonN=377hDkV3e13?F8>Pi3{TTJsjK3a&hE$+KV?`397vQgA{c#QcX~hwSI`2 z>EtnU>N74;>_wZ0F`LFRpOl%#^rUpY8-0|l$iR@4QDH!>K=JYY{g0PPq;>9dJ&C+b zq{VhuewR=}+V=b1yG5vz5Ni;xP+SU%-uDt+Kvl-scUOsi*sB!X8;9+99Civj8|z}k zR~D?N7_~F%P9&9-3`u%Bi%@2!!GX^4t8{s^-8a6dXtuZTa|{ZEdwg4bTYBp|MeOHz-0uV9#kxk466j2to`WZ_*y)-xwPcb8m4#^p}ye%Jj16el{%<>{N8*bd4VWJt4ddK5@^9 z3Hyu#cU25AC-@_?@-^x6aDvz5zn8+me!6tDYbmvBx$_g6I|HP00cP8BS#oJ~!iS9{ zCATv%gi-5JTT1MLX`RBRio<&{Li~=qtO6uR`|_rtL4kzaTwK}C2oo{G^%T#`kaX?) z!A2zV$-)Qx8=CPRLKDq7$t;G_)kOq)7SM*n%uHI9U6t-AtlwkTXUwgYUqtfiUT~Xm zW0;<=WIHdG5<+{tLipABwX4Qg+qu_CX^CvNs3nguIaQ^l=q!)QG|XGq15M;(z+H8D z^46Dgy*TdQ|4M3YIn=N}2=z1u9SN_~suU~CP|?xR*;L4)qwKAD(I>FWcMTkMkNOtF8Q1ui$ zZ{*?-UlaE$TwASqYtGsoRp$lL_CeamI+N zm#s~Ll#GOK&9(x^@A&#d?!!h=8fEgf_I6$#o|3ogF4*tSvb68-aw6>Q?Dhw4Nm5^+ z_Y(E0M$h)%`9xrjzpHoR;qLZjb4O&Uv5>tCF;CORD$FWz)GoU&6=kv7S_3bSVNuLa zTqY+^_1WhoO(>_-njA^!GFuctydGo<98-ru*Xa^oykL9jzBR5%cEJJe(_78xc^M3< zd)*-_QCS)@R53B}ADu-7je-K;CeiWU)od0hxT`s&1ahx*=Qt$jL#r06Uz?$D_A-}8 z)td>rbZ)iommARZ<;gBGWd$46!)!?%D81ryv%G+i&O^4!(NV)f?|nyY$9%E;Op*dI z#DMTrweyCC^%ryH$h=&z;d^#=-_64!`biQ;V%t^}*#6nj$f&V2EFx=5gDOo4r{u7F zhaNGP`bU!&BqHAX=>0Wj)8Q#ZTGG77l%&qGs(a43iSbke;Rcol1%|O+f$rT2iw6!h zqqA>KhG6DZQ<8yCH^WPbioU+DrNE$6mc^<=*LmXeCOhhey~>-%0&|hu@>UOvM6|V8 z8a?3O8Y07~GhIg@=(vO$#=NQ5=%9)?>30T(tu>5ZqMEw8x)Te8Lv|W$9w*pi9^lp0b zeImU61O^G7vhu9({2`Wh!snif#Y4ArNVYc9`c_mu(~;9WerFR{@hBsX{!V=EC$A=GTWy?jxm+iVX$^5vvmbmW2OSrVsoIZu0xa5S=4VXv`M8{yRX@!TBPxB$O zwuH|P1}$vEcwWDOTB$UX9ZP^N7iMo9gXQ}#H7(@*$w)~Dhlb2I^s}`|DW5u7OX=u> z{V?BG7Zq*&ssGg4+B#Z#|MMD#8p*R!j#)qaBXIeGN37&g(mJl^c*9!(CL_$c5^c$Y zKTZbOUv~-KInnj0gl%;cl!$z8-#-dXRE%@%eo#&8>yqxQNb#Uw!@SRgQ-qFYW|fGD z1H^&^4?RStMm_pQw!D@(c;Iy%lg%u`{q! z_#V~W^y^#6WeOu~j!Xd<+sWN42Pk}lm2JGhQTu*f)n^CSTJu~j-Wv2g3WP4*N*1yIddsic4c$1u`M51-o%^4)l|DEPZ$*iMay1Wo1dZyt16F5dpFgb z$)(!k6+V!fo}O)g!BIIv&$B033~|6lM~J)tZsYM7XXXB4^Y)M&BrCrnY7%cWaUv|* z%G9!Sq(Exo7$dmi+FK1T-dfg&K`h|G`=Bm8pBLk4a>b-Q_kW+e^(59UFVCxO^oFd- z2%GF~6g8>*qdk5^^~f*q?21!k2RNHHc#EU%6JYJ~<3BmqzN!A>#d}@wp^I5ob4~&I zv5Zn+&E*oR6rH0j4K@rxf!V@^BAbzYzmE84m}`{dGUIQ|2Rso*dvgO{)thH&kz&Xp z*8z2P^;$N~wHS-scIS=B_>d4=?2+qQ;2J1Kobh;89~dYm#s~#qE^9Jc{a|}M0HKDw z_nHTcR@|!w6=CiEKR1ldzJ2U^dF&1X%>1MD!<+cH*l{n$>`2B3OiZceoO!yD@!{e3 zSanpl8#$NJWFMW2oAh*Jk~mcp|8#bCnvrTbf_;#yiYqJvFnZm3J{7BCpzGpvE=+O2 zC#WwuV^~sH?u7fM&DV)LqdA{N#!ue4dQ@2}mM!M7Z)2UW^inrAE-o%IQUe0Xhr7)t zt!G)SNrr30i-EZc{mxXOv9Qn;-`?FF92^{VQ!{a5RUOv8Zhzryx}Bb4m0ucrRIGKZ zXmKrOwC)}JIW(4Nb72>gU-je1j~SDP!m03Gsb2z2OmlBHtYutETe4!uT0eo^Ou$*A zqseh>M+#C>?pd18$)FXV=+;)}>ps7HdRJ_{sy~RA54jIqC!k$z9h-hV3esFTA4r9b zgriNq&IIYuq%B}+p`@DDU$Qs$!xhz?)gDzeSAszm&3sze9>XVoCXQL#4TSbb;n|`c z1#ed@kIkE#o5M$jbgB!qg()&}a>n6MIOcI?)fe4~r_#$h{1ttKQ8WvSS5KM4LOIpG z{h?xB70~W1iT!EB+FEvq!@$Wp?LPpx61u{%pBD%+haw7Qo<-G6%|!qU!Rfcz*V-< z2)1kVT*B?PxTpBmWi0@1zwfKkSqn2WGtH>9d(6yo6B?SD**66$I8qk!G(MV>HN7J! z2?4(opa$lqGw-9}D3G07A5~Fq#$E(>{ypJ|oB*VRl>|^;@Q3N@8v=(h&fwI}IN(7{Vt)US8l`FlHE_8KA6**(ew1Qa-nC@8R)0!U42rF(s`eix zLyLZwla$Qcr~W-v^ujaN*wl1MdSZ@Fz~ZBM;%GnAAY(Vk#n_k` z@{Zu&U+$W&BS=xJMq1Vv6h_a7Rl98pq)|e>TL=IVxa-L+xl25+ap>o>%BZ1bdz}R(y3_VEwKmH7prJNv= zx&D(=I^h zLWKYFU!SZ~r}79(tv5mnP?ox8{X$RG;rz${nG24gR_4a6M9APZUwm&~r^g3l9}HX? zEp#a^Dj}zkrCZ>!J$S4a?jq_FwE>~@c??M51$ul?XofV7b1NQQy^FXAY6jE_9-CeF zP%2J}=*alO1xad9C$zA=_NF&~CbEHpK7kPKR1=&w=cZz zb=h~cZ9X-MmIZNO-wttmKXvQECe1C>YBEdSb^?SSp{zDA&Oh8H=rSbQuqvia;}j@a z^CRjy^KcNW#&ZxK$oR`Z)ANQib&S`KVF4_nEy9Xl&q=6OLFgI2v9 zR-6#C++z(xhpEn63_JgPW2z;iTr}BBC3|mDw9K zR@}wyErVtJKv+SP6Buzl3zuZrLm3W{>~ z??;rjbhWW>`^&C}9XFKXeI42sr_DADwfIzn|FF`pM0TR1F;N@ad-@>99q56LVxvBB z)$;Mw?+l;)C#u@4{jOugzk0VOZTG4z3nBuJisSbHYdRwCO?60BEKLnW;p$Pw--*yI zVJx;mF27(k#hI!*1xg~it-J|61w9YQ7o7wfmmuuvHyk)g*b5~AMHWO}P8;&VuE9JH@ZEfR6;C zL*x1u@j4zQhn4$h1A_4S``CvFylb3hsg-jw0P%}U9m}b-MR}UE*d1G~Tz?f7F9_-G z%jjV@Jhe({Z=y;<4Yp=|Lfq$URpr}(ifl_N$;|K8!DP13p>6QMSD8s#slc<82F_rUs9C>tI3_I!CuUO z5q)Sh=D~#OtE}!7pjE{Z;JajDGe`lg4BM?Sm&NB9#6rmTlV0KO{BI;GMy}<2eth z)PsIUB0v)Ejn8b$Q^snSEW~By)wVle{CE8a5$QCds;Pn2R;Ce-6gAzJaMy)nWBmUd zT`BaF$9zc4!`mY>8dg1CY8%h8&A)@0;r_Le+%E`?MH1uxrO$x zCmYwy7QCufV|Ia-byPoQL%ltWVA&cLwoCMi1iGdq^?1^361s4(k}h;*{m~8EV{8zF zB58d%gU&EuBCW-LEpa|v1!~tuO<~{tKzMsjVt{OUAOMKcsqI%x`kY~2=1ZlVGriJ} zP0UtiWtLoyduL@RGADm+IGo@1v!c4;4$=m>s(}_2Nv&86U9h_64O>T}=grf~PLsBcOu*54)F@bbY5moD$-TI^{?+WTr~c#r*(gdklipFDvZm`tp>vxJjv?`=GShO zPJB~Ofxm9DSTg=r`~66zt4_>56HA;V)2$|ZxSUUPDpH-qeuFVI8!kBET`^j~hcXp) ztrXJROS(r!7vYo9ZQB+pB5G;nNnjO_k?1d8ew2D0pi2dX)c7A#r@j)fgb7o;%xbb0bNz&D`59B-TPv$ z&ysY$K(u25jzA^aaLr*8cFzOz6O2Kg(pW;W1`mF;K;gG4E(Q)x<%GNWt<}Yv8H*== z+4%-<)ZUp{SxnM9rqPSH?FWATX7{Urhs(&>s!+kRa|G zU4|!kEUhg75jICk$w)LlMhGFV)J$NGAGPfTnnZ+uQeD@SCOQs18!lgd9Jz)H8aF8@ z>0q487dmJ$7BO6Z{SE}D?*j($=lzv)?7w{a_CFsYybgXn5Fr6b$Qexl$Gu@& zb^Y&wZJvN1Ae5}BUdIud9Pa$8JJIpeX5!lE=?IbDk1wBJ1uwwz(*s{Vdws)JGVa6q zK`F@3uZS3V>->0KKTpXHU=)NVe@}ny|NX&^vV2`$s6F)1 zlsF}?thfplz8b$_ym?i-SAqkCo{KYp&R5+4qhp~ zrV6t#p4o{k{ATe!j@QNN#>;fP%yct6V-)`6B&s)jLb}e~rb61aR}!O#xppFcMM^Yvysnk~GTu5Q{s8sfA*+8`egm51v+wJz);cm! zy?c$xh|?d2{ppePG*=9f`W|dp!Fs@@E9|=aQB$0{C%+ zz*zn?&8kx*!>8h2>9PUEZem8HBj;#Qa;&ntu?GLGVh0!Z7TA8TYJ8rgofcBB+Bj_{ zl@-${#0_CF53kS^Z__!UIXr+q3mTHtF(y##?3+J!K$djth;my#Q&09%r8u8kkZr=m zC9?0;MKw>Ft}xU7Zdzp*&L%)zj=prS5|+WbBl)?)ZZIrUJ}i?VidciT@^4ij70N@8mr=#b2+>E;_b*%D%{34zl%6@Gi_sG7Oiwk*nsz%JE-o0|eb{#eR$aLL0N}aCW8G@~7?gh2pKV)LBzKOc} zLEFd)zE+4CXqCi@daQ0)KGfzVcs57(t(;XMfmmB-GK}!#UB^9pcgQ#EX888FnO=e=(1gOEoG-6x6{xA8LtCn0J2Mof*)JAq8S(G5UMgSh?r9=dF6;5zZ>RIfn!50*v1`X1{;cMY z`49{)Zub$J#|o?yz8Xx=n@?d^B;!sBK#`}POy=G}jT189u-n$WYYzf5sVaB$H`YhY zSlA9;ajnV#*8Y7xh5^rQDUX$^0XT>vgICS1OHR!9$+bS-tKQkuZeqj`BELo8(M2nV z%Z5&j)~c$Bwlg2wFJ^@FnXH4s#6=37Atv^OL3#9-S%}kG%cQCAt2y&6xeSGaYINu= zbFiUAZ6wh5Cy!Ov$K}_w>L(WM?kVa*TjkNT+NDT6dBbPNC%k_ve+?PLmYbFJVOAYd z@6xF8hy1GN$+3jiz(sUx9nln{eq@+s`R;WB)`>x-idc7Ej{0N`Ds>Nf=oIMEEc|h0 zgRzcp`_EU_vV5;c`7BYI91O4NklHGCVzlDYPU(k-F!&_e6CsR;3RmST;t~}7 zif*g9J!hV@M>O?!h>{~U#jYUMqGs{B?|*-aV~cIL2Jma2?-r_H=h9FF75}}IYb++6 zV|RAv|4d79uC~1Ri&8`U2u6GP)S45qmK`ugT$>G3298oa`h@?2%8IAy&Ho(*&*3-j z!#}9_UtW7ZrtdlfMMC7QGnBlGz_C3$-FrflH)kkGuk;s5{*AZ~{(;=T2z>VXJs}I? ztSY>x5=FSu*v|Yv1HSj;iOYXU!{fWB<49=_@Q2g+dlCQn|3o4H$vAx*IE`l?I-gPh zomre8^OWQW^WZ`fD|hJa)3MC_;_-{SKNezII({W|R2)C4-@B$|NGEEm2w^P$9WAVA z;kG{;VavL*Yq;a;^cP~4CW?i`)Zb#}DC8rhS*+#S90>f6W4C*8vL&tK2qCn^Cj|W4 z?)$SqI!wmbCUqB|T^NXBmJ_D5Tt)c?k}Ffy#fu6n=bqg_rA0;#mX+ys_C7qPIpZ*T zEaFNzFejY(92Y|aRhBUitt&YCDFE7=T;bl)n zC7J@Z@IsAt1qxmJEM4_oePS>*ogUw^@De#*ITtphvNp1Gw+yzWwLN$B0{IeuE6lR)Ir_aSp}8=c8g$IFaj>^VplyfHv7A3qy(&r&!=IvV7{G zR5(=T4Wg9LO0%p1jO8AF`QAvxJyWWX1Z$w?x^fu!Otl<}I`$KqvwqIzfajv6|Bxqt zaAfb>i1I6v828nbTRdrCp-i3pqr^Nhh(i*R&m^(Asao%yMQG-nIv=!X+skvlx3@2X zr-c}3eKp72o!U6}oFHX9_uD}*%c!RqaaFD} zn=r~`IbZI?1v?6IkL+gMHT=IgebvN#1^d`@+VWGblKRl@1B9x4&{E=EPT|_x6p%|Y zEhfhC$Rsd&TGxqWU?gh;2o$Lv=971ac`!LY~2#FKW~Sk2}}Q5;lydgn3l zVKABOGb)B31dUj@y<7t;yq4c=pu3^2%@P0l!udhKbLow-Jz&~;Wv7Z%7e}8cLf#M} zwRyKnGcnExzGn7pPba1cBOy-^_a2eQIxo@KqL(^=`(t=wb4;92xLNah1LjPO{MVP+ zGu;g{xz+R>A{U#b9a7=DTIXEhsoGAa`8F;yH|zr64b%^Levsb(Xp6q=*Djp5NaGBL zKh~+*Ka@1+0~2e@AW`b*Gmh0Lvk}nM4@dcs)>WL)1|u!aV&$njuHa!x91P_Voea$8 z=Minl!l+jN-o?E}WtOVi`Bh#P5w3U8-Q|6?8R?Z&^V0rE1-NCwfZ@re>Od*5;GWOO zMNxV08_ej6YtxW^(^@1V5_prb23_#99SVn5V1%wc^GTa;nx_esRcY8tj+w0%*2*<9 zc$>!Y^tG$iVc;cqa%52xyn5d^buRa_a#-`It}0>yJ6+^MpIHeW3Vythdtq^Pnuu7v zqqM~rk%h!{UuVT7W)O1GoR_eT(oR_|FhTc#Vb}W`m@VKo#u42~b<7~341%o+?>6U1Fdt$M` z8MaPgyw|*hJ76LJE)*pFz%vAW5BId0JaZ;wbmB6yT-_t5fUQ|w8C+qJYmrqIT!Fp$ zZC$G7aN^==CBK2%C{mKAIag&$z)C(xMjYF}xN;6rrw;G`1vdP582AfkXMcPeU;xPY z{}OM&3I1mc`ybRXP{HTl8UI|_#;u>-j?D=?6w}MkQ&fhiKhK*%N@;00GGfnTr$0NH zz@cxX(^e%)fq7{4Z2OY^Iw83rQd(XAQsnt)< z@w1#6VPtc2a-f_LI?%c>F~YuM5ZSzX0=g~Mnip~wjt6XQJ-j}vx#&uT)%Vxg-WYd{ zT5>p!2L6PyaxD9aof^@pte%P?1eVGMm^A8+WO;HhszvL!Gqj}cj*yC=bK(DIj0}l7 zU;>vmY%$ye62Tdk^o}I~1O`~Jz@q9J9Lz+W_V`$5WKV}(UEx~hQbZ{~A5!*o8y)wj z;nFcRwTvgG-HXk%H<|^AGv%P201zM`Khv5V&hu15=y(5a$iuFagkqkPeN(=6&}@Kd zY-844UF{9eE32%_)Jj+aX?J^w?B1>7u!zVwvFy%8vf>jhTxG3|+J-f#$y_NLR|I3k z**Mte!$ASuAlC4i1Q@%tyiFtJ@$5zT(c@8W7a8N}k2LNnI;rs3*RKs#T-7@hS8D)` zP>a0#DMnX*4TRu4Tc=$r`&)lpj2xQ@1-9_NvUsf1)o~`@y~pQV1a(n!3VFS?nHoE^ z)(UGCy;CE8xTfW`7b^&6M2Kv}0=Gw*kbt_kvs=0&K{_Wf&5uP^KT~Quk8MgCDV@`tXtq3jat-nUE=lMgg2{bN!?<^8z zl}|IH`@QO7`bEM_-lQ8Rc*S9P@>fIA{p0?;l9Dnpk7UBvEhEVhlm~ahL_ZK}Z zboH}qQ<|p}KmOa0BMWnecd{YSnm_48T%R?yP!0r}@1?+j%gcWVx+Tg@4oJci=btm3 zl@KpHDV<@vf!Prro0QwzEGAYkqJdl94iy=K$ape1&snn?V`~ z$++lymdoB>1HBw`R3&_jQ|8(qvH16p_9vU}eq(+q^I+x+J1kq|OX}R()q`e-%mFX7 zw;pH@=0m@Yjw*-TvK(C%VPJ<=35vmIO^8ie-AW08c8^euspb;{O%)#+=E$lm%;nO@ z#yw$xWIS|gj0E;5z-Dt~UqbYs-I!v6e+@QT-KGl>#If`=VBI0)bY*rBG=1VIkr(sMMzsZC=R!>Hs!-ow~o5ff46FGtQA zdVmx4KlG~KIs#w48XoM@Y`HFm__S*RpDt1`J}nt2oV=pQFEQKHndgA8MEe&IeqO{m zvlP$y|9`rL|97I`Uqu?klV@h(e0GJNhm2d3`D!resG+T(jxZB<; z=7uz@eP&(B88y9JxBmHhaLcncc)XIOm8mXu#6GAfVBoZG0{EVm7a)tzs34X3Svo9y zDxdyn6C?2yJ3$ym4iZTakUT0a+3|Sh_cPx4n-V;Y-l|UIx$g=E64>mm{eVv8eOA`4 zW|^>3owZKunurT`v+)Rmoq8ojethpYMEQbNQ92%H$~h{Bx*soNYxifr0c(6iz`mjnG4C3C z07%j`B05ms#%wlh4gnlh?xXz$yhL4tyaIVznlfZ_EiKpe3%9(zq;lveW<)?cr%w~J zryhY;jfAclp#DDx1CH_}Yjzw`73^>Wm*(d{dz5F7R}UzPMI|5uY~dJ95g&1FpLCU3 zKLk|*1b&=>N}bKFV1aB9R zw(*#PvJ;+h3uH8=4>`5O_0`|KdYy15m*a(e10bI&twkLJsLb2rc9(4YLyX?oMT#Mq z7RlDeS#Z^?vsYA0XLm-9X3_@=$~OzHtl*a6_NRb{+xEqf+sT-OGf2+kjrS+R?6OX% zGpZKIlO0<%r=I6iKNAD&m)O98ppTr=EM`G)o3+g4;PLCr@SSg(W_o2%dzC@Z5^1+< zmzSr9Yb$vCl|xC$W%jwWx~qEN>Wuy1_Ky61?LZBv%9+iW+V;mDM_heNfM-;*aq)qK zkin}2QlK&IOc2YpzzO2|;mDVF(NMoaM?2KZZ)za)a#D11k}=i?gr%l{Skv#cB%j`; z#A-gh>gao=v(X}Y~Tnep4RGZyPBh!H)**yG7hSa}W=e;=0U(Lq;0(NqRl1%}kI41p_vx&XKR3txIUd2Vz{;jy z3dVHxF`k>J75dc4Vp72q!)9DtqZtR?5~nT8YX^>aiWt3BOW58l4QgG=LvvTc^hGS5 z@J@F~=hu*LGHs>oEdwB`#lAfhMuKoI+gSbMuF0MVq7gJTwk`REm$2=t+h$&47gBjS zpa^T8*NriEwz37jNxe4q+6Ig&Zu|c7#4+eXCeKl;{w3o+n((XFdHi@*WZFiW=@fQs zW_(@j%i6RTgtoBSb@K{kpWuhXkD^H{)kh6h{U^CXWvq>_c$ zf5YX#Jk92^kJ=_2c@JR386E0Gi7YAw4arpV#jOslX~3FUR>s+JXW>RSB*$lQ_dDz4 zh{%-vsJp&rufQe7R-ML`LVwD`tbP*$1{n5Eo3?oBO^uuhi9P#URZ!+h3It{pEOh~U zcGw1OiLYUPWl(0j^+A^)sweA6_R2&JI|cp*ZsQZsZQ*2pD5=%Of;-rH z`{SNTI~;VE3?{nge36f^#7@{(66*X) zwh78sQE-vZ7T8KtiE{3+(fPQ=+-}3hT~H!)$Px?$ty1Zq8jjqQu16mhVt}n!ZSOw^ z4H}B`q@`ZUfa@fj5}1lo5dthq-t1ckoXqvrzMxHy4MbSQwUz^MUxhsi&} zID+_^tomBs$J=R5{)6(@PaVb6eAt<1=@9}f6JM}YcImS`08;|AvvB%{*7CdLi}Dn2 zqQA)-nQp}#y$wACGNx31x%#H9Akzz~IQLaUO?ob#;Bfdtp`0GIBb{y3*LRjG`mT-N zeJ&Dw6K>UL9f7U-)aZaN)a1CamY{aEZ@TeBXBtw#>O3jFi}>$+?!N`Sf93EgrCX3b zqiO#?*7E;nmiOO0?sMuX-~{9TEO#_{*B|td2`Ra?TUOo@)oR(jd{vh-rQEHk7w-_i zW%;yQt^Q=l@y2*%`DR+S%DLgKNr{|q|72KkQR)adS{J1(jRKcao2?g59ItD2-~=;_d;MS&OXW!Dx5 zu2qL0!DI=HW)5NZE|(q*Sl{u^%5*>(Prm&S4C88r^wXgSct%;uYz7Mayb#X};E%O{ zr`3l90VUN9mis7%)_(nd(lh++w2|R$Xtnk`1#|7#m=Yc?>D}zC^^av9f)~H~Kz8{u zbFIT=F%o(v6p1fhU}B1cYwn!(=T!mubHwFUV``-4z*A#Vu?N2-R-Svp zWESpB36D=Yv@%Du@TkP>gL-*;1k`x39UaEnSRD_wPIAy#rhc41!fu2EFdq~fPlo{e zJ$h__&w@O@|D42Ax4Im)HtpCF4k4|k8ZZZtdSx~muL6e3uJE$`TK;ofaJO624 zR-}v(+5f8TI-r`&wzWRTf`VW{lqy~6y_XRNxqyI*(v?vO2uPLQVg*Ji5(K1$kt!WS z?}LQilqy|&Hxk4fJi=W%W#W$LysGG0eRORG18fk-&uOa?o+!US$Pu8;RZ0*5R!e%Yx0OnnaZ2V&nZ#o;D% ztjh%&M9lGm>oDqO-wb;m=VK@i!!)1;b1Jc^qKKQ{(83;-4NX-AzOpX>54S0+_p)la z38T?b$~(uD)Wmj)5p5srz7SsF-fWU4tYXzI z;Z1iu7HIG7v!r!)&3s~o%1qFf8JPpT9>lk z%A8~;-&UOSr9+YclMxH=Y?IQtFwnki?$l)sI)Qr&w>)bg&cmXRpuL$F=hhCcx`9s; zs=_5JZxoDN{dd}$hw9*cn~jMi0o#BUff*BE0z11W5!_wN<<^Z8BmZi+;^;0D6-5kJ zu4VKty)VyPxB}$jGhJ-xImB$Kqq=d)HqJSVl(2MQ?77ahH!)#%V-{gf#GUHn0?Dq# zWURaMfLBgccY_mw`2Oo+x!`$O+}`vD1O5HO4n{pA_>0(bLAc3Dw8-WSJuu3m)VC(e z^I`C*p$`{ZQW9SWy#+>+tslkF0K1xAi+LECXM8rjjWT@}NCkslYu_XkC(uiFAYhC% zp2iQLU*{wp1HI%V8ZbGCVQC>;ry>lfjZc4t`pHt)1MrJHFB@s6|3;8kM0OgubuJvY zM+GOv#f1$KP*C)kTo&F6u{(_*3Up@~aN&SRSA=f^?4}^gyk72W-DOd5W;hy5m3=$8 zS>9DsTdX*w*rvg5R!E40^05)*My^UA>+V(DDpUQ`2EPUX!x59#up4@mW9T*;ev0B z%#PFSA6)vMuqVG2lq+Z1E4lbcUXKN+{SfI_%zkQ06F3jhpmG*6cw#0`G2W+Vcm?}) zX6ao+1q&}Xs{%*ZXS1XF-#V}|Ua_doUZ<(U4<;qWC}|$bi$CU++eF1XI{UlUl}GbTZ|!Q?PgbPq2oZY+038;m<}S9o4ue$V zyVWPqo<*|0>h6iH-Ysj=Kr=RvfMQdGI(houFLQ$g zS7wx7fW-pEw>t^U%XbX!g@z)X*qS^8${l!AA8Y9u8=Fi%so%69n)V&&%atCAm}Het zM4 z9k8FP>eVLMiAJJAVhYH?48m2UraiDX3Yh1YGrjJ1fhqsu%cBu;FB_h>omaYYoimM3 z_b7}H8dsyHDJ++jHCdY8cXe=#`GdVX@1xz|TNq&bDL~L}lMIvRKLC&s>@U&9<`B&e zuLEq~c32Wr@|gHsVm#FgxkRy}Y`yjm%=15&Nl>amO;j;$qr+mQ+n~>!Zpqs!Gj~cb^K)E6CBPqN& z!ZioxaXR zs#5N_5+}GQh)-S`CqeiiSa-THxXf>+Cq>7IiG)xnU@&K!{ifu;t9eyte# zs7aqr{O~3A_G7y3=QSLguhOPDSRsCG`(V-ezLrfYyAWhezkUQ1VoPOYR#iFylL?gG zWVSpYEqZ)EIe5gYn)Vy8+C)}ph-gCzv8CcMJrZkrd$+!&+#L04BalplM?u_q2 z*QqFLcJ_n-uf5x#n;R}LEvw3z>awa{p7RD|H5y)d`SnE!)23HCq%A>;!#wa_p>GsU z5AqfXr*N*irLiZ6q&{js+Xne)|9I-ozKD^46B67sPg;Ovo!?DMYzl)Boz_g~@^fQA z3q$_JJgGCEktps4L~$7gawzWAt`Dm%8ZP&~@0z4@qYlK>$5n0)4zTq47hlxj^LFLd zm<}P+ble<9xQJ$jt5=fawbB*2Bq;`Amd!hZ*WgJc$k8F$Nwq)uKwl}3QdVcO3q7Ie zw2}4vgp`Ej_T8CFV8vIYc0|FFfD8b`z=;05{cSzxgalq;6Qsn2fr^{JBB8 zLG&gL@H>D>s}T$}nq z+DdO$3GT&fdxVp+)z+l-2*FENYfkMo=|0E|co7(GgZk7hCyW;W!1e}>3l}S;3fAdB zCw<|G%wk};0j5st<+O7DIsQ1D6an;~ps{AqjViY~-#VQl0ZWRAt4-UrTohn{ zG9(Yow)@tg`hb5TCn$mkAI-5ejL6Uvm=BMfLH>={$-edH-T4##u_I>w z*5v)`?ElwQm%q*@{1x2r&8_wuwDK1a&_4r_f7Od}tSJIJ6aNv(L2L8XF-IZrTp1YN ze62VtSTrv7rXG=PIzNvz>-@*Ba~}})3|K4!CR(0X0cov3vFV$=G0+u(6JaD_Ea<@NI!5A?$f;w#}g0b)l^iy_8k5f8CCFds0}?R>$l z^n~^2&DPUQ?cLp}5zOmhag@Y6v0xd=7QurAlRKPS0lLYxEkM}VqNtp#F385gJ`!_* z_{C(;3T)G9*E{^+!AF>|DHT&?1<`83v9FG^R6a*Z(k=#BPf{mAGCp(p{1%8;Atohi zF{Q;mOyKKBRI8LDB*>sZJp#)C2n8$8YZ9Q*gVbC3j?Rthr=A{++swCa(B<)DR21p)u96rXo%e3gIz@#z+*9m?9re}~!tPL#oovF;m?8}LWh{-&D z1lWl2fK^EHFbPOj$y$q=;GvRQu*`rL783rlXLn}|etf|we9h9P5wk6P9ay*%W;sAU zc85uD@d1~=(e3!Q#RbVw?Q{5kXFEx21&kbms8e@E%kIyBS&&gvNC}RoDA@e|f~jvK zXc-V3Bc+GYs(%Ygn3;f0E4dt6J_!_ZwxBrKI^h-58;XDb|3BDyzmj%`9Cyu`|6fzU z-zCrAg7$wWO7d?zeSBW%JKf8rticXjX1eVI$0EOQBqMy$}{rXghmXUusPTQiXJ-!sw&&R`s}Khn9$J904;CSqDM~$bKs4z>+5sYM zvd&60Nq?D%%*&vo3^FPMj{t`lti^mRpDHZREmw&V1*`B8W8*+rmHfdFi*F9A;nm$oO; zFL-wtNoBUCvbMfH^pq_y8tOvoy&BMr0T*nU$38zrAU|pdLopG^TCOOz*eaipawVj8 z?qRq48=w9=M?CKP&4&K9i?(-dY{UXDZcKcdC?Ls{;+4c0FElbNfi!;CrJ(?TNU60-8@O zgY`7M$;z*=%IP>ql<&-p>{4Q;8XMv0LwO(&yu;W!cf0fYaYA}h!RyQlnI(4CI6Z+7 zxOfFdTs;S=3s}aPl3MMKl^p5YGB>DY;=YU5rBvmm>EJw}bxh6er4^vC4J-sc zw9D@tDFhtMC`c9l$>R|I+x6~|gof-VzJh4#a~+PYXcDm-kI+#yt=NuB_c~|{U1PRt zVWWVqS7|&AHD|^m{4}#`793lk(f0H!P606v7Y{Do1+1B8DH+G^3xi9N{U=bUx{`%q zm2|Jb=moOa%QFS9M_y<-3iDD!13IWKM@&Ty0|IySY?8`s8r))26DEY2=xgg>odrPZ z=@nsiVReU8f#3xm)EEd-Dh`a+L}Su@eAT`COWQ_{Me>EGjZS&=bHTA-G>+OjeU0aB zIL)&kR748-d4L@cU{=TC0o5C-odt(Tb0f!k|BK^j7cRN;F4MDA0vpRIjD9@8;7U)B zT%v(x5>!r;bIiafQ)Ppp)CRv099KHtY=fA28@rx7x@tRIB_lwz4dBPoNHMuK?DOzN zpNL5~L4RX5u9{H|fMZ*A8923xjE=KRkEpYcNq+)KczyUi+yH5ZPmME#2&3@^&{yCn zl;*Jj*GJw+`2qqZ)e4##Pg4Slq3AkcUnp-pAx%dod6Ag;w~i^>+EJ30pO^Vh*-1@~uFwe9YL zfuJt?BS+3HQ}71_f`;Y=6rP2A%5D{=yxSt+AR?V~eqLmY$UmoxohI|o!4WRwjUZeF zEz(E;$<|e(_6q~$Smd?t5p{)%#T6j^p)_s1_pSry3Q!YX$C2xAp(WkSh^hno33t9Vbobtz3@6%;eYr5@=nMzfco5h$?raZY|Wi# z?2a%yk<~wW;CDX{@-zP61ETptbmO-x4tm8=m-SbCeacqkAuye(yv*|9Yonw3(Rh&< zx0%wn(=QgL3l&pcV@_{geeAC;?9Lq(E=vs`WBVhUlfPN-`J5(v2aeonXCsPQZA!pM zW?j_XR6b1F>SXxLQ04rnD66lbvDf)DoCX;u?>7I_hjl+4!~6+B_?^z;GsHdmnmTO? zP0LUw6ZnY46yMq5?1TLLy4V|vZFJkhX)+E!N0&%b+U(^rMWzLIUXv&9@b#0y{A~Y( zeP_q{bz0`~d`F#i%T%_t&xz;Z-gzZ@6W)_Kifw5_6YH&B%Az)oVw?TJ`|clph8dOf zxT8~+Rhu#^f5%<7cFU0bw09&ESNGIr_!;VAtC=rqL5X3gTa@fM6%&IoYQ3nlrA^`O z%OB(Cax`9ngnicFDf%-xZ!`AW2TfaTGuBFhmURbJB`8D6QeGTLQkrO9zl zH)VZ>=3YjTHL_PJz7Br?zgNSewrou&ZoN5M*YtAEjR8?M*7yxVB;kG=q*hF{**q3k zOB!#RS#`;}@_N+HvB|9^o=--NOD{2c-l|v@CG`xBG8N4_9N4!fwv?SUK`gPhbC00+ z)Dm*_s+QQUtCZiraOJx%_{b5%A{Vb}42F@7+oD|2TGEK8bd^vhAM~nBz>l*!5ye@b z=eN?{PYJ;Uhr6DZA8V$OSCe29$8!dthc+8AK`{(DLo1IXe9!}9X++hHwb*I{h4=TH z$l-6R)wpfygp5Na@$JPG-R}v>j!|!2n+pwZg#09&!p_RTRm6J>HfWYRs#Lq zovmNGeP<)Kwm!Tn?aG3oPGzqwx{Y>rD82MW%(sW;#)0}@S}@&{-BxAx+Yj$jU~vZJ zW-GhjyEMNt7#Q!y>in6Ap$o~@z8Gdg_7G=a700)ycY7`b-)Q`TDTpDc>R0A1-DOv@ z94|Hean;W)eS~5-B*}6hDe>)@&}OTEdZDB|!tJs4O{|7`VtJ0|n|6}FdoNXl;LN2^LJUa zW%J#qa!P5U%B(kw=Fwa!>Q62G?uFBZ(4%@B!ZzOB@mry%UA$y@9ZvYvtOsGqI6!%M zqnwe3RcRu)p2(KqUfU8PW3=-s=n$WwWGwwRJQsnJ1)MyVP}O25jPmC5Zaj&dQ_Imd{|m-Lz# z>tmPpk|bQiCL2!lPEx~o`PwqY+;8RQ1U25X_<05IZKW&TD@vY>gs_MTQv6|($r^Q8 ziCd-KDX3j&m6vYjHghj!Lx-ZYqx@E|&o)DQuM8NC47;}En|-E!cK2qkXU*#xO7HZ9 zVhr*=_<8|v$umP0MTCShxuXgjcQtb~YNM2c$e#it3^ki>fSb^{XPo7CCaw_EBBujRT-)bxn#*s}hDmU8B*NAh&y?L1#2alQ@zthE$MTgWo1_2YvU zVH>3)ne|9X;mls8XXw@l+x=Ral@6BvhF5y%;@{qOxo0v%Cbo{NT5R*1^+1tobBQAp z4zm~XE>~=QdOKb=>~26WCzGsfGtp{#dautiOID(={TDHJBKdmb z;%8A(e&A%bF&kU>&W9eWFeqvWa@%~fl=!^Y?$YFPjMV%z#icCi)+BsK(_+5*;qk)E zB6q6CDbBJ`tYSQAwByB;jRUIItFae#>GA4n67@F&V~YfA0;E~=Pe1pm^z|1W?jt=s z7AA(S6cNYMqBguorDI%m(Uo6iR5#v_3fFE;y$zLQRA3%pGdM(1P~3X#L%*{T6!E&R zxbR^k{2TGl>FlMPaN=N|W*35{NJl_gk`mM3d*h$d zzd!KjZ5;U-|9u<(^%?(*HokIgi;5F_MwDd^GX|3#x3ij|v-vG&l-wOB6#TVEOhi;t zPz3pNO+;2sOiE5tj9)}VPDI4LjO)Qay};Jq+{(h^k6%DF5vu?%*rTDQ^Fy}EkB|Nn D*9eNS literal 36206 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 X>MUrv7W8iwXx`q_)z4*}Q$iB}b*#S; diff --git a/docs/sources/docker-hub/hub-images/repos.png b/docs/sources/docker-hub/hub-images/repos.png index f25bb3a48d4298cd052aba5ea3daecd3aef91435..4e83d34053f3282f3a603aa522cbd76ab1f7219a 100644 GIT binary patch literal 63242 zcma&O1zc2J`!=d5AfUjI(lDSj2vQ=U2-1y&l(ckrjPwA~DJe)wOXnyIU4nEobTf-**Ua8~t#$7^uIsudOhrlh5iTX}ojZ3P$;wEo-MMpb68I*0 zfB_uoHh#Pb{CD5twc_hLcPgXtP)3-*-*=tWq$Tc@4N~6@(woSrDc-r`_4Ll2PeFI? zTmpwaZQQxz`s~h~tq*tZ2&de+L++5?s44;s;5f+WIN!O0M|}Hz_fBg1W8ff`vAne8 z9rW$LuPp`fckY~`WhGy0cueoKV0w@#29$|b3A0yuX%6nau&_Ya=uE^8A!i-&?h`%O zEBiG^gMkB*m<@{!s&4|mypP0XU;k-N5{!9;G2}azSlrau*f>4kTCApj9cU!+fFX!b zqT6~MTjW0Q3-Ff;9Ow4XgKQ}AKZ7iY|8;Q*CM+ATmgK8A8V-Hb})!#fWAHA=`D%UT@Qls z;>Thb^HNQ(@H7QFCB)a9wN{p~R#&<=;CBYT0nj!EO z#0=bUKJV%2siUK#k&#h_ZBN4+2?@e`$DM}#;dwkfJTfscF~5KR{<(a{6^;x#2ifm$ zqcgT|VoqUAbmM$DkJhVs!i9j9=QU$xWermSv$5e0a()&O5FjI7T3Uj31<8EEDe3-p zJ{X1!T8`1@n>A+V_{P!R9(`r)Ei?o!L7B;BUIx}W_XpadYaCt_a;s>wxysSi$ zjkV}dZSU@Obl6&3TU%LKnVDfqadC4OK-F(eBqmHMn^TX^``+*clc=buxHz39FE1~! z%7kH3Is3ShQXVfpG$Cg=5KdAz=h(6Bav88dd8+Ci#FLP{)YLd7p{%S7fj~@7PR7I#W-rG<@0oV!egfabf&KVI3~A9b6Pc*J zkI9Zf3r$O~QN|~=-!E#yg!C?MuCJ!1rr46tPEVH_A;^R5FJGp^F zDy2$v6eh%a8~hn8*1^in9jyUoVR=AF{~i&fEnn(njNvpgI?5EHrmC$q|Df z?L+qbygXU(-qI2Sr-YYjnazdF*~T!V!K-sWE-kfA@cZn9 zCuf&7?RtKqga=B_V2fPZ_!>bf$0K!6XbYQ6h9LaXf8@lzs`rF#q80?jF}NL=Tw<3Uf1MVkZb#W$Y;wUewy$P=4QtO7&JS55FcdG zO$evjJ!cDxiOMQ+bDx(~5jX4c+-sb=MrQj$QY3dnTfXOa_MP`IFARa0x%hZ5r!b2y z6T+|yO5^+V^sfOs#=>WPN~HdFP>f00bYzxSt%ZY?oxG}^?U^?U^<=mo#l(WF(mAd~ zw0>i5YHV`E$p(kM@mf3L-^`K_+99yn&5lE(`Uud+--gC*7c#C&@VWI(=9MuV<{d|1 z>2)mhOb;Ul^;@!4Y6xka*IQpGJvsuR+ozf>#Ct=KUn?AfPjX20vZeIed&X2!1o6&P zNp^l0RUwp$>}{$Yoni;Maf?hU$Sm$kKc1*X`g?u<>}7a`JUQ z!ll66G#tJxTp$wyXXer)s}kZ*>BtW?vz zft{&vN4Vo?m5(k8^Go^k(@{I#m6u*T^L;*>C^V?Ox|PwSUQzT|9N(Cp1QT%BE8_GB zS}i2O3!>cITxg~G%64Z*N8~nSljLxd0TDmHH8p3QsdDa-@D=Ipt9k#f*xHNqJvc0|C*2d<;7FdjpO^(-QmThQtWQZWH zv{{O&U7u;dczR;uoCa1;C+v}*yY3;56ByjLe06!rfG|ts|04reJta~$zHcNDm;6&E zW-YKB778UNA#INXg~2K)hM`fR!hX8eR@;)7K`XJvpxSWDk97I%IXNBR2a<10h+Xdf ziO?*WuW%kx$S~`cOrhY@H-$=hZ3~FjU&x<6!*eH>kC5+EF^4aPJva_@ z{y>S(3E{eFM>n6Z&IYsG%e+jH@_og@FXH*uhH@Km$gL(}Sn+@hoc>|C%(rjC4mDL^ zXglTop|^G{Eb!Y>f)T5p98;%~Z{0^$rN}}%SuocYGn*83*b?SPzeyn1HLKyDb0Lyfp$187MN~hCQk}#9Y+s{+QzAz0yj_JBD!k3UH ztZcL@)$jz3(Fn=QA&VhtFGmSd$$xF@;*OOSS;0doHOpr?7@rU3$rt2YE3Dt1uYjZC z19V^Mt=DTf@KqQh&@%OJf(@d#({VxKo~rTC)8I5!NO?9(`NA$;C>-`O+&A5;3 zp_c7@)Mk}ddBj?=|F2(1;k`vq5pm4fD=Fg+y=-j7@IeWy4m8jE_cL%>S@68@be&=z zG@{dl8rM+-^r2d45dqy5^6*$$a3@VPCcb@?@d6wU-xLh?mRCjyzRj$WEwVHgV6n-q z%IYm+B~;H&F(V}%*{Z~mZGV3Pn(%SrCtp(pWhHR9Tbqg9*h#joa~g&{hZ<(uUOns( z&!3*2$-9TKu)DW;_~-#qRo}SXPs=zZ$KeeJvgs`KO^o)+F?vLW!Z5cC3O~Kc%S5|L z43TU<|GFPY2O_qmnPi6gQ?0}koj?<7*wU}zu?5}K;uVPXkKaWX>1Wxbn)CDDimc^^ zI5a>aJQUgm=@D$A2JypZrx`Hc{o@X=g$>GC&kw#w)10z!s?=114A@a{)O-P6@~)U; zZO)~qx|WSv?{rHD9k=%7*a&G;RMzGeEvRa8MI$idN=)bLRbG@>l4evs%+kDJdD7BS z${qH}vsf_P(iG|%`}ktpZW;tScPZH#3IE9f|2JUPtcdKQ=~h|2tTp+%Iyyzfj*s-!SrEAOp4cIho$OtLd;$vsHP&{P zX=GroOXzYXGv#;j41NMi&hxl>IFlu5HkPsUZse(tG)e45GwLg@?sq*zhj zJ#}A`Z?hvB;X-6#E%DgObmF#>ddEFudMSdKdt#5k^S>vVlIHonr|&P5Kjtw0a4>Qg zRAW8*eAPQ4E|UTFt46%weR%ySJYs>YfA58lf;^~eKl)>7m!(7xk3U^ukwda2;XjTB zJf+&+LHnI;hB;`##-slLWoA>?Sc{J-BS)v2zm}w#u>L*lg%cMWySUrM zT~wSD;VsfSfmu{R2ONl zcUZi+@#TPqV!x1eaBy&QbMv>I-iD{A=YcslH+Llov4DVpl$4aMt!=$7VtKiPa5*PK zcb1`EaBl%tt^{WgO-f1{=}QHiBk*%eQbbUYTt2H-i(jCzsYy`$%AyRpGd{zHpqRRF zAE1H1Og&mVkd6+dp7p8O#1YH`jPQc_#*=dz?+G_67v~=}xTueh6muw}#UOflNW9Sc zju_6H&j^^Zba|$jF2l)+wn>D%&?2CiGoYVpw2*ir^w?u<3Vw z7?ERQu&4URz0@4QQR=G{Du$sI8K4G7fWNx)I{6$VJ*TS!&s)Zd)ErkC_?8c3rMi00 zC#^4FoB4zK`M;(0(&(sz8R=cQ1nX)uQo)%C3At+URc<}p^`z9!Rna+icJ@4;4${Vy zVq5(EeYB~6Sy%ikOSk+RN(Kb$m#O`M_u6VVE`K5iIG?X^KMH^B7{?hJwg1xhHA8V~ zqgO6Biz*6MjRi8SFpvMXZW4S#K&`L2D@@PLfD(JcC=W&E2U;2a+}+u{I!UO+A{WH} ze389(L&3b~_VG}dLKmJDI`=yM$hGirT3bRQAT4bs5Su_jQE_)?hrVe{2bXLNcS&YrO$Kr)k!n+hKEVRDtxa&dM$u>bxyz0xQ8r6xSfH*3~tKubwhl7>-4gygAW*Ka)$DAGrOeF0#WJgh}k}c(&!L+JU>Tc~USAojx*R;zjN4j5L6!N} zoIhJx&9^EcnA!wM`fFEx^{0C9^{p)vx6^SN#H+AYBW?x+n=-|NNNUgG`=lP}+$ff@ zmd(S0E|R~lDC$m`@N5-QRHxebbaW>b+^S!$U~Zm&^1M(9j*W%&qpE7@A)+bGiLZJf zQB$KWGomk3oc=6Rm|GGRTaE^z%}SW7;67iKa~oVsUW)~f@0kdM;yr&T|nI%Y0{~fF`8=nG$NiH{Ky9;<34vbS7DKTvo>C<7I-NG zJMfJ7FzKAMbq#olPj^2uB1Rww9d6lnITl;oVrZ}SK~Z5W>t_)(8ia>Hr4@4GN7Z|L zvaWk3mgPaC;*~`ZJ%YYi!Q0`U-T0$(3>V_Owm;#gaYggN>`Y4rSmTul&q$atu_c>J z!rsRt#j#KydcM1g`wF~T@sOgX7V}jqY4TX5OwiP0IOpK#axv^lTR=%lfx=5*7W zm)no$O~0|Kg@uKlUV1OtA&->a{KeH`Lm#E&?5sgNG&()M*{Az*YYtmXx0=JYx^K!h zBGd`we(VVbo=$c@Ma~}q*X`ZAbJBh|=UEMfv$I^U#D04XbgAS;EF6Tr>gSH0wcFnF z6fJNflJbeNMr@NfZufteod$&?T?(2C>Pmh*Ew89<$a@qW6aM|ZwoooLYJks4s~CQGlw#J;@H5+kD(a$0NF-j@9>ljc84@rv@~YeSydmH!)$USnsK zmhv^uR^J5$D`}iC=%{Yprnnf2WaybTra-RU{Wq8(Y!75uSK8x^dl-$rFosNL7G^!8 zi|NP_bU@xZLjO4)M}V*0oV&`&ugA2$n8d7kpuLlE`=}_$t zef}(n$0>o+NxO*00)g~14q;yx~FTE$9@X8NFxp!`c^VqF03k0 zhUaVK(LwN*(q01TVeH!2o?SdPfunbLtSBtK=>W)u-tqH~e4(UhJ-CkUI0#nTK3WO$ z@RsgUYG))=pA_HGabbbH0BMPtVL*%49OLs7pw<_GjN%x;lK>PFKXIgj3A-F{@eLo- z(Q$`}0{CHvp>|CQK0f|nPtPXpE)PphaKzbz!y(TNkGF5%s;I>Lw&QUKKSAX8G9+T= zSj&BM**P{meWbgpuP(=)X_c~(N<>{$YaNcUeNNMAC2|DurqXf;Qrui*nmNG=n9In< ztjVWOW{VmWR#VqF_;2-0S_0xKO?`;IDz`{bW{^^m6@LuTiLxc z*CLiurG4{b?YllUuhX#;UIhIDkeM8^TGI6!xOBD2H7m0kjKkr+R98Ew0Kn4)jh@&i zmpgfi9LWieOJ(M`itw{vQR&~4g>%ofWThk-s4v7A>IK-+v^uy}uNJ!I-=s!jN9CKE zz8fC}7e5#LoGSg2-jWsJL{4t+aFe_gxi(lf;mO9V3NN1V_A}$~zXg}`O6T0;R#sSqucxT3Y((J7;5!3=RAVsLMq*EWg0O-hQD$*Bz$!!+7ueBMW;< zng$O=BV^kUdGX`cXKV~OQDP?ZapkUB#;RKG?uz=NPZxhf(_voUib~rTt8U_>GNaci zOVD-~Th|s`?}h4RzWfhW zxbzmYXiHlMdim#R=h@(d4;%A&pqOwbt4Xz~cN8j(zc_n2j}yhcAEHsKxCbHpXkyPE z{A9J_0F|Sb=>-r9l2mZ^2K2?S1-5efUe@%2tY?!{b4?Au70ECQS0^|qmHFw@@v*Vj zt&VO9(nqgNm6er&+(J!F&B4LJ#-?PHRcb|`jNr0a(dF!9xAM?xcgycOEmIG~ZM%Cj zQ0y1JkPh29heZa!Ts~(lCk1Wp>l2}Sg-%vEQ01TB^iDs$E*$Qh*YVf_7z2gZ^i#(7 z-wfZ6f&q*M1qFreP--}7YdLGyV_F(N&gak7Pe{ngq7*u>!n}DONm5Umy}P~{xU+z` zj2q_)njW0@w zHFW~PmmDB9O8IDOt0*J$vk^>OU+?qK>F5k9Bt)lY0cugq{G z7$>?8Dtz5ehe~ojj6s~3KbGl^_*dvHk2-Cau)&8aBHktK(?#JsdI&dG)HfU}(lGvK z@~H{Q$q{e&k_iv}{r!LZ;H7PMmx4dRO%>i_O6EG10oksIC%E3+1g716wjaDFE8Tu9 zdStyV>^+a<35euruJVV0Ziz9moZ>LyrM1IxC*F#7P{FOLC#&VM<`<}flxT8#gd%KQ)c`QH=#hy46M&ivr7aR7YXe=ZpW z_?f@N>Hp`ze`o%mcl>vP|J#xn1Os6IuV26J9&!0Uk2)56Cn*A}2=X_Q!xVcAT&o$2 z-ZVIJ7mx1A%~Zx$I=v$^4|0q_{y`5_^_PP;8X(8{aMP`ycyR!~yZz&!R=l3#m7p*7 zy+L5Gxaq%y=OGUt-{{0d-A_(`xywPUbYH!D<&)CEV5PrP??yhRrrz1xyZJNYe=_V| z7u~!2>h=XJN+9OHi)`xi@Zm!j7Z;Vc3x}Vq?`UBF(>I6#_S~QtT31suEO~qQ6EIV5 znZNsj1+3TKH|~Fe*45Shkz~E4i9KEu-7?P4f%X5p7=M2S7{0$N)O-qV9bsx!XBRu4$UVc&(4Bda=^iC-_Pbp4)+m`Fe2=NADCbH{GQW##P)EJFu%e}xb{!&A+J_;l6 z4{k>CcF`eC~4Cwb{kW9QubsoOMOt>>VCZO>-3kRPMyL4{MvGr}`~R zq>3pAY$fqYH@#-CAGLNnyug0_HQ{h1dl*t}NQ33H+~6$joP{FpGHq!36U<$9hmLxh zI_6aHynQhPSj0G6YwcVmIAl9j+?2fP`A#OT`v=Q!5ZQKb0iN#rt3iLzHL`_(zsSzM zvRoxRUufBj-ukti<5k%^l_HTqxQZ&$0Wue&k3c$r>N<1OzrI(cM~p_;9l+j8+TnDo z?J<`92HzyAqk!$Gy*kfdvcBf^!ooUj{=)%Zadfv6uK{A%5zuC zVCRtU_}tc*z6l0-{z~p4t2j|CH^7KO4$kdF^zW~CHX^UKwNNqbA*D2j zve81NIW1IWstICw-mRG@AOcqFg!Movmc$gd?jpC|(qRV?^e>KC z&bmwK34@J47}@8XCD${q%n9NIn#3^INpRu(*cr(PL9W+FZNVr`=7P7GXk3r9e zg9(j!^yh}FhS3eDy;oTuwR1TT);`Z~I>5$Jx@tPQb!Kn#MysSelj%Nl1s|CUx^*A> zCl5gqQ0MuKZ*PXkKx!f!B84vS6!eDX2Rn6XTMKTd-=}idyW2M=;X>c3)3W%}89#v7 zuwY(O%Fy})Z%-Kc3b(-M^JmCN=YX{;ho@|4S=9^;o=?G{$^6s=LnaabKEU>+Q1oU7 zy>|Hn|GdcL<%qngg@(QI^cxqKrS96yp3B9;RDl z*Y`v)p{SlnU*8=IGo(<=f1ww}`H-!wiw8oAR{$k$`|IbbL5Gg6ivI4J)$Vk-L-D3G z46Ti)rW*bm5HQ634^xfaJipW&_z^gY&|sL!zz5aM<)%GusP`IfG!Jn+x6Il(w=s@6 zq8rF^Q`A{cX4p@4+V04=zF%*@|5FC9F#tN@dbTC9?aJ7gKWVPj)0Cgv*O<=&6~Z7& zE6`$^sLgmRD)c^5irA1^bNYad4DgXbZ898o`}jO%0q8M!+sU3~nuuP^~0 zpTEh{kzzc0Gc#E`ziw}8rJbt-UvGT!=n(-r-ZMyLX=L2)@v&iA&`p~#Wx!RUdNbkS zS?gVNTnMBFifV9mdGAZruf6r+URLIE(g^`~Ye~azQP1}Hen>48+1JDX7z$Qsh-7<% z9^9n(y^0ZX;q5O7-?a8ko{7kruZ7Cl!j}*u(%@wNL@AVdsF01)rMrL9(tEw(Vr?Kk z{Nu;xl|b$T#W-xX8c-DbvWm_1%VbG~{Rh2eTN_0-`~GBf5&g+CwINcJlGJ`#3$vC< zn*N&$t$UCst@S0PP4*6jIW03h1(aT#j|}U3Af_Jhy_?t30gd8R)<_8jNew+47)XDb(}O`jU{?U$}{(+#H&ettvWjh(}LdMV|cDt0Bk#MhBSfYba8Ko%H*o z0nzKDK~9oGEwhgw3jwSwH<#)8^XC9x1c*_dK79%hN`Tg&zW#B<+XZdz`EM1wsIR24I8rSMaI259~mw17!)XUWE0?7}97X*YOWg@ay|3hK6+HUW@IuqGlF$ZZv=h;Z4}>ZSDD~ zdt2C;MIdC2I#GEnLqP4pUTmG849#kEqv8pEl6ML2V6Y!a`zv%~PK}HUs6#mK*DXp(@9b?3PgR@-zKD44N;qnF$sFJUf$269i77MdJ@scmcJvrN%vT$rysz+vot_j^SHOtFbWog$#Xuwg!p*I z^Zjnswzf7&XQ6zfm7EuPP@$V}OSlPH@v-~uH7U~4FK>3F`s#MJwzuQK+>~ElQSZ-8 z!lqh?!FJ1ATpcH;=ld@_@ngHV471)ASn^5&$u;E@h5r^Kbu1M%hA>L7H z*bBbHkj;BMRHRu#Bls;>U)!$92x%LZe#UaUoQN4zXt*B_Vn=bcv<%JB{L!|)Zf(c? zLMP%*armyr)tHPod}>+CPGfwOw!vd}X0=%55ir|vHMq1DzAP(rbgJSVgQL50bY6V% ze645}mtIJP@s)uzf&GOwsgi-1_V64xJ+GMKuECq!3#T63_Tyys1mQ+LK7Rd1Z^-;s zB;CN+h1-{3H_yqdj*dt(Wy_+kAG+eLH71qd#j$SB|1OT3J?0CuD>{|oEx z3u_o>(IigMX4<3ZWh6vM%R*amB7uh>U@Z>N;z^}1agrZiUU~)Zu>g89AT^BH$;rvd z%%qJ~co;u2Hug?Wj~bBB5D|f0^oSfkl_0lYfgq;}i@*D7cLE5z3Uc}DYJJP=DEG7t z-f=2BPmEwqW81fynmYBN7$m8y04OqPdbsNe0=PN@d-us9yI?H+OPGo`C|XwG?aC2n z(@N|v4NZ(v?D6RpnHGR0OQ!@+$T8yMd<$-n@QgNSv@^Da?Vn)o@SgyyHY+54l19O zSg_+V5)w4@^*LGVCzWL#t*otMUu|)4`IkI(pe7|)_;CMbwmA?i7N_jk^Z7#FqFk?T z8~G*nOv?~*F>beom}PBZ*JSIR2I1VaTO3JUsQJ z_mk}}!z)9SsFkg?7AvBCt116;1E*)@wM3htO|4cX=>WD-Kd{A6ED##VvlE8h1yGx% zpXy%2`}+DA6CdE>S^>nDqNapm?ixGK@WR5vzbd1;9OE_voo`TB9~j?eP9*qX7a%8$ zVq;#o!|r&RT1t&!SCJ3!Yom$4H#K{5wpb>r9y-Sd!Nmx(fp<;#-E)TDv7FWIs?+dkeY5RE_^LgwHQ^Kdd{G)++fBnjmdFuHnJ!fP@4R9=C9xx%&G0u*>lw7jmG^WL+Wqm!nlMr${19*AbGE zq$VHPN!+HlJ@TUrNPKz5V9>yQI{S`_kN}gK7BWF%8=ec4`40eZV`aS`!yG}oTav?N z^TI{XuKMYCxc%46RzF>LvZj#`!*xkOwg0J=cogG(Y!GV!g?gpww0np@k<3iq-3*L(#QNMfs za939rNwspB(_sgty>FWLUHrJaLz{GNR}>jsG~OgCJ?IX@%lDL!_m{Z286>`X=Xs1msxZuxqaU-qts;7g@GP z8;s;{B9IFZ;{r`M^%Hwekc=u8TW@nk;;;WOk2W%dz!Lfeg&xLY4;p-ymwkt?hV5ch zJ!l|?tJ`q=lk%|Jl&@GtVSyJ?81wdH8PmX|4jwl7M#=Bl=(Vj6g4UTdf}8+dV=L;b zbKEf~(W^WBpS89w+T7j-)TZ)mY+0*>rv-Hw8~OW6(In}P>F)njrpT)hDeAUTH8`M1 zg}>wC59+(tg-46HQ&HY34sM>{s_Up@N1S>Q;bsi5veddb2-bNsg?UK5+of^#W^q-u zlrk`46`6$hHq`7|J~#C>iLcCyFWp|=Y0f$Np87dGAg0>`?(Tm6bGqfaU5@>G3`EJ# zQQIX)08C4og%Lm4Jy`y|h?}+ev!Vifabc2XP(T+`F8HbFOLS`u-cfRd(2j^GdwFTg zo9yog=9~Wy+~3KGk}qgt7Cb*IJOajlPG(Zdk~`&v4C+-??_@{dnxlUXONEC)- zLocSNdb{saHPhFSA65xQ67C-|snF^G5ow+XyIQ93r}}9-32fbp~0)5X1$XTDG@-zng{JOtS7l*<&W%h zr#dCFRB#ODS_9U@J1J@yhnuCOCjJ)x`t|GO+F7h)&rzQCeMfRB-X9JRqE#!0%l7O{ z^6Lig7&6N#5C7ugR#Ypr7esw zWN`bu__GQhm#;rBJD(sV*iwV7&f0AIRmtF?u`j_hD zQv*2VF8}Vq!4PP~zFqI@+GS2zdwV((0Zw*h+RZH*7NAsa!j_N!Q$?yUJ=*E&aT39x zotlt;iO0e3`e^>f=(l5TrSmZ>Yx&4qxMR}Ntia&Bb{{fZ-NDpnf8#Drq|&SYIc)r+ zPbn#IK3RByMl?wx*Ue$iI+x$r%ZGo&U;0$#Re4JsK}5k^MJw)oT853@bXXi%g$8x! zF(~i!*l&gymmkXpVJ)gVMqzY-dB0N-DOo6!4Z((VOj5CyD%WnE^w#G+K%-5jd%bD~3l)C$F zcrzg^#aBPpilOxyJg-V$g}+YxIo$5OYtRnl%_^T7u(s={h3)qDo}7M&h3GgBWDav% zZ*5Rz!b%P{!73BKq2c$fKq_uK-L2~D&z-U+-oI>W?B7X`xd1|R1%rl?gLX42FxRT8 z*WN(b!-IROo@SF2pk_UFmx&H}2-;Gf+@jUD77$Q2o}D+;vgkr3+o`N{bg{x6hEps=~Z=kIT!;pS^_CLs7Cp~j~X3y-m^ca@Qm z0s1TYHZ1Urird;c7NVk}PEK5yUhx+CeE9Gc+1headX?<0qy9OSC^>!j&&VKb1`r}*lr32PRWlqo6=g`AjZa^OCUXt?^x!^_oBUyZSr zk(m))1HbwTC}=~aXFAR&J!;%sm2GNC9RGo-L3C;V544w+ogEqLYF53ywRNCXgdL|E zwxcYZC(_2SOoVY6`ZD@6quuJpm!w3&GDkQSWf~9|Kdc*59|CI&+9ZB#w7G~MFHq(9 z27{%kTFywep>*3Mx9cs#`H>jnu!kaQ&(HY8k&1jDKFV=neb@4f=b4>YH}Rg``W7*1 z^2p4L;I5=k+}D4aN(jS|lhw7gZzI8!9Ua<1D#vg*91#3>yF(zbBkv;U&gN!OdAYKj z94pn?AzbwqT}xwq6;Tj2ksj{XJjv!TUUVl?FB%nHI{ zVy)k;OizqGqe(7)E{zy+kaF7@TdJ!QYmy9Xb%!rUslq)8|55{>iEyAj2MT@g(M44? zHg~wKDm2SiTyt-AM0!6j_R(`*-mi-)7eIxV2`KFdFmBld7R0m~P}Y%>j?K)lqr5++ zySTfXxSF;-m;2}Dv!T(^M@LS>-Pc55v1iXzG&MW_$o^*t#`F*Eg2VV1aR=&mfHwUH zU}yLr9Q!YY(M+8R$`fa)S5B~GF5$qpg0)XcKWjp=@8Uy$DA2|K?b$>y)TlU3^ z;4h5-x0`GUQ}%ZD;X*B;|I#bK{T&1_0kgZk$bZlHKZC%1I-XNJmRu~qYfphS&wC5o zNToL^dptRK1v0?7ZBc_LG+dcB4(l0~`cBqakeA<|=WRCg=(|0ePl?lc4_rGqiIuZo z;Uj6Y*#mou+g*!(wRrucWhZM5bu-H?MM;65dg+N*oS?=kHu+H^yJH!qpEN_?p? zy;!)O;C7=E7M^p5Rpg$u=->g8Ri53I)o)csRONK^|?1LhUGiJH5h(8P|FSchone>dt6zMFJ@{*~i&Wf&(1PZ{X({ ztqvENtWxQJDH;U=kht&W_n45Zn<4MphJ}u5pabHB^suWeU;RcT zTE{G&tXhNL;?_IzL6Q2;BhF!{y@h$S$CY1u(zqrEuZSmBM5}JKhFr7LvL?J_PT)sX z112VT=4-`45Ah)Y_%x=u`TTh0UiRx!-hJQQ)ja|kzLl!qHRMzD4Gm-kGK8RH72SnT zFXeHuhxBR7p+hOK(eVM3W&dvrT!#Gir=4B?3~E2%)h=@bFJnK9mEFr8u2d5fQe-Zl zOKq}h@4`#kvYFgkT;kk~YJL{{?cL!&qGL#__CA$kl^TqPalt&?z&2&;m8upS(5#B8`4?!=$P;~@iAS{Q&&Ocp=&#;2H*Eo<_%B^s? zIBLjyX_#jO%z~FsGdC;6D>xLpzM4Z?%;S5q6Wl|1$eg`YrCCBlP5m$|yyKX3=zX|Q zDS~t#JWz%qpRqHO12EOYXLv)8EiKr} z3E4C2pN(LR&kr9?mBeZuV0vMtxI%W=MeeZ-WY*^&KPS_ag`2J?ck86SI4egVaQH>Z>dsy zJ$?0?<#uKEV(Bw2RN8~=uZ7y*OK4v>)BYLU%S?HT`D!Klq~4Hlq;nzodbKehUXc6C zrQ;-kKaE zV?Ij@zASFl`AVD?!-N3h|5KlHc55r~l&Z&&j-8g!+8O+;T&S{Pu?ks4kM+E|JNd`f z!6%?-d4;$iBL?ui&ZG2ZpUPenbSy5LCj104fTSj!;Ma9GzC<=e#2-dvE5R9;i6{2> zQEJkLTWhy~nGN!Pq>xmL5~d0C5iKk{l6MNSzg zaxN5?5`QeObIbY~@Z;f{1ClYqs$_eLrTwv&?nC-5hd|R{UzJvw$=OV>{8N+j$Wog_ zhu{eDM^)r4;@+_yPU`T2qIcaLh*MOS{t_2}g#qnEK&j^KeRbQTnNs8CLWX`V=RL)s zXK-8leYfb>4VcMo$a8gYcqaN%Yj3J>L)c3!q_z!^2)1;R9QM!pa7a;l-qt#4GcSP- zKq_iBPTnf#I7t*TF-@(xKDc8SCG>0saH2e6Jvm+wJE3&n<`A$balG#q$V!d&<>2MTXcP>tb2947rQqgAO zsFDj5%-kmmmK{5M^D8^2DqP}tUeZlTF8K-HY0w_#^5CAd%^T1ToeIZJ<-@jz{WT2 z5~_U0`3A@xV!|5ejmDI*?XIS-)C8w|rImYKU*|yyU`V1gk99S#uty%3$gn)9jkSGp zDo)l^)0C2 z(+IgfxNe-p|DuKaVYkm<)K8P4FSEZ%2W={^Dwg+bTDP0Ka=T#L|M{VWG(M%-YQn!` zyH1W>h{tHdva6`Py4qQczM# zgdk5B`%-WHiRT=7?s@ztKuOpB>}EGm`DT-x&QHwy=E7PqZ@8B)0{L_C>*gD4OKm}* ziQwBZac=tdR+bxaR#BA#oc@V9gK*?G36V;$-{MZkKu8zqh(>gjC^c!8%xxNQa+?Nd zF(a};s)j>`sAzHYoru~U(XUGIyd^yJX$yt!IgO2-XSS*iUqAW;GGAO;oFxL%+7NF! zJqTMGZe1+=1umJhe$bvIi8?u*dG-Oi4?IO#^^K}g8Lx9_C6^g2WPml?4P;ndqm}Ru600TfGqs# z@=Dby;cf#(Cd|ghj6F<<4d=Jr<7Pe|1{4)(2%!E0Lp1KFBohW|k~rR`$Aj47*uZL-8_B7C&TszjDKC&|A8PgUax7uXpZmHX%49#3 zm?&&vM#m^CQ`q}14*vV<9=5M|@3H<(lU3j~s|y5rS6!mXSJ+squ`Sp4{%i}Kotz#U zBTN#Tl%L{2+v+j?CnK-dM2(F*8)iL!SLf29kDbyevUdFZ>4G62`5!UX7vkED+)TwP z=e4{0rNWn8*3ooM2Q-wFxP4rg9kDa8mZRUWy(Y>ORr=D1Zp?`i>Kt`Qp# zQq#(kl-jExaXqU<+<$B$MacbB>^f6I*L{dALRu!w=f-EbNGF-&2m5wz2|Qmk{>Pd@ zB|JaYCOUG0uI$A)=W{31;FR4Y+UCK(Yhr*Sq7tNq!Jw`LPoAy57&F7>@(4=c=L00T z=z%D_m=8jSTEPXt|EWWhJ++RW>^>xq)K_^->5rsX!} z+KfQ>HpLVpPw&BY9|5h~32|6)>Wn7E%PC1FYzwl+&OC_o{0@O8U9p{Ay#3{nKzj2r z)A#x^)|QgSulY-%jaQuQnXvU6dY9aZNWBH1c! zZ6t;48?ZQu+_^V$4Dff}w#|8YXwL&-W1?_2xQ*RAQS(y9I3^HUKd`-~bk}%Xlz~{@ zLjOmMrlD6KnOoV9^m~DUzxRsgjgA!Qs?TE!E-uqIue$bRs`hDG0`-@r{`qu^2VcU$ zTr!-cjT;oa=i#f-kD^{VC^uq#UF$X!y&@qyRGSEtPrWu1PDlhLR*Y z1S}+1YBiEmdye&~VtB&}MfhW$-7g8jn~H083_8IStNc@muE6eK^nSDZ^TFZc2HIA% zX{znfAMHcs@vIa#`wX>zKK^806@>lvW?1dzxtBUQX64P)Qd+t6U$d}pr#dR}gPf`k zfd4Pn-ZHN0t$X|3ZbcN7Zje}@bSX%PpaLQ#Eggb{bV!33#3H0yL1_?JbR!5Xqy(g; zWzii1(w?z!@Be*2&pGEgZ;mhg@PQS-nsbgh#`n60xY$T$=o9DsEA? za1(bIJXJ<&yH4VSzSY$TTPz8CWmM{YR%tYbcH|b*AbgU!LFwL6NMLCzuOMYGEc}VF zyOZB;2~VTP8J5;~l}7~s$ulHyAWmTLiigs{gZWE)Kg|mwu(`NlQj)5FNEc=p>ORc->eAJ`10_#=QO*Yogc}cU&EtX zjO1p!aXIv`Y?gVF71Q(KkGqUA+K{w9$8K+wlW>uGSl%w~f8GFgEM=)6e5x+5%T9mC z;AQp);S9%JIKy)0;~iqwCm7xbV>*>BiHW+ndv=#klXVxi|BC!eBc6y7fS{l~{11u2 zv>^czkY`|8dckpr3PK@;4V*wxp3z2Zwj=fB$;+3RNbHV+)a&k+$2IH;EE5BvIQ6Ds zIRX1Nc>exPrl{#*Ct>W@oMt2b{51&wpLnwpy6oB|b{N@vKI zA!QVFbQ0s@;;O3LLA_FplmUT)l+@=H1~Io6duLWwSNr<^qG6cj=PboKQw z+fmqz5^ehWo^z&c4l2}rN+|!h zHtGhmg%uStAp*gcuNNrZM@P>rEpaqUHmD4LMs2dbg^L6Cxa$t_dn01dFxuJL4jQ20 zmKf#vT4?$strips&VF&3Js(Ey>EXebl$=Z;{pFllgsir9f++F&>MD6j{AX)#?@#e? z%U~Pv;&$*B6we>fz6lA5zP`D!5k7@+%GKv0wbp-m}nF7tqM)?D!18qc_|Z<-@W77(NS@e zBt!75*Afs@a&JCQ+rUly&sp30s=JR`dZZWx5L=I%P z7#Z)#Ev~Oi7+?j8zU}gztAA2|*o)xv6R|co=f4eCK6R(MxVRUMW)Tys1-Jz&>Dk$A z2*f>kVo*L`+>AN!3$|XLtmoZvq>e*oWn>JGj0F9TX%y$d?E&-Fz-A90N@Nkyw& z>Oi_#KUjJfm6#ahffU1aHxc`1yaUmkMO|pHU-%k(&VV)ZLC6&crw29%-@j|Y(@QX} z2y+V78VlH`A>W0=Gx!DB**}zk8$6vcuuSxSe*7~@{P~>!?_Zz30-&l(QCBh2S7*UU z=*S!USxC_X-19;5?eLwKNVzZrWjl(U`A30&{`3Kl$RE%XyPAVqA*+-N3|#{WB`zJ> z_=nM%$wC&644sA^Z6n$susI*U@2g-#m9!LLHkI&$RWZ$CEPIj{K8e0ic~O$ogngx&)#FRXtmbMPE zBE`o40Q=#8!;H?yL?P}cH6~Q@AV|B~`*&9Gb|!M*QxGz%Z2PK^OJ7J<(x1ho_Ize4 zlA-o-NPPCzeSTWC;gEQ{ouTJ|QLT;SSLoBrvn$1m9T_jm9d7tG>$>;|_$>b795O6- zjxnc?)-2keY3_F3E_&8h{^`-(p5D}Nsfjk`1uD0G+V}m)T^xUdhNuX(`O{| z`z#jg<0hG?W=PvQP;>seaz(rJj_Yn>r_38Jw!uGv4lIXJ$bwsr=keZ-ee+xNtq#SZ zb-fd3S0(0ulFV1+;j^1+ML-{k#J}Ye9e(jm-TWe9Zr$R_)!2KpMHq$Gm#NERypKduf6p&O6V7oNRQ;wZ zrBA+XH(uWTZ2L7n2Z)}yo{TWF-zv?f5dpe&cRNbDBV}*y`D2&In}sebuVhpSD5Nz7 z8djzjvUMuIWTbr$uzjnV_wTc*l#2AcHqX*uE~%T+^KJHaoNaJMoQ*#Ja#A8|hJ<6w zN^k~e^XNMwhde7<8dmPx&z4U#J01lRFl8y&6ry}wa{5)COl2lH4caz5vvlI67$EEEQ;NuhVbrM#*l1NkX6L5l93ui6I^Flb&~ix!0vtow!ZHTb^=A7gfojnbfO}ztB+*SMf%Rv znKyTgbW)NIL#oE-MGsOh{*LnNh+P$Ro4mzVn(Z&A_$TNBMUOPy(jrCNCLf=i&w7Ls z<+6L$TyO@y(3-EMq`~_Dclq!Mc2Q~M9&2!Hhm(`iR~l`an2<$y4v?{C>{OoTjdBdl z_%oa2mv;394?OlQ(O$Xr4=1YhUL$2R1(=RCYGk&oUG;1CGf*#U!zx9I!*Z;@EHQi? zh*&2!BTg9X4LC6I3}lVQ#A4^X~+8e|*d7{M7gw zF2nqL{f|T>%eT@|?3-masm)zTPYbaLxl^-MRRW>F2`fQ8g0(0H`x33zs8RD3!tIXbL zc(QZfAn~na1SOX|)0o^?W{uYA5K#;N8Bqbq;1o1d&Bmhdh|Bk%LLOGAk=u^Q2fk^B z|78%_Ti{~J}qV{2}#g|$30c}^;FZgZL`@-Mc3^HRkL~sxAhp#Ly*`z@kwmbR@aae)PsOM}~?at_r zO)WfjA1bPh-nYyhOH;?GedSxinD<;dC`USn&i9pst=yx}cb8zmJq}ZzgGnuw;-%aB z?R$B_iD!FF7X3};(YJCg{W~488lVlYULel$xUTSpbuD-=(DVH1KYvQe9^KtQ&miHZ zXcC7p9USQlSV4}@p3Td_x<=06C{am|wtzrH1 zBZN~gUqVa{s~7fl|NY~C|NOta;-A6zzrXSS`PU~j3s82>&E@3aXm4!=000S5CJhd0 zyfh01)0xOk)-a$Mghe?YZ8Fv(ME0A*=y4NtswPV8OPzNK0+Du>wi$mSumUAC)8Opv ztgNhTM4D7csu(wJU=iM%cj3ts6Llxg>2ZN1cPVv!=CS7=uTyisC5s|Py?;<}jyzU; zm$2T%?0s~sBJ3d`B4E`=KknV%-;cDV$PDXjpU>--reaH!7A2@iBd(CjZRtVSl%_4-Sie`m zT7wkvSQO>ugU_q2E^ioaVjv~3eeS%YhEl05$vrI30mW6Od92TaZGC(hC)x_IU)-aO z3ctxG3>ix7jxtx(z<^%lDWg-+UVn>_umWA%*2g8;5h<0Y+7{39E||t>*hL{Fwst7r z@)eiHj#vLK`PuZx?AaS0BsT|w;xx4mzBQ;FR=cXm z`o*MWeaa1Ul%V)?!eyqg`4SQma*S-acAhn5_yt1zB~@}gSG8ch>EVutl-`A3P*EdC z{roZ)3zT#t!o;eE|Hk6N5)VhdoLqPC!>>}G&zTXo1v@E#+|=5}rt|Apu$8ehH;)Ss z|8QL!ZYF3ZGBRetRYXlybp~NzXt=(#6eQi+0DIWc(VCQ$Gn;#>_h=yKRjF?@Ouwsu z_57L>5fK6EMT=5|@%N7(MvEE=VBh$}!q#@}#}8#?Wg{^;XXmm#oUYbZdq>B6Q~CM% z$H&L8Nt%!&F37if_RI&-IZ|^lD0EXPhvZt8X^lH>s1^A_WsTBCTFc$nuhmN5;z7cm zog3qJK0PBtx{jhSMblyP<484>*t`1;Ze$C^^oiS1)fq^BHzGn2bAk;VudJFepD}6oQ9sj^_TgG zZis_U0L{lM`PJ_p%~GGOm2tFxUSj?7FciLR#J!B-w>l`s3rhbM|9IQQ?Vo$E~tYLgN?EGxK3lb>h~pXv%!+_lb&nuS(ZKO|l%; zpw3tDz3x|^Uq>B?$>)pu*b{=zUZ7eTW6h6@h|*QjcRFfUe0M#bjbOU)BZ}QE#5>K3 z(OCRlZr|9*wQiyS{ALRI`fokUi>u^WixZNgw^_TVzCN$;5_I_;g;Na~?axJxDfH1h zBhl}1W{n=Nb7lDI`!#=I5cM&|iV}FWg1n{X;zAVU+Br*W#pA+uSE41gjcZTnB41A`~q0|`Q(dl`riQF7H8@q(>5vI#4 zOQA|#rG?%C2E+{e39CytE4-hq&cuXA4)+X8{~4dc-|D!H za3ab2!NsXcj<_X?P&K#f^LE90PGnWtXY`0csU8PzTl%!B>Q!EKf<{4&K)3Jl2$Xl% zXI>V%<&O$a3!QxQCDTNSe{B&)uA8Xei;TX`A+92YQRWQZGi#dWv-7ioLLhO!VG?%O z$GtDUMe(S~@&IWlcryy2Pk;DnV`i5yngTq~l%%Q;aV)+qgSmm6-F-`T47ZI<_Lpph z*VQkh;=&#+0a((ThRUveU^hz71@+&L=kb-g*;UP_5R7N^_0Aew(9j`z<}N3BgMWo% zP1Et=uOc>PW)rt(Zfpx6dF=gh6Dx8jM{yZA1Oga!{SrQY6U@`4)z^_Ou05dyuUo85 zO*2~atE#kB-I*}ie+45<&CP>J@Dw$NQYE~lYf*J=4V_9%y&kMRV8;-WRFCF~t{ z@V>To`BXi;riH(Zzy*}flzc#KsTCZ6A$=%5@q7xh38to|Zr97g!1~meRnEi1LtVW; zeV4MuZmbq&r|ky{t!1h_n4u@H#3QY{jB%Xwiydl?a49KJMn=GA9$PNr;SdjvR_i7= zp-9b{?C}R}pZk$l;a7Yf*WXMsIkF6t78Ddj+!s*CC$_V& z*x1@4vE+IAUbqP1=+E!!;u19IaCE>Pmr_{xt*uQKS$58rjcFF?GqN`_%pNG*ucrWZ zXX2k~$H)0W$+LAc5L;6VuzR4AgZUkY7u@kF+4pi?Puvw1e=u?an2x4*dwbVk3T>rE z`mpn>v@MRb>7N{7*cmUK!yXlY6*o4N_iy;NqEI|f#~f_#Yg4Zaa{#N~{nmM&dvl8r z^jTImR+MYmd4^YK$sL2MO-~AIvdrx*F5n&R8{al1&dWwc99}KGWXa|s+&?I!C0yy- z{3Un}_3OQ<=TZA*_ls|Jb-Z8QJKM;t5mZ@gSPUv1adWXEqxtsL7OWqmQj%t9TyW(D zV1AyH(1hMDE_97GU_i^h%c(yv4=y0QHad6FL+iIf&iXY)CdSd4}{(Dlb7rGv&|4rhn_SgOGU>dTqO=4qv_wv%>i?O#Zt-o)-+M1>bXJE6aj;0dK zW)i%j9({aVM*_W(2r1mbQrZ=9PymSC58}AC%ffuZ%EWyAorgEK& zi;IPra{Qi`w>Q%yLQ)p}vbC?J1=j~A!RU@}K=iot^|>qCpS#9e$M~=D>zf`d?5Q(5;ilh} zOK;K9swk@3*|{X$J33@;HXWOsB=KdsIA^vzCn=WU+#TDwo3?w3nOKxkT8|r6-H{OW z3!b5Yfln-vuSL{d%bUP$9F}to|wt! z7K{}sHArPhgLp`T9pm{hS( zMM@Hv%8zk9j+c!izt?DL^$PRc_}E~>eTU0rl$7|y-H^9yZzu7{=fW_8@-jFtKOfS* zej4%!9)mGoB-npLimA`vP%`o!Q;HFX2Tj7*z%bYaswBQ^G12mW^#~ zLyN1bs~;|lV+g{IevFm#h)0Ka_q>l78T=eA_VV8;)0$m~cy z3ReX0gZI;UvBQT<_mt82`Zv1A_*B6d=^&t8NcieBnIn7Rr`bh7-7VPKQ09_7vS+p` zhBqpU%90v&y&d>fg+`B(?Cj)8nOJi*)33o7c)W3FIlLvz*ne!o; zvAcXLVW(YsthY_WU2;jPHX~ng=_a!V-{6{H^g)xrO$i2>)Uy0zsG>u>@Nw$Glesx7 zcFZKtjc;?|mJ95*kJs})A*ZuB@{*&ISrc4St#yhw0B@>{wk8flA04ZY7t&m@frY`$ z?EEKiN&?gjEM<)Q$%OeIIXrt7Emey`bu~BR+_W<>iHe9wVZ<;CHua#tORX=+H?_~q z&O&B)qTYvL|3{oM$>x-KTBeb{r>AF+n)FgAVj`Y0EmKQddml26sF^+$w0t{<_utpd zj1cRrQyEc{!y}!y_^#eI|M~=A^QoR5O>2F6HEiDj1S&B(IhnsciG78-`)p2;8ZEJm zG4j@RhM!In5Bkz}D$dWYA6?A=>$Atq@>Ztvv94U@jxJGI1u*;RMpzWbWNGGvkqneM z_sZy!HG;6NP1k0i3@Ica2#IsJ=t2`#7uLHCz+o`vP+H8}I&9bO(cZ$>U|uVFH7)6h z1R0usX}AIT9f@FZCh8-tqpqW(p`pv#xpvFhK+fNsNx}OoGb2NfbP1Ak+e4EQAh~O@ z=rqzB87V6M?{_FQpH5v+2~0RT_ENi0L2)*v7F~c6;2v4Ot2y-KP2uMGnoKgXxTX|C zep250!qmv1A!Qvk&bY1i#EX=h#X(D+6rq3Cgj@1WhEBXp&|^$FB~Iflg+mHBpF2Y5puY|U_c z|6#s~vi(*#+a<$n#jY)i&5XC@@`kpW1nmeOe_r*>(p1%|Mm{kV#b%<8DQJ({uEZe$ z5R?suDQ5Qg_PsQdj3*J@_g#q<_a5^*sNz!X&(A^y#yxoNDl@5bi<;z6G`K+KjLJH= zm&t+5zn&edCXR}NY`;-={PRFMV-piFlTX>&+abMeZm{Y6m7NpQlr~M$m~QCdOgI^i zDIV&_>G`u zwpK6IIWjol&Wd9Q>D~+7@F}R!(PwaoVcblPnol2?@g&KsQS|7g|pi;v2qP}hLA0opv(Gh{$1le8wHobh;C%Fx|eNF;< zgX|B!Tw$*(%h#VM2Z8TdbKZ}1a;l8$%xi0uANJiI`Xf7PGFxk2aOw)zWY)6x_V#}0 zB65iN)Kq4D0wzs-f`Wd2HIyKZd67GM=m z#&ixnU&1;xJUG>9TCTJ(kP|LOy?-F)jzEcdJvLCOlEar-nAz}ql8D@Ey`rx&O-q$y zYHas=Bk1GEkX|D&;A&Pw{4&B&VUuajU{oS7e1P@&(U1_;*_J0pjZID#WFT`x8Ybi8 zscihO_ZPbcwewo>=v2%et$ISdRv*75J>sR^_kG0nEBrqC%~bx;^YpZN{DL-cF3{a9 z>ktVB(oq0e87mU@T-&MY{OHeZ!hTis)TA+Ketz~Xq{*?R3U-Mlau2noYhcjdGc+;RFQa&CU3~zp}pqj8|oPbmB=5s z-mSXqn9r?sYsKZ>ZE0($4I&rXWrQeTULz1dWP%uBo7J2LZeCq|3nN3(=P(t2l|ot+aU z?()WB&GO}I$c@jnwY8b-663N(#`*<_O4Zt`Gmg(iIwarR!RaBQhlqU4dxuz;+D z<8TsCw>}0k6Qu?A2`MKpM#y#-z{Z*;#(QpPr1bVK>u4UB%bX%0z$RW%=>Zzs4QY*%N-$9iv? z6@Qs~q&qKkbHY$sBfsj++p;K4Rh^r3)#a$7@)LIpOWPQTP|Eb{7M^BXp>?!|Mtbn~ zYfoH`UGJf}n@B!XF(g%5kx(sDMR_zZ&-AH%(Pa?3qP!CSGip-XJs$Bevkn{aJ5gacL~&u^ zCr{=+m|{jVckK$Zy}ggwsjF-!Ue1+=t?3B2zRuNr!Gbwq>cNwj{;b0+8EHPV`(|F( znQYTx1MryR>Ew>%v%Jsd*H;PP&z^lfZM~0`JC%XEwTS{grD30!Gwcg` zTC9n?zs}fO+l72UE|^cdXsV*K{=T9=ZoihkU7L+oIHzC~`@W2|PaVBXlSnsKdPNKk zd-aqecT4*2&Co>19q&2*OWIXgRFqL((!%p8pJG9bR(qCfi5)Di$zyT4_jY1}%aPJ9 zvp;{=uUB{<0dj|HN^yi~U1eCLMF}}Ct}VAzQ{I*=*REzhCUpL#DOAzW%rtnJ`LsN8jldieLm$FvLbcEcX5=GZ991aY6d->g3xOewg36QWf6pw3>l zpGSBn!tJ(#BA_%-;q+Un#k}}N<=1+0@)GjPvoGb6&&kYFDD({NgNdcVzwF(IX4A_- z17(?^C?NER)-cuA&P9CqSY;Da6s0KTXm6HcrFS4S-+5xR!(Um(g0Ww&pLFiI!H&Q9 z(~8Cy`TJxkO_!n4{82w6qZv?#EVpKU9I|kCw@Mrk4$-}J+TZ6hFnm|_+vGKK#c}FU z-_OAb*At1i-Pk(32?;IjtxP1oC#s;phn(2X#%5rQRR3>j%no2jzO~*6dg7>e?{*t; zXVRwC)^FRlw{r_zcLhOs9Iu>0XHbxQf@(k!DkaByTT!hFIZqy2Ugiqt?JB}J2(%vS zNbPUOpVZ#WhcCA03zXgr3*rdYON`oYeRPdy!L-VF43S!vD{$(q02?~?nA_vtEOZ8P7mZf4-A}TwPUjE7WzF@DPyP; zZp~1}$RR?vCd=&e@~zU5^$CvYv5)k(FC}<3Z?&A4G5W-+e-RfOj?hHNFSv{%=UJ3NM`LKgkoZPmfug9%e{lpN++-;5zPe(t1>$zW_d2o0eB zZ8d(;od9|SoL*lYO3}HWjBK6F$Hut&YUR`eJPr6@CbA^YaH(8=4>O6<-78syAX{bclgEZlq^8!}K6D6xCWu*4ciN z+1kc?v&=C+5A`;bvPl<#*%!Qw2g?4MJ6=w?OZ?uuDn#kpClUt)=6)pwUm=ktgrl;kdnU0Ed8j_&V{hH&U!*q!GdJH3< zzX*J$YKFLk*TM$ru^q}*tj^@*oX{khZr%yzFfU}Rj2L)J1%x0pQ3%ln!#!c zBav@!`jkvJ+T{zM6FG_V)>Nw8lTY&?yhLCt;N++KTiLaqN`{W#f49!3XibT?{YS-SPEI9z(U}m-UtTSM(kiz4>#)TG1x~vFB;+MR0$@{j{%?xv)E zQ_cUJDCjN|}Y^iB!8qaO&fQZx@oR=!Gv|7tU7>B2{M>MPabu-}9*a#~vsF0#V^4F8I zadMLWefuSL=|x({RQUb9ah=gO+*J;J_EqDfyzzsbm8-coSCGh{Bnn(7(bxpgTdGrT zo}8ROWpJMlTIaEE(X+Q3ANMlqKH*va@Jcc5ui8scksc@B^iA*QtuuULvaYJ9YH9ZL zaQ18SaFyDp*O+HqH{fWWGGaay6{*l{(E32Ns+57!w3gAS3Te_;dmi4OM6!7tpv3&3 z5&%Y+t2=RtK4=kGR^3d zZbH(cyIyyXg-<=m!THyT$8Kbf7gd%?0;Dt)ZV(6ffM(vJ@8md$-&k_X#r5d%`2pFg%ZO=OfiPh{2?W9;52{4fG#_R%jx`kwh5>ods>_A((vHHi ztzH;uJ=+N^0sty!sY^H(%RhABiiwMo89nM!_p!40bJ+zbHm5c(X=iH>0%oR+o;`QY zZ?S-SUpTbkV6|`IwRj)u^zsxTtqP|9d>E*Qv27Mvds+u_%G)(>xI4l-u`1S}StFx( z)yE?mTxG=YwWl&IR^MObrzQOqK<55+1;y%*YYR?!xr=_->i#JgaN4`^6vT!9@US4o zDI|a;Wl8^V)Td^pZidAF^Vz2~z^Qi|9;Cq ze>^>ndu&*u*VDbfEC~@v(J81 z)>8%T{gYQYsC*zDW*r!@D2FIOgxKpzs^7A@4r$uv%U@iuH`bTUqw(`!EGdQAWQ|yc zTUC#4U%p8XCX<3&>AsnZJ$pEZF!ba?0Kd13L9KUWp|6F5+s&&<>b zSur&ZzrBwatTQrV1_C>2D;I{D9PC|%Bi;<8+zg}SI`%3u05dnQh_rxTpy~1v&@u@T=$5X)t_io>;G>;&Bneqe zER+K6+P-=3}orn)ZF+B#6XtZ;NM_3Gdu11;GDx$xwq zTi|C7NqVl8B>h@%mD^fXChuP%(>^2bmY3TmgPl45OKn_2I|J3zgL;w}=w_}t1gf(H ztx;x@lgs!4!6s1DiBPEXFjeFp6!o4C)k8--zGqoYW&%<|D%nA3xi>3fr0b#U%Bxi( z8>C$`MC53wughu%JBErY;_Q^0N2$jB)I2l^tv`odmyXPXV_l!QY<~Q6Tl>JD0(wd1 zSLNQkL~t2LT8jC4L7|p5`f7L{G!d<+up6I{1QmFzv{&Ydj(hv}bg~%=nk%{Q0$Uqe zduc8v>go_fJ>7qiRMW0Ks+r|lhxAdXAGw))J7*z^4D^Y_fzh=ePanEg0n$xSKr6Il zq_2rOfDt}@#GSinxJwTW>u*~%>+mn6TyXs9j|4sc9d}ZCrN5i4;W-1PZX{rWMPgCC zK^$D2UA&*S&&p7`6W{@`1}#DiRp`B5-E-H#&PQ-VuVFwE0Cmp^#|2@s;Z1EHt12X% z?A-=sWnPQGuQXR!AI*_T7E5GvdqAp26Co7 zjrkc!@Kszp9uAJ|y-|d^N7T9W=fP26CB~iGeHfp_Zzn6S`?eT~dtd7D(>r_k(@~$t z!Hg+0mO{bMqsa4D0`2`sWH5>yNO0xGic`g!tUPC~71q;`#G%lvyR{=xaObkIi@k-#VUL<4aW^}g z7jUf3o`gHQyKO@K-}=$=L8s#y+CpjG=fQMx=R*QPs<6;?*Nw#OWcL$qTprOhIh4$e znqD$v+YHPm=fwGh7nO}eDg;YTP;U0;t@KRc4}r6OrQ4w`_`Y2-bUUu|i_W47G;J8^ z{AIHeQIe3<2YxkR?9=qm_tpAg6+8i+B~zQ=dqY&tAd;Y_jCJ*N2y~NF`w9Ms^}2Nj zFfcW(Czly09)oKor9*gha+OQL6j%h+>zmo{W~$tkWhWUcD~&LxjR`M3USM|<%kzdM z5UR>G;=9vV|MWtJAVCSAM756zvu%yN=P+--mjXA2gM-72;f9nHFej*$@Kk9oDR>S> z*nj@+R$X0#mEJyix_X7m`d6zHJk!i)^T$4QGfSSn1ZJ)WzdwHt%oU={8{TgFdmQGu zfzj&Gh9*x73#81Pe$`(Ghw!8Vnf()Ap6Z`}>3`jR!Lr>^{SX$Xga`OAIonv;N_&UM zyvE)Hyw0+4-^SG5iw;Q65g8wXIf988-tX$`G0sXHrGX6Qy1zcDUGUS^^E?=;3Uz3tTH1IW~lXA{%&Rf*lO>eTH_w*(!W5 zwWW-SNf@c6y{b!gHZIb&VO=-gLMyZW5Kg&sth@{cgijJvON_}08#yDR438EZ!gFGl zZ{y48ny-gLZ>PCOL3_H*-1V(K-<<=8L=%b;1Vzvt8N6wN=&l-*%{~=*5Tap_6`_V^ z4TpZHp!fapV5G7dz9Jb^z|G6n-@nMeQbtd<88$v7oFsz0>%v|?)NMwpztlGNwwz44 zeqD*`Erw1N{;^$aR|s!EFJ2w9TcSPL|DtA?bYn-pbD*}ptYr$CRR)R8{nBJ`4w8*B z0|g-A_uF&iN|_&!P3b>>{=7Q%C~Mr2tk-B~C~cZ-^CJ3RY^}mmiT157RA8hZba!Q7 z={Bph>hg)16nTBgzJ4x)-=ANUMNyX4%k#n1b?Trx4jr zwt|ao9AZD&zKOA%kYk}OHVjmqVV~90(-To}>H6JAt>Fl9eQf8W02MG}b=|!HXDs+G zDG3NlQc_ZOOu-v4otr97ex3pV;#5zd3rZX^WE|iwF+h`W3o2R~;4(2F1ctmSXhU4c zp4FEheLnu66(bK>t(%EeC&T_x^(9*yo-*sc^JL7u9Ab+gWtE+ZHJq7TsbuUt*&LZ} z;&o9JnWsO3owe=3{i7$NB6|n5xrDT%x0+K?6LkY$9!lI4epN>B{q~?xv7y7zT`rHv z*nqOPVKv}7J0DUHAnXDUt#$7l=;CRMw5LeCOFnRyKA zck14F!|ZPcm9EZPo_!bY@B9yw=JG4BWa4E5FqRxbDmp6eeJc&ntFBI`8k1Hf3XSXx z%9cMvp?0q==Kn;c$(4h@YK~D|LpOs6c(JNtm&LXUlxY{VTDb=J-#`r^GWB`QSEJFX zC|HZgUGj5lbVukyX^mm>sbCNZ{7Ouk6n}ISfGQvyl};~;$w#Uz)W4JoecMe+M@L4> z54=Wib(Nnq$m@B>OOGLT5$#*E=S3PM4Cpxw3 z8ox(14}X5|9rSN1Gy8RyE*745uaq<)pPesIrm(k>YG(b&8TjfIC+<6f{YREdOAZ>? znY(=J4XOdc9PT7N-O&;9_SXWc*QiCR$Oi;l3dYcaM-3~|t!HqeVy|UpaOMS1vvYH^ z7yMYKmpDwv(8w4JlbyZn{pJG067+uv54< z^7#iNkoWtOv`XAnPy2@ZvT7Gf`o2H{kRe7>Rk_c;?Z$S6COyYTS`j~!awI2m&u-z$ z*poGXMFCfrV@Qo&HbWWrxX6dqk9nQ%O48I1dq7o1+<8|;SzFS{R^yYil&XV% zA0ITn<^sewBI^8gpJt5d?5s~tTEPbya3-y`G)AeSncTy5oDDJn2#fiej{_v zNMX6X&A$iC@dlXumICz94EqNi1yca7K~USvbxyy|SVg<4JrX8_QVIvx_u;)k5y^Ja z8RP~^jhQ-|TX^!&v)6J|ZgkR6UG?$o!qfGow6QQ)N*g}Ep2A?Wfj{DV&eXJQ`YGij zu2*35Ro06uv8wmlK`dPBi!^y^_q&fZR@P``a;@@3AULyHZ$JLtH+iAI`eZ$}m%SON#7r`9`$Nxfp)hvL$UGhO zd$vidq|3T6FhtaR^7WJslXose1`7`+^4kS^G0eU}gNk4BE8XjzUJ2dV@}RU7d#`Gl z$uoBvW@tWH4xOPYx6r1X>K#Ai*wNfq58CL_OFF_t#@!0r(iv7b790&rezlxder;E$ z1W6ea3)agA(D-P-oQSy*jPlSyOe#g|^;)`z_hgjB4`Dm7wbdc5H$^p3?k3E~#|N?H z)2HArSelxU6cybIOpMR=7ZwDfad2?(>(>=%K@ZYTa1!T;+WGYh)SNmxI^te?d{?gm zWw5ie1Nt&8UpfGkYCnCVrJ*4To8(C^Dk>tbIDdk;Y#bCEOn$w)tqpJtbam^QeF(bc z3))M|%9tdboSblR175xQds+qh**Ejr-@or46-9~%Y4Fwc^~v7etw%v_Hu`C4honyj zCk86&irnR5_(ZrzJ8#+R<^E_H9B19@nf}nwz&@flyGkvX;bTNJ;yui{z3h}J(Le43 z`QHfWzdEC@EJc(mWBO0SuWlJ%}1>-1fb$~ zQ_f69a7d3yaNR2b2=N91O~PQ01c0~H^-7$t z8YM!K3Pl23*Le64c11XEkv=>}r)laBy}!Ohv^YHMZ1-Q3 z+YI2<^RD_?3B^w0*OI=RQX|B9Z5BO}IW8VSM7%*=0N zd3jv3Pq2{fs~0c+0-greUs<%gyu7}i9)w1G%OJyai@&`l{Lc09gsCaBrlYhpPUh5b zd%YRBjfXKvgwo>3y?MeGS!pvh**N!jUJH04=;IXc?`J}c_u^@`7r5SxvSMHC_fB$V<)sI(D$fK%YqRwKBH8v`R6JZz4~X8r`}hzY(<0#8 z-a&A3b|1lMF7z}aC;zphKcAa>0ZG5clK|5y<#(IOGW-{+Ei&%g+?|k?Dcaf2kP-u& z!GKB1TT&-FI+^`<_v?ObJsu1guW(4Dr)>4+$QTm`(u9@{f|cohaMtCsX-OaK-Ggu1 zEv$f|JbLsBaQk7>&>E^Ktx57O^txqm8+?OD3T%C2Ofx4B3~W_@Y!5Tw@*ioQ?^)v+ z>3O^n50ng9m7tK2JO{57o#1_y^pzEBm3t|ROFG+G1<0WRA(2Y<6Fbl~{(lg^YPq?M zqwJV#FNre|fzNJ}eP`fQ36ht}UE<`w(h^}iBt zJZ%3(ypfud)5OD;%%fz=!Nls8-_RoT%i}GnMzI5$|9k$0|Hd2COF3eoaeg(f4_FXJ zK_`O&=SBaK_Q@`F@h+Oi2B)}~8|qYVamWexIbsF`c^&BU^~cC>9`74}0)tq+I`hQv z#1rUNE3p?TApcu6+;vl_t5u!CL7r7J2AAO@cLuTDEFy!9*(W8|f>B(20;`+0#~&Lf ze;>FvKoV?eX(<(Akxc=ch;Kh;XRr3_fy<<^^vorbwUrgJ{OP?NH6F>wk5d3(BC@)u zM|-cG_;=}MS&15nLv~d>DnFlGUd8&WzprntB7(qkbK^2QhlbaDWntymvh4RCwBEzS zrj=Gx@v#Y6Oo~c&b`PX#Zt++xv1$uHJRF880^XK2J6||Mt^^ z39~$ubS>~QFftQLYBvoe-F$WtF^GxbjDyL{@P}NtJr=7)bu}!!HR0C&{gJet+V-Yc zX87A$*YJ&=&E%H4I`PZSuYsnL>pp}ovil8_?7H$*)ocgX*^zE;c}sWgW-qTo7XzZ# zY20S!`C7Mx%W$8>Bch>~Jprq-!S_`dcUi;a&Xx7Jvdchv|ceSK186v3`wj|c7LY@FK z0=;34_ZlY!g+t1AN5$`-7~hHMJ(yePO-z&9p!LA@DPEyY{yjQ$Jjv`+??{w$v9fc!)&f9s^h1=}6u;XI{aRfaRjBHe-@coDQ!FoJ6Ia)QqKlS>OiuPaV-AiO z&Pz1b8m7T}sA=>57&IrN+ZZ=SundOq)r9gwSAUmfoAG?U+Q8D+cInb$%rZ9hTsbm( z&_WYpf&MqKlKo)P&m|=Wt{R-2@(X3f$k$*~z*Ssc{_x>Libvdmm2M8C2m^KXnfZBO z`63l^m%S(Y`k1BY>JxBqaNtFeNO^B`(fH@jcl^XLsRhARRU++kOG~2phsaxETd$o) ztM}Xv17yBH>q<&>^?`ENXQ5Sy-e}DMg!dEc>w{&^&qKUs>|7p_6a_9mTvPgDy+j{} zWEPqU#5)h$nRDipYtM%{qXS+cPJ%?DqoXbQChy*1)8ASc6e_BNjIUnN*qFkp8Z(E z1&F3N=DVwNXU2H<7t3$=(b7J)TpAX<3~K0EZ#4M?f6RO4rxz$Md!r$+2`dS!4EmYv zxV)u+nymO%Yqj5eRrM&d(QpKQ=ZN3>CgD zgEGt+F#&xI?<>9}H6fRgk%MVy|Vp3zU}c@9e%9*iMl>r~fgD-R*`-2#0YxI2V- zZOF3$UR0u@R)_*3U?B(LEJJMfHCRP0FE8)t_*hzVYmp5D5r+5~V$#T%Ur`&qZR^&n zb64UclW&+6JHUGv7C6g!)Mon@p)1hUwAwFBq8gVd>e|Luv0FSlYwBz8`wv-J=3nj1 z@jSo$yrX&>u)aX1wN|gM-N%e?1PX`ZA&c0K)7mFNlsY`t>#;VdZ*yPqu9(*|h$as* z982HGWvHk*;^9d%Tj@(OKf57tu=uOi$Z@J@bDTF;Z*5Gq{qW}Q)>MWGNmK_%E?1J? zEKXD~AaB#Mg+`s%+z&r_k*{PLKG-|(4$#+llbHGtJ+#74rZ|GPX%gi2wE3rs+Qj=$ z7v`m(-~Oz&O7EUp8mY(;miWr#)$Lb50L+XqP@-FR2N~zeCQIRBbIK(B{((GuK|;LM zL>V+D-=8AUADGpV05f#_~enexi2yqb0e% z)MNT*&WeKfK9*xbVBvK#RDz zyAn+!l=Tds@ARCE!RA#!4rPYJ5V_fJ#mPxd(-_mD*&t4b`9~k-3RgE|!JqXiH`u2{vDLRPL?h)g{pW#H}X;ooi6x z$D2lShFGB<(}A*G{0LJoC+xMSo(7AGh{$kzJKk={Vq7cJ&6|ULeWV;=h7vtabL56C z?}{O#EXu>AaFC(-3zqNLH;%$NRJx)X10mk`0C9;KaK07`h$$p5Khm0&QjfA+yLO)@ z&>aj+40FL|=JL9&oW&#?3ZRbQK^qb1x+_O4Lp>8*x?h|L>F#*LK|MCwCf)tx2QuB1 zCzS3>BQ~>y0wXn^Pl*6Qr#~*9bG#e`u7TMZ$l&uP*7rz`9Re%&isue3L*KSm)qd0w zk?a>BFsu}vixY&a;fKt{2P@ayqel^qsJ7oRDaF%oY7Adg+kLP+Db6$_j&r>{zcHnT zlIG&RymX0JbNH5C#~dN-6#+}osszEGnb}-=o&Vt%8O4{fO<9J<&5+EK@5nXK6}d9z zB^VaB<%md5OjzEq_^6(atykwC$~A@iZBZ;YHs14__Z7XO$v(?bkveARr6);!uj)fN z1jOJ_{{&L@a3fuwxwIYPan^R@eh>#!LXddk%^q8z2nNTUF?p^=_VtOAYOUWc_u+Du zuY=cK5H{sh;Z@eqbqxN7AKwofK)ETf>^2L@#1W`JF_K~OH*H%UrFRdD#q~Gbh1i`- z`el8O;CG)nW0&VC)l>K_F+VpfD02HRZjzSR z$Kx(VoL-gr#|md54*9gJhM|!RRc@C9bS6}!m?ZUBQDMhe;VEqFwH!`$p2HkJhqlBh zVq!+C-RYnWmF{nFbWHc{>eBs0EF;c`kbAyk`CU+*TUonzv|MIl8};oh{m@wyUNl|v zmj-O(sE6pYGXEEI?;X@s7w(N>MLI|q0RaK&iqe~O=}46>y-5j34Mn9zK#(q7x^$@t zT_H#Z=^Z5W5_+%Sitl^AbG|e8&fGh5?#$(n43lJM@4eRAYpv(`l_w#-nm;Joo(I)Z z4*4zQBbd0!Ncd-PGcus9Dm2;SGe4~?%je?-Wf98sC-OFc#!G~cKOqcdvIg0FG6?xY zX6F2}KM|shz!R#QZVrg-u1}1Q=YN7_Jr+%NBDSfunbM}Z?;QPmW@A=m;fiZ+d(NU3 z=m1{muHKvd@q&vKdKRLqHQcy#_i+C(Nxv>asJR0nBg)Qw%gg0h3Nx(z-d3(8FdEi0 zHJJc7hdQ7M1dkK=mfw80$av>&%F5uOIf^}L#dz6J_g3hs9*=?A#GZopBjT0)NwCG= zM@``SX=vyy(vTfk+2O_UdGydSn#Y_CL#yNe3tJaFK)F4>+Pb=VC#3skwuXjNh)M$n zxz8ynqE_?}z$mzU4Ns;fquHm`7^d|DV75ALOPQ?KJ;CcT=2ubb~H^N zd<{S!^{$L*QBhBFgThK}8*5=DbNISU&K2%Rxy+!{YM_-Ls}84@7D8}8EX8ZeHxNonP)=uh^=sUpCDB~aw3x# zcnE+R*b2y(mp~|4<@O1fEA;@wKzhz;R-&u#>(NWv(Ms%5MU4&vg(`poN*Ad5L*l5o zIY#}@<@=uso$1OvB-mir1ymFNQ^dqNX4DnrkMNW}E-)bkQZrwD52>}}-c zM{(u!93Bs%gulTe9~b(NXzKlD{;mkt&H98eD>7qz=0M_I3Ye#dT4`BMMGZRZcY9!? zxQhA5{lmi`8J1ULwJoF2v(wy(pWVG2AA*>spnn91gIRpwGUoy2a_p2Bxs7uUP2Ze) zlP>^e_iFZVuR5v&#_9AXN+6vFVZ~guSl}@RAd`36mykZom>;jaSVrkW{QZ6{cn}Z* zPtH%hJgxfeYw3rECoOz!my_FaK68US@6%n0er*81(5t$|jZ!WNET?!6S}%Z})ziWUKk87asPOj|CF=d1;&0bW~%eJ4RW3{d!X1ojCpTXP57 z6=jxmhWlzQb^tlozy9#}ypX8tUeaPt!z#@V@IqA2_R^*2ul8s(d^wy0D3ExI34DD1 zmKB+qX7!B51_p&WAHs@(CgAVDtcRCM@N!pWRVa1s)6yD6LD-VwE>xmoOy1Zn>g$Pd zHF@u-!UR(US(?aj%_!rpOOxL#m}2xB;QLbwPd$3k&0b*t_LI_8R#jV+apyvxKpJij zw-gJIFi0W&Aq(zC_H94@{CSzj>R*0&6BRUZdAb`=7@n$MS1TL-u+w>D*4S%hBkKVf zmem{DC@sU$z~evNQpj06SVyTXtefkEr-g?7%*UPy)s}i;I{E88lU^%%?CdVh$y+Ym z6Kfh($BMUok4ig=zV$aaobjBMgpPQ}Qea!e)EHQ+nYdb09@+#Xt}Bsg8+nHwd-j7V z*vrDr?cmR&wO}?fp|G1E56lj2`eyI3yd9(&h^Onwq#V_JU&8fdg3COO3^Q$jEdR|g zgcSV_xd-jT5b-S%&YW{#@|f|llW}Hn^s4T5NJC6TPHf)vbqNs%PD_{WlaG}zUCZrTZyuIUOYbqP5?Q`PT{FDy6DvpjOJe4X)YFa;=FGd9bi_6W% zOTfrKo|tzIx@k>!>vCheXF2ZA<+a$yoOLf^4*`NH8~)1TF4Y-agu7k_^zE`QpK;~+ z*U08fkd5AHg@TP35N2v<*xu`#Mz%wyCJ@LR3*b;o)pgOW4dzvxVUutb2uWF32 zwGf_`J&($ZGOn_$)`Ze4ruzD|7-<1d=?DBwTS&{O-LzP+&{kMgfPMC|6q8N{}c?F

tp3@! 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⪯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
7i@!(6dY;9m^s$c!uGTQy%^FYud$zDQM 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
dPu1mzW>&42AO9WM2>O7A=FYBr(B;XH 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 literal 0 HcmV?d00001 diff --git a/docs/sources/project/images/windows-mingw.png b/docs/sources/project/images/windows-mingw.png new file mode 100644 index 0000000000000000000000000000000000000000..09f53ba9ebf11ad1ef9790b5a5752b2480f3de7d GIT binary patch literal 230524 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