diff --git a/.gitignore b/.gitignore
index 9696b9a30..195b04c1a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,33 +1,37 @@
# 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
+*.rej
+*.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
+# generated by man/man/md2man-all.sh
+man/man1
+man/man5
+pyenv
+vendor/pkg/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 28d9569d7..c690ed932 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,37 @@
# Changelog
+## 1.7.0 (2015-06-16)
+
+#### Runtime
++ Experimental feature: support for out-of-process volume plugins
+* The userland proxy can be disabled in favor of hairpin NAT using the daemon’s `--userland-proxy=false` flag
+* The `exec` command supports the `-u|--user` flag to specify the new process owner
++ Default gateway for containers can be specified daemon-wide using the `--default-gateway` and `--default-gateway-v6` flags
++ The CPU CFS (Completely Fair Scheduler) quota can be set in `docker run` using `--cpu-quota`
++ Container block IO can be controlled in `docker run` using`--blkio-weight`
++ ZFS support
++ The `docker logs` command supports a `--since` argument
++ UTS namespace can be shared with the host with `docker run --uts=host`
+
+#### Quality
+* Networking stack was entirely rewritten as part of the libnetwork effort
+* Engine internals refactoring
+* Volumes code was entirely rewritten to support the plugins effort
++ Sending SIGUSR1 to a daemon will dump all goroutines stacks without exiting
+
+#### Build
++ Support ${variable:-value} and ${variable:+value} syntax for environment variables
++ Support resource management flags `--cgroup-parent`, `--cpu-period`, `--cpu-quota`, `--cpuset-cpus`, `--cpuset-mems`
++ git context changes with branches and directories
+* The .dockerignore file support exclusion rules
+
+#### Distribution
++ Client support for v2 mirroring support for the official registry
+
+#### Bugfixes
+* Firewalld is now supported and will automatically be used when available
+* mounting --device recursively
+
## 1.6.2 (2015-05-13)
#### Runtime
@@ -21,10 +53,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
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e6bf6ad5f..306207d47 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,7 +4,7 @@ Want to hack on Docker? Awesome! We have a contributor's guide that explains
[setting up a Docker development environment and the contribution
process](https://docs.docker.com/project/who-written-for/).
-
+
This page contains information about reporting issues as well as some tips and
guidelines useful to experienced open source contributors. Finally, make sure
@@ -129,12 +129,12 @@ However, there might be a way to implement that feature *on top of* Docker.
-
Internet Relay Chat (IRC)
+
Internet Relay Chat (IRC)
IRC a direct line to our most knowledgeable Docker users; we have
- both the #docker and #docker-dev group on
- irc.freenode.net.
+ both the #docker and #docker-dev group on
+ irc.freenode.net.
IRC is a rich chat protocol but it can overwhelm new users. You can search
our chat archives.
@@ -146,9 +146,9 @@ However, there might be a way to implement that feature *on top of* Docker.
There are two groups.
Docker-user
- is for people using Docker containers.
- The docker-dev
- group is for contributors and other people contributing to the Docker
+ is for people using Docker containers.
+ The docker-dev
+ group is for contributors and other people contributing to the Docker
project.
@@ -156,15 +156,15 @@ However, there might be a way to implement that feature *on top of* Docker.
Twitter
You can follow Docker's Twitter feed
- to get updates on our products. You can also tweet us questions or just
+ to get updates on our products. You can also tweet us questions or just
share blogs or stories.
Stack Overflow
- Stack Overflow has over 7000K Docker questions listed. We regularly
- monitor Docker questions
+ Stack Overflow has over 7000K Docker questions listed. We regularly
+ monitor Docker questions
and so do many other knowledgeable Docker users.
diff --git a/Dockerfile b/Dockerfile
index b06407613..a74712ade 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -26,11 +26,15 @@
FROM ubuntu:14.04
MAINTAINER Tianon Gravi (@tianon)
+RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net --recv-keys E871F18B51E0147C77796AC81196BA81F6B0FC61
+RUN echo deb http://ppa.launchpad.net/zfs-native/stable/ubuntu trusty main > /etc/apt/sources.list.d/zfs.list
+
# Packaged dependencies
RUN apt-get update && apt-get install -y \
apparmor \
aufs-tools \
automake \
+ bash-completion \
btrfs-tools \
build-essential \
curl \
@@ -49,6 +53,8 @@ RUN apt-get update && apt-get install -y \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.0* \
+ ubuntu-zfs \
+ libzfs-dev \
--no-install-recommends
# Get lvm2 source for compiling statically
@@ -97,12 +103,19 @@ 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
# 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 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
@@ -113,7 +126,8 @@ RUN set -x \
&& git clone https://github.com/docker/distribution.git /go/src/github.com/docker/distribution \
&& (cd /go/src/github.com/docker/distribution && git checkout -q $REGISTRY_COMMIT) \
&& GOPATH=/go/src/github.com/docker/distribution/Godeps/_workspace:/go \
- go build -o /go/bin/registry-v2 github.com/docker/distribution/cmd/registry
+ go build -o /go/bin/registry-v2 github.com/docker/distribution/cmd/registry \
+ && rm -rf /go/src/github.com/docker/distribution/
# Get the "docker-py" source so we can run their integration tests
ENV DOCKER_PY_COMMIT 91985b239764fe54714fa0a93d52aa362357d251
@@ -137,32 +151,37 @@ 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
+# 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 \
busybox:latest@4986bf8c15363d1c5d15512d5266f8777bfba4974ac56e3270e7760f6f0a8125 \
- hello-world:frozen@e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5
+ hello-world:frozen@e45a5af57b00862e5ef5782a9925979a02ba2b12dff832fd0991335f4a11e5c5 \
+ jess/unshare@5c9f6ea50341a2a8eb6677527f2bdedbf331ae894a41714fda770fb130f3314d
# 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"]
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 04951bf45..0e06d8818 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
@@ -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,
@@ -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
@@ -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.
@@ -200,14 +205,9 @@ 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).
# 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.
@@ -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"
@@ -296,7 +296,9 @@ made through a pull request.
[Org.Operators.security]
people = [
- "erw"
+ "erw",
+ "diogomonica",
+ "nathanmccauley"
]
[Org.Operators."monthly meetings"]
@@ -312,13 +314,29 @@ made through a pull request.
"jfrazelle",
"crosbymichael"
]
+
+ [Org.Operators.community]
+ people = [
+ "theadactyl"
+ ]
# 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".
"Chief Maintainer" = "crosbymichael"
+
+ # The community manager is responsible for serving the project community, including users,
+ # contributors and partners. This involves:
+ # - facilitating communication between maintainers, contributors and users
+ # - organizing contributor and maintainer events
+ # - helping new contributors get involved
+ # - anything the project community needs to be successful
+ #
+ # The community manager is a point of contact for any contributor who has questions, concerns
+ # or feedback about project operations.
+ "Community Manager" = "theadactyl"
[Org."Core maintainers"]
@@ -339,14 +357,15 @@ made through a pull request.
people = [
- "unclejack",
"crosbymichael",
"erikh",
"estesp",
"icecrime",
"jfrazelle",
"lk4d4",
+ "runcom",
"tibor",
+ "unclejack",
"vbatts",
"vieux",
"vishh"
@@ -359,80 +378,81 @@ 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.
- # on their component. This includes IRC, GitHub requests and the mailing
- # list.
+ # on their component. This includes IRC, GitHub requests and the mailing
+ # list.
# 4. Make sure their subsystem respects the philosophy, design and
- # road map of the project.
+ # 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
- # a comment adressing the issues.
- # - If the changes needed to a PR are small, you can add a "LGTM once the
- # following comments are adressed..." this will reduce needless back and
- # forth.
- # - 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.
- #
+ #
+ # - 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
+ # a comment adressing the issues.
+ # - If the changes needed to a PR are small, you can add a "LGTM once the
+ # following comments are adressed..." this will reduce needless back and
+ # forth.
+ # - 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.
- # - If you are not sure if the pull request implements a good feature or you
- # do not understand the purpose of the PR, ask the contributor to provide
- # more documentation. If the contributor is not able to adequately explain
- # the purpose of the PR, the PR may be closed by any MAINTAINER.
- # - If a MAINTAINER feels that the pull request is sufficiently architecturally
- # flawed, or if the pull request needs significantly more design discussion
- # before being considered, the MAINTAINER should close the pull request with
- # a short explanation of what discussion still needs to be had. It is
- # important not to leave such pull requests open, as this will waste both the
- # MAINTAINER's time and the contributor's time. It is not good to string a
- # contributor on for weeks or months, having them make many changes to a PR
- # that will eventually be rejected.
+ #
+ # - 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.
+ # - If you are not sure if the pull request implements a good feature or you
+ # do not understand the purpose of the PR, ask the contributor to provide
+ # more documentation. If the contributor is not able to adequately explain
+ # the purpose of the PR, the PR may be closed by any MAINTAINER.
+ # - If a MAINTAINER feels that the pull request is sufficiently architecturally
+ # flawed, or if the pull request needs significantly more design discussion
+ # before being considered, the MAINTAINER should close the pull request with
+ # a short explanation of what discussion still needs to be had. It is
+ # important not to leave such pull requests open, as this will waste both the
+ # MAINTAINER's time and the contributor's time. It is not good to string a
+ # contributor on for weeks or months, having them make many changes to a PR
+ # that will eventually be rejected.
[Org.Subsystems.Documentation]
people = [
"fredlf",
"james",
- "sven",
+ "moxiegirl",
+ "thaJeztah",
+ "jamtur01",
"spf13",
- "mary"
+ "sven"
]
[Org.Subsystems.libcontainer]
people = [
"crosbymichael",
- "vmarmol",
- "mpatel",
"jnagal",
- "lk4d4"
+ "lk4d4",
+ "mpatel",
+ "vmarmol"
]
[Org.Subsystems.registry]
people = [
+ "dmcg",
"dmp42",
- "vbatts",
- "joffrey",
+ "jlhawn",
"samalba",
"sday",
- "jlhawn",
- "dmcg"
+ "vbatts"
]
[Org.Subsystems."build tools"]
@@ -471,11 +491,27 @@ made through a pull request.
[Org.Subsystem.builder]
people = [
+ "duglin",
"erikh",
- "tibor",
- "duglin"
+ "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]
@@ -500,11 +536,21 @@ 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"
GitHub = "crosbymichael"
+ [people.diogomonica]
+ Name = "Diogo Monica"
+ Email = "diogo@docker.com"
+ GitHub = "diogomonica"
+
[people.duglin]
Name = "Doug Davis"
Email = "dug@us.ibm.com"
@@ -552,7 +598,7 @@ made through a pull request.
[people.jfrazelle]
Name = "Jessie Frazelle"
- Email = "jess@docker.com"
+ Email = "j@docker.com"
GitHub = "jfrazelle"
[people.jlhawn]
@@ -560,21 +606,26 @@ 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"
GitHub = "lk4d4"
- [people.mary]
+ [people.moxiegirl]
Name = "Mary Anthony"
Email = "mary.anthony@docker.com"
GitHub = "moxiegirl"
+ [people.nathanmccauley]
+ Name = "Nathan McCauley"
+ Email = "nathan.mccauley@docker.com"
+ GitHub = "nathanmccauley"
+
+ [people.runcom]
+ Name = "Antonio Murdaca"
+ Email = "me@runcom.ninja"
+ GitHub = "runcom"
+
[people.sday]
Name = "Stephen Day"
Email = "stephen.day@docker.com"
@@ -584,17 +635,27 @@ 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"
GitHub = "SvenDowideit"
+ [people.thajeztah]
+ Name = "Sebastiaan van Stijn"
+ Email = "github@gone.nl"
+ GitHub = "thaJeztah"
+
+ [people.theadactyl]
+ Name = "Thea Lamkin"
+ Email = "thea@docker.com"
+ GitHub = "theadactyl"
+
[people.tianon]
Name = "Tianon Gravi"
Email = "admwiggin@gmail.com"
diff --git a/Makefile b/Makefile
index 9bf1b16c9..4c532cf56 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
@@ -7,7 +7,10 @@ DOCKER_ENVS := \
-e BUILDFLAGS \
-e DOCKER_CLIENTONLY \
-e DOCKER_EXECDRIVER \
+ -e DOCKER_EXPERIMENTAL \
-e DOCKER_GRAPHDRIVER \
+ -e DOCKER_STORAGE_OPTS \
+ -e DOCKER_USERLANDPROXY \
-e TESTDIRS \
-e TESTFLAGS \
-e TIMEOUT
@@ -19,14 +22,9 @@ DOCKER_ENVS := \
BIND_DIR := $(if $(BINDDIR),$(BINDDIR),$(if $(DOCKER_HOST),,bundles))
DOCKER_MOUNT := $(if $(BIND_DIR),-v "$(CURDIR)/$(BIND_DIR):/go/src/github.com/docker/docker/$(BIND_DIR)")
-# to allow `make DOCSDIR=docs docs-shell` (to create a bind mount in docs)
-DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR))
-
-# to allow `make DOCSPORT=9000 docs`
-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)"
@@ -47,29 +45,13 @@ binary: build
cross: build
$(DOCKER_RUN_DOCKER) hack/make.sh binary cross
-docs: docs-build
- $(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" mkdocs serve
-
-docs-shell: docs-build
- $(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash
-
-docs-release: docs-build
- $(DOCKER_RUN_DOCS) -e OPTIONS -e BUILD_ROOT -e DISTRIBUTION_ID \
- -v $(CURDIR)/docs/awsconfig:/docs/awsconfig \
- "$(DOCKER_DOCS_IMAGE)" ./release.sh
-
-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
@@ -77,7 +59,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-test validate-toml validate-vet
shell: build
$(DOCKER_RUN_DOCKER) bash
@@ -85,13 +67,5 @@ shell: build
build: bundles
docker build -t "$(DOCKER_IMAGE)" .
-docs-build:
- cp ./VERSION docs/VERSION
- echo "$(GIT_BRANCH)" > docs/GIT_BRANCH
-# echo "$(AWS_S3_BUCKET)" > docs/AWS_S3_BUCKET
- echo "$(GITCOMMIT)" > docs/GITCOMMIT
- docker pull docs/base
- docker build -t "$(DOCKER_DOCS_IMAGE)" docs
-
bundles:
mkdir bundles
diff --git a/NOTICE b/NOTICE
index 435ace7f0..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.
@@ -10,10 +10,10 @@ 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
+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 079713ced..11c16b758 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
@@ -13,12 +13,12 @@ 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.
-
+
## Security Disclosure
@@ -30,7 +30,7 @@ security@docker.com and not by creating a github issue.
A common method for distributing applications and sandboxing their
execution is to use virtual machines, or VMs. Typical VM formats are
-VMWare's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory
+VMware's vmdk, Oracle Virtualbox's vdi, and Amazon EC2's ami. In theory
these formats should allow every developer to automatically package
their application into a "machine" for easy distribution and deployment.
In practice, that almost never happens, for a few reasons:
@@ -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.
@@ -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
@@ -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,19 +147,19 @@ 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
==============
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
+examples](https://docs.docker.com/examples/) in the
documentation.
Under the hood
@@ -171,16 +171,16 @@ 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](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)
Contributing to Docker
======================
-[](https://godoc.org/github.com/docker/docker)
-[](https://jenkins.dockerproject.com/job/Docker%20Master/)
+[](https://godoc.org/github.com/docker/docker)
+[](https://jenkins.dockerproject.org/job/Docker%20Master/)
Want to hack on Docker? Awesome! We have [instructions to help you get
started contributing code or documentation.](https://docs.docker.com/project/who-written-for/).
@@ -192,12 +192,12 @@ Getting the development builds
==============================
Want to run Docker from a master build? You can download
-master builds at [master.dockerproject.com](https://master.dockerproject.com).
+master builds at [master.dockerproject.org](https://master.dockerproject.org).
They are updated with each commit merged into the master branch.
Don't know how to use that super cool new feature in the master build? Check
out the master docs at
-[docs.master.dockerproject.com](http://docs.master.dockerproject.com).
+[docs.master.dockerproject.org](http://docs.master.dockerproject.org).
How the project is run
======================
@@ -207,17 +207,65 @@ or want to get more involved, the best place to start is [the project directory]
We are always open to suggestions on process improvements, and are always looking for more maintainers.
+### Talking to other Docker users and contributors
+
+
+
+
+
+
Internet Relay Chat (IRC)
+
+
+ IRC a direct line to our most knowledgeable Docker users; we have
+ both the #docker and #docker-dev group on
+ irc.freenode.net.
+ IRC is a rich chat protocol but it can overwhelm new users. You can search
+ our chat archives.
+
+ There are two groups.
+ Docker-user
+ is for people using Docker containers.
+ The docker-dev
+ group is for contributors and other people contributing to the Docker
+ project.
+
+
+
+
Twitter
+
+ You can follow Docker's Twitter feed
+ to get updates on our products. You can also tweet us questions or just
+ share blogs or stories.
+
+
+
+
Stack Overflow
+
+ Stack Overflow has over 7000K Docker questions listed. We regularly
+ monitor Docker questions
+ and so do many other knowledgeable Docker users.
+
","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/article-img/ipv6_routed_network_example.svg b/docs/article-img/ipv6_routed_network_example.svg
new file mode 100644
index 000000000..c97b02c26
--- /dev/null
+++ b/docs/article-img/ipv6_routed_network_example.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/docs/sources/article-img/ipv6_slash64_subnet_config.gliffy b/docs/article-img/ipv6_slash64_subnet_config.gliffy
similarity index 100%
rename from docs/sources/article-img/ipv6_slash64_subnet_config.gliffy
rename to docs/article-img/ipv6_slash64_subnet_config.gliffy
diff --git a/docs/sources/article-img/ipv6_slash64_subnet_config.svg b/docs/article-img/ipv6_slash64_subnet_config.svg
similarity index 100%
rename from docs/sources/article-img/ipv6_slash64_subnet_config.svg
rename to docs/article-img/ipv6_slash64_subnet_config.svg
diff --git a/docs/sources/article-img/ipv6_switched_network_example.gliffy b/docs/article-img/ipv6_switched_network_example.gliffy
similarity index 100%
rename from docs/sources/article-img/ipv6_switched_network_example.gliffy
rename to docs/article-img/ipv6_switched_network_example.gliffy
diff --git a/docs/sources/article-img/ipv6_switched_network_example.svg b/docs/article-img/ipv6_switched_network_example.svg
similarity index 100%
rename from docs/sources/article-img/ipv6_switched_network_example.svg
rename to docs/article-img/ipv6_switched_network_example.svg
diff --git a/docs/sources/articles/ambassador_pattern_linking.md b/docs/articles/ambassador_pattern_linking.md
similarity index 75%
rename from docs/sources/articles/ambassador_pattern_linking.md
rename to docs/articles/ambassador_pattern_linking.md
index 9b1b0329a..5a0454030 100644
--- a/docs/sources/articles/ambassador_pattern_linking.md
+++ b/docs/articles/ambassador_pattern_linking.md
@@ -1,8 +1,15 @@
-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,27 +37,27 @@ 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
- 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 +69,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 +103,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 +114,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/b2d_volume_images/add_cd.png b/docs/articles/b2d_volume_images/add_cd.png
similarity index 100%
rename from docs/sources/articles/b2d_volume_images/add_cd.png
rename to docs/articles/b2d_volume_images/add_cd.png
diff --git a/docs/sources/articles/b2d_volume_images/add_new_controller.png b/docs/articles/b2d_volume_images/add_new_controller.png
similarity index 100%
rename from docs/sources/articles/b2d_volume_images/add_new_controller.png
rename to docs/articles/b2d_volume_images/add_new_controller.png
diff --git a/docs/sources/articles/b2d_volume_images/add_volume.png b/docs/articles/b2d_volume_images/add_volume.png
similarity index 100%
rename from docs/sources/articles/b2d_volume_images/add_volume.png
rename to docs/articles/b2d_volume_images/add_volume.png
diff --git a/docs/sources/articles/b2d_volume_images/boot_order.png b/docs/articles/b2d_volume_images/boot_order.png
similarity index 100%
rename from docs/sources/articles/b2d_volume_images/boot_order.png
rename to docs/articles/b2d_volume_images/boot_order.png
diff --git a/docs/sources/articles/b2d_volume_images/gparted.png b/docs/articles/b2d_volume_images/gparted.png
similarity index 100%
rename from docs/sources/articles/b2d_volume_images/gparted.png
rename to docs/articles/b2d_volume_images/gparted.png
diff --git a/docs/sources/articles/b2d_volume_images/gparted2.png b/docs/articles/b2d_volume_images/gparted2.png
similarity index 100%
rename from docs/sources/articles/b2d_volume_images/gparted2.png
rename to docs/articles/b2d_volume_images/gparted2.png
diff --git a/docs/sources/articles/b2d_volume_images/verify.png b/docs/articles/b2d_volume_images/verify.png
similarity index 100%
rename from docs/sources/articles/b2d_volume_images/verify.png
rename to docs/articles/b2d_volume_images/verify.png
diff --git a/docs/sources/articles/b2d_volume_resize.md b/docs/articles/b2d_volume_resize.md
similarity index 92%
rename from docs/sources/articles/b2d_volume_resize.md
rename to docs/articles/b2d_volume_resize.md
index 1b39b49ed..34884613f 100644
--- a/docs/sources/articles/b2d_volume_resize.md
+++ b/docs/articles/b2d_volume_resize.md
@@ -1,6 +1,12 @@
-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?
@@ -60,7 +66,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/baseimages.md b/docs/articles/baseimages.md
similarity index 82%
rename from docs/sources/articles/baseimages.md
rename to docs/articles/baseimages.md
index 5a5addd1a..b7eba6f82 100644
--- a/docs/sources/articles/baseimages.md
+++ b/docs/articles/baseimages.md
@@ -1,8 +1,14 @@
-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!
@@ -22,9 +28,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
@@ -65,4 +71,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/basics.md b/docs/articles/basics.md
similarity index 69%
rename from docs/sources/articles/basics.md
rename to docs/articles/basics.md
index 4cdcab4aa..04b1d4d5d 100644
--- a/docs/sources/articles/basics.md
+++ b/docs/articles/basics.md
@@ -1,29 +1,39 @@
-page_title: First steps with Docker
-page_description: Common usage and commands
-page_keywords: Examples, Usage, basic commands, docker, documentation, examples
+
-# First steps with Docker
-
-## Check your Docker install
+# "Get started with containers
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)
@@ -37,7 +47,9 @@ image cache.
> characters of the full image ID - which can be found using
> `docker inspect` or `docker images --no-trunc=true`
-{{ include "no-remote-sudo.md" }}
+> **Note:** if you are using a remote Docker daemon, such as Boot2Docker,
+> then _do not_ type the `sudo` before the `docker` commands shown in the
+> documentation's examples.
## Running an interactive shell
@@ -46,7 +58,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 +104,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 +112,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 +178,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
+ # List your images
+ $ docker images
You now have an image state from which you can create new instances.
diff --git a/docs/sources/articles/certificates.md b/docs/articles/certificates.md
similarity index 91%
rename from docs/sources/articles/certificates.md
rename to docs/articles/certificates.md
index ebd606f38..29bacf291 100644
--- a/docs/sources/articles/certificates.md
+++ b/docs/articles/certificates.md
@@ -1,6 +1,13 @@
-page_title: Using certificates for repository client verification
-page_description: How to set up and use certificates with a registry to verify access
-page_keywords: Usage, registry, repository, client, root, certificate, docker, apache, ssl, tls, documentation, examples, articles, tutorials
+
# Using certificates for repository client verification
diff --git a/docs/sources/articles/cfengine_process_management.md b/docs/articles/cfengine_process_management.md
similarity index 93%
rename from docs/sources/articles/cfengine_process_management.md
rename to docs/articles/cfengine_process_management.md
index e32b26639..b2e573730 100644
--- a/docs/sources/articles/cfengine_process_management.md
+++ b/docs/articles/cfengine_process_management.md
@@ -1,8 +1,14 @@
-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.
@@ -94,7 +100,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/articles/chef.md
similarity index 81%
rename from docs/sources/articles/chef.md
rename to docs/articles/chef.md
index cb70215c5..6a30ccb78 100644
--- a/docs/sources/articles/chef.md
+++ b/docs/articles/chef.md
@@ -1,6 +1,12 @@
-page_title: Chef Usage
-page_description: Installation and using Docker via Chef
-page_keywords: chef, installation, usage, docker, documentation
+
# Using Chef
@@ -43,7 +49,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 +74,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/articles/configuring.md b/docs/articles/configuring.md
new file mode 100644
index 000000000..3321573d0
--- /dev/null
+++ b/docs/articles/configuring.md
@@ -0,0 +1,245 @@
+
+
+# Configuring and running Docker on various distributions
+
+After successfully installing Docker, the `docker` daemon runs with its default
+configuration.
+
+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.
+
+### Running the docker daemon directly
+
+The `docker` daemon can be run directly using the `-d` option. By default it listens on
+the Unix socket `unix:///var/run/docker.sock`
+
+ $ docker -d
+
+ INFO[0000] +job init_networkdriver()
+ INFO[0000] +job serveapi(unix:///var/run/docker.sock)
+ INFO[0000] Listening for HTTP on unix (/var/run/docker.sock)
+ ...
+ ...
+
+### Configuring the docker daemon directly
+
+If you're running the `docker` daemon directly by running `docker -d` instead
+of using a process manager, you can append the configuration options to the `docker` run
+command directly. Just like the `-d` option, other options can be passed to the `docker`
+daemon to configure it.
+
+Some of the daemon's options are:
+
+| 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. |
+
+
+Here is a an example of running the `docker` daemon with configuration options:
+
+ $ docker -d -D --tls=true --tlscert=/var/docker/server.pem --tlskey=/var/docker/serverkey.pem -H tcp://192.168.59.3:2376
+
+These options :
+
+- Enable `-D` (debug) mode
+- Set `tls` to true with the server certificate and key specified using `--tlscert` and `--tlskey` respectively
+- Listen for connections on `tcp://192.168.59.3:2376`
+
+The command line reference has the [complete list of daemon flags](/reference/commandline/cli/#daemon)
+with explanations.
+
+## Ubuntu
+
+As of `14.04`, Ubuntu uses Upstart as a process manager. By default, Upstart jobs
+are located in `/etc/init` and the `docker` Upstart job can be found at `/etc/init/docker.conf`.
+
+After successfully [installing Docker for Ubuntu](/installation/ubuntulinux/),
+you can check the running status using Upstart in this way:
+
+ $ sudo status docker
+
+ docker start/running, process 989
+
+### Running Docker
+
+You can start/stop/restart the `docker` daemon using
+
+ $ sudo start docker
+
+ $ sudo stop docker
+
+ $ sudo restart docker
+
+
+### Configuring Docker
+
+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:
+
+1. Log into your host as a user with `sudo` or `root` privileges.
+
+2. If you don't have one, create the `/etc/default/docker` file on your host. Depending on how
+you installed Docker, you may already have this file.
+
+3. Open the file with your favorite editor.
+
+ ```
+ $ 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.
+
+```
+ DOCKER_OPTS="-D --tls=true --tlscert=/var/docker/server.pem --tlskey=/var/docker/serverkey.pem -H tcp://192.168.59.3:2376"
+```
+
+These options :
+
+- Enable `-D` (debug) mode
+- Set `tls` to true with the server certificate and key specified using `--tlscert` and `--tlskey` respectively
+- Listen for connections on `tcp://192.168.59.3:2376`
+
+The command line reference has the [complete list of daemon flags](/reference/commandline/cli/#daemon)
+with explanations.
+
+
+5. Save and close the file.
+
+6. Restart the `docker` daemon.
+
+ ```
+ $ sudo restart docker
+ ```
+
+7. Verify that the `docker` daemon is running as specified with the `ps` command.
+
+ ```
+ $ ps aux | grep docker | grep -v grep
+ ```
+
+### Logs
+
+By default logs for Upstart jobs are located in `/var/log/upstart` and the logs for `docker` daemon
+can be located at `/var/log/upstart/docker.log`
+
+ $ tail -f /var/log/upstart/docker.log
+ INFO[0000] Loading containers: done.
+ INFO[0000] docker daemon: 1.6.0 4749651; execdriver: native-0.2; graphdriver: aufs
+ INFO[0000] +job acceptconnections()
+ INFO[0000] -job acceptconnections() = OK (0)
+ INFO[0000] Daemon has completed initialization
+
+
+## CentOS / Red Hat Enterprise Linux / Fedora
+
+As of `7.x`, CentOS and RHEL use `systemd` as the process manager. As of `21`, Fedora uses
+`systemd` as its process manager.
+
+After successfully installing Docker for [CentOS](/installation/centos/)/[Red Hat Enterprise Linux]
+(/installation/rhel/)/[Fedora](/installation/fedora), you can check the running status in this way:
+
+ $ sudo systemctl status docker
+
+### Running Docker
+
+You can start/stop/restart the `docker` daemon using
+
+ $ sudo systemctl start docker
+
+ $ sudo systemctl stop docker
+
+ $ sudo systemctl restart docker
+
+If you want Docker to start at boot, you should also:
+
+ $ sudo systemctl enable docker
+
+### Configuring Docker
+
+You configure the `docker` daemon in the `/etc/sysconfig/docker` file on your
+host. You do this by specifying values in a variable. For CentOS 7.x and RHEL 7.x, the name
+of the variable is `OPTIONS` and for CentOS 6.x and RHEL 6.x, the name of the variable is
+`other_args`. For this section, we will use CentOS 7.x as an example to configure the `docker`
+daemon.
+
+By default, systemd services are located either in `/etc/systemd/service`, `/lib/systemd/system`
+or `/usr/lib/systemd/system`. The `docker.service` file can be found in either of these three
+directories depending on your host.
+
+To configure Docker options:
+
+1. Log into your host as a user with `sudo` or `root` privileges.
+
+2. If you don't have one, create the `/etc/sysconfig/docker` file on your host. Depending on how
+you installed Docker, you may already have this file.
+
+3. Open the file with your favorite editor.
+
+ ```
+ $ sudo vi /etc/sysconfig/docker
+ ```
+
+4. Add a `OPTIONS` variable with the following options. These options are appended to the
+command that starts the `docker` daemon.
+
+```
+ OPTIONS="-D --tls=true --tlscert=/var/docker/server.pem --tlskey=/var/docker/serverkey.pem -H tcp://192.168.59.3:2376"
+```
+
+These options :
+
+- Enable `-D` (debug) mode
+- Set `tls` to true with the server certificate and key specified using `--tlscert` and `--tlskey` respectively
+- Listen for connections on `tcp://192.168.59.3:2376`
+
+The command line reference has the [complete list of daemon flags](/reference/commandline/cli/#daemon)
+with explanations.
+
+5. Save and close the file.
+
+6. Restart the `docker` daemon.
+
+ ```
+ $ sudo service docker restart
+ ```
+
+7. Verify that the `docker` daemon is running as specified with the `ps` command.
+
+ ```
+ $ ps aux | grep docker | grep -v grep
+ ```
+
+### Logs
+
+systemd has its own logging system called the journal. The logs for the `docker` daemon can
+be viewed using `journalctl -u docker`
+
+ $ sudo journalctl -u docker
+ May 06 00:22:05 localhost.localdomain systemd[1]: Starting Docker Application Container Engine...
+ May 06 00:22:05 localhost.localdomain docker[2495]: time="2015-05-06T00:22:05Z" level="info" msg="+job serveapi(unix:///var/run/docker.sock)"
+ May 06 00:22:05 localhost.localdomain docker[2495]: time="2015-05-06T00:22:05Z" level="info" msg="Listening for HTTP on unix (/var/run/docker.sock)"
+ May 06 00:22:06 localhost.localdomain docker[2495]: time="2015-05-06T00:22:06Z" level="info" msg="+job init_networkdriver()"
+ May 06 00:22:06 localhost.localdomain docker[2495]: time="2015-05-06T00:22:06Z" level="info" msg="-job init_networkdriver() = OK (0)"
+ May 06 00:22:06 localhost.localdomain docker[2495]: time="2015-05-06T00:22:06Z" level="info" msg="Loading containers: start."
+ May 06 00:22:06 localhost.localdomain docker[2495]: time="2015-05-06T00:22:06Z" level="info" msg="Loading containers: done."
+ May 06 00:22:06 localhost.localdomain docker[2495]: time="2015-05-06T00:22:06Z" level="info" msg="docker daemon: 1.5.0-dev fc0329b/1.5.0; execdriver: native-0.2; graphdriver: devicemapper"
+ May 06 00:22:06 localhost.localdomain docker[2495]: time="2015-05-06T00:22:06Z" level="info" msg="+job acceptconnections()"
+ May 06 00:22:06 localhost.localdomain docker[2495]: time="2015-05-06T00:22:06Z" level="info" msg="-job acceptconnections() = OK (0)"
+
+_Note: Using and configuring journal is an advanced topic and is beyond the scope of this article._
diff --git a/docs/sources/articles/dockerfile_best-practices.md b/docs/articles/dockerfile_best-practices.md
similarity index 92%
rename from docs/sources/articles/dockerfile_best-practices.md
rename to docs/articles/dockerfile_best-practices.md
index 2ea796582..8a56ce8cd 100644
--- a/docs/sources/articles/dockerfile_best-practices.md
+++ b/docs/articles/dockerfile_best-practices.md
@@ -1,6 +1,12 @@
-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
+
# Best practices for writing Dockerfiles
@@ -32,13 +38,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
@@ -398,7 +405,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).
@@ -411,16 +426,16 @@ 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/)
* [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/dsc.md b/docs/articles/dsc.md
similarity index 95%
rename from docs/sources/articles/dsc.md
rename to docs/articles/dsc.md
index 18cbc0a8e..b6497baf0 100644
--- a/docs/sources/articles/dsc.md
+++ b/docs/articles/dsc.md
@@ -1,6 +1,12 @@
-page_title: PowerShell DSC Usage
-page_description: Using DSC to configure a new Docker host
-page_keywords: powershell, dsc, installation, usage, docker, documentation
+
# Using PowerShell DSC
diff --git a/docs/sources/articles/host_integration.md b/docs/articles/host_integration.md
similarity index 88%
rename from docs/sources/articles/host_integration.md
rename to docs/articles/host_integration.md
index cbcb21a35..531709619 100644
--- a/docs/sources/articles/host_integration.md
+++ b/docs/articles/host_integration.md
@@ -1,8 +1,15 @@
-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 +25,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/articles/https.md
similarity index 95%
rename from docs/sources/articles/https.md
rename to docs/articles/https.md
index 94d9ca3f2..9a3ecb511 100644
--- a/docs/sources/articles/https.md
+++ b/docs/articles/https.md
@@ -1,8 +1,15 @@
-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
+# Protect the Docker daemon socket
By default, Docker runs via a non-networked Unix socket. It can also
optionally communicate using a HTTP socket.
@@ -193,7 +200,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/https/Dockerfile b/docs/articles/https/Dockerfile
similarity index 100%
rename from docs/sources/articles/https/Dockerfile
rename to docs/articles/https/Dockerfile
diff --git a/docs/sources/articles/https/Makefile b/docs/articles/https/Makefile
similarity index 100%
rename from docs/sources/articles/https/Makefile
rename to docs/articles/https/Makefile
diff --git a/docs/sources/articles/https/README.md b/docs/articles/https/README.md
similarity index 88%
rename from docs/sources/articles/https/README.md
rename to docs/articles/https/README.md
index 3e1dd27f6..1faca04a1 100644
--- a/docs/sources/articles/https/README.md
+++ b/docs/articles/https/README.md
@@ -9,7 +9,7 @@ so my process is
$ boot2docker ssh
$$ git clone https://github.com/docker/docker
-$$ cd docker/docs/sources/articles/https
+$$ cd docker/docs/articles/https
$$ make cert
lots of things to see and manually answer, as openssl wants to be interactive
**NOTE:** make sure you enter the hostname (`boot2docker` in my case) when prompted for `Computer Name`)
@@ -18,7 +18,7 @@ $$ sudo make run
start another terminal
$ boot2docker ssh
-$$ cd docker/docs/sources/articles/https
+$$ cd docker/docs/articles/https
$$ make client
the last will connect first with `--tls` and then with `--tlsverify`
diff --git a/docs/sources/articles/https/make_certs.sh b/docs/articles/https/make_certs.sh
similarity index 100%
rename from docs/sources/articles/https/make_certs.sh
rename to docs/articles/https/make_certs.sh
diff --git a/docs/sources/articles/https/parsedocs.sh b/docs/articles/https/parsedocs.sh
similarity index 100%
rename from docs/sources/articles/https/parsedocs.sh
rename to docs/articles/https/parsedocs.sh
diff --git a/docs/sources/articles/networking.md b/docs/articles/networking.md
similarity index 92%
rename from docs/sources/articles/networking.md
rename to docs/articles/networking.md
index 7247d298d..1efb404ca 100644
--- a/docs/sources/articles/networking.md
+++ b/docs/articles/networking.md
@@ -1,10 +1,16 @@
-page_title: Network Configuration
-page_description: Docker networking
-page_keywords: network, networking, bridge, docker, documentation
+
-# Network Configuration
+# Network configuration
-## TL;DR
+## Summary
When Docker starts, it creates a virtual interface named `docker0` on
the host machine. It randomly chooses an address and subnet from the
@@ -41,7 +47,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
@@ -56,6 +62,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)
@@ -87,6 +99,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
@@ -121,8 +136,25 @@ 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` 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 built 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 +328,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.
@@ -377,7 +408,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
@@ -426,11 +457,25 @@ 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.
+> **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.
@@ -484,7 +529,9 @@ want to configure `eth0` via Router Advertisements you should set:

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"
@@ -551,9 +598,9 @@ Therefore the router thinks it can talk to these containers directly.
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
+`2001:db8::c009`? But it will get no answer because no one 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
@@ -578,9 +625,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:
@@ -626,9 +673,9 @@ 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 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
@@ -652,7 +699,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.
@@ -692,9 +739,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
@@ -723,7 +767,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
@@ -853,12 +897,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
@@ -908,14 +953,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
@@ -972,7 +1017,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
@@ -1016,18 +1061,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/articles/puppet.md
similarity index 88%
rename from docs/sources/articles/puppet.md
rename to docs/articles/puppet.md
index d9a7ceb70..34f3712dd 100644
--- a/docs/sources/articles/puppet.md
+++ b/docs/articles/puppet.md
@@ -1,6 +1,12 @@
-page_title: Puppet Usage
-page_description: Installating and using Puppet
-page_keywords: puppet, installation, usage, docker, documentation
+
# Using Puppet
@@ -47,7 +53,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 +77,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/articles/registry_mirror.md
similarity index 82%
rename from docs/sources/articles/registry_mirror.md
rename to docs/articles/registry_mirror.md
index adc470d71..13461d518 100644
--- a/docs/sources/articles/registry_mirror.md
+++ b/docs/articles/registry_mirror.md
@@ -1,6 +1,13 @@
-page_title: Run a local registry mirror
-page_description: How to set up and run a local registry mirror
-page_keywords: docker, registry, mirror, examples
+
# Run a local registry mirror
@@ -29,11 +36,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 +54,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 +65,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 +75,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/runmetrics.md b/docs/articles/runmetrics.md
similarity index 97%
rename from docs/sources/articles/runmetrics.md
rename to docs/articles/runmetrics.md
index 327640969..669ee4017 100644
--- a/docs/sources/articles/runmetrics.md
+++ b/docs/articles/runmetrics.md
@@ -1,8 +1,15 @@
-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 +18,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 +35,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 +46,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 +62,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 +269,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/articles/security.md
similarity index 95%
rename from docs/sources/articles/security.md
rename to docs/articles/security.md
index a26f79cf9..9f38b1896 100644
--- a/docs/sources/articles/security.md
+++ b/docs/articles/security.md
@@ -1,8 +1,15 @@
-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,11 +21,11 @@ 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
-run`, behind the scenes Docker creates a set of namespaces and control
+similar security features. When you start a container with
+`docker run`, behind the scenes Docker creates a set of namespaces and control
groups for the container.
**Namespaces provide the first and most straightforward form of
@@ -53,7 +60,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 +79,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
@@ -103,7 +110,7 @@ Docker directly on your local machine, outside of a VM). You can then
use traditional UNIX permission checks to limit access to the control
socket.
-You can also expose the REST API over HTTP if you explicitly decide so.
+You can also expose the REST API over HTTP if you explicitly decide to do so.
However, if you do that, being aware of the above mentioned security
implication, you should ensure that it will be reachable only from a
trusted network or VPN; or protected with e.g., `stunnel` and client SSL
@@ -132,7 +139,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 +213,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,
@@ -249,11 +256,11 @@ 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'.
-Eventually, it is expected that Docker will direct, native support
+Eventually, it is expected that Docker will have direct, native support
for user-namespaces, simplifying the process of hardening containers.
## Conclusions
diff --git a/docs/sources/articles/systemd.md b/docs/articles/systemd.md
similarity index 69%
rename from docs/sources/articles/systemd.md
rename to docs/articles/systemd.md
index fddd146b0..919ad50da 100644
--- a/docs/sources/articles/systemd.md
+++ b/docs/articles/systemd.md
@@ -1,8 +1,15 @@
-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
+# Control and configure Docker with systemd
Many Linux distributions use systemd to start the Docker daemon. This document
shows a few examples of how to customise Docker's settings.
@@ -24,14 +31,28 @@ If you want Docker to start at boot, you should also:
## Custom Docker daemon options
There are a number of ways to configure the daemon flags and environment variables
-for your Docker daemon.
+for your Docker daemon.
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`.
+Check if the `docker.service` uses an `EnvironmentFile`:
+
+ $ sudo systemctl show docker | grep EnvironmentFile
+ EnvironmentFile=-/etc/sysconfig/docker (ignore_errors=yes)
+
+Alternatively, find out where the service file is located, and look for the
+property:
+
+ $ sudo systemctl status docker | grep Loaded
+ Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled)
+ $ sudo grep EnvironmentFile /usr/lib/systemd/system/docker.service
+ EnvironmentFile=-/etc/sysconfig/docker
+
+You can customize the Docker daemon options using override files as explained in the
+[HTTP Proxy example](#http-proxy) below. The files located in `/usr/lib/systemd/system`
+or `/lib/systemd/system` contain the default options and should not be edited.
### Runtime directory and storage driver
@@ -42,7 +63,7 @@ In this example, we'll assume that your `docker.service` file looks something li
[Unit]
Description=Docker Application Container Engine
- Documentation=http://docs.docker.com
+ Documentation=https://docs.docker.com
After=network.target docker.socket
Requires=docker.socket
@@ -64,7 +85,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.
@@ -90,6 +111,11 @@ Flush changes:
$ sudo systemctl daemon-reload
+Verify that the configuration has been loaded:
+
+ $ sudo systemctl show docker --property Environment
+ Environment=HTTP_PROXY=http://proxy.example.com:80/
+
Restart Docker:
$ sudo systemctl restart docker
diff --git a/docs/sources/articles/using_supervisord.md b/docs/articles/using_supervisord.md
similarity index 91%
rename from docs/sources/articles/using_supervisord.md
rename to docs/articles/using_supervisord.md
index 5806707ee..931f38c1c 100644
--- a/docs/sources/articles/using_supervisord.md
+++ b/docs/articles/using_supervisord.md
@@ -1,6 +1,12 @@
-page_title: Using Supervisor with Docker
-page_description: How to use Supervisor process management with Docker
-page_keywords: docker, supervisor, process management
+
# Using Supervisor with Docker
@@ -91,13 +97,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/docker-hub/accounts.md b/docs/docker-hub/accounts.md
new file mode 100644
index 000000000..8fb24018e
--- /dev/null
+++ b/docs/docker-hub/accounts.md
@@ -0,0 +1,90 @@
+
+
+# Accounts on Docker Hub
+
+## 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
+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
+
+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
+email address is required to register, which you will need to verify for
+account activation.
+
+### Email activation process
+
+You need to have at least one verified email address to be able to use your
+[Docker Hub](https://hub.docker.com) account. If you can't find the validation email,
+you can request another by visiting the [Resend Email Confirmation](
+https://hub.docker.com/account/resend-email-confirmation/) page.
+
+### Password reset process
+
+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 and groups
+
+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.
+
+
+
+### 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.
+
+
+
+### Repository group permissions
+
+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.
+
+
+
+
diff --git a/docs/sources/docker-hub/builds.md b/docs/docker-hub/builds.md
similarity index 66%
rename from docs/sources/docker-hub/builds.md
rename to docs/docker-hub/builds.md
index 1613ad1d4..fb1ef13aa 100644
--- a/docs/sources/docker-hub/builds.md
+++ b/docs/docker-hub/builds.md
@@ -1,6 +1,13 @@
-page_title: Automated Builds on Docker Hub
-page_description: Docker Hub Automated Builds
-page_keywords: Docker, docker, registry, accounts, plans, Dockerfile, Docker Hub, docs, documentation, trusted, builds, trusted builds, automated builds
+
# Automated Builds on Docker Hub
@@ -8,20 +15,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.
@@ -29,20 +34,30 @@ Automated Builds are supported for both public and private repositories
on both [GitHub](http://github.com) and [Bitbucket](https://bitbucket.org/).
To use Automated Builds, you must have an [account on Docker Hub](
-http://docs.docker.com/userguide/dockerhub/#creating-a-docker-hub-account)
+https://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 +66,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".
+
+
+
+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.
+
+
+
+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 +211,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 +240,16 @@ hooks for your Automated Build:
Click on "Webhooks & Services" on the left side of the page.
3.
-
Find the service labeled "Docker" and click on it.
-
4.
+
+
Find the service labeled "Docker" (or click on "Add service") and click on it.
+
4.
+
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 +345,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/docker-hub/home.md b/docs/docker-hub/home.md
new file mode 100644
index 000000000..c2e44e17a
--- /dev/null
+++ b/docs/docker-hub/home.md
@@ -0,0 +1,20 @@
+
+
+# The Docker Hub Registry help
+
+## Introduction
+
+For your questions about the [Docker Hub](https://hub.docker.com) registry you
+can use [this documentation](docs.md).
+
+If you can not find something you are looking for, please feel free to
+[contact us](https://docker.com/resources/support/).
diff --git a/docs/sources/docker-hub/hub-images/bb_hooks.png b/docs/docker-hub/hub-images/bb_hooks.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/bb_hooks.png
rename to docs/docker-hub/hub-images/bb_hooks.png
diff --git a/docs/sources/docker-hub/hub-images/bb_menu.png b/docs/docker-hub/hub-images/bb_menu.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/bb_menu.png
rename to docs/docker-hub/hub-images/bb_menu.png
diff --git a/docs/sources/docker-hub/hub-images/bb_post-hook.png b/docs/docker-hub/hub-images/bb_post-hook.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/bb_post-hook.png
rename to docs/docker-hub/hub-images/bb_post-hook.png
diff --git a/docs/sources/docker-hub/hub-images/build-trigger.png b/docs/docker-hub/hub-images/build-trigger.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/build-trigger.png
rename to docs/docker-hub/hub-images/build-trigger.png
diff --git a/docs/docker-hub/hub-images/dashboard.png b/docs/docker-hub/hub-images/dashboard.png
new file mode 100644
index 000000000..594c5d145
Binary files /dev/null and b/docs/docker-hub/hub-images/dashboard.png differ
diff --git a/docs/sources/docker-hub/hub-images/deploy_key.png b/docs/docker-hub/hub-images/deploy_key.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/deploy_key.png
rename to docs/docker-hub/hub-images/deploy_key.png
diff --git a/docs/docker-hub/hub-images/gh-check-admin-org-dh-app-access.png b/docs/docker-hub/hub-images/gh-check-admin-org-dh-app-access.png
new file mode 100644
index 000000000..0df38c694
Binary files /dev/null and b/docs/docker-hub/hub-images/gh-check-admin-org-dh-app-access.png differ
diff --git a/docs/docker-hub/hub-images/gh-check-user-org-dh-app-access.png b/docs/docker-hub/hub-images/gh-check-user-org-dh-app-access.png
new file mode 100644
index 000000000..13ad6468f
Binary files /dev/null and b/docs/docker-hub/hub-images/gh-check-user-org-dh-app-access.png differ
diff --git a/docs/sources/docker-hub/hub-images/gh_add_ssh_user_key.png b/docs/docker-hub/hub-images/gh_add_ssh_user_key.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/gh_add_ssh_user_key.png
rename to docs/docker-hub/hub-images/gh_add_ssh_user_key.png
diff --git a/docs/sources/docker-hub/hub-images/gh_docker-service.png b/docs/docker-hub/hub-images/gh_docker-service.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/gh_docker-service.png
rename to docs/docker-hub/hub-images/gh_docker-service.png
diff --git a/docs/sources/docker-hub/hub-images/gh_menu.png b/docs/docker-hub/hub-images/gh_menu.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/gh_menu.png
rename to docs/docker-hub/hub-images/gh_menu.png
diff --git a/docs/sources/docker-hub/hub-images/gh_org_members.png b/docs/docker-hub/hub-images/gh_org_members.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/gh_org_members.png
rename to docs/docker-hub/hub-images/gh_org_members.png
diff --git a/docs/sources/docker-hub/hub-images/gh_repo_deploy_key.png b/docs/docker-hub/hub-images/gh_repo_deploy_key.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/gh_repo_deploy_key.png
rename to docs/docker-hub/hub-images/gh_repo_deploy_key.png
diff --git a/docs/sources/docker-hub/hub-images/gh_service_hook.png b/docs/docker-hub/hub-images/gh_service_hook.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/gh_service_hook.png
rename to docs/docker-hub/hub-images/gh_service_hook.png
diff --git a/docs/sources/docker-hub/hub-images/gh_settings.png b/docs/docker-hub/hub-images/gh_settings.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/gh_settings.png
rename to docs/docker-hub/hub-images/gh_settings.png
diff --git a/docs/sources/docker-hub/hub-images/gh_team_members.png b/docs/docker-hub/hub-images/gh_team_members.png
similarity index 100%
rename from docs/sources/docker-hub/hub-images/gh_team_members.png
rename to docs/docker-hub/hub-images/gh_team_members.png
diff --git a/docs/docker-hub/hub-images/groups.png b/docs/docker-hub/hub-images/groups.png
new file mode 100644
index 000000000..23dbbfcff
Binary files /dev/null and b/docs/docker-hub/hub-images/groups.png differ
diff --git a/docs/docker-hub/hub-images/hub.png b/docs/docker-hub/hub-images/hub.png
new file mode 100644
index 000000000..489f730f9
Binary files /dev/null and b/docs/docker-hub/hub-images/hub.png differ
diff --git a/docs/docker-hub/hub-images/invite.png b/docs/docker-hub/hub-images/invite.png
new file mode 100644
index 000000000..f66334044
Binary files /dev/null and b/docs/docker-hub/hub-images/invite.png differ
diff --git a/docs/docker-hub/hub-images/org-repo-collaborators.png b/docs/docker-hub/hub-images/org-repo-collaborators.png
new file mode 100644
index 000000000..fdb53f7b6
Binary files /dev/null and b/docs/docker-hub/hub-images/org-repo-collaborators.png differ
diff --git a/docs/docker-hub/hub-images/orgs.png b/docs/docker-hub/hub-images/orgs.png
new file mode 100644
index 000000000..6987cd3b4
Binary files /dev/null and b/docs/docker-hub/hub-images/orgs.png differ
diff --git a/docs/docker-hub/hub-images/repos.png b/docs/docker-hub/hub-images/repos.png
new file mode 100644
index 000000000..4e83d3405
Binary files /dev/null and b/docs/docker-hub/hub-images/repos.png differ
diff --git a/docs/docker-hub/index.md b/docs/docker-hub/index.md
new file mode 100644
index 000000000..42b1a4c8c
--- /dev/null
+++ b/docs/docker-hub/index.md
@@ -0,0 +1,38 @@
+
+
+# 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.
+
+
+
+## [Finding and pulling images](./userguide.md)
+
+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.
+
+## [Your Repositories](./repos.md)
+
+Find out how to share your Docker images in [Docker Hub
+repositories](./repos.md) and how to store and manage private images.
+
+## [Automated builds](./builds.md)
+
+Learn how to automate your build and deploy pipeline with [Automated
+Builds](./builds.md)
+
diff --git a/docs/docker-hub/official_repos.md b/docs/docker-hub/official_repos.md
new file mode 100644
index 000000000..b869f7967
--- /dev/null
+++ b/docs/docker-hub/official_repos.md
@@ -0,0 +1,113 @@
+
+
+# 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. They are
+designed to:
+
+* 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.
+
+* 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.
+
+* Exemplify [`Dockerfile` best practices](/articles/dockerfile_best-practices)
+ and provide clear documentation to serve as a reference for other `Dockerfile`
+ authors.
+
+* 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.
+
+* 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.
+
+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.
+
+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.
+
+## Should I use Official Repositories?
+
+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.
+
+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.
+
+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.
+
+## How can I get involved?
+
+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.
+
+## How do I create a new Official Repository?
+
+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:
+
+* [docker-library/official-images](https://github.com/docker-library/official-images)
+
+* [docker-library/docs](https://github.com/docker-library/docs)
+
+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.
+
+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.
+
+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.
+
+> **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/docker-hub/repos.md
similarity index 70%
rename from docs/sources/docker-hub/repos.md
rename to docs/docker-hub/repos.md
index 576583584..ada258e43 100644
--- a/docs/sources/docker-hub/repos.md
+++ b/docs/docker-hub/repos.md
@@ -1,42 +1,44 @@
-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
+# 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.
+
+Alternatively, 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.

-## Searching for repositories and images
-
-You can `search` for all the publicly available repositories and images using
-Docker.
-
- $ sudo 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,26 +50,11 @@ 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/).
+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/).
-## 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, 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
@@ -100,8 +87,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 +131,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 +151,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 +185,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 publicly 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/docker-hub/userguide.md b/docs/docker-hub/userguide.md
new file mode 100644
index 000000000..4a9bb73db
--- /dev/null
+++ b/docs/docker-hub/userguide.md
@@ -0,0 +1,63 @@
+
+
+# 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.
+
+
+
+## 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.
diff --git a/docs/sources/examples/apt-cacher-ng.Dockerfile b/docs/examples/apt-cacher-ng.Dockerfile
similarity index 100%
rename from docs/sources/examples/apt-cacher-ng.Dockerfile
rename to docs/examples/apt-cacher-ng.Dockerfile
diff --git a/docs/sources/examples/apt-cacher-ng.md b/docs/examples/apt-cacher-ng.md
similarity index 81%
rename from docs/sources/examples/apt-cacher-ng.md
rename to docs/examples/apt-cacher-ng.md
index cd92cb59a..20cb977ea 100644
--- a/docs/sources/examples/apt-cacher-ng.md
+++ b/docs/examples/apt-cacher-ng.md
@@ -1,8 +1,14 @@
-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
@@ -35,16 +41,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 +74,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 +84,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 +108,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/examples/couchdb_data_volumes.md
similarity index 62%
rename from docs/sources/examples/couchdb_data_volumes.md
rename to docs/examples/couchdb_data_volumes.md
index 8cd2408e4..3c8f620ff 100644
--- a/docs/sources/examples/couchdb_data_volumes.md
+++ b/docs/examples/couchdb_data_volumes.md
@@ -1,8 +1,14 @@
-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
@@ -16,7 +22,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 +30,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/examples/mongodb.md
similarity index 64%
rename from docs/sources/examples/mongodb.md
rename to docs/examples/mongodb.md
index c4fbf5768..6c0974bbe 100644
--- a/docs/sources/examples/mongodb.md
+++ b/docs/examples/mongodb.md
@@ -1,6 +1,12 @@
-page_title: Dockerizing MongoDB
-page_description: Creating a Docker image with MongoDB pre-installed using a Dockerfile and sharing the image on Docker Hub
-page_keywords: docker, dockerize, dockerizing, article, example, docker.io, platform, package, installation, networking, mongodb, containers, images, image, sharing, dockerfile, build, auto-building, virtualization, framework
+
# Dockerizing MongoDB
@@ -10,6 +16,11 @@ In this example, we are going to learn how to build a Docker image with
MongoDB pre-installed. We'll also see how to `push` that image to the
[Docker Hub registry](https://hub.docker.com) and share it with others!
+> **Note:**
+>
+> This guide will show the mechanics of building a MongoDB container, but
+> you will probably want to use the official image on [Docker Hub]( https://registry.hub.docker.com/_/mongo/)
+
Using Docker and containers for deploying [MongoDB](https://www.mongodb.org/)
instances will bring several benefits, such as:
@@ -59,8 +70,8 @@ a MongoDB repository file for the package manager.
# Installation:
# Import MongoDB public GPG key AND create a MongoDB list file
- RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv 7F0CEB10
- RUN echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/10gen.list
+ RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
+ RUN echo "deb http://repo.mongodb.org/apt/ubuntu "$(lsb_release -sc)"/mongodb-org/3.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-3.0.list
After this initial preparation we can update our packages and install MongoDB.
@@ -70,7 +81,7 @@ After this initial preparation we can update our packages and install MongoDB.
> **Tip:** You can install a specific version of MongoDB by using a list
> of required packages with versions, e.g.:
>
-> RUN apt-get update && apt-get install -y mongodb-org=2.6.1 mongodb-org-server=2.6.1 mongodb-org-shell=2.6.1 mongodb-org-mongos=2.6.1 mongodb-org-tools=2.6.1
+> RUN apt-get update && apt-get install -y mongodb-org=3.0.1 mongodb-org-server=3.0.1 mongodb-org-shell=3.0.1 mongodb-org-mongos=3.0.1 mongodb-org-tools=3.0.1
MongoDB requires a data directory. Let's create it as the final step of our
installation instructions.
@@ -86,7 +97,7 @@ the `EXPOSE` instruction.
EXPOSE 27017
# Set usr/bin/mongod as the dockerized entry-point application
- ENTRYPOINT usr/bin/mongod
+ ENTRYPOINT ["/usr/bin/mongod"]
Now save the file and let's build our image.
@@ -100,9 +111,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 +125,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,20 +143,36 @@ 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 -p 27017:27017 --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 -p 27017:27017 --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
- $ mongo --port 12345
+ $ mongo --port 27017
+
+ # If using boot2docker
+ # Usage: mongo --port --host
+ $ mongo --port 27017 --host 192.168.59.103
+
+> **Tip:**
+If you want to run two containers on the same engine, then you will need to map
+the exposed port to two different ports on the host
+
+ # Start two containers and map the ports
+ $ docker run -p 28001:27017 --name mongo_instance_001 -d my/repo
+ $ docker run -p 28002:27017 --name mongo_instance_002 -d my/repo
+
+ # Now you can connect to each MongoDB instance on the two ports
+ $ mongo --port 28001
+ $ mongo --port 28002
- [Linking containers](/userguide/dockerlinks)
- [Cross-host linking containers](/articles/ambassador_pattern_linking/)
diff --git a/docs/sources/examples/mongodb/Dockerfile b/docs/examples/mongodb/Dockerfile
similarity index 74%
rename from docs/sources/examples/mongodb/Dockerfile
rename to docs/examples/mongodb/Dockerfile
index c17a6360e..3513da471 100644
--- a/docs/sources/examples/mongodb/Dockerfile
+++ b/docs/examples/mongodb/Dockerfile
@@ -7,9 +7,8 @@ MAINTAINER Docker
# Installation:
# Import MongoDB public GPG key AND create a MongoDB list file
-RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv 7F0CEB10
-RUN echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | tee /etc/apt/sources.list.d/10gen.list
-
+RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
+RUN echo "deb http://repo.mongodb.org/apt/ubuntu "$(lsb_release -sc)"/mongodb-org/3.0 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-3.0.list
# Update apt-get sources AND install MongoDB
RUN apt-get update && apt-get install -y mongodb-org
diff --git a/docs/sources/examples/nodejs_web_app.md b/docs/examples/nodejs_web_app.md
similarity index 91%
rename from docs/sources/examples/nodejs_web_app.md
rename to docs/examples/nodejs_web_app.md
index 56f7687cd..f605f29c4 100644
--- a/docs/sources/examples/nodejs_web_app.md
+++ b/docs/examples/nodejs_web_app.md
@@ -1,8 +1,14 @@
-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
@@ -125,11 +131,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 +148,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 +165,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.Dockerfile b/docs/examples/postgresql_service.Dockerfile
similarity index 94%
rename from docs/sources/examples/postgresql_service.Dockerfile
rename to docs/examples/postgresql_service.Dockerfile
index 9c0c0d4fc..d5767c938 100644
--- a/docs/sources/examples/postgresql_service.Dockerfile
+++ b/docs/examples/postgresql_service.Dockerfile
@@ -1,12 +1,12 @@
#
-# example Dockerfile for http://docs.docker.com/examples/postgresql_service/
+# example Dockerfile for https://docs.docker.com/examples/postgresql_service/
#
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/docs/sources/examples/postgresql_service.md b/docs/examples/postgresql_service.md
similarity index 90%
rename from docs/sources/examples/postgresql_service.md
rename to docs/examples/postgresql_service.md
index 87960098f..968cf7737 100644
--- a/docs/sources/examples/postgresql_service.md
+++ b/docs/examples/postgresql_service.md
@@ -1,6 +1,12 @@
-page_title: Dockerizing PostgreSQL
-page_description: Running and installing a PostgreSQL service
-page_keywords: docker, example, package installation, postgresql
+
# Dockerizing PostgreSQL
@@ -21,7 +27,7 @@ Start by creating a new `Dockerfile`:
> suitably secure.
#
- # example Dockerfile for http://docs.docker.com/examples/postgresql_service/
+ # example Dockerfile for https://docs.docker.com/examples/postgresql_service/
#
FROM ubuntu
@@ -72,11 +78,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 +99,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 +110,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 +141,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/examples/running_redis_service.md
similarity index 83%
rename from docs/sources/examples/running_redis_service.md
rename to docs/examples/running_redis_service.md
index 99036a042..d8b673bdc 100644
--- a/docs/sources/examples/running_redis_service.md
+++ b/docs/examples/running_redis_service.md
@@ -1,13 +1,19 @@
-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.
@@ -20,7 +26,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 +39,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 +49,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/examples/running_riak_service.Dockerfile b/docs/examples/running_riak_service.Dockerfile
new file mode 100644
index 000000000..1051c1a42
--- /dev/null
+++ b/docs/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/examples/running_riak_service.md b/docs/examples/running_riak_service.md
new file mode 100644
index 000000000..b42a1af9b
--- /dev/null
+++ b/docs/examples/running_riak_service.md
@@ -0,0 +1,108 @@
+
+
+# Dockerizing a Riak service
+
+The goal of this example is to show you how to build a Docker image with
+Riak pre-installed.
+
+## Creating a Dockerfile
+
+Create an empty file called `Dockerfile`:
+
+ $ touch 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:
+`trusty`), which is available on [Docker Hub](https://hub.docker.com):
+
+ # Riak
+ #
+ # VERSION 0.1.1
+
+ # Use the Ubuntu base image provided by dotCloud
+ FROM ubuntu:trusty
+ MAINTAINER Hector Castro hector@basho.com
+
+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:
+
+ - `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 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
+
+After that, we modify Riak's configuration:
+
+ # 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:
+
+ # Expose Riak Protocol Buffers and HTTP interfaces
+ EXPOSE 8087 8098
+
+Finally, run `supervisord` so that Riak is started:
+
+ CMD ["/usr/bin/supervisord"]
+
+## Create a supervisord configuration file
+
+Create an empty file called `supervisord.conf`. Make
+sure it's at the same directory level as your `Dockerfile`:
+
+ touch supervisord.conf
+
+Populate it with the following program definitions:
+
+ [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
+
+## Build the Docker image for Riak
+
+Now you should be able to build a Docker image for Riak:
+
+ $ docker build -t "/riak" .
+
+## Next steps
+
+Riak is a distributed database. Many production deployments consist of
+[at least five nodes](
+http://basho.com/why-your-riak-cluster-should-have-at-least-five-nodes/).
+See the [docker-riak](https://github.com/hectcastro/docker-riak) project
+details on how to deploy a Riak cluster using Docker and Pipework.
diff --git a/docs/sources/examples/running_ssh_service.Dockerfile b/docs/examples/running_ssh_service.Dockerfile
similarity index 100%
rename from docs/sources/examples/running_ssh_service.Dockerfile
rename to docs/examples/running_ssh_service.Dockerfile
diff --git a/docs/sources/examples/running_ssh_service.md b/docs/examples/running_ssh_service.md
similarity index 81%
rename from docs/sources/examples/running_ssh_service.md
rename to docs/examples/running_ssh_service.md
index 445cfe525..c22e510cf 100644
--- a/docs/sources/examples/running_ssh_service.md
+++ b/docs/examples/running_ssh_service.md
@@ -1,8 +1,14 @@
-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
@@ -33,15 +39,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 +78,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/examples/supervisord.conf b/docs/examples/supervisord.conf
new file mode 100644
index 000000000..385fbe7a4
--- /dev/null
+++ b/docs/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
diff --git a/docs/sources/installation/SUSE.md b/docs/installation/SUSE.md
similarity index 76%
rename from docs/sources/installation/SUSE.md
rename to docs/installation/SUSE.md
index 2a0aa91d9..18c075733 100644
--- a/docs/sources/installation/SUSE.md
+++ b/docs/installation/SUSE.md
@@ -1,6 +1,12 @@
-page_title: Installation on openSUSE and SUSE Linux Enterprise
-page_description: Installation instructions for Docker on openSUSE and on SUSE Linux Enterprise.
-page_keywords: openSUSE, SUSE Linux Enterprise, SUSE, SLE, docker, documentation, installation
+
# openSUSE
@@ -8,7 +14,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.
@@ -28,7 +34,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 +82,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/installation/amazon.md b/docs/installation/amazon.md
new file mode 100644
index 000000000..81e5a756e
--- /dev/null
+++ b/docs/installation/amazon.md
@@ -0,0 +1,21 @@
+
+
+## Amazon EC2
+
+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.
+
+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).
diff --git a/docs/sources/installation/archlinux.md b/docs/installation/archlinux.md
similarity index 63%
rename from docs/sources/installation/archlinux.md
rename to docs/installation/archlinux.md
index 99849c7aa..1a28c5b1a 100644
--- a/docs/sources/installation/archlinux.md
+++ b/docs/installation/archlinux.md
@@ -1,6 +1,12 @@
-page_title: Installation on Arch Linux
-page_description: Installation instructions for Docker on ArchLinux.
-page_keywords: arch linux, virtualization, docker, documentation, installation
+
# Arch Linux
@@ -30,13 +36,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 +65,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/azure.md b/docs/installation/azure.md
similarity index 75%
rename from docs/sources/installation/azure.md
rename to docs/installation/azure.md
index a8e700fea..75c5726fe 100644
--- a/docs/sources/installation/azure.md
+++ b/docs/installation/azure.md
@@ -1,6 +1,12 @@
-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
+
# Microsoft Azure
diff --git a/docs/sources/installation/binaries.md b/docs/installation/binaries.md
similarity index 61%
rename from docs/sources/installation/binaries.md
rename to docs/installation/binaries.md
index ef9f5cafa..2625e1411 100644
--- a/docs/sources/installation/binaries.md
+++ b/docs/installation/binaries.md
@@ -1,6 +1,13 @@
-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
+
# Binaries
@@ -78,18 +85,93 @@ 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).
-## Run the docker daemon
+> **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
# start the docker in daemon mode from the directory you unpacked
$ sudo ./docker -d &
diff --git a/docs/installation/centos.md b/docs/installation/centos.md
new file mode 100644
index 000000000..9940f8c0b
--- /dev/null
+++ b/docs/installation/centos.md
@@ -0,0 +1,196 @@
+
+
+# CentOS
+
+Docker is supported on the following versions of CentOS:
+
+* CentOS 7.X
+* CentOS 6.5 or higher
+
+Installation on other binary compatible EL6/EL7 distributions such as Scientific
+Linux might succeed, but Docker does not test or support Docker on these
+distributions.
+
+This page instructs you to install using Docker-managed release packages and
+installation mechanisms. Using these packages ensures you get the latest release
+of Docker. If you wish to install using CentOS-managed packages, consult your
+CentOS documentation.
+
+## Prerequisites
+
+Docker requires a 64-bit installation regardless of your CentOS version. Also,
+your kernel must be 3.10 at minimum. CentOS 7 runs the 3.10 kernel, 6.5 does
+not. We make an exception for CentOS 6.5. To run Docker on
+[CentOS-6.5](https://www.centos.org) or later, you need kernel 2.6.32-431 or
+higher.
+
+To check your current kernel version, open a terminal and use `uname -r` to
+display your kernel version:
+
+ $ uname -r
+ 2.6.32-431.el6.x86_64
+
+Finally, is it recommended that you fully update your system. Please keep in
+mind that CentOS 6 should be fully patched to fix any potential kernel bugs. Any
+reported kernel bugs may have already been fixed on the latest kernel packages
+
+## Install
+
+You use the same installation procedure for all versions of CentOS,
+only the package you install differs. There are two packages to choose from:
+
+
+
+
+This procedure depicts an installation on version 6.5. If you are installing on
+7.X, substitute that package for your installation.
+
+1. Log into your machine as a user with `sudo` or `root` privileges.
+
+2. Make sure your existing packages are up-to-date.
+
+ $ sudo yum update
+
+3. Download the Docker RPM to the current directory.
+
+ $ curl -O -sSL https://get.docker.com/rpm/1.7.0/centos-6/RPMS/x86_64/docker-engine-1.7.0-1.el6.x86_64.rpm
+
+4. Use `yum` to install the package.
+
+ $ sudo yum localinstall --nogpgcheck docker-engine-1.7.0-1.el6.x86_64.rpm
+
+5. Start the Docker daemon.
+
+ $ sudo service docker start
+
+6. Verify `docker` is installed correctly by running a test image in a container.
+
+ $ sudo docker run hello-world
+ Unable to find image 'hello-world:latest' locally
+ latest: Pulling from hello-world
+ a8219747be10: Pull complete
+ 91c95931e552: Already exists
+ 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.
+ Digest: sha256:aa03e5d0d5553b4c3473e89c8619cf79df368babd1.7.0cf5daeb82aab55838d
+ 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 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/
+
+## 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
+`sudo`. For this reason, `docker` daemon always runs as the `root` user.
+
+To avoid having to use `sudo` when you use the `docker` command, create a Unix
+group called `docker` and add users to it. When the `docker` daemon starts, it
+makes the ownership of the Unix socket read/writable by the `docker` group.
+
+>**Warning**: The `docker` group is equivalent to the `root` user; For details
+>on how this impacts security in your system, see [*Docker Daemon Attack
+>Surface*](/articles/security/#docker-daemon-attack-surface) for details.
+
+To create the `docker` group and add your user:
+
+1. Log into Centos as a user with `sudo` privileges.
+
+2. Create the `docker` group and add your user.
+
+ `sudo usermod -aG docker your_username`
+
+3. Log out and log back in.
+
+ This ensures your user is running with the correct permissions.
+
+4. Verify your work by running `docker` without `sudo`.
+
+ $ docker run hello-world
+
+## Start the docker daemon at boot
+
+To ensure Docker starts when you boot your system, do the following:
+
+ $ sudo chkconfig docker on
+
+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/).
+
+
+## Uninstall
+
+You can uninstall the Docker software with `yum`.
+
+1. List the package you have installed.
+
+ $ yum list installed | grep docker
+ yum list installed | grep docker
+ docker-engine.x86_64 1.7.0-1.el6
+ @/docker-engine-1.7.0-1.el6.x86_64.rpm
+
+2. Remove the package.
+
+ $ sudo yum -y remove docker-engine.x86_64
+
+ This command does not remove images, containers, volumes, or user-created
+ configuration files on your host.
+
+3. To delete all images, containers, and volumes, run the following command:
+
+ $ rm -rf /var/lib/docker
+
+4. Locate and delete any user-created configuration files.
\ No newline at end of file
diff --git a/docs/sources/installation/cruxlinux.md b/docs/installation/cruxlinux.md
similarity index 69%
rename from docs/sources/installation/cruxlinux.md
rename to docs/installation/cruxlinux.md
index ead4c273c..e5276c9b7 100644
--- a/docs/sources/installation/cruxlinux.md
+++ b/docs/installation/cruxlinux.md
@@ -1,6 +1,12 @@
-page_title: Installation on CRUX Linux
-page_description: Docker installation on CRUX Linux.
-page_keywords: crux linux, virtualization, Docker, documentation, installation
+
# CRUX Linux
@@ -15,19 +21,19 @@ 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
+## 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.
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 +45,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 +66,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/installation/debian.md
similarity index 57%
rename from docs/sources/installation/debian.md
rename to docs/installation/debian.md
index 4644a2440..b49caf40b 100644
--- a/docs/sources/installation/debian.md
+++ b/docs/installation/debian.md
@@ -1,6 +1,12 @@
-page_title: Installation on Debian
-page_description: Instructions for installing Docker on Debian.
-page_keywords: Docker, Docker documentation, installation, debian
+
# Debian
@@ -11,8 +17,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 +25,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
@@ -27,19 +34,38 @@ 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
> [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
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),
@@ -62,9 +88,34 @@ 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 -
+
+### 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/installation/fedora.md b/docs/installation/fedora.md
new file mode 100644
index 000000000..8f616d256
--- /dev/null
+++ b/docs/installation/fedora.md
@@ -0,0 +1,232 @@
+
+
+# Fedora
+
+Docker is supported on the following versions of Fedora:
+
+- Fedora 20
+- Fedora 21
+- Fedora 22
+
+This page instructs you to install using Docker-managed release packages and
+installation mechanisms. Using these packages ensures you get the latest release
+of Docker. If you wish to install using Fedora-managed packages, consult your
+Fedora release documentation for information on Fedora's Docker support.
+
+##Prerequisites
+
+Docker requires a 64-bit installation regardless of your Fedora version. Also, your kernel must be 3.10 at minimum. To check your current kernel
+version, open a terminal and use `uname -r` to display your kernel version:
+
+ $ uname -r
+ 3.19.5-100.fc20.x86_64
+
+If your kernel is at a older version, you must update it.
+
+Finally, is it recommended that you fully update your system. Please keep in
+mind that your system should be fully patched to fix any potential kernel bugs. Any
+reported kernel bugs may have already been fixed on the latest kernel packages
+
+
+## Install
+
+You use the same installation procedure for all versions of Fedora,
+only the package you install differs. There are two packages to choose from:
+
+
+
+
+This procedure depicts an installation on version 21. If you are installing on
+20 or 22, substitute that package for your installation.
+
+1. Log into your machine as a user with `sudo` or `root` privileges.
+
+2. Make sure you don't have an older version of Docker installed.
+
+ $ yum list installed | grep docker
+
+ If you have an older version, remove it using the `yum -y remove ` command.
+
+3. Download the Docker RPM to the current directory.
+
+ $ curl -O -sSL https://url_to_package/docker-engine-1.7.0-0.1.fc21.x86_64.rpm
+
+4. Use `yum` to install the package.
+
+ $ sudo yum localinstall --nogpgcheck docker-engine-1.7.0-0.1.fc21.x86_64.rpm
+
+5. Start the Docker daemon.
+
+ $ sudo service docker start
+
+6. Verify `docker` is installed correctly by running a test image in a container.
+
+ $ sudo docker run hello-world
+ Unable to find image 'hello-world:latest' locally
+ latest: Pulling from hello-world
+ a8219747be10: Pull complete
+ 91c95931e552: Already exists
+ 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.
+ Digest: sha256:aa03e5d0d5553b4c3473e89c8619cf79df368babd18681cf5daeb82aab55838d
+ 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 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/
+
+## 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
+`sudo`. For this reason, `docker` daemon always runs as the `root` user.
+
+To avoid having to use `sudo` when you use the `docker` command, create a Unix
+group called `docker` and add users to it. When the `docker` daemon starts, it
+makes the ownership of the Unix socket read/writable by the `docker` group.
+
+>**Warning**: The `docker` group is equivalent to the `root` user; For details
+>on how this impacts security in your system, see [*Docker Daemon Attack
+>Surface*](/articles/security/#docker-daemon-attack-surface) for details.
+
+To create the `docker` group and add your user:
+
+1. Log into your system as a user with `sudo` privileges.
+
+2. Create the `docker` group and add your user.
+
+ `sudo usermod -aG docker your_username`
+
+3. Log out and log back in.
+
+ This ensures your user is running with the correct permissions.
+
+4. Verify your work by running `docker` without `sudo`.
+
+ $ docker run hello-world
+ Unable to find image 'hello-world:latest' locally
+ latest: Pulling from hello-world
+ a8219747be10: Pull complete
+ 91c95931e552: Already exists
+ 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.
+ Digest: sha256:aa03e5d0d5553b4c3473e89c8619cf79df368babd18681cf5daeb82aab55838d
+ 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 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/
+
+## Start the docker daemon at boot
+
+To ensure Docker starts when you boot your system, do the following:
+
+ $ sudo chkconfig docker on
+
+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/).
+
+
+## Uninstall
+
+You can uninstall the Docker software with `yum`.
+
+1. List the package you have installed.
+
+ $ yum list installed | grep docker
+ yum list installed | grep docker
+ docker-engine.x86_64 1.7.0-0.1.fc20
+ @/docker-engine-1.7.0-0.1.fc20.el6.x86_64
+
+2. Remove the package.
+
+ $ sudo yum -y remove docker-engine.x86_64
+
+ This command does not remove images, containers, volumes, or user-created
+ configuration files on your host.
+
+3. To delete all images, containers, and volumes, run the following command:
+
+ $ rm -rf /var/lib/docker
+
+4. Locate and delete any user-created configuration files.
diff --git a/docs/sources/installation/frugalware.md b/docs/installation/frugalware.md
similarity index 56%
rename from docs/sources/installation/frugalware.md
rename to docs/installation/frugalware.md
index 6b4db23b2..d8c3ac6dc 100644
--- a/docs/sources/installation/frugalware.md
+++ b/docs/installation/frugalware.md
@@ -1,6 +1,12 @@
-page_title: Installation on FrugalWare
-page_description: Installation instructions for Docker on FrugalWare.
-page_keywords: frugalware linux, virtualization, docker, documentation, installation
+
# FrugalWare
@@ -28,7 +34,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 +54,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/installation/gentoolinux.md
similarity index 83%
rename from docs/sources/installation/gentoolinux.md
rename to docs/installation/gentoolinux.md
index 716eab9d8..f954e60ed 100644
--- a/docs/sources/installation/gentoolinux.md
+++ b/docs/installation/gentoolinux.md
@@ -1,6 +1,12 @@
-page_title: Installation on Gentoo
-page_description: Installation instructions for Docker on Gentoo.
-page_keywords: gentoo linux, virtualization, docker, documentation, installation
+
# Gentoo
@@ -95,3 +101,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/google.md b/docs/installation/google.md
similarity index 81%
rename from docs/sources/installation/google.md
rename to docs/installation/google.md
index 1cee5290d..b507f41b1 100644
--- a/docs/sources/installation/google.md
+++ b/docs/installation/google.md
@@ -1,6 +1,12 @@
-page_title: Installation on Google Cloud Platform
-page_description: Installation instructions for Docker on the Google Cloud Platform.
-page_keywords: Docker, Docker documentation, installation, google, Google Compute Engine, Google Cloud Platform
+
# Google Cloud Platform
diff --git a/docs/sources/installation/images/bad_host.png b/docs/installation/images/bad_host.png
similarity index 100%
rename from docs/sources/installation/images/bad_host.png
rename to docs/installation/images/bad_host.png
diff --git a/docs/sources/installation/images/cool_view.png b/docs/installation/images/cool_view.png
similarity index 100%
rename from docs/sources/installation/images/cool_view.png
rename to docs/installation/images/cool_view.png
diff --git a/docs/sources/installation/images/good_host.png b/docs/installation/images/good_host.png
similarity index 100%
rename from docs/sources/installation/images/good_host.png
rename to docs/installation/images/good_host.png
diff --git a/docs/sources/installation/images/kitematic.png b/docs/installation/images/kitematic.png
similarity index 100%
rename from docs/sources/installation/images/kitematic.png
rename to docs/installation/images/kitematic.png
diff --git a/docs/installation/images/linux_docker_host.svg b/docs/installation/images/linux_docker_host.svg
new file mode 100644
index 000000000..0ad7240b7
--- /dev/null
+++ b/docs/installation/images/linux_docker_host.svg
@@ -0,0 +1,1195 @@
+
+
+
+
diff --git a/docs/installation/images/mac_docker_host.svg b/docs/installation/images/mac_docker_host.svg
new file mode 100644
index 000000000..a885a32cb
--- /dev/null
+++ b/docs/installation/images/mac_docker_host.svg
@@ -0,0 +1,1243 @@
+
+
+
+
diff --git a/docs/sources/installation/images/newsite_view.png b/docs/installation/images/newsite_view.png
similarity index 100%
rename from docs/sources/installation/images/newsite_view.png
rename to docs/installation/images/newsite_view.png
diff --git a/docs/sources/installation/images/osx-installer.png b/docs/installation/images/osx-installer.png
similarity index 100%
rename from docs/sources/installation/images/osx-installer.png
rename to docs/installation/images/osx-installer.png
diff --git a/docs/installation/images/win_docker_host.svg b/docs/installation/images/win_docker_host.svg
new file mode 100644
index 000000000..eef284e75
--- /dev/null
+++ b/docs/installation/images/win_docker_host.svg
@@ -0,0 +1,1259 @@
+
+
+
+
diff --git a/docs/sources/installation/images/windows-boot2docker-cmd.png b/docs/installation/images/windows-boot2docker-cmd.png
similarity index 100%
rename from docs/sources/installation/images/windows-boot2docker-cmd.png
rename to docs/installation/images/windows-boot2docker-cmd.png
diff --git a/docs/sources/installation/images/windows-boot2docker-powershell.png b/docs/installation/images/windows-boot2docker-powershell.png
similarity index 100%
rename from docs/sources/installation/images/windows-boot2docker-powershell.png
rename to docs/installation/images/windows-boot2docker-powershell.png
diff --git a/docs/sources/installation/images/windows-boot2docker-start.png b/docs/installation/images/windows-boot2docker-start.png
similarity index 100%
rename from docs/sources/installation/images/windows-boot2docker-start.png
rename to docs/installation/images/windows-boot2docker-start.png
diff --git a/docs/sources/installation/images/windows-installer.png b/docs/installation/images/windows-installer.png
similarity index 100%
rename from docs/sources/installation/images/windows-installer.png
rename to docs/installation/images/windows-installer.png
diff --git a/docs/sources/installation/joyent.md b/docs/installation/joyent.md
similarity index 72%
rename from docs/sources/installation/joyent.md
rename to docs/installation/joyent.md
index daa33b799..b135fa5ed 100644
--- a/docs/sources/installation/joyent.md
+++ b/docs/installation/joyent.md
@@ -1,6 +1,12 @@
-page_title: Install on Joyent Public Cloud
-page_description: Installation instructions for Docker on the Joyent Compute Service.
-page_keywords: Docker, Docker documentation, installation, joyent, Joyent Public Cloud, Joyent Compute Service, Joyent Container Service
+
## Install on Joyent Public Cloud
diff --git a/docs/sources/installation/mac.md b/docs/installation/mac.md
similarity index 52%
rename from docs/sources/installation/mac.md
rename to docs/installation/mac.md
index 9bf763268..ee5fcaaef 100644
--- a/docs/sources/installation/mac.md
+++ b/docs/installation/mac.md
@@ -1,21 +1,26 @@
-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
contribute to the Docker project on GitHub.
+[](https://kitematic.com/download)
+
Alternatively, you may want to try Kitematic, an application that lets you set up Docker and
run containers using a graphical user interface (GUI).
-
-
-
## Command-line Docker with Boot2Docker
Because the Docker daemon uses Linux-specific kernel features, you can't run
@@ -42,12 +47,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`.
-
+
In an OS X installation, the `docker` daemon is running inside a Linux virtual
machine provided by Boot2Docker.
-
+
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
@@ -55,17 +60,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 and VirtualBox in your "Applications" folder.
The installation places the `docker` and `boot2docker` binaries in your
`/usr/local/bin` directory.
@@ -96,30 +101,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,45 +137,45 @@ 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
+## Basic Boot2Docker exercises
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 +183,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:
- 
+ 
- 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:
- 
+ 
- 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 +238,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:
- 
+ 
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.
- 
+ 
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 +293,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,27 +308,51 @@ 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.
-## Learning more and Acknowledgement
+## 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).
-Thanks to Chris Jones whose [blog](http://goo.gl/Be6cCk) inspired me to redo
-this page.
+Thanks to Chris Jones whose [blog](http://viget.com/extend/how-to-use-docker-on-os-x-the-missing-guide)
+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/oracle.md b/docs/installation/oracle.md
similarity index 79%
rename from docs/sources/installation/oracle.md
rename to docs/installation/oracle.md
index 6d2f782b4..1a0083110 100644
--- a/docs/sources/installation/oracle.md
+++ b/docs/installation/oracle.md
@@ -1,6 +1,12 @@
-page_title: Installation on Oracle Linux
-page_description: Installation instructions for Docker on Oracle Linux.
-page_keywords: Docker, Docker documentation, requirements, linux, rhel, centos, oracle, ol
+
# Oracle Linux 6 and 7
@@ -43,35 +49,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 +105,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
@@ -110,7 +130,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/rackspace.md b/docs/installation/rackspace.md
similarity index 90%
rename from docs/sources/installation/rackspace.md
rename to docs/installation/rackspace.md
index 9fddf5e45..07c04e877 100644
--- a/docs/sources/installation/rackspace.md
+++ b/docs/installation/rackspace.md
@@ -1,6 +1,12 @@
-page_title: Installation on Rackspace Cloud
-page_description: Installation instructions for Docker on Rackspace Cloud.
-page_keywords: Rackspace Cloud, installation, docker, linux, ubuntu
+
# Rackspace Cloud
diff --git a/docs/installation/rhel.md b/docs/installation/rhel.md
new file mode 100644
index 000000000..1b96167d0
--- /dev/null
+++ b/docs/installation/rhel.md
@@ -0,0 +1,189 @@
+
+
+# Red Hat Enterprise Linux
+
+Docker is supported on the following versions of RHEL:
+
+- Red Hat Enterprise Linux 7
+- Red Hat Enterprise Linux 6.6 or later
+
+This page instructs you to install using Docker-managed release packages and
+installation mechanisms. Using these packages ensures you get the latest release
+of Docker. If you wish to install using Red Hat-managed packages, consult your
+Red Hat release documentation for information on Red Hat's Docker support.
+
+## Prerequisites
+
+Docker requires a 64-bit installation regardless of your Red Hat version. Docker
+requires that your kernel must be 3.10 at minimum. Red Hat 7 runs the 3.10
+kernel, 6.6 does not. We make an exception for Red Hat 6.6. To run Docker on
+[Red Hat-6.6](http://www.centos.org) or later, you need kernel 2.6.32-431 or
+higher.
+
+To check your current kernel version, open a terminal and use `uname -r` to
+display your kernel version:
+
+ $ uname -r
+ 3.10.0-229.el7.x86_64
+
+Finally, is it recommended that you fully update your system. Please keep in
+mind that your system should be fully patched to fix any potential kernel bugs.
+Any reported kernel bugs may have already been fixed on the latest kernel
+packages
+
+
+## Install
+
+You use the same installation procedure for all versions of Red Hat Enterprise,
+only the package you install differs. There are two packages to choose from:
+
+
diff --git a/docs/sources/project.md b/docs/sources/project.md
deleted file mode 100644
index fc1fe18ed..000000000
--- a/docs/sources/project.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# Project
-
-## Contents:
-
--[README first](who-written-for.md)
--[Get the required software](software-required.md)
--[Configure Git for contributing](set-up-git.md)
--[Work with a development container](set-up-dev-env.md)
--[Run tests and test documentation](test-and-docs.md)
--[Understand contribution workflow](make-a-contribution.md)
--[Find an issue](find-an-issue.md)
--[Work on an issue](work-issue.md)
--[Create a pull request](create-pr.md)
--[Participate in the PR Review](review-pr.md)
--[Advanced contributing](advanced-contributing.md)
--[Where to get help](get-help.md)
--[Coding style guide](coding-style.md)
--[Documentation style guide](doc-style.md)
-
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.md b/docs/sources/reference.md
deleted file mode 100644
index 6c1ab462d..000000000
--- a/docs/sources/reference.md
+++ /dev/null
@@ -1,9 +0,0 @@
-# Reference Manual
-
-## Contents:
-
- - [Commands](commandline/)
- - [Dockerfile Reference](builder/)
- - [Docker Run Reference](run/)
- - [APIs](api/)
-
diff --git a/docs/sources/release-notes.md b/docs/sources/release-notes.md
deleted file mode 100644
index fe79d881d..000000000
--- a/docs/sources/release-notes.md
+++ /dev/null
@@ -1,75 +0,0 @@
-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
-
-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.
-
-## 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*
-
-* [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
-
-> **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
-
-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.
-
-* **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 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**
-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).
diff --git a/docs/sources/terms.md b/docs/sources/terms.md
deleted file mode 100644
index 228b18fbd..000000000
--- a/docs/sources/terms.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Glossary
-
-*Definitions of terms used in Docker documentation.*
-
-## Contents:
-
- - [File System](filesystem/)
- - [Layers](layer/)
- - [Image](image/)
- - [Container](container/)
- - [Registry](registry/)
- - [Repository](repository/)
-
diff --git a/docs/sources/toctree.md b/docs/sources/toctree.md
deleted file mode 100644
index ec1832fc2..000000000
--- a/docs/sources/toctree.md
+++ /dev/null
@@ -1,17 +0,0 @@
-page_title: Documentation
-page_description: -- todo: change me
-page_keywords: todo, docker, documentation, installation, usage, examples, contributing, faq, command line, concepts
-
-# Documentation
-
-This documentation has the following resources:
-
- - [Installation](../installation/)
- - [Use](../use/)
- - [Examples](../examples/)
- - [Reference Manual](../reference/)
- - [Contributing](../contributing/)
- - [Glossary](../terms/)
- - [Articles](../articles/)
- - [FAQ](../faq/)
-
diff --git a/docs/sources/static_files/README.md b/docs/static_files/README.md
similarity index 79%
rename from docs/sources/static_files/README.md
rename to docs/static_files/README.md
index 99dc104d4..f09a25af4 100644
--- a/docs/sources/static_files/README.md
+++ b/docs/static_files/README.md
@@ -1,7 +1,7 @@
Static files dir
================
-Files you put in /sources/static_files/ will be copied to the web visible /_static/
+Files you put in /static_files/ will be copied to the web visible /_static/
Be careful not to override pre-existing static files from the template.
diff --git a/docs/sources/static_files/contributors.png b/docs/static_files/contributors.png
similarity index 100%
rename from docs/sources/static_files/contributors.png
rename to docs/static_files/contributors.png
diff --git a/docs/sources/static_files/docker-logo-compressed.png b/docs/static_files/docker-logo-compressed.png
similarity index 100%
rename from docs/sources/static_files/docker-logo-compressed.png
rename to docs/static_files/docker-logo-compressed.png
diff --git a/docs/sources/static_files/docker_pull_chart.png b/docs/static_files/docker_pull_chart.png
similarity index 100%
rename from docs/sources/static_files/docker_pull_chart.png
rename to docs/static_files/docker_pull_chart.png
diff --git a/docs/sources/static_files/docker_push_chart.png b/docs/static_files/docker_push_chart.png
similarity index 100%
rename from docs/sources/static_files/docker_push_chart.png
rename to docs/static_files/docker_push_chart.png
diff --git a/docs/sources/static_files/dockerlogo-v.png b/docs/static_files/dockerlogo-v.png
similarity index 100%
rename from docs/sources/static_files/dockerlogo-v.png
rename to docs/static_files/dockerlogo-v.png
diff --git a/docs/sources/terms/container.md b/docs/terms/container.md
similarity index 86%
rename from docs/sources/terms/container.md
rename to docs/terms/container.md
index 8b4286878..f52f728dd 100644
--- a/docs/sources/terms/container.md
+++ b/docs/terms/container.md
@@ -1,6 +1,13 @@
-page_title: Container
-page_description: Definitions of a container
-page_keywords: containers, lxc, concepts, explanation, image, container
+
# Container
@@ -17,7 +24,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/terms/filesystem.md
similarity index 84%
rename from docs/sources/terms/filesystem.md
rename to docs/terms/filesystem.md
index 5587e3c83..d0e0e36f0 100644
--- a/docs/sources/terms/filesystem.md
+++ b/docs/terms/filesystem.md
@@ -1,8 +1,15 @@
-page_title: File Systems
-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/terms/image.md
similarity index 81%
rename from docs/sources/terms/image.md
rename to docs/terms/image.md
index e42a6cfa1..a902882b4 100644
--- a/docs/sources/terms/image.md
+++ b/docs/terms/image.md
@@ -1,6 +1,13 @@
-page_title: Images
-page_description: Definition of an image
-page_keywords: containers, lxc, concepts, explanation, image, container
+
# Image
@@ -19,7 +26,7 @@ images do not have state.

-## Parent Image
+## Parent image

@@ -27,7 +34,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/images/docker-filesystems-busyboxrw.png b/docs/terms/images/docker-filesystems-busyboxrw.png
similarity index 100%
rename from docs/sources/terms/images/docker-filesystems-busyboxrw.png
rename to docs/terms/images/docker-filesystems-busyboxrw.png
diff --git a/docs/sources/terms/images/docker-filesystems-debian.png b/docs/terms/images/docker-filesystems-debian.png
similarity index 100%
rename from docs/sources/terms/images/docker-filesystems-debian.png
rename to docs/terms/images/docker-filesystems-debian.png
diff --git a/docs/sources/terms/images/docker-filesystems-debianrw.png b/docs/terms/images/docker-filesystems-debianrw.png
similarity index 100%
rename from docs/sources/terms/images/docker-filesystems-debianrw.png
rename to docs/terms/images/docker-filesystems-debianrw.png
diff --git a/docs/sources/terms/images/docker-filesystems-generic.png b/docs/terms/images/docker-filesystems-generic.png
similarity index 100%
rename from docs/sources/terms/images/docker-filesystems-generic.png
rename to docs/terms/images/docker-filesystems-generic.png
diff --git a/docs/sources/terms/images/docker-filesystems-multilayer.png b/docs/terms/images/docker-filesystems-multilayer.png
similarity index 100%
rename from docs/sources/terms/images/docker-filesystems-multilayer.png
rename to docs/terms/images/docker-filesystems-multilayer.png
diff --git a/docs/sources/terms/images/docker-filesystems-multiroot.png b/docs/terms/images/docker-filesystems-multiroot.png
similarity index 100%
rename from docs/sources/terms/images/docker-filesystems-multiroot.png
rename to docs/terms/images/docker-filesystems-multiroot.png
diff --git a/docs/sources/terms/images/docker-filesystems.svg b/docs/terms/images/docker-filesystems.svg
similarity index 100%
rename from docs/sources/terms/images/docker-filesystems.svg
rename to docs/terms/images/docker-filesystems.svg
diff --git a/docs/sources/terms/layer.md b/docs/terms/layer.md
similarity index 83%
rename from docs/sources/terms/layer.md
rename to docs/terms/layer.md
index 3e8704cd0..1b0ebad57 100644
--- a/docs/sources/terms/layer.md
+++ b/docs/terms/layer.md
@@ -1,6 +1,13 @@
-page_title: Layers
-page_description: Organizing the Docker Root File System
-page_keywords: containers, lxc, concepts, explanation, image, container
+
# Layers
diff --git a/docs/sources/terms/registry.md b/docs/terms/registry.md
similarity index 59%
rename from docs/sources/terms/registry.md
rename to docs/terms/registry.md
index 8a7e6237e..d1b268aa1 100644
--- a/docs/sources/terms/registry.md
+++ b/docs/terms/registry.md
@@ -1,6 +1,13 @@
-page_title: Registry
-page_description: Definition of an Registry
-page_keywords: containers, concepts, explanation, image, repository, container
+
# Registry
@@ -12,9 +19,9 @@ 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
+## Further reading
For more information see [*Working with
Repositories*](/userguide/dockerrepos/#working-with-the-repository)
diff --git a/docs/sources/terms/repository.md b/docs/terms/repository.md
similarity index 68%
rename from docs/sources/terms/repository.md
rename to docs/terms/repository.md
index c4d1d4353..5bf429cd1 100644
--- a/docs/sources/terms/repository.md
+++ b/docs/terms/repository.md
@@ -1,6 +1,13 @@
-page_title: Repository
-page_description: Definition of an Repository
-page_keywords: containers, concepts, explanation, image, repository, container
+
# Repository
@@ -13,11 +20,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:
@@ -29,7 +36,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/test.sh b/docs/test.sh
deleted file mode 100755
index 351236844..000000000
--- a/docs/test.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-
-mkdocs serve &
-echo "Waiting for 5 seconds to allow mkdocs server to be ready"
-sleep 5
-./docvalidate.py
diff --git a/docs/touch-up.sh b/docs/touch-up.sh
new file mode 100755
index 000000000..1dd0b1dc5
--- /dev/null
+++ b/docs/touch-up.sh
@@ -0,0 +1,20 @@
+#!/bin/bash -e
+
+
+# Sed to process GitHub Markdown
+# 1-2 Remove comment code from metadata block
+#
+for i in ls -l /docs/content/*
+ do # Line breaks are important
+ if [ -d $i ] # Spaces are important
+ then
+ y=${i##*/}
+ find $i -type f -name "*.md" -exec sed -i.old \
+ -e '/^/g' \
+ -e '/^/g' {} \;
+ fi
+done
+
+
+
+
diff --git a/docs/sources/userguide/dockerhub.md b/docs/userguide/dockerhub.md
similarity index 72%
rename from docs/sources/userguide/dockerhub.md
rename to docs/userguide/dockerhub.md
index 62438b994..89726d989 100644
--- a/docs/sources/userguide/dockerhub.md
+++ b/docs/userguide/dockerhub.md
@@ -1,8 +1,15 @@
-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),
@@ -10,7 +17,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.
@@ -21,7 +28,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:
@@ -42,7 +49,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 +65,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/userguide/dockerimages.md
similarity index 88%
rename from docs/sources/userguide/dockerimages.md
rename to docs/userguide/dockerimages.md
index 6224479fb..b4fd39aeb 100644
--- a/docs/sources/userguide/dockerimages.md
+++ b/docs/userguide/dockerimages.md
@@ -1,8 +1,15 @@
-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
+# Get started with images
In the [introduction](/introduction/understanding-docker/) we've discovered that Docker
images are the basis of containers. In the
@@ -27,7 +34,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
@@ -54,6 +61,13 @@ We can see three crucial pieces of information about our images in the listing.
* The tags for each image, for example `14.04`.
* The image ID of each image.
+> **Note:**
+> Previously, the `docker images` command supported the `--tree` and `--dot`
+> arguments, which displayed different visualizations of the image data. Docker
+> core removed this functionality in the 1.7 version. If you liked this
+> functionality, you can still find it in
+> [the third-party dockviz tool](https://github.com/justone/dockviz).
+
A repository potentially holds multiple variants of an image. In the case of
our `ubuntu` image we can see multiple variants covering Ubuntu 10.04, 12.04,
12.10, 13.04, 13.10 and 14.04. Each variant is identified by a tag and you can
@@ -63,11 +77,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 +99,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 +113,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 +131,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
@@ -131,11 +145,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,
@@ -152,11 +166,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 +188,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 +206,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 +229,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 +237,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`
@@ -242,6 +256,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.
@@ -273,7 +290,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 +484,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,19 +506,38 @@ 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
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
@@ -509,7 +545,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 +559,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/userguide/dockerizing.md
similarity index 86%
rename from docs/sources/userguide/dockerizing.md
rename to docs/userguide/dockerizing.md
index cc7bc8e1c..515c60111 100644
--- a/docs/sources/userguide/dockerizing.md
+++ b/docs/userguide/dockerizing.md
@@ -1,21 +1,29 @@
-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?*
Docker allows you to run applications inside containers. Running an
application inside a container takes a single command: `docker run`.
-{{ include "no-remote-sudo.md" }}
+> **Note:** if you are using a remote Docker daemon, such as Boot2Docker,
+> then _do not_ type the `sudo` before the `docker` commands shown in the
+> documentation's examples.
## Hello world
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!
@@ -48,12 +56,12 @@ 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.
- $ 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
@@ -90,7 +98,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,
@@ -98,7 +106,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 +143,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 +163,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 +179,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 +188,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/userguide/dockerlinks.md
similarity index 88%
rename from docs/sources/userguide/dockerlinks.md
rename to docs/userguide/dockerlinks.md
index 79ba17900..228e46a3b 100644
--- a/docs/sources/userguide/dockerlinks.md
+++ b/docs/userguide/dockerlinks.md
@@ -1,8 +1,15 @@
-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,12 +18,12 @@ 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:
- $ 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 +37,15 @@ 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:
+the `-p` flag. Here port 80 of the host is mapped to port 5000 of the
+container:
- $ sudo 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,26 +55,26 @@ 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: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
`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: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
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,22 +106,20 @@ 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
- /web
-> **Note:**
+> **Note:**
> Container names have to be unique. That means you can only call
> one container `web`. If you want to re-use a container name you must delete
> the old container (with `docker rm`) before you can create a new
@@ -129,7 +135,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 +143,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:
@@ -150,10 +156,18 @@ earlier. The `--link` flag takes the form:
Where `name` is the name of the container we're linking to and `alias` is an
alias for the link name. You'll see how that alias gets used shortly.
+The `--link` flag also takes the form:
+
+ --link
+
+In which case the alias will match the name. You could have written the previous
+example as:
+
+ $ docker run -d -P --name web --link db training/webapp python app.py
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
@@ -174,7 +188,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
@@ -239,7 +253,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 +290,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 +328,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/userguide/dockerrepos.md
similarity index 85%
rename from docs/sources/userguide/dockerrepos.md
rename to docs/userguide/dockerrepos.md
index d8dc44e69..5b3b6c533 100644
--- a/docs/sources/userguide/dockerrepos.md
+++ b/docs/userguide/dockerrepos.md
@@ -1,8 +1,15 @@
-page_title: Working with Docker Hub
-page_description: Learn how to use the Docker Hub to manage Docker images and work flow
-page_keywords: repo, Docker Hub, Docker Hub, registry, index, repositories, usage, pull image, push image, image, documentation
+
-# Working with Docker Hub
+# Get started with Docker Hub
So far you've learned how to use the command line to run Docker on your local host.
You've learned how to [pull down images](/userguide/usingdocker/) to build containers
@@ -27,7 +34,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,22 +52,22 @@ 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
...
-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 `:
- $ sudo docker pull centos
+ $ docker pull centos
Pulling repository centos
0b443ba03958: Download complete
539c0211cd76: Download complete
@@ -86,7 +93,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.
@@ -94,14 +101,14 @@ community.
## Features of Docker Hub
Let's take a closer look at some of the features of Docker Hub. You can find more
-information [here](http://docs.docker.com/docker-hub/).
+information [here](https://docs.docker.com/docker-hub/).
* Private repositories
* Organizations and teams
* 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 +157,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.
@@ -163,7 +170,7 @@ a webhook you can specify a target URL and a JSON payload that will be
delivered when the image is pushed.
See the Docker Hub documentation for [more information on
-webhooks](http://docs.docker.com/docker-hub/repos/#webhooks)
+webhooks](https://docs.docker.com/docker-hub/repos/#webhooks)
## Next steps
diff --git a/docs/sources/userguide/dockervolumes.md b/docs/userguide/dockervolumes.md
similarity index 78%
rename from docs/sources/userguide/dockervolumes.md
rename to docs/userguide/dockervolumes.md
index d53322465..40ff48301 100644
--- a/docs/sources/userguide/dockervolumes.md
+++ b/docs/userguide/dockervolumes.md
@@ -1,8 +1,15 @@
-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
@@ -25,8 +32,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.
@@ -44,7 +51,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`.
@@ -52,7 +59,28 @@ 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.
-### Mount a Host Directory as a Data Volume
+### 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
directory from your Docker daemon's host into a container.
@@ -65,7 +93,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`.
@@ -90,17 +118,17 @@ 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.
-### 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.
- $ 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
@@ -113,7 +141,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
@@ -124,15 +152,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
@@ -145,7 +173,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
@@ -168,7 +196,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
@@ -180,11 +208,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 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.
diff --git a/docs/sources/userguide/index.md b/docs/userguide/index.md
similarity index 85%
rename from docs/sources/userguide/index.md
rename to docs/userguide/index.md
index d0dbdb84e..0e5819db7 100644
--- a/docs/sources/userguide/index.md
+++ b/docs/userguide/index.md
@@ -1,8 +1,14 @@
-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 +25,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 +35,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 +44,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 +54,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 +63,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 +71,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.
@@ -110,7 +116,7 @@ Go to [Docker Swarm user guide](/swarm/).
* [Docker homepage](http://www.docker.com/)
* [Docker Hub](https://hub.docker.com)
* [Docker blog](http://blog.docker.com/)
-* [Docker documentation](http://docs.docker.com/)
+* [Docker documentation](https://docs.docker.com/)
* [Docker Getting Started Guide](http://www.docker.com/gettingstarted/)
* [Docker code on GitHub](https://github.com/docker/docker)
* [Docker mailing
diff --git a/docs/sources/userguide/labels-custom-metadata.md b/docs/userguide/labels-custom-metadata.md
similarity index 91%
rename from docs/sources/userguide/labels-custom-metadata.md
rename to docs/userguide/labels-custom-metadata.md
index 7cf25c060..693bfd55b 100644
--- a/docs/sources/userguide/labels-custom-metadata.md
+++ b/docs/userguide/labels-custom-metadata.md
@@ -1,6 +1,12 @@
-page_title: Apply custom metadata
-page_description: Learn how to work with custom metadata in Docker, using labels.
-page_keywords: Usage, user guide, labels, metadata, docker, documentation, examples, annotating
+
# Apply custom metadata
@@ -38,7 +44,7 @@ notation. Use the following guidelines to name your keys:
reverse DNS notation of a domain controlled by the author. For
example, `com.example.some-label`.
-- The `com.docker.*`, `io.docker.*` and `com.dockerproject.*` namespaces are
+- The `com.docker.*`, `io.docker.*` and `org.dockerproject.*` namespaces are
reserved for Docker's internal use.
- Keys should only consist of lower-cased alphanumeric characters,
@@ -129,10 +135,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
@@ -171,6 +181,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
@@ -179,7 +190,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/docs/sources/userguide/level1.md b/docs/userguide/level1.md
similarity index 91%
rename from docs/sources/userguide/level1.md
rename to docs/userguide/level1.md
index cca77dc36..74a81d790 100644
--- a/docs/sources/userguide/level1.md
+++ b/docs/userguide/level1.md
@@ -1,10 +1,14 @@
-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/userguide/level2.md
similarity index 93%
rename from docs/sources/userguide/level2.md
rename to docs/userguide/level2.md
index fe6654e71..da57faf21 100644
--- a/docs/sources/userguide/level2.md
+++ b/docs/userguide/level2.md
@@ -1,10 +1,14 @@
-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/login-web.png b/docs/userguide/login-web.png
similarity index 100%
rename from docs/sources/userguide/login-web.png
rename to docs/userguide/login-web.png
diff --git a/docs/sources/userguide/register-web.png b/docs/userguide/register-web.png
similarity index 100%
rename from docs/sources/userguide/register-web.png
rename to docs/userguide/register-web.png
diff --git a/docs/sources/userguide/search.png b/docs/userguide/search.png
similarity index 100%
rename from docs/sources/userguide/search.png
rename to docs/userguide/search.png
diff --git a/docs/sources/userguide/usingdocker.md b/docs/userguide/usingdocker.md
similarity index 76%
rename from docs/sources/userguide/usingdocker.md
rename to docs/userguide/usingdocker.md
index 8d57def4e..32dff55d9 100644
--- a/docs/sources/userguide/usingdocker.md
+++ b/docs/userguide/usingdocker.md
@@ -1,8 +1,14 @@
-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
@@ -27,12 +33,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
@@ -49,49 +55,31 @@ language powering Docker).
Last stable version: 0.8.0
-### Seeing what the Docker client can do
+## Get Docker command help
-We can see all of the commands available to us with the Docker client by
-running the `docker` binary without any options.
+You can display the help for specific Docker commands. The help details the
+options and their usage. To see a list of all the possible commands, use the
+following:
- $ sudo docker
+ $ docker --help
-You will see a list of all currently available commands.
+To see usage for a specific command, specify the command with the `--help` flag:
- Commands:
- attach Attach to a running container
- build Build an image from a Dockerfile
- commit Create a new image from a container's changes
- . . .
-
-### Seeing Docker command usage
-
-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
- Help output . . .
-
-Or you can also pass the `--help` flag to the `docker` binary.
-
- $ sudo docker attach --help
-
-This will display the help text and all available flags:
+ $ docker attach --help
Usage: docker attach [OPTIONS] CONTAINER
Attach to a running container
- --no-stdin=false: Do not attach stdin
- --sig-proxy=true: Proxify all received signal to the process (non-TTY mode only)
+ --help=false Print usage
+ --no-stdin=false Do not attach stdin
+ --sig-proxy=true Proxy all received signals to the process
> **Note:**
-> You can see a full list of Docker's commands
-> [here](/reference/commandline/cli/).
+> For further details and examples of each command, see the
+> [command reference](/reference/commandline/cli/) in this guide.
-## 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
@@ -102,7 +90,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
@@ -121,11 +109,11 @@ 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.
- $ 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,9 +148,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:
- $ sudo 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
@@ -179,35 +167,35 @@ 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
>
> 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
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
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`.
- $ 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 -
@@ -217,25 +205,25 @@ 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.
- $ sudo docker top nostalgic_morse
+ $ docker top nostalgic_morse
PID USER COMMAND
854 root python app.py
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
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,30 +243,30 @@ 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
+## 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`.
- $ 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
+## 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
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
@@ -289,22 +277,22 @@ 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.
- $ 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
-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.
- $ 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.
diff --git a/docs/sources/userguide/webapp1.png b/docs/userguide/webapp1.png
similarity index 100%
rename from docs/sources/userguide/webapp1.png
rename to docs/userguide/webapp1.png
diff --git a/engine/engine.go b/engine/engine.go
deleted file mode 100644
index 60532349a..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/common"
- "github.com/docker/docker/pkg/ioutils"
-)
-
-// 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) Status
-
-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: common.RandomString(),
- Stdout: os.Stdout,
- Stderr: os.Stderr,
- Stdin: os.Stdin,
- Logging: true,
- }
- eng.Register("commands", func(job *Job) Status {
- for _, name := range eng.commands() {
- job.Printf("%s\n", name)
- }
- return StatusOK
- })
- // 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 96c3f0df3..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) Status {
- j.Printf("%s\n", j.Name)
- return 42
- }
-
- 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) != 42 {
- 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) Status { return StatusOK }
- 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) Status {
- called = true
- return StatusOK
- })
- 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) Status {
- called = true
- return StatusOK
- })
- 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) Status {
- 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 StatusOK
- }
-
- innerHandler = func(job *Job) Status {
- job.Stdout.Write([]byte(" inner"))
-
- return StatusOK
- }
-
- 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 a671f13c6..000000000
--- a/engine/env.go
+++ /dev/null
@@ -1,310 +0,0 @@
-package engine
-
-import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "strconv"
- "strings"
- "time"
-
- "github.com/docker/docker/utils"
-)
-
-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{})
- if err := json.NewDecoder(src).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 http://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 http://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 := utils.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 2ed99d0fe..000000000
--- a/engine/env_test.go
+++ /dev/null
@@ -1,346 +0,0 @@
-package engine
-
-import (
- "bytes"
- "encoding/json"
- "testing"
- "time"
-
- "github.com/docker/docker/pkg/testutils"
-)
-
-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 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{testutils.RandomString(5), testutils.RandomString(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 be4fadbe6..000000000
--- a/engine/hack.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package engine
-
-type Hack map[string]interface{}
-
-func (eng *Engine) Hack_GetGlobalVar(key string) interface{} {
- if eng.hack == nil {
- return nil
- }
- val, exists := eng.hack[key]
- if !exists {
- return nil
- }
- return val
-}
-
-func (eng *Engine) Hack_SetGlobalVar(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 ecb68c3eb..000000000
--- a/engine/job.go
+++ /dev/null
@@ -1,269 +0,0 @@
-package engine
-
-import (
- "bytes"
- "fmt"
- "io"
- "strings"
- "sync"
- "time"
-
- log "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, 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.
-//
-type Job struct {
- Eng *Engine
- Name string
- Args []string
- env *Env
- Stdout *Output
- Stderr *Output
- Stdin *Input
- handler Handler
- status Status
- 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
-}
-
-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.
-func (job *Job) Run() error {
- 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 {
- log.Infof("+job %s", job.CallString())
- defer func() {
- log.Infof("-job %s%s", job.CallString(), job.StatusString())
- }()
- }
- var errorMessage = bytes.NewBuffer(nil)
- job.Stderr.Add(errorMessage)
- if job.handler == nil {
- job.Errorf("%s: command not found", job.Name)
- job.status = 127
- } else {
- job.status = 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
- }
- }
- if job.status != 0 {
- return fmt.Errorf("%s", Tail(errorMessage, 1))
- }
-
- return nil
-}
-
-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
-}
-
-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) 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...)
-}
-
-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
-}
-
-// 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 9f8c76095..000000000
--- a/engine/job_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package engine
-
-import (
- "bytes"
- "fmt"
- "testing"
-)
-
-func TestJobStatusOK(t *testing.T) {
- eng := New()
- eng.Register("return_ok", func(job *Job) Status { return StatusOK })
- err := eng.Job("return_ok").Run()
- if err != nil {
- t.Fatalf("Expected: err=%v\nReceived: err=%v", nil, err)
- }
-}
-
-func TestJobStatusErr(t *testing.T) {
- eng := New()
- eng.Register("return_err", func(job *Job) Status { return StatusErr })
- 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")
- }
-}
-
-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 {
- job.Printf("Hello world\n")
- return StatusOK
- })
-
- 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)
- }
-}
-
-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
deleted file mode 100644
index 13d804926..000000000
--- a/engine/shutdown_test.go
+++ /dev/null
@@ -1,80 +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()
- var called bool
- eng.Register("foo", func(job *Job) Status {
- called = true
- return StatusOK
- })
- 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) Status {
- time.Sleep(jobDelay)
- completed = true
- return StatusOK
- })
- 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 216fb8980..000000000
--- a/engine/streams.go
+++ /dev/null
@@ -1,225 +0,0 @@
-package engine
-
-import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "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
-}
-
-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/streams_test.go b/engine/streams_test.go
deleted file mode 100644
index 476a721ba..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 shoul 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/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/events/events.go b/events/events.go
deleted file mode 100644
index 559bf687e..000000000
--- a/events/events.go
+++ /dev/null
@@ -1,230 +0,0 @@
-package events
-
-import (
- "bytes"
- "encoding/json"
- "io"
- "strings"
- "sync"
- "time"
-
- "github.com/docker/docker/engine"
- "github.com/docker/docker/pkg/parsers/filters"
- "github.com/docker/docker/utils"
-)
-
-const eventsLimit = 64
-
-type listener chan<- *utils.JSONMessage
-
-type Events struct {
- mu sync.RWMutex
- events []*utils.JSONMessage
- subscribers []listener
-}
-
-func New() *Events {
- return &Events{
- events: make([]*utils.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) engine.Status {
- 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 job.Error(err)
- }
-
- // If no until, disable timeout
- if until == 0 {
- timeout.Stop()
- }
-
- listener := make(chan *utils.JSONMessage)
- e.subscribe(listener)
- defer e.unsubscribe(listener)
-
- job.Stdout.Write(nil)
-
- // 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)
- }
- }
-
- for {
- select {
- case event, ok := <-listener:
- if !ok {
- return engine.StatusOK
- }
- if err := writeEvent(job, event, eventFilters); err != nil {
- return job.Error(err)
- }
- case <-timeout.C:
- return engine.StatusOK
- }
- }
-}
-
-func (e *Events) Log(job *engine.Job) engine.Status {
- if len(job.Args) != 3 {
- return job.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
-}
-
-func (e *Events) SubscribersCount(job *engine.Job) engine.Status {
- ret := &engine.Env{}
- ret.SetInt("count", e.subscribersCount())
- ret.WriteTo(job.Stdout)
- return engine.StatusOK
-}
-
-func writeEvent(job *engine.Job, event *utils.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 := &utils.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 d4fc664ba..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/utils"
-)
-
-func TestEventsPublish(t *testing.T) {
- e := New()
- l1 := make(chan *utils.JSONMessage)
- l2 := make(chan *utils.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 *utils.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 []utils.JSONMessage
- for {
- var jm utils.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 *utils.JSONMessage)
- l2 := make(chan *utils.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)
- }
-}
diff --git a/experimental/README.md b/experimental/README.md
new file mode 100644
index 000000000..0933e7eca
--- /dev/null
+++ b/experimental/README.md
@@ -0,0 +1,54 @@
+# Docker Experimental Features
+
+ This page contains a list of features in the Docker engine which are
+experimental. Experimental features are **not** ready for production. They are
+provided for test and evaluation in your sandbox environments.
+
+The information below describes each feature and the Github pull requests and
+issues associated with it. If necessary, links are provided to additional
+documentation on an issue. As an active Docker user and community member,
+please feel free to provide any feedback on these features you wish.
+
+## Install Docker experimental
+
+Unlike the regular Docker binary, the experimental channels is built and updated nightly on https://experimental.docker.com. From one day to the next, new features may appear, while existing experimental features may be refined or entirely removed.
+
+1. Verify that you have `wget` installed.
+
+ $ which wget
+
+ If `wget` isn't installed, install it after updating your manager:
+
+ $ sudo apt-get update
+ $ sudo apt-get install wget
+
+2. Get the latest Docker package.
+
+ $ wget -qO- https://experimental.docker.com/ | sh
+
+ 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://experimental.docker.com/gpg | sudo apt-key add -
+
+3. Verify `docker` is installed correctly.
+
+ $ sudo docker run hello-world
+
+ This command downloads a test image and runs it in a container.
+
+## Current experimental features
+
+* [Support for Docker plugins](plugins.md)
+* [Volume plugins](plugins_volume.md)
+
+## How to comment on an experimental feature
+
+Each feature's documentation includes a list of proposal pull requests or PRs associated with the feature. If you want to comment on or suggest a change to a feature, please add it to the existing feature PR.
+
+Issues or problems with a feature? Inquire for help on the `#docker` IRC channel or in on the [Docker Google group](https://groups.google.com/forum/#!forum/docker-user).
diff --git a/experimental/plugin_api.md b/experimental/plugin_api.md
new file mode 100644
index 000000000..9309b2846
--- /dev/null
+++ b/experimental/plugin_api.md
@@ -0,0 +1,221 @@
+# Experimental: Docker Plugin API
+
+Docker plugins are out-of-process extensions which add capabilities to the
+Docker Engine.
+
+This page is intended for people who want to develop their own Docker plugin.
+If you just want to learn about or use Docker plugins, look
+[here](/experimental/plugins.md).
+
+This is an experimental feature. For information on installing and using experimental features, see [the experimental feature overview](README.md).
+
+## What plugins are
+
+A plugin is a process running on the same docker host as the docker daemon,
+which registers itself by placing a file in `/usr/share/docker/plugins` (the
+"plugin directory").
+
+Plugins have human-readable names, which are short, lowercase strings. For
+example, `flocker` or `weave`.
+
+Plugins can run inside or outside containers. Currently running them outside
+containers is recommended.
+
+## Plugin discovery
+
+Docker discovers plugins by looking for them in the plugin directory whenever a
+user or container tries to use one by name.
+
+There are two types of files which can be put in the plugin directory.
+
+* `.sock` files are UNIX domain sockets.
+* `.spec` files are text files containing a URL, such as `unix:///other.sock`.
+
+The name of the file (excluding the extension) determines the plugin name.
+
+For example, the `flocker` plugin might create a UNIX socket at
+`/usr/share/docker/plugins/flocker.sock`.
+
+Plugins must be run locally on the same machine as the Docker daemon. UNIX
+domain sockets are strongly encouraged for security reasons.
+
+## Plugin lifecycle
+
+Plugins should be started before Docker, and stopped after Docker. For
+example, when packaging a plugin for a platform which supports `systemd`, you
+might use [`systemd` dependencies](
+http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before=) to
+manage startup and shutdown order.
+
+When upgrading a plugin, you should first stop the Docker daemon, upgrade the
+plugin, then start Docker again.
+
+If a plugin is packaged as a container, this may cause issues. Plugins as
+containers are currently considered experimental due to these shutdown/startup
+ordering issues. These issues are mitigated by plugin retries (see below).
+
+## Plugin activation
+
+When a plugin is first referred to -- either by a user referring to it by name
+(e.g. `docker run --volume-driver=foo`) or a container already configured to
+use a plugin being started -- Docker looks for the named plugin in the plugin
+directory and activates it with a handshake. See Handshake API below.
+
+Plugins are *not* activated automatically at Docker daemon startup. Rather,
+they are activated only lazily, or on-demand, when they are needed.
+
+## API design
+
+The Plugin API is RPC-style JSON over HTTP, much like webhooks.
+
+Requests flow *from* the Docker daemon *to* the plugin. So the plugin needs to
+implement an HTTP server and bind this to the UNIX socket mentioned in the
+"plugin discovery" section.
+
+All requests are HTTP `POST` requests.
+
+The API is versioned via an Accept header, which currently is always set to
+`application/vnd.docker.plugins.v1+json`.
+
+## Handshake API
+
+Plugins are activated via the following "handshake" API call.
+
+### /Plugin.Activate
+
+**Request:** empty body
+
+**Response:**
+```
+{
+ "Implements": ["VolumeDriver"]
+}
+```
+
+Responds with a list of Docker subsystems which this plugin implements.
+After activation, the plugin will then be sent events from this subsystem.
+
+## Volume API
+
+If a plugin registers itself as a `VolumeDriver` (see above) then it is
+expected to provide writeable paths on the host filesystem for the Docker
+daemon to provide to containers to consume.
+
+The Docker daemon handles bind-mounting the provided paths into user
+containers.
+
+### /VolumeDriver.Create
+
+**Request**:
+```
+{
+ "Name": "volume_name"
+}
+```
+
+Instruct the plugin that the user wants to create a volume, given a user
+specified volume name. The plugin does not need to actually manifest the
+volume on the filesystem yet (until Mount is called).
+
+**Response**:
+```
+{
+ "Err": null
+}
+```
+
+Respond with a string error if an error occurred.
+
+### /VolumeDriver.Remove
+
+**Request**:
+```
+{
+ "Name": "volume_name"
+}
+```
+
+Create a volume, given a user specified volume name.
+
+**Response**:
+```
+{
+ "Err": null
+}
+```
+
+Respond with a string error if an error occurred.
+
+### /VolumeDriver.Mount
+
+**Request**:
+```
+{
+ "Name": "volume_name"
+}
+```
+
+Docker requires the plugin to provide a volume, given a user specified volume
+name. This is called once per container start.
+
+**Response**:
+```
+{
+ "Mountpoint": "/path/to/directory/on/host",
+ "Err": null
+}
+```
+
+Respond with the path on the host filesystem where the volume has been made
+available, and/or a string error if an error occurred.
+
+### /VolumeDriver.Path
+
+**Request**:
+```
+{
+ "Name": "volume_name"
+}
+```
+
+Docker needs reminding of the path to the volume on the host.
+
+**Response**:
+```
+{
+ "Mountpoint": "/path/to/directory/on/host",
+ "Err": null
+}
+```
+
+Respond with the path on the host filesystem where the volume has been made
+available, and/or a string error if an error occurred.
+
+### /VolumeDriver.Unmount
+
+**Request**:
+```
+{
+ "Name": "volume_name"
+}
+```
+
+Indication that Docker no longer is using the named volume. This is called once
+per container stop. Plugin may deduce that it is safe to deprovision it at
+this point.
+
+**Response**:
+```
+{
+ "Err": null
+}
+```
+
+Respond with a string error if an error occurred.
+
+## Plugin retries
+
+Attempts to call a method on a plugin are retried with an exponential backoff
+for up to 30 seconds. This may help when packaging plugins as containers, since
+it gives plugin containers a chance to start up before failing any user
+containers which depend on them.
diff --git a/experimental/plugins.md b/experimental/plugins.md
new file mode 100644
index 000000000..9ba4f9296
--- /dev/null
+++ b/experimental/plugins.md
@@ -0,0 +1,45 @@
+# Experimental: Extend Docker with a plugin
+
+You can extend the capabilities of the Docker Engine by loading third-party
+plugins.
+
+This is an experimental feature. For information on installing and using experimental features, see [the experimental feature overview](README.md).
+
+## Types of plugins
+
+Plugins extend Docker's functionality. They come in specific types. For
+example, a [volume plugin](/experimental/plugins_volume.md) might enable Docker
+volumes to persist across multiple Docker hosts.
+
+Currently Docker supports volume plugins. In the future it will support
+additional plugin types.
+
+## Installing a plugin
+
+Follow the instructions in the plugin's documentation.
+
+## Finding a plugin
+
+The following plugins exist:
+
+* The [Flocker plugin](https://clusterhq.com/docker-plugin/) is a volume plugin
+which provides multi-host portable volumes for Docker, enabling you to run
+ databases and other stateful containers and move them around across a cluster
+ of machines.
+
+## Troubleshooting a plugin
+
+If you are having problems with Docker after loading a plugin, ask the authors
+of the plugin for help. The Docker team may not be able to assist you.
+
+## Writing a plugin
+
+If you are interested in writing a plugin for Docker, or seeing how they work
+under the hood, see the [docker plugins reference](/experimental/plugin_api.md).
+
+# Related GitHub PRs and issues
+
+- [#13222](https://github.com/docker/docker/pull/13222) Plugins plumbing
+
+Send us feedback and comments on [#13419](https://github.com/docker/docker/issues/13419),
+or on the usual Google Groups (docker-user, docker-dev) and IRC channels.
diff --git a/experimental/plugins_volume.md b/experimental/plugins_volume.md
new file mode 100644
index 000000000..cc74796fb
--- /dev/null
+++ b/experimental/plugins_volume.md
@@ -0,0 +1,42 @@
+# Experimental: Docker volume plugins
+
+Docker volume plugins enable Docker deployments to be integrated with external
+storage systems, such as Amazon EBS, and enable data volumes to persist beyond
+the lifetime of a single Docker host. See the [plugin documentation](/experimental/plugins.md)
+for more information.
+
+This is an experimental feature. For information on installing and using experimental features, see [the experimental feature overview](README.md).
+
+# Command-line changes
+
+This experimental features introduces two changes to the `docker run` command:
+
+- The `--volume-driver` flag is introduced.
+- The `-v` syntax is changed to accept a volume name a first component.
+
+Example:
+
+ $ docker run -ti -v volumename:/data --volume-driver=flocker busybox sh
+
+By specifying a volume name in conjunction with a volume driver, volume plugins
+such as [Flocker](https://clusterhq.com/docker-plugin/), once installed, can be
+used to manage volumes external to a single host, such as those on EBS. In this
+example, "volumename" is passed through to the volume plugin as a user-given
+name for the volume which allows the plugin to associate it with an external
+volume beyond the lifetime of a single container or container host. This can be
+used, for example, to move a stateful container from one server to another.
+
+The `volumename` must not begin with a `/`.
+
+# API changes
+
+The container creation endpoint (`/containers/create`) accepts a `VolumeDriver`
+field of type `string` allowing to specify the name of the driver. It's default
+value of `"local"` (the default driver for local volumes).
+
+# Related GitHub PRs and issues
+
+- [#13161](https://github.com/docker/docker/pull/13161) Volume refactor and external volume plugins
+
+Send us feedback and comments on [#13420](https://github.com/docker/docker/issues/13420),
+or on the usual Google Groups (docker-user, docker-dev) and IRC channels.
diff --git a/graph/export.go b/graph/export.go
index 3f7ecd3c4..c356a2322 100644
--- a/graph/export.go
+++ b/graph/export.go
@@ -5,10 +5,9 @@ import (
"io"
"io/ioutil"
"os"
- "path"
+ "path/filepath"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
+ "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/registry"
@@ -19,42 +18,45 @@ 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 {
- if len(job.Args) < 1 {
- return job.Errorf("Usage: %s IMAGE [IMAGE...]\n", job.Name)
- }
+type ImageExportConfig struct {
+ Names []string
+ Outstream io.Writer
+}
+
+func (s *TagStore) ImageExport(imageExportConfig *ImageExportConfig) error {
+
// get image json
tempdir, err := ioutil.TempDir("", "docker-export-")
if err != nil {
- return job.Error(err)
+ return err
}
defer os.RemoveAll(tempdir)
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 {
repo[tag] = id
}
}
- for _, name := range job.Args {
+ for _, name := range imageExportConfig.Names {
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'
for tag, id := range rootRepo {
addKey(name, tag, id)
- if err := s.exportImage(job.Eng, id, tempdir); err != nil {
- return job.Error(err)
+ if err := s.exportImage(id, tempdir); err != nil {
+ return err
}
}
} else {
img, err := s.LookupImage(name)
if err != nil {
- return job.Error(err)
+ return err
}
if img != nil {
@@ -66,47 +68,54 @@ func (s *TagStore) CmdImageExport(job *engine.Job) engine.Status {
if len(repoTag) > 0 {
addKey(repoName, repoTag, img.ID)
}
- if err := s.exportImage(job.Eng, img.ID, tempdir); err != nil {
- return job.Error(err)
+ 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(job.Eng, name, tempdir); err != nil {
- return job.Error(err)
+ if err := s.exportImage(name, tempdir); err != nil {
+ return err
}
}
}
- log.Debugf("End Serializing %s", name)
+ logrus.Debugf("End Serializing %s", name)
}
// 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 {
- return job.Error(err)
+ 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
+ }
+ if err := json.NewEncoder(f).Encode(rootRepoMap); err != nil {
+ return err
+ }
+ if err := f.Close(); err != nil {
+ 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)
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)
+ if _, err := io.Copy(imageExportConfig.Outstream, fs); err != nil {
+ return err
}
- log.Debugf("End export job: %s", job.Name)
- return engine.StatusOK
+ logrus.Debugf("End export image")
+ return nil
}
// 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)
+ tmpImageDir := filepath.Join(tempdir, n)
if err := os.Mkdir(tmpImageDir, os.FileMode(0755)); err != nil {
if os.IsExist(err) {
return nil
@@ -117,40 +126,42 @@ func (s *TagStore) exportImage(eng *engine.Engine, 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
}
- 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"))
+ fsTar, err := os.Create(filepath.Join(tmpImageDir, "layer.tar"))
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
}
// 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/graph.go b/graph/graph.go
index 5c6ac9701..e95887fab 100644
--- a/graph/graph.go
+++ b/graph/graph.go
@@ -7,24 +7,24 @@ import (
"io"
"io/ioutil"
"os"
- "path"
"path/filepath"
"runtime"
"strings"
"syscall"
"time"
- log "github.com/Sirupsen/logrus"
+ "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"
"github.com/docker/docker/pkg/archive"
- "github.com/docker/docker/pkg/common"
"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"
- "github.com/docker/docker/utils"
)
// A Graph is a store for versioned filesystem images and the relationship between them.
@@ -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
}
@@ -70,14 +70,14 @@ 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
}
// 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.
@@ -121,7 +121,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,
@@ -153,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)
@@ -201,7 +201,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
@@ -220,7 +220,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()
@@ -229,8 +229,8 @@ 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())
- 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
@@ -254,9 +254,6 @@ func bufferToFile(f *os.File, src io.Reader) (int64, digest.Digest, error) {
if err != nil {
return 0, "", err
}
- if err = f.Sync(); err != nil {
- return 0, "", err
- }
n, err := f.Seek(0, os.SEEK_CUR)
if err != nil {
return 0, "", err
@@ -290,28 +287,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
}
}
@@ -348,9 +345,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)
}
@@ -433,7 +429,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 {
diff --git a/integration/graph_test.go b/graph/graph_test.go
similarity index 88%
rename from integration/graph_test.go
rename to graph/graph_test.go
index 8518fae8e..81471b674 100644
--- a/integration/graph_test.go
+++ b/graph/graph_test.go
@@ -1,20 +1,18 @@
-package docker
+package graph
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/image"
+ "github.com/docker/docker/pkg/stringid"
)
func TestMount(t *testing.T) {
@@ -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) {
@@ -70,7 +69,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(),
}
@@ -102,7 +101,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" {
@@ -130,7 +129,7 @@ func TestRegister(t *testing.T) {
t.Fatal(err)
}
image := &image.Image{
- ID: common.GenerateRandomID(),
+ ID: stringid.GenerateRandomID(),
Comment: "testing",
Created: time.Now(),
}
@@ -160,24 +159,12 @@ 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)
}
-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)
@@ -246,19 +233,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,
@@ -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
-}
diff --git a/graph/history.go b/graph/history.go
index 7f5063e91..56e759a8e 100644
--- a/graph/history.go
+++ b/graph/history.go
@@ -3,19 +3,15 @@ package graph
import (
"strings"
- "github.com/docker/docker/engine"
+ "github.com/docker/docker/api/types"
"github.com/docker/docker/image"
"github.com/docker/docker/utils"
)
-func (s *TagStore) CmdHistory(job *engine.Job) engine.Status {
- if n := len(job.Args); n != 1 {
- return job.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 job.Error(err)
+ return nil, err
}
lookupMap := make(map[string][]string)
@@ -29,19 +25,19 @@ func (s *TagStore) CmdHistory(job *engine.Job) engine.Status {
}
}
- 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.Slice(), " "),
+ Tags: lookupMap[img.ID],
+ Size: img.Size,
+ Comment: img.Comment,
+ })
return nil
})
- if _, err := outs.WriteListTo(job.Stdout); err != nil {
- return job.Error(err)
- }
- return engine.StatusOK
+
+ return history, err
}
diff --git a/graph/import.go b/graph/import.go
index 44b1ecbd5..2e08e6c84 100644
--- a/graph/import.go
+++ b/graph/import.go
@@ -1,57 +1,52 @@
package graph
import (
- "bytes"
- "encoding/json"
+ "io"
"net/http"
"net/url"
- log "github.com/Sirupsen/logrus"
- "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"
"github.com/docker/docker/utils"
)
-func (s *TagStore) CmdImport(job *engine.Job) engine.Status {
- if n := len(job.Args); n != 2 && n != 3 {
- return job.Errorf("Usage: %s SRC REPO [TAG]", job.Name)
- }
+type ImageImportConfig struct {
+ Changes []string
+ InConfig io.ReadCloser
+ OutStream io.Writer
+ ContainerConfig *runconfig.Config
+}
+
+func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig *ImageImportConfig) error {
var (
- src = job.Args[0]
- repo = job.Args[1]
- tag string
- sf = utils.NewStreamFormatter(job.GetenvBool("json"))
- archive archive.ArchiveReader
- resp *http.Response
- stdoutBuffer = bytes.NewBuffer(nil)
- newConfig runconfig.Config
+ sf = streamformatter.NewJSONStreamFormatter()
+ archive archive.ArchiveReader
+ resp *http.Response
)
- 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 {
- return job.Error(err)
+ return err
}
if u.Scheme == "" {
u.Scheme = "http"
u.Host = src
u.Path = ""
}
- job.Stdout.Write(sf.FormatStatus("", "Downloading from %s", u))
- resp, err = utils.Download(u.String())
+ imageImportConfig.OutStream.Write(sf.FormatStatus("", "Downloading from %s", u))
+ resp, err = httputils.Download(u.String())
if err != nil {
- return job.Error(err)
+ return err
}
progressReader := progressreader.New(progressreader.Config{
In: resp.Body,
- Out: job.Stdout,
+ Out: imageImportConfig.OutStream,
Formatter: sf,
Size: int(resp.ContentLength),
NewLines: true,
@@ -62,36 +57,22 @@ func (s *TagStore) CmdImport(job *engine.Job) engine.Status {
archive = progressReader
}
- buildConfigJob := job.Eng.Job("build_config")
- buildConfigJob.Stdout.Add(stdoutBuffer)
- buildConfigJob.Setenv("changes", job.Getenv("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 job.Error(err)
- }
- if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil {
- return job.Error(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 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)
+ if err := s.Tag(repo, tag, img.ID, true); err != nil {
+ 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)
}
- 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
+
+ s.eventsService.Log("import", logID, "")
+ return nil
}
diff --git a/graph/list.go b/graph/list.go
index 9f7bccdfa..f95508e95 100644
--- a/graph/list.go
+++ b/graph/list.go
@@ -1,11 +1,13 @@
package graph
import (
+ "fmt"
"log"
"path"
+ "sort"
"strings"
- "github.com/docker/docker/engine"
+ "github.com/docker/docker/api/types"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/parsers/filters"
"github.com/docker/docker/utils"
@@ -16,47 +18,60 @@ var acceptedImageFilterTags = map[string]struct{}{
"label": {},
}
-func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
+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) Images(config *ImagesConfig) ([]*types.Image, 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"))
+ imageFilters, err := filters.FromParam(config.Filters)
if err != nil {
- return job.Error(err)
+ return nil, err
}
for name := range imageFilters {
if _, ok := acceptedImageFilterTags[name]; !ok {
- return job.Errorf("Invalid filter '%s'", name)
+ return nil, fmt.Errorf("Invalid filter '%s'", name)
}
}
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 config.All && filtTagged {
allImages, err = s.graph.Map()
} else {
allImages, err = s.graph.Heads()
}
if err != nil {
- return job.Error(err)
+ return nil, 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") != "" {
- if match, _ := path.Match(job.Getenv("filter"), repoName); !match {
+ if config.Filter != "" {
+ if match, _ := path.Match(config.Filter, repoName); !match {
continue
}
}
@@ -68,12 +83,12 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
continue
}
- if out, exists := lookup[id]; exists {
- if filt_tagged {
+ 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 {
@@ -82,24 +97,24 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
if !imageFilters.MatchKVList("label", image.ContainerConfig.Labels) {
continue
}
- if filt_tagged {
- 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)
+ if filtTagged {
+ 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
}
}
@@ -107,33 +122,32 @@ func (s *TagStore) CmdImages(job *engine.Job) engine.Status {
}
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
- if job.Getenv("filter") == "" || filt_label {
+ if config.Filter == "" || filtLabel {
for _, image := range allImages {
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 {
- return job.Error(err)
- }
- return engine.StatusOK
+ sort.Sort(sort.Reverse(ByCreated(images)))
+
+ return images, nil
}
diff --git a/graph/load.go b/graph/load.go
index c257e9ef1..9afde34c9 100644
--- a/graph/load.go
+++ b/graph/load.go
@@ -1,40 +1,39 @@
-// +build linux
+// +build linux windows
package graph
import (
"encoding/json"
+ "io"
"io/ioutil"
"os"
- "path"
+ "path/filepath"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
+ "github.com/Sirupsen/logrus"
"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.
// The input stream is an uncompressed tar ball containing images and metadata.
-func (s *TagStore) CmdLoad(job *engine.Job) engine.Status {
+func (s *TagStore) Load(inTar io.ReadCloser, outStream io.Writer) error {
tmpImageDir, err := ioutil.TempDir("", "docker-import-")
if err != nil {
- return job.Error(err)
+ return err
}
defer os.RemoveAll(tmpImageDir)
var (
- repoDir = path.Join(tmpImageDir, "repo")
+ repoDir = filepath.Join(tmpImageDir, "repo")
)
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
@@ -42,73 +41,77 @@ func (s *TagStore) CmdLoad(job *engine.Job) engine.Status {
excludes[i] = k
i++
}
- if err := chrootarchive.Untar(job.Stdin, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil {
- return job.Error(err)
+ if err := chrootarchive.Untar(inTar, repoDir, &archive.TarOptions{ExcludePatterns: excludes}); err != nil {
+ 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)
+ if err := s.recursiveLoad(d.Name(), tmpImageDir); err != nil {
+ return err
}
}
}
- repositoriesJson, err := ioutil.ReadFile(path.Join(tmpImageDir, "repo", "repositories"))
- if err == nil {
- repositories := map[string]Repository{}
- if err := json.Unmarshal(repositoriesJson, &repositories); err != nil {
- return job.Error(err)
+ reposJSONFile, err := os.Open(filepath.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.Set(imageName, tag, address, true); err != nil {
- return job.Error(err)
- }
- }
- }
- } else if !os.IsNotExist(err) {
- return job.Error(err)
+ repositories := map[string]Repository{}
+ if err := json.NewDecoder(reposJSONFile).Decode(&repositories); err != nil {
+ return err
}
- return engine.StatusOK
+ for imageName, tagMap := range repositories {
+ for tag, address := range tagMap {
+ if err := s.SetLoad(imageName, tag, address, true, outStream); err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
}
-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)
+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 {
- 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"))
+ layer, err := os.Open(filepath.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)
+ if err := image.ValidateID(img.ID); err != nil {
+ 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
}
@@ -120,7 +123,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
}
}
@@ -129,7 +132,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/load_unsupported.go b/graph/load_unsupported.go
index 164e9176a..45bdd98be 100644
--- a/graph/load_unsupported.go
+++ b/graph/load_unsupported.go
@@ -1,11 +1,12 @@
-// +build !linux
+// +build !linux,!windows
package graph
import (
- "github.com/docker/docker/engine"
+ "fmt"
+ "io"
)
-func (s *TagStore) CmdLoad(job *engine.Job) engine.Status {
- return job.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/manifest.go b/graph/manifest.go
index 3b1d82557..36b1adcfa 100644
--- a/graph/manifest.go
+++ b/graph/manifest.go
@@ -1,104 +1,171 @@
package graph
import (
- "bytes"
"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"
- "github.com/docker/docker/utils"
+ "github.com/docker/docker/trust"
"github.com/docker/libtrust"
)
-// loadManifest loads a manifest from a byte array and verifies its content.
-// The signature must be verified or an error is returned. If the manifest
-// 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) {
- sig, err := libtrust.ParsePrettySignature(manifestBytes, "signatures")
+// loadManifest loads a manifest from a byte array and verifies its content,
+// returning the local digest, the manifest itself, whether or not it was
+// verified. If ref is a digest, rather than a tag, this will be treated as
+// the local digest. An error will be returned if the signature verification
+// fails, local digest verification fails and, if provided, the remote digest
+// verification fails. The boolean return will only be false without error on
+// the failure of signatures trust check.
+func (s *TagStore) loadManifest(manifestBytes []byte, ref string, remoteDigest digest.Digest) (digest.Digest, *registry.ManifestData, bool, error) {
+ payload, keys, err := unpackSignedManifest(manifestBytes)
if err != nil {
- return nil, false, fmt.Errorf("error parsing payload: %s", err)
+ return "", nil, false, fmt.Errorf("error unpacking manifest: %v", err)
}
- keys, err := sig.Verify()
- if err != nil {
- return nil, false, fmt.Errorf("error verifying payload: %s", err)
- }
+ // TODO(stevvooe): It would be a lot better here to build up a stack of
+ // verifiers, then push the bytes one time for signatures and digests, but
+ // the manifests are typically small, so this optimization is not worth
+ // hacking this code without further refactoring.
- payload, err := sig.Payload()
- if err != nil {
- return nil, false, fmt.Errorf("error retrieving payload: %s", err)
- }
+ var localDigest digest.Digest
- var manifestDigest digest.Digest
+ // Verify the local digest, if present in ref. ParseDigest will validate
+ // that the ref is a digest and verify against that if present. Otherwize
+ // (on error), we simply compute the localDigest and proceed.
+ if dgst, err := digest.ParseDigest(ref); err == nil {
+ // verify the manifest against local ref
+ if err := verifyDigest(dgst, payload); err != nil {
+ return "", nil, false, fmt.Errorf("verifying local digest: %v", err)
+ }
- if dgst != "" {
- manifestDigest, err = digest.ParseDigest(dgst)
+ localDigest = dgst
+ } else {
+ // We don't have a local digest, since we are working from a tag.
+ // Compute the digest of the payload and return that.
+ logrus.Debugf("provided manifest reference %q is not a digest: %v", ref, err)
+ localDigest, err = digest.FromBytes(payload)
if err != nil {
- return nil, false, fmt.Errorf("invalid manifest digest from registry: %s", err)
- }
-
- dgstVerifier, err := digest.NewDigestVerifier(manifestDigest)
- if err != nil {
- return nil, false, fmt.Errorf("unable to verify manifest digest from registry: %s", err)
- }
-
- dgstVerifier.Write(payload)
-
- if !dgstVerifier.Verified() {
- computedDigest, _ := digest.FromBytes(payload)
- return nil, false, fmt.Errorf("unable to verify manifest digest: registry has %q, computed %q", manifestDigest, computedDigest)
+ // near impossible
+ logrus.Errorf("error calculating local digest during tag pull: %v", err)
+ return "", nil, false, err
}
}
- if utils.DigestReference(ref) && ref != manifestDigest.String() {
- return nil, false, fmt.Errorf("mismatching image manifest digest: got %q, expected %q", manifestDigest, ref)
+ // verify against the remote digest, if available
+ if remoteDigest != "" {
+ if err := verifyDigest(remoteDigest, payload); err != nil {
+ return "", nil, false, fmt.Errorf("verifying remote digest: %v", err)
+ }
}
var manifest registry.ManifestData
if err := json.Unmarshal(payload, &manifest); err != nil {
- return nil, false, fmt.Errorf("error unmarshalling manifest: %s", err)
+ return "", nil, false, fmt.Errorf("error unmarshalling manifest: %s", err)
}
- if manifest.SchemaVersion != 1 {
- return nil, false, fmt.Errorf("unsupported schema version: %d", manifest.SchemaVersion)
+
+ // validate the contents of the manifest
+ if err := validateManifest(&manifest); err != nil {
+ return "", nil, false, err
}
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)
- }
- result := engine.Tail(stdoutBuffer, 1)
- log.Debugf("Key check result: %q", result)
- if result == "verified" {
- verified = true
- }
+ verified, err = s.verifyTrustedKeys(manifest.Name, keys)
+ if err != nil {
+ return "", nil, false, fmt.Errorf("error verifying trusted keys: %v", err)
}
- return &manifest, verified, nil
+ return localDigest, &manifest, verified, nil
}
-func checkValidManifest(manifest *registry.ManifestData) error {
+// unpackSignedManifest takes the raw, signed manifest bytes, unpacks the jws
+// and returns the payload and public keys used to signed the manifest.
+// Signatures are verified for authenticity but not against the trust store.
+func unpackSignedManifest(p []byte) ([]byte, []libtrust.PublicKey, error) {
+ sig, err := libtrust.ParsePrettySignature(p, "signatures")
+ if err != nil {
+ return nil, nil, fmt.Errorf("error parsing payload: %s", err)
+ }
+
+ keys, err := sig.Verify()
+ if err != nil {
+ return nil, nil, fmt.Errorf("error verifying payload: %s", err)
+ }
+
+ payload, err := sig.Payload()
+ if err != nil {
+ return nil, nil, fmt.Errorf("error retrieving payload: %s", err)
+ }
+
+ return payload, keys, nil
+}
+
+// verifyTrustedKeys checks the keys provided against the trust store,
+// ensuring that the provided keys are trusted for the namespace. The keys
+// provided from this method must come from the signatures provided as part of
+// the manifest JWS package, obtained from unpackSignedManifest or libtrust.
+func (s *TagStore) verifyTrustedKeys(namespace string, keys []libtrust.PublicKey) (verified bool, err error) {
+ if namespace[0] != '/' {
+ namespace = "/" + namespace
+ }
+
+ for _, key := range keys {
+ b, err := key.MarshalJSON()
+ if err != nil {
+ return false, fmt.Errorf("error marshalling public key: %s", err)
+ }
+ // 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 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
+}
+
+// verifyDigest checks the contents of p against the provided digest. Note
+// that for manifests, this is the signed payload and not the raw bytes with
+// signatures.
+func verifyDigest(dgst digest.Digest, p []byte) error {
+ if err := dgst.Validate(); err != nil {
+ return fmt.Errorf("error validating digest %q: %v", dgst, err)
+ }
+
+ verifier, err := digest.NewDigestVerifier(dgst)
+ if err != nil {
+ // There are not many ways this can go wrong: if it does, its
+ // fatal. Likley, the cause would be poor validation of the
+ // incoming reference.
+ return fmt.Errorf("error creating verifier for digest %q: %v", dgst, err)
+ }
+
+ if _, err := verifier.Write(p); err != nil {
+ return fmt.Errorf("error writing payload to digest verifier (verifier target %q): %v", dgst, err)
+ }
+
+ if !verifier.Verified() {
+ return fmt.Errorf("verification against digest %q failed", dgst)
+ }
+
+ return nil
+}
+
+func validateManifest(manifest *registry.ManifestData) error {
+ if manifest.SchemaVersion != 1 {
+ return fmt.Errorf("unsupported schema version: %d", manifest.SchemaVersion)
+ }
+
if len(manifest.FSLayers) != len(manifest.History) {
return fmt.Errorf("length of history not equal to number of layers")
}
diff --git a/graph/manifest_test.go b/graph/manifest_test.go
index 913704182..63086f4d5 100644
--- a/graph/manifest_test.go
+++ b/graph/manifest_test.go
@@ -8,11 +8,13 @@ import (
"os"
"testing"
+ "github.com/docker/distribution/digest"
"github.com/docker/docker/image"
"github.com/docker/docker/pkg/tarsum"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
+ "github.com/docker/libtrust"
)
const (
@@ -135,7 +137,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)
}
@@ -181,3 +183,121 @@ func TestManifestTarsumCache(t *testing.T) {
t.Fatalf("Unexpected json value\nExpected:\n%s\nActual:\n%s", v1compat, manifest.History[0].V1Compatibility)
}
}
+
+// TestManifestDigestCheck ensures that loadManifest properly verifies the
+// remote and local digest.
+func TestManifestDigestCheck(t *testing.T) {
+ tmp, err := utils.TestDirectory("")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmp)
+ store := mkTestTagStore(tmp, t)
+ defer store.graph.driver.Cleanup()
+
+ archive, err := fakeTar()
+ if err != nil {
+ t.Fatal(err)
+ }
+ img := &image.Image{ID: testManifestImageID}
+ if err := store.graph.Register(img, archive); err != nil {
+ t.Fatal(err)
+ }
+ if err := store.Tag(testManifestImageName, testManifestTag, testManifestImageID, false); err != nil {
+ t.Fatal(err)
+ }
+
+ if cs, err := img.GetCheckSum(store.graph.ImageRoot(testManifestImageID)); err != nil {
+ t.Fatal(err)
+ } else if cs != "" {
+ t.Fatalf("Non-empty checksum file after register")
+ }
+
+ // Generate manifest
+ payload, err := store.newManifest(testManifestImageName, testManifestImageName, testManifestTag)
+ if err != nil {
+ t.Fatalf("unexpected error generating test manifest: %v", err)
+ }
+
+ pk, err := libtrust.GenerateECP256PrivateKey()
+ if err != nil {
+ t.Fatalf("unexpected error generating private key: %v", err)
+ }
+
+ sig, err := libtrust.NewJSONSignature(payload)
+ if err != nil {
+ t.Fatalf("error creating signature: %v", err)
+ }
+
+ if err := sig.Sign(pk); err != nil {
+ t.Fatalf("error signing manifest bytes: %v", err)
+ }
+
+ signedBytes, err := sig.PrettySignature("signatures")
+ if err != nil {
+ t.Fatalf("error getting signed bytes: %v", err)
+ }
+
+ dgst, err := digest.FromBytes(payload)
+ if err != nil {
+ t.Fatalf("error getting digest of manifest: %v", err)
+ }
+
+ // use this as the "bad" digest
+ zeroDigest, err := digest.FromBytes([]byte{})
+ if err != nil {
+ t.Fatalf("error making zero digest: %v", err)
+ }
+
+ // Remote and local match, everything should look good
+ local, _, _, err := store.loadManifest(signedBytes, dgst.String(), dgst)
+ if err != nil {
+ t.Fatalf("unexpected error verifying local and remote digest: %v", err)
+ }
+
+ if local != dgst {
+ t.Fatalf("local digest not correctly calculated: %v", err)
+ }
+
+ // remote and no local, since pulling by tag
+ local, _, _, err = store.loadManifest(signedBytes, "tag", dgst)
+ if err != nil {
+ t.Fatalf("unexpected error verifying tag pull and remote digest: %v", err)
+ }
+
+ if local != dgst {
+ t.Fatalf("local digest not correctly calculated: %v", err)
+ }
+
+ // remote and differing local, this is the most important to fail
+ local, _, _, err = store.loadManifest(signedBytes, zeroDigest.String(), dgst)
+ if err == nil {
+ t.Fatalf("error expected when verifying with differing local digest")
+ }
+
+ // no remote, no local (by tag)
+ local, _, _, err = store.loadManifest(signedBytes, "tag", "")
+ if err != nil {
+ t.Fatalf("unexpected error verifying manifest without remote digest: %v", err)
+ }
+
+ if local != dgst {
+ t.Fatalf("local digest not correctly calculated: %v", err)
+ }
+
+ // no remote, with local
+ local, _, _, err = store.loadManifest(signedBytes, dgst.String(), "")
+ if err != nil {
+ t.Fatalf("unexpected error verifying manifest without remote digest: %v", err)
+ }
+
+ if local != dgst {
+ t.Fatalf("local digest not correctly calculated: %v", err)
+ }
+
+ // bad remote, we fail the check.
+ local, _, _, err = store.loadManifest(signedBytes, dgst.String(), zeroDigest)
+ if err == nil {
+ t.Fatalf("error expected when verifying with differing remote digest")
+ }
+}
diff --git a/graph/pull.go b/graph/pull.go
index adad6f323..fff8a7552 100644
--- a/graph/pull.go
+++ b/graph/pull.go
@@ -10,104 +10,203 @@ 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/cliconfig"
"github.com/docker/docker/image"
- "github.com/docker/docker/pkg/common"
"github.com/docker/docker/pkg/progressreader"
+ "github.com/docker/docker/pkg/streamformatter"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/docker/pkg/transport"
"github.com/docker/docker/registry"
"github.com/docker/docker/utils"
)
-func (s *TagStore) CmdPull(job *engine.Job) engine.Status {
- if n := len(job.Args); n != 1 && n != 2 {
- return job.Errorf("Usage: %s IMAGE [TAG|DIGEST]", job.Name)
- }
+type ImagePullConfig struct {
+ MetaHeaders map[string][]string
+ AuthConfig *cliconfig.AuthConfig
+ OutStream io.Writer
+}
+func (s *TagStore) Pull(image string, tag string, imagePullConfig *ImagePullConfig) error {
var (
- localName = job.Args[0]
- tag string
- sf = utils.NewStreamFormatter(job.GetenvBool("json"))
- authConfig = ®istry.AuthConfig{}
- metaHeaders map[string][]string
+ sf = streamformatter.NewJSONStreamFormatter()
)
// Resolve the Repository name from fqn to RepositoryInfo
- repoInfo, err := registry.ResolveRepositoryInfo(job, localName)
+ repoInfo, err := s.registryService.ResolveRepository(image)
if err != nil {
- return job.Error(err)
+ return err
}
- if len(job.Args) > 1 {
- tag = job.Args[1]
+ if err := validateRepoName(repoInfo.LocalName); err != nil {
+ return err
}
- 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 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)
- }
-
- r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, true)
- if err != nil {
- return job.Error(err)
- }
-
logName := repoInfo.LocalName
if tag != "" {
logName = utils.ImageReference(logName, tag)
}
+ // Attempt pulling official content from a provided v2 mirror
+ if repoInfo.Index.Official {
+ v2mirrorEndpoint, v2mirrorRepoInfo, err := configureV2Mirror(repoInfo, s.registryService)
+ if err != nil {
+ logrus.Errorf("Error configuring mirrors: %s", err)
+ return err
+ }
+
+ if v2mirrorEndpoint != nil {
+ logrus.Debugf("Attempting to pull from v2 mirror: %s", v2mirrorEndpoint.URL)
+ return s.pullFromV2Mirror(v2mirrorEndpoint, v2mirrorRepoInfo, imagePullConfig, tag, sf, logName)
+ }
+ }
+
+ logrus.Debugf("pulling image from host %q with remote name %q", repoInfo.Index.Name, repoInfo.RemoteName)
+
+ endpoint, err := repoInfo.GetEndpoint(imagePullConfig.MetaHeaders)
+ if err != nil {
+ return err
+ }
+ // TODO(tiborvass): reuse client from endpoint?
+ // Adds Docker-specific headers as well as user-specified headers (metaHeaders)
+ tr := transport.NewTransport(
+ registry.NewTransport(registry.ReceiveTimeout, endpoint.IsSecure),
+ registry.DockerHeaders(imagePullConfig.MetaHeaders)...,
+ )
+ client := registry.HTTPClient(tr)
+ r, err := registry.NewSession(client, imagePullConfig.AuthConfig, endpoint)
+ if err != nil {
+ return err
+ }
+
if len(repoInfo.Index.Mirrors) == 0 && (repoInfo.Index.Official || endpoint.Version == registry.APIVersion2) {
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)
- }
+ s.trustService.UpdateBase()
}
- log.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)
- }
- return engine.StatusOK
+ logrus.Debugf("pulling v2 repository with local name %q", repoInfo.LocalName)
+ if err := s.pullV2Repository(r, imagePullConfig.OutStream, repoInfo, tag, sf); err == nil {
+ s.eventsService.Log("pull", logName, "")
+ 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)
- if err = s.pullRepository(r, job.Stdout, repoInfo, tag, sf, job.GetenvBool("parallel")); err != nil {
- return job.Error(err)
+ if utils.DigestReference(tag) {
+ return fmt.Errorf("pulling with digest reference failed from v2 registry")
}
- if err = job.Eng.Job("log", "pull", logName, "").Run(); err != nil {
- log.Errorf("Error logging event 'pull' for %s: %s", logName, err)
+ logrus.Debugf("pulling v1 repository with local name %q", repoInfo.LocalName)
+ if err = s.pullRepository(r, imagePullConfig.OutStream, repoInfo, tag, sf); err != nil {
+ return err
}
- return engine.StatusOK
+ s.eventsService.Log("pull", logName, "")
+
+ return nil
+
}
-func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, askedTag string, sf *utils.StreamFormatter, parallel bool) error {
+func makeMirrorRepoInfo(repoInfo *registry.RepositoryInfo, mirror string) *registry.RepositoryInfo {
+ mirrorRepo := ®istry.RepositoryInfo{
+ RemoteName: repoInfo.RemoteName,
+ LocalName: repoInfo.LocalName,
+ CanonicalName: repoInfo.CanonicalName,
+ Official: false,
+
+ Index: ®istry.IndexInfo{
+ Official: false,
+ Secure: repoInfo.Index.Secure,
+ Name: mirror,
+ Mirrors: []string{},
+ },
+ }
+ return mirrorRepo
+}
+
+func configureV2Mirror(repoInfo *registry.RepositoryInfo, s *registry.Service) (*registry.Endpoint, *registry.RepositoryInfo, error) {
+ mirrors := repoInfo.Index.Mirrors
+ if len(mirrors) == 0 {
+ // no mirrors configured
+ return nil, nil, nil
+ }
+
+ v1MirrorCount := 0
+ var v2MirrorEndpoint *registry.Endpoint
+ var v2MirrorRepoInfo *registry.RepositoryInfo
+ for _, mirror := range mirrors {
+ mirrorRepoInfo := makeMirrorRepoInfo(repoInfo, mirror)
+ endpoint, err := registry.NewEndpoint(mirrorRepoInfo.Index, nil)
+ if err != nil {
+ logrus.Errorf("Unable to create endpoint for %s: %s", mirror, err)
+ continue
+ }
+ if endpoint.Version == 2 {
+ if v2MirrorEndpoint == nil {
+ v2MirrorEndpoint = endpoint
+ v2MirrorRepoInfo = mirrorRepoInfo
+ } else {
+ // > 1 v2 mirrors given
+ return nil, nil, fmt.Errorf("multiple v2 mirrors configured")
+ }
+ } else {
+ v1MirrorCount++
+ }
+ }
+
+ if v1MirrorCount == len(mirrors) {
+ // OK, but mirrors are v1
+ return nil, nil, nil
+ }
+ if v2MirrorEndpoint != nil && v1MirrorCount == 0 {
+ // OK, 1 v2 mirror specified
+ return v2MirrorEndpoint, v2MirrorRepoInfo, nil
+ }
+ if v2MirrorEndpoint != nil && v1MirrorCount > 0 {
+ return nil, nil, fmt.Errorf("v1 and v2 mirrors configured")
+ }
+ // No endpoint could be established with the given mirror configurations
+ // Fallback to pulling from the hub as per v1 behavior.
+ return nil, nil, nil
+}
+
+func (s *TagStore) pullFromV2Mirror(mirrorEndpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo,
+ imagePullConfig *ImagePullConfig, tag string, sf *streamformatter.StreamFormatter, logName string) error {
+
+ tr := transport.NewTransport(
+ registry.NewTransport(registry.ReceiveTimeout, mirrorEndpoint.IsSecure),
+ registry.DockerHeaders(imagePullConfig.MetaHeaders)...,
+ )
+ client := registry.HTTPClient(tr)
+ mirrorSession, err := registry.NewSession(client, &cliconfig.AuthConfig{}, mirrorEndpoint)
+ if err != nil {
+ return err
+ }
+ logrus.Debugf("Pulling v2 repository with local name %q from %s", repoInfo.LocalName, mirrorEndpoint.URL)
+ if err := s.pullV2Repository(mirrorSession, imagePullConfig.OutStream, repoInfo, tag, sf); err != nil {
+ return err
+ }
+ s.eventsService.Log("pull", logName, "")
+ return nil
+}
+
+func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *registry.RepositoryInfo, askedTag string, sf *streamformatter.StreamFormatter) error {
out.Write(sf.FormatStatus("", "Pulling repository %s", repoInfo.CanonicalName))
repoData, err := r.GetRepositoryData(repoInfo.RemoteName)
@@ -119,10 +218,10 @@ func (s *TagStore) pullRepository(r *registry.Session, out io.Writer, repoInfo *
return err
}
- log.Debugf("Retrieving the tag list")
- tagsList, err := r.GetRemoteTags(repoData.Endpoints, repoInfo.RemoteName, repoData.Tokens)
+ logrus.Debugf("Retrieving the tag list")
+ tagsList, err := r.GetRemoteTags(repoData.Endpoints, repoInfo.RemoteName)
if err != nil {
- log.Errorf("unable to get remote tags: %s", err)
+ logrus.Errorf("unable to get remote tags: %s", err)
return err
}
@@ -134,7 +233,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 {
@@ -151,108 +250,95 @@ 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 {
- if parallel {
- errors <- nil
- }
+ errors <- nil
return
}
if img.Tag == "" {
- log.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID)
- if parallel {
- errors <- nil
- }
+ logrus.Debugf("Image (id: %s) present in this repository but untagged, skipping", img.ID)
+ errors <- nil
return
}
// 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)
- }
- if parallel {
- errors <- nil
+ logrus.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
}
+ errors <- nil
return
}
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
+ var isDownloaded 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))
- if is_downloaded, err = s.pullImage(r, out, img.ID, ep, repoData.Tokens, sf); err != nil {
+ // Ensure endpoint is v1
+ ep = ep + "v1/"
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, mirror: %s", img.Tag, repoInfo.CanonicalName, ep), 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)
+ logrus.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(common.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 {
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), fmt.Sprintf("Pulling image (%s) from %s, endpoint: %s", img.Tag, repoInfo.CanonicalName, ep), 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(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
+ layersDownloaded = layersDownloaded || isDownloaded
success = true
break
}
}
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))
- if parallel {
- errors <- err
- return
- }
+ out.Write(sf.FormatProgress(stringid.TruncateID(img.ID), err.Error(), nil))
+ 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
- }
+ errors <- nil
}
- if parallel {
- go downloadImage(image)
- } else {
- downloadImage(image)
+ go downloadImage(image)
+ }
+
+ var lastError error
+ for i := 0; i < len(repoData.ImgList); i++ {
+ if err := <-errors; err != nil {
+ lastError = err
}
}
- if parallel {
- var lastError error
- for i := 0; i < len(repoData.ImgList); i++ {
- if err := <-errors; err != nil {
- lastError = err
- }
- }
- if lastError != nil {
- return lastError
- }
-
+ if lastError != nil {
+ return lastError
}
+
for tag, id := range tagsList {
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
}
}
@@ -261,32 +347,32 @@ 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
}
-func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint string, token []string, sf *utils.StreamFormatter) (bool, error) {
- history, err := r.GetRemoteHistory(imgID, endpoint, token)
+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)
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
- layers_downloaded := false
+ layersDownloaded := false
for i := len(history) - 1; i >= 0; i-- {
id := history[i]
// 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)
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
@@ -295,19 +381,19 @@ func (s *TagStore) pullImage(r *registry.Session, out io.Writer, imgID, endpoint
)
retries := 5
for j := 1; j <= retries; j++ {
- imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint, token)
+ imgJSON, imgSize, err = r.GetRemoteImageJSON(id, endpoint)
if err != nil && j == retries {
- out.Write(sf.FormatProgress(common.TruncateID(id), "Error pulling dependent layers", nil))
- return layers_downloaded, err
+ out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil))
+ 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(common.TruncateID(id), "Error pulling dependent layers", nil))
- return layers_downloaded, fmt.Errorf("Failed to parse json: %s", err)
+ out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil))
+ return layersDownloaded, fmt.Errorf("Failed to parse json: %s", err)
} else if err != nil {
time.Sleep(time.Duration(j) * 500 * time.Millisecond)
continue
@@ -322,8 +408,8 @@ 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))
- layer, err := r.GetRemoteImageLayer(img.ID, endpoint, token, int64(imgSize))
+ out.Write(sf.FormatProgress(stringid.TruncateID(id), status, nil))
+ layer, err := r.GetRemoteImageLayer(img.ID, endpoint, int64(imgSize))
if uerr, ok := err.(*url.Error); ok {
err = uerr.Err
}
@@ -331,10 +417,10 @@ 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))
- return layers_downloaded, err
+ out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error pulling dependent layers", nil))
+ return layersDownloaded, err
}
- layers_downloaded = true
+ layersDownloaded = true
defer layer.Close()
err = s.graph.Register(img,
@@ -344,49 +430,38 @@ 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))
- return layers_downloaded, err
+ out.Write(sf.FormatProgress(stringid.TruncateID(id), "Error downloading dependent layers", nil))
+ return layersDownloaded, 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
+ return layersDownloaded, nil
}
-func WriteStatus(requestedTag string, out io.Writer, sf *utils.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))
}
}
-// downloadInfo is used to pass information from download to extractor
-type downloadInfo struct {
- imgJSON []byte
- img *image.Image
- digest digest.Digest
- tmpFile *os.File
- length int64
- downloaded bool
- 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(r *registry.Session, 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 {
- 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)
@@ -397,7 +472,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
@@ -406,14 +481,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, 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, auth); err != nil {
return err
} else if downloaded {
layersDownloaded = true
@@ -428,30 +503,37 @@ 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) {
- log.Debugf("Pulling tag from V2 registry: %q", tag)
+func (s *TagStore) pullV2Tag(r *registry.Session, out io.Writer, endpoint *registry.Endpoint, repoInfo *registry.RepositoryInfo, tag string, sf *streamformatter.StreamFormatter, auth *registry.RequestAuthorization) (bool, error) {
+ logrus.Debugf("Pulling tag from V2 registry: %q", tag)
- manifestBytes, manifestDigest, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth)
+ remoteDigest, manifestBytes, err := r.GetV2ImageManifest(endpoint, repoInfo.RemoteName, tag, auth)
if err != nil {
return false, err
}
// 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)
+ localDigest, manifest, verified, err := s.loadManifest(manifestBytes, tag, remoteDigest)
if err != nil {
return false, fmt.Errorf("error verifying manifest: %s", err)
}
- if err := checkValidManifest(manifest); err != nil {
- return false, err
- }
-
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))
+ // downloadInfo is used to pass information from download to extractor
+ type downloadInfo struct {
+ imgJSON []byte
+ img *image.Image
+ digest digest.Digest
+ tmpFile *os.File
+ length int64
+ downloaded bool
+ err chan error
+ }
+
downloads := make([]downloadInfo, len(manifest.FSLayers))
for i := len(manifest.FSLayers) - 1; i >= 0; i-- {
@@ -468,7 +550,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
}
@@ -478,18 +560,18 @@ 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)
+ 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 {
- 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)
+ logrus.Debugf("Image (id: %s) pull is already running, skipping: %v", img.ID, err)
}
} else {
defer s.poolRemove("pull", "img:"+img.ID)
@@ -498,7 +580,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
}
@@ -515,22 +597,21 @@ 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
+ return fmt.Errorf("image layer digest verification failed for %q", di.digest)
}
- 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())
+ logrus.Debugf("Downloaded %s to tempfile %s", img.ID, tmpFile.Name())
di.tmpFile = tmpFile
di.length = l
di.downloaded = true
@@ -540,25 +621,17 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
return nil
}
- if parallel {
- downloads[i].err = make(chan error)
- go func(di *downloadInfo) {
- di.err <- downloadFunc(di)
- }(&downloads[i])
- } else {
- err := downloadFunc(&downloads[i])
- if err != nil {
- return false, err
- }
- }
+ downloads[i].err = make(chan error)
+ go func(di *downloadInfo) {
+ di.err <- downloadFunc(di)
+ }(&downloads[i])
}
var tagUpdated bool
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
}
}
@@ -574,19 +647,23 @@ 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 {
return false, err
}
+ if err := d.img.SaveCheckSum(s.graph.ImageRoot(d.img.ID), d.digest.String()); err != nil {
+ return false, err
+ }
+
// 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))
}
}
@@ -601,6 +678,8 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
if _, exists := repo[tag]; !exists {
tagUpdated = true
}
+ } else {
+ tagUpdated = true
}
}
@@ -608,17 +687,35 @@ func (s *TagStore) pullV2Tag(eng *engine.Engine, r *registry.Session, out io.Wri
out.Write(sf.FormatStatus(utils.ImageReference(repoInfo.CanonicalName, tag), "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."))
}
- if manifestDigest != "" {
- out.Write(sf.FormatStatus("", "Digest: %s", manifestDigest))
+ if localDigest != remoteDigest { // this is not a verification check.
+ // NOTE(stevvooe): This is a very defensive branch and should never
+ // happen, since all manifest digest implementations use the same
+ // algorithm.
+ logrus.WithFields(
+ logrus.Fields{
+ "local": localDigest,
+ "remote": remoteDigest,
+ }).Debugf("local digest does not match remote")
+
+ out.Write(sf.FormatStatus("", "Remote Digest: %s", remoteDigest))
}
- if utils.DigestReference(tag) {
- if err = s.SetDigest(repoInfo.LocalName, tag, downloads[0].img.ID); err != nil {
+ out.Write(sf.FormatStatus("", "Digest: %s", localDigest))
+
+ if tag == localDigest.String() {
+ // TODO(stevvooe): Ideally, we should always set the digest so we can
+ // use the digest whether we pull by it or not. Unfortunately, the tag
+ // store treats the digest as a separate tag, meaning there may be an
+ // untagged digest image that would seem to be dangling by a user.
+
+ if err = s.SetDigest(repoInfo.LocalName, localDigest.String(), downloads[0].img.ID); err != nil {
return false, err
}
- } else {
+ }
+
+ if !utils.DigestReference(tag) {
// 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/push.go b/graph/push.go
index 08c383b44..532256fb5 100644
--- a/graph/push.go
+++ b/graph/push.go
@@ -7,15 +7,18 @@ import (
"io"
"io/ioutil"
"os"
- "path"
- "strings"
+ "path/filepath"
"sync"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/distribution/digest"
+ "github.com/docker/docker/cliconfig"
"github.com/docker/docker/image"
- "github.com/docker/docker/pkg/common"
+ "github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/progressreader"
+ "github.com/docker/docker/pkg/streamformatter"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/docker/pkg/transport"
"github.com/docker/docker/registry"
"github.com/docker/docker/runconfig"
"github.com/docker/docker/utils"
@@ -24,6 +27,13 @@ import (
var ErrV2RegistryUnavailable = errors.New("error v2 registry unavailable")
+type ImagePushConfig struct {
+ MetaHeaders map[string][]string
+ AuthConfig *cliconfig.AuthConfig
+ Tag string
+ 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 (
@@ -72,14 +82,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)
@@ -128,21 +138,21 @@ 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 {
- if err := r.LookupRemoteImage(image.id, image.endpoint, image.tokens); err != nil {
- log.Errorf("Error in LookupRemoteImage: %s", err)
+ if err := r.LookupRemoteImage(image.id, image.endpoint); err != nil {
+ logrus.Errorf("Error in LookupRemoteImage: %s", err)
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)))
}
}
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 {
@@ -189,8 +199,8 @@ 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))
- if err := r.PushRegistryTag(remoteName, id, tag, endpoint, repo.Tokens); err != nil {
+ 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); err != nil {
return err
}
}
@@ -201,9 +211,9 @@ 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 {
- log.Debugf("Local repo: %s", localRepo)
- out = utils.NewWriteFlusher(out)
+ tag string, sf *streamformatter.StreamFormatter) error {
+ logrus.Debugf("Local repo: %s", localRepo)
+ out = ioutils.NewWriteFlusher(out)
imgList, tags, err := s.getImageList(localRepo, tag)
if err != nil {
return err
@@ -211,9 +221,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
@@ -236,22 +246,22 @@ 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) {
- out = utils.NewWriteFlusher(out)
- jsonRaw, err := ioutil.ReadFile(path.Join(s.graph.Root, imgID, "json"))
+func (s *TagStore) pushImage(r *registry.Session, out io.Writer, imgID, ep string, token []string, sf *streamformatter.StreamFormatter) (checksum string, err error) {
+ out = ioutils.NewWriteFlusher(out)
+ 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)
}
- out.Write(sf.FormatProgress(common.TruncateID(imgID), "Pushing", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(imgID), "Pushing", nil))
imgData := ®istry.ImgData{
ID: imgID,
}
// Send the json
- if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep, token); err != nil {
+ if err := r.PushImageJSONRegistry(imgData, jsonRaw, ep); 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
@@ -264,7 +274,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{
@@ -273,28 +283,28 @@ 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)
+ }), ep, jsonRaw)
if err != nil {
return "", err
}
imgData.Checksum = checksum
imgData.ChecksumPayload = checksumPayload
// Send the checksum
- if err := r.PushImageChecksumRegistry(imgData, ep, token); err != nil {
+ if err := r.PushImageChecksumRegistry(imgData, ep); err != nil {
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
}
-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 {
- 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)
@@ -314,7 +324,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 {
@@ -355,11 +365,10 @@ 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)
- if err != nil {
+ if err := runconfig.Merge(&metadata, layer.Config); err != nil {
return err
}
}
@@ -375,15 +384,15 @@ 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(common.TruncateID(layer.ID), "Image push failed", nil))
+ out.Write(sf.FormatProgress(stringid.TruncateID(layer.ID), "Image push failed", nil))
return err
}
}
@@ -398,17 +407,17 @@ 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)}
}
- if err := checkValidManifest(m); err != nil {
+ if err := validateManifest(m); err != nil {
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
@@ -426,7 +435,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)
@@ -440,8 +449,8 @@ 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))
+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)
if err != nil {
@@ -465,87 +474,87 @@ func (s *TagStore) pushV2Image(r *registry.Session, img *image.Image, endpoint *
size, dgst, err := bufferToFile(tf, arch)
// 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(),
+ if err := r.PutV2ImageBlob(endpoint, imageName, dgst,
progressreader.New(progressreader.Config{
In: tf,
Out: out,
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
}
// FIXME: Allow to interrupt current push when new push of same image is done.
-func (s *TagStore) CmdPush(job *engine.Job) engine.Status {
- if n := len(job.Args); n != 1 {
- return job.Errorf("Usage: %s IMAGE", job.Name)
- }
+func (s *TagStore) Push(localName string, imagePushConfig *ImagePushConfig) error {
var (
- localName = job.Args[0]
- sf = utils.NewStreamFormatter(job.GetenvBool("json"))
- authConfig = ®istry.AuthConfig{}
- metaHeaders map[string][]string
+ sf = streamformatter.NewJSONStreamFormatter()
)
// Resolve the Repository name from fqn to RepositoryInfo
- repoInfo, err := registry.ResolveRepositoryInfo(job, localName)
+ repoInfo, err := s.registryService.ResolveRepository(localName)
if err != nil {
- return job.Error(err)
+ return err
}
- tag := job.Getenv("tag")
- job.GetenvJson("authConfig", authConfig)
- 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()
+ endpoint, err := repoInfo.GetEndpoint(imagePushConfig.MetaHeaders)
if err != nil {
- return job.Error(err)
+ return err
}
-
- r, err := registry.NewSession(authConfig, registry.HTTPRequestFactory(metaHeaders), endpoint, false)
+ // TODO(tiborvass): reuse client from endpoint?
+ // Adds Docker-specific headers as well as user-specified headers (metaHeaders)
+ tr := transport.NewTransport(
+ registry.NewTransport(registry.NoTimeout, endpoint.IsSecure),
+ registry.DockerHeaders(imagePushConfig.MetaHeaders)...,
+ )
+ client := registry.HTTPClient(tr)
+ r, err := registry.NewSession(client, imagePushConfig.AuthConfig, endpoint)
if err != nil {
- return job.Error(err)
+ 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 {
- 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)
+ err := s.pushV2Repository(r, localRepo, imagePushConfig.OutStream, repoInfo, imagePushConfig.Tag, sf)
if err == nil {
- return engine.StatusOK
+ s.eventsService.Log("push", repoInfo.LocalName, "")
+ 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)
+ if err := s.pushRepository(r, imagePushConfig.OutStream, repoInfo, localRepo, imagePushConfig.Tag, sf); err != nil {
+ return err
}
- return engine.StatusOK
+ s.eventsService.Log("push", repoInfo.LocalName, "")
+ return nil
}
diff --git a/graph/service.go b/graph/service.go
index 350ed8cf9..52dde1d98 100644
--- a/graph/service.go
+++ b/graph/service.go
@@ -4,178 +4,65 @@ import (
"fmt"
"io"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
- "github.com/docker/docker/image"
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/docker/api/types"
)
-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,
- "image_export": s.CmdImageExport,
- "history": s.CmdHistory,
- "images": s.CmdImages,
- "viz": s.CmdViz,
- "load": s.CmdLoad,
- "import": s.CmdImport,
- "pull": s.CmdPull,
- "push": s.CmdPush,
- } {
- 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
-}
-// 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) engine.Status {
- if len(job.Args) != 1 {
- return job.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")
- }
- // 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)
+ imageInspectRaw, err := image.RawJson()
if err != nil {
- return job.Error(err)
+ return nil, err
}
- if err := s.graph.Register(img, layer); err != nil {
- return job.Error(err)
- }
- return engine.StatusOK
+
+ return imageInspectRaw, 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 {
- if len(job.Args) != 1 {
- return job.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]
- res := &engine.Env{}
- img, err := s.LookupImage(name)
- // Note: if the image doesn't exist, LookupImage returns
- // nil, nil.
- if err != nil {
- return job.Error(err)
+
+ 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,
}
- 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 engine.StatusOK
+
+ return imageInspect, nil
}
-// CmdLookup return an image encoded in JSON
-func (s *TagStore) CmdLookup(job *engine.Job) engine.Status {
- if len(job.Args) != 1 {
- return job.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)
- }
- job.Stdout.Write(b)
- return engine.StatusOK
- }
-
- 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 job.Error(err)
- }
- return engine.StatusOK
- }
- return job.Errorf("No such image: %s", name)
-}
-
-// CmdTarLayer return the tarLayer of the image
-func (s *TagStore) CmdTarLayer(job *engine.Job) engine.Status {
- if len(job.Args) != 1 {
- return job.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 {
- return job.Error(err)
+ return err
}
defer fs.Close()
- written, err := io.Copy(job.Stdout, fs)
+ written, err := io.Copy(dest, 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
+ logrus.Debugf("rendered layer for %s of [%d] size", image.ID, written)
+ 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
deleted file mode 100644
index b33e49d59..000000000
--- a/graph/tag.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package graph
-
-import (
- "github.com/docker/docker/engine"
-)
-
-func (s *TagStore) CmdTag(job *engine.Job) engine.Status {
- if len(job.Args) != 2 && len(job.Args) != 3 {
- return job.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
-}
diff --git a/graph/tags.go b/graph/tags.go
index 5d26b8cfb..166a3d733 100644
--- a/graph/tags.go
+++ b/graph/tags.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ "io"
"io/ioutil"
"os"
"path/filepath"
@@ -12,10 +13,13 @@ import (
"strings"
"sync"
+ "github.com/docker/docker/daemon/events"
+ "github.com/docker/docker/graph/tags"
"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/trust"
"github.com/docker/docker/utils"
"github.com/docker/libtrust"
)
@@ -23,9 +27,8 @@ import (
const DEFAULTTAG = "latest"
var (
- //FIXME these 2 regexes also exist in registry/v2/regexp.go
- validTagName = regexp.MustCompile(`^[\w][\w.-]{0,127}$`)
- validDigest = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`)
+ //FIXME this regex also exists in registry/v2/regexp.go
+ validDigest = regexp.MustCompile(`[a-zA-Z0-9-_+.]+:[a-fA-F0-9]+`)
)
type TagStore struct {
@@ -36,8 +39,11 @@ 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
+ eventsService *events.Events
+ trustService *trust.TrustStore
}
type Repository map[string]string
@@ -60,19 +66,30 @@ func (r Repository) Contains(u Repository) bool {
return true
}
-func NewTagStore(path string, graph *Graph, key libtrust.PrivateKey) (*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
}
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: cfg.Graph,
+ trustKey: cfg.Key,
+ Repositories: make(map[string]Repository),
+ pullingPool: make(map[string]chan struct{}),
+ pushingPool: make(map[string]chan struct{}),
+ 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) {
@@ -98,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
@@ -163,7 +181,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 {
@@ -218,7 +236,11 @@ 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)
+}
+
+func (store *TagStore) SetLoad(repoName, tag, imageName string, force bool, out io.Writer) error {
img, err := store.LookupImage(imageName)
store.Lock()
defer store.Unlock()
@@ -226,12 +248,12 @@ func (store *TagStore) Set(repoName, tag, imageName string, force bool) error {
return err
}
if tag == "" {
- tag = DEFAULTTAG
+ tag = tags.DEFAULTTAG
}
if err := validateRepoName(repoName); err != nil {
return err
}
- if err := ValidateTagName(tag); err != nil {
+ if err := tags.ValidateTagName(tag); err != nil {
return err
}
if err := store.reload(); err != nil {
@@ -241,8 +263,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)
@@ -316,9 +347,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)
+ }
}
}
@@ -331,7 +365,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))
}
}
@@ -350,17 +384,6 @@ func validateRepoName(name string) error {
return nil
}
-// ValidateTagName validates the name of a tag
-func ValidateTagName(name string) error {
- if name == "" {
- return fmt.Errorf("tag name can't be empty")
- }
- if !validTagName.MatchString(name) {
- return fmt.Errorf("Illegal tag name (%s): only [A-Za-z0-9_.-] are allowed, minimum 1, maximum 128 in length", name)
- }
- return nil
-}
-
func validateDigest(dgst string) error {
if dgst == "" {
return errors.New("digest can't be empty")
diff --git a/graph/tags/tags.go b/graph/tags/tags.go
new file mode 100644
index 000000000..1abb593db
--- /dev/null
+++ b/graph/tags/tags.go
@@ -0,0 +1,24 @@
+package tags
+
+import (
+ "fmt"
+ "regexp"
+)
+
+const DEFAULTTAG = "latest"
+
+var (
+ //FIXME this regex also exists in registry/v2/regexp.go
+ validTagName = regexp.MustCompile(`^[\w][\w.-]{0,127}$`)
+)
+
+// ValidateTagName validates the name of a tag
+func ValidateTagName(name string) error {
+ if name == "" {
+ return fmt.Errorf("tag name can't be empty")
+ }
+ if !validTagName.MatchString(name) {
+ return fmt.Errorf("Illegal tag name (%s): only [A-Za-z0-9_.-] are allowed, minimum 1, maximum 128 in length", name)
+ }
+ return nil
+}
diff --git a/graph/tags/tags_unit_test.go b/graph/tags/tags_unit_test.go
new file mode 100644
index 000000000..5114da107
--- /dev/null
+++ b/graph/tags/tags_unit_test.go
@@ -0,0 +1,23 @@
+package tags
+
+import (
+ "testing"
+)
+
+func TestValidTagName(t *testing.T) {
+ validTags := []string{"9", "foo", "foo-test", "bar.baz.boo"}
+ for _, tag := range validTags {
+ if err := ValidateTagName(tag); err != nil {
+ t.Errorf("'%s' should've been a valid tag", tag)
+ }
+ }
+}
+
+func TestInvalidTagName(t *testing.T) {
+ validTags := []string{"-9", ".foo", "-test", ".", "-"}
+ for _, tag := range validTags {
+ if err := ValidateTagName(tag); err == nil {
+ t.Errorf("'%s' shouldn't have been a valid tag", tag)
+ }
+ }
+}
diff --git a/graph/tags_unit_test.go b/graph/tags_unit_test.go
index c1a686bbc..ad919d9bb 100644
--- a/graph/tags_unit_test.go
+++ b/graph/tags_unit_test.go
@@ -1,17 +1,19 @@
package graph
import (
+ "archive/tar"
"bytes"
"io"
"os"
"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"
+ "github.com/docker/docker/trust"
"github.com/docker/docker/utils"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
)
const (
@@ -59,7 +61,18 @@ func mkTestTagStore(root string, t *testing.T) *TagStore {
if err != nil {
t.Fatal(err)
}
- store, err := NewTagStore(path.Join(root, "tags"), graph, nil)
+
+ trust, err := trust.NewTrustStore(root + "/trust")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tagCfg := &TagStoreConfig{
+ Graph: graph,
+ Events: events.New(),
+ Trust: trust,
+ }
+ store, err := NewTagStore(path.Join(root, "tags"), tagCfg)
if err != nil {
t.Fatal(err)
}
@@ -71,7 +84,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()
@@ -82,7 +95,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 {
@@ -176,24 +189,6 @@ func TestLookupImage(t *testing.T) {
}
}
-func TestValidTagName(t *testing.T) {
- validTags := []string{"9", "foo", "foo-test", "bar.baz.boo"}
- for _, tag := range validTags {
- if err := ValidateTagName(tag); err != nil {
- t.Errorf("'%s' should've been a valid tag", tag)
- }
- }
-}
-
-func TestInvalidTagName(t *testing.T) {
- validTags := []string{"-9", ".foo", "-test", ".", "-"}
- for _, tag := range validTags {
- if err := ValidateTagName(tag); err == nil {
- t.Errorf("'%s' shouldn't have been a valid tag", tag)
- }
- }
-}
-
func TestValidateDigest(t *testing.T) {
tests := []struct {
input string
diff --git a/graph/viz.go b/graph/viz.go
deleted file mode 100644
index 924c22b6a..000000000
--- a/graph/viz.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package graph
-
-import (
- "strings"
-
- "github.com/docker/docker/engine"
- "github.com/docker/docker/image"
-)
-
-func (s *TagStore) CmdViz(job *engine.Job) engine.Status {
- images, _ := s.graph.Map()
- if images == nil {
- return engine.StatusOK
- }
- 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 job.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 engine.StatusOK
-}
diff --git a/hack/dind b/hack/dind
index f8fae6379..9289ba655 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).
@@ -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
diff --git a/hack/install.sh b/hack/install.sh
index b0177e670..4beb71e52 100755
--- a/hack/install.sh
+++ b/hack/install.sh
@@ -49,11 +49,18 @@ do_install() {
;;
esac
- if command_exists docker || command_exists lxc-docker; then
+ if command_exists docker; then
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.
+ Warning: the "docker" command appears to already exist on this system.
+
+ If you already have Docker installed, this script can cause trouble, which is
+ why we're displaying this warning and provide the opportunity to cancel the
+ installation.
+
+ If you installed the current Docker package using this script and are using it
+ again to update Docker, you can safely ignore this message.
+
+ You may press Ctrl+C now to abort this script.
EOF
( set -x; sleep 20 )
fi
@@ -126,7 +133,22 @@ do_install() {
exit 0
;;
- ubuntu|debian|linuxmint)
+ 'opensuse project'|opensuse|'suse linux'|sled)
+ (
+ set -x
+ $sh_c 'sleep 3; zypper -n install docker'
+ )
+ if command_exists docker && [ -e /var/run/docker.sock ]; then
+ (
+ set -x
+ $sh_c 'docker version'
+ ) || true
+ fi
+ echo_docker_as_nonroot
+ exit 0
+ ;;
+
+ ubuntu|debian|linuxmint|'elementary os'|kali)
export DEBIAN_FRONTEND=noninteractive
did_apt_get_update=
@@ -185,6 +207,8 @@ do_install() {
$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"
+ elif [ "https://experimental.docker.com/" = "$url" ]; then
+ $sh_c "apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys E33FF7BF5C91D50A6F91FFFD4CC38D40F9A96B49"
else
$sh_c "$curl ${url}gpg | apt-key add -"
fi
diff --git a/hack/make.sh b/hack/make.sh
index 118d4327f..a16d3bea3 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.
@@ -24,10 +24,12 @@ set -e
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!
-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"
@@ -44,7 +46,9 @@ echo
DEFAULT_BUNDLES=(
validate-dco
validate-gofmt
+ validate-test
validate-toml
+ validate-vet
binary
@@ -53,7 +57,6 @@ DEFAULT_BUNDLES=(
test-docker-py
dynbinary
- test-integration
cover
cross
@@ -61,7 +64,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
@@ -81,15 +84,21 @@ 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
- 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
+if [ "$DOCKER_EXPERIMENTAL" ]; then
+ echo >&2 '# WARNING! DOCKER_EXPERIMENTAL is set: building experimental features'
+ echo >&2
+ DOCKER_BUILDTAGS+=" experimental"
+fi
+
if [ -z "$DOCKER_CLIENTONLY" ]; then
DOCKER_BUILDTAGS+=" daemon"
fi
@@ -98,10 +107,27 @@ 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
+
+# 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'
-source "$(dirname "$BASH_SOURCE")/make/.go-autogen"
+source "$SCRIPTDIR/make/.go-autogen"
LDFLAGS='-w'
LDFLAGS_STATIC='-linkmode external'
@@ -156,7 +182,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
@@ -166,13 +197,13 @@ go_test_dir() {
# if our current go install has -cover, we want to use it :)
mkdir -p "$DEST/coverprofiles"
coverprofile="docker${dir#.}"
- coverprofile="$DEST/coverprofiles/${coverprofile//\//-}"
+ coverprofile="$ABS_DEST/coverprofiles/${coverprofile//\//-}"
testcover=( -cover -coverprofile "$coverprofile" $coverpkg )
fi
(
- export DEST
echo '+ go test' $TESTFLAGS "${DOCKER_PKG}${dir#.}"
cd "$dir"
+ export DEST="$ABS_DEST" # we're in a subshell, so this is safe -- our integration-cli tests need DEST, and "cd" screws it up
test_env go test ${testcover[@]} -ldflags "$LDFLAGS" "${BUILDFLAGS[@]}" $TESTFLAGS
)
}
@@ -182,9 +213,10 @@ 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" \
+ HOME="$ABS_DEST/fake-HOME" \
PATH="$PATH" \
TEST_DOCKERINIT_PATH="$TEST_DOCKERINIT_PATH" \
"$@"
@@ -204,7 +236,6 @@ find_dirs() {
find . -not \( \
\( \
-path './vendor/*' \
- -o -path './integration/*' \
-o -path './integration-cli/*' \
-o -path './contrib/*' \
-o -path './pkg/mflag/example/*' \
@@ -239,11 +270,9 @@ hash_files() {
}
bundle() {
- bundlescript=$1
- bundle=$(basename $bundlescript)
- echo "---> Making bundle: $bundle (in bundles/$VERSION/$bundle)"
- mkdir -p bundles/$VERSION/$bundle
- source "$bundlescript" "$(pwd)/bundles/$VERSION/$bundle"
+ local bundle="$1"; shift
+ echo "---> Making bundle: $(basename "$bundle") (in $DEST)"
+ source "$SCRIPTDIR/make/$bundle" "$@"
}
main() {
@@ -252,17 +281,31 @@ 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
- SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+
+ if [ "$(go env GOHOSTOS)" != 'windows' ]; then
+ # Windows and symlinks don't get along well
+
+ rm -f bundles/latest
+ ln -s "$VERSION" bundles/latest
+ fi
+
if [ $# -lt 1 ]; then
bundles=(${DEFAULT_BUNDLES[@]})
else
bundles=($@)
fi
for bundle in ${bundles[@]}; do
- bundle $SCRIPTDIR/make/$bundle
+ export DEST="bundles/$VERSION/$(basename "$bundle")"
+ # Cygdrive paths don't play well with go build -o.
+ if [[ "$(uname -s)" == CYGWIN* ]]; then
+ export DEST="$(cygpath -mw "$DEST")"
+ fi
+ mkdir -p "$DEST"
+ ABS_DEST="$(cd "$DEST" && pwd -P)"
+ bundle "$bundle"
echo
done
}
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..dc7e5c5f5
--- /dev/null
+++ b/hack/make/.build-deb/control
@@ -0,0 +1,27 @@
+Source: docker-engine
+Maintainer: Docker
+Homepage: https://dockerproject.org
+Vcs-Browser: https://github.com/docker/docker
+Vcs-Git: git://github.com/docker/docker.git
+
+Package: docker-engine
+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-engine.bash-completion b/hack/make/.build-deb/docker-engine.bash-completion
new file mode 100644
index 000000000..6ea111930
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.bash-completion
@@ -0,0 +1 @@
+contrib/completion/bash/docker
diff --git a/hack/make/.build-deb/docker-engine.docker.default b/hack/make/.build-deb/docker-engine.docker.default
new file mode 120000
index 000000000..4278533d6
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.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-engine.docker.init b/hack/make/.build-deb/docker-engine.docker.init
new file mode 120000
index 000000000..8cb89d30d
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.docker.init
@@ -0,0 +1 @@
+../../../contrib/init/sysvinit-debian/docker
\ No newline at end of file
diff --git a/hack/make/.build-deb/docker-engine.docker.upstart b/hack/make/.build-deb/docker-engine.docker.upstart
new file mode 120000
index 000000000..7e1b64a3e
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.docker.upstart
@@ -0,0 +1 @@
+../../../contrib/init/upstart/docker.conf
\ No newline at end of file
diff --git a/hack/make/.build-deb/docker-engine.install b/hack/make/.build-deb/docker-engine.install
new file mode 100644
index 000000000..a8857a96d
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.install
@@ -0,0 +1,11 @@
+#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-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-engine/contrib/
+contrib/nuke-graph-directory.sh usr/share/docker-engine/contrib/
+contrib/syntax/nano/Dockerfile.nanorc usr/share/nano/
diff --git a/hack/make/.build-deb/docker-engine.manpages b/hack/make/.build-deb/docker-engine.manpages
new file mode 100644
index 000000000..1aa62186a
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.manpages
@@ -0,0 +1 @@
+man/man*/*
diff --git a/hack/make/.build-deb/docker-engine.postinst b/hack/make/.build-deb/docker-engine.postinst
new file mode 100644
index 000000000..eeef6ca80
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.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-engine.udev b/hack/make/.build-deb/docker-engine.udev
new file mode 120000
index 000000000..914a36195
--- /dev/null
+++ b/hack/make/.build-deb/docker-engine.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..6b77a3f8a
--- /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-engine.substvars
+ dh_gencontrol
+
+override_dh_auto_build:
+ ./hack/make.sh dynbinary
+ # ./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-engine/usr/bin
+ cp -aT "$$(readlink -f bundles/$(VERSION)/dynbinary/docker)" debian/docker-engine/usr/bin/docker
+ 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"
+ 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-rpm/docker-engine.spec b/hack/make/.build-rpm/docker-engine.spec
new file mode 100644
index 000000000..066161f57
--- /dev/null
+++ b/hack/make/.build-rpm/docker-engine.spec
@@ -0,0 +1,184 @@
+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.org
+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
+# ./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 man/man1/*.1 $RPM_BUILD_ROOT/%{_mandir}/man1
+install -d %{buildroot}%{_mandir}/man5
+install -p -m 644 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
+
+# add nano
+install -d $RPM_BUILD_ROOT/usr/share/nano
+install -p -m 644 contrib/syntax/nano/Dockerfile.nanorc $RPM_BUILD_ROOT/usr/share/nano/Dockerfile.nanorc
+
+# 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
+/usr/share/nano/Dockerfile.nanorc
+
+%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/.dockerinit b/hack/make/.dockerinit
index fceba7db9..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 \
@@ -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..989086384 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 \
@@ -12,6 +12,7 @@ go build --compiler=gccgo \
-g
-Wl,--no-export-dynamic
$EXTLDFLAGS_STATIC_DOCKER
+ -lnetgo
" \
./dockerinit
@@ -27,4 +28,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/.ensure-frozen-images b/hack/make/.ensure-frozen-images
index 379f73849..deded80e7 100644
--- a/hack/make/.ensure-frozen-images
+++ b/hack/make/.ensure-frozen-images
@@ -5,6 +5,7 @@ set -e
images=(
busybox:latest
hello-world:frozen
+ jess/unshare:latest
)
if ! docker inspect "${images[@]}" &> /dev/null; then
diff --git a/hack/make/.integration-daemon-start b/hack/make/.integration-daemon-start
index 570c6c7a9..ba1d47bc6 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="$ABS_DEST/../binary:$ABS_DEST/../dynbinary:$ABS_DEST/../gccgo:$ABS_DEST/../dyngccgo:$PATH"
if ! command -v docker &> /dev/null; then
echo >&2 'error: binary or dynbinary must be run before .integration-daemon-start'
@@ -14,8 +14,30 @@ exec 41>&1 42>&2
export DOCKER_GRAPHDRIVER=${DOCKER_GRAPHDRIVER:-vfs}
export DOCKER_EXECDRIVER=${DOCKER_EXECDRIVER:-native}
+export DOCKER_USERLANDPROXY=${DOCKER_USERLANDPROXY:-true}
+
+# example usage: DOCKER_STORAGE_OPTS="dm.basesize=20G,dm.loopdatasize=200G"
+storage_params=""
+if [ -n "$DOCKER_STORAGE_OPTS" ]; then
+ IFS=','
+ for i in ${DOCKER_STORAGE_OPTS}; do
+ storage_params="--storage-opt $i $storage_params"
+ done
+ unset IFS
+fi
if [ -z "$DOCKER_TEST_HOST" ]; then
+ # Start apparmor if it is enabled
+ if [ "$(cat /sys/module/apparmor/parameters/enabled)" == "Y" ]; then
+ # reset container variable so apparmor profile is applied to process
+ # see https://github.com/docker/libcontainer/blob/master/apparmor/apparmor.go#L16
+ export container=""
+ (
+ set -x
+ /etc/init.d/apparmor start
+ )
+ fi
+
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 \
@@ -23,8 +45,11 @@ if [ -z "$DOCKER_TEST_HOST" ]; then
--storage-driver "$DOCKER_GRAPHDRIVER" \
--exec-driver "$DOCKER_EXECDRIVER" \
--pidfile "$DEST/docker.pid" \
+ --userland-proxy="$DOCKER_USERLANDPROXY" \
+ $storage_params \
&> "$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 319aaa4a1..364490bde 100644
--- a/hack/make/.integration-daemon-stop
+++ b/hack/make/.integration-daemon-stop
@@ -1,9 +1,21 @@
#!/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 )
- 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
+
+if [ -z "$DOCKER_TEST_HOST" ]; then
+ # Stop apparmor if it is enabled
+ if [ "$(cat /sys/module/apparmor/parameters/enabled)" == "Y" ]; then
+ (
+ set -x
+ /etc/init.d/apparmor stop
+ )
+ fi
+fi
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/binary b/hack/make/binary
index 0f57ea0d6..d7643364d 100644
--- a/hack/make/binary
+++ b/hack/make/binary
@@ -1,18 +1,13 @@
#!/bin/bash
set -e
-DEST=$1
BINARY_NAME="docker-$VERSION"
BINARY_EXTENSION="$(binary_extension)"
BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION"
-# Cygdrive paths don't play well with go build -o.
-if [[ "$(uname -s)" == CYGWIN* ]]; then
- DEST=$(cygpath -mw $DEST)
-fi
-
-source "$(dirname "$BASH_SOURCE")/.go-autogen"
+source "${MAKEDIR}/.go-autogen"
+echo "Building: $DEST/$BINARY_FULLNAME"
go build \
-o "$DEST/$BINARY_FULLNAME" \
"${BUILDFLAGS[@]}" \
diff --git a/hack/make/build-deb b/hack/make/build-deb
new file mode 100644
index 000000000..f007d999f
--- /dev/null
+++ b/hack/make/build-deb
@@ -0,0 +1,67 @@
+#!/bin/bash
+set -e
+
+# subshell so that we can export PATH and TZ without breaking other things
+(
+ export TZ=UTC # make sure our "date" variables are UTC-based
+
+ source "${MAKEDIR}/.integration-daemon-start"
+
+ # TODO consider using frozen images for the dockercore/builder-deb tags
+
+ tilde='~' # ouch Bash 4.2 vs 4.3, you keel me
+ debVersion="${VERSION//-/$tilde}" # using \~ or '~' here works in 4.3, but not 4.2; just ~ causes $HOME to be inserted, hence the $tilde
+ # 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
+ ./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
+ tempImage="docker-temp/build-deb:$version"
+ ( 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
+
+ source "${MAKEDIR}/.integration-daemon-stop"
+) 2>&1 | tee -a "$DEST/test.log"
diff --git a/hack/make/build-rpm b/hack/make/build-rpm
new file mode 100644
index 000000000..6ad01c40c
--- /dev/null
+++ b/hack/make/build-rpm
@@ -0,0 +1,73 @@
+#!/bin/bash
+set -e
+
+# subshell so that we can export PATH and TZ without breaking other things
+(
+ export TZ=UTC # make sure our "date" variables are UTC-based
+
+ 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
+ ./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/cover b/hack/make/cover
index ca772d03b..624943b8a 100644
--- a/hack/make/cover
+++ b/hack/make/cover
@@ -1,8 +1,6 @@
#!/bin/bash
set -e
-DEST="$1"
-
bundle_cover() {
coverprofiles=( "$DEST/../"*"/coverprofiles/"* )
for p in "${coverprofiles[@]}"; do
diff --git a/hack/make/cross b/hack/make/cross
index 3c5cb0401..b5eab6812 100644
--- a/hack/make/cross
+++ b/hack/make/cross
@@ -1,8 +1,6 @@
#!/bin/bash
set -e
-DEST=$1
-
# explicit list of os/arch combos that support being a daemon
declare -A daemonSupporting
daemonSupporting=(
@@ -21,13 +19,15 @@ fi
for platform in $DOCKER_CROSSPLATFORMS; do
(
- mkdir -p "$DEST/$platform" # bundles/VERSION/cross/GOOS/GOARCH/docker-VERSION
+ export DEST="$DEST/$platform" # bundles/VERSION/cross/GOOS/GOARCH/docker-VERSION
+ mkdir -p "$DEST"
+ ABS_DEST="$(cd "$DEST" && pwd -P)"
export GOOS=${platform%/*}
export GOARCH=${platform##*/}
if [ -z "${daemonSupporting[$platform]}" ]; then
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"
)
done
diff --git a/hack/make/dynbinary b/hack/make/dynbinary
index 861a19214..e5fc0be26 100644
--- a/hack/make/dynbinary
+++ b/hack/make/dynbinary
@@ -1,11 +1,9 @@
#!/bin/bash
set -e
-DEST=$1
-
if [ -z "$DOCKER_CLIENTONLY" ]; then
- source "$(dirname "$BASH_SOURCE")/.dockerinit"
-
+ source "${MAKEDIR}/.dockerinit"
+
hash_files "$DEST/dockerinit-$VERSION"
else
# DOCKER_CLIENTONLY must be truthy, so we don't need to bother with dockerinit :)
@@ -18,5 +16,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 a76e9c5b5..df76ac7f5 100644
--- a/hack/make/dyngccgo
+++ b/hack/make/dyngccgo
@@ -1,11 +1,9 @@
#!/bin/bash
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
# DOCKER_CLIENTONLY must be truthy, so we don't need to bother with dockerinit :)
@@ -19,5 +17,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..972934abe 100644
--- a/hack/make/gccgo
+++ b/hack/make/gccgo
@@ -1,13 +1,15 @@
#!/bin/bash
set -e
-DEST=$1
BINARY_NAME="docker-$VERSION"
BINARY_EXTENSION="$(binary_extension)"
BINARY_FULLNAME="$BINARY_NAME$BINARY_EXTENSION"
-source "$(dirname "$BASH_SOURCE")/.go-autogen"
+source "${MAKEDIR}/.go-autogen"
+if [[ "${BUILDFLAGS[@]}" =~ 'netgo ' ]]; then
+ EXTLDFLAGS_STATIC_DOCKER+=' -lnetgo'
+fi
go build -compiler=gccgo \
-o "$DEST/$BINARY_FULLNAME" \
"${BUILDFLAGS[@]}" \
diff --git a/hack/make/test-docker-py b/hack/make/test-docker-py
index b95cf40af..cbd59addd 100644
--- a/hack/make/test-docker-py
+++ b/hack/make/test-docker-py
@@ -1,30 +1,18 @@
#!/bin/bash
set -e
-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,
- # 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 "$(dirname "$BASH_SOURCE")/.integration-daemon-stop"
-
- [ -z "$didFail" ] # "set -e" ftw
-) 2>&1 | tee -a $DEST/test.log
+ source "${MAKEDIR}/.integration-daemon-stop"
+) 2>&1 | tee -a "$DEST/test.log"
diff --git a/hack/make/test-integration b/hack/make/test-integration
deleted file mode 100644
index 5cb7102bc..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 "$(dirname "$BASH_SOURCE")/.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/hack/make/test-integration-cli b/hack/make/test-integration-cli
index 3ef41d919..61d62f160 100644
--- a/hack/make/test-integration-cli
+++ b/hack/make/test-integration-cli
@@ -1,31 +1,19 @@
#!/bin/bash
set -e
-DEST=$1
-
bundle_test_integration_cli() {
go_test_dir ./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
+ bundle_test_integration_cli
- source "$(dirname "$BASH_SOURCE")/.integration-daemon-stop"
-
- [ -z "$didFail" ] # "set -e" ftw
-) 2>&1 | tee -a $DEST/test.log
+ source "${MAKEDIR}/.integration-daemon-stop"
+) 2>&1 | tee -a "$DEST/test.log"
diff --git a/hack/make/test-unit b/hack/make/test-unit
index 540ba41f3..1053f6151 100644
--- a/hack/make/test-unit
+++ b/hack/make/test-unit
@@ -1,7 +1,6 @@
#!/bin/bash
set -e
-DEST=$1
: ${PARALLEL_JOBS:=$(nproc 2>/dev/null || echo 1)} # if nproc fails (usually because we don't have it), let's not parallelize by default
RED=$'\033[31m'
@@ -12,7 +11,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() {
{
@@ -26,10 +25,9 @@ bundle_test_unit() {
export LDFLAGS
export TESTFLAGS
export HAVE_GO_TEST_COVER
- export DEST
# some hack to export array variables
- export BUILDFLAGS_FILE="buildflags_file"
+ export BUILDFLAGS_FILE="$DEST/buildflags-file"
( IFS=$'\n'; echo "${BUILDFLAGS[*]}" ) > "$BUILDFLAGS_FILE"
if command -v parallel &> /dev/null; then
@@ -39,12 +37,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
@@ -59,7 +57,7 @@ go_run_test_dir() {
while read dir; do
echo
echo '+ go test' $TESTFLAGS "${DOCKER_PKG}${dir#.}"
- precompiled="$DEST/precompiled/$dir.test$(binary_extension)"
+ precompiled="$ABS_DEST/precompiled/$dir.test$(binary_extension)"
if ! ( cd "$dir" && test_env "$precompiled" $TESTFLAGS ); then
TESTS_FAILED+=("$dir")
echo
@@ -85,4 +83,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/tgz b/hack/make/tgz
index 7234218da..a512b832d 100644
--- a/hack/make/tgz
+++ b/hack/make/tgz
@@ -1,6 +1,5 @@
#!/bin/bash
-DEST="$1"
CROSS="$DEST/../cross"
set -e
@@ -18,17 +17,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/ubuntu b/hack/make/ubuntu
index e34369eb1..a791f9c96 100644
--- a/hack/make/ubuntu
+++ b/hack/make/ubuntu
@@ -1,7 +1,5 @@
#!/bin/bash
-DEST=$1
-
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
@@ -23,7 +21,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
@@ -37,35 +35,35 @@ PACKAGE_LICENSE="Apache-2.0"
# Build docker as an ubuntu package using FPM and REPREPRO (sue me).
# bundle_binary must be called first.
bundle_ubuntu() {
- DIR=$DEST/build
+ DIR="$ABS_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
+ man/md2man-all.sh -q
manRoot="$DIR/usr/share/man"
mkdir -p "$manRoot"
- for manDir in docs/man/man?; do
+ for manDir in man/man?; do
manBase="$(basename "$manDir")" # "man1"
for manFile in "$manDir"/*; do
manName="$(basename "$manFile")" # "docker-build.1"
@@ -76,11 +74,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 +102,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 +111,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 +129,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 "$ABS_DEST/postinst" \
+ --before-remove "$ABS_DEST/prerm" \
+ --after-remove "$ABS_DEST/postrm" \
--architecture "$PACKAGE_ARCHITECTURE" \
--prefix / \
--depends iptables \
@@ -184,8 +182,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/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-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/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
new file mode 100644
index 000000000..febe93e5c
--- /dev/null
+++ b/hack/make/validate-vet
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+source "${MAKEDIR}/.validate"
+
+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 passes go vet
+ failedVet=$(go vet "$f")
+ if [ "$failedVet" ]; then
+ errors+=( "$failedVet" )
+ fi
+done
+
+
+if [ ${#errors[@]} -eq 0 ]; then
+ echo 'Congratulations! All Go source files have been vetted.'
+else
+ {
+ 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
diff --git a/hack/release.sh b/hack/release.sh
index da95808c5..5ea4f12e4 100755
--- a/hack/release.sh
+++ b/hack/release.sh
@@ -54,13 +54,13 @@ RELEASE_BUNDLES=(
if [ "$1" != '--release-regardless-of-test-failure' ]; then
RELEASE_BUNDLES=(
- test-unit test-integration
+ test-unit
"${RELEASE_BUNDLES[@]}"
test-integration-cli
)
fi
-VERSION=$(cat VERSION)
+VERSION=$(< VERSION)
BUCKET=$AWS_S3_BUCKET
# These are the 2 keys we've used to sign the deb's
@@ -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,48 +246,50 @@ 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
}
+ local debfiles=( "bundles/$VERSION/ubuntu/"*.deb )
+
# Sign our packages
- dpkg-sig -g "--passphrase $GPG_PASSPHRASE" -k releasedocker \
- --sign builder bundles/$VERSION/ubuntu/*.deb
+ dpkg-sig -g "--passphrase $GPG_PASSPHRASE" -k releasedocker --sign builder "${debfiles[@]}"
# 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
gpgFingerprint=740B314AE3941731B942C66ADF4FD13717AAD7D6
+ elif [[ $BUCKET == experimental* ]]; then
+ gpgFingerprint=E33FF7BF5C91D50A6F91FFFD4CC38D40F9A96B49
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 +343,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 <
- 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:
diff --git a/integration-cli/check_test.go b/integration-cli/check_test.go
new file mode 100644
index 000000000..202799cbf
--- /dev/null
+++ b/integration-cli/check_test.go
@@ -0,0 +1,81 @@
+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())
+}
+
+func init() {
+ check.Suite(&DockerSuite{})
+}
+
+type DockerSuite struct {
+ TimerSuite
+}
+
+func (s *DockerSuite) TearDownTest(c *check.C) {
+ deleteAllContainers()
+ deleteAllImages()
+ s.TimerSuite.TearDownTest(c)
+}
+
+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)
+}
+
+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_api_attach_test.go b/integration-cli/docker_api_attach_test.go
index b16a7bb2f..c784d5c36 100644
--- a/integration-cli/docker_api_attach_test.go
+++ b/integration-cli/docker_api_attach_test.go
@@ -3,64 +3,77 @@ package main
import (
"bytes"
"os/exec"
- "testing"
+ "strings"
"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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
config, err := websocket.NewConfig(
"/containers/"+cleanedContainerID+"/attach/ws?stream=1&stdin=1&stdout=1&stderr=1",
"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()
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 {
- t.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 {
- t.Fatal(err)
- }
- inChan <- "done"
+ _, err := ws.Write(expected)
+ inChan <- err
+ close(inChan)
}()
- <-inChan
- <-outChan
-
- if !bytes.Equal(expected, actual) {
- t.Fatal("Expected output on websocket to match input")
+ select {
+ case err := <-inChan:
+ if err != nil {
+ c.Fatal(err)
+ }
+ case <-time.After(5 * time.Second):
+ c.Fatal("Timeout writing to ws")
}
- logDone("container attach websocket - can echo input via cat")
+ 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")
+ }
}
diff --git a/integration-cli/docker_api_containers_test.go b/integration-cli/docker_api_containers_test.go
index ea2f2450a..c89d11eb1 100644
--- a/integration-cli/docker_api_containers_test.go
+++ b/integration-cli/docker_api_containers_test.go
@@ -1,70 +1,140 @@
package main
import (
+ "archive/tar"
"bytes"
"encoding/json"
"io"
+ "net/http"
+ "net/http/httputil"
+ "os"
"os/exec"
+ "strconv"
"strings"
- "testing"
"time"
"github.com/docker/docker/api/types"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
+ "github.com/docker/docker/pkg/stringid"
+ "github.com/docker/docker/runconfig"
+ "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)
- }
+ 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
}
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()
+// regression test for empty json field being omitted #13691
+func (s *DockerSuite) TestContainerApiGetJSONNoFieldsOmitted(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "busybox", "true")
+ _, err := runCommand(runCmd)
+ c.Assert(err, check.IsNil)
+ status, body, err := sockRequest("GET", "/containers/json?all=1", nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+
+ // empty Labels field triggered this bug, make sense to check for everything
+ // cause even Ports for instance can trigger this bug
+ // better safe than sorry..
+ fields := []string{
+ "Id",
+ "Names",
+ "Image",
+ "Command",
+ "Created",
+ "Ports",
+ "Labels",
+ "Status",
+ }
+
+ // decoding into types.Container do not work since it eventually unmarshal
+ // and empty field to an empty go map, so we just check for a string
+ for _, f := range fields {
+ if !strings.Contains(string(body), f) {
+ c.Fatalf("Field %s is missing and it shouldn't", f)
+ }
+ }
+}
+
+type containerPs struct {
+ Names []string
+ Ports []map[string]interface{}
+}
+
+// regression test for non-empty fields from #13901
+func (s *DockerSuite) TestContainerPsOmitFields(c *check.C) {
+ name := "pstest"
+ port := 80
+ runCmd := exec.Command(dockerBinary, "run", "-d", "--name", name, "--expose", strconv.Itoa(port), "busybox", "sleep", "5")
+ _, err := runCommand(runCmd)
+ c.Assert(err, check.IsNil)
+
+ status, body, err := sockRequest("GET", "/containers/json?all=1", nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+
+ var resp []containerPs
+ err = json.Unmarshal(body, &resp)
+ c.Assert(err, check.IsNil)
+
+ var foundContainer *containerPs
+ for _, container := range resp {
+ for _, testName := range container.Names {
+ if "/"+name == testName {
+ foundContainer = &container
+ break
+ }
+ }
+ }
+
+ c.Assert(len(foundContainer.Ports), check.Equals, 1)
+ c.Assert(foundContainer.Ports[0]["PrivatePort"], check.Equals, float64(port))
+ _, ok := foundContainer.Ports[0]["PublicPort"]
+ c.Assert(ok, check.Not(check.Equals), true)
+ _, ok = foundContainer.Ports[0]["IP"]
+ c.Assert(ok, check.Not(check.Equals), true)
+}
+
+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)
- }
+ 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)); ; {
@@ -73,7 +143,7 @@ func TestContainerApiGetExport(t *testing.T) {
if err == io.EOF {
break
}
- t.Fatal(err)
+ c.Fatal(err)
}
if h.Name == "test" {
found = true
@@ -82,33 +152,28 @@ 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)
- }
+ 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
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
@@ -119,56 +184,50 @@ 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",
"Volumes": map[string]struct{}{"/tmp": {}},
}
- if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
- t.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 _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
- t.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 {
- 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",
"Volumes": map[string]struct{}{"/tmp": {}},
}
- if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
- t.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")
@@ -176,177 +235,241 @@ 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 {
- t.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)
- }
- }
+ status, body, err := sockRequest("POST", "/containers/"+name+"/start", config)
+ c.Assert(status, check.Equals, http.StatusInternalServerError)
+ c.Assert(err, check.IsNil)
- logDone("container REST API - check for duplicate volume binds error on start")
+ if !strings.Contains(string(body), "Duplicate bind") {
+ c.Fatalf("Expected failure due to duplicate bind mounts to same path, instead got: %q with error: %v", string(body), err)
+ }
}
-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"
+ name := "TestContainerApiStartDupVolumeBinds"
config := map[string]interface{}{
"Image": "busybox",
"Volumes": map[string]struct{}{volPath: {}},
}
- if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
- t.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 _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
- t.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 {
- 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()
- volName := "voltst"
- volPath := "/tmp"
-
- if out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", volName, "-v", volPath, "busybox")); err != nil {
- t.Fatal(out, err)
- }
-
- name := "testing"
- config := map[string]interface{}{
- "Image": "busybox",
- "Volumes": map[string]struct{}{volPath: {}},
- }
-
- if _, err := sockRequest("POST", "/containers/create?name="+name, config); err != nil && !strings.Contains(err.Error(), "201 Created") {
- t.Fatal(err)
- }
-
- bindPath := randomUnixTmpDirPath("test")
- config = map[string]interface{}{
- "VolumesFrom": []string{volName},
- "Binds": []string{bindPath + ":/tmp"},
- }
- if _, err := sockRequest("POST", "/containers/"+name+"/start", config); err != nil && !strings.Contains(err.Error(), "204 No Content") {
- t.Fatal(err)
- }
-
- pth, err := inspectFieldMap(name, "Volumes", volPath)
- if err != nil {
- t.Fatal(err)
- }
- pth2, err := inspectFieldMap(volName, "Volumes", volPath)
- if err != nil {
- t.Fatal(err)
- }
-
- if pth != pth2 {
- t.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
- err error
+ status int
+ body []byte
+ err error
}
bc := make(chan b, 1)
go func() {
- body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
- bc <- b{body, err}
+ status, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
+ bc <- b{status, body, err}
}()
// 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.Assert(sr.err, check.IsNil)
+ c.Assert(sr.status, check.Equals, http.StatusOK)
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) TestGetContainerStatsRmRunning(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-d", "busybox", "top")
+ id := strings.TrimSpace(out)
+
+ buf := &channelBuffer{make(chan []byte, 1)}
+ defer buf.Close()
+ chErr := make(chan error)
+ go func() {
+ _, body, err := sockRequestRaw("GET", "/containers/"+id+"/stats?stream=1", nil, "application/json")
+ if err != nil {
+ chErr <- err
+ }
+ defer body.Close()
+ _, err = io.Copy(buf, body)
+ chErr <- err
+ }()
+ defer func() {
+ c.Assert(<-chErr, check.IsNil)
+ }()
+
+ b := make([]byte, 32)
+ // make sure we've got some stats
+ _, err := buf.ReadTimeout(b, 2*time.Second)
+ c.Assert(err, check.IsNil)
+
+ // Now remove without `-f` and make sure we are still pulling stats
+ _, err = runCommand(exec.Command(dockerBinary, "rm", id))
+ c.Assert(err, check.Not(check.IsNil), check.Commentf("rm should have failed but didn't"))
+ _, err = buf.ReadTimeout(b, 2*time.Second)
+ c.Assert(err, check.IsNil)
+ dockerCmd(c, "rm", "-f", id)
+
+ _, err = buf.ReadTimeout(b, 2*time.Second)
+ c.Assert(err, check.Not(check.IsNil))
+}
+
+// regression test for gh13421
+// previous test was just checking one stat entry so it didn't fail (stats with
+// stream false always return one stat)
+func (s *DockerSuite) TestGetContainerStatsStream(c *check.C) {
+ name := "statscontainer"
+ runCmd := exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "top")
+ _, err := runCommand(runCmd)
+ c.Assert(err, check.IsNil)
+
+ type b struct {
+ status int
+ body []byte
+ err error
+ }
+ bc := make(chan b, 1)
+ go func() {
+ status, body, err := sockRequest("GET", "/containers/"+name+"/stats", nil)
+ bc <- b{status, body, err}
+ }()
+
+ // 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 {
+ 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):
+ c.Fatal("stream was not closed after container was removed")
+ case sr := <-bc:
+ c.Assert(sr.err, check.IsNil)
+ c.Assert(sr.status, check.Equals, http.StatusOK)
+
+ s := string(sr.body)
+ // count occurrences of "read" of types.Stats
+ if l := strings.Count(s, "read"); l < 2 {
+ c.Fatalf("Expected more than one stat streamed, got %d", l)
+ }
+ }
+}
+
+func (s *DockerSuite) TestGetContainerStatsNoStream(c *check.C) {
+ name := "statscontainer"
+ runCmd := exec.Command(dockerBinary, "run", "-d", "--name", name, "busybox", "top")
+ _, err := runCommand(runCmd)
+ c.Assert(err, check.IsNil)
+
+ type b struct {
+ status int
+ body []byte
+ err error
+ }
+ bc := make(chan b, 1)
+ go func() {
+ status, body, err := sockRequest("GET", "/containers/"+name+"/stats?stream=0", nil)
+ bc <- b{status, body, err}
+ }()
+
+ // 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 {
+ 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):
+ c.Fatal("stream was not closed after container was removed")
+ case sr := <-bc:
+ c.Assert(sr.err, check.IsNil)
+ c.Assert(sr.status, check.Equals, http.StatusOK)
+
+ s := string(sr.body)
+ // count occurrences of "read" of types.Stats
+ if l := strings.Count(s, "read"); l != 1 {
+ c.Fatalf("Expected only one stat streamed, got %d", l)
+ }
+ }
+}
+
+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")
)
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() {
// 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 {
- t.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
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)
@@ -358,28 +481,30 @@ 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)
}
- out, err := sockRequestRaw("POST", "/build?dockerfile=../Dockerfile", buffer, "application/x-tar")
- if err == nil {
- t.Fatalf("Build was supposed to fail: %s", out)
+ 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)
+ if err != nil {
+ 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/
@@ -387,13 +512,17 @@ RUN find / -name ba*
RUN find /tmp/`,
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
- buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json")
+ 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)
if err != nil {
- t.Fatalf("Build failed: %s", err)
+ c.Fatal(err)
}
// Make sure Dockerfile exists.
@@ -401,36 +530,36 @@ 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()
- buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
+ 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)
if err != nil {
- t.Fatalf("Build failed: %s\n%q", err, buf)
+ 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`,
@@ -438,26 +567,28 @@ RUN echo from baz`,
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
- buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+git.RepoURL, nil, "application/json")
+ 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)
if err != nil {
- t.Fatalf("Build failed: %s\n%q", err, buf)
+ 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`,
@@ -465,25 +596,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
- buf, err := sockRequestRaw("POST", "/build?remote="+git.RepoURL, nil, "application/json")
+ 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)
if err != nil {
- t.Fatalf("Build failed: %s", 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)
@@ -495,15 +628,19 @@ 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)
}
- out, err := sockRequestRaw("POST", "/build", buffer, "application/x-tar")
- if err == nil {
- t.Fatalf("Build was supposed to fail: %s", out)
+ 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)
+ if err != nil {
+ c.Fatal(err)
}
// The reason the error is "Cannot locate specified Dockerfile" is because
@@ -511,45 +648,856 @@ 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"}}
- _, err = sockRequest("POST", "/containers/two/start", bindSpec)
- if err != nil && !strings.Contains(err.Error(), "204 No Content") {
- t.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 {
- t.Fatal(err)
+ c.Fatal(err)
}
if fooDir2 != fooDir {
- t.Fatal("expected volume path to be %s, got: %s", fooDir, fooDir2)
+ c.Fatalf("expected volume path to be %s, got: %s", fooDir, fooDir2)
+ }
+}
+
+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 {
+ c.Fatalf("failed to create a container: %s, %v", out, err)
+ }
+ ContainerID := strings.TrimSpace(out)
+
+ status, _, err := sockRequest("POST", "/containers/"+ContainerID+"/pause", nil)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ pausedContainers, err := getSliceOfPausedContainers()
+
+ if err != nil {
+ c.Fatalf("error thrown while checking if containers were paused: %v", err)
}
- logDone("container REST API - can use path from normal volume as bind-mount to overwrite another volume")
+ if len(pausedContainers) != 1 || stringid.TruncateID(ContainerID) != pausedContainers[0] {
+ c.Fatalf("there should be one paused container and not %d", len(pausedContainers))
+ }
+
+ status, _, err = sockRequest("POST", "/containers/"+ContainerID+"/unpause", nil)
+ c.Assert(status, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ pausedContainers, err = getSliceOfPausedContainers()
+
+ if err != nil {
+ c.Fatalf("error thrown while checking if containers were paused: %v", err)
+ }
+
+ if pausedContainers != nil {
+ c.Fatalf("There should be no paused container.")
+ }
+}
+
+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 {
+ c.Fatal(err)
+ }
+
+ type topResp struct {
+ Titles []string
+ Processes [][]string
+ }
+ var top topResp
+ 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)
+ }
+
+ if len(top.Titles) != 11 {
+ c.Fatalf("expected 11 titles, found %d: %v", len(top.Titles), top.Titles)
+ }
+
+ if top.Titles[0] != "USER" || top.Titles[10] != "COMMAND" {
+ c.Fatalf("expected `USER` at `Titles[0]` and `COMMAND` at Titles[10]: %v", top.Titles)
+ }
+ if len(top.Processes) != 2 {
+ c.Fatalf("expected 2 processes, found %d: %v", len(top.Processes), top.Processes)
+ }
+ 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] != "top" {
+ c.Fatalf("expected `top`, found: %s", top.Processes[1][10])
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCommit(c *check.C) {
+ cName := "testapicommit"
+ out, err := exec.Command(dockerBinary, "run", "--name="+cName, "busybox", "/bin/sh", "-c", "touch /test").CombinedOutput()
+ if err != nil {
+ c.Fatal(err, out)
+ }
+
+ 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)
+
+ type resp struct {
+ Id string
+ }
+ var img resp
+ if err := json.Unmarshal(b, &img); err != nil {
+ c.Fatal(err)
+ }
+
+ 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
+ out, err = exec.Command(dockerBinary, "run", img.Id, "ls", "/test").CombinedOutput()
+ if err != nil {
+ c.Fatalf("error checking committed image: %v - %q", err, string(out))
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCreate(c *check.C) {
+ config := map[string]interface{}{
+ "Image": "busybox",
+ "Cmd": []string{"/bin/sh", "-c", "touch /test && ls /test"},
+ }
+
+ 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
+ }
+ var container createResp
+ if err := json.Unmarshal(b, &container); err != nil {
+ c.Fatal(err)
+ }
+
+ 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)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCreateEmptyConfig(c *check.C) {
+ config := map[string]interface{}{}
+
+ status, b, err := sockRequest("POST", "/containers/create", config)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusInternalServerError)
+
+ expected := "Config cannot be empty in order to create a container\n"
+ if body := string(b); body != expected {
+ c.Fatalf("Expected to get %q, got %q", expected, body)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCreateWithHostName(c *check.C) {
+ hostName := "test-host"
+ config := map[string]interface{}{
+ "Image": "busybox",
+ "Hostname": hostName,
+ }
+
+ status, body, err := sockRequest("POST", "/containers/create", config)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusCreated)
+
+ var container types.ContainerCreateResponse
+ if err := json.Unmarshal(body, &container); err != nil {
+ c.Fatal(err)
+ }
+
+ status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusOK)
+
+ var containerJSON types.ContainerJSON
+ if err := json.Unmarshal(body, &containerJSON); err != nil {
+ c.Fatal(err)
+ }
+
+ if containerJSON.Config.Hostname != hostName {
+ c.Fatalf("Mismatched Hostname, Expected %s, Actual: %s ", hostName, containerJSON.Config.Hostname)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCreateWithDomainName(c *check.C) {
+ domainName := "test-domain"
+ config := map[string]interface{}{
+ "Image": "busybox",
+ "Domainname": domainName,
+ }
+
+ status, body, err := sockRequest("POST", "/containers/create", config)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusCreated)
+
+ var container types.ContainerCreateResponse
+ if err := json.Unmarshal(body, &container); err != nil {
+ c.Fatal(err)
+ }
+
+ status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusOK)
+
+ var containerJSON types.ContainerJSON
+ if err := json.Unmarshal(body, &containerJSON); err != nil {
+ c.Fatal(err)
+ }
+
+ if containerJSON.Config.Domainname != domainName {
+ c.Fatalf("Mismatched Domainname, Expected %s, Actual: %s ", domainName, containerJSON.Config.Domainname)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCreateNetworkMode(c *check.C) {
+ UtilCreateNetworkMode(c, "host")
+ UtilCreateNetworkMode(c, "bridge")
+ UtilCreateNetworkMode(c, "container:web1")
+}
+
+func UtilCreateNetworkMode(c *check.C, networkMode string) {
+ config := map[string]interface{}{
+ "Image": "busybox",
+ "HostConfig": map[string]interface{}{"NetworkMode": networkMode},
+ }
+
+ status, body, err := sockRequest("POST", "/containers/create", config)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusCreated)
+
+ var container types.ContainerCreateResponse
+ if err := json.Unmarshal(body, &container); err != nil {
+ c.Fatal(err)
+ }
+
+ status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusOK)
+
+ var containerJSON types.ContainerJSON
+ if err := json.Unmarshal(body, &containerJSON); err != nil {
+ c.Fatal(err)
+ }
+
+ if containerJSON.HostConfig.NetworkMode != runconfig.NetworkMode(networkMode) {
+ c.Fatalf("Mismatched NetworkMode, Expected %s, Actual: %s ", networkMode, containerJSON.HostConfig.NetworkMode)
+ }
+}
+
+func (s *DockerSuite) TestContainerApiCreateWithCpuSharesCpuset(c *check.C) {
+ config := map[string]interface{}{
+ "Image": "busybox",
+ "CpuShares": 512,
+ "CpusetCpus": "0,1",
+ }
+
+ status, body, err := sockRequest("POST", "/containers/create", config)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusCreated)
+
+ var container types.ContainerCreateResponse
+ if err := json.Unmarshal(body, &container); err != nil {
+ c.Fatal(err)
+ }
+
+ status, body, err = sockRequest("GET", "/containers/"+container.ID+"/json", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, http.StatusOK)
+
+ var containerJson types.ContainerJSON
+
+ c.Assert(json.Unmarshal(body, &containerJson), check.IsNil)
+
+ out, err := inspectField(containerJson.Id, "HostConfig.CpuShares")
+ c.Assert(err, check.IsNil)
+ c.Assert(out, check.Equals, "512")
+
+ outCpuset, errCpuset := inspectField(containerJson.Id, "HostConfig.CpusetCpus")
+ c.Assert(errCpuset, check.IsNil, check.Commentf("Output: %s", outCpuset))
+ c.Assert(outCpuset, check.Equals, "0,1")
+}
+
+func (s *DockerSuite) TestContainerApiVerifyHeader(c *check.C) {
+ config := map[string]interface{}{
+ "Image": "busybox",
+ }
+
+ 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)
+ }
+ return sockRequestRaw("POST", "/containers/create", jsonData, ct)
+ }
+
+ // Try with no content-type
+ res, body, err := create("")
+ c.Assert(err, check.IsNil)
+ c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError)
+ body.Close()
+
+ // Try with wrong content-type
+ res, body, err = create("application/xml")
+ c.Assert(err, check.IsNil)
+ c.Assert(res.StatusCode, check.Equals, http.StatusInternalServerError)
+ body.Close()
+
+ // now application/json
+ res, body, err = create("application/json")
+ c.Assert(err, check.IsNil)
+ c.Assert(res.StatusCode, check.Equals, http.StatusCreated)
+ body.Close()
+}
+
+// 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 (s *DockerSuite) TestContainerApiPostCreateNull(c *check.C) {
+ 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}`
+
+ 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)
+ if err != nil {
+ c.Fatal(err)
+ }
+ type createResp struct {
+ Id string
+ }
+ var container createResp
+ if err := json.Unmarshal(b, &container); err != nil {
+ c.Fatal(err)
+ }
+
+ out, err := inspectField(container.Id, "HostConfig.CpusetCpus")
+ if err != nil {
+ c.Fatal(err, out)
+ }
+ if out != "" {
+ c.Fatalf("expected empty string, got %q", out)
+ }
+
+ outMemory, errMemory := inspectField(container.Id, "HostConfig.Memory")
+ c.Assert(outMemory, check.Equals, "0")
+ if errMemory != nil {
+ c.Fatal(errMemory, outMemory)
+ }
+ outMemorySwap, errMemorySwap := inspectField(container.Id, "HostConfig.MemorySwap")
+ c.Assert(outMemorySwap, check.Equals, "0")
+ if errMemorySwap != nil {
+ c.Fatal(errMemorySwap, outMemorySwap)
+ }
+}
+
+func (s *DockerSuite) TestCreateWithTooLowMemoryLimit(c *check.C) {
+ config := `{
+ "Image": "busybox",
+ "Cmd": "ls",
+ "OpenStdin": true,
+ "CpuShares": 100,
+ "Memory": 524287
+ }`
+
+ res, body, _ := sockRequestRaw("POST", "/containers/create", strings.NewReader(config), "application/json")
+ b, err2 := readBody(body)
+ if err2 != nil {
+ c.Fatal(err2)
+ }
+
+ c.Assert(res.StatusCode, 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) {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "create", "busybox"))
+ if err != nil {
+ c.Fatal(err, out)
+ }
+
+ containerID := strings.TrimSpace(out)
+
+ config := `{
+ "CpuShares": 100,
+ "Memory": 524287
+ }`
+
+ res, body, _ := sockRequestRaw("POST", "/containers/"+containerID+"/start", strings.NewReader(config), "application/json")
+ b, err2 := readBody(body)
+ if err2 != nil {
+ c.Fatal(err2)
+ }
+
+ c.Assert(res.StatusCode, 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", "TestContainerApiRename", "-d", "busybox", "sh")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ containerID := strings.TrimSpace(out)
+ newName := "TestContainerApiRenameNew"
+ 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)
+ }
+}
+
+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) 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{}{
+ "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)
+}
+
+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)
+ }
+}
+
+// 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])
+ }
+}
+
+func (s *DockerSuite) TestPostContainerStop(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ containerID := strings.TrimSpace(out)
+ c.Assert(waitRun(containerID), check.IsNil)
+
+ statusCode, _, err := sockRequest("POST", "/containers/"+containerID+"/stop", nil)
+
+ // 204 No Content is expected, not 200
+ c.Assert(statusCode, check.Equals, http.StatusNoContent)
+ c.Assert(err, check.IsNil)
+
+ if err := waitInspect(containerID, "{{ .State.Running }}", "false", 5); err != nil {
+ c.Fatal(err)
+ }
}
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..ab753d8ec
--- /dev/null
+++ b/integration-cli/docker_api_exec_resize_test.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+ "net/http"
+ "os/exec"
+ "strings"
+
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestExecResizeApiHeightWidthNoInt(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf(out, err)
+ }
+ cleanedContainerID := strings.TrimSpace(out)
+
+ endpoint := "/exec/" + cleanedContainerID + "/resize?h=foo&w=bar"
+ status, _, err := sockRequest("POST", endpoint, nil)
+ 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 1ed99a256..b7957480f 100644
--- a/integration-cli/docker_api_exec_test.go
+++ b/integration-cli/docker_api_exec_test.go
@@ -5,23 +5,25 @@ package main
import (
"bytes"
"fmt"
+ "net/http"
"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)
- }
+ 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)
- logDone("exec create API - returns error when missing Cmd")
+ 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
new file mode 100644
index 000000000..573a89046
--- /dev/null
+++ b/integration-cli/docker_api_images_test.go
@@ -0,0 +1,134 @@
+package main
+
+import (
+ "encoding/json"
+ "net/http"
+ "net/url"
+ "os/exec"
+ "strings"
+
+ "github.com/docker/docker/api/types"
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestApiImagesFilter(c *check.C) {
+ name := "utest:tag1"
+ name2 := "utest/docker:tag2"
+ name3 := "utest:5000/docker:tag3"
+ 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 types.Image
+ getImages := func(filter string) []image {
+ v := url.Values{}
+ v.Set("filter", filter)
+ 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)
+ }
+
+ return images
+ }
+
+ errMsg := "incorrect number of matches returned"
+ if images := getImages("utest*/*"); len(images[0].RepoTags) != 2 {
+ c.Fatal(errMsg)
+ }
+ if images := getImages("utest"); len(images[0].RepoTags) != 1 {
+ c.Fatal(errMsg)
+ }
+ if images := getImages("utest*"); len(images[0].RepoTags) != 1 {
+ c.Fatal(errMsg)
+ }
+ if images := getImages("*5000*/*"); len(images[0].RepoTags) != 1 {
+ c.Fatal(errMsg)
+ }
+}
+
+func (s *DockerSuite) TestApiImagesSaveAndLoad(c *check.C) {
+ testRequires(c, Network)
+ out, err := buildImage("saveandload", "FROM hello-world\nENV FOO bar", false)
+ if err != nil {
+ c.Fatal(err)
+ }
+ id := strings.TrimSpace(out)
+
+ 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()
+
+ if out, err := exec.Command(dockerBinary, "rmi", id).CombinedOutput(); err != nil {
+ c.Fatal(err, out)
+ }
+
+ 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()
+
+ 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")
+ }
+}
+
+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 {
+ 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: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)
+}
+
+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")
+}
diff --git a/integration-cli/docker_api_info_test.go b/integration-cli/docker_api_info_test.go
new file mode 100644
index 000000000..408428910
--- /dev/null
+++ b/integration-cli/docker_api_info_test.go
@@ -0,0 +1,36 @@
+package main
+
+import (
+ "net/http"
+ "strings"
+
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestInfoApi(c *check.C) {
+ endpoint := "/info"
+
+ status, body, err := sockRequest("GET", endpoint, nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
+
+ // 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) {
+ c.Errorf("couldn't find string %v in output", linePrefix)
+ }
+ }
+}
diff --git a/integration-cli/docker_api_inspect_test.go b/integration-cli/docker_api_inspect_test.go
index ed6f596b2..62f32357c 100644
--- a/integration-cli/docker_api_inspect_test.go
+++ b/integration-cli/docker_api_inspect_test.go
@@ -2,58 +2,43 @@ package main
import (
"encoding/json"
+ "net/http"
"os/exec"
- "testing"
+ "strings"
+
+ "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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
- // test on json marshal version
- // and latest version
- testVersions := []string{"v1.11", "latest"}
+ endpoint := "/containers/" + cleanedContainerID + "/json"
+ status, body, err := sockRequest("GET", endpoint, nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
- for _, testVersion := range testVersions {
- endpoint := "/containers/" + cleanedContainerID + "/json"
- if testVersion != "latest" {
- endpoint = "/" + testVersion + endpoint
- }
- body, err := sockRequest("GET", endpoint, nil)
- if err != nil {
- t.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)
- }
-
- keys := []string{"State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings", "ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "ExecDriver", "MountLabel", "ProcessLabel", "Volumes", "VolumesRW"}
-
- if testVersion == "v1.11" {
- keys = append(keys, "ID")
- } else {
- keys = append(keys, "Id")
- }
-
- for _, key := range keys {
- if _, ok := inspectJSON[key]; !ok {
- t.Fatalf("%s does not exist in reponse 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")
- }
+ var inspectJSON map[string]interface{}
+ if err = json.Unmarshal(body, &inspectJSON); err != nil {
+ c.Fatalf("unable to unmarshal body for latest version: %v", err)
}
- logDone("container json - check keys in container json response")
+ keys := []string{"State", "Created", "Path", "Args", "Config", "Image", "NetworkSettings", "ResolvConfPath", "HostnamePath", "HostsPath", "LogPath", "Name", "Driver", "ExecDriver", "MountLabel", "ProcessLabel", "Volumes", "VolumesRW"}
+
+ keys = append(keys, "Id")
+
+ for _, key := range keys {
+ if _, ok := inspectJSON[key]; !ok {
+ c.Fatalf("%s does not exist in response for latest version", key)
+ }
+ }
+ //Issue #6830: type not properly converted to JSON/back
+ if _, ok := inspectJSON["Path"].(bool); ok {
+ c.Fatalf("Path of `true` should not be converted to boolean `true` via JSON marshalling")
+ }
}
diff --git a/integration-cli/docker_api_logs_test.go b/integration-cli/docker_api_logs_test.go
new file mode 100644
index 000000000..caef7255d
--- /dev/null
+++ b/integration-cli/docker_api_logs_test.go
@@ -0,0 +1,83 @@
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "net/http"
+ "os/exec"
+ "strings"
+ "time"
+
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestLogsApiWithStdout(c *check.C) {
+ 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)
+ }
+
+ type logOut struct {
+ out string
+ res *http.Response
+ err error
+ }
+ chLog := make(chan logOut)
+
+ go func() {
+ 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), res, err}
+ }()
+
+ select {
+ case l := <-chLog:
+ 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")
+ }
+ case <-time.After(2 * time.Second):
+ c.Fatal("timeout waiting for logs to exit")
+ }
+}
+
+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 {
+ c.Fatal(out, err)
+ }
+
+ 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)) {
+ c.Fatalf("Expected %s, got %s", expected, string(body[:]))
+ }
+}
+
+// Regression test for #12704
+func (s *DockerSuite) TestLogsApiFollowEmptyOutput(c *check.C) {
+ 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)
+ }
+}
diff --git a/integration-cli/docker_api_resize_test.go b/integration-cli/docker_api_resize_test.go
index 6ba95c305..6d5528069 100644
--- a/integration-cli/docker_api_resize_test.go
+++ b/integration-cli/docker_api_resize_test.go
@@ -1,53 +1,62 @@
package main
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 := stripTrailingCharacters(out)
+ 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)
- }
-
- logDone("container resize - when started")
+ status, _, err := sockRequest("POST", endpoint, nil)
+ c.Assert(status, check.Equals, http.StatusOK)
+ c.Assert(err, check.IsNil)
}
-func TestResizeApiResponseWhenContainerNotStarted(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 {
+ c.Fatalf(out, err)
+ }
+ cleanedContainerID := strings.TrimSpace(out)
+
+ endpoint := "/containers/" + cleanedContainerID + "/resize?h=foo&w=bar"
+ status, _, err := sockRequest("POST", endpoint, nil)
+ c.Assert(status, check.Equals, http.StatusInternalServerError)
+ c.Assert(err, check.IsNil)
+}
+
+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 := stripTrailingCharacters(out)
+ 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 {
- 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")
- }
- 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))
- }
+ status, body, err := sockRequest("POST", endpoint, nil)
+ c.Assert(status, check.Equals, http.StatusInternalServerError)
+ c.Assert(err, check.IsNil)
- logDone("container resize - when not started should not resize")
+ 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_stats_test.go b/integration-cli/docker_api_stats_test.go
new file mode 100644
index 000000000..6a143fed1
--- /dev/null
+++ b/integration-cli/docker_api_stats_test.go
@@ -0,0 +1,35 @@
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+
+ "github.com/docker/docker/api/types"
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestCliStatsNoStreamGetCpu(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "while true;do echo 'Hello'; usleep 100000; done")
+
+ id := strings.TrimSpace(out)
+ err := waitRun(id)
+ c.Assert(err, check.IsNil)
+
+ resp, body, err := sockRequestRaw("GET", fmt.Sprintf("/containers/%s/stats?stream=false", id), nil, "")
+ c.Assert(err, check.IsNil)
+ c.Assert(resp.ContentLength > 0, check.Equals, true, check.Commentf("should not use chunked encoding"))
+ c.Assert(resp.Header.Get("Content-Type"), check.Equals, "application/json")
+
+ var v *types.Stats
+ err = json.NewDecoder(body).Decode(&v)
+ c.Assert(err, check.IsNil)
+
+ var cpuPercent = 0.0
+ cpuDelta := float64(v.CpuStats.CpuUsage.TotalUsage - v.PreCpuStats.CpuUsage.TotalUsage)
+ systemDelta := float64(v.CpuStats.SystemUsage - v.PreCpuStats.SystemUsage)
+ cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CpuStats.CpuUsage.PercpuUsage)) * 100.0
+ if cpuPercent == 0 {
+ c.Fatalf("docker stats with no-stream get cpu usage failed: was %v", cpuPercent)
+ }
+}
diff --git a/integration-cli/docker_api_test.go b/integration-cli/docker_api_test.go
new file mode 100644
index 000000000..0bd48880a
--- /dev/null
+++ b/integration-cli/docker_api_test.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "net/http"
+ "net/http/httputil"
+ "time"
+
+ "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")
+}
+
+func (s *DockerSuite) TestVersionStatusCode(c *check.C) {
+ conn, err := sockConn(time.Duration(10 * time.Second))
+ c.Assert(err, check.IsNil)
+
+ client := httputil.NewClientConn(conn, nil)
+ defer client.Close()
+
+ req, err := http.NewRequest("GET", "/v999.0/version", nil)
+ c.Assert(err, check.IsNil)
+ req.Header.Set("User-Agent", "Docker-Client/999.0")
+
+ res, err := client.Do(req)
+ c.Assert(res.StatusCode, check.Equals, http.StatusBadRequest)
+}
diff --git a/integration-cli/docker_api_version_test.go b/integration-cli/docker_api_version_test.go
new file mode 100644
index 000000000..b756794c2
--- /dev/null
+++ b/integration-cli/docker_api_version_test.go
@@ -0,0 +1,25 @@
+package main
+
+import (
+ "encoding/json"
+ "net/http"
+
+ "github.com/docker/docker/api/types"
+ "github.com/docker/docker/autogen/dockerversion"
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestGetVersion(c *check.C) {
+ 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)
+ }
+
+ if v.Version != dockerversion.VERSION {
+ c.Fatal("Version mismatch")
+ }
+}
diff --git a/integration-cli/docker_cli_attach_test.go b/integration-cli/docker_cli_attach_test.go
index cf21cda58..fc2ea1a1d 100644
--- a/integration-cli/docker_cli_attach_test.go
+++ b/integration-cli/docker_cli_attach_test.go
@@ -1,18 +1,20 @@
package main
import (
+ "bufio"
+ "fmt"
"io"
"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{}
@@ -20,7 +22,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{})
@@ -38,32 +40,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))
}
}()
}
@@ -71,66 +73,111 @@ 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)
}
}()
- 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 {
- t.Fatal(err)
+ done <- err
+ return
}
expected := "cannot enable tty mode"
if out, _, err := runCommandWithOutput(cmd); err == nil {
- t.Fatal("attach should have failed")
+ done <- fmt.Errorf("attach should have failed")
+ return
} else if !strings.Contains(out, expected) {
- t.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):
- t.Fatal("attach is running but should have failed")
+ c.Fatal("attach is running but should have failed")
+ }
+}
+
+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 {
+ c.Fatal(err)
+ }
+ defer stdin.Close()
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer stdout.Close()
+ if err := cmd.Start(); err != nil {
+ c.Fatal(err)
+ }
+ defer cmd.Process.Kill()
+
+ if _, err := stdin.Write([]byte("hello\n")); err != nil {
+ c.Fatal(err)
+ }
+ out, err = bufio.NewReader(stdout).ReadString('\n')
+ if err != nil {
+ c.Fatal(err)
+ }
+ if strings.TrimSpace(out) != "hello" {
+ c.Fatalf("expected 'hello', got %q", out)
+ }
+
+ if err := stdin.Close(); err != nil {
+ c.Fatal(err)
+ }
+
+ // Expect container to still be running after stdin is closed
+ running, err := inspectField(id, "State.Running")
+ if err != nil {
+ c.Fatal(err)
+ }
+ if running != "true" {
+ c.Fatal("expected container to still be running")
}
- logDone("attach - forbid piped stdin to tty enabled container")
}
diff --git a/integration-cli/docker_cli_attach_unix_test.go b/integration-cli/docker_cli_attach_unix_test.go
index a3bfa5b1c..82808a5b0 100644
--- a/integration-cli/docker_cli_attach_unix_test.go
+++ b/integration-cli/docker_cli_attach_unix_test.go
@@ -3,37 +3,38 @@
package main
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 := stripTrailingCharacters(out)
+ id := strings.TrimSpace(out)
if err := waitRun(id); err != nil {
- t.Fatal(err)
+ 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 {
- t.Fatalf("could not open pty: %v", err)
+ errChan <- err
+ return
}
attachCmd := exec.Command(dockerBinary, "attach", id)
attachCmd.Stdin = tty
@@ -41,58 +42,61 @@ func TestAttachClosedOnContainerStop(t *testing.T) {
attachCmd.Stderr = tty
if err := attachCmd.Run(); err != nil {
- t.Fatalf("attach returned error %s", err)
+ errChan <- err
+ return
}
}()
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 err := <-errChan:
+ c.Assert(err, check.IsNil)
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
cmd.Stdout = tty
cmd.Stderr = tty
- detached := make(chan struct{})
+ errChan := make(chan error)
go func() {
- if err := cmd.Run(); err != nil {
- t.Fatalf("attach returned error %s", err)
- }
- close(detached)
+ errChan <- cmd.Run()
+ close(errChan)
}()
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)
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 {
- t.Fatalf("Could not open pty: %v", err)
+ c.Fatalf("Could not open pty: %v", err)
}
cmd = exec.Command(dockerBinary, "attach", name)
@@ -101,7 +105,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)
@@ -120,20 +124,162 @@ func TestAttachAfterDetach(t *testing.T) {
select {
case err := <-readErr:
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
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]))
+ }
+
+}
+
+// TestAttachDetach checks that attach in tty mode can be detached using the long container ID
+func (s *DockerSuite) TestAttachDetach(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-itd", "busybox", "cat")
+ id := strings.TrimSpace(out)
+ if err := waitRun(id); err != nil {
+ c.Fatal(err)
+ }
+
+ cpty, tty, err := pty.Open()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer cpty.Close()
+
+ cmd := exec.Command(dockerBinary, "attach", id)
+ cmd.Stdin = tty
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer stdout.Close()
+ if err := cmd.Start(); err != nil {
+ c.Fatal(err)
+ }
+ if err := waitRun(id); err != nil {
+ c.Fatalf("error waiting for container to start: %v", err)
+ }
+
+ if _, err := cpty.Write([]byte("hello\n")); err != nil {
+ c.Fatal(err)
+ }
+ out, err = bufio.NewReader(stdout).ReadString('\n')
+ if err != nil {
+ c.Fatal(err)
+ }
+ if strings.TrimSpace(out) != "hello" {
+ c.Fatalf("expected 'hello', got %q", out)
+ }
+
+ // escape sequence
+ if _, err := cpty.Write([]byte{16}); err != nil {
+ c.Fatal(err)
+ }
+ time.Sleep(100 * time.Millisecond)
+ if _, err := cpty.Write([]byte{17}); err != nil {
+ c.Fatal(err)
+ }
+
+ ch := make(chan struct{})
+ go func() {
+ cmd.Wait()
+ ch <- struct{}{}
+ }()
+
+ running, err := inspectField(id, "State.Running")
+ if err != nil {
+ c.Fatal(err)
+ }
+ if running != "true" {
+ c.Fatal("expected container to still be running")
+ }
+
+ go func() {
+ dockerCmd(c, "kill", id)
+ }()
+
+ select {
+ case <-ch:
+ case <-time.After(10 * time.Millisecond):
+ c.Fatal("timed out waiting for container to exit")
+ }
+
+}
+
+// TestAttachDetachTruncatedID checks that attach in tty mode can be detached
+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 {
+ c.Fatal(err)
+ }
+
+ cpty, tty, err := pty.Open()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer cpty.Close()
+
+ cmd := exec.Command(dockerBinary, "attach", id)
+ cmd.Stdin = tty
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer stdout.Close()
+ if err := cmd.Start(); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := cpty.Write([]byte("hello\n")); err != nil {
+ c.Fatal(err)
+ }
+ out, err = bufio.NewReader(stdout).ReadString('\n')
+ if err != nil {
+ c.Fatal(err)
+ }
+ if strings.TrimSpace(out) != "hello" {
+ c.Fatalf("expected 'hello', got %q", out)
+ }
+
+ // escape sequence
+ if _, err := cpty.Write([]byte{16}); err != nil {
+ c.Fatal(err)
+ }
+ time.Sleep(100 * time.Millisecond)
+ if _, err := cpty.Write([]byte{17}); err != nil {
+ c.Fatal(err)
+ }
+
+ ch := make(chan struct{})
+ go func() {
+ cmd.Wait()
+ ch <- struct{}{}
+ }()
+
+ running, err := inspectField(id, "State.Running")
+ if err != nil {
+ c.Fatal(err)
+ }
+ if running != "true" {
+ c.Fatal("expected container to still be running")
+ }
+
+ go func() {
+ dockerCmd(c, "kill", id)
+ }()
+
+ select {
+ case <-ch:
+ case <-time.After(10 * time.Millisecond):
+ c.Fatal("timed out waiting for container to exit")
}
- logDone("attach - reconnect after detaching")
}
diff --git a/integration-cli/docker_cli_build_test.go b/integration-cli/docker_cli_build_test.go
index 6c96e2aa9..5e35d6e36 100644
--- a/integration-cli/docker_cli_build_test.go
+++ b/integration-cli/docker_cli_build_test.go
@@ -15,18 +15,17 @@ import (
"runtime"
"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)
_, err := buildImage(
name,
@@ -37,15 +36,13 @@ 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)
_, err := buildImage(
name,
@@ -58,15 +55,13 @@ 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)
_, err := buildImage(
name,
@@ -78,7 +73,7 @@ func TestBuildShCmdJSONEntrypoint(t *testing.T) {
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
out, _, err := runCommandWithOutput(
@@ -89,19 +84,17 @@ 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)
_, err := buildImage(name, `
FROM scratch
@@ -109,24 +102,22 @@ 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)
_, err := buildImage(name, `
FROM scratch
@@ -134,30 +125,28 @@ 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)
_, err := buildImage(name, `
FROM scratch
@@ -165,30 +154,28 @@ 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)
_, err := buildImage(name, `
FROM busybox
@@ -198,47 +185,48 @@ 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)
ctx, err := fakeContext(`
FROM scratch
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 {
- 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)
-
_, err := buildImage(name,
`
FROM busybox
@@ -256,18 +244,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
@@ -278,32 +266,34 @@ 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" {
+ 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)
-
_, err := buildImage(name,
`
FROM scratch
@@ -312,22 +302,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)
@@ -340,20 +330,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)
@@ -370,31 +360,28 @@ 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"
- defer deleteImages(name, name2)
-
_, err := buildImage(name,
`
FROM busybox
@@ -402,7 +389,7 @@ func TestBuildOnBuildLowercase(t *testing.T) {
`, true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
_, out, err := buildImageWithOut(name2, fmt.Sprintf(`
@@ -410,24 +397,21 @@ 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
@@ -439,20 +423,17 @@ 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,
`
@@ -463,40 +444,36 @@ 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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
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,
@@ -504,33 +481,29 @@ 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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
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,
@@ -538,33 +511,29 @@ 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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
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,
@@ -572,23 +541,18 @@ 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
ONBUILD CMD ["hello world"]
@@ -597,71 +561,64 @@ 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
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{
"robots.txt": "hello",
"index.html": "world",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -669,10 +626,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,
@@ -680,24 +637,22 @@ 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)
server, err := fakeStorage(map[string]string{
"file": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -710,13 +665,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.
@@ -724,12 +679,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
@@ -738,45 +693,41 @@ 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),
map[string]string{
"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
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -790,48 +741,44 @@ 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
ADD test_file .`,
map[string]string{
"test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
- done := make(chan struct{})
+ errChan := make(chan error)
go func() {
- if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
- }
- close(done)
+ _, err := buildImageFromContext(name, ctx, true)
+ errChan <- err
+ close(errChan)
}()
select {
case <-time.After(5 * time.Second):
- t.Fatal("Build with adding to workdir timed out")
- case <-done:
+ c.Fatal("Build with adding to workdir timed out")
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
}
- 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
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -846,27 +793,25 @@ 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()
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
@@ -893,18 +838,16 @@ 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
ADD file1.txt file2.txt test
`,
@@ -914,20 +857,18 @@ 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
ADD ["file1.txt", "file2.txt", "test"]
`,
@@ -937,20 +878,18 @@ 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
ADD file*.txt test
`,
@@ -960,20 +899,18 @@ 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
ADD ["file*.txt", "test"]
`,
@@ -983,20 +920,18 @@ 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
COPY file1.txt file2.txt test
`,
@@ -1006,20 +941,18 @@ 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
COPY ["file1.txt", "file2.txt", "test"]
`,
@@ -1029,20 +962,18 @@ 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
RUN mkdir "/test dir"
RUN mkdir "/test_dir"
@@ -1068,18 +999,16 @@ 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
RUN mkdir "/test dir"
RUN mkdir "/test_dir"
@@ -1105,18 +1034,16 @@ 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
ADD [ "test file1", "test file2", "test" ]
`,
@@ -1126,20 +1053,18 @@ 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
COPY [ "test file1", "test file2", "test" ]
`,
@@ -1149,26 +1074,24 @@ 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{
"robots.txt": "hello",
"index.html": "world",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -1189,67 +1112,88 @@ func TestBuildCopyWildcard(t *testing.T) {
"dir/nested_dir/nest_nest_file": "2 times nested",
"dirt": "dirty",
})
- defer ctx.Close()
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)
}
// 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
COPY file*.txt /tmp/
`, 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) {
- name := "testcopywildcardcache"
+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
COPY file1.txt /tmp/`,
map[string]string{
"file1.txt": "test1",
})
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.
@@ -1259,19 +1203,17 @@ 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
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1285,20 +1227,18 @@ 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
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1311,19 +1251,17 @@ 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
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1338,19 +1276,17 @@ 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
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1366,40 +1302,36 @@ 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
ADD . /`,
map[string]string{
"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
ADD suidbin /usr/bin/suidbin
RUN chmod 4755 /usr/bin/suidbin
@@ -1411,19 +1343,17 @@ 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
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1437,48 +1367,44 @@ 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
COPY test_file .`,
map[string]string{
"test_file": "test1",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
- done := make(chan struct{})
+ errChan := make(chan error)
go func() {
- if _, err := buildImageFromContext(name, ctx, true); err != nil {
- t.Fatal(err)
- }
- close(done)
+ _, err := buildImageFromContext(name, ctx, true)
+ errChan <- err
+ close(errChan)
}()
select {
case <-time.After(5 * time.Second):
- t.Fatal("Build with adding to workdir timed out")
- case <-done:
+ c.Fatal("Build with adding to workdir timed out")
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
}
- 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
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1493,19 +1419,17 @@ 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
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1519,19 +1443,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 - 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
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1544,19 +1466,17 @@ 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
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1571,19 +1491,17 @@ 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
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
RUN echo 'dockerio:x:1001:' >> /etc/group
@@ -1599,48 +1517,43 @@ 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
COPY . /`,
map[string]string{
"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
@@ -1652,16 +1565,15 @@ func TestBuildAddBadLinks(t *testing.T) {
var (
name = "test-link-absolute"
)
- 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)
@@ -1669,7 +1581,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]
}
@@ -1685,7 +1597,7 @@ func TestBuildAddBadLinks(t *testing.T) {
tarOut, err := os.Create(tarPath)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
tarWriter := tar.NewWriter(tarOut)
@@ -1701,7 +1613,7 @@ func TestBuildAddBadLinks(t *testing.T) {
err = tarWriter.WriteHeader(header)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
tarWriter.Close()
@@ -1709,26 +1621,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
@@ -1741,11 +1652,10 @@ func TestBuildAddBadLinksVolume(t *testing.T) {
name = "test-link-absolute-volume"
dockerfile = ""
)
- defer deleteImages(name)
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)
@@ -1754,76 +1664,73 @@ 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")
}
}
{
name := "testbuildinaccessibledirectory"
- 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
@@ -1831,118 +1738,111 @@ 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")
}
}
{
name := "testlinksok"
- 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)
}
}
{
name := "testbuildignoredinaccessible"
- defer deleteImages(name)
ctx, err := fakeContext("FROM scratch\nADD . /foo/",
map[string]string{
"directoryWeCantStat/bar": "foo",
".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")
- 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) {
+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.
@@ -1952,95 +1852,79 @@ 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) {
- // TODO(jfrazelle): Make this work on Windows.
- testRequires(t, SameHostDaemon)
-
+func (s *DockerSuite) TestBuildCancelationKillsSleep(c *check.C) {
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()
- 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(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()
// Goroutine responsible for watching start/die events from `docker events`
- wg.Add(1)
go func() {
- defer wg.Done()
+ cid := <-containerID
- // Watch for events since epoch.
- eventsCmd := exec.Command(dockerBinary, "events",
- "-since", fmt.Sprint(startEpoch.Unix()))
- stdout, err := eventsCmd.StdoutPipe()
- err = eventsCmd.Start()
- if err != nil {
- t.Fatalf("failed to start 'docker events': %s", err)
- }
-
- go func() {
- <-finish
- eventsCmd.Process.Kill()
- }()
-
- var started, died bool
- matchStart := regexp.MustCompile(" \\(from busybox\\:latest\\) start$")
- matchDie := regexp.MustCompile(" \\(from busybox\\:latest\\) 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
}
}
-
- err = eventsCmd.Wait()
- if err != nil && !IsKilled(err) {
- t.Fatalf("docker events had bad exit status: %s", err)
- }
}()
buildCmd := exec.Command(dockerBinary, "build", "-t", name, ".")
buildCmd.Dir = ctx.Dir
- buildCmd.Stdout = os.Stdout
- err = buildCmd.Start()
- if err != nil {
- t.Fatalf("failed to run build: %s", err)
+ stdoutBuild, err := buildCmd.StdoutPipe()
+ if err := buildCmd.Start(); err != nil {
+ c.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):
- t.Fatal("failed to observe build container start in timely fashion")
+ case <-time.After(5 * time.Second):
+ 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".
@@ -2049,56 +1933,52 @@ func TestBuildCancelationKillsSleep(t *testing.T) {
// 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 {
- t.Fatalf("error killing build command: %s", err)
+ 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) {
- t.Fatalf("wait failed during build run: %T %s", err, err)
+ if err := buildCmd.Wait(); err != nil && !IsKilled(err) {
+ c.Fatalf("wait failed during build run: %T %s", err, err)
}
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")
+ 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)
}
@@ -2106,22 +1986,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)
}
@@ -2129,33 +2009,30 @@ 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")
- logDone("build - ensure --rm=false overrides the default")
}
-func TestBuildWithVolumes(t *testing.T) {
+func (s *DockerSuite) TestBuildWithVolumes(c *check.C) {
var (
result map[string]map[string]struct{}
name = "testbuildvolumes"
@@ -2171,7 +2048,6 @@ func TestBuildWithVolumes(t *testing.T) {
"/test8]": emptyMap,
}
)
- defer deleteImages(name)
_, err := buildImage(name,
`FROM scratch
VOLUME /test1
@@ -2182,52 +2058,48 @@ 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)
_, err := buildImage(name,
`FROM scratch
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)
_, err := buildImage(name,
`FROM busybox
RUN echo 'dockerio:x:1001:1001::/bin:/bin/false' >> /etc/passwd
@@ -2235,22 +2107,20 @@ 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)
_, err := buildImage(name,
`FROM busybox
RUN [ "$PWD" = '/' ]
@@ -2262,22 +2132,20 @@ 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)
_, err := buildImage(name,
`FROM busybox
ENV DIRPATH /test1
@@ -2286,21 +2154,19 @@ 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 := `
FROM busybox
WORKDIR /test1
@@ -2329,19 +2195,17 @@ 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)
_, err := buildImage(name,
`FROM busybox
ENV PATH /test:$PATH
@@ -2349,116 +2213,106 @@ 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)
+ expected := "{[/bin/echo Hello World]}"
_, err := buildImage(name,
`FROM scratch
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)
+ expected := "map[2375/tcp:{}]"
_, err := buildImage(name,
`FROM scratch
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)
@@ -2484,127 +2338,118 @@ func TestBuildExposeMorePorts(t *testing.T) {
tmpl.Execute(buf, portList)
name := "testbuildexpose"
- 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
}
id1 := buildID("testbuildexpose1", "80 2375")
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)
+ expected := "map[5678/udp:{}]"
_, err := buildImage(name,
`FROM scratch
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[]]"
- defer deleteImages(name)
+ expected := "map[5678/tcp:{}]"
_, out, err := buildImageWithOut(name,
`FROM scratch
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)
_, err := buildImage(name,
`FROM busybox
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]"
+ 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,
@@ -2612,69 +2457,64 @@ 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 = "[]"
+ 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 := "[]"
+ expected := "{[]}"
_, err := buildImage(name,
`FROM busybox
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)
+ expected := "{[/bin/echo]}"
_, err := buildImage(name,
`FROM scratch
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
)
@@ -2687,15 +2527,14 @@ 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)
}
{
name2 := "testonbuildtrigger2"
@@ -2704,15 +2543,14 @@ 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)
}
{
name3 := "testonbuildtrigger3"
@@ -2721,34 +2559,31 @@ 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)
}
// 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,
`FROM scratch
MAINTAINER dockerio
@@ -2756,7 +2591,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
@@ -2765,18 +2600,16 @@ 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)
id1, err := buildImage(name,
`FROM scratch
MAINTAINER dockerio
@@ -2784,7 +2617,7 @@ func TestBuildWithoutCache(t *testing.T) {
ENTRYPOINT ["/bin/echo"]`,
true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
id2, err := buildImage(name2,
@@ -2794,18 +2627,15 @@ 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)
dockerfile := `
FROM busybox
@@ -2814,42 +2644,39 @@ 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)
dockerfile := `
FROM busybox
MAINTAINER dockerio
@@ -2860,26 +2687,24 @@ 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)
dockerfile := `
FROM busybox
MAINTAINER dockerio
@@ -2890,26 +2715,24 @@ 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)
dockerfile := `
FROM busybox
MAINTAINER dockerio
@@ -2920,26 +2743,24 @@ 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)
dockerfile := `
FROM scratch
COPY dir /tmp/`
@@ -2948,33 +2769,31 @@ 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"
name4 := name + "4"
name5 := name + "5"
- defer deleteImages(name, name2, name3, name4, name5)
dockerfile := `
FROM scratch
MAINTAINER dockerio
@@ -2984,60 +2803,58 @@ 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)
dockerfile := `
FROM scratch
MAINTAINER dockerio
@@ -3047,30 +2864,28 @@ 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()
@@ -3080,7 +2895,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
@@ -3088,23 +2903,21 @@ 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)
server, err := fakeStorage(map[string]string{
"baz": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -3114,7 +2927,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
@@ -3122,26 +2935,23 @@ 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"
name4 := name + "4"
- defer deleteImages(name, name2, name3, name4)
-
files := map[string]string{"baz": "hello"}
server, err := fakeStorage(files)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -3149,21 +2959,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)
@@ -3174,7 +2984,7 @@ func TestBuildADDRemoteFileMTime(t *testing.T) {
server2, err := fakeStorage(files)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server2.Close()
@@ -3182,36 +2992,34 @@ 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()
@@ -3223,24 +3031,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
@@ -3251,57 +3058,51 @@ 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, "-")
- defer deleteImages(name)
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)
}
- logDone(fmt.Sprintf("build - build an image with a context tar, compression: %v", compression))
}
-func TestBuildContextTarGzip(t *testing.T) {
- testContextTar(t, archive.Gzip)
+func (s *DockerSuite) TestBuildContextTarGzip(c *check.C) {
+ testContextTar(c, archive.Gzip)
}
-func TestBuildContextTarNoCompression(t *testing.T) {
- testContextTar(t, 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, _, err := dockerCmd(t, "run", "--rm", "nocontext"); out != "ok\n" || err != nil {
- 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)
server, err := fakeStorage(map[string]string{
"baz": "hello",
})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -3313,26 +3114,24 @@ 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)
_, err := buildImage(name,
`FROM busybox:latest
@@ -3341,36 +3140,34 @@ 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
@@ -3382,25 +3179,23 @@ 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)
+ if res != "" {
+ c.Fatalf("Cmd %s, expected nil", res)
}
- 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
ADD ../../ test/
`,
@@ -3410,51 +3205,47 @@ 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
ADD foo /usr/local/bar`,
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)
_, err := buildImage(name,
`FROM scratch
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,
@@ -3462,133 +3253,118 @@ 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)
+ if expected := "{[/bin/echo]}"; 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(), "Dockerfile cannot be empty") {
- t.Fatalf("Wrong error %v, must be about empty Dockerfile", err)
+ if !strings.Contains(err.Error(), "The Dockerfile (Dockerfile) cannot be empty") {
+ 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,
`FROM busybox
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,
`FROM busybox
ONBUILD ONBUILD RUN touch foobar`,
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,
`FROM busybox
ONBUILD FROM scratch`,
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,
`FROM busybox
ONBUILD MAINTAINER docker.io`,
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
RUN mkdir /foo
RUN ln -s /foo /bar
@@ -3599,18 +3375,16 @@ 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)
_, err := buildImage(name, `
FROM busybox
@@ -3622,20 +3396,18 @@ 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)
_, err := buildImage(name, `
FROM busybox
@@ -3644,19 +3416,17 @@ 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("Unexpected 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 := `
FROM busybox
ADD . /bla
@@ -3665,29 +3435,36 @@ func TestBuildDockerignore(t *testing.T) {
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 {
- 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")
}
-func TestBuildDockerignoreCleanPaths(t *testing.T) {
+func (s *DockerSuite) TestBuildDockerignoreCleanPaths(c *check.C) {
name := "testbuilddockerignorecleanpaths"
- defer deleteImages(name)
dockerfile := `
FROM busybox
ADD . /tmp/
@@ -3699,19 +3476,66 @@ 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) {
- name := "testbuilddockerignoredockerfile"
+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 := `
FROM busybox
ADD . /tmp/
RUN ! ls /tmp/Dockerfile
@@ -3721,26 +3545,24 @@ 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 := `
FROM busybox
ADD . /tmp/
@@ -3753,26 +3575,24 @@ 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 := `
FROM busybox
ADD . /tmp/
@@ -3784,20 +3604,18 @@ 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
name := "testbuilddockerignoretouchdockerfile"
- defer deleteImages(name)
dockerfile := `
FROM busybox
ADD . /tmp/`
@@ -3807,48 +3625,46 @@ 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 := `
FROM busybox
COPY . /
@@ -3857,21 +3673,20 @@ func TestBuildDockerignoringWholeDir(t *testing.T) {
ctx, err := fakeContext(dockerfile, map[string]string{
"Dockerfile": "FROM scratch",
"Makefile": "all:",
+ ".gitignore": "",
".dockerignore": ".*\n",
})
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,
`FROM busybox
RUN sh -c 'echo root:testpass \
@@ -3881,14 +3696,12 @@ 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,
`FROM busybox
RUN sh -c 'echo root:testpass > /tmp/passwd'
@@ -3898,14 +3711,12 @@ 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,
`FROM busybox
# This is an ordinary comment.
@@ -3918,14 +3729,12 @@ 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,
`FROM busybox
@@ -3982,14 +3791,12 @@ 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
ENV HOME /root
ENV PATH $HOME/bin:$PATH
@@ -4013,20 +3820,18 @@ 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
ENV abc=def
RUN [ "$abc" = "def" ]
@@ -4071,6 +3876,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
@@ -4096,20 +3922,18 @@ 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 := `
FROM busybox
ADD test /test
@@ -4120,20 +3944,18 @@ 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)
ctx := func() *FakeContext {
dockerfile := `
@@ -4154,7 +3976,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()
@@ -4164,32 +3986,30 @@ 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)
ctx := func() *FakeContext {
dockerfile := `
@@ -4199,7 +4019,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()
@@ -4209,23 +4029,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)
}()
@@ -4233,15 +4053,13 @@ 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)
ctx := func() *FakeContext {
dockerfile := `
@@ -4251,7 +4069,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()
@@ -4261,31 +4079,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)
}()
@@ -4293,15 +4111,13 @@ 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{
"Dockerfile": `FROM busybox
ADD first /first
@@ -4310,317 +4126,315 @@ 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) {
- name := "testbuildcmdcleanuponentrypoint"
+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"
if _, err := buildImage(name,
`FROM scratch
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)
+ if res != "" {
+ c.Fatalf("Cmd %s, expected nil", res)
}
+
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)
+ if expected := "{[cat]}"; 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,
`From scratch
ENTRYPOINT ["/bin/bash"]
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"
- defer deleteImages(childname)
-
_, 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) {
- name := "abcd:" + makeRandomString(200)
- defer deleteImages(name)
+func (s *DockerSuite) TestBuildInvalidTag(c *check.C) {
+ name := "abcd:" + stringutils.GenerateRandomAlphaOnlyString(200)
_, 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
name := "testbuildcmdspaces"
- defer deleteImages(name)
var id1 string
var id2 string
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) {
- defer deleteImages("parent", "child")
- defer deleteAllContainers()
+func (s *DockerSuite) TestBuildEntrypointInheritance(c *check.C) {
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"
expected = `["/bin/sh","-c","echo quux"]`
)
- 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,
`FROM busybox
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)
_, err := buildImage(name, `
FROM busybox
@@ -4642,21 +4456,18 @@ 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
// 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)
- defer deleteAllContainers()
_, err := buildImage(name,
`FROM busybox
@@ -4665,15 +4476,13 @@ 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)
_, out, err := buildImageWithOut(name,
`FROM busybox
@@ -4681,87 +4490,81 @@ 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)
_, err := buildImage(name,
`FROM busybox
LABEL Vendor=Acme
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)
id1, err := buildImage(name,
`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
@@ -4769,28 +4572,26 @@ 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"
- defer deleteImages(name)
_, _, stderr, err := buildImageWithStdoutStderr(name,
"FROM busybox\nRUN echo one", true)
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
if runtime.GOOS == "windows" {
@@ -4798,22 +4599,20 @@ 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)
ctx, err := fakeContext(`
FROM busybox
@@ -4824,46 +4623,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{
@@ -4883,19 +4681,17 @@ 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)
ctx, err := fakeContext(`
FROM busybox
@@ -4911,23 +4707,21 @@ 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"
)
- defer deleteImages(name)
ctx, err := fakeContext(`
FROM busybox
COPY content /foo/file
@@ -4937,27 +4731,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`,
@@ -4970,100 +4762,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)
+ if expected := fmt.Sprintf("The Dockerfile (%s) must be within the build context (.)", nonDockerfileFile); !strings.Contains(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
- defer deleteImages("test1")
+func (s *DockerSuite) TestBuildFromMixedcaseDockerfile(c *check.C) {
+ testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows
ctx, err := fakeContext(`FROM busybox
RUN echo from dockerfile`,
@@ -5072,24 +4862,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
- defer deleteImages("test1")
+func (s *DockerSuite) TestBuildWithTwoDockerfiles(c *check.C) {
+ testRequires(c, UnixCli) // Dockerfile overwrites dockerfile on windows
ctx, err := fakeContext(`FROM busybox
RUN echo from Dockerfile`,
@@ -5098,30 +4886,28 @@ 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) {
- defer deleteImages("test1")
+func (s *DockerSuite) TestBuildFromURLWithF(c *check.C) {
server, err := fakeStorage(map[string]string{"baz": `FROM busybox
RUN echo from baz
COPY * /tmp/
RUN find /tmp/`})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer server.Close()
@@ -5130,34 +4916,32 @@ 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) {
- defer deleteImages("test1")
+func (s *DockerSuite) TestBuildFromStdinWithF(c *check.C) {
ctx, err := fakeContext(`FROM busybox
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
@@ -5170,19 +4954,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",
@@ -5196,45 +4979,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{
@@ -5244,10 +5026,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)
}
@@ -5258,14 +5040,11 @@ 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 (
@@ -5274,23 +5053,22 @@ func TestBuildSpaces(t *testing.T) {
)
name := "testspaces"
- defer deleteImages(name)
ctx, err := fakeContext("FROM busybox\nCOPY\n",
map[string]string{
"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 {
@@ -5303,12 +5081,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
@@ -5317,12 +5095,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
@@ -5331,16 +5109,14 @@ 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)
dockerfile := `FROM busybox
RUN echo " \
@@ -5348,19 +5124,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 contain %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
@@ -5370,13 +5145,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.
@@ -5387,8 +5161,6 @@ func TestBuildMissingArgs(t *testing.T) {
"INSERT": {},
}
- defer deleteAllContainers()
-
for cmd := range command.Commands {
cmd = strings.ToUpper(cmd)
if _, ok := skipCmds[cmd]; ok {
@@ -5405,57 +5177,50 @@ 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) {
- defer deleteImages("sc")
+func (s *DockerSuite) TestBuildEmptyScratch(c *check.C) {
_, 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) {
- defer deleteImages("sc")
+func (s *DockerSuite) TestBuildDotDotFile(c *check.C) {
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()
- defer deleteImages("verbose")
+func (s *DockerSuite) TestBuildNotVerbose(c *check.C) {
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()
@@ -5464,10 +5229,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
@@ -5475,25 +5240,21 @@ 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)
-
ctx, err := fakeContext(`FROM hello-world:frozen
RUN [ "/hello" ]`, map[string]string{})
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
defer ctx.Close()
@@ -5501,90 +5262,80 @@ 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)
ctx, err := fakeContext(`
FROM hello-world:frozen
RUN ["/hello"]
`, map[string]string{})
if err != nil {
- t.Fatal(err)
+ c.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", "--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)
if err != nil {
- t.Fatal(err, out)
- }
- out, _, err = dockerCmd(t, "ps", "-lq")
- if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
+ out, _ = dockerCmd(c, "ps", "-lq")
- cID := stripTrailingCharacters(out)
+ 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
- CpuShares int
+ CpusetMems string
+ CpuShares int64
+ CpuQuota int64
}
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 != "1" || 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 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
- _, _, err = dockerCmd(t, "run", "--name=test", name)
- if err != nil {
- t.Fatal(err)
- }
+ _, _ = 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 == "1" || 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 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)
}
- logDone("build - resource constraints applied")
}
-func TestBuildEmptyStringVolume(t *testing.T) {
+func (s *DockerSuite) TestBuildEmptyStringVolume(c *check.C) {
name := "testbuildemptystringvolume"
- defer deleteImages(name)
_, err := buildImage(name, `
FROM busybox
@@ -5592,8 +5343,86 @@ 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")
+}
+
+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 {
+ c.Fatalf("failed to read '/proc/self/cgroup - %v", err)
+ }
+ selfCgroupPaths := parseCgroupPaths(string(data))
+ _, found := selfCgroupPaths["memory"]
+ if !found {
+ c.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 {
+ 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 := "\nUnknown flag: boo\n"
+ if !strings.Contains(out, exp) {
+ 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 a3`, false) // tab between a2 and a3
+ if err == nil {
+ c.Fatal("Should have failed to build")
+ }
+
+ exp := `The command '/bin/sh -c badEXE a1 \& a2 a3' 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/integration-cli/docker_cli_by_digest_test.go b/integration-cli/docker_cli_by_digest_test.go
index 24ebf0cf7..d4d494995 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,18 +22,17 @@ 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)
// delete the container as we don't need it any more
if err := deleteContainer(containerName); err != nil {
@@ -41,15 +40,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,473 +62,436 @@ func setupImageWithTag(tag string) (string, error) {
return pushDigest, nil
}
-func TestPullByTagDisplaysDigest(t *testing.T) {
- defer setupRegistry(t)()
-
+func (s *DockerRegistrySuite) TestPullByTagDisplaysDigest(c *check.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 *DockerRegistrySuite) TestPullByDigest(c *check.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 *DockerRegistrySuite) TestPullByDigestNoFallback(c *check.C) {
+ // pull from the registry using the @ reference
+ imageReference := fmt.Sprintf("%s@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", repoName)
+ cmd := exec.Command(dockerBinary, "pull", imageReference)
+ out, _, err := runCommandWithOutput(cmd)
+ if err == nil || !strings.Contains(out, "pulling with digest reference failed from v2 registry") {
+ c.Fatalf("expected non-zero exit status and correct error message when pulling non-existing image: %s", out)
+ }
+}
+func (s *DockerRegistrySuite) TestCreateByDigest(c *check.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 *DockerRegistrySuite) TestRunByDigest(c *check.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 *DockerRegistrySuite) TestRemoveImageByDigest(c *check.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 *DockerRegistrySuite) TestBuildByDigest(c *check.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
name := "buildbydigest"
- defer deleteImages(name)
_, err = buildImage(name, fmt.Sprintf(
`FROM %s
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 *DockerRegistrySuite) TestTagByDigest(c *check.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 *DockerRegistrySuite) TestListImagesWithoutDigests(c *check.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)()
- defer deleteImages(repoName+":tag1", repoName+":tag2")
+func (s *DockerRegistrySuite) TestListImagesWithDigests(c *check.C) {
// 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 *DockerRegistrySuite) TestDeleteImageByIDOnlyPulledByDigest(c *check.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 8d596bdda..ef157c63f 100644
--- a/integration-cli/docker_cli_commit_test.go
+++ b/integration-cli/docker_cli_commit_test.go
@@ -3,149 +3,127 @@ 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 := stripTrailingCharacters(out)
+ 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 := stripTrailingCharacters(out)
+ 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 := stripTrailingCharacters(out)
+ 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 := stripTrailingCharacters(out)
+ 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 := stripTrailingCharacters(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)
- }
- cleanedImageID := stripTrailingCharacters(out)
- defer deleteImages(cleanedImageID)
-
- 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 commit container to image: %s, %v", out, err)
}
+ out, err = inspectField(cleanedContainerID, "State.Paused")
+ c.Assert(err, check.IsNil)
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)
cmd = exec.Command(dockerBinary, "run", imageID, "cat", "/foo")
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,21 +136,20 @@ 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)
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,68 +162,60 @@ 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) {
- defer deleteImages("ttytest")
- defer deleteAllContainers()
+func (s *DockerSuite) TestCommitTTY(c *check.C) {
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")
- defer deleteImages(imageID)
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,25 +226,68 @@ 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)
expected := map[string]string{
- "Config.ExposedPorts": "map[8080/tcp:map[]]",
+ "Config.ExposedPorts": "map[8080/tcp:{}]",
"Config.Env": "[DEBUG=true test=1 PATH=/foo]",
}
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.Assert(err, check.IsNil)
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 (s *DockerSuite) TestCommitMergeConfigRun(c *check.C) {
+ name := "commit-test"
+ out, _ := dockerCmd(c, "run", "-d", "-e=FOO=bar", "busybox", "/bin/sh", "-c", "echo testing > /tmp/foo")
+ id := strings.TrimSpace(out)
+
+ dockerCmd(c, "commit", `--run={"Cmd": ["cat", "/tmp/foo"]}`, id, "commit-test")
+
+ out, _ = dockerCmd(c, "run", "--name", name, "commit-test")
+ if strings.TrimSpace(out) != "testing" {
+ c.Fatal("run config in committed container was not merged")
+ }
+
+ type cfg struct {
+ Env []string
+ Cmd []string
+ }
+ config1 := cfg{}
+ if err := inspectFieldAndMarshall(id, "Config", &config1); err != nil {
+ c.Fatal(err)
+ }
+ config2 := cfg{}
+ if err := inspectFieldAndMarshall(name, "Config", &config2); err != nil {
+ c.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 != "" {
+ c.Fatalf("expected envs to match: %v - %v", config1.Env, config2.Env)
+ }
+
}
diff --git a/integration-cli/docker_cli_config_test.go b/integration-cli/docker_cli_config_test.go
new file mode 100644
index 000000000..5ccd7af10
--- /dev/null
+++ b/integration-cli/docker_cli_config_test.go
@@ -0,0 +1,56 @@
+package main
+
+import (
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "os/exec"
+ "path/filepath"
+
+ "github.com/docker/docker/pkg/homedir"
+ "github.com/go-check/check"
+)
+
+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
+
+ 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 {
+ 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" {
+ c.Fatalf("Missing/bad header: %q\nout:%v", headers, out)
+ }
+}
diff --git a/integration-cli/docker_cli_cp_test.go b/integration-cli/docker_cli_cp_test.go
index db5f36388..fd63bdbf2 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,26 @@ 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)
+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 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ 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 +53,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,52 +61,47 @@ 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(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, 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)
+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 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ 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)
@@ -116,7 +111,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)
@@ -128,55 +123,50 @@ 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)
}
- _, _, 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(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, 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)
+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 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ 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)
@@ -186,7 +176,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)
@@ -194,53 +184,48 @@ 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(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, 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)
+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 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ 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)
@@ -250,7 +235,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)
@@ -258,53 +243,48 @@ 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(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, 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)
+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 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ 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)
@@ -314,7 +294,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)
@@ -322,237 +302,333 @@ 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(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, 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(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch "+cpTestName)
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ 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 TestCpVolumePath(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 {
+ c.Fatal(err)
+ }
+ defer os.RemoveAll(outDir)
+
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "touch /foo")
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
+ }
+
+ cleanedContainerID := strings.TrimSpace(out)
+
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out)
+ }
+
+ // Copy actual /etc/resolv.conf
+ _, _ = 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) {
+ c.Fatalf("Expected copied file to be duplicate of the container resolvconf")
+ }
+
+ // Copy actual /etc/hosts
+ _, _ = 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) {
+ c.Fatalf("Expected copied file to be duplicate of the container hosts")
+ }
+
+ // Copy actual /etc/resolv.conf
+ _, _ = 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) {
+ c.Fatalf("Expected copied file to be duplicate of the container resolvconf")
+ }
+
+}
+
+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, 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(c, "run", "-d", "-v", "/foo", "-v", tmpDir+"/test:/test", "-v", tmpDir+":/baz", "busybox", "/bin/sh", "-c", "touch /foo/bar")
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.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(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
- _, _, 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(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
- _, _, 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(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
- _, _, err = 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
- _, _, err = 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, 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)
+func (s *DockerSuite) TestCpToDot(c *check.C) {
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test")
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
}
- cleanedContainerID := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cleanedContainerID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatal("failed to set up container", out, err)
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ 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)
- }
- _, _, err = dockerCmd(t, "cp", cleanedContainerID+":/test", ".")
- if err != nil {
- t.Fatalf("couldn't docker cp to \".\" path: %s", err)
+ c.Fatal(err)
}
+ _, _ = 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, 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)
+func (s *DockerSuite) TestCpToStdout(c *check.C) {
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /test")
+ if exitCode != 0 {
+ c.Fatalf("failed to create a container:%s\n", out)
}
- cID := stripTrailingCharacters(out)
- defer deleteContainer(cID)
+ cID := strings.TrimSpace(out)
- out, _, err = dockerCmd(t, "wait", cID)
- if err != nil || stripTrailingCharacters(out) != "0" {
- t.Fatalf("failed to set up container:%s\n%s", out, err)
+ out, _ = dockerCmd(c, "wait", cID)
+ if strings.TrimSpace(out) != "0" {
+ c.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)
+ 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)
+ }
+}
+
+func (s *DockerSuite) TestCpNameHasColon(c *check.C) {
+ testRequires(c, SameHostDaemon)
+
+ out, exitCode := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "echo lololol > /te:s:t")
+ if exitCode != 0 {
+ c.Fatal("failed to create a container", out)
+ }
+
+ cleanedContainerID := strings.TrimSpace(out)
+
+ out, _ = dockerCmd(c, "wait", cleanedContainerID)
+ if strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out)
+ }
+
+ tmpdir, err := ioutil.TempDir("", "docker-integration")
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+ _, _ = dockerCmd(c, "cp", cleanedContainerID+":/te:s:t", tmpdir)
+ content, err := ioutil.ReadFile(tmpdir + "/te:s:t")
+ if string(content) != "lololol\n" {
+ c.Fatalf("Wrong content in copied file %q, should be %q", content, "lololol\n")
+ }
+}
+
+func (s *DockerSuite) TestCopyAndRestart(c *check.C) {
+ expectedMsg := "hello"
+ out, err := exec.Command(dockerBinary, "run", "-d", "busybox", "echo", expectedMsg).CombinedOutput()
+ if err != nil {
+ c.Fatal(string(out), err)
+ }
+ id := strings.TrimSpace(string(out))
+
+ if out, err = exec.Command(dockerBinary, "wait", id).CombinedOutput(); err != nil {
+ c.Fatalf("unable to wait for container: %s", err)
+ }
+
+ status := strings.TrimSpace(string(out))
+ if status != "0" {
+ c.Fatalf("container exited with status %s", status)
+ }
+
+ tmpDir, err := ioutil.TempDir("", "test-docker-restart-after-copy-")
+ if err != nil {
+ c.Fatalf("unable to make temporary directory: %s", err)
+ }
+ defer os.RemoveAll(tmpDir)
+
+ if _, err = exec.Command(dockerBinary, "cp", fmt.Sprintf("%s:/etc/issue", id), tmpDir).CombinedOutput(); err != nil {
+ c.Fatalf("unable to copy from busybox container: %s", err)
+ }
+
+ if out, err = exec.Command(dockerBinary, "start", "-a", id).CombinedOutput(); err != nil {
+ c.Fatalf("unable to start busybox container after copy: %s - %s", err, out)
+ }
+
+ msg := strings.TrimSpace(string(out))
+ if msg != expectedMsg {
+ c.Fatalf("expected %q but got %q", expectedMsg, msg)
}
- logDone("cp - to stdout")
}
diff --git a/integration-cli/docker_cli_create_test.go b/integration-cli/docker_cli_create_test.go
index e32400e60..019ea97fc 100644
--- a/integration-cli/docker_cli_create_test.go
+++ b/integration-cli/docker_cli_create_test.go
@@ -2,31 +2,31 @@ package main
import (
"encoding/json"
+ "fmt"
"os"
"os/exec"
"reflect"
- "testing"
+ "strings"
"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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
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 {
@@ -37,48 +37,46 @@ 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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
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 {
@@ -87,39 +85,37 @@ 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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
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 {
@@ -128,47 +124,45 @@ 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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
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 {
@@ -177,130 +171,173 @@ 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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
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,
`FROM busybox
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)
+ }
+}
+
+func (s *DockerSuite) TestCreateHostnameWithNumber(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-h", "web.0", "busybox", "hostname")
+ if strings.TrimSpace(out) != "web.0" {
+ c.Fatalf("hostname not set, expected `web.0`, got: %s", out)
+ }
+}
+
+func (s *DockerSuite) TestCreateRM(c *check.C) {
+ // Test to make sure we can 'rm' a new container that is in
+ // "Created" state, and has ever been run. Test "rm -f" too.
+
+ // create a container
+ createCmd := exec.Command(dockerBinary, "create", "busybox")
+ out, _, err := runCommandWithOutput(createCmd)
+ if err != nil {
+ c.Fatalf("Failed to create container:%s\n%s", out, err)
+ }
+ cID := strings.TrimSpace(out)
+
+ rmCmd := exec.Command(dockerBinary, "rm", cID)
+ out, _, err = runCommandWithOutput(rmCmd)
+ if err != nil {
+ c.Fatalf("Failed to rm container:%s\n%s", out, err)
}
- deleteAllContainers()
+ // Now do it again so we can "rm -f" this time
+ createCmd = exec.Command(dockerBinary, "create", "busybox")
+ out, _, err = runCommandWithOutput(createCmd)
+ if err != nil {
+ c.Fatalf("Failed to create 2nd container:%s\n%s", out, err)
+ }
- logDone("create - labels from image")
+ cID = strings.TrimSpace(out)
+ rmCmd = exec.Command(dockerBinary, "rm", "-f", cID)
+ out, _, err = runCommandWithOutput(rmCmd)
+ if err != nil {
+ c.Fatalf("Failed to rm -f container:%s\n%s", out, err)
+ }
+}
+
+func (s *DockerSuite) TestCreateModeIpcContainer(c *check.C) {
+ testRequires(c, SameHostDaemon)
+
+ cmd := exec.Command(dockerBinary, "create", "busybox")
+ out, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out)
+ }
+ id := strings.TrimSpace(out)
+
+ cmd = exec.Command(dockerBinary, "create", fmt.Sprintf("--ipc=container:%s", id), "busybox")
+ out, _, err = runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatalf("Create container with ipc mode container should success with non running container: %s\n%s", out, err)
+ }
}
diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go
index c515a6378..0c1eaa768 100644
--- a/integration-cli/docker_cli_daemon_test.go
+++ b/integration-cli/docker_cli_daemon_test.go
@@ -6,148 +6,127 @@ import (
"encoding/json"
"fmt"
"io/ioutil"
+ "net"
"os"
"os/exec"
"path/filepath"
+ "regexp"
+ "strconv"
"strings"
- "testing"
"time"
+ "github.com/docker/libnetwork/iptables"
"github.com/docker/libtrust"
+ "github.com/go-check/check"
)
-func TestDaemonRestartWithRunningContainersPorts(t *testing.T) {
- d := NewDaemon(t)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatalf("Could not start daemon with busybox: %v", err)
+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 {
- t.Fatalf("Could not run top1: err=%v\n%s", err, out)
+ 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 {
- t.Fatalf("Could not run top2: err=%v\n%s", err, out)
+ 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 c, shouldRun := range m {
- out, err := d.Cmd("ps")
+ for cont, shouldRun := range m {
+ out, err := s.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)
}
}
}
testRun(map[string]bool{"top1": true, "top2": true}, "")
- if err := d.Restart(); err != nil {
- t.Fatalf("Could not restart daemon: %v", err)
+ 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: ")
-
- logDone("daemon - running containers on daemon restart")
}
-func TestDaemonRestartWithVolumesRefs(t *testing.T) {
- d := NewDaemon(t)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatal(err)
+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 {
- t.Fatal(err, out)
+ 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 {
- t.Fatal(err)
+ 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 {
- t.Fatal(err)
+ 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 {
- t.Fatal(err, out)
+ 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 {
- 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)
- 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)
+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()
-
- 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 *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 {
- t.Fatalf("Could not start daemon: %v", err)
+ if err := s.d.Start(); err != nil {
+ c.Fatalf("Could not start daemon: %v", err)
}
- if err := d.Stop(); err != nil {
- t.Fatalf("Could not stop daemon: %v", err)
+ if err := s.d.Stop(); err != nil {
+ 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 {
+ if err := s.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)
- }
-
- logDone("daemon - successful daemon start when bridge has no IP association")
}
-func TestDaemonIptablesClean(t *testing.T) {
- defer deleteAllContainers()
-
- d := NewDaemon(t)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatalf("Could not start daemon with busybox: %v", err)
+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 {
- t.Fatalf("Could not run top: %s, %v", out, err)
+ 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)
}
// get output from iptables with container running
@@ -155,42 +134,36 @@ 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)
+ if err := s.d.Stop(); err != nil {
+ 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()
-
- d := NewDaemon(t)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatalf("Could not start daemon with busybox: %v", err)
+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 {
- t.Fatalf("Could not run top: %s, %v", out, err)
+ 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)
}
// get output from iptables with container running
@@ -198,101 +171,94 @@ 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)
+ 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 {
- 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 *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 {
- t.Fatal("Daemon should not have been able to start")
+func (s *DockerDaemonSuite) TestDaemonLogLevelDebug(c *check.C) {
+ if err := s.d.Start("--log-level=debug"); err != nil {
+ c.Fatal(err)
}
-
- d = NewDaemon(t)
- if err := d.Start("--log-level=debug"); err != nil {
- t.Fatal(err)
- }
- d.Stop()
- content, _ := ioutil.ReadFile(d.logFile.Name())
+ content, _ := ioutil.ReadFile(s.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)
- if err := d.Start("--log-level=fatal"); err != nil {
- t.Fatal(err)
+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`) {
- 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)
- if err := d.Start("-D"); err != nil {
- t.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))
- }
-
- d = NewDaemon(t)
- if err := d.Start("--debug"); err != nil {
- t.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))
- }
-
- d = NewDaemon(t)
- if err := d.Start("--debug", "--log-level=fatal"); err != nil {
- t.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))
- }
-
- logDone("daemon - Logging Level")
}
-func TestDaemonAllocatesListeningPort(t *testing.T) {
+func (s *DockerDaemonSuite) TestDaemonFlagD(c *check.C) {
+ if err := s.d.Start("-D"); err != nil {
+ c.Fatal(err)
+ }
+ content, _ := ioutil.ReadFile(s.d.logFile.Name())
+ if !strings.Contains(string(content), `level=debug`) {
+ c.Fatalf(`Should have level="debug" in log file using -D:\n%s`, string(content))
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonFlagDebug(c *check.C) {
+ if err := s.d.Start("--debug"); err != nil {
+ c.Fatal(err)
+ }
+ content, _ := ioutil.ReadFile(s.d.logFile.Name())
+ if !strings.Contains(string(content), `level=debug`) {
+ c.Fatalf(`Should have level="debug" in log file using --debug:\n%s`, string(content))
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonFlagDebugLogLevelFatal(c *check.C) {
+ if err := s.d.Start("--debug", "--log-level=fatal"); err != nil {
+ c.Fatal(err)
+ }
+ content, _ := ioutil.ReadFile(s.d.logFile.Name())
+ if !strings.Contains(string(content), `level=debug`) {
+ c.Fatalf(`Should have level="debug" in log file when using both --debug and --log-level=fatal:\n%s`, string(content))
+ }
+}
+
+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"},
@@ -304,527 +270,997 @@ func TestDaemonAllocatesListeningPort(t *testing.T) {
cmdArgs = append(cmdArgs, "--host", fmt.Sprintf("tcp://%s:%s", hostDirective[0], hostDirective[2]))
}
- d := NewDaemon(t)
- if err := d.StartWithBusybox(cmdArgs...); err != nil {
- t.Fatalf("Could not start daemon with busybox: %v", err)
+ 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 {
- 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)
-
- if err := d.StartWithBusybox(); err != nil {
- t.Fatal(err)
- }
-
- tmp, err := ioutil.TempDir(os.TempDir(), "")
- if err != nil {
- t.Fatal(err)
- }
- defer os.RemoveAll(tmp)
-
- if err := ioutil.WriteFile(tmp+"/test", []byte("testing"), 0655); err != nil {
- t.Fatal(err)
- }
-
- if out, err := d.Cmd("create", "-v", tmp+":/foo", "--name=voltest", "busybox"); err != nil {
- t.Fatal(err, out)
- }
-
- if err := d.Restart(); err != nil {
- t.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)
- }
-
- logDone("daemon - bind refs in data-containers survive daemon restart")
-}
-
-func TestDaemonKeyGeneration(t *testing.T) {
+func (s *DockerDaemonSuite) TestDaemonKeyGeneration(c *check.C) {
// TODO: skip or update for Windows daemon
os.Remove("/etc/docker/key.json")
- d := NewDaemon(t)
- if err := d.Start(); err != nil {
- t.Fatalf("Could not start daemon: %v", err)
+ 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 {
- 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 *DockerDaemonSuite) 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)
- if err := d.Start(); err != nil {
- t.Fatalf("Could not start daemon: %v", err)
+ 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 {
- 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)
-
- graphDir := filepath.Join(os.TempDir(), "docker-test")
- defer os.RemoveAll(graphDir)
- if err := d.StartWithBusybox("-g", graphDir); err != nil {
- t.Fatal(err)
- }
-
- tmpDir := filepath.Join(os.TempDir(), "test")
- defer os.RemoveAll(tmpDir)
-
- if out, err := d.Cmd("create", "-v", tmpDir+":/foo", "--name=test", "busybox"); err != nil {
- t.Fatal(err, out)
- }
-
- if err := d.Stop(); err != nil {
- t.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)
- }
-
- configDir := filepath.Join(graphDir, "volumes")
-
- if err := os.RemoveAll(configDir); err != nil {
- t.Fatal(err)
- }
-
- if err := d.Start("-g", graphDir); err != nil {
- t.Fatal(err)
- }
-
- if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
- t.Fatalf("expected volume path %s to exist but it does not", tmpDir)
- }
-
- dir, err := ioutil.ReadDir(configDir)
- if err != nil {
- t.Fatal(err)
- }
- if len(dir) == 0 {
- t.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)
- }
-
- if err := os.RemoveAll(configDir); err != nil {
- t.Fatal(err)
- }
-
- if err := d.Start("-g", graphDir); err != nil {
- t.Fatal(err)
- }
-
- dir, err = ioutil.ReadDir(configDir)
- if err != nil {
- t.Fatal(err)
- }
-
- if len(dir) == 0 {
- t.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)
- 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") {
- 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", "Shutting down daemon due to", d.LogfileName())
+ runCmd := exec.Command("grep", "Error starting daemon", s.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)
+ 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 *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()
- if err := d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil {
- t.Fatal(err)
+ bridgeName := "external-bridge"
+ bridgeIp := "192.169.1.1/24"
+ _, bridgeIPNet, _ := net.ParseCIDR(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)
+ 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("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 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)
+ if err != nil {
+ return out, err
}
- out, err := d.Cmd("run", "--ulimit", "nproc=2048", "--name=test", "busybox", "/bin/sh", "-c", "echo $(ulimit -n); echo $(ulimit -p)")
+ ifCfgCmd := exec.Command("ifconfig", ifName, ipNet, "up")
+ out, _, err = runCommandWithOutput(ifCfgCmd)
+ return out, err
+}
+
+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))
+
+ flushCmd := exec.Command("iptables", "-t", "nat", "--flush")
+ 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) {
+ // 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 (via defered action)
+ // This Restart takes care of bringing docker0 interface back to auto-assigned IP
+
+ defaultNetworkBridge := "docker0"
+ deleteInterface(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)
+ defer d.Restart()
+
+ 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("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))
+ deleteInterface(c, defaultNetworkBridge)
+}
+
+func (s *DockerDaemonSuite) TestDaemonRestartWithBridgeIPChange(c *check.C) {
+ if err := s.d.Start(); err != nil {
+ c.Fatalf("Could not start daemon: %v", err)
+ }
+ defer s.d.Restart()
+ if err := s.d.Stop(); err != nil {
+ c.Fatalf("Could not stop daemon: %v", err)
+ }
+
+ // now we will change the docker0's IP and then try starting the daemon
+ bridgeIP := "192.169.100.1/24"
+ _, bridgeIPNet, _ := net.ParseCIDR(bridgeIP)
+
+ ipCmd := exec.Command("ifconfig", "docker0", bridgeIP)
+ stdout, stderr, _, err := runCommandWithStdoutStderr(ipCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatalf("failed to change docker0's IP association: %v, stdout: %q, stderr: %q", err, stdout, stderr)
+ }
+
+ if err := s.d.Start("--bip", bridgeIP); err != nil {
+ c.Fatalf("Could not start daemon: %v", err)
+ }
+
+ //check if the iptables contains new bridgeIP MASQUERADE rule
+ ipTablesSearchString := bridgeIPNet.String()
+ ipTablesCmd := exec.Command("iptables", "-t", "nat", "-nvL")
+ out, _, err := runCommandWithOutput(ipTablesCmd)
+ if err != nil {
+ c.Fatalf("Could not run iptables -nvL: %s, %v", out, err)
+ }
+ if !strings.Contains(out, ipTablesSearchString) {
+ c.Fatalf("iptables output should have contained new MASQUERADE rule with IP %q, but was %q", ipTablesSearchString, out)
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonBridgeFixedCidr(c *check.C) {
+ d := s.d
+
+ bridgeName := "external-bridge"
+ bridgeIp := "192.169.1.1/24"
+
+ 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...)
+ c.Assert(err, check.IsNil)
+ defer d.Restart()
+
+ 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))
+ }
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonDefaultGatewayIPv4Implicit(c *check.C) {
+ defaultNetworkBridge := "docker0"
+ deleteInterface(c, defaultNetworkBridge)
+
+ d := s.d
+
+ bridgeIp := "192.169.1.1"
+ bridgeIpNet := fmt.Sprintf("%s/24", bridgeIp)
+
+ err := d.StartWithBusybox("--bip", bridgeIpNet)
+ c.Assert(err, check.IsNil)
+ defer d.Restart()
+
+ expectedMessage := fmt.Sprintf("default via %s dev", bridgeIp)
+ out, err := d.Cmd("run", "busybox", "ip", "-4", "route", "list", "0/0")
+ c.Assert(strings.Contains(out, expectedMessage), check.Equals, true,
+ check.Commentf("Implicit default gateway should be bridge IP %s, but default route was '%s'",
+ bridgeIp, strings.TrimSpace(out)))
+ deleteInterface(c, defaultNetworkBridge)
+}
+
+func (s *DockerDaemonSuite) TestDaemonDefaultGatewayIPv4Explicit(c *check.C) {
+ defaultNetworkBridge := "docker0"
+ deleteInterface(c, defaultNetworkBridge)
+
+ d := s.d
+
+ bridgeIp := "192.169.1.1"
+ bridgeIpNet := fmt.Sprintf("%s/24", bridgeIp)
+ gatewayIp := "192.169.1.254"
+
+ err := d.StartWithBusybox("--bip", bridgeIpNet, "--default-gateway", gatewayIp)
+ c.Assert(err, check.IsNil)
+ defer d.Restart()
+
+ expectedMessage := fmt.Sprintf("default via %s dev", gatewayIp)
+ out, err := d.Cmd("run", "busybox", "ip", "-4", "route", "list", "0/0")
+ c.Assert(strings.Contains(out, expectedMessage), check.Equals, true,
+ check.Commentf("Explicit default gateway should be %s, but default route was '%s'",
+ gatewayIp, strings.TrimSpace(out)))
+ deleteInterface(c, defaultNetworkBridge)
+}
+
+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)
+ defer d.Restart()
+
+ out, err := d.Cmd("run", "-d", "-p", "8000:8000", "busybox", "top")
+ 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"
+ 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)
+
+ 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))
+}
+
+func (s *DockerDaemonSuite) TestDaemonICCPing(c *check.C) {
+ d := s.d
+
+ bridgeName := "external-bridge"
+ bridgeIp := "192.169.1.1/24"
+
+ 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...)
+ c.Assert(err, check.IsNil)
+ defer d.Restart()
+
+ 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)
+}
+
+func (s *DockerDaemonSuite) TestDaemonICCLinkExpose(c *check.C) {
+ d := s.d
+
+ bridgeName := "external-bridge"
+ bridgeIp := "192.169.1.1/24"
+
+ 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...)
+ c.Assert(err, check.IsNil)
+ defer d.Restart()
+
+ 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))
+
+ out, err = d.Cmd("run", "-d", "--expose", "4567", "--name", "icc1", "busybox", "nc", "-l", "-p", "4567")
+ c.Assert(err, check.IsNil, check.Commentf(out))
+
+ out, err = d.Cmd("run", "--link", "icc1:icc1", "busybox", "nc", "icc1", "4567")
+ c.Assert(err, check.IsNil, check.Commentf(out))
+}
+
+func (s *DockerDaemonSuite) TestDaemonLinksIpTablesRulesWhenLinkAndUnlink(c *check.C) {
+ bridgeName := "external-bridge"
+ bridgeIp := "192.169.1.1/24"
+
+ 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 = s.d.StartWithBusybox(args...)
+ c.Assert(err, check.IsNil)
+ defer s.d.Restart()
+
+ _, err = s.d.Cmd("run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "top")
+ c.Assert(err, check.IsNil)
+ _, err = s.d.Cmd("run", "-d", "--name", "parent", "--link", "child:http", "busybox", "top")
+ c.Assert(err, check.IsNil)
+
+ childIP := s.d.findContainerIP("child")
+ parentIP := s.d.findContainerIP("parent")
+
+ sourceRule := []string{"-i", bridgeName, "-o", bridgeName, "-p", "tcp", "-s", childIP, "--sport", "80", "-d", parentIP, "-j", "ACCEPT"}
+ destinationRule := []string{"-i", bridgeName, "-o", bridgeName, "-p", "tcp", "-s", parentIP, "--dport", "80", "-d", childIP, "-j", "ACCEPT"}
+ if !iptables.Exists("filter", "DOCKER", sourceRule...) || !iptables.Exists("filter", "DOCKER", destinationRule...) {
+ c.Fatal("Iptables rules not found")
+ }
+
+ s.d.Cmd("rm", "--link", "parent/http")
+ if iptables.Exists("filter", "DOCKER", sourceRule...) || iptables.Exists("filter", "DOCKER", destinationRule...) {
+ c.Fatal("Iptables rules should be removed when unlink")
+ }
+
+ s.d.Cmd("kill", "child")
+ s.d.Cmd("kill", "parent")
+}
+
+func (s *DockerDaemonSuite) TestDaemonUlimitDefaults(c *check.C) {
+ testRequires(c, NativeExecDriver)
+
+ if err := s.d.StartWithBusybox("--default-ulimit", "nofile=42:42", "--default-ulimit", "nproc=1024:1024"); err != nil {
+ c.Fatal(err)
+ }
+
+ 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)
}
outArr := strings.Split(out, "\n")
if len(outArr) < 2 {
- t.Fatal("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)
+ 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 {
- t.Fatal(err)
+ c.Fatal(err)
}
outArr = strings.Split(out, "\n")
if len(outArr) < 2 {
- t.Fatal("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)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatal(err)
+func (s *DockerDaemonSuite) TestDaemonRestartRenameContainer(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
}
- if out, err := d.Cmd("run", "--name=test", "busybox"); err != nil {
- t.Fatal(err, out)
+ if out, err := s.d.Cmd("run", "--name=test", "busybox"); err != nil {
+ c.Fatal(err, out)
}
- if out, err := d.Cmd("rename", "test", "test2"); err != nil {
- t.Fatal(err, out)
+ if out, err := s.d.Cmd("rename", "test", "test2"); err != nil {
+ c.Fatal(err, out)
}
- if err := d.Restart(); err != nil {
- t.Fatal(err)
+ if err := s.d.Restart(); err != nil {
+ c.Fatal(err)
}
- if out, err := d.Cmd("start", "test2"); err != nil {
- t.Fatal(err, out)
+ if out, err := s.d.Cmd("start", "test2"); err != nil {
+ c.Fatal(err, out)
}
-
- logDone("daemon - rename persists through daemon restart")
}
-func TestDaemonLoggingDriverDefault(t *testing.T) {
- d := NewDaemon(t)
-
- if err := d.StartWithBusybox(); err != nil {
- t.Fatal(err)
+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 {
- 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)
+ 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 {
- 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`
- 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)
+ 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)
-
- if err := d.StartWithBusybox(); err != nil {
- t.Fatal(err)
+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 {
- 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)
+ 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) {
- 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)
-
- if err := d.StartWithBusybox("--log-driver=none"); err != nil {
- t.Fatal(err)
+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 {
- 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)
+ 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) {
- 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)
-
- if err := d.StartWithBusybox("--log-driver=none"); err != nil {
- t.Fatal(err)
+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 {
- 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)
+ 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 {
- 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`
- 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)
+ 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)
-
- if err := d.StartWithBusybox("--log-driver=none"); err != nil {
- t.Fatal(err)
+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 {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
id := strings.TrimSpace(out)
- out, err = d.Cmd("logs", id)
+ out, err = s.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)
+ if !strings.Contains(out, `"logs" command is supported only for "json-file" logging driver`) {
+ 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)
- if err := d.StartWithBusybox(); err != nil {
- t.Fatal(err)
+func (s *DockerDaemonSuite) TestDaemonDots(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
}
// Now create 4 containers
- if _, err := d.Cmd("create", "busybox"); err != nil {
- t.Fatalf("Error creating container: %q", err)
+ if _, err := s.d.Cmd("create", "busybox"); err != nil {
+ c.Fatalf("Error creating container: %q", err)
}
- if _, err := d.Cmd("create", "busybox"); err != nil {
- t.Fatalf("Error creating container: %q", err)
+ if _, err := s.d.Cmd("create", "busybox"); err != nil {
+ c.Fatalf("Error creating container: %q", err)
}
- if _, err := d.Cmd("create", "busybox"); err != nil {
- t.Fatalf("Error creating container: %q", err)
+ if _, err := s.d.Cmd("create", "busybox"); err != nil {
+ c.Fatalf("Error creating container: %q", err)
}
- if _, err := d.Cmd("create", "busybox"); err != nil {
- t.Fatalf("Error creating container: %q", err)
+ 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), "....") {
- 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())
+ s.d.Start("--log-level=error")
+ s.d.Stop()
+ content, _ = ioutil.ReadFile(s.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())
+ s.d.Start("--log-level=info")
+ s.d.Stop()
+ content, _ = ioutil.ReadFile(s.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 *DockerDaemonSuite) TestDaemonUnixSockCleanedUp(c *check.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)
+ if err := s.d.Start("--host", "unix://"+sockPath); err != nil {
+ c.Fatal(err)
}
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)
+ if err := s.d.Stop(); err != nil {
+ 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")
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonWithWrongkey(c *check.C) {
+ 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"`
}
- logDone("daemon - unix socket is cleaned up")
+ os.Remove("/etc/docker/key.json")
+ if err := s.d.Start(); err != nil {
+ c.Fatalf("Failed to start daemon: %v", err)
+ }
+
+ if err := s.d.Stop(); err != nil {
+ c.Fatalf("Could not stop daemon: %v", err)
+ }
+
+ config := &Config{}
+ bytes, err := ioutil.ReadFile("/etc/docker/key.json")
+ if err != nil {
+ c.Fatalf("Error reading key.json file: %s", err)
+ }
+
+ // byte[] to Data-Struct
+ if err := json.Unmarshal(bytes, &config); err != nil {
+ c.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 {
+ c.Fatalf("Error Marshal: %s", err)
+ }
+
+ // write back
+ if err := ioutil.WriteFile("/etc/docker/key.json", newBytes, 0400); err != nil {
+ c.Fatalf("Error ioutil.WriteFile: %s", err)
+ }
+
+ defer os.Remove("/etc/docker/key.json")
+
+ 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(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 *DockerDaemonSuite) TestDaemonRestartKillWait(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatalf("Could not start daemon with busybox: %v", err)
+ }
+
+ 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 := s.d.Cmd("kill", containerID); err != nil {
+ c.Fatalf("Could not kill %s: err=%v\n%s", containerID, err, out)
+ }
+
+ if err := s.d.Restart(); err != nil {
+ c.Fatalf("Could not restart daemon: %v", err)
+ }
+
+ errchan := make(chan error)
+ go func() {
+ if out, err := s.d.Cmd("wait", containerID); err != nil {
+ errchan <- fmt.Errorf("%v:\n%s", err, out)
+ }
+ close(errchan)
+ }()
+
+ select {
+ case <-time.After(5 * time.Second):
+ c.Fatal("Waiting on a stopped (killed) container timed out")
+ case err := <-errchan:
+ if err != nil {
+ c.Fatal(err)
+ }
+ }
+}
+
+// TestHttpsInfo connects via two-way authenticated HTTPS to the info endpoint
+func (s *DockerDaemonSuite) TestHttpsInfo(c *check.C) {
+ const (
+ testDaemonHttpsAddr = "localhost:4271"
+ )
+
+ 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)
+ }
+
+ //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 := s.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 *DockerDaemonSuite) TestHttpsInfoRogueCert(c *check.C) {
+ const (
+ errBadCertificate = "remote error: bad certificate"
+ testDaemonHttpsAddr = "localhost:4271"
+ )
+ 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)
+ }
+
+ //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 := 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)
+ }
+}
+
+// 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 *DockerDaemonSuite) TestHttpsInfoRogueServerCert(c *check.C) {
+ const (
+ errCaUnknown = "x509: certificate signed by unknown authority"
+ testDaemonRogueHttpsAddr = "localhost:4272"
+ )
+ 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)
+ }
+
+ //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 := 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)
+ }
+}
+
+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)
+
+ 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...))
+
+ if expectFailure {
+ c.Assert(err, check.NotNil)
+ } else {
+ c.Assert(err, check.IsNil)
+ }
+
+ args = append(dargs, "rm", "-f", "container1")
+ runCommand(exec.Command(dockerBinary, args...))
+}
+
+func (s *DockerDaemonSuite) TestDaemonRestartWithSocketAsVolume(c *check.C) {
+ c.Assert(s.d.StartWithBusybox(), check.IsNil)
+
+ socket := filepath.Join(s.d.folder, "docker.sock")
+
+ out, err := s.d.Cmd("run", "-d", "-v", socket+":/sock", "busybox")
+ c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
+ c.Assert(s.d.Restart(), check.IsNil)
+}
+
+func (s *DockerDaemonSuite) TestCleanupMountsAfterCrash(c *check.C) {
+ c.Assert(s.d.StartWithBusybox(), check.IsNil)
+
+ out, err := s.d.Cmd("run", "-d", "busybox", "top")
+ c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
+ id := strings.TrimSpace(out)
+ c.Assert(s.d.cmd.Process.Signal(os.Kill), check.IsNil)
+ c.Assert(s.d.Start(), check.IsNil)
+ mountOut, err := exec.Command("mount").CombinedOutput()
+ c.Assert(err, check.IsNil, check.Commentf("Output: %s", mountOut))
+ c.Assert(strings.Contains(string(mountOut), id), check.Equals, false, check.Commentf("Something mounted from older daemon start: %s", mountOut))
+}
+
+func (s *DockerDaemonSuite) TestRunContainerWithBridgeNone(c *check.C) {
+ testRequires(c, NativeExecDriver)
+ c.Assert(s.d.StartWithBusybox("-b", "none"), check.IsNil)
+
+ out, err := s.d.Cmd("run", "--rm", "busybox", "ip", "l")
+ c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
+ c.Assert(strings.Contains(out, "eth0"), check.Equals, false,
+ check.Commentf("There shouldn't be eth0 in container when network is disabled: %s", out))
+}
+
+func (s *DockerDaemonSuite) TestDaemonRestartWithContainerRunning(t *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ t.Fatal(err)
+ }
+ if out, err := s.d.Cmd("run", "-ti", "-d", "--name", "test", "busybox"); err != nil {
+ t.Fatal(out, err)
+ }
+
+ if err := s.d.Restart(); err != nil {
+ t.Fatal(err)
+ }
+ // Container 'test' should be removed without error
+ if out, err := s.d.Cmd("rm", "test"); err != nil {
+ t.Fatal(out, err)
+ }
+}
+
+func (s *DockerDaemonSuite) TestDaemonRestartCleanupNetns(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
+ }
+ out, err := s.d.Cmd("run", "--name", "netns", "-d", "busybox", "top")
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ if out, err := s.d.Cmd("stop", "netns"); err != nil {
+ c.Fatal(out, err)
+ }
+
+ // Construct netns file name from container id
+ out = strings.TrimSpace(out)
+ nsFile := out[:12]
+
+ // Test if the file still exists
+ out, _, err = runCommandWithOutput(exec.Command("stat", "-c", "%n", "/var/run/docker/netns/"+nsFile))
+ out = strings.TrimSpace(out)
+ c.Assert(err, check.IsNil, check.Commentf("Output: %s", out))
+ c.Assert(out, check.Equals, "/var/run/docker/netns/"+nsFile, check.Commentf("Output: %s", out))
+
+ // Remove the container and restart the daemon
+ if out, err := s.d.Cmd("rm", "netns"); err != nil {
+ c.Fatal(out, err)
+ }
+
+ if err := s.d.Restart(); err != nil {
+ c.Fatal(err)
+ }
+
+ // Test again and see now the netns file does not exist
+ out, _, err = runCommandWithOutput(exec.Command("stat", "-c", "%n", "/var/run/docker/netns/"+nsFile))
+ out = strings.TrimSpace(out)
+ c.Assert(err, check.Not(check.IsNil), check.Commentf("Output: %s", out))
+ // c.Assert(out, check.Equals, "", check.Commentf("Output: %s", out))
}
diff --git a/integration-cli/docker_cli_diff_test.go b/integration-cli/docker_cli_diff_test.go
index 36881a572..725b76286 100644
--- a/integration-cli/docker_cli_diff_test.go
+++ b/integration-cli/docker_cli_diff_test.go
@@ -3,24 +3,25 @@ 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 := stripTrailingCharacters(out)
+ cleanCID := strings.TrimSpace(out)
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,62 +32,56 @@ 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"}
+ 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 {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- cleanCID := stripTrailingCharacters(out)
+ cleanCID := strings.TrimSpace(out)
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 := stripTrailingCharacters(out)
+ cleanCID := strings.TrimSpace(out)
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 +104,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 a74ce15fb..02767c29a 100644
--- a/integration-cli/docker_cli_events_test.go
+++ b/integration-cli/docker_cli_events_test.go
@@ -1,25 +1,63 @@
package main
import (
+ "bufio"
"fmt"
"os/exec"
"regexp"
"strconv"
"strings"
- "testing"
+ "sync"
"time"
+
+ "github.com/go-check/check"
)
-func TestEventsUntag(t *testing.T) {
+func (s *DockerSuite) TestEventsTimestampFormats(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")
+
+ // Start stopwatch, generate an event
+ time.Sleep(time.Second) // so that we don't grab events from previous test occured in the same second
+ start := daemonTime(c)
+ time.Sleep(time.Second) // remote API precision is only a second, wait a while before creating an event
+ dockerCmd(c, "tag", image, "timestamptest:1")
+ dockerCmd(c, "rmi", "timestamptest:1")
+ time.Sleep(time.Second) // so that until > since
+ end := daemonTime(c)
+
+ // List of available time formats to --since
+ unixTs := func(t time.Time) string { return fmt.Sprintf("%v", t.Unix()) }
+ rfc3339 := func(t time.Time) string { return t.Format(time.RFC3339) }
+
+ // --since=$start must contain only the 'untag' event
+ for _, f := range []func(time.Time) string{unixTs, rfc3339} {
+ since, until := f(start), f(end)
+ cmd := exec.Command(dockerBinary, "events", "--since="+since, "--until="+until)
+ out, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatalf("docker events cmd failed: %v\nout=%s", err, out)
+ }
+ events := strings.Split(strings.TrimSpace(out), "\n")
+ if len(events) != 2 {
+ c.Fatalf("unexpected events, was expecting only 2 events tag/untag (since=%s, until=%s) out=%s", since, until, out)
+ }
+ if !strings.Contains(out, "untag") {
+ c.Fatalf("expected 'untag' event not found (since=%s, until=%s) out=%s", since, until, out)
+ }
+ }
+
+}
+
+func (s *DockerSuite) TestEventsUntag(c *check.C) {
+ image := "busybox"
+ 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)
@@ -28,185 +66,271 @@ 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()
- for i := 0; i < 30; i++ {
- dockerCmd(t, "run", "busybox", "echo", strconv.Itoa(i))
+func (s *DockerSuite) TestEventsLimit(c *check.C) {
+
+ 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()
+ errChan <- exec.Command(dockerBinary, args...).Run()
+ }()
}
- eventsCmd := exec.Command(dockerBinary, "events", "--since=0", fmt.Sprintf("--until=%d", daemonTime(t).Unix()))
+
+ 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")
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 TestEventsImageUntagDelete(t *testing.T) {
+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(c).Unix()))
+ out, exitCode, err := runCommandWithOutput(eventsCmd)
+ if exitCode != 0 || err != nil {
+ 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 {
+ 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" {
+ c.Fatalf("event should be create, not %#v", createEvent)
+ }
+ if startEvent[len(startEvent)-1] != "start" {
+ c.Fatalf("event should be start, not %#v", startEvent)
+ }
+ if dieEvent[len(dieEvent)-1] != "die" {
+ c.Fatalf("event should be die, not %#v", dieEvent)
+ }
+ if destroyEvent[len(destroyEvent)-1] != "destroy" {
+ c.Fatalf("event should be destroy, not %#v", destroyEvent)
+ }
+
+}
+
+func (s *DockerSuite) TestEventsImageUntagDelete(c *check.C) {
name := "testimageevents"
- defer deleteImages(name)
_, err := buildImage(name,
`FROM scratch
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()
+func (s *DockerSuite) TestEventsImageTag(c *check.C) {
+ time.Sleep(time.Second * 2) // because API has seconds granularity
+ since := daemonTime(c).Unix()
+ image := "testimageevents:tag"
+ dockerCmd(c, "tag", "busybox", image)
- defer deleteImages("hello-world")
+ eventsCmd := exec.Command(dockerBinary, "events",
+ fmt.Sprintf("--since=%d", since),
+ fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
+ out, _, err := runCommandWithOutput(eventsCmd)
+ c.Assert(err, check.IsNil)
+
+ events := strings.Split(strings.TrimSpace(out), "\n")
+ if len(events) != 1 {
+ c.Fatalf("was expecting 1 event. out=%s", out)
+ }
+ event := strings.TrimSpace(events[0])
+ expectedStr := image + ": tag"
+
+ if !strings.HasSuffix(event, expectedStr) {
+ c.Fatalf("wrong event format. expected='%s' got=%s", expectedStr, event)
+ }
+
+}
+
+func (s *DockerSuite) TestEventsImagePull(c *check.C) {
+ since := daemonTime(c).Unix()
+ testRequires(c, Network)
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()
+
+ 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)
+ }
+ if err := eventsCmd.Start(); 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 {
- t.Fatal("failed to create a container", out, err)
+ c.Fatal("failed to create a container", out, err)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
out, _, err = runCommandPipelineWithOutput(
exec.Command(dockerBinary, "export", cleanedContainerID),
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)
}
+ newContainerID := strings.TrimSpace(out)
+ id <- newContainerID
- eventsCmd := exec.Command(dockerBinary, "events",
- fmt.Sprintf("--since=%d", since),
- fmt.Sprintf("--until=%d", daemonTime(t).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 pull 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
}
-
- 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]
@@ -214,175 +338,235 @@ 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", "true"))
+ 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 := 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)
+ c.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))
- 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)
+ 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 {
+ 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 {
+ c.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 {
+ 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 TestEventsFilterContainerID(t *testing.T) {
- since := daemonTime(t).Unix()
- defer deleteAllContainers()
+func (s *DockerSuite) TestEventsFilterContainer(c *check.C) {
+ since := fmt.Sprintf("%d", daemonTime(c).Unix())
+ nameID := make(map[string]string)
- out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "true"))
- if err != nil {
- t.Fatal(out, err)
+ for _, name := range []string{"container_1", "container_2"} {
+ 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] = id
}
- container1 := stripTrailingCharacters(out)
- out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox", "true"))
- if err != nil {
- t.Fatal(out, err)
+ until := fmt.Sprintf("%d", daemonTime(c).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
}
- container2 := stripTrailingCharacters(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))
+ 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)
+ c.Fatal(err)
}
- 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)
+
+ events := strings.Split(strings.TrimSuffix(out, "\n"), "\n")
+ if err := checkEvents(ID, events); err != nil {
+ c.Fatal(err)
}
- 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)
- }
- }
- }
- 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"} {
- 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)
+ // 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)
+ c.Fatal(err)
}
- 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)
- }
+
+ events = strings.Split(strings.TrimSuffix(out, "\n"), "\n")
+ if err := checkEvents(ID, events); err != nil {
+ c.Fatal(err)
}
}
- logDone("events - filters using container name")
+}
+
+func (s *DockerSuite) TestEventsStreaming(c *check.C) {
+ start := daemonTime(c).Unix()
+
+ 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() {
+ 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)
+ }
+ }
+ }()
+
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox:latest", "true")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ cleanedContainerID := strings.TrimSpace(out)
+ id <- cleanedContainerID
+
+ select {
+ case <-time.After(5 * time.Second):
+ c.Fatal("failed to observe container create in timely fashion")
+ case <-eventCreate:
+ // ignore, done
+ }
+
+ select {
+ case <-time.After(5 * time.Second):
+ c.Fatal("failed to observe container start in timely fashion")
+ case <-eventStart:
+ // ignore, done
+ }
+
+ select {
+ case <-time.After(5 * time.Second):
+ c.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 {
+ c.Fatal(out, err)
+ }
+
+ select {
+ case <-time.After(5 * time.Second):
+ c.Fatal("failed to observe container destroy in timely fashion")
+ case <-eventDestroy:
+ // ignore, done
+ }
+}
+
+// #13753
+func (s *DockerSuite) TestEventsDefaultEmpty(c *check.C) {
+ dockerCmd(c, "run", "-d", "busybox")
+ out, _ := dockerCmd(c, "events", fmt.Sprintf("--until=%d", daemonTime(c).Unix()))
+ c.Assert(strings.TrimSpace(out), check.Equals, "")
}
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 01adc43c0..9abafb8fa 100644
--- a/integration-cli/docker_cli_exec_test.go
+++ b/integration-cli/docker_cli_exec_test.go
@@ -12,224 +12,170 @@ 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 && 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)
+ 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()
- out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-itd", "busybox", "/bin/cat"))
- if err != nil {
- t.Fatal(err)
- }
+func (s *DockerSuite) TestExecInteractive(c *check.C) {
- 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 {
- t.Fatal(err)
- }
-
- out, err := cmd.CombinedOutput()
- if err != nil {
- t.Fatal(err, string(out))
- }
-
- if string(out) == "" {
- t.Fatalf("Output was empty, likely blocked by standard input")
- }
-
- returnchan <- struct{}{}
- }()
-
- select {
- case <-returnchan:
- case <-time.After(10 * time.Second):
- t.Fatal("timed out running docker exec")
- }
-
- logDone("exec - interactive mode closes stdin after execution")
-}
-
-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)
+ 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{})
+ errChan := make(chan error)
go func() {
- if err := execCmd.Wait(); err != nil {
- t.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):
- 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 := stripTrailingCharacters(out)
+ 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 *DockerDaemonSuite) TestExecAfterDaemonRestart(c *check.C) {
+ testRequires(c, SameHostDaemon)
- d := NewDaemon(t)
- if err := d.StartWithBusybox(); err != nil {
- t.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)
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatalf("Could not start daemon with busybox: %v", err)
}
- if err := d.Restart(); err != nil {
- t.Fatalf("Could not restart daemon: %v", err)
+ 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 out, err := d.Cmd("start", "top"); err != nil {
- t.Fatalf("Could not start top after daemon restart: err=%v\n%s", err, out)
+ if err := s.d.Restart(); err != nil {
+ c.Fatalf("Could not restart daemon: %v", err)
}
- out, err := d.Cmd("exec", "top", "echo", "hello")
+ 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 := s.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,193 +183,191 @@ 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 := stripTrailingCharacters(out)
+ ContainerID := strings.TrimSpace(out)
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)
}
}()
- 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 {
- t.Fatal(err)
+ errChan <- err
+ return
}
expected := "cannot enable tty mode"
if out, _, err := runCommandWithOutput(cmd); err == nil {
- t.Fatal("exec should have failed")
+ errChan <- fmt.Errorf("exec should have failed")
+ return
} else if !strings.Contains(out, expected) {
- t.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):
- 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{})
+ type dstop struct {
+ out []byte
+ err error
+ }
+
+ ch := make(chan dstop)
go func() {
- if out, err := exec.Command(dockerBinary, "stop", "testing").CombinedOutput(); err != nil {
- t.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):
- t.Fatal("Container stop timed out")
- case <-wait:
+ c.Fatal("Container stop timed out")
+ case s := <-ch:
+ c.Assert(s.err, check.IsNil)
}
- 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{}
+ errChan := make(chan error)
// exec a few times concurrently to get consistent failure
for i := 0; i < 5; i++ {
wg.Add(1)
@@ -431,17 +375,23 @@ 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)
+ errChan <- err
+ return
}
cg := sort.StringSlice(strings.Split(string(out), "\n"))
- s.Lock()
+ mu.Lock()
execCgroups = append(execCgroups, cg)
- s.Unlock()
+ mu.Unlock()
wg.Done()
}()
}
wg.Wait()
+ close(errChan)
+
+ for err := range errChan {
+ c.Assert(err, check.IsNil)
+ }
for _, cg := range execCgroups {
if !reflect.DeepEqual(cg, containerCgroups) {
@@ -454,86 +404,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)
+ if 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", "sleep", "10")
- idA := stripTrailingCharacters(out)
+ 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", "sleep", "10")
- idB := stripTrailingCharacters(out)
+ 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 +487,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,32 +579,58 @@ 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 (s *DockerSuite) TestExecWithUser(c *check.C) {
+
+ runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "parent", "busybox", "top")
+ if out, _, err := runCommandWithOutput(runCmd); err != nil {
+ c.Fatal(out, err)
+ }
+
+ cmd := exec.Command(dockerBinary, "exec", "-u", "1", "parent", "id")
+ out, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out)
+ }
+ if !strings.Contains(out, "uid=1(daemon) gid=1(daemon)") {
+ 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 {
+ c.Fatal(err, out)
+ }
+ if !strings.Contains(out, "uid=0(root) gid=0(root)") {
+ c.Fatalf("exec with user by root expected root user got %s", out)
+ }
+
}
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")
+ }
+}
diff --git a/integration-cli/docker_cli_experimental_test.go b/integration-cli/docker_cli_experimental_test.go
new file mode 100644
index 000000000..26bab8544
--- /dev/null
+++ b/integration-cli/docker_cli_experimental_test.go
@@ -0,0 +1,29 @@
+// +build experimental
+
+package main
+
+import (
+ "os/exec"
+ "strings"
+
+ "github.com/go-check/check"
+)
+
+func (s *DockerSuite) TestExperimentalVersion(c *check.C) {
+ versionCmd := exec.Command(dockerBinary, "version")
+ out, _, err := runCommandWithOutput(versionCmd)
+ if err != nil {
+ c.Fatalf("failed to execute docker version: %s, %v", out, err)
+ }
+
+ for _, line := range strings.Split(out, "\n") {
+ if strings.HasPrefix(line, "Experimental (client):") || strings.HasPrefix(line, "Experimental (server):") {
+ c.Assert(line, check.Matches, "*true")
+ }
+ }
+
+ versionCmd = exec.Command(dockerBinary, "-v")
+ if out, _, err = runCommandWithOutput(versionCmd); err != nil || !strings.Contains(out, ", experimental") {
+ c.Fatalf("docker version did not contain experimental: %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 5b2a016f1..22772704e 100644
--- a/integration-cli/docker_cli_export_import_test.go
+++ b/integration-cli/docker_cli_export_import_test.go
@@ -4,96 +4,69 @@ 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) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+func (s *DockerSuite) TestExportContainerAndImportImage(c *check.C) {
+ containerID := "testexportcontainerandimportimage"
+
+ runCmd := exec.Command(dockerBinary, "run", "--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)
}
- cleanedContainerID := stripTrailingCharacters(out)
-
- inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
- out, _, err = runCommandWithOutput(inspectCmd)
- if err != nil {
- t.Fatalf("output should've been a container id: %s %s ", cleanedContainerID, err)
- }
-
- 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)
+ 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 := stripTrailingCharacters(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)
+ cleanedImageID := strings.TrimSpace(out)
+ if cleanedImageID == "" {
+ c.Fatalf("output should have been an image id, got: %s", out)
}
-
- deleteContainer(cleanedContainerID)
- deleteImages("repo/testexp:v1")
-
- logDone("export - export a container")
- logDone("import - import an image")
}
// Used to test output flag in the export command
-func TestExportContainerWithOutputAndImportImage(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+func (s *DockerSuite) TestExportContainerWithOutputAndImportImage(c *check.C) {
+ containerID := "testexportcontainerwithoutputandimportimage"
+
+ runCmd := exec.Command(dockerBinary, "run", "--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)
}
- cleanedContainerID := stripTrailingCharacters(out)
+ defer os.Remove("testexp.tar")
- inspectCmd := exec.Command(dockerBinary, "inspect", cleanedContainerID)
- out, _, err = runCommandWithOutput(inspectCmd)
- if err != nil {
- t.Fatalf("output should've been a container id: %s %s ", cleanedContainerID, err)
- }
-
- exportCmd := exec.Command(dockerBinary, "export", "--output=testexp.tar", cleanedContainerID)
+ 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 := stripTrailingCharacters(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)
+ cleanedImageID := strings.TrimSpace(out)
+ if cleanedImageID == "" {
+ c.Fatalf("output should have been an image id, got: %s", out)
}
-
- deleteContainer(cleanedContainerID)
- deleteImages("repo/testexp:v1")
-
- os.Remove("/tmp/testexp.tar")
-
- logDone("export - export a container with output flag")
- logDone("import - import an image with output flag")
}
diff --git a/integration-cli/docker_cli_help_test.go b/integration-cli/docker_cli_help_test.go
index 8fc5cd1aa..da57601c1 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,17 +82,19 @@ 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:"
// Skip first line, its "Commands:"
cmds := []string{}
for _, cmd := range strings.Split(out[i:], "\n")[1:] {
+ var stderr string
+
// Stop on blank line or non-idented line
if cmd == "" || !unicode.IsSpace(rune(cmd[0])) {
break
@@ -102,54 +104,226 @@ func TestHelpTextVerify(t *testing.T) {
cmd = strings.Split(strings.TrimSpace(cmd), " ")[0]
cmds = append(cmds, cmd)
+ // Check the full usage text
helpCmd := exec.Command(dockerBinary, cmd, "--help")
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)
+ out, stderr, ec, err = runCommandWithStdoutStderr(helpCmd)
+ if len(stderr) != 0 {
+ c.Fatalf("Error on %q help. non-empty stderr:%q", cmd, stderr)
}
+ if strings.HasSuffix(out, "\n\n") {
+ c.Fatalf("Should not have blank line on %q\nout:%q", cmd, out)
+ }
+ if !strings.Contains(out, "--help=false") {
+ c.Fatalf("Should show full usage on %q\nout:%q", cmd, out)
+ }
+ if err != nil || ec != 0 {
+ c.Fatalf("Error on %q help: %s\nexit code:%d", cmd, out, ec)
+ }
+
+ // Check each line for lots of stuff
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)
}
}
+
+ // For each command make sure we generate an error
+ // if we give a bad arg
+ dCmd := exec.Command(dockerBinary, cmd, "--badArg")
+ out, stderr, ec, err = runCommandWithStdoutStderr(dCmd)
+ if len(out) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
+ c.Fatalf("Bad results from 'docker %s --badArg'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", cmd, ec, out, stderr, err)
+ }
+ // Be really picky
+ if strings.HasSuffix(stderr, "\n\n") {
+ c.Fatalf("Should not have a blank line at the end of 'docker rm'\n%s", stderr)
+ }
+
+ // Now make sure that each command will print a short-usage
+ // (not a full usage - meaning no opts section) if we
+ // are missing a required arg or pass in a bad arg
+
+ // These commands will never print a short-usage so don't test
+ noShortUsage := map[string]string{
+ "images": "",
+ "login": "",
+ "logout": "",
+ }
+
+ if _, ok := noShortUsage[cmd]; !ok {
+ // For each command run it w/o any args. It will either return
+ // valid output or print a short-usage
+ var dCmd *exec.Cmd
+ var stdout, stderr string
+ var args []string
+
+ // skipNoArgs are ones that we don't want to try w/o
+ // any args. Either because it'll hang the test or
+ // lead to incorrect test result (like false negative).
+ // Whatever the reason, skip trying to run w/o args and
+ // jump to trying with a bogus arg.
+ skipNoArgs := map[string]string{
+ "events": "",
+ "load": "",
+ }
+
+ ec = 0
+ if _, ok := skipNoArgs[cmd]; !ok {
+ args = []string{cmd}
+ dCmd = exec.Command(dockerBinary, args...)
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(dCmd)
+ }
+
+ // If its ok w/o any args then try again with an arg
+ if ec == 0 {
+ args = []string{cmd, "badArg"}
+ dCmd = exec.Command(dockerBinary, args...)
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(dCmd)
+ }
+
+ if len(stdout) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
+ c.Fatalf("Bad output from %q\nstdout:%q\nstderr:%q\nec:%d\nerr:%q", args, stdout, stderr, ec, err)
+ }
+ // Should have just short usage
+ if !strings.Contains(stderr, "\nUsage: ") {
+ c.Fatalf("Missing short usage on %q\nstderr:%q", args, stderr)
+ }
+ // But shouldn't have full usage
+ if strings.Contains(stderr, "--help=false") {
+ c.Fatalf("Should not have full usage on %q\nstderr:%q", args, stderr)
+ }
+ if strings.HasSuffix(stderr, "\n\n") {
+ c.Fatalf("Should not have a blank line on %q\nstderr:%q", args, stderr)
+ }
+ }
+
}
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")
+}
+
+func (s *DockerSuite) TestHelpExitCodesHelpOutput(c *check.C) {
+ // Test to make sure the exit code and output (stdout vs stderr) of
+ // various good and bad cases are what we expect
+
+ // docker : stdout=all, stderr=empty, rc=0
+ cmd := exec.Command(dockerBinary)
+ stdout, stderr, ec, err := runCommandWithStdoutStderr(cmd)
+ if len(stdout) == 0 || len(stderr) != 0 || ec != 0 || err != nil {
+ c.Fatalf("Bad results from 'docker'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
+ }
+ // Be really pick
+ if strings.HasSuffix(stdout, "\n\n") {
+ c.Fatalf("Should not have a blank line at the end of 'docker'\n%s", stdout)
+ }
+
+ // docker help: stdout=all, stderr=empty, rc=0
+ cmd = exec.Command(dockerBinary, "help")
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd)
+ if len(stdout) == 0 || len(stderr) != 0 || ec != 0 || err != nil {
+ c.Fatalf("Bad results from 'docker help'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
+ }
+ // Be really pick
+ if strings.HasSuffix(stdout, "\n\n") {
+ c.Fatalf("Should not have a blank line at the end of 'docker help'\n%s", stdout)
+ }
+
+ // docker --help: stdout=all, stderr=empty, rc=0
+ cmd = exec.Command(dockerBinary, "--help")
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd)
+ if len(stdout) == 0 || len(stderr) != 0 || ec != 0 || err != nil {
+ c.Fatalf("Bad results from 'docker --help'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
+ }
+ // Be really pick
+ if strings.HasSuffix(stdout, "\n\n") {
+ c.Fatalf("Should not have a blank line at the end of 'docker --help'\n%s", stdout)
+ }
+
+ // docker inspect busybox: stdout=all, stderr=empty, rc=0
+ // Just making sure stderr is empty on valid cmd
+ cmd = exec.Command(dockerBinary, "inspect", "busybox")
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd)
+ if len(stdout) == 0 || len(stderr) != 0 || ec != 0 || err != nil {
+ c.Fatalf("Bad results from 'docker inspect busybox'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
+ }
+ // Be really pick
+ if strings.HasSuffix(stdout, "\n\n") {
+ c.Fatalf("Should not have a blank line at the end of 'docker inspect busyBox'\n%s", stdout)
+ }
+
+ // docker rm: stdout=empty, stderr=all, rc!=0
+ // testing the min arg error msg
+ cmd = exec.Command(dockerBinary, "rm")
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd)
+ if len(stdout) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
+ c.Fatalf("Bad results from 'docker rm'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
+ }
+ // Should not contain full help text but should contain info about
+ // # of args and Usage line
+ if !strings.Contains(stderr, "requires a minimum") {
+ c.Fatalf("Missing # of args text from 'docker rm'\nstderr:%s", stderr)
+ }
+
+ // docker rm NoSuchContainer: stdout=empty, stderr=all, rc=0
+ // testing to make sure no blank line on error
+ cmd = exec.Command(dockerBinary, "rm", "NoSuchContainer")
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd)
+ if len(stdout) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
+ c.Fatalf("Bad results from 'docker rm NoSuchContainer'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
+ }
+ // Be really picky
+ if strings.HasSuffix(stderr, "\n\n") {
+ c.Fatalf("Should not have a blank line at the end of 'docker rm'\n%s", stderr)
+ }
+
+ // docker BadCmd: stdout=empty, stderr=all, rc=0
+ cmd = exec.Command(dockerBinary, "BadCmd")
+ stdout, stderr, ec, err = runCommandWithStdoutStderr(cmd)
+ if len(stdout) != 0 || len(stderr) == 0 || ec == 0 || err == nil {
+ c.Fatalf("Bad results from 'docker BadCmd'\nec:%d\nstdout:%s\nstderr:%s\nerr:%q", ec, stdout, stderr, err)
+ }
+ if stderr != "docker: 'BadCmd' is not a docker command.\nSee 'docker --help'.\n" {
+ c.Fatalf("Unexcepted output for 'docker badCmd'\nstderr:%s", stderr)
+ }
+ // Be really picky
+ if strings.HasSuffix(stderr, "\n\n") {
+ c.Fatalf("Should not have a blank line at the end of 'docker rm'\n%s", stderr)
+ }
+
}
diff --git a/integration-cli/docker_cli_history_test.go b/integration-cli/docker_cli_history_test.go
index ecb0a3a07..d229f1a8c 100644
--- a/integration-cli/docker_cli_history_test.go
+++ b/integration-cli/docker_cli_history_test.go
@@ -3,15 +3,17 @@ package main
import (
"fmt"
"os/exec"
+ "regexp"
+ "strconv"
"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
RUN echo "A"
RUN echo "B"
@@ -42,12 +44,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,27 +60,101 @@ 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")
+ }
+}
+
+func (s *DockerSuite) TestHistoryImageWithComment(c *check.C) {
+ name := "testhistoryimagewithcomment"
+
+ // make a image through docker commit [ -m messages ]
+ //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 {
+ c.Fatalf("failed to run container: %s, %v", out, err)
+ }
+
+ waitCmd := exec.Command(dockerBinary, "wait", name)
+ if out, _, err := runCommandWithOutput(waitCmd); err != nil {
+ 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 {
+ 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 {
+ c.Fatalf("failed to get image history: %s, %v", out, err)
+ }
+
+ outputTabs := strings.Fields(strings.Split(out, "\n")[1])
+ //outputTabs := regexp.MustCompile(" +").Split(outputLine, -1)
+ actualValue := outputTabs[len(outputTabs)-1]
+
+ if !strings.Contains(actualValue, comment) {
+ c.Fatalf("Expected comments %q, but found %q", comment, actualValue)
+ }
+
+}
+
+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)
+ }
}
- logDone("history - history on non-existent image must pass")
}
diff --git a/integration-cli/docker_cli_images_test.go b/integration-cli/docker_cli_images_test.go
index 694971191..0ab646250 100644
--- a/integration-cli/docker_cli_images_test.go
+++ b/integration-cli/docker_cli_images_test.go
@@ -6,137 +6,124 @@ import (
"reflect"
"sort"
"strings"
- "testing"
"time"
- "github.com/docker/docker/pkg/common"
+ "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) {
- defer deleteImages("order:test_a")
- defer deleteImages("order:test_c")
- defer deleteImages("order:test_b")
+func (s *DockerSuite) TestImagesOrderedByCreationDate(c *check.C) {
id1, err := buildImage("order:test_a",
`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)
image1ID, err := buildImage(imageName1,
`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
RUN touch /test/foo
@@ -156,7 +143,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 +159,45 @@ 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 := common.TruncateID(strings.TrimSpace(out))
- defer deleteImages(imageId)
+ imageId := stringid.TruncateID(strings.TrimSpace(out))
// 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 1a7ee6f20..f4bd08521 100644
--- a/integration-cli/docker_cli_import_test.go
+++ b/integration-cli/docker_cli_import_test.go
@@ -3,41 +3,50 @@ 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 := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
out, _, err = runCommandPipelineWithOutput(
exec.Command(dockerBinary, "export", cleanedContainerID),
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)
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")
+}
+
+func (s *DockerSuite) TestImportBadURL(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "import", "http://nourl/bad")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err == nil {
+ c.Fatal("import was supposed to fail but didn't")
+ }
+ if !strings.Contains(out, "dial tcp") {
+ c.Fatalf("expected an error msg but didn't get one:\n%s", out)
+ }
}
diff --git a/integration-cli/docker_cli_info_test.go b/integration-cli/docker_cli_info_test.go
index 2e8239a4b..cf7738d14 100644
--- a/integration-cli/docker_cli_info_test.go
+++ b/integration-cli/docker_cli_info_test.go
@@ -3,24 +3,40 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/docker/docker/utils"
+ "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)
}
- stringsToCheck := []string{"Containers:", "Execution Driver:", "Kernel Version:"}
+ // always shown fields
+ stringsToCheck := []string{
+ "ID:",
+ "Containers:",
+ "Images:",
+ "Execution Driver:",
+ "Logging Driver:",
+ "Operating System:",
+ "CPUs:",
+ "Total Memory:",
+ "Kernel Version:",
+ "Storage Driver:",
+ }
+
+ if utils.ExperimentalBuild() {
+ stringsToCheck = append(stringsToCheck, "Experimental: true")
+ }
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..066e014b4 100644
--- a/integration-cli/docker_cli_inspect_test.go
+++ b/integration-cli/docker_cli_inspect_test.go
@@ -1,23 +1,91 @@
package main
import (
+ "fmt"
"os/exec"
+ "strconv"
"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)
+ id, err := inspectField(imageTest, "Id")
+ c.Assert(err, check.IsNil)
+
+ if id != imageTestID {
+ c.Fatalf("Expected id: %s for image: %s but received id: %s", imageTestID, imageTest, id)
+ }
+
+}
+
+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)
+
+ inspectOut, err := inspectField(out, "HostConfig.Memory")
+ c.Assert(err, check.IsNil)
+
+ if inspectOut != "314572800" {
+ c.Fatalf("inspect got wrong value, got: %q, expected: 314572800", inspectOut)
+ }
+}
+
+func (s *DockerSuite) TestInspectImageFilterInt(c *check.C) {
+ imageTest := "emptyfs"
+ out, err := inspectField(imageTest, "Size")
+ c.Assert(err, check.IsNil)
+
+ size, err := strconv.Atoi(out)
+ 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 {
- t.Fatalf("failed to inspect image: %s, %v", out, err)
+ 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"))
+ }
+}
- if id := strings.TrimSuffix(out, "\n"); id != imageTestID {
- t.Fatalf("Expected id: %s for image: %s but received id: %s", imageTestID, imageTest, id)
+func (s *DockerSuite) TestInspectContainerFilterInt(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "cat")
+ runCmd.Stdin = strings.NewReader("blahblah")
+ out, _, _, err := runCommandWithStdoutStderr(runCmd)
+ if err != nil {
+ c.Fatalf("failed to run container: %v, output: %q", err, out)
+ }
+
+ id := strings.TrimSpace(out)
+
+ out, err = inspectField(id, "State.ExitCode")
+ c.Assert(err, check.IsNil)
+
+ exitCode, err := strconv.Atoi(out)
+ 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)
}
-
- logDone("inspect - inspect an image")
}
diff --git a/integration-cli/docker_cli_kill_test.go b/integration-cli/docker_cli_kill_test.go
index 33135a3be..8c0031a4a 100644
--- a/integration-cli/docker_cli_kill_test.go
+++ b/integration-cli/docker_cli_kill_test.go
@@ -3,73 +3,117 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestKillContainer(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "sleep 10")
+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 := stripTrailingCharacters(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)
- }
+ cleanedContainerID := strings.TrimSpace(out)
+ c.Assert(waitRun(cleanedContainerID), check.IsNil)
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 sleep 10")
}
-func TestKillDifferentUserContainer(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-u", "daemon", "-d", "busybox", "sh", "-c", "sleep 10")
+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 := stripTrailingCharacters(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)
- }
+ cleanedContainerID := strings.TrimSpace(out)
+ c.Assert(waitRun(cleanedContainerID), check.IsNil)
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")
+ }
+}
+
+// regression test about correct signal parsing see #13665
+func (s *DockerSuite) TestKillWithSignal(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ cid := strings.TrimSpace(out)
+ c.Assert(waitRun(cid), check.IsNil)
+
+ killCmd := exec.Command(dockerBinary, "kill", "-s", "SIGWINCH", cid)
+ _, err = runCommand(killCmd)
+ c.Assert(err, check.IsNil)
+
+ running, err := inspectField(cid, "State.Running")
+ if running != "true" {
+ c.Fatal("Container should be in running state after SIGWINCH")
+ }
+}
+
+func (s *DockerSuite) TestKillWithInvalidSignal(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ cid := strings.TrimSpace(out)
+ c.Assert(waitRun(cid), check.IsNil)
+
+ killCmd := exec.Command(dockerBinary, "kill", "-s", "0", cid)
+ out, _, err = runCommandWithOutput(killCmd)
+ c.Assert(err, check.NotNil)
+ if !strings.ContainsAny(out, "Invalid signal: 0") {
+ c.Fatal("Kill with an invalid signal didn't error out correctly")
}
- deleteContainer(cleanedContainerID)
+ running, err := inspectField(cid, "State.Running")
+ if running != "true" {
+ c.Fatal("Container should be in running state after an invalid signal")
+ }
- logDone("kill - kill container running sleep 10 from a different user")
+ runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ cid = strings.TrimSpace(out)
+ c.Assert(waitRun(cid), check.IsNil)
+
+ killCmd = exec.Command(dockerBinary, "kill", "-s", "SIG42", cid)
+ out, _, err = runCommandWithOutput(killCmd)
+ c.Assert(err, check.NotNil)
+ if !strings.ContainsAny(out, "Invalid signal: SIG42") {
+ c.Fatal("Kill with an invalid signal error out correctly")
+ }
+
+ running, err = inspectField(cid, "State.Running")
+ if running != "true" {
+ c.Fatal("Container should be in running state after an invalid signal")
+ }
}
diff --git a/integration-cli/docker_cli_links_test.go b/integration-cli/docker_cli_links_test.go
index efee8d04e..1f7432a8b 100644
--- a/integration-cli/docker_cli_links_test.go
+++ b/integration-cli/docker_cli_links_test.go
@@ -8,89 +8,79 @@ 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.Fatal("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 +88,43 @@ 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", "sleep", "10")
- idA := stripTrailingCharacters(out)
- out, _, _ = dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "sleep", "10")
- idB := stripTrailingCharacters(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)
+ out, _ := dockerCmd(c, "run", "-d", "--name", "container1", "busybox", "top")
+ idA := strings.TrimSpace(out)
+ out, _ = dockerCmd(c, "run", "-d", "--name", "container2", "busybox", "top")
+ idB := strings.TrimSpace(out)
+ 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()
-
- dockerCmd(t, "run", "-d", "--name", "child", "--publish", "8080:80", "busybox", "sleep", "10")
- dockerCmd(t, "run", "-d", "--name", "parent", "--link", "child:http", "busybox", "sleep", "10")
-
- childIP := findContainerIP(t, "child")
- parentIP := findContainerIP(t, "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")
- }
-
- dockerCmd(t, "rm", "--link", "parent/http")
- if iptables.Exists("filter", "DOCKER", sourceRule...) || iptables.Exists("filter", "DOCKER", destinationRule...) {
- t.Fatal("Iptables rules should be removed when unlink")
- }
-
- dockerCmd(t, "kill", "child")
- dockerCmd(t, "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", "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(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 +132,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", "sleep", "10")
- dockerCmd(t, "run", "-d", "--name", "container2", "busybox", "sleep", "10")
- 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 +159,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 +203,124 @@ 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)
+ if err == nil || !strings.Contains(out, "--net=host can't be used with links. This would result in undefined behavior") {
+ 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")
+}
+
+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)
+ }
+}
+
+func (s *DockerSuite) TestLinkShortDefinition(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "--name", "shortlinkdef", "busybox", "top")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ cid := strings.TrimSpace(out)
+ c.Assert(waitRun(cid), check.IsNil)
+
+ runCmd = exec.Command(dockerBinary, "run", "-d", "--name", "link2", "--link", "shortlinkdef", "busybox", "top")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ cid2 := strings.TrimSpace(out)
+ c.Assert(waitRun(cid2), check.IsNil)
+
+ links, err := inspectFieldJSON(cid2, "HostConfig.Links")
+ c.Assert(err, check.IsNil)
+ c.Assert(links, check.Equals, "[\"/shortlinkdef:/link2/shortlinkdef\"]")
}
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 b86a50480..2e41f8a19 100644
--- a/integration-cli/docker_cli_logs_test.go
+++ b/integration-cli/docker_cli_logs_test.go
@@ -1,119 +1,110 @@
package main
import (
+ "encoding/json"
"fmt"
+ "io"
"os/exec"
"regexp"
+ "strconv"
"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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
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,180 +113,235 @@ 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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
exec.Command(dockerBinary, "wait", cleanedContainerID).Run()
logsCmd := exec.Command(dockerBinary, "logs", "-f", cleanedContainerID)
if err := logsCmd.Start(); err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- c := make(chan struct{})
+ errChan := make(chan error)
go func() {
- if err := logsCmd.Wait(); err != nil {
- t.Fatal(err)
- }
- close(c)
+ errChan <- logsCmd.Wait()
+ close(errChan)
}()
select {
- case <-c:
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
case <-time.After(1 * time.Second):
- t.Fatal("Following logs is hanged")
+ c.Fatal("Following logs is hanged")
+ }
+}
+
+func (s *DockerSuite) TestLogsSince(c *check.C) {
+ name := "testlogssince"
+ runCmd := exec.Command(dockerBinary, "run", "--name="+name, "busybox", "/bin/sh", "-c", "for i in $(seq 1 3); do sleep 2; echo `date +%s` log$i; done")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("run failed with errors: %s, %v", out, err)
}
- deleteContainer(cleanedContainerID)
- logDone("logs - logs follow stopped container")
+ log2Line := strings.Split(strings.Split(out, "\n")[1], " ")
+ t, err := strconv.ParseInt(log2Line[0], 10, 64) // the timestamp log2 is writen
+ c.Assert(err, check.IsNil)
+ since := t + 1 // add 1s so log1 & log2 doesn't show up
+ logsCmd := exec.Command(dockerBinary, "logs", "-t", fmt.Sprintf("--since=%v", since), name)
+
+ out, _, err = runCommandWithOutput(logsCmd)
+ if err != nil {
+ c.Fatalf("failed to log container: %s, %v", out, err)
+ }
+
+ // Skip 2 seconds
+ unexpected := []string{"log1", "log2"}
+ for _, v := range unexpected {
+ if strings.Contains(out, v) {
+ c.Fatalf("unexpected log message returned=%v, since=%v\nout=%v", v, since, out)
+ }
+ }
+
+ // Test with default value specified and parameter omitted
+ expected := []string{"log1", "log2", "log3"}
+ for _, cmd := range []*exec.Cmd{
+ exec.Command(dockerBinary, "logs", "-t", name),
+ exec.Command(dockerBinary, "logs", "-t", "--since=0", name),
+ } {
+ out, _, err = runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatalf("failed to log container: %s, %v", out, err)
+ }
+ for _, v := range expected {
+ if !strings.Contains(out, v) {
+ c.Fatalf("'%v' does not contain=%v\nout=%s", cmd.Args, v, out)
+ }
+ }
+ }
+}
+
+func (s *DockerSuite) TestLogsSinceFutureFollow(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", `for i in $(seq 1 5); do date +%s; sleep 1; done`)
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("run failed with errors: %s, %v", out, err)
+ }
+ cleanedContainerID := strings.TrimSpace(out)
+
+ now := daemonTime(c).Unix()
+ since := now + 2
+ logCmd := exec.Command(dockerBinary, "logs", "-f", fmt.Sprintf("--since=%v", since), cleanedContainerID)
+ out, _, err = runCommandWithOutput(logCmd)
+ if err != nil {
+ c.Fatalf("failed to log container: %s, %v", out, err)
+ }
+ lines := strings.Split(strings.TrimSpace(out), "\n")
+ if len(lines) == 0 {
+ c.Fatal("got no log lines")
+ }
+ for _, v := range lines {
+ ts, err := strconv.ParseInt(v, 10, 64)
+ if err != nil {
+ c.Fatalf("cannot parse timestamp output from log: '%v'\nout=%s", v, out)
+ }
+ if ts < since {
+ c.Fatalf("earlier log found. since=%v logdate=%v", since, ts)
+ }
+ }
}
// 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 := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
stopSlowRead := make(chan bool)
@@ -307,31 +353,75 @@ func TestLogsFollowSlowStdoutConsumer(t *testing.T) {
logCmd := exec.Command(dockerBinary, "logs", "-f", cleanedContainerID)
stdout, err := logCmd.StdoutPipe()
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
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.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 {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
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")
+}
+
+func (s *DockerSuite) TestLogsFollowGoroutinesWithStdout(c *check.C) {
+ out, _ := dockerCmd(c, "run", "-d", "busybox", "/bin/sh", "-c", "while true; do echo hello; sleep 2; done")
+ id := strings.TrimSpace(out)
+ c.Assert(waitRun(id), check.IsNil)
+
+ type info struct {
+ NGoroutines int
+ }
+ getNGoroutines := func() int {
+ var i info
+ status, b, err := sockRequest("GET", "/info", nil)
+ c.Assert(err, check.IsNil)
+ c.Assert(status, check.Equals, 200)
+ c.Assert(json.Unmarshal(b, &i), check.IsNil)
+ return i.NGoroutines
+ }
+
+ nroutines := getNGoroutines()
+
+ cmd := exec.Command(dockerBinary, "logs", "-f", id)
+ r, w := io.Pipe()
+ cmd.Stdout = w
+ c.Assert(cmd.Start(), check.IsNil)
+
+ // Make sure pipe is written to
+ chErr := make(chan error)
+ go func() {
+ b := make([]byte, 1)
+ _, err := r.Read(b)
+ chErr <- err
+ }()
+ c.Assert(<-chErr, check.IsNil)
+ c.Assert(cmd.Process.Kill(), check.IsNil)
+
+ // NGoroutines is not updated right away, so we need to wait before failing
+ t := time.After(30 * time.Second)
+ for {
+ select {
+ case <-t:
+ if n := getNGoroutines(); n > nroutines {
+ c.Fatalf("leaked goroutines: expected less than or equal to %d, got: %d", nroutines, n)
+ }
+ default:
+ if n := getNGoroutines(); n <= nroutines {
+ return
+ }
+ time.Sleep(200 * time.Millisecond)
+ }
+ }
}
diff --git a/integration-cli/docker_cli_nat_test.go b/integration-cli/docker_cli_nat_test.go
index 729977bce..582b41dc5 100644
--- a/integration-cli/docker_cli_nat_test.go
+++ b/integration-cli/docker_cli_nat_test.go
@@ -2,61 +2,116 @@ package main
import (
"fmt"
+ "io/ioutil"
"net"
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestNetworkNat(t *testing.T) {
- testRequires(t, SameHostDaemon, NativeExecDriver)
- defer deleteAllContainers()
+func startServerContainer(c *check.C, msg string, port int) string {
+ name := "server"
+ cmd := []string{
+ "-d",
+ "-p", fmt.Sprintf("%d:%d", port, port),
+ "busybox",
+ "sh", "-c", fmt.Sprintf("echo %q | nc -lp %d", msg, port),
+ }
+ 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 {
- 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")
+ return ifaceIP
+}
+
+func getContainerLogs(c *check.C, containerID string) string {
+ runCmd := exec.Command(dockerBinary, "logs", containerID)
out, _, err := runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
+ }
+ return strings.Trim(out, "\r\n")
+}
+
+func getContainerStatus(c *check.C, containerID string) string {
+ out, err := inspectField(containerID, "State.Running")
+ c.Assert(err, check.IsNil)
+ return out
+}
+
+func (s *DockerSuite) TestNetworkNat(c *check.C) {
+ testRequires(c, SameHostDaemon, NativeExecDriver)
+ msg := "it works"
+ startServerContainer(c, msg, 8080)
+ endpoint := getExternalAddress(c)
+ conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", endpoint.String(), 8080))
+ if err != nil {
+ c.Fatalf("Failed to connect to container (%v)", err)
+ }
+ data, err := ioutil.ReadAll(conn)
+ conn.Close()
+ if err != nil {
+ c.Fatal(err)
+ }
+ final := strings.TrimRight(string(data), "\n")
+ if final != msg {
+ c.Fatalf("Expected message %q but received %q", msg, final)
+ }
+}
+
+func (s *DockerSuite) TestNetworkLocalhostTCPNat(c *check.C) {
+ testRequires(c, SameHostDaemon, NativeExecDriver)
+ var (
+ msg = "hi yall"
+ )
+ startServerContainer(c, msg, 8081)
+ conn, err := net.Dial("tcp", "localhost:8081")
+ if err != nil {
+ c.Fatalf("Failed to connect to container (%v)", err)
+ }
+ data, err := ioutil.ReadAll(conn)
+ conn.Close()
+ if err != nil {
+ c.Fatal(err)
+ }
+ final := strings.TrimRight(string(data), "\n")
+ if final != msg {
+ c.Fatalf("Expected message %q but received %q", msg, final)
+ }
+}
+
+func (s *DockerSuite) TestNetworkLoopbackNat(c *check.C) {
+ testRequires(c, SameHostDaemon, NativeExecDriver)
+ msg := "it works"
+ startServerContainer(c, msg, 8080)
+ endpoint := getExternalAddress(c)
+ runCmd := exec.Command(dockerBinary, "run", "-t", "--net=container:server", "busybox",
+ "sh", "-c", fmt.Sprintf("stty raw && nc -w 5 %s 8080", endpoint.String()))
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ final := strings.TrimRight(string(out), "\n")
+ if final != msg {
+ c.Fatalf("Expected message %q but received %q", msg, final)
}
-
- cleanedContainerID := stripTrailingCharacters(out)
-
- 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)
- }
-
- 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)
- }
-
- 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)
- }
-
- killCmd := exec.Command(dockerBinary, "kill", cleanedContainerID)
- if out, _, err = runCommandWithOutput(killCmd); err != nil {
- t.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 f1ccde9cf..0256fb92b 100644
--- a/integration-cli/docker_cli_pause_test.go
+++ b/integration-cli/docker_cli_pause_test.go
@@ -4,83 +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, "sleep", "2")
+ 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", 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)
}
- 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")
}
-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, "sleep", "2")
+ 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))
@@ -92,21 +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)
}
}
- 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_port_test.go b/integration-cli/docker_cli_port_test.go
index 6b68346d4..db2fbb7c5 100644
--- a/integration-cli/docker_cli_port_test.go
+++ b/integration-cli/docker_cli_port_test.go
@@ -1,45 +1,48 @@
package main
import (
+ "fmt"
+ "net"
"os/exec"
+ "regexp"
"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 := stripTrailingCharacters(out)
+ 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
@@ -50,36 +53,36 @@ func TestPortList(t *testing.T) {
"busybox", "top")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- ID := stripTrailingCharacters(out)
+ 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
@@ -91,46 +94,45 @@ func TestPortList(t *testing.T) {
"busybox", "top")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.Fatal(out, err)
}
- ID = stripTrailingCharacters(out)
+ 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)
@@ -138,10 +140,179 @@ 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
}
}
return true
}
+
+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 {
+ c.Fatal(out, err)
+ }
+ firstID := strings.TrimSpace(out)
+
+ runCmd = exec.Command(dockerBinary, "port", firstID, "80")
+ out, _, err = runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+
+ 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 {
+ c.Fatal(out, err)
+ }
+
+ runCmd = exec.Command(dockerBinary, "rm", "-f", firstID)
+ if out, _, err = runCommandWithOutput(runCmd); err != nil {
+ c.Fatal(out, err)
+ }
+
+ runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox",
+ "nc", "localhost", "9876")
+ if out, _, err = runCommandWithOutput(runCmd); err == nil {
+ c.Error("Port is still bound after the Container is removed")
+ }
+}
+
+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 {
+ c.Fatal(out, err)
+ }
+ firstID := strings.TrimSpace(out)
+
+ runCmd = exec.Command(dockerBinary, "port", firstID, "80")
+ out, _, err = runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+
+ _, exposedPort, err := net.SplitHostPort(out)
+
+ if err != nil {
+ c.Fatal(out, err)
+ }
+
+ runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox",
+ "nc", "localhost", strings.TrimSpace(exposedPort))
+ if out, _, err = runCommandWithOutput(runCmd); err != nil {
+ c.Fatal(out, err)
+ }
+
+ runCmd = exec.Command(dockerBinary, "rm", "-f", firstID)
+ if out, _, err = runCommandWithOutput(runCmd); err != nil {
+ c.Fatal(out, err)
+ }
+
+ runCmd = exec.Command(dockerBinary, "run", "--net=host", "busybox",
+ "nc", "localhost", strings.TrimSpace(exposedPort))
+ if out, _, err = runCommandWithOutput(runCmd); err == nil {
+ c.Error("Port is still bound after the Container is removed")
+ }
+}
+
+func stopRemoveContainer(id string, c *check.C) {
+ runCmd := exec.Command(dockerBinary, "rm", "-f", id)
+ _, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+}
+
+func (s *DockerSuite) TestUnpublishedPortsInPsOutput(c *check.C) {
+ // Run busybox with command line expose (equivalent to EXPOSE in image's Dockerfile) for the following ports
+ port1 := 80
+ port2 := 443
+ expose1 := fmt.Sprintf("--expose=%d", port1)
+ expose2 := fmt.Sprintf("--expose=%d", port2)
+ runCmd := exec.Command(dockerBinary, "run", "-d", expose1, expose2, "busybox", "sleep", "5")
+ out, _, err := runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ // Check docker ps o/p for last created container reports the unpublished ports
+ unpPort1 := fmt.Sprintf("%d/tcp", port1)
+ unpPort2 := fmt.Sprintf("%d/tcp", port2)
+ runCmd = exec.Command(dockerBinary, "ps", "-n=1")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+ if !strings.Contains(out, unpPort1) || !strings.Contains(out, unpPort2) {
+ c.Errorf("Missing unpublished ports(s) (%s, %s) in docker ps output: %s", unpPort1, unpPort2, out)
+ }
+
+ // Run the container forcing to publish the exposed ports
+ runCmd = exec.Command(dockerBinary, "run", "-d", "-P", expose1, expose2, "busybox", "sleep", "5")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ // Check docker ps o/p for last created container reports the exposed ports in the port bindings
+ expBndRegx1 := regexp.MustCompile(`0.0.0.0:\d\d\d\d\d->` + unpPort1)
+ expBndRegx2 := regexp.MustCompile(`0.0.0.0:\d\d\d\d\d->` + unpPort2)
+ runCmd = exec.Command(dockerBinary, "ps", "-n=1")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+ if !expBndRegx1.MatchString(out) || !expBndRegx2.MatchString(out) {
+ c.Errorf("Cannot find expected port binding ports(s) (0.0.0.0:xxxxx->%s, 0.0.0.0:xxxxx->%s) in docker ps output:\n%s",
+ unpPort1, unpPort2, out)
+ }
+
+ // Run the container specifying explicit port bindings for the exposed ports
+ offset := 10000
+ pFlag1 := fmt.Sprintf("%d:%d", offset+port1, port1)
+ pFlag2 := fmt.Sprintf("%d:%d", offset+port2, port2)
+ runCmd = exec.Command(dockerBinary, "run", "-d", "-p", pFlag1, "-p", pFlag2, expose1, expose2, "busybox", "sleep", "5")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+ id := strings.TrimSpace(out)
+
+ // Check docker ps o/p for last created container reports the specified port mappings
+ expBnd1 := fmt.Sprintf("0.0.0.0:%d->%s", offset+port1, unpPort1)
+ expBnd2 := fmt.Sprintf("0.0.0.0:%d->%s", offset+port2, unpPort2)
+ runCmd = exec.Command(dockerBinary, "ps", "-n=1")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+ if !strings.Contains(out, expBnd1) || !strings.Contains(out, expBnd2) {
+ c.Errorf("Cannot find expected port binding(s) (%s, %s) in docker ps output: %s", expBnd1, expBnd2, out)
+ }
+ // Remove container now otherwise it will interfeer with next test
+ stopRemoveContainer(id, c)
+
+ // Run the container with explicit port bindings and no exposed ports
+ runCmd = exec.Command(dockerBinary, "run", "-d", "-p", pFlag1, "-p", pFlag2, "busybox", "sleep", "5")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+ id = strings.TrimSpace(out)
+
+ // Check docker ps o/p for last created container reports the specified port mappings
+ runCmd = exec.Command(dockerBinary, "ps", "-n=1")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+ if !strings.Contains(out, expBnd1) || !strings.Contains(out, expBnd2) {
+ c.Errorf("Cannot find expected port binding(s) (%s, %s) in docker ps output: %s", expBnd1, expBnd2, out)
+ }
+ // Remove container now otherwise it will interfeer with next test
+ stopRemoveContainer(id, c)
+
+ // Run the container with one unpublished exposed port and one explicit port binding
+ runCmd = exec.Command(dockerBinary, "run", "-d", expose1, "-p", pFlag2, "busybox", "sleep", "5")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+
+ // Check docker ps o/p for last created container reports the specified unpublished port and port mapping
+ runCmd = exec.Command(dockerBinary, "ps", "-n=1")
+ out, _, err = runCommandWithOutput(runCmd)
+ c.Assert(err, check.IsNil)
+ if !strings.Contains(out, unpPort1) || !strings.Contains(out, expBnd2) {
+ c.Errorf("Missing unpublished ports or port binding (%s, %s) in docker ps output: %s", unpPort1, expBnd2, out)
+ }
+}
diff --git a/integration-cli/docker_cli_proxy_test.go b/integration-cli/docker_cli_proxy_test.go
index b39dd5634..8b55c67d8 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 http://golang.org/pkg/net/http/#ProxyFromEnvironment
-func TestCliProxyProxyTCPSock(t *testing.T) {
- testRequires(t, SameHostDaemon)
+// See https://golang.org/pkg/net/http/#ProxyFromEnvironment
+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()
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
var ip string
for _, addr := range addrs {
@@ -40,25 +40,23 @@ 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)
- if err := d.Start("-H", "tcp://"+ip+":2375"); err != nil {
- t.Fatal(err)
+ if err := s.d.Start("-H", "tcp://"+ip+":2375"); err != nil {
+ 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 c2e108576..a9d36be49 100644
--- a/integration-cli/docker_cli_ps_test.go
+++ b/integration-cli/docker_cli_ps_test.go
@@ -6,68 +6,78 @@ 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 := 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)
+ c.Fatal(out, err)
}
- secondID := stripTrailingCharacters(out)
+ secondID := strings.TrimSpace(out)
// not long running
runCmd = exec.Command(dockerBinary, "run", "-d", "busybox", "true")
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatal(out, err)
+ c.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)
+ c.Fatal(out, err)
+ }
+ fourthID := strings.TrimSpace(out)
+
+ // make sure the second is running
+ if err := waitRun(secondID); err != nil {
+ c.Fatalf("waiting for container failed: %v", err)
}
- fourthID := stripTrailingCharacters(out)
// 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 {
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
// from here all flag '-a' is ignored
@@ -76,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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ 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.Error("Container list is not in the correct order")
+ c.Errorf("Container list is not in the correct order: %s", out)
}
- logDone("ps - test ps options")
}
func assertContainerList(out string, expected []string) bool {
@@ -245,30 +254,28 @@ 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)
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)
+ 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")
@@ -280,346 +287,351 @@ 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 {
+ 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 + 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)
+ if !strings.Contains(foundSize, expectedSize) {
+ 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 := stripTrailingCharacters(out)
+ 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 := stripTrailingCharacters(out)
+ secondID := strings.TrimSpace(out)
// filter containers by exited
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 := stripTrailingCharacters(out)
+ 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)
+ 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 := 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")
+ 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 := 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)
+ c.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)
+ c.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")
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 containerst got %d", len(ids))
+ 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 containers 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 {
+ 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 {
+ 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) != 2 {
- t.Fatalf("There should be 2 running container, got %d", len(lines))
+ if len(lines) != 3 {
+ 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:latest" {
- t.Fatalf("Expected %s tag for id %s, got %s", "busybox", id1, f[1])
+ if f[1] != "busybox" {
+ 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, id1, f[1])
+ c.Fatalf("Expected %s tag for id %s, got %s", tag, id2, f[1])
+ }
+ case id3:
+ if f[1] != imageID {
+ c.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)
+ 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
@@ -631,28 +643,40 @@ 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 := "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)
+ 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")
+}
+
+func (s *DockerSuite) TestPsWithSize(c *check.C) {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "sizetest", "busybox", "top"))
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "ps", "--size"))
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ if !strings.Contains(out, "virtual") {
+ c.Fatalf("docker ps with --size should show virtual size of container")
+ }
}
diff --git a/integration-cli/docker_cli_pull_test.go b/integration-cli/docker_cli_pull_test.go
index 926e76343..a3ded8f03 100644
--- a/integration-cli/docker_cli_pull_test.go
+++ b/integration-cli/docker_cli_pull_test.go
@@ -4,15 +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 *DockerRegistrySuite) TestPullImageWithAliases(c *check.C) {
repoName := fmt.Sprintf("%v/dockercli/busybox", privateRegistryURL)
- defer deleteImages(repoName)
repos := []string{}
for _, tag := range []string{"recent", "fresh"} {
@@ -22,90 +20,92 @@ 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) {
+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.
verifiedName := "hello-world"
- defer deleteImages(verifiedName)
// pull it
expected := "The image you are pulling has been verified"
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) {
- defer deleteImages("hello-world")
+func (s *DockerSuite) TestPullImageFromCentralRegistry(c *check.C) {
+ testRequires(c, Network)
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) {
- 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)
+func (s *DockerSuite) TestPullNonExistingImage(c *check.C) {
+ 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)
}
- 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) {
+func (s *DockerSuite) TestPullImageOfficialNames(c *check.C) {
+ testRequires(c, Network)
+
names := []string{
"docker.io/hello-world",
"index.docker.io/hello-world",
@@ -117,7 +117,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
}
@@ -125,10 +125,28 @@ 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")
+}
+
+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)
+ }
}
diff --git a/integration-cli/docker_cli_push_test.go b/integration-cli/docker_cli_push_test.go
index f1274ba70..ca971807f 100644
--- a/integration-cli/docker_cli_push_test.go
+++ b/integration-cli/docker_cli_push_test.go
@@ -1,158 +1,140 @@
package main
import (
+ "archive/tar"
"fmt"
"io/ioutil"
"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 *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)
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 *DockerRegistrySuite) TestPushUntagged(c *check.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: output %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 *DockerRegistrySuite) TestPushBadTag(c *check.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: output %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 *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)
- // 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 {
- 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 *DockerRegistrySuite) TestPushInterrupt(c *check.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)
+ // 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)
}
- 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 := 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 failed with timeout - there would be some error,
+ // so no logic about it here
+ for exec.Command(dockerBinary, "push", repoName).Run() != nil {
}
-
- logDone("push - interrupted")
}
-func TestPushEmptyLayer(t *testing.T) {
- defer setupRegistry(t)()
+func (s *DockerRegistrySuite) TestPushEmptyLayer(c *check.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 3aaf79522..156ea6eeb 100644
--- a/integration-cli/docker_cli_rename_test.go
+++ b/integration-cli/docker_cli_rename_test.go
@@ -3,119 +3,110 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/docker/docker/pkg/stringid"
+ "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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
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")
- 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 {
- 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)
+ if name != "/"+newName {
+ 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 := stripTrailingCharacters(out)
- runCmd = exec.Command(dockerBinary, "rename", "first_name", "new_name")
+ newName := "new_name" + stringid.GenerateRandomID()
+ cleanedContainerID := strings.TrimSpace(out)
+ runCmd = exec.Command(dockerBinary, "rename", "first_name", newName)
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 ")
+ if name != "/"+newName {
+ 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")
+ newName := "new_name" + stringid.GenerateRandomID()
+ runCmd = exec.Command(dockerBinary, "rename", "first_name", newName)
out, _, err = runCommandWithOutput(runCmd)
if err != nil {
- t.Fatalf(out, err)
+ c.Fatalf(out, err)
}
- name, err := inspectField("new_name", "Name")
+ name, err := inspectField(newName, "Name")
if err != nil {
- t.Fatal(err)
+ c.Fatal(err)
}
- if name != "/new_name" {
- t.Fatal("Failed to rename container ")
+ if name != "/"+newName {
+ 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 - running container")
}
-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 7b97c0725..fd95fd022 100644
--- a/integration-cli/docker_cli_restart_test.go
+++ b/integration-cli/docker_cli_restart_test.go
@@ -3,244 +3,221 @@ 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 := stripTrailingCharacters(out)
+ 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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
time.Sleep(1 * time.Second)
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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
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)
- }
+ volumes, err := inspectField(cleanedContainerID, ".Volumes")
+ c.Assert(err, check.IsNil)
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)
- }
+ volumesAfterRestart, err := inspectField(cleanedContainerID, ".Volumes")
+ c.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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")
+ c.Assert(err, check.IsNil)
+
+ // MaximumRetryCount=0 if the restart policy is always
+ if 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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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 d01b36d45..f5884dc0f 100644
--- a/integration-cli/docker_cli_rm_test.go
+++ b/integration-cli/docker_cli_rm_test.go
@@ -4,94 +4,68 @@ 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) TestRmForceRemoveRunningContainer(c *check.C) {
- createRunningContainer(t, "foo")
-
- endpoint := "/containers/foo"
- _, 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") {
- t.Fatalf("Expected error to contain '409 Conflict' but found %s", err)
- }
-
- logDone("rm - running container")
-}
-
-func TestRmForceRemoveRunningContainer(t *testing.T) {
- defer deleteAllContainers()
-
- 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"]`
@@ -102,47 +76,44 @@ func TestRmContainerOrphaning(t *testing.T) {
// build first dockerfile
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")
- } 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.Fatal("Expected error on rm unknown container, got none")
+ } else if !strings.Contains(out, "failed to remove containers") {
+ c.Fatalf("Expected output to contain 'failed to remove 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 fd34c2242..ec5510c9a 100644
--- a/integration-cli/docker_cli_rmi_test.go
+++ b/integration-cli/docker_cli_rmi_test.go
@@ -1,107 +1,193 @@
package main
import (
+ "fmt"
"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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
// try to delete the image
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 TestRmiTagWithExistingContainers(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRmiImgIDMultipleTag(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "/bin/sh", "-c", "mkdir '/busybox-one'")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("failed to create a container:%s, %v", out, err)
+ }
+ containerID := strings.TrimSpace(out)
+ runCmd = exec.Command(dockerBinary, "commit", containerID, "busybox-one")
+ out, _, err = runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatalf("failed to commit a new busybox-one:%s, %v", out, err)
+ }
+ imagesBefore, _ := dockerCmd(c, "images", "-a")
+ dockerCmd(c, "tag", "busybox-one", "busybox-one:tag1")
+ dockerCmd(c, "tag", "busybox-one", "busybox-one:tag2")
+
+ imagesAfter, _ := dockerCmd(c, "images", "-a")
+ if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+2 {
+ c.Fatalf("tag busybox to create 2 more images with same imageID; docker images shows: %q\n", imagesAfter)
+ }
+
+ imgID, err := inspectField("busybox-one:tag1", "Id")
+ c.Assert(err, check.IsNil)
+
+ // run a container with the image
+ out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "busybox-one", "top"))
+ if err != nil {
+ c.Fatalf("failed to create a container:%s, %v", out, err)
+ }
+ containerID = strings.TrimSpace(out)
+
+ // first checkout without force it fails
+ out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "rmi", imgID))
+ expected := fmt.Sprintf("Conflict, cannot delete %s because the running container %s is using it, stop it and use -f to force", imgID[:12], containerID[:12])
+ if err == nil || !strings.Contains(out, expected) {
+ c.Fatalf("rmi tagged in multiple repos should have failed without force: %s, %v, expected: %s", out, err, expected)
+ }
+
+ dockerCmd(c, "stop", containerID)
+ dockerCmd(c, "rmi", "-f", imgID)
+
+ imagesAfter, _ = dockerCmd(c, "images", "-a")
+ if strings.Contains(imagesAfter, imgID[:12]) {
+ c.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter)
+ }
+
+}
+
+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 {
+ 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 {
+ c.Fatalf("failed to commit a new busybox-test:%s, %v", out, err)
+ }
+
+ 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(c, "images", "-a")
+ if strings.Count(imagesAfter, "\n") != strings.Count(imagesBefore, "\n")+4 {
+ c.Fatalf("tag busybox to create 4 more images with same imageID; docker images shows: %q\n", imagesAfter)
+ }
+ }
+ imgID, err := inspectField("busybox-test", "Id")
+ c.Assert(err, check.IsNil)
+
+ // 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 multiple repos should have failed without force:%s, %v", out, err)
+ }
+
+ dockerCmd(c, "rmi", "-f", imgID)
+ {
+ imagesAfter, _ := dockerCmd(c, "images", "-a")
+ if strings.Contains(imagesAfter, imgID[:12]) {
+ c.Fatalf("rmi -f %s failed, image still exists: %q\n\n", imgID, imagesAfter)
+ }
+
+ }
+}
+
+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"
@@ -110,64 +196,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 7fda0a455..8b432125d 100644
--- a/integration-cli/docker_cli_run_test.go
+++ b/integration-cli/docker_cli_run_test.go
@@ -16,688 +16,572 @@ import (
"strconv"
"strings"
"sync"
- "testing"
"time"
"github.com/docker/docker/nat"
- "github.com/docker/docker/pkg/networkfs/resolvconf"
+ "github.com/docker/libnetwork/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) {
+ testRequires(c, NativeExecDriver)
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 TestRunEchoNamedContainer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunEchoStdoutWithCPUQuota(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "--cpu-quota", "8000", "--name", "test", "busybox", "echo", "test")
+ out, _, _, err := runCommandWithStdoutStderr(runCmd)
+ if err != nil {
+ 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") {
+ c.Skip("Your kernel does not support CPU cfs quota, skip this test")
+ }
+ if out != "test" {
+ c.Errorf("container should've printed 'test'")
+ }
+ out, err = inspectField("test", "HostConfig.CpuQuota")
+ c.Assert(err, check.IsNil)
+
+ if out != "8000" {
+ c.Errorf("setting the CPU CFS quota failed")
+ }
+}
+
+// "test" should be printed
+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)
- }
-
- 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) {
- 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()
-
- runCmd := exec.Command("bash", "-c", `echo "blahblah" | docker run -i -a stdin busybox cat`)
+func (s *DockerSuite) TestRunStdinPipe(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "-i", "-a", "stdin", "busybox", "cat")
+ runCmd.Stdin = strings.NewReader("blahblah")
out, _, _, err := runCommandWithStdoutStderr(runCmd)
if err != nil {
- t.Fatalf("failed to run container: %v, output: %q", err, out)
- }
-
- out = stripTrailingCharacters(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("failed to run container: %v, output: %q", err, out)
}
+ out = strings.TrimSpace(out)
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 := stripTrailingCharacters(logsOut)
+ 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)
- }
-
- out = stripTrailingCharacters(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("failed to run container: %v, output: %q", err, out)
}
+ out = strings.TrimSpace(out)
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 = stripTrailingCharacters(rmOut)
+ 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 = stripTrailingCharacters(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 = stripTrailingCharacters(out)
+ 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")
- logDone("run - run with working directory set by --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")
- logDone("run - disable networking with -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)
- }
- ip = strings.TrimSpace(ip)
+ ip, err := inspectField("parent", "NetworkSettings.IPAddress")
+ c.Assert(err, check.IsNil)
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)
- }
- ip = strings.TrimSpace(ip)
+ ip, err := inspectField(cID, "NetworkSettings.IPAddress")
+ c.Assert(err, check.IsNil)
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) 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")
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()
-
- runCmd := exec.Command(dockerBinary, "run", "--name", "test-data", "--volume", "/etc/hosts:/target-file", "busybox", "true")
- out, stderr, exitCode, err := runCommandWithStdoutStderr(runCmd)
- if err != nil && exitCode != 0 {
- t.Fatal("1", out, stderr, err)
- }
-
- runCmd = exec.Command(dockerBinary, "run", "--volumes-from", "test-data", "busybox", "cat", "/target-file")
- out, stderr, exitCode, err = runCommandWithStdoutStderr(runCmd)
- if err != nil && exitCode != 0 {
- t.Fatal("2", out, stderr, err)
- }
-
- 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)
- }
- defer deleteImages(name)
-
- if out, _, err := dockerCmd(t, "run", "-v", "/test/test", name); err != nil {
- t.Fatal(err, out)
+ c.Fatal(err)
}
- 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()
-
- 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")
- }
-
- 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 inheritted volume is "ro"
+ // 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)
+ if !strings.Contains(out, "Duplicate bind mount") {
+ 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)
buildCmd := exec.Command(dockerBinary, "build", "-t", image, "-")
buildCmd.Stdin = strings.NewReader(`FROM busybox
@@ -705,43 +589,38 @@ 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)
buildCmd := exec.Command(dockerBinary, "build", "-t", name, "-")
buildCmd.Stdin = strings.NewReader(`FROM busybox
@@ -750,166 +629,131 @@ 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)
+ 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 {
- t.Fatal(err)
- }
+ _, err := runCommand(cmd)
+ errChan <- err
}()
}
group.Wait()
+ close(errChan)
- logDone("run - two concurrent containers")
+ for err := range errChan {
+ c.Assert(err, check.IsNil)
+ }
}
-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",
@@ -918,7 +762,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")
@@ -942,29 +786,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")
@@ -982,28 +823,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")
@@ -1022,60 +860,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()
-
- _, _, err := 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)
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 (
@@ -1090,14 +915,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
@@ -1105,293 +928,252 @@ 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) TestRunWithCpuPeriod(c *check.C) {
+ runCmd := exec.Command(dockerBinary, "run", "--cpu-period", "50000", "--name", "test", "busybox", "true")
+ out, _, _, err := runCommandWithStdoutStderr(runCmd)
+ if err != nil {
+ 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 period") {
+ c.Skip("Your kernel does not support CPU cfs period, skip this test")
+ }
+ out, err = inspectField("test", "HostConfig.CpuPeriod")
+ c.Assert(err, check.IsNil)
+ if out != "50000" {
+ c.Errorf("setting the CPU CFS period failed")
+ }
+}
+
+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 successfuly 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 successfuly 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 TestRunDeviceNumbers(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 {
+ c.Fatalf("container should run successfully with cpuset-mems of 0: %s", err)
+ }
+}
+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)
if err != nil {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
deviceLineFields := strings.Fields(out)
deviceLineFields[6] = ""
@@ -1400,137 +1182,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, _, err := dockerCmd(t, "run", "--workdir", "/", "busybox", "pwd")
+func (s *DockerSuite) TestRunRootWorkdir(c *check.C) {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "--workdir", "/", "busybox", "pwd"))
if err != nil {
- t.Fatal(s, err)
+ c.Fatalf("Failed with errors: %s, %v", out, err)
}
- if s != "/\n" {
- t.Fatalf("pwd returned %q (expected /\\n)", s)
+ if out != "/\n" {
+ c.Fatalf("pwd returned %q (expected /\\n)", s)
}
-
- logDone("run - workdir /")
}
-func TestRunAllowBindMountingRoot(t *testing.T) {
- defer deleteAllContainers()
-
- s, _, err := dockerCmd(t, "run", "-v", "/:/host", "busybox", "ls", "/host")
+func (s *DockerSuite) TestRunAllowBindMountingRoot(c *check.C) {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-v", "/:/host", "busybox", "ls", "/host"))
if err != nil {
- t.Fatal(s, err)
+ c.Fatalf("Failed with errors: %s, %v", out, err)
}
-
- logDone("run - bind mount / as volume")
}
-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)
}
}()
@@ -1539,14 +1291,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
@@ -1554,54 +1306,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)
@@ -1610,58 +1355,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)
@@ -1670,35 +1415,56 @@ 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) {
- // 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)
+// Test to see if a non-root user can resolve a DNS name and reach out to it. Also
+// check if the container resolv.conf file has atleast 0644 perm.
+func (s *DockerSuite) TestRunNonRootUserResolvName(c *check.C) {
+ testRequires(c, SameHostDaemon)
+ testRequires(c, Network)
+
+ cmd := exec.Command(dockerBinary, "run", "--name=testperm", "--user=default", "busybox", "ping", "-c", "1", "www.docker.io")
+ if out, err := runCommand(cmd); err != nil {
+ c.Fatal(err, out)
+ }
+
+ cID, err := getIDByName("testperm")
+ if err != nil {
+ c.Fatal(err)
+ }
+
+ fmode := (os.FileMode)(0644)
+ finfo, err := os.Stat(containerStorageFile(cID, "resolv.conf"))
+ if err != nil {
+ c.Fatal(err)
+ }
+
+ if (finfo.Mode() & fmode) != fmode {
+ c.Fatalf("Expected container resolv.conf mode to be atleast %s, instead got %s", fmode.String(), finfo.Mode().String())
+ }
+}
+
+// Test if container resolv.conf gets updated the next time it restarts
+// if host /etc/resolv.conf has changed. This only applies if the container
+// uses the host's /etc/resolv.conf and does not have any dns options provided.
+func (s *DockerSuite) TestRunResolvconfUpdate(c *check.C) {
+ testRequires(c, SameHostDaemon)
tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78")
tmpLocalhostResolvConf := []byte("nameserver 127.0.0.1")
@@ -1706,125 +1472,128 @@ 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
+ //1. test that a restarting 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)
+ }
+
+ // start the container again to pickup changes
+ cmd = exec.Command(dockerBinary, "start", "first")
+ if out, err := runCommand(cmd); err != nil {
+ c.Fatalf("Errored out %s, \nerror: %v", string(out), 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("Restarted 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
+ /* //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 {
+ c.Fatal(err)
+ } */
+ //2. test that a restarting 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)
- }
- 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")
+ // start the container again
+ cmd = exec.Command(dockerBinary, "start", "second")
+ if out, err := runCommand(cmd); err != nil {
+ c.Fatalf("Errored out %s, \nerror: %v", string(out), err)
+ }
+
+ // check for update in container
+ containerResolv, err = readContainerFile(containerID2, "resolv.conf")
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)
+ if bytes.Equal(containerResolv, resolvConfSystem) {
+ c.Fatalf("Restarting a container after container updated resolv.conf should not pick up host changes; expected %q, got %q", string(containerResolv), string(resolvConfSystem))
}
//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)
- }
-
// 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
- // getting updated
- time.Sleep(time.Second / 2)
- containerResolvHashAfter, err = readContainerFile(runningContainerID, "resolv.conf.hash")
+ // check for update in container
+ containerResolv, err = readContainerFile(runningContainerID, "resolv.conf")
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)
+ if bytes.Equal(containerResolv, bytesResolvConf) {
+ c.Fatalf("Running container should not have updated resolv.conf; expected %q, got %q", string(resolvConfSystem), string(containerResolv))
}
//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", string(bytesResolvConf), string(containerResolv))
}
//5. test that additions of a localhost resolver are cleaned from
@@ -1833,20 +1602,25 @@ 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)
+ }
+
+ // start the container again to pickup changes
+ cmd = exec.Command(dockerBinary, "start", "first")
+ if out, err := runCommand(cmd); err != nil {
+ c.Fatalf("Errored out %s, \nerror: %v", string(out), err)
}
- time.Sleep(time.Second / 2)
// our first exited container ID should have been updated, but with default DNS
// 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
@@ -1854,194 +1628,163 @@ 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)
+ }
+
+ // start the container again to pickup changes
+ cmd = exec.Command(dockerBinary, "start", "third")
+ if out, err := runCommand(cmd); err != nil {
+ c.Fatalf("Errored out %s, \nerror: %v", string(out), 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) TestRunAttachWithDetach(c *check.C) {
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)
+ c.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")
+ 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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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
@@ -2049,184 +1792,166 @@ 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{})
+ finish := make(chan error)
go func() {
- if err := runCmd.Wait(); err != nil {
- t.Fatal(err)
- }
+ finish <- runCmd.Wait()
close(finish)
}()
select {
- case <-finish:
+ case err := <-finish:
+ c.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
+
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 := stripTrailingCharacters(out1)
+ cID := strings.TrimSpace(out1)
cmd = exec.Command(dockerBinary, "diff", cID)
- base_diff, _, err := runCommandWithOutput(cmd)
+ baseDiff, _, err := runCommandWithOutput(cmd)
if err != nil {
- t.Fatal(err, base_diff)
+ c.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 {
@@ -2244,859 +1969,769 @@ 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`
+ 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 output. 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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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)
- }
- 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")
- }
- if !strings.Contains(out, "address already in use") {
- t.Fatalf("Out must be about \"address already in use\", got %s", out)
+ if err != nil {
+ c.Fatalf("Fail to run listening container")
}
- logDone("run - error out if port already in use")
+ 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, "port is already allocated") {
+ c.Fatalf("Out must be about \"port is already allocated\", got %s", out)
+ }
}
-// https://github.com/docker/docker/issues/8428
-func TestRunPortProxy(t *testing.T) {
- testRequires(t, SameHostDaemon)
-
- defer deleteAllContainers()
-
- port := "12345"
- cmd := exec.Command(dockerBinary, "run", "-d", "-p", port+":80", "busybox", "top")
-
+// 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 {
- t.Fatalf("Failed to run and bind port %s, output: %s, error: %s", port, out, err)
+ c.Fatalf("Failed to run, output: %s, error: %s", out, err)
}
+ id := strings.TrimSpace(out)
- // connect 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)
+ cmd = exec.Command(dockerBinary, "port", id, "80")
+ out, _, err = runCommandWithOutput(cmd)
if err != nil {
- t.Errorf("list docker process failed with output %s, error %s", out, err)
+ c.Fatalf("Failed to get port, output: %s, error: %s", out, err)
}
- if strings.Contains(out, "docker ") {
- t.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)
+ 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)
}
- logDone("run - proxy should work with unavailable port")
+ // 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 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")
+ 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.Fatal("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.Fatal("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.Fatal("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) {
- defer deleteImages("dataimage")
- defer deleteAllContainers()
+func (s *DockerSuite) TestVolumesNoCopyData(c *check.C) {
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)
-
- // Clear out any remnants from other tests
- deleteAllContainers()
- info, err := ioutil.ReadDir(volumesConfigPath)
- if err != nil {
- t.Fatal(err)
- }
- if len(info) > 0 {
- for _, f := range info {
- if err := os.RemoveAll(volumesConfigPath + "/" + f.Name()); err != nil {
- t.Fatal(err)
- }
- }
- }
-
- defer deleteAllContainers()
- cmd := exec.Command(dockerBinary, "run", "-v", "/foo", "--name", "lone_starr", "busybox")
- if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
- }
-
- cmd = exec.Command(dockerBinary, "start", "lone_starr")
- if _, err := runCommand(cmd); err != nil {
- t.Fatal(err)
- }
-
- info, err = ioutil.ReadDir(volumesConfigPath)
- if err != nil {
- t.Fatal(err)
- }
- if len(info) != 1 {
- t.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)
- }
- if out != "" {
- t.Fatalf("Found unexpected volume entry for '/foo/' in volumes\n%q", out)
+ c.Assert(err, check.IsNil)
+ if 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)
- }
- if !strings.Contains(out, volumesStoragePath) {
- t.Fatalf("Volume was not defined for /foo\n%q", out)
+ c.Assert(err, check.IsNil)
+ if !strings.Contains(out, volumesConfigPath) {
+ c.Fatalf("Volume was not defined for /foo\n%q", out)
}
out, err = inspectFieldMap("dark_helmet", "Volumes", "/bar/")
- if err != nil {
- t.Fatal(err)
- }
- if out != "" {
- t.Fatalf("Found unexpected volume entry for '/bar/' in volumes\n%q", out)
+ c.Assert(err, check.IsNil)
+ if 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.Assert(err, check.IsNil)
+ if !strings.Contains(out, volumesConfigPath) {
+ c.Fatalf("Volume was not defined for /bar\n%q", out)
}
- if !strings.Contains(out, volumesStoragePath) {
- t.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.Assert(err, check.IsNil)
var ports nat.PortMap
- err = unmarshalJSON([]byte(portstr), &ports)
+ if err = unmarshalJSON([]byte(portstr), &ports); err != nil {
+ c.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)
+ 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.Fatalf("Port is not mapped for the port %d", port)
}
}
- if err := deleteContainer(id); err != nil {
- t.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)
runCmd = exec.Command(dockerBinary, "start", cID)
_, _, _, _ = runCommandWithStdoutStderr(runCmd)
- runCmd = exec.Command(dockerBinary, "inspect", "--format={{.State.ExitCode}}", cID)
- rc, _, _, err2 := runCommandWithStdoutStderr(runCmd)
- rc = strings.TrimSpace(rc)
-
- if err2 != nil {
- t.Fatalf("Error getting status of container: %v", err2)
- }
-
+ rc, err := inspectField(cID, "State.ExitCode")
+ c.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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 TestContainerNetworkMode(t *testing.T) {
- defer deleteAllContainers()
- testRequires(t, SameHostDaemon)
+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 {
+ c.Fatalf("run IPC from a non exists container should with correct error out")
+ }
+}
+
+func (s *DockerSuite) TestRunModeIpcContainerNotRunning(c *check.C) {
+ testRequires(c, SameHostDaemon)
+
+ cmd := exec.Command(dockerBinary, "create", "busybox")
+ out, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out)
+ }
+ id := strings.TrimSpace(out)
+
+ cmd = exec.Command(dockerBinary, "run", fmt.Sprintf("--ipc=container:%s", id), "busybox")
+ out, _, err = runCommandWithOutput(cmd)
+ if err == nil {
+ c.Fatalf("Run container with ipc mode container should fail with non running container: %s\n%s", out, err)
+ }
+}
+
+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.Assert(err, check.IsNil)
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) 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)
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) TestRunModeUTSHost(c *check.C) {
+ testRequires(c, NativeExecDriver, SameHostDaemon)
+
+ hostUTS, err := os.Readlink("/proc/1/ns/uts")
+ if err != nil {
+ c.Fatal(err)
+ }
+
+ cmd := exec.Command(dockerBinary, "run", "--uts=host", "busybox", "readlink", "/proc/self/ns/uts")
+ out2, _, err := runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out2)
+ }
+
+ out2 = strings.Trim(out2, "\n")
+ if hostUTS != out2 {
+ c.Fatalf("UTS different with --uts=host %s != %s\n", hostUTS, out2)
+ }
+
+ cmd = exec.Command(dockerBinary, "run", "busybox", "readlink", "/proc/self/ns/uts")
+ out2, _, err = runCommandWithOutput(cmd)
+ if err != nil {
+ c.Fatal(err, out2)
+ }
+
+ out2 = strings.Trim(out2, "\n")
+ if hostUTS == out2 {
+ c.Fatalf("UTS should be different without --uts=host %s == %s\n", hostUTS, out2)
+ }
+}
+
+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
@@ -3105,318 +2740,354 @@ 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()
-
- done := make(chan struct{})
+func (s *DockerSuite) TestRunTtyWithPipe(c *check.C) {
+ 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 {
- t.Fatal(err)
+ errChan <- err
+ return
}
expected := "cannot enable tty mode"
if out, _, err := runCommandWithOutput(cmd); err == nil {
- t.Fatal("run should have failed")
+ errChan <- fmt.Errorf("run should have failed")
+ return
} else if !strings.Contains(out, expected) {
- t.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):
- 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.Assert(err, check.IsNil)
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 is out of range ", portnum, binding, out)
+ 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()
-
- done := make(chan struct{})
+func (s *DockerSuite) TestRunOOMExitCode(c *check.C) {
+ errChan := make(chan error)
go func() {
- defer close(done)
-
- runCmd := exec.Command(dockerBinary, "run", "-m", "4MB", "busybox", "sh", "-c", "x=a; while true; do x=$x$x; 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 {
- t.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 <-time.After(3 * time.Second):
- t.Fatal("Timeout waiting for container to die on OOM")
+ case err := <-errChan:
+ c.Assert(err, check.IsNil)
+ case <-time.After(30 * time.Second):
+ 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)
- }
- out = strings.Trim(out, "\r\n")
+ out, err := inspectField("test", "HostConfig.RestartPolicy.Name")
+ c.Assert(err, check.IsNil)
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", 5); err != nil {
- t.Fatal(err)
+ if err := waitInspect(id, "{{ .State.Restarting }} {{ .State.Running }}", "false false", 10); err != nil {
+ c.Fatal(err)
}
count, err := inspectField(id, "RestartCount")
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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()
+ 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 {
- 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) 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 {
- 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")
}
-func TestRunPidHostWithChildIsKillable(t *testing.T) {
- defer deleteAllContainers()
+// run container with --rm should remove container if exit code != 0
+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 {
+ c.Fatal("Expected docker run to fail", out, err)
+ }
+
+ out, err = getAllContainers()
+ if err != nil {
+ c.Fatal(out, err)
+ }
+
+ if out != "" {
+ c.Fatal("Expected not to have containers", out)
+ }
+}
+
+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 {
+ c.Fatal("Expected docker run to fail", out, err)
+ }
+
+ out, err = getAllContainers()
+ if err != nil {
+ c.Fatal(out, err)
+ }
+
+ if out != "" {
+ c.Fatal("Expected not to have containers", out)
+ }
+}
+
+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)
@@ -3428,80 +3099,115 @@ func TestRunPidHostWithChildIsKillable(t *testing.T) {
}()
select {
case err := <-errchan:
- if err != nil {
- t.Fatal(err)
- }
+ c.Assert(err, check.IsNil)
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")
}
-func TestRunWithTooSmallMemoryLimit(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunWithTooSmallMemoryLimit(c *check.C) {
// 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)
+ c.Fatalf("expected run to fail when using too low a memory limit: %q", out)
}
-
- logDone("run - can't set too low memory limit")
}
-func TestRunWriteToProcAsound(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunWriteToProcAsound(c *check.C) {
code, err := runCommand(exec.Command(dockerBinary, "run", "busybox", "sh", "-c", "echo 111 >> /proc/asound/version"))
if err == nil || code == 0 {
- t.Fatal("standard container should not be able to write to /proc/asound")
+ c.Fatal("standard container should not be able to write to /proc/asound")
}
- logDone("run - ro write to /proc/asound")
}
-func TestRunReadProcTimer(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestRunReadProcTimer(c *check.C) {
+ testRequires(c, NativeExecDriver)
out, code, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "busybox", "cat", "/proc/timer_stats"))
if err != nil || code != 0 {
- t.Fatal(err)
+ c.Fatal(err)
}
if strings.Trim(out, "\n ") != "" {
- t.Fatalf("expected to receive no output from /proc/timer_stats but received %q", out)
+ c.Fatalf("expected to receive no output from /proc/timer_stats but received %q", out)
}
- logDone("run - read /proc/timer_stats")
}
-func TestRunReadProcLatency(t *testing.T) {
+func (s *DockerSuite) TestRunReadProcLatency(c *check.C) {
+ testRequires(c, NativeExecDriver)
// some kernels don't have this configured so skip the test if this file is not found
// on the host running the tests.
if _, err := os.Stat("/proc/latency_stats"); err != nil {
- t.Skip()
+ c.Skip("kernel doesnt have latency_stats configured")
return
}
- defer deleteAllContainers()
out, code, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "busybox", "cat", "/proc/latency_stats"))
if err != nil || code != 0 {
- t.Fatal(err)
+ c.Fatal(err)
}
if strings.Trim(out, "\n ") != "" {
- t.Fatalf("expected to receive no output from /proc/latency_stats but received %q", out)
+ c.Fatalf("expected to receive no output from /proc/latency_stats but received %q", out)
}
- logDone("run - read /proc/latency_stats")
}
-func TestMountIntoProc(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestMountIntoProc(c *check.C) {
+ testRequires(c, NativeExecDriver)
code, err := runCommand(exec.Command(dockerBinary, "run", "-v", "/proc//sys", "busybox", "true"))
if err == nil || code == 0 {
- t.Fatal("container should not be able to mount into /proc")
+ c.Fatal("container should not be able to mount into /proc")
}
- logDone("run - mount into proc")
}
-func TestMountIntoSys(t *testing.T) {
- defer deleteAllContainers()
+func (s *DockerSuite) TestMountIntoSys(c *check.C) {
+ testRequires(c, NativeExecDriver)
_, err := runCommand(exec.Command(dockerBinary, "run", "-v", "/sys/fs/cgroup", "busybox", "true"))
if err != nil {
- t.Fatal("container should be able to mount into /sys")
+ c.Fatal("container should be able to mount into /sys/fs/cgroup")
+ }
+}
+
+func (s *DockerSuite) TestTwoContainersInNetHost(c *check.C) {
+ dockerCmd(c, "run", "-d", "--net=host", "--name=first", "busybox", "top")
+ dockerCmd(c, "run", "-d", "--net=host", "--name=second", "busybox", "top")
+ dockerCmd(c, "stop", "first")
+ dockerCmd(c, "stop", "second")
+}
+
+func (s *DockerSuite) TestRunUnshareProc(c *check.C) {
+ testRequires(c, Apparmor, NativeExecDriver)
+
+ name := "acidburn"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "--mount-proc=/proc", "mount")
+ if out, _, err := runCommandWithOutput(runCmd); err == nil || !strings.Contains(out, "Permission denied") {
+ c.Fatalf("unshare should have failed with permission denied, got: %s, %v", out, err)
+ }
+
+ name = "cereal"
+ runCmd = exec.Command(dockerBinary, "run", "--name", name, "jess/unshare", "unshare", "-p", "-m", "-f", "-r", "mount", "-t", "proc", "none", "/proc")
+ if out, _, err := runCommandWithOutput(runCmd); err == nil || !strings.Contains(out, "Permission denied") {
+ c.Fatalf("unshare should have failed with permission denied, got: %s, %v", out, err)
+ }
+}
+
+func (s *DockerSuite) TestRunPublishPort(c *check.C) {
+ out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "run", "-d", "--name", "test", "--expose", "8080", "busybox", "top"))
+ c.Assert(err, check.IsNil)
+ out, _, err = runCommandWithOutput(exec.Command(dockerBinary, "port", "test"))
+ c.Assert(err, check.IsNil)
+ out = strings.Trim(out, "\r\n")
+ if out != "" {
+ c.Fatalf("run without --publish-all should not publish port, out should be nil, but got: %s", out)
+ }
+}
+
+// Issue #10184.
+func (s *DockerSuite) TestDevicePermissions(c *check.C) {
+ testRequires(c, NativeExecDriver)
+ const permissions = "crw-rw-rw-"
+ out, status := dockerCmd(c, "run", "--device", "/dev/fuse:/dev/fuse:mrw", "busybox:latest", "ls", "-l", "/dev/fuse")
+ if status != 0 {
+ c.Fatalf("expected status 0, got %d", status)
+ }
+ if !strings.HasPrefix(out, permissions) {
+ c.Fatalf("output should begin with %q, got %q", permissions, out)
}
- logDone("run - mount into sys")
}
diff --git a/integration-cli/docker_cli_run_unix_test.go b/integration-cli/docker_cli_run_unix_test.go
index 9327ac240..59b623162 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"
@@ -10,58 +11,52 @@ 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
cmd.Stdout = tty
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)
}
+ ch := make(chan error)
go func() {
- if err := cmd.Wait(); err != nil {
- t.Fatalf("wait err=%v", err)
- }
+ ch <- cmd.Wait()
close(ch)
}()
select {
case <-time.After(10 * time.Second):
- t.Fatal("command timeout")
- case <-ch:
+ c.Fatal("command timeout")
+ case err := <-ch:
+ if err != nil {
+ c.Fatalf("wait err=%v", err)
+ }
}
}
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)
@@ -69,68 +64,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)
@@ -141,24 +130,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 {
@@ -168,8 +155,98 @@ 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)
+ }
+}
+
+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 {
+ c.Fatal(err, out)
}
- logDone("run - cgroup parent with absolute cgroup path")
+ if actual := strings.Trim(out, "\r\n"); !strings.Contains(out, "timer") {
+ 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 {
+ c.Fatal(err, out)
+ }
+
+ if actual := strings.Trim(out, "\r\n"); !strings.Contains(out, "seq") {
+ c.Fatalf("expected output /dev/othersnd/seq, received %s", actual)
+ }
+}
+
+// TestRunDetach checks attaching and detaching with the escape sequence.
+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 {
+ c.Fatal(err)
+ }
+ cpty, tty, err := pty.Open()
+ if err != nil {
+ c.Fatal(err)
+ }
+ defer cpty.Close()
+ cmd.Stdin = tty
+ if err := cmd.Start(); err != nil {
+ c.Fatal(err)
+ }
+ if err := waitRun(name); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := cpty.Write([]byte("hello\n")); err != nil {
+ c.Fatal(err)
+ }
+
+ out, err := bufio.NewReader(stdout).ReadString('\n')
+ if err != nil {
+ c.Fatal(err)
+ }
+ if strings.TrimSpace(out) != "hello" {
+ c.Fatalf("expected 'hello', got %q", out)
+ }
+
+ // escape sequence
+ if _, err := cpty.Write([]byte{16}); err != nil {
+ c.Fatal(err)
+ }
+ time.Sleep(100 * time.Millisecond)
+ if _, err := cpty.Write([]byte{17}); err != nil {
+ c.Fatal(err)
+ }
+
+ ch := make(chan struct{})
+ go func() {
+ cmd.Wait()
+ ch <- struct{}{}
+ }()
+
+ running, err := inspectField(name, "State.Running")
+ if err != nil {
+ c.Fatal(err)
+ }
+ if running != "true" {
+ c.Fatal("expected container to still be running")
+ }
+
+ go func() {
+ exec.Command(dockerBinary, "kill", name).Run()
+ }()
+
+ select {
+ case <-ch:
+ case <-time.After(10 * time.Millisecond):
+ c.Fatal("timed out waiting for container to exit")
+ }
}
diff --git a/integration-cli/docker_cli_save_load_test.go b/integration-cli/docker_cli_save_load_test.go
index 1dd71ee5f..c53889039 100644
--- a/integration-cli/docker_cli_save_load_test.go
+++ b/integration-cli/docker_cli_save_load_test.go
@@ -9,37 +9,31 @@ 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) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+func (s *DockerSuite) TestSaveXzAndLoadRepoStdout(c *check.C) {
+ name := "test-save-xz-and-load-repo-stdout"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "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 := stripTrailingCharacters(out)
-
repoName := "foobar-save-load-test-xz-gz"
- 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)
- }
-
- commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName)
+ commitCmd := exec.Command(dockerBinary, "commit", name, 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)
+ 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 +41,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,48 +49,40 @@ 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) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+func (s *DockerSuite) TestSaveXzGzAndLoadRepoStdout(c *check.C) {
+ name := "test-save-xz-gz-and-load-repo-stdout"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "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 := stripTrailingCharacters(out)
-
repoName := "foobar-save-load-test-xz-gz"
- 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)
- }
-
- commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName)
+ commitCmd := exec.Command(dockerBinary, "commit", name, 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)
+ 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 +90,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,90 +99,82 @@ 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 := stripTrailingCharacters(out)
+ cleanedImageID := strings.TrimSpace(out)
out, _, err = runCommandPipelineWithOutput(
exec.Command(dockerBinary, "save", fmt.Sprintf("%v:latest", repoName)),
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 := stripTrailingCharacters(out)
+ cleanedLongImageID := strings.TrimSpace(out)
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 := stripTrailingCharacters(out)
+ cleanedShortImageID := strings.TrimSpace(out)
saveCmd := exec.Command(dockerBinary, "save", cleanedShortImageID)
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,40 +182,31 @@ 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) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+func (s *DockerSuite) TestSaveAndLoadRepoFlags(c *check.C) {
+ name := "test-save-and-load-repo-flags"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "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 := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
-
repoName := "foobar-save-load-test"
- 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)
- }
-
- commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName)
+ commitCmd := exec.Command(dockerBinary, "commit", name, 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)
+ 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,39 +214,35 @@ 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")
// Make two images
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")
out, _, err = runCommandPipelineWithOutput(
exec.Command(dockerBinary, "save", fmt.Sprintf("%v-one", repoName), fmt.Sprintf("%v-two:latest", repoName)),
@@ -285,13 +250,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,16 +264,15 @@ 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 := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
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 := stripTrailingCharacters(out)
+ imageID := strings.TrimSpace(out)
return imageID
}
@@ -318,9 +281,7 @@ func TestSaveRepoWithMultipleImages(t *testing.T) {
tagBar := repoName + ":bar"
idFoo := makeImage("busybox:latest", tagFoo)
- defer deleteImages(idFoo)
idBar := makeImage("busybox:latest", tagBar)
- defer deleteImages(idBar)
deleteImages(repoName)
@@ -331,61 +292,59 @@ 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(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"))
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(stripTrailingCharacters(out), "\n"), idFoo, idBar)
+ expected := append(strings.Split(strings.TrimSpace(out), "\n"), idFoo, idBar)
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("archive 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)
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
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 +355,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 +365,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 +376,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 c6704877a..5a6f5809a 100644
--- a/integration-cli/docker_cli_save_load_unix_test.go
+++ b/integration-cli/docker_cli_save_load_unix_test.go
@@ -4,99 +4,95 @@ package main
import (
"bytes"
- "fmt"
+ "io/ioutil"
"os"
"os/exec"
- "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) {
- runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "true")
+func (s *DockerSuite) TestSaveAndLoadRepoStdout(c *check.C) {
+ name := "test-save-and-load-repo-stdout"
+ runCmd := exec.Command(dockerBinary, "run", "--name", name, "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 := stripTrailingCharacters(out)
-
repoName := "foobar-save-load-test"
- 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)
- }
-
- commitCmd := exec.Command(dockerBinary, "commit", cleanedContainerID, repoName)
+ commitCmd := exec.Command(dockerBinary, "commit", name, 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)
+ 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)
+ tmpFile, err := ioutil.TempFile("", "foobar-save-load-test.tar")
+ c.Assert(err, check.IsNil)
+ defer os.Remove(tmpFile.Name())
+
+ saveCmd := exec.Command(dockerBinary, "save", repoName)
+ saveCmd.Stdout = tmpFile
+
+ if _, err = runCommand(saveCmd); err != nil {
+ c.Fatalf("failed to save repo: %v", err)
}
+ tmpFile, err = os.Open(tmpFile.Name())
+ c.Assert(err, check.IsNil)
+
deleteImages(repoName)
- loadCmdFinal := `cat /tmp/foobar-save-load-test.tar | docker load`
- loadCmd := exec.Command("bash", "-c", loadCmdFinal)
+ loadCmd := exec.Command(dockerBinary, "load")
+ loadCmd.Stdin = tmpFile
+
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)
deleteImages(repoName)
- 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)
+ 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 - do not save to a tty")
}
diff --git a/integration-cli/docker_cli_search_test.go b/integration-cli/docker_cli_search_test.go
index fafb5df75..da298a1e0 100644
--- a/integration-cli/docker_cli_search_test.go
+++ b/integration-cli/docker_cli_search_test.go
@@ -3,20 +3,103 @@ 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) {
+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.'")
+ }
+
+}
+
+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 {
+ c.Fatalf("Should not get right information: %s, %v", out, err)
+ }
+
+ if !strings.Contains(out, "invalid value") {
+ 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 {
+ c.Fatalf("Should not get right information: %s, %v", out, err)
+ }
+
+ if !strings.Contains(out, "invalid value") {
+ c.Fatal("couldn't find the invalid value warning")
+ }
+
+}
+
+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 {
+ c.Fatalf("failed to get search help information: %s, %v", out, err)
+ }
+
+ if !strings.Contains(out, "Usage: docker search [OPTIONS] TERM") {
+ 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 {
+ 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 {
+ 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 ") {
+ 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 {
+ 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]") {
+ 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 {
+ c.Fatalf("failed to search with stars&automated&no-trunc options on the central registry: %s, %v", out, err)
}
- logDone("search - search for repositories named (or containing) 'Busybox base image.'")
}
diff --git a/integration-cli/docker_cli_start_test.go b/integration-cli/docker_cli_start_test.go
index 3ec04c916..047582673 100644
--- a/integration-cli/docker_cli_start_test.go
+++ b/integration-cli/docker_cli_start_test.go
@@ -4,253 +4,200 @@ 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{})
+ 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 {
- t.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):
- 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 = stripTrailingCharacters(out)
+ out = strings.TrimSpace(out)
// 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 TestStartSilentAttach(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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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.Assert(err, check.IsNil)
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()
-
- // Create the first data volume
- dockerCmd(t, "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")
- }
-
- // Create the second data volume
- dockerCmd(t, "run", "-d", "--name", "data_after", "-v", "/bar", "busybox")
-
- // Now, all the volumes should be there
- dockerCmd(t, "start", "consumer")
-
- // 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)
- }
-
- 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)
- }
- out = strings.Trim(out, "\r\n")
+ out, err := inspectField("parent", "State.Running")
+ c.Assert(err, check.IsNil)
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
+ // 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)
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)
- }
- out = strings.Trim(out, "\r\n")
+ out, err := inspectField(container, "State.Running")
+ c.Assert(err, check.IsNil)
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 +205,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,22 +214,19 @@ 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")
}
}
// 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)
+ out, err := inspectField(container, "State.Running")
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_start_volume_driver_unix_test.go b/integration-cli/docker_cli_start_volume_driver_unix_test.go
new file mode 100644
index 000000000..1cc90801d
--- /dev/null
+++ b/integration-cli/docker_cli_start_volume_driver_unix_test.go
@@ -0,0 +1,249 @@
+// +build experimental
+// +build !windows
+
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/go-check/check"
+)
+
+func init() {
+ check.Suite(&DockerExternalVolumeSuite{
+ ds: &DockerSuite{},
+ })
+}
+
+type eventCounter struct {
+ activations int
+ creations int
+ removals int
+ mounts int
+ unmounts int
+ paths int
+}
+
+type DockerExternalVolumeSuite struct {
+ server *httptest.Server
+ ds *DockerSuite
+ d *Daemon
+ ec *eventCounter
+}
+
+func (s *DockerExternalVolumeSuite) SetUpTest(c *check.C) {
+ s.d = NewDaemon(c)
+ s.ds.SetUpTest(c)
+ s.ec = &eventCounter{}
+
+}
+
+func (s *DockerExternalVolumeSuite) TearDownTest(c *check.C) {
+ s.d.Stop()
+ s.ds.TearDownTest(c)
+}
+
+func (s *DockerExternalVolumeSuite) SetUpSuite(c *check.C) {
+ mux := http.NewServeMux()
+ s.server = httptest.NewServer(mux)
+
+ type pluginRequest struct {
+ name string
+ }
+
+ mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
+ s.ec.activations++
+
+ w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, `{"Implements": ["VolumeDriver"]}`)
+ })
+
+ mux.HandleFunc("/VolumeDriver.Create", func(w http.ResponseWriter, r *http.Request) {
+ s.ec.creations++
+
+ w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, `{}`)
+ })
+
+ mux.HandleFunc("/VolumeDriver.Remove", func(w http.ResponseWriter, r *http.Request) {
+ s.ec.removals++
+
+ w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, `{}`)
+ })
+
+ mux.HandleFunc("/VolumeDriver.Path", func(w http.ResponseWriter, r *http.Request) {
+ s.ec.paths++
+
+ var pr pluginRequest
+ if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+
+ p := hostVolumePath(pr.name)
+
+ w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, fmt.Sprintf("{\"Mountpoint\": \"%s\"}", p))
+ })
+
+ mux.HandleFunc("/VolumeDriver.Mount", func(w http.ResponseWriter, r *http.Request) {
+ s.ec.mounts++
+
+ var pr pluginRequest
+ if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+
+ p := hostVolumePath(pr.name)
+ if err := os.MkdirAll(p, 0755); err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+
+ if err := ioutil.WriteFile(filepath.Join(p, "test"), []byte(s.server.URL), 0644); err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+
+ w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, fmt.Sprintf("{\"Mountpoint\": \"%s\"}", p))
+ })
+
+ mux.HandleFunc("/VolumeDriver.Unmount", func(w http.ResponseWriter, r *http.Request) {
+ s.ec.unmounts++
+
+ var pr pluginRequest
+ if err := json.NewDecoder(r.Body).Decode(&pr); err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+
+ p := hostVolumePath(pr.name)
+ if err := os.RemoveAll(p); err != nil {
+ http.Error(w, err.Error(), 500)
+ }
+
+ w.Header().Set("Content-Type", "appplication/vnd.docker.plugins.v1+json")
+ fmt.Fprintln(w, `{}`)
+ })
+
+ if err := os.MkdirAll("/usr/share/docker/plugins", 0755); err != nil {
+ c.Fatal(err)
+ }
+
+ if err := ioutil.WriteFile("/usr/share/docker/plugins/test-external-volume-driver.spec", []byte(s.server.URL), 0644); err != nil {
+ c.Fatal(err)
+ }
+}
+
+func (s *DockerExternalVolumeSuite) TearDownSuite(c *check.C) {
+ s.server.Close()
+
+ if err := os.RemoveAll("/usr/share/docker/plugins"); err != nil {
+ c.Fatal(err)
+ }
+}
+
+func (s *DockerExternalVolumeSuite) TestStartExternalNamedVolumeDriver(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
+ }
+
+ out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "external-volume-test:/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", "busybox:latest", "cat", "/tmp/external-volume-test/test")
+ if err != nil {
+ c.Fatal(err)
+ }
+
+ if !strings.Contains(out, s.server.URL) {
+ c.Fatalf("External volume mount failed. Output: %s\n", out)
+ }
+
+ p := hostVolumePath("external-volume-test")
+ _, err = os.Lstat(p)
+ if err == nil {
+ c.Fatalf("Expected error checking volume path in host: %s\n", p)
+ }
+
+ if !os.IsNotExist(err) {
+ c.Fatalf("Expected volume path in host to not exist: %s, %v\n", p, err)
+ }
+
+ c.Assert(s.ec.activations, check.Equals, 1)
+ c.Assert(s.ec.creations, check.Equals, 1)
+ c.Assert(s.ec.removals, check.Equals, 1)
+ c.Assert(s.ec.mounts, check.Equals, 1)
+ c.Assert(s.ec.unmounts, check.Equals, 1)
+}
+
+func (s *DockerExternalVolumeSuite) TestStartExternalVolumeUnnamedDriver(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
+ }
+
+ out, err := s.d.Cmd("run", "--rm", "--name", "test-data", "-v", "/tmp/external-volume-test", "--volume-driver", "test-external-volume-driver", "busybox:latest", "cat", "/tmp/external-volume-test/test")
+ if err != nil {
+ c.Fatal(err)
+ }
+
+ if !strings.Contains(out, s.server.URL) {
+ c.Fatalf("External volume mount failed. Output: %s\n", out)
+ }
+
+ c.Assert(s.ec.activations, check.Equals, 1)
+ c.Assert(s.ec.creations, check.Equals, 1)
+ c.Assert(s.ec.removals, check.Equals, 1)
+ c.Assert(s.ec.mounts, check.Equals, 1)
+ c.Assert(s.ec.unmounts, check.Equals, 1)
+}
+
+func (s DockerExternalVolumeSuite) TestStartExternalVolumeDriverVolumesFrom(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := s.d.Cmd("run", "--rm", "--volumes-from", "vol-test1", "--name", "vol-test2", "busybox", "ls", "/tmp"); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := s.d.Cmd("rm", "-f", "vol-test1"); err != nil {
+ c.Fatal(err)
+ }
+
+ c.Assert(s.ec.activations, check.Equals, 1)
+ c.Assert(s.ec.creations, check.Equals, 2)
+ c.Assert(s.ec.removals, check.Equals, 1)
+ c.Assert(s.ec.mounts, check.Equals, 2)
+ c.Assert(s.ec.unmounts, check.Equals, 2)
+}
+
+func (s DockerExternalVolumeSuite) TestStartExternalVolumeDriverDeleteContainer(c *check.C) {
+ if err := s.d.StartWithBusybox(); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := s.d.Cmd("run", "-d", "--name", "vol-test1", "-v", "/foo", "--volume-driver", "test-external-volume-driver", "busybox:latest"); err != nil {
+ c.Fatal(err)
+ }
+
+ if _, err := s.d.Cmd("rm", "-fv", "vol-test1"); err != nil {
+ c.Fatal(err)
+ }
+
+ c.Assert(s.ec.activations, check.Equals, 1)
+ c.Assert(s.ec.creations, check.Equals, 1)
+ c.Assert(s.ec.removals, check.Equals, 1)
+ c.Assert(s.ec.mounts, check.Equals, 1)
+ c.Assert(s.ec.unmounts, check.Equals, 1)
+}
+
+func hostVolumePath(name string) string {
+ return fmt.Sprintf("/var/lib/docker/volumes/%s", name)
+}
diff --git a/integration-cli/docker_cli_stats_test.go b/integration-cli/docker_cli_stats_test.go
new file mode 100644
index 000000000..9c5c07123
--- /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(3 * time.Second):
+ statsCmd.Process.Kill()
+ c.Fatalf("stats did not return immediately when not streaming")
+ }
+}
diff --git a/integration-cli/docker_cli_tag_test.go b/integration-cli/docker_cli_tag_test.go
index 081e239c3..7db21a6f3 100644
--- a/integration-cli/docker_cli_tag_test.go
+++ b/integration-cli/docker_cli_tag_test.go
@@ -1,49 +1,37 @@
package main
import (
- "fmt"
"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) {
- 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)
+func (s *DockerSuite) TestTagUnprefixedRepoByID(c *check.C) {
+ imageID, err := inspectField("busybox", "Id")
+ c.Assert(err, check.IsNil)
+ tagCmd := exec.Command(dockerBinary, "tag", imageID, "testfoobarbaz")
+ if out, _, err := runCommandWithOutput(tagCmd); err != nil {
+ c.Fatal(out, err)
}
-
- cleanedImageID := stripTrailingCharacters(out)
- tagCmd := exec.Command(dockerBinary, "tag", cleanedImageID, "testfoobarbaz")
- if out, _, err = runCommandWithOutput(tagCmd); err != nil {
- t.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"}
@@ -51,32 +39,30 @@ 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) {
- long_tag := makeRandomString(121)
+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:..", 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)
_, _, 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"}
@@ -85,57 +71,73 @@ 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)
- logMessage := fmt.Sprintf("tag - busybox %v", repo)
- logDone(logMessage)
}
}
// 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")
+func (s *DockerSuite) TestTagWithSuffixHyphen(c *check.C) {
+ if err := pullImageIfNotExist("busybox:latest"); err != nil {
+ c.Fatal("couldn't find the busybox:latest image locally and failed to pull it")
+ }
+ // test repository name begin with '-'
+ tagCmd := exec.Command(dockerBinary, "tag", "busybox:latest", "-busybox:test")
+ out, _, err := runCommandWithOutput(tagCmd)
+ if err == nil || !strings.Contains(out, "Invalid repository name (-busybox). Cannot begin or end with a hyphen") {
+ c.Fatal("tag a name begin with '-' should failed")
+ }
+ // test namespace name begin with '-'
+ tagCmd = exec.Command(dockerBinary, "tag", "busybox:latest", "-test/busybox:test")
+ out, _, err = runCommandWithOutput(tagCmd)
+ if err == nil || !strings.Contains(out, "Invalid namespace name (-test). Cannot begin or end with a hyphen") {
+ c.Fatal("tag a name begin with '-' should failed")
+ }
+ // test index name begin wiht '-'
+ tagCmd = exec.Command(dockerBinary, "tag", "busybox:latest", "-index:5000/busybox:test")
+ out, _, err = runCommandWithOutput(tagCmd)
+ if err == nil || !strings.Contains(out, "Invalid index name (-index:5000). Cannot begin or end with a hyphen") {
+ c.Fatal("tag a name begin with '-' should failed")
+ }
}
// 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 +150,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,13 +158,10 @@ 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")
- } else {
- logMessage := fmt.Sprintf("tag official name - busybox %v", name)
- logDone(logMessage)
}
}
@@ -170,11 +169,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")
- logMessage := fmt.Sprintf("tag official name - %v fooo/bar", name)
- logDone(logMessage)
}
}
diff --git a/integration-cli/docker_cli_top_test.go b/integration-cli/docker_cli_top_test.go
index de0d3d2e8..f28e20ba3 100644
--- a/integration-cli/docker_cli_top_test.go
+++ b/integration-cli/docker_cli_top_test.go
@@ -3,106 +3,99 @@ package main
import (
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
-func TestTopMultipleArgs(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "sleep", "20")
+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 := stripTrailingCharacters(out)
- defer deleteContainer(cleanedContainerID)
+ cleanedContainerID := strings.TrimSpace(out)
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) {
- runCmd := exec.Command(dockerBinary, "run", "-i", "-d", "busybox", "sleep", "20")
+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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
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, "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") {
+ c.Fatal("top should've listed `top` in the process list, but failed twice")
+ } else if !strings.Contains(out1, "top") {
+ c.Fatal("top should've listed `top` in the process list, but failed the first time")
+ } else if !strings.Contains(out2, "top") {
+ c.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")
}
-func TestTopPrivileged(t *testing.T) {
- runCmd := exec.Command(dockerBinary, "run", "--privileged", "-i", "-d", "busybox", "sleep", "20")
+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 := stripTrailingCharacters(out)
+ cleanedContainerID := strings.TrimSpace(out)
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, "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") {
+ c.Fatal("top should've listed `top` in the process list, but failed twice")
+ } else if !strings.Contains(out1, "top") {
+ c.Fatal("top should've listed `top` in the process list, but failed the first time")
+ } else if !strings.Contains(out2, "top") {
+ c.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")
}
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
new file mode 100644
index 000000000..c6d469eed
--- /dev/null
+++ b/integration-cli/docker_cli_wait_test.go
@@ -0,0 +1,142 @@
+package main
+
+import (
+ "bytes"
+ "os/exec"
+ "strings"
+ "time"
+
+ "github.com/go-check/check"
+)
+
+// non-blocking wait with 0 exit code
+func (s *DockerSuite) TestWaitNonBlockedExitZero(c *check.C) {
+
+ runCmd := exec.Command(dockerBinary, "run", "-d", "busybox", "sh", "-c", "true")
+ out, _, err := runCommandWithOutput(runCmd)
+ if err != nil {
+ c.Fatal(out, err)
+ }
+ containerID := strings.TrimSpace(out)
+
+ status := "true"
+ for i := 0; status != "false"; i++ {
+ status, err = inspectField(containerID, "State.Running")
+ c.Assert(err, check.IsNil)
+
+ time.Sleep(time.Second)
+ if i >= 60 {
+ c.Fatal("Container should have stopped by now")
+ }
+ }
+
+ runCmd = exec.Command(dockerBinary, "wait", containerID)
+ out, _, err = runCommandWithOutput(runCmd)
+
+ if err != nil || strings.TrimSpace(out) != "0" {
+ c.Fatal("failed to set up container", out, err)
+ }
+
+}
+
+// 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' TERM; while true; do sleep 0.01; done")
+ containerID := strings.TrimSpace(out)
+
+ 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) != "0" {
+ c.Fatalf("expected exit 0, got %s", status)
+ }
+ case <-time.After(2 * time.Second):
+ c.Fatal("timeout waiting for `docker wait` to exit")
+ }
+
+}
+
+// non-blocking wait with random exit code
+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 {
+ c.Fatal(out, err)
+ }
+ containerID := strings.TrimSpace(out)
+
+ status := "true"
+ for i := 0; status != "false"; i++ {
+ status, err = inspectField(containerID, "State.Running")
+ c.Assert(err, check.IsNil)
+
+ time.Sleep(time.Second)
+ if i >= 60 {
+ c.Fatal("Container should have stopped by now")
+ }
+ }
+
+ 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)
+ }
+
+}
+
+// blocking wait with random exit code
+func (s *DockerSuite) TestWaitBlockedExitRandom(c *check.C) {
+ 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)
+ }
+ if err := waitRun(containerID); err != nil {
+ c.Fatal(err)
+ }
+
+ 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() {
+ chWait <- waitCmd.Wait()
+ }()
+
+ dockerCmd(c, "stop", containerID)
+
+ select {
+ 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")
+ }
+}
diff --git a/integration-cli/docker_test_vars.go b/integration-cli/docker_test_vars.go
index 9cb28b274..ed394d26d 100644
--- a/integration-cli/docker_test_vars.go
+++ b/integration-cli/docker_test_vars.go
@@ -18,7 +18,6 @@ var (
dockerBasePath = "/var/lib/docker"
volumesConfigPath = dockerBasePath + "/volumes"
- volumesStoragePath = dockerBasePath + "/vfs/dir"
containerStoragePath = dockerBasePath + "/containers"
runtimePath = "/var/run/docker"
diff --git a/integration-cli/docker_utils.go b/integration-cli/docker_utils.go
index 10cc6c921..e9c0df6eb 100644
--- a/integration-cli/docker_utils.go
+++ b/integration-cli/docker_utils.go
@@ -18,15 +18,17 @@ import (
"path/filepath"
"strconv"
"strings"
- "testing"
"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"
)
// 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
@@ -35,32 +37,50 @@ 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.
// 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))
+ dir := filepath.Join(dest, fmt.Sprintf("d%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)
+ }
+
+ userlandProxy := true
+ if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" {
+ if val, err := strconv.ParseBool(env); err != nil {
+ userlandProxy = val
+ }
}
return &Daemon{
- t: t,
+ c: c,
folder: daemonFolder,
storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"),
execDriver: os.Getenv("DOCKER_EXECDRIVER"),
+ userlandProxy: userlandProxy,
}
}
@@ -69,7 +89,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{
@@ -77,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
@@ -103,7 +124,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
@@ -117,7 +138,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)
}()
@@ -127,7 +148,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")
@@ -146,7 +167,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)
@@ -154,10 +175,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
}
}
@@ -184,7 +205,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
}
@@ -216,7 +237,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
}
}
@@ -229,10 +250,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)
}
@@ -240,7 +261,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
}
@@ -267,12 +288,20 @@ 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()
}
func daemonHost() string {
- daemonUrlStr := "unix://" + api.DEFAULTUNIXSOCKET
+ daemonUrlStr := "unix://" + opts.DefaultUnixSocket
if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" {
daemonUrlStr = daemonHostVar
}
@@ -297,58 +326,62 @@ 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")
+ res, body, err := sockRequestRaw(method, endpoint, jsonData, "application/json")
+ if err != nil {
+ b, _ := ioutil.ReadAll(body)
+ return -1, b, err
+ }
+ var b []byte
+ b, err = readBody(body)
+ return res.StatusCode, b, err
}
-func sockRequestRaw(method, endpoint string, data io.Reader, ct string) ([]byte, 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 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)
- defer client.Close()
req, err := http.NewRequest(method, endpoint, data)
if err != nil {
- return nil, fmt.Errorf("could not create new request: %v", err)
+ client.Close()
+ return nil, 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 {
- return 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)
+ client.Close()
+ 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 ioutil.ReadAll(resp.Body)
+ return resp, body, nil
+}
+
+func readBody(b io.ReadCloser) ([]byte, error) {
+ defer b.Close()
+ return ioutil.ReadAll(b)
}
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))
+ 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 {
err = fmt.Errorf("failed to remove container: `docker rm` exit is non-zero")
@@ -380,6 +413,58 @@ 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]
+ // just for case if we have dangling images in tested daemon
+ if imgTag != ":" {
+ 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)
@@ -393,11 +478,13 @@ 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
- } else {
- return []string{out}, err
}
+ return []string{out}, err
}
func unpauseContainer(container string) error {
@@ -431,8 +518,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)
@@ -440,7 +526,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
}
@@ -465,12 +550,12 @@ func pullImageIfNotExist(image string) (err error) {
return
}
-func dockerCmd(t *testing.T, args ...string) (string, int, error) {
+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, err
+ return out, status
}
// execute a docker command with a timeout
@@ -483,7 +568,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)
@@ -504,16 +589,21 @@ func dockerCmdInDirWithTimeout(timeout time.Duration, path string, args ...strin
return out, status, err
}
-func findContainerIP(t *testing.T, 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 {
- t.Fatal(err, out)
+ c.Fatal(err, out)
}
return strings.Trim(out, " \r\n'")
}
+func (d *Daemon) findContainerIP(id string) string {
+ return findContainerIP(d.c, id, "--host", d.sock())
+}
+
func getContainerCount() (int, error) {
const containers = "Containers:"
@@ -526,7 +616,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)
@@ -597,7 +687,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 {
@@ -696,8 +785,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
@@ -766,14 +855,14 @@ 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, 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(c, "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")
@@ -972,28 +1061,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)
@@ -1038,36 +1127,35 @@ 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.Fatal("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
}
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) *testRegistryV2 {
+ 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.
@@ -1079,10 +1167,9 @@ 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() }
+ return reg
}
// appendBaseEnv appends the minimum set of environment variables to exec the
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-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 346d0cdf6..fc4f5ee95 100644
--- a/integration-cli/requirements.go
+++ b/integration-cli/requirements.go
@@ -3,10 +3,13 @@ package main
import (
"encoding/json"
"fmt"
+ "io/ioutil"
"log"
+ "net/http"
"os/exec"
"strings"
- "testing"
+
+ "github.com/go-check/check"
)
type TestCondition func() bool
@@ -32,6 +35,23 @@ 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.",
+ }
+ Apparmor = TestRequirement{
+ func() bool {
+ buf, err := ioutil.ReadFile("/sys/module/apparmor/parameters/enabled")
+ return err == nil && len(buf) > 1 && buf[0] == 'Y'
+ },
+ "Test requires apparmor is enabled.",
+ }
RegistryHosting = TestRequirement{
func() bool {
// for now registry binary is built only if we're running inside
@@ -46,8 +66,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)
}
@@ -66,7 +86,6 @@ var (
},
"Test requires the native (libcontainer) exec driver.",
}
-
NotOverlay = TestRequirement{
func() bool {
cmd := exec.Command("grep", "^overlay / overlay", "/proc/mounts")
@@ -81,10 +100,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 85e6f1ccd..0ec4e758c 100644
--- a/integration-cli/utils.go
+++ b/integration-cli/utils.go
@@ -1,13 +1,12 @@
package main
import (
+ "archive/tar"
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
- "math/rand"
- "net/http"
"net/http/httptest"
"os"
"os/exec"
@@ -17,13 +16,13 @@ import (
"syscall"
"time"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
+ "github.com/docker/docker/pkg/stringutils"
)
func getExitCode(err error) (int, error) {
exitCode := 0
if exiterr, ok := err.(*exec.ExitError); ok {
- if procExit := exiterr.Sys().(syscall.WaitStatus); ok {
+ if procExit, ok := exiterr.Sys().(syscall.WaitStatus); ok {
return procExit.ExitStatus(), nil
}
}
@@ -44,12 +43,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
}
@@ -141,6 +142,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)
}
@@ -165,17 +167,8 @@ func runCommandPipelineWithOutput(cmds ...*exec.Cmd) (output string, exitCode in
return runCommandWithOutput(cmds[len(cmds)-1])
}
-func logDone(message string) {
- fmt.Printf("[PASSED]: %s\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 {
+ if err := json.Unmarshal(data, result); err != nil {
return err
}
@@ -215,7 +208,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)
@@ -272,50 +274,10 @@ type FileServer struct {
*httptest.Server
}
-func fileServer(files map[string]string) (*FileServer, error) {
- var handler http.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
- if filePath, found := files[r.URL.Path]; found {
- http.ServeFile(w, r, filePath)
- } else {
- http.Error(w, http.StatusText(404), 404)
- }
- }
-
- for _, file := range files {
- if _, err := os.Stat(file); err != nil {
- return nil, err
- }
- }
- server := httptest.NewServer(handler)
- return &FileServer{
- Server: server,
- }, nil
-}
-
-func copyWithCP(source, target string) error {
- copyCmd := exec.Command("cp", "-rp", source, target)
- out, exitCode, err := runCommandWithOutput(copyCmd)
- if err != nil || exitCode != 0 {
- return fmt.Errorf("failed to copy: error: %q ,output: %q", err, out)
- }
- 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.
@@ -354,3 +316,26 @@ func parseCgroupPaths(procCgroupData string) map[string]string {
}
return cgroupPaths
}
+
+type channelBuffer struct {
+ c chan []byte
+}
+
+func (c *channelBuffer) Write(b []byte) (int, error) {
+ c.c <- b
+ return len(b), nil
+}
+
+func (c *channelBuffer) Close() error {
+ close(c.c)
+ return nil
+}
+
+func (c *channelBuffer) ReadTimeout(p []byte, n time.Duration) (int, error) {
+ select {
+ case b := <-c.c:
+ return copy(p[0:], b), nil
+ case <-time.After(n):
+ return -1, fmt.Errorf("timeout reading from channel")
+ }
+}
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 e978c311b..000000000
--- a/integration/api_test.go
+++ /dev/null
@@ -1,991 +0,0 @@
-package docker
-
-import (
- "bufio"
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "net/http"
- "net/http/httptest"
- "strings"
- "testing"
- "time"
-
- "github.com/docker/docker/api"
- "github.com/docker/docker/api/server"
- "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 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()
-
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"/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}
- b.Install()
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- // Create a container and remove a file
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"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()
-
- configJSON, err := json.Marshal(&runconfig.Config{
- Image: unitTestImageID,
- Memory: 33554432,
- Cmd: []string{"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()
-
- configJSON, err := json.Marshal(&runconfig.Config{
- Image: unitTestImageID,
- Memory: 33554432,
- Cmd: []string{"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) {
- 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.Config.Cpuset != "" {
- t.Fatalf("Cpuset should have been empty - instead its:" + c.Config.Cpuset)
- }
-}
-
-func TestPostContainersKill(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- containerID := createTestContainer(eng,
- &runconfig.Config{
- Image: unitTestImageID,
- Cmd: []string{"/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: []string{"/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: []string{"/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: []string{"/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: []string{"/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: []string{"/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: []string{"/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 hearder 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, "")
-
- if err := eng.Job("tag", unitTestImageName, "test", "test").Run(); err != nil {
- t.Fatal(err)
- }
-
- 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")))
- }
-
- 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)
- }
-
- outs := engine.NewTable("Created", 0)
- if _, err := outs.ReadListFrom(r2.Body.Bytes()); err != nil {
- t.Fatal(err)
- }
- if len(outs.Data) != 1 {
- t.Fatalf("Expected %d event (untagged), got %d", 1, len(outs.Data))
- }
- images = getImages(eng, t, false, "")
-
- if images.Len() != initialImages.Len() {
- t.Errorf("Expected %d image, %d found", initialImages.Len(), images.Len())
- }
-}
-
-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: []string{"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
- // http://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, execpted 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/commands_test.go b/integration/commands_test.go
deleted file mode 100644
index 6c6ad0e71..000000000
--- a/integration/commands_test.go
+++ /dev/null
@@ -1,436 +0,0 @@
-package docker
-
-import (
- "bufio"
- "fmt"
- "io"
- "io/ioutil"
- "strings"
- "testing"
- "time"
-
- 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/term"
- "github.com/kr/pty"
-)
-
-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 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
-
- 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 occured 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
-}
-
-// 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()
- 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()
- 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(common.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()
- 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 {
- log.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")
- 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)
- }
-}
diff --git a/integration/container_test.go b/integration/container_test.go
deleted file mode 100644
index b6cbfd096..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: []string{"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: []string{"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: []string{"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: []string{"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: []string{"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("Unexecpted 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/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
deleted file mode 100644
index 153c38562..000000000
--- a/integration/runtime_test.go
+++ /dev/null
@@ -1,915 +0,0 @@
-package docker
-
-import (
- "bytes"
- "fmt"
- "io"
- std_log "log"
- "net"
- "net/url"
- "os"
- "path/filepath"
- "runtime"
- "strconv"
- "strings"
- "syscall"
- "testing"
- "time"
-
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/daemon"
- "github.com/docker/docker/daemon/execdriver"
- "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/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 (
- // 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
- 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)
- }
- job := eng.Job("images")
- images, err := job.Stdout.AddTable()
- 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()
- }
- }
- 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 {
- log.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)
- }
- 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)
- }
- defer dst.Close()
- if _, err := io.Copy(dst, src); err != nil {
- log.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()
- spawnLegitHttpsDaemon()
- spawnRogueHttpsDaemon()
- startFds, startGoroutines = utils.GetTotalUsedFds(), runtime.NumGoroutine()
-}
-
-func setupBaseImage() {
- eng := newTestEngine(std_log.New(os.Stderr, "", 0), false, unitTestStoreBase)
- job := eng.Job("image_inspect", unitTestImageName)
- img, _ := job.Stdout.AddEnv()
- // 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 {
- log.Fatalf("Unable to pull the test image: %s", err)
- }
- }
-}
-
-func spawnGlobalDaemon() {
- if globalDaemon != nil {
- log.Debugf("Global daemon already exists. Skipping.")
- return
- }
- t := std_log.New(os.Stderr, "", 0)
- eng := NewTestEngine(t)
- globalEngine = eng
- globalDaemon = mkDaemonFromEngine(eng, t)
-
- // Spawn a Daemon
- go func() {
- log.Debugf("Spawning global daemon for integration tests")
- listenURL := &url.URL{
- Scheme: testDaemonProto,
- Host: testDaemonAddr,
- }
- 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)
- }
- }()
-
- // Give some time to ListenAndServer to actually start
- // FIXME: use inmem transports instead of tcp
- time.Sleep(time.Second)
-
- if err := eng.Job("acceptconnections").Run(); err != nil {
- log.Fatalf("Unable to accept connections for test api: %s", err)
- }
-}
-
-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)
-
- // Spawn a Daemon
- go func() {
- log.Debugf("Spawning https daemon for integration tests")
- listenURL := &url.URL{
- Scheme: testDaemonHttpsProto,
- Host: addr,
- }
- job := eng.Job("serveapi", listenURL.String())
- job.SetenvBool("Logging", true)
- job.SetenvBool("Tls", true)
- job.SetenvBool("TlsVerify", true)
- job.Setenv("TlsCa", cacert)
- job.Setenv("TlsCert", cert)
- job.Setenv("TlsKey", key)
- if err := job.Run(); err != nil {
- log.Fatalf("Unable to spawn the test daemon: %s", err)
- }
- }()
-
- // Give some time to ListenAndServer to actually start
- time.Sleep(time.Second)
-
- if err := eng.Job("acceptconnections").Run(); err != nil {
- log.Fatalf("Unable to accept connections for test api: %s", err)
- }
- return eng
-}
-
-// FIXME: test that ImagePull(json=true) send correct json output
-
-func GetTestImage(daemon *daemon.Daemon) *image.Image {
- imgs, err := daemon.Graph().Map()
- if err != nil {
- log.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)
- 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: []string{"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
- testContainer, _, _ := daemon.Create(
- &runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"ls", "-al"},
- },
- &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)) {
- 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: []string{},
- },
- &runconfig.HostConfig{},
- "",
- ); err == nil {
- t.Fatal("Builder.Create should throw an error when Cmd is empty")
- }
-
- config := &runconfig.Config{
- Image: GetTestImage(daemon).ID,
- Cmd: []string{"/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: []string{"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: []string{"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
- outputBuffer = bytes.NewBuffer(nil)
- 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{}{}
-
- 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)
- // 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)
-
- }
-
- jobStart := eng.Job("start", id)
- portBindings := make(map[nat.Port][]nat.PortBinding)
- portBindings[p] = []nat.PortBinding{
- {},
- }
- if err := jobStart.SetenvJson("PortsBindings", portBindings); err != nil {
- t.Fatal(err)
- }
- if err := jobStart.Run(); 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 {
- log.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)
- }
-}
-
-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)
- }
-
- var outputBuffer = bytes.NewBuffer(nil)
- job := eng.Job("create", test.Name)
- if err := job.ImportEnv(config); err != nil {
- t.Fatal(err)
- }
- job.Stdout.Add(outputBuffer)
- if err := job.Run(); err != nil {
- if !test.Valid {
- continue
- }
- t.Fatal(err)
- }
-
- container, err := daemon.Get(engine.Tail(outputBuffer, 1))
- 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: []string{"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("Conttainer 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/server_test.go b/integration/server_test.go
deleted file mode 100644
index 6d12ad35a..000000000
--- a/integration/server_test.go
+++ /dev/null
@@ -1,300 +0,0 @@
-package docker
-
-import (
- "bytes"
- "testing"
- "time"
-
- "github.com/docker/docker/builder"
- "github.com/docker/docker/engine"
-)
-
-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 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 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)
-
- 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(outs.Data) != 1 {
- t.Errorf("Expected 1 container, %v found", len(outs.Data))
- }
-
- 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("kill", id)
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
-
- eng = newTestEngine(t, false, runtime.Config().Root)
-
- 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(outs.Data) != 1 {
- t.Errorf("Expected 1 container, %v found", len(outs.Data))
- }
-
- setTimeout(t, "Waiting on stopped container timedout", 5*time.Second, func() {
- job = eng.Job("wait", outs.Data[0].Get("Id"))
- if err := job.Run(); err != nil {
- t.Fatal(err)
- }
- })
-}
-
-func TestCreateStartRestartStopStartKillRm(t *testing.T) {
- eng := NewTestEngine(t)
- defer mkDaemonFromEngine(eng, t).Nuke()
-
- config, hostConfig, _, err := parseRun([]string{"-i", unitTestImageID, "/bin/cat"})
- if err != nil {
- t.Fatal(err)
- }
-
- id := createTestContainer(eng, config, t)
-
- 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(outs.Data) != 1 {
- t.Errorf("Expected 1 container, %v found", len(outs.Data))
- }
-
- 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)
- }
-
- 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(outs.Data) != 0 {
- t.Errorf("Expected 0 container, %v found", len(outs.Data))
- }
-}
-
-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.Data[0].GetList("RepoTags")) != 2 {
- t.Fatal("incorrect number of matches returned")
- }
-
- images = getImages(eng, t, false, "utest")
-
- if len(images.Data[0].GetList("RepoTags")) != 1 {
- t.Fatal("incorrect number of matches returned")
- }
-
- images = getImages(eng, t, false, "utest*")
-
- if len(images.Data[0].GetList("RepoTags")) != 1 {
- t.Fatal("incorrect number of matches returned")
- }
-
- images = getImages(eng, t, false, "*5000*/*")
-
- if len(images.Data[0].GetList("RepoTags")) != 1 {
- t.Fatal("incorrect number of matches returned")
- }
-}
diff --git a/integration/utils_test.go b/integration/utils_test.go
deleted file mode 100644
index 2e90e4f51..000000000
--- a/integration/utils_test.go
+++ /dev/null
@@ -1,360 +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/builtins"
- "github.com/docker/docker/daemon"
- "github.com/docker/docker/engine"
- 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) {
- job := eng.Job("create", name)
- if err := job.ImportEnv(config); err != nil {
- f.Fatal(err)
- }
- var outputBuffer = bytes.NewBuffer(nil)
- job.Stdout.Add(outputBuffer)
- if err := job.Run(); err != nil {
- f.Fatal(err)
- }
- return engine.Tail(outputBuffer, 1)
-}
-
-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) {
- job := eng.Job("start", id)
- if err := job.Run(); 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 := eng.Job("kill", id).Run(); 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.Hack_GetGlobalVar("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
- // Load default plugins
- 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{
- Root: root,
- AutoRestart: autorestart,
- 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"},
- }
- d, err := daemon.NewDaemon(cfg, eng)
- 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 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)
- job.Setenv("filter", filter)
- images, err := job.Stdout.AddListTable()
- if err != nil {
- t.Fatal(err)
- }
- if err := job.Run(); 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)
-}
diff --git a/integration/z_final_test.go b/integration/z_final_test.go
deleted file mode 100644
index ad1eb4340..000000000
--- a/integration/z_final_test.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package docker
-
-import (
- "github.com/docker/docker/utils"
- "runtime"
- "testing"
-)
-
-func displayFdGoroutines(t *testing.T) {
- t.Logf("Fds: %d, Goroutines: %d", utils.GetTotalUsedFds(), runtime.NumGoroutine())
-}
-
-func TestFinal(t *testing.T) {
- nuke(globalDaemon)
- t.Logf("Start Fds: %d, Start Goroutines: %d", startFds, startGoroutines)
- displayFdGoroutines(t)
-}
diff --git a/links/links.go b/links/links.go
index 96c18cc24..a756c8b0e 100644
--- a/links/links.go
+++ b/links/links.go
@@ -2,10 +2,10 @@ package links
import (
"fmt"
- "github.com/docker/docker/engine"
- "github.com/docker/docker/nat"
"path"
"strings"
+
+ "github.com/docker/docker/nat"
)
type Link struct {
@@ -15,10 +15,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
@@ -36,7 +35,6 @@ func NewLink(parentIP, childIP, name string, env []string, exposedPorts map[nat.
ParentIP: parentIP,
ChildEnvironment: env,
Ports: ports,
- eng: eng,
}
return l, nil
@@ -107,8 +105,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)
@@ -140,39 +138,10 @@ func (l *Link) getDefaultPort() *nat.Port {
}
func (l *Link) Enable() error {
- // -A == iptables append flag
- if err := l.toggle("-A", false); err != nil {
- return err
- }
l.IsEnabled = true
return nil
}
func (l *Link) Disable() {
- // We do not care about errors here because the link may not
- // exist in iptables
- // -D == iptables delete flag
- l.toggle("-D", true)
-
l.IsEnabled = false
}
-
-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
-}
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)
}
diff --git a/docs/man/Dockerfile b/man/Dockerfile
similarity index 60%
rename from docs/man/Dockerfile
rename to man/Dockerfile
index 9910bd48f..33e515d85 100644
--- a/docs/man/Dockerfile
+++ b/man/Dockerfile
@@ -1,7 +1,7 @@
-FROM golang:1.3
+FROM golang:1.4
RUN mkdir -p /go/src/github.com/cpuguy83
RUN mkdir -p /go/src/github.com/cpuguy83 \
- && git clone -b v1 https://github.com/cpuguy83/go-md2man.git /go/src/github.com/cpuguy83/go-md2man \
+ && git clone -b v1.0.1 https://github.com/cpuguy83/go-md2man.git /go/src/github.com/cpuguy83/go-md2man \
&& cd /go/src/github.com/cpuguy83/go-md2man \
&& go get -v ./...
CMD ["/go/bin/go-md2man", "--help"]
diff --git a/docs/man/Dockerfile.5.md b/man/Dockerfile.5.md
similarity index 94%
rename from docs/man/Dockerfile.5.md
rename to man/Dockerfile.5.md
index 7f884888e..73f74fa82 100644
--- a/docs/man/Dockerfile.5.md
+++ b/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
@@ -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.
@@ -196,7 +196,7 @@ A Dockerfile is similar to a Makefile.
ADD
# Required for paths with whitespace
- ADD ["", ""]
+ ADD ["",... ""]
```
The **ADD** instruction copies new files, directories
@@ -215,7 +215,7 @@ A Dockerfile is similar to a Makefile.
COPY
# Required for paths with whitespace
- COPY ["", ""]
+ COPY ["",... ""]
```
The **COPY** instruction copies new files from `` and
@@ -273,10 +273,17 @@ 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.
-**WRKDIR**
+ 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.
+
+**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
diff --git a/docs/man/README.md b/man/README.md
similarity index 95%
rename from docs/man/README.md
rename to man/README.md
index 402178a9c..e25a925ad 100644
--- a/docs/man/README.md
+++ b/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-attach.1.md b/man/docker-attach.1.md
similarity index 100%
rename from docs/man/docker-attach.1.md
rename to man/docker-attach.1.md
diff --git a/docs/man/docker-build.1.md b/man/docker-build.1.md
similarity index 61%
rename from docs/man/docker-build.1.md
rename to man/docker-build.1.md
index fe6250fc1..7a5ceab0e 100644
--- a/docs/man/docker-build.1.md
+++ b/man/docker-build.1.md
@@ -17,7 +17,11 @@ docker-build - Build a new image from the source code at PATH
[**-m**|**--memory**[=*MEMORY*]]
[**--memory-swap**[=*MEMORY-SWAP*]]
[**-c**|**--cpu-shares**[=*0*]]
+[**--cpu-period**[=*0*]]
+[**--cpu-quota**[=*0*]]
[**--cpuset-cpus**[=*CPUSET-CPUS*]]
+[**--cpuset-mems**[=*CPUSET-MEMS*]]
+[**--cgroup-parent**[=*CGROUP-PARENT*]]
PATH | URL | -
@@ -62,6 +66,77 @@ as context.
**-t**, **--tag**=""
Repository name (and optionally a tag) to be applied to the resulting image in case of success
+**-m**, **--memory**=*MEMORY*
+ Memory limit
+
+**--memory-swap**=*MEMORY-SWAP*
+ Total memory (memory + swap), '-1' to disable swap.
+
+**-c**, **--cpu-shares**=*0*
+ CPU shares (relative weight).
+
+ By default, all containers get the same proportion of CPU cycles. You can
+ change this proportion by adjusting the container's CPU share weighting
+ relative to the weighting of all other running containers.
+
+ To modify the proportion from the default of 1024, use the **-c** or
+ **--cpu-shares** flag to set the weighting to 2 or higher.
+
+ The proportion is only applied when CPU-intensive processes are running.
+ When tasks in one container are idle, the other containers can use the
+ left-over CPU time. The actual amount of CPU time used varies depending on
+ 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 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.
+
+ On a multi-core system, the shares of CPU time are distributed across the CPU
+ cores. Even if a container is limited to less than 100% of CPU time, it can
+ use 100% of each individual CPU core.
+
+ For example, consider a system with more than three cores. If you start one
+ container **{C0}** with **-c=512** running one process, and another container
+ **{C1}** with **-c=1024** running two processes, this can result in the following
+ division of CPU shares:
+
+ PID container CPU CPU share
+ 100 {C0} 0 100% of CPU0
+ 101 {C1} 1 100% of CPU1
+ 102 {C1} 2 100% of CPU2
+
+**--cpu-period**=*0*
+ Limit the CPU CFS (Completely Fair Scheduler) period.
+
+ Limit the container's CPU usage. This flag causes the kernel to restrict the
+ container's CPU usage to the period you specify.
+
+**--cpu-quota**=*0*
+ Limit the CPU CFS (Completely Fair Scheduler) quota.
+
+ By default, containers run with the full CPU resource. This flag causes the
+kernel to restrict the container's CPU usage to the quota you specify.
+
+**--cpuset-cpus**=*CPUSET-CPUS*
+ CPUs in which to allow execution (0-3, 0,1).
+
+**--cpuset-mems**=*CPUSET-MEMS*
+ Memory nodes (MEMs) in which to allow execution (-1-3, 0,1). Only effective on
+ NUMA systems.
+
+ For example, if you have four memory nodes on your system (0-3), use `--cpuset-mems=0,1`
+to ensure the processes in your Docker container only use memory from the first
+two memory nodes.
+
+**--cgroup-parent**=*CGROUP-PARENT*
+ Path to `cgroups` under which the container's `cgroup` are created.
+
+ If the path is not absolute, the path is considered relative to the `cgroups` path of the init process.
+Cgroups are created if they do not already exist.
+
# EXAMPLES
## Building an image using a Dockerfile located inside the current directory
diff --git a/docs/man/docker-commit.1.md b/man/docker-commit.1.md
similarity index 86%
rename from docs/man/docker-commit.1.md
rename to man/docker-commit.1.md
index e3459197a..5a290682d 100644
--- a/docs/man/docker-commit.1.md
+++ b/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
@@ -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
diff --git a/docs/man/docker-cp.1.md b/man/docker-cp.1.md
similarity index 100%
rename from docs/man/docker-cp.1.md
rename to man/docker-cp.1.md
diff --git a/docs/man/docker-create.1.md b/man/docker-create.1.md
similarity index 84%
rename from docs/man/docker-create.1.md
rename to man/docker-create.1.md
index 1a0da1b8f..546eb3407 100644
--- a/docs/man/docker-create.1.md
+++ b/man/docker-create.1.md
@@ -8,11 +8,15 @@ docker-create - Create a new container
**docker create**
[**-a**|**--attach**[=*[]*]]
[**--add-host**[=*[]*]]
+[**--blkio-weight**[=*[BLKIO-WEIGHT]*]]
[**-c**|**--cpu-shares**[=*0*]]
[**--cap-add**[=*[]*]]
[**--cap-drop**[=*[]*]]
[**--cidfile**[=*CIDFILE*]]
+[**--cpu-period**[=*0*]]
[**--cpuset-cpus**[=*CPUSET-CPUS*]]
+[**--cpuset-mems**[=*CPUSET-MEMS*]]
+[**--cpu-quota**[=*0*]]
[**--device**[=*[]*]]
[**--dns-search**[=*[]*]]
[**--dns**[=*[]*]]
@@ -29,14 +33,17 @@ docker-create - Create a new container
[**--link**[=*[]*]]
[**--lxc-conf**[=*[]*]]
[**--log-driver**[=*[]*]]
+[**--log-opt**[=*[]*]]
[**-m**|**--memory**[=*MEMORY*]]
[**--memory-swap**[=*MEMORY-SWAP*]]
[**--mac-address**[=*MAC-ADDRESS*]]
[**--name**[=*NAME*]]
[**--net**[=*"bridge"*]]
+[**--oom-kill-disable**[=*false*]]
[**-P**|**--publish-all**[=*false*]]
[**-p**|**--publish**[=*[]*]]
[**--pid**[=*[]*]]
+[**--uts**[=*[]*]]
[**--privileged**[=*false*]]
[**--read-only**[=*false*]]
[**--restart**[=*RESTART*]]
@@ -56,6 +63,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)
@@ -71,9 +81,22 @@ IMAGE [COMMAND] [ARG...]
**--cgroup-parent**=""
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.
+**--cpu-peroid**=0
+ Limit the CPU CFS (Completely Fair Scheduler) period
+
**--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.
+
+**-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)
@@ -116,15 +139,19 @@ IMAGE [COMMAND] [ARG...]
Read labels from a file. Delimit each label with an EOL.
**--link**=[]
- Add link to another container in the form of :alias
+ Add link to another container in the form of :alias or just
+ in which case the alias will match the name.
**--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.
+**--log-opt**=[]
+ Logging driver specific options.
+
**-m**, **--memory**=""
Memory limit (format: , where unit = b, k, m or g)
@@ -138,7 +165,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)
@@ -153,6 +180,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*.
@@ -168,6 +198,11 @@ This value should always larger than **-m**, so you should alway use this with *
**host**: use the host's PID namespace inside the container.
Note: the host mode gives the container full access to local PID and is therefore considered insecure.
+**--uts**=host
+ Set the UTS mode for the container
+ **host**: use the host's UTS namespace inside the container.
+ Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure.
+
**--privileged**=*true*|*false*
Give extended privileges to this container. The default is *false*.
diff --git a/docs/man/docker-diff.1.md b/man/docker-diff.1.md
similarity index 100%
rename from docs/man/docker-diff.1.md
rename to man/docker-diff.1.md
diff --git a/docs/man/docker-events.1.md b/man/docker-events.1.md
similarity index 94%
rename from docs/man/docker-events.1.md
rename to man/docker-events.1.md
index bff04a6d1..b0723c623 100644
--- a/docs/man/docker-events.1.md
+++ b/man/docker-events.1.md
@@ -63,7 +63,12 @@ Again the output container IDs have been shortened for the purposes of this docu
2015-01-28T20:25:45.000000000-08:00 c21f6c22ba27: (from whenry/testimage:latest) die
2015-01-28T20:25:46.000000000-08:00 c21f6c22ba27: (from whenry/testimage:latest) stop
+
+If you do not provide the --since option, the command returns only new and/or
+live events.
+
# HISTORY
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
+June 2015, updated by Brian Goff
diff --git a/docs/man/docker-exec.1.md b/man/docker-exec.1.md
similarity index 77%
rename from docs/man/docker-exec.1.md
rename to man/docker-exec.1.md
index e7554419e..c1de7b59e 100644
--- a/docs/man/docker-exec.1.md
+++ b/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/man/docker-export.1.md b/man/docker-export.1.md
similarity index 100%
rename from docs/man/docker-export.1.md
rename to man/docker-export.1.md
diff --git a/man/docker-history.1.md b/man/docker-history.1.md
new file mode 100644
index 000000000..268e378d0
--- /dev/null
+++ b/man/docker-history.1.md
@@ -0,0 +1,51 @@
+% DOCKER(1) Docker User Manuals
+% Docker Community
+% JUNE 2014
+# NAME
+docker-history - Show the history of an image
+
+# SYNOPSIS
+**docker history**
+[**--help**]
+[**--no-trunc**[=*false*]]
+[**-q**|**--quiet**[=*false*]]
+IMAGE
+
+# DESCRIPTION
+
+Show the history of when and how an image was created.
+
+# OPTIONS
+**--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*.
+
+**-q**, **--quiet**=*true*|*false*
+ Only show numeric IDs. The default is *false*.
+
+# EXAMPLES
+ $ docker history fedora
+ IMAGE CREATED CREATED BY SIZE COMMENT
+ 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 -
+
+## 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
+ 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)
+based on docker.com source material and internal work.
+June 2014, updated by Sven Dowideit
diff --git a/docs/man/docker-images.1.md b/man/docker-images.1.md
similarity index 88%
rename from docs/man/docker-images.1.md
rename to man/docker-images.1.md
index c5151f110..16dd86476 100644
--- a/docs/man/docker-images.1.md
+++ b/man/docker-images.1.md
@@ -66,6 +66,11 @@ used in builds use **-a**:
docker images -a
+Previously, the docker images command supported the --tree and --dot arguments,
+which displayed different visualizations of the image data. Docker core removed
+this functionality in the 1.7 version. If you liked this functionality, you can
+still find it in the third-party dockviz tool: https://github.com/justone/dockviz.
+
## Listing only the shortened image IDs
Listing just the shortened image IDs. This can be useful for some automated
diff --git a/docs/man/docker-import.1.md b/man/docker-import.1.md
similarity index 91%
rename from docs/man/docker-import.1.md
rename to man/docker-import.1.md
index 6b3899b6a..b45bf5d4c 100644
--- a/docs/man/docker-import.1.md
+++ b/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/man/docker-info.1.md b/man/docker-info.1.md
similarity index 97%
rename from docs/man/docker-info.1.md
rename to man/docker-info.1.md
index 346df866a..a3bbd7982 100644
--- a/docs/man/docker-info.1.md
+++ b/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/man/docker-inspect.1.md b/man/docker-inspect.1.md
similarity index 53%
rename from docs/man/docker-inspect.1.md
rename to man/docker-inspect.1.md
index 85f673000..6f3cf5122 100644
--- a/docs/man/docker-inspect.1.md
+++ b/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
diff --git a/docs/man/docker-kill.1.md b/man/docker-kill.1.md
similarity index 100%
rename from docs/man/docker-kill.1.md
rename to man/docker-kill.1.md
diff --git a/docs/man/docker-load.1.md b/man/docker-load.1.md
similarity index 94%
rename from docs/man/docker-load.1.md
rename to man/docker-load.1.md
index 52eaa37a1..c04544368 100644
--- a/docs/man/docker-load.1.md
+++ b/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-login.1.md b/man/docker-login.1.md
similarity index 94%
rename from docs/man/docker-login.1.md
rename to man/docker-login.1.md
index f73df77ed..87ad31b70 100644
--- a/docs/man/docker-login.1.md
+++ b/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/man/docker-logout.1.md
similarity index 85%
rename from docs/man/docker-logout.1.md
rename to man/docker-logout.1.md
index d464f00fd..3726fd66c 100644
--- a/docs/man/docker-logout.1.md
+++ b/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-logs.1.md b/man/docker-logs.1.md
similarity index 91%
rename from docs/man/docker-logs.1.md
rename to man/docker-logs.1.md
index 01a15f54d..e2cacea22 100644
--- a/docs/man/docker-logs.1.md
+++ b/man/docker-logs.1.md
@@ -8,6 +8,7 @@ docker-logs - Fetch the logs of a container
**docker logs**
[**-f**|**--follow**[=*false*]]
[**--help**]
+[**--since**[=*SINCE*]]
[**-t**|**--timestamps**[=*false*]]
[**--tail**[=*"all"*]]
CONTAINER
@@ -31,6 +32,9 @@ then continue streaming new output from the container’s stdout and stderr.
**-f**, **--follow**=*true*|*false*
Follow log output. The default is *false*.
+**--since**=""
+ Show logs since timestamp
+
**-t**, **--timestamps**=*true*|*false*
Show timestamps. The default is *false*.
@@ -42,3 +46,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
July 2014, updated by Sven Dowideit
+April 2015, updated by Ahmet Alp Balkan
diff --git a/docs/man/docker-pause.1.md b/man/docker-pause.1.md
similarity index 100%
rename from docs/man/docker-pause.1.md
rename to man/docker-pause.1.md
diff --git a/docs/man/docker-port.1.md b/man/docker-port.1.md
similarity index 100%
rename from docs/man/docker-port.1.md
rename to man/docker-port.1.md
diff --git a/docs/man/docker-ps.1.md b/man/docker-ps.1.md
similarity index 100%
rename from docs/man/docker-ps.1.md
rename to man/docker-ps.1.md
diff --git a/docs/man/docker-pull.1.md b/man/docker-pull.1.md
similarity index 93%
rename from docs/man/docker-pull.1.md
rename to man/docker-pull.1.md
index d7c1d59ad..30a949e81 100644
--- a/docs/man/docker-pull.1.md
+++ b/man/docker-pull.1.md
@@ -8,7 +8,7 @@ docker-pull - Pull an image or a repository from a registry
**docker pull**
[**-a**|**--all-tags**[=*false*]]
[**--help**]
-NAME[:TAG]
+NAME[:TAG] | [REGISTRY_HOST[:REGISTRY_PORT]/]NAME[:TAG]
# DESCRIPTION
@@ -31,7 +31,7 @@ registry located at `registry-1.docker.io` by default.
# 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
@@ -40,7 +40,7 @@ registry located at `registry-1.docker.io` by default.
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
@@ -51,7 +51,7 @@ registry located at `registry-1.docker.io` by default.
# 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
@@ -59,7 +59,7 @@ registry located at `registry-1.docker.io` by default.
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-push.1.md b/man/docker-push.1.md
similarity index 100%
rename from docs/man/docker-push.1.md
rename to man/docker-push.1.md
diff --git a/docs/man/docker-rename.1.md b/man/docker-rename.1.md
similarity index 100%
rename from docs/man/docker-rename.1.md
rename to man/docker-rename.1.md
diff --git a/docs/man/docker-restart.1.md b/man/docker-restart.1.md
similarity index 100%
rename from docs/man/docker-restart.1.md
rename to man/docker-restart.1.md
diff --git a/docs/man/docker-rm.1.md b/man/docker-rm.1.md
similarity index 100%
rename from docs/man/docker-rm.1.md
rename to man/docker-rm.1.md
diff --git a/docs/man/docker-rmi.1.md b/man/docker-rmi.1.md
similarity index 100%
rename from docs/man/docker-rmi.1.md
rename to man/docker-rmi.1.md
diff --git a/docs/man/docker-run.1.md b/man/docker-run.1.md
similarity index 88%
rename from docs/man/docker-run.1.md
rename to man/docker-run.1.md
index 1831237de..ee70159ad 100644
--- a/docs/man/docker-run.1.md
+++ b/man/docker-run.1.md
@@ -8,12 +8,16 @@ 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**[=*[]*]]
[**--cidfile**[=*CIDFILE*]]
+[**--cpu-period**[=*0*]]
[**--cpuset-cpus**[=*CPUSET-CPUS*]]
+[**--cpuset-mems**[=*CPUSET-MEMS*]]
[**-d**|**--detach**[=*false*]]
+[**--cpu-quota**[=*0*]]
[**--device**[=*[]*]]
[**--dns-search**[=*[]*]]
[**--dns**[=*[]*]]
@@ -30,14 +34,17 @@ docker-run - Run a command in a new container
[**--link**[=*[]*]]
[**--lxc-conf**[=*[]*]]
[**--log-driver**[=*[]*]]
+[**--log-opt**[=*[]*]]
[**-m**|**--memory**[=*MEMORY*]]
[**--memory-swap**[=*MEMORY-SWAP*]]
[**--mac-address**[=*MAC-ADDRESS*]]
[**--name**[=*NAME*]]
[**--net**[=*"bridge"*]]
+[**--oom-kill-disable**[=*false*]]
[**-P**|**--publish-all**[=*false*]]
[**-p**|**--publish**[=*[]*]]
[**--pid**[=*[]*]]
+[**--uts**[=*[]*]]
[**--privileged**[=*false*]]
[**--read-only**[=*false*]]
[**--restart**[=*RESTART*]]
@@ -83,6 +90,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)
@@ -101,7 +111,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.
@@ -131,9 +141,28 @@ division of CPU shares:
**--cidfile**=""
Write the container ID to the file
+**--cpu-period**=0
+ Limit the CPU CFS (Completely Fair Scheduler) period
+
+ Limit the container's CPU usage. This flag tell the kernel to restrict the container's CPU usage to the period you specify.
+
**--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.
+
+**--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*.
@@ -211,7 +240,8 @@ ENTRYPOINT.
Read in a line delimited file of labels
**--link**=[]
- Add link to another container in the form of :alias
+ Add link to another container in the form of :alias or just
+in which case the alias will match the name
If the operator
uses **--link** when starting the new client container, then the client
@@ -222,10 +252,13 @@ 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.
+**--log-opt**=[]
+ Logging driver specific options.
+
**-m**, **--memory**=""
Memory limit (format: , where unit = b, k, m or g)
@@ -239,7 +272,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)
@@ -269,6 +302,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*.
@@ -292,6 +328,11 @@ ports and the exposed ports, use `docker port`.
**host**: use the host's PID namespace inside the container.
Note: the host mode gives the container full access to local PID and is therefore considered insecure.
+**--uts**=host
+ Set the UTS mode for the container
+ **host**: use the host's UTS namespace inside the container.
+ Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure.
+
**--privileged**=*true*|*false*
Give extended privileges to this container. The default is *false*.
@@ -341,7 +382,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)
@@ -354,6 +400,21 @@ used in other containers using the **--volumes-from** option.
read-only or read-write mode, respectively. By default, the volumes are mounted
read-write. See examples.
+Labeling systems like SELinux require proper labels be placed on volume content
+mounted into a container, otherwise the secuirty system might prevent the
+processes running inside the container from using the content. By default,
+volumes are not relabeled.
+
+Two suffixes :z or :Z can be added to the volume mount. These suffixes tell
+Docker to relabel file objects on the shared volumes. The 'z' option tells
+Docker that the volume content will be shared between containers. Docker will
+label the content with a shared content label. Shared volumes labels allow all
+containers to read/write content. The 'Z' option tells Docker to label the
+content with a private unshared label. Private volumes can only be used by the
+current container.
+
+Note: Multiple Volume options can be added separated by a ","
+
**--volumes-from**=[]
Mount volumes from the specified container(s)
@@ -411,7 +472,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:
@@ -428,7 +489,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
@@ -437,7 +498,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
@@ -447,7 +508,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
@@ -457,7 +518,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
@@ -466,7 +527,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/man/docker-save.1.md
similarity index 91%
rename from docs/man/docker-save.1.md
rename to man/docker-save.1.md
index 91be06c35..5f336ffd3 100644
--- a/docs/man/docker-save.1.md
+++ b/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/man/docker-search.1.md
similarity index 96%
rename from docs/man/docker-search.1.md
rename to man/docker-search.1.md
index 3c8e0a7b1..6316008f5 100644
--- a/docs/man/docker-search.1.md
+++ b/man/docker-search.1.md
@@ -40,7 +40,7 @@ of stars awarded, whether the image is official, and whether it is automated.
Search a 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
@@ -52,7 +52,7 @@ ranked 3 or higher:
Search Docker Hub 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-start.1.md b/man/docker-start.1.md
similarity index 100%
rename from docs/man/docker-start.1.md
rename to man/docker-start.1.md
diff --git a/docs/man/docker-stats.1.md b/man/docker-stats.1.md
similarity index 66%
rename from docs/man/docker-stats.1.md
rename to man/docker-stats.1.md
index 493e402fb..4b4858855 100644
--- a/docs/man/docker-stats.1.md
+++ b/man/docker-stats.1.md
@@ -17,12 +17,15 @@ 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.
- $ 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
+ 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/man/docker-stop.1.md b/man/docker-stop.1.md
similarity index 100%
rename from docs/man/docker-stop.1.md
rename to man/docker-stop.1.md
diff --git a/docs/man/docker-tag.1.md b/man/docker-tag.1.md
similarity index 100%
rename from docs/man/docker-tag.1.md
rename to man/docker-tag.1.md
diff --git a/docs/man/docker-top.1.md b/man/docker-top.1.md
similarity index 95%
rename from docs/man/docker-top.1.md
rename to man/docker-top.1.md
index be2bed221..c3bbf883f 100644
--- a/docs/man/docker-top.1.md
+++ b/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-unpause.1.md b/man/docker-unpause.1.md
similarity index 100%
rename from docs/man/docker-unpause.1.md
rename to man/docker-unpause.1.md
diff --git a/docs/man/docker-version.1.md b/man/docker-version.1.md
similarity index 100%
rename from docs/man/docker-version.1.md
rename to man/docker-version.1.md
diff --git a/docs/man/docker-wait.1.md b/man/docker-wait.1.md
similarity index 88%
rename from docs/man/docker-wait.1.md
rename to man/docker-wait.1.md
index a1e2aa212..5f07bacc0 100644
--- a/docs/man/docker-wait.1.md
+++ b/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/man/docker.1.md b/man/docker.1.md
similarity index 67%
rename from docs/man/docker.1.md
rename to man/docker.1.md
index bcb9d2541..0b1de775f 100644
--- a/docs/man/docker.1.md
+++ b/man/docker.1.md
@@ -41,12 +41,24 @@ 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
**-e**, **--exec-driver**=""
Force Docker to use specific exec driver. Default is `native`.
+**--exec-opt**=[]
+ Set exec driver options. See EXEC DRIVER OPTIONS.
+
+**--exec-root**=""
+ Path to use as the root of the Docker execdriver. Default is `/var/run/docker`.
+
**--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)
@@ -89,10 +101,13 @@ unix://[/path/to/socket] to use.
**--label**="[]"
Set key=value labels to the daemon (displayed in `docker info`)
-**--log-driver**="*json-file*|*syslog*|*none*"
- Container's logging driver. Default is `default`.
+**--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.
+**--log-opt**=[]
+ Logging driver specific options.
+
**--mtu**=VALUE
Set the containers network mtu. Default is `0`.
@@ -105,6 +120,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.
@@ -115,127 +133,165 @@ 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.
-**--selinux-enabled**=*true*|*false*
- 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)**
- Register or login to a Docker Registry Service
+**login**
+ Register or login to a Docker Registry
+ See **docker-login(1)** for full documentation on the **login** command.
-**docker-logout(1)**
- Log the user out of a Docker Registry Service
+**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 an image or a repository from a Docker Registry Service
+**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 an image or a repository to a Docker Registry Service
+**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
@@ -313,6 +369,18 @@ for data and metadata:
--storage-opt dm.metadatadev=/dev/vdc \
--storage-opt dm.basesize=20G
+# EXEC DRIVER OPTIONS
+
+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`. 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
command. For example:
diff --git a/docs/man/md2man-all.sh b/man/md2man-all.sh
similarity index 100%
rename from docs/man/md2man-all.sh
rename to man/md2man-all.sh
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
diff --git a/nat/sort.go b/nat/sort.go
index f36c12f7b..fa584c173 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,65 @@ 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})
+ }
+ bindings[p] = []PortBinding{}
+ } else {
+ s = append(s, portMapEntry{port: p})
+ }
+ }
+
+ 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
+ if _, ok := bindings[entry.port]; ok {
+ 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)
+ }
+}
diff --git a/opts/ip.go b/opts/ip.go
index f8a493e68..d960dcd93 100644
--- a/opts/ip.go
+++ b/opts/ip.go
@@ -22,10 +22,13 @@ func (o *IpOpt) Set(val string) error {
if ip == nil {
return fmt.Errorf("%s is not an ip address", val)
}
- (*o.IP) = net.ParseIP(val)
+ *o.IP = ip
return nil
}
func (o *IpOpt) String() string {
- return (*o.IP).String()
+ if *o.IP == nil {
+ return ""
+ }
+ return o.IP.String()
}
diff --git a/opts/opts.go b/opts/opts.go
index e867c0a21..e40c1a334 100644
--- a/opts/opts.go
+++ b/opts/opts.go
@@ -8,24 +8,36 @@ 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
+ // 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
)
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, nil), names, usage)
+}
+
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) {
@@ -126,6 +138,40 @@ 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)
@@ -141,12 +187,14 @@ func ValidateAttach(val string) (string, error) {
}
func ValidateLink(val string) (string, error) {
- if _, err := parsers.PartParser("name:alias", val); err != nil {
+ if _, _, err := parsers.ParseLink(val); err != nil {
return val, err
}
return val, nil
}
+// ValidatePath will make sure 'val' is in the form:
+// [host-dir:]container-path[:rw|ro] - but doesn't validate the mode part
func ValidatePath(val string) (string, error) {
var containerPath string
@@ -174,7 +222,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
@@ -192,9 +240,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.
@@ -235,3 +282,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/opts/opts_test.go b/opts/opts_test.go
index 8370926da..921009c6b 100644
--- a/opts/opts_test.go
+++ b/opts/opts_test.go
@@ -1,6 +1,8 @@
package opts
import (
+ "fmt"
+ "net"
"strings"
"testing"
)
@@ -28,6 +30,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 +179,24 @@ func TestValidateExtraHosts(t *testing.T) {
}
}
}
+
+func TestIpOptString(t *testing.T) {
+ addresses := []string{"", "0.0.0.0"}
+ var ip net.IP
+
+ for _, address := range addresses {
+ stringAddress := NewIpOpt(&ip, address).String()
+ if stringAddress != address {
+ t.Fatalf("IpOpt string should be `%s`, not `%s`", address, stringAddress)
+ }
+ }
+}
+
+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/pkg/archive/archive.go b/pkg/archive/archive.go
index bfa6e1846..cde4de55a 100644
--- a/pkg/archive/archive.go
+++ b/pkg/archive/archive.go
@@ -1,6 +1,7 @@
package archive
import (
+ "archive/tar"
"bufio"
"bytes"
"compress/bzip2"
@@ -11,14 +12,12 @@ import (
"io/ioutil"
"os"
"os/exec"
- "path"
"path/filepath"
+ "runtime"
"strings"
"syscall"
- "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 +77,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 {
@@ -292,17 +291,8 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, L
file.Close()
case tar.TypeBlock, tar.TypeChar, tar.TypeFifo:
- mode := uint32(hdr.Mode & 07777)
- switch hdr.Typeflag {
- case tar.TypeBlock:
- mode |= syscall.S_IFBLK
- case tar.TypeChar:
- mode |= syscall.S_IFCHR
- case tar.TypeFifo:
- mode |= syscall.S_IFIFO
- }
-
- if err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
+ // Handle this is an OS-specific way
+ if err := handleTarTypeBlockCharFifo(hdr, path); err != nil {
return err
}
@@ -331,15 +321,18 @@ 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:
return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag)
}
- if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil && Lchown {
- return err
+ // Lchown is not supported on Windows
+ if runtime.GOOS != "windows" {
+ if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil && Lchown {
+ return err
+ }
}
for key, value := range hdr.Xattrs {
@@ -350,15 +343,19 @@ 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 err := os.Chmod(path, hdrInfo.Mode()); err != nil {
- return err
- }
+ if err := handleLChmod(hdr, path, hdrInfo); err != nil {
+ return err
}
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
- // syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and
- if hdr.Typeflag != tar.TypeSymlink {
+ // syscall.UtimesNano doesn't support a NOFOLLOW flag atm
+ 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
}
@@ -376,25 +373,16 @@ 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) {
+
+ patterns, patDirs, exceptions, err := fileutils.CleanPatterns(options.ExcludePatterns)
+
+ if err != nil {
+ return nil, err
+ }
+
pipeReader, pipeWriter := io.Pipe()
compressWriter, err := CompressStream(pipeWriter, options.Compression)
@@ -426,7 +414,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
}
@@ -445,15 +433,15 @@ 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 {
- log.Debugf("Error matching %s", relFilePath, err)
+ logrus.Debugf("Error matching %s", relFilePath, err)
return err
}
}
if skip {
- if f.IsDir() {
+ if !exceptions && f.IsDir() {
return filepath.SkipDir
}
return nil
@@ -474,7 +462,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 +470,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)
}
}()
@@ -529,7 +517,7 @@ loop:
parent := filepath.Dir(hdr.Name)
parentPath := filepath.Join(dest, parent)
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
- err = os.MkdirAll(parentPath, 0777)
+ err = system.MkdirAll(parentPath, 0777)
if err != nil {
return err
}
@@ -606,7 +594,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 +636,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)
- if err := os.MkdirAll(dst, 0755); err != nil && !os.IsExist(err) {
+ logrus.Debugf("Creating dest directory: %s", dst)
+ if err := system.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 +653,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
@@ -673,12 +661,12 @@ func (archiver *Archiver) CopyFileWithTar(src, dst string) (err error) {
if srcSt.IsDir() {
return fmt.Errorf("Can't copy a directory")
}
- // Clean up the trailing /
- if dst[len(dst)-1] == '/' {
- dst = path.Join(dst, filepath.Base(src))
+ // Clean up the trailing slash
+ if dst[len(dst)-1] == os.PathSeparator {
+ dst = filepath.Join(dst, filepath.Base(src))
}
// Create the holding directory if necessary
- if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
+ if err := system.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
return err
}
@@ -791,9 +779,6 @@ func NewTempArchive(src Archive, dir string) (*TempArchive, error) {
if _, err := io.Copy(f, src); err != nil {
return nil, err
}
- if err = f.Sync(); err != nil {
- return nil, err
- }
if _, err := f.Seek(0, 0); err != nil {
return nil, err
}
diff --git a/pkg/archive/archive_test.go b/pkg/archive/archive_test.go
index 6cd95d5ad..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"
@@ -14,9 +15,149 @@ import (
"testing"
"time"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
+ "github.com/docker/docker/pkg/system"
)
+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)
@@ -66,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 {
@@ -179,11 +629,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 +691,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 +754,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) : %v", changes)
+ }
+}
+
func TestTarWithHardLink(t *testing.T) {
origin, err := ioutil.TempDir("", "docker-test-tar-hardlink")
if err != nil {
@@ -435,6 +985,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 (../)
diff --git a/pkg/archive/archive_unix.go b/pkg/archive/archive_unix.go
index cbce65e31..8a15cfebe 100644
--- a/pkg/archive/archive_unix.go
+++ b/pkg/archive/archive_unix.go
@@ -3,11 +3,12 @@
package archive
import (
+ "archive/tar"
"errors"
"os"
"syscall"
- "github.com/docker/docker/vendor/src/code.google.com/p/go/src/pkg/archive/tar"
+ "github.com/docker/docker/pkg/system"
)
// canonicalTarNameForPath returns platform-specific filepath
@@ -36,8 +37,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)))
}
@@ -52,3 +53,37 @@ func major(device uint64) uint64 {
func minor(device uint64) uint64 {
return (device & 0xff) | ((device >> 12) & 0xfff00)
}
+
+// handleTarTypeBlockCharFifo is an OS-specific helper function used by
+// createTarFile to handle the following types of header: Block; Char; Fifo
+func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
+ mode := uint32(hdr.Mode & 07777)
+ switch hdr.Typeflag {
+ case tar.TypeBlock:
+ mode |= syscall.S_IFBLK
+ case tar.TypeChar:
+ mode |= syscall.S_IFCHR
+ case tar.TypeFifo:
+ mode |= syscall.S_IFIFO
+ }
+
+ if err := system.Mknod(path, mode, int(system.Mkdev(hdr.Devmajor, hdr.Devminor))); err != nil {
+ return err
+ }
+ return nil
+}
+
+func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
+ 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
+ }
+ }
+ return nil
+}
diff --git a/pkg/archive/archive_windows.go b/pkg/archive/archive_windows.go
index 6caef3b73..10db4bd00 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
@@ -15,11 +14,11 @@ import (
// path.
func CanonicalTarNameForPath(p string) (string, error) {
// windows: convert windows style relative path with backslashes
- // into forward slashes. since windows does not allow '/' or '\'
+ // into forward slashes. Since windows does not allow '/' or '\'
// in file names, it is mostly safe to replace however we must
// check just in case
if strings.Contains(p, "/") {
- return "", fmt.Errorf("windows path contains forward slash: %s", p)
+ return "", fmt.Errorf("Windows path contains forward slash: %s", p)
}
return strings.Replace(p, string(os.PathSeparator), "/", -1), nil
@@ -39,3 +38,13 @@ func setHeaderForSpecialDevice(hdr *tar.Header, ta *tarAppender, name string, st
// do nothing. no notion of Rdev, Inode, Nlink in stat on Windows
return
}
+
+// handleTarTypeBlockCharFifo is an OS-specific helper function used by
+// createTarFile to handle the following types of header: Block; Char; Fifo
+func handleTarTypeBlockCharFifo(hdr *tar.Header, path string) error {
+ return nil
+}
+
+func handleLChmod(hdr *tar.Header, path string, hdrInfo os.FileInfo) error {
+ return nil
+}
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/archive/changes.go b/pkg/archive/changes.go
index c3cb4ebe0..affafad39 100644
--- a/pkg/archive/changes.go
+++ b/pkg/archive/changes.go
@@ -1,6 +1,7 @@
package archive
import (
+ "archive/tar"
"bytes"
"fmt"
"io"
@@ -11,9 +12,7 @@ import (
"syscall"
"time"
- "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"
)
@@ -175,10 +174,6 @@ func (info *FileInfo) path() string {
return filepath.Join(info.parent.path(), info.name)
}
-func (info *FileInfo) isDir() bool {
- return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR == syscall.S_IFDIR
-}
-
func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
sizeAtEntry := len(*changes)
@@ -215,13 +210,7 @@ func (info *FileInfo) addChanges(oldInfo *FileInfo, changes *[]Change) {
// be visible when actually comparing the stat fields. The only time this
// breaks down is if some code intentionally hides a change by setting
// back mtime
- if oldStat.Mode() != newStat.Mode() ||
- oldStat.Uid() != newStat.Uid() ||
- oldStat.Gid() != newStat.Gid() ||
- oldStat.Rdev() != newStat.Rdev() ||
- // Don't look at size for dirs, its not a good measure of change
- (oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
- (!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) ||
+ if statDifferent(oldStat, newStat) ||
bytes.Compare(oldChild.capability, newChild.capability) != 0 {
change := Change{
Path: newChild.path(),
@@ -401,22 +390,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/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/changes_unix.go b/pkg/archive/changes_unix.go
new file mode 100644
index 000000000..d780f1639
--- /dev/null
+++ b/pkg/archive/changes_unix.go
@@ -0,0 +1,27 @@
+// +build !windows
+
+package archive
+
+import (
+ "syscall"
+
+ "github.com/docker/docker/pkg/system"
+)
+
+func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool {
+ // Don't look at size for dirs, its not a good measure of change
+ if oldStat.Mode() != newStat.Mode() ||
+ oldStat.Uid() != newStat.Uid() ||
+ oldStat.Gid() != newStat.Gid() ||
+ oldStat.Rdev() != newStat.Rdev() ||
+ // Don't look at size for dirs, its not a good measure of change
+ (oldStat.Mode()&syscall.S_IFDIR != syscall.S_IFDIR &&
+ (!sameFsTimeSpec(oldStat.Mtim(), newStat.Mtim()) || (oldStat.Size() != newStat.Size()))) {
+ return true
+ }
+ return false
+}
+
+func (info *FileInfo) isDir() bool {
+ return info.parent == nil || info.stat.Mode()&syscall.S_IFDIR != 0
+}
diff --git a/pkg/archive/changes_windows.go b/pkg/archive/changes_windows.go
new file mode 100644
index 000000000..4809b7a59
--- /dev/null
+++ b/pkg/archive/changes_windows.go
@@ -0,0 +1,20 @@
+package archive
+
+import (
+ "github.com/docker/docker/pkg/system"
+)
+
+func statDifferent(oldStat *system.Stat_t, newStat *system.Stat_t) bool {
+
+ // Don't look at size for dirs, its not a good measure of change
+ if oldStat.ModTime() != newStat.ModTime() ||
+ oldStat.Mode() != newStat.Mode() ||
+ oldStat.Size() != newStat.Size() && !oldStat.IsDir() {
+ return true
+ }
+ return false
+}
+
+func (info *FileInfo) isDir() bool {
+ return info.parent == nil || info.stat.IsDir()
+}
diff --git a/pkg/archive/diff.go b/pkg/archive/diff.go
index b5eb63fd4..fd4946078 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"
)
@@ -48,7 +47,7 @@ func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
parent := filepath.Dir(hdr.Name)
parentPath := filepath.Join(dest, parent)
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
- err = os.MkdirAll(parentPath, 0600)
+ err = system.MkdirAll(parentPath, 0600)
if err != nil {
return 0, err
}
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/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])
+ }
+ }
+}
diff --git a/pkg/broadcastwriter/broadcastwriter.go b/pkg/broadcastwriter/broadcastwriter.go
index 9a0b17624..bd9b67555 100644
--- a/pkg/broadcastwriter/broadcastwriter.go
+++ b/pkg/broadcastwriter/broadcastwriter.go
@@ -6,8 +6,9 @@ import (
"sync"
"time"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
"github.com/docker/docker/pkg/jsonlog"
+ "github.com/docker/docker/pkg/timeutils"
)
// BroadcastWriter accumulate multiple io.WriteCloser by stream.
@@ -33,7 +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) {
- created := time.Now().UTC()
w.Lock()
if writers, ok := w.streams[""]; ok {
for sw := range writers {
@@ -42,26 +42,47 @@ func (w *BroadcastWriter) Write(p []byte) (n int, err error) {
delete(writers, sw)
}
}
+ if len(w.streams) == 1 {
+ if w.buf.Len() >= 4096 {
+ w.buf.Reset()
+ } else {
+ w.buf.Write(p)
+ }
+ 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 {
- 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 {
- 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/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()
+ }
+}
diff --git a/pkg/chrootarchive/archive.go b/pkg/chrootarchive/archive.go
index 17d3739d1..49d19175d 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)
+ 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)
+ }
+ //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 fb4c5c4e4..f9b5b0970 100644
--- a/pkg/chrootarchive/archive_test.go
+++ b/pkg/chrootarchive/archive_test.go
@@ -1,10 +1,15 @@
package chrootarchive
import (
+ "bytes"
+ "fmt"
+ "hash/crc32"
"io"
"io/ioutil"
"os"
+ "path"
"path/filepath"
+ "strings"
"testing"
"time"
@@ -45,6 +50,255 @@ 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 {
+ 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, 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 makeSymLinks {
+ if err := os.Symlink(path.Join(targetPath, fileName), path.Join(targetPath, fileName+"-link")); err != nil {
+ return 0, err
+ }
+ }
+ }
+ totalSize := numberOfFiles * len(fileData)
+ 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 {
+ return err
+ }
+ if len(changes) > 0 {
+ return fmt.Errorf("Unexpected differences after untar: %v", changes)
+ }
+ 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 {
+ 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)
+ }
+ if err := compareDirectories(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)
+ }
+
+ // Copy directory
+ dest := filepath.Join(tmpdir, "dest")
+ if err := CopyWithTar(src, dest); err != nil {
+ t.Fatal(err)
+ }
+ 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)
+ }
+ if err := compareFiles(srcfile, destfile); err != nil {
+ t.Fatal(err)
+ }
+
+ // Copy symbolic link
+ 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)
+ }
+ if err := compareFiles(srcLinkfile, destLinkfile); 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)
+ }
+
+ // 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")
+ dest = filepath.Join(tmpdir, "destFile")
+ destfile := filepath.Join(dest, "file-1")
+ 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")
+ dest = filepath.Join(tmpdir, "destSymlink")
+ destLinkfile := filepath.Join(dest, "file-1-link")
+ 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) {
+ 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)
+ }
+ if err := compareDirectories(src, dest); err != nil {
+ t.Fatal(err)
+ }
+}
+
type slowEmptyTarReader struct {
size int
offset int
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 f3b55c853..e7f17b88c 100644
--- a/pkg/devicemapper/devmapper.go
+++ b/pkg/devicemapper/devmapper.go
@@ -8,8 +8,9 @@ import (
"os"
"runtime"
"syscall"
+ "unsafe"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
)
type DevmapperLogger interface {
@@ -54,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")
@@ -68,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 (
@@ -83,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
@@ -218,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 == "" {
@@ -226,7 +239,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,
@@ -237,7 +250,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 +258,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
@@ -283,9 +296,9 @@ func FindLoopDeviceFor(file *os.File) *os.File {
return nil
}
-func UdevWait(cookie uint) error {
- if res := DmUdevWait(cookie); res != 1 {
- log.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
@@ -305,7 +318,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 +361,6 @@ 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)
task, err := TaskCreateNamed(DeviceRemove, name)
if task == nil {
return err
@@ -359,7 +370,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 {
@@ -372,10 +383,59 @@ 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
+}
+
+// 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 {
- log.Errorf("Error getblockdevicesize: %s", err)
+ logrus.Errorf("Error getblockdevicesize: %s", err)
return 0, ErrGetBlockSize
}
return uint64(size), nil
@@ -426,7 +486,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)
@@ -480,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 {
@@ -494,25 +565,25 @@ 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)
}
- _, start, length, targetType, params := task.GetNextTarget(0)
+ _, start, length, targetType, params := task.GetNextTarget(unsafe.Pointer(nil))
return start, length, targetType, params, nil
}
@@ -557,7 +628,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)
@@ -567,7 +638,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
@@ -586,9 +657,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
}
@@ -632,7 +704,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)
@@ -681,9 +753,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 {
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 {
diff --git a/pkg/devicemapper/devmapper_wrapper.go b/pkg/devicemapper/devmapper_wrapper.go
index ae4f30fb3..87c200376 100644
--- a/pkg/devicemapper/devmapper_wrapper.go
+++ b/pkg/devicemapper/devmapper_wrapper.go
@@ -90,28 +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
+ 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) {
@@ -219,7 +221,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 +233,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) {
diff --git a/pkg/devicemapper/devmapper_wrapper_deferred_remove.go b/pkg/devicemapper/devmapper_wrapper_deferred_remove.go
new file mode 100644
index 000000000..ced482c96
--- /dev/null
+++ b/pkg/devicemapper/devmapper_wrapper_deferred_remove.go
@@ -0,0 +1,33 @@
+// +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)))
+}
+
+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
new file mode 100644
index 000000000..16631bf19
--- /dev/null
+++ b/pkg/devicemapper/devmapper_wrapper_no_deferred_remove.go
@@ -0,0 +1,14 @@
+// +build linux,libdm_no_deferred_remove
+
+package devicemapper
+
+const LibraryDeferredRemovalSupport = false
+
+func dmTaskDeferredRemoveFct(task *CDmTask) int {
+ // Error. Nobody should be calling it.
+ return -1
+}
+
+func dmTaskGetInfoWithDeferredFct(task *CDmTask, info *Info) int {
+ return -1
+}
diff --git a/pkg/fileutils/fileutils.go b/pkg/fileutils/fileutils.go
index 4e4a91b91..fdafb53c7 100644
--- a/pkg/fileutils/fileutils.go
+++ b/pkg/fileutils/fileutils.go
@@ -1,26 +1,170 @@
package fileutils
import (
- log "github.com/Sirupsen/logrus"
+ "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 {
- log.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) == "." {
- log.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])], "/"))
}
- log.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) {
+ cleanSrc := filepath.Clean(src)
+ cleanDst := filepath.Clean(dst)
+ if cleanSrc == cleanDst {
+ return 0, nil
+ }
+ sf, err := os.Open(cleanSrc)
+ if err != nil {
+ return 0, err
+ }
+ defer sf.Close()
+ if err := os.Remove(cleanDst); err != nil && !os.IsNotExist(err) {
+ return 0, err
+ }
+ df, err := os.Create(cleanDst)
+ 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..ef931684c
--- /dev/null
+++ b/pkg/fileutils/fileutils_test.go
@@ -0,0 +1,357 @@
+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
+ 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 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)
+ }
+}
+
+// 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("")
+ 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])
+ }
+}
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..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")
}
}
@@ -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)
}
}
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/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/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")
+ }
+}
diff --git a/pkg/ioutils/fmt.go b/pkg/ioutils/fmt.go
new file mode 100644
index 000000000..801132ff3
--- /dev/null
+++ b/pkg/ioutils/fmt.go
@@ -0,0 +1,14 @@
+package ioutils
+
+import (
+ "fmt"
+ "io"
+)
+
+// FprintfIfNotEmpty prints the string value if it's not empty
+func FprintfIfNotEmpty(w io.Writer, format, value string) (int, error) {
+ if value != "" {
+ return fmt.Fprintf(w, format, value)
+ }
+ return 0, nil
+}
diff --git a/pkg/ioutils/fmt_test.go b/pkg/ioutils/fmt_test.go
new file mode 100644
index 000000000..896886329
--- /dev/null
+++ b/pkg/ioutils/fmt_test.go
@@ -0,0 +1,17 @@
+package ioutils
+
+import "testing"
+
+func TestFprintfIfNotEmpty(t *testing.T) {
+ wc := NewWriteCounter(&NopWriter{})
+ n, _ := FprintfIfNotEmpty(wc, "foo%s", "")
+
+ if wc.Count != 0 || n != 0 {
+ t.Errorf("Wrong count: %v vs. %v vs. 0", wc.Count, n)
+ }
+
+ n, _ = FprintfIfNotEmpty(wc, "foo%s", "bar")
+ if wc.Count != 6 || n != 6 {
+ t.Errorf("Wrong count: %v vs. %v vs. 6", wc.Count, n)
+ }
+}
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/readers_test.go b/pkg/ioutils/readers_test.go
index 0af978e06..d220487ad 100644
--- a/pkg/ioutils/readers_test.go
+++ b/pkg/ioutils/readers_test.go
@@ -2,11 +2,91 @@ 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) {
+ reader := strings.NewReader("a string reader.")
+ wrapper := NewReaderErrWrapper(reader, func() {
+ t.Fatalf("readErrWrapper should not have called the anonymous function on failure")
+ })
+ // 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 +113,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/writeflusher.go b/pkg/ioutils/writeflusher.go
new file mode 100644
index 000000000..25095474d
--- /dev/null
+++ b/pkg/ioutils/writeflusher.go
@@ -0,0 +1,47 @@
+package ioutils
+
+import (
+ "io"
+ "net/http"
+ "sync"
+)
+
+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
+}
+
+// Flush the stream immediately.
+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 {
+ flusher = f
+ } else {
+ flusher = &NopFlusher{}
+ }
+ return &WriteFlusher{w: w, flusher: flusher}
+}
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..564b1cd4f
--- /dev/null
+++ b/pkg/ioutils/writers_test.go
@@ -0,0 +1,65 @@
+package ioutils
+
+import (
+ "bytes"
+ "strings"
+ "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'})
+ 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/jsonlog/jsonlog.go b/pkg/jsonlog/jsonlog.go
index e2c2a2cab..85afb3b50 100644
--- a/pkg/jsonlog/jsonlog.go
+++ b/pkg/jsonlog/jsonlog.go
@@ -5,8 +5,6 @@ import (
"fmt"
"io"
"time"
-
- log "github.com/Sirupsen/logrus"
)
type JSONLog struct {
@@ -32,16 +30,21 @@ func (jl *JSONLog) Reset() {
jl.Created = time.Time{}
}
-func WriteLog(src io.Reader, dst io.Writer, format string) error {
+func WriteLog(src io.Reader, dst io.Writer, format string, since time.Time) error {
dec := json.NewDecoder(src)
l := &JSONLog{}
for {
- if err := dec.Decode(l); err == io.EOF {
- return nil
- } else if err != nil {
- log.Printf("Error streaming logs: %s", err)
+ l.Reset()
+ if err := dec.Decode(l); err != nil {
+ if err == io.EOF {
+ return nil
+ }
return err
}
+ if !since.IsZero() && l.Created.Before(since) {
+ continue
+ }
+
line, err := l.Format(format)
if err != nil {
return err
@@ -49,6 +52,5 @@ func WriteLog(src io.Reader, dst io.Writer, format string) error {
if _, err := io.WriteString(dst, line); err != nil {
return err
}
- l.Reset()
}
}
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/jsonlog/jsonlog_test.go b/pkg/jsonlog/jsonlog_test.go
index fa53825b9..d4b26fcb4 100644
--- a/pkg/jsonlog/jsonlog_test.go
+++ b/pkg/jsonlog/jsonlog_test.go
@@ -21,7 +21,7 @@ func TestWriteLog(t *testing.T) {
}
w := bytes.NewBuffer(nil)
format := timeutils.RFC3339NanoFixed
- if err := WriteLog(&buf, w, format); err != nil {
+ if err := WriteLog(&buf, w, format, time.Time{}); err != nil {
t.Fatal(err)
}
res := w.String()
@@ -52,7 +52,7 @@ func BenchmarkWriteLog(b *testing.B) {
b.SetBytes(int64(r.Len()))
b.ResetTimer()
for i := 0; i < b.N; i++ {
- if err := WriteLog(r, w, format); err != nil {
+ if err := WriteLog(r, w, format, time.Time{}); err != nil {
b.Fatal(err)
}
b.StopTimer()
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('"')
+}
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/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..97d622c15 100644
--- a/pkg/listenbuffer/buffer.go
+++ b/pkg/listenbuffer/buffer.go
@@ -1,14 +1,38 @@
/*
- 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.
-func NewListenBuffer(proto, addr string, activate chan struct{}) (net.Listener, error) {
+// 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 {
return nil, err
@@ -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
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))
+ }
+}
diff --git a/pkg/mflag/flag.go b/pkg/mflag/flag.go
index b35692bfd..9626a2f6b 100644
--- a/pkg/mflag/flag.go
+++ b/pkg/mflag/flag.go
@@ -289,7 +289,8 @@ type FlagSet struct {
// Usage is the function called when an error occurs while parsing flags.
// The field is a function (not a method) that may be changed to point to
// a custom error handler.
- Usage func()
+ Usage func()
+ ShortUsage func()
name string
parsed bool
@@ -486,8 +487,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 {
@@ -512,6 +512,12 @@ func (f *FlagSet) PrintDefaults() {
if runtime.GOOS != "windows" && home == "/" {
home = ""
}
+
+ // Add a blank line between cmd description and list of options
+ if f.FlagCount() > 0 {
+ fmt.Fprintln(writer, "")
+ }
+
f.VisitAll(func(flag *Flag) {
format := " -%s=%s"
names := []string{}
@@ -561,10 +567,16 @@ func defaultUsage(f *FlagSet) {
// Usage prints to standard error a usage message documenting all defined command-line flags.
// The function is a variable that may be changed to point to a custom function.
var Usage = func() {
- fmt.Fprintf(CommandLine.output, "Usage of %s:\n", os.Args[0])
+ fmt.Fprintf(CommandLine.Out(), "Usage of %s:\n", os.Args[0])
PrintDefaults()
}
+// Usage prints to standard error a usage message documenting the standard command layout
+// The function is a variable that may be changed to point to a custom function.
+var ShortUsage = func() {
+ fmt.Fprintf(CommandLine.output, "Usage of %s:\n", os.Args[0])
+}
+
// FlagCount returns the number of flags that have been defined.
func (f *FlagSet) FlagCount() int { return len(sortFlags(f.formal)) }
@@ -941,11 +953,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 +974,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 +983,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 {
@@ -1054,6 +1066,44 @@ 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.SetOutput(os.Stdout)
+ cmd.Usage()
+ os.Exit(0)
+ }
+ if str := cmd.CheckArgs(); str != "" {
+ cmd.SetOutput(os.Stderr)
+ cmd.ReportError(str, withHelp)
+ cmd.ShortUsage()
+ os.Exit(1)
+ }
+ return nil
+}
+
+func (cmd *FlagSet) ReportError(str string, withHelp bool) {
+ if withHelp {
+ if os.Args[0] == cmd.Name() {
+ str += ".\nSee '" + os.Args[0] + " --help'"
+ } else {
+ str += ".\nSee '" + os.Args[0] + " " + cmd.Name() + " --help'"
+ }
+ }
+ fmt.Fprintf(cmd.Out(), "docker: %s.\n", str)
+}
+
// Parsed reports whether f.Parse has been called.
func (f *FlagSet) Parsed() bool {
return f.parsed
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")
}
diff --git a/pkg/namesgenerator/names-generator.go b/pkg/namesgenerator/names-generator.go
index 0a1eee3b1..b081cc7db 100644
--- a/pkg/namesgenerator/names-generator.go
+++ b/pkg/namesgenerator/names-generator.go
@@ -3,7 +3,8 @@ package namesgenerator
import (
"fmt"
"math/rand"
- "time"
+
+ "github.com/docker/docker/pkg/random"
)
var (
@@ -119,6 +120,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",
@@ -185,6 +189,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",
@@ -299,19 +309,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(random.NewSource())
)
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
}
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
diff --git a/pkg/parsers/kernel/kernel.go b/pkg/parsers/kernel/kernel.go
index 70d09003a..5f7930684 100644
--- a/pkg/parsers/kernel/kernel.go
+++ b/pkg/parsers/kernel/kernel.go
@@ -1,3 +1,5 @@
+// +build !windows
+
package kernel
import (
diff --git a/pkg/parsers/kernel/kernel_windows.go b/pkg/parsers/kernel/kernel_windows.go
new file mode 100644
index 000000000..399d63e5f
--- /dev/null
+++ b/pkg/parsers/kernel/kernel_windows.go
@@ -0,0 +1,65 @@
+package kernel
+
+import (
+ "fmt"
+ "syscall"
+ "unsafe"
+)
+
+type KernelVersionInfo struct {
+ kvi string
+ major int
+ minor int
+ build int
+}
+
+func (k *KernelVersionInfo) String() string {
+ return fmt.Sprintf("%d.%d %d (%s)", k.major, k.minor, k.build, k.kvi)
+}
+
+func GetKernelVersion() (*KernelVersionInfo, error) {
+
+ var (
+ h syscall.Handle
+ dwVersion uint32
+ err error
+ )
+
+ KVI := &KernelVersionInfo{"Unknown", 0, 0, 0}
+
+ if err = syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE,
+ syscall.StringToUTF16Ptr(`SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\`),
+ 0,
+ syscall.KEY_READ,
+ &h); err != nil {
+ return KVI, err
+ }
+ defer syscall.RegCloseKey(h)
+
+ var buf [1 << 10]uint16
+ var typ uint32
+ n := uint32(len(buf) * 2) // api expects array of bytes, not uint16
+
+ if err = syscall.RegQueryValueEx(h,
+ syscall.StringToUTF16Ptr("BuildLabEx"),
+ nil,
+ &typ,
+ (*byte)(unsafe.Pointer(&buf[0])),
+ &n); err != nil {
+ return KVI, err
+ }
+
+ KVI.kvi = syscall.UTF16ToString(buf[:])
+
+ // Important - docker.exe MUST be manifested for this API to return
+ // the correct information.
+ if dwVersion, err = syscall.GetVersion(); err != nil {
+ return KVI, err
+ }
+
+ KVI.major = int(dwVersion & 0xFF)
+ KVI.minor = int((dwVersion & 0XFF00) >> 8)
+ KVI.build = int((dwVersion & 0xFFFF0000) >> 16)
+
+ return KVI, nil
+}
diff --git a/pkg/parsers/operatingsystem/operatingsystem.go b/pkg/parsers/operatingsystem/operatingsystem_linux.go
similarity index 100%
rename from pkg/parsers/operatingsystem/operatingsystem.go
rename to pkg/parsers/operatingsystem/operatingsystem_linux.go
diff --git a/pkg/parsers/operatingsystem/operatingsystem_windows.go b/pkg/parsers/operatingsystem/operatingsystem_windows.go
new file mode 100644
index 000000000..c843c6f84
--- /dev/null
+++ b/pkg/parsers/operatingsystem/operatingsystem_windows.go
@@ -0,0 +1,47 @@
+package operatingsystem
+
+import (
+ "syscall"
+ "unsafe"
+)
+
+// See https://code.google.com/p/go/source/browse/src/pkg/mime/type_windows.go?r=d14520ac25bf6940785aabb71f5be453a286f58c
+// for a similar sample
+
+func GetOperatingSystem() (string, error) {
+
+ var h syscall.Handle
+
+ // Default return value
+ ret := "Unknown Operating System"
+
+ if err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE,
+ syscall.StringToUTF16Ptr(`SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\`),
+ 0,
+ syscall.KEY_READ,
+ &h); err != nil {
+ return ret, err
+ }
+ defer syscall.RegCloseKey(h)
+
+ var buf [1 << 10]uint16
+ var typ uint32
+ n := uint32(len(buf) * 2) // api expects array of bytes, not uint16
+
+ if err := syscall.RegQueryValueEx(h,
+ syscall.StringToUTF16Ptr("ProductName"),
+ nil,
+ &typ,
+ (*byte)(unsafe.Pointer(&buf[0])),
+ &n); err != nil {
+ return ret, err
+ }
+ ret = syscall.UTF16ToString(buf[:])
+
+ return ret, nil
+}
+
+// No-op on Windows
+func IsContainerized() (bool, error) {
+ return false, nil
+}
diff --git a/pkg/parsers/parsers.go b/pkg/parsers/parsers.go
index 59e294dc2..32d87734f 100644
--- a/pkg/parsers/parsers.go
+++ b/pkg/parsers/parsers.go
@@ -2,6 +2,7 @@ package parsers
import (
"fmt"
+ "runtime"
"strconv"
"strings"
)
@@ -10,7 +11,12 @@ import (
func ParseHost(defaultTCPAddr, defaultUnixAddr, addr string) (string, error) {
addr = strings.TrimSpace(addr)
if addr == "" {
- addr = fmt.Sprintf("unix://%s", defaultUnixAddr)
+ if runtime.GOOS != "windows" {
+ addr = fmt.Sprintf("unix://%s", defaultUnixAddr)
+ } else {
+ // Note - defaultTCPAddr already includes tcp:// prefix
+ addr = fmt.Sprintf("%s", defaultTCPAddr)
+ }
}
addrParts := strings.Split(addr, "://")
if len(addrParts) == 1 {
@@ -135,3 +141,17 @@ func ParsePortRange(ports string) (uint64, uint64, error) {
}
return start, end, nil
}
+
+func ParseLink(val string) (string, string, error) {
+ if val == "" {
+ return "", "", fmt.Errorf("empty string specified for links")
+ }
+ arr := strings.Split(val, ":")
+ if len(arr) > 2 {
+ return "", "", fmt.Errorf("bad format for links: %s", val)
+ }
+ if len(arr) == 1 {
+ return val, val, nil
+ }
+ return arr[0], arr[1], nil
+}
diff --git a/pkg/parsers/parsers_test.go b/pkg/parsers/parsers_test.go
index bc9a1e943..89f4ae023 100644
--- a/pkg/parsers/parsers_test.go
+++ b/pkg/parsers/parsers_test.go
@@ -123,3 +123,35 @@ func TestParsePortRangeIncorrectStartRange(t *testing.T) {
t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
}
}
+
+func TestParseLink(t *testing.T) {
+ name, alias, err := ParseLink("name:alias")
+ if err != nil {
+ t.Fatalf("Expected not to error out on a valid name:alias format but got: %v", err)
+ }
+ if name != "name" {
+ t.Fatalf("Link name should have been name, got %s instead", name)
+ }
+ if alias != "alias" {
+ t.Fatalf("Link alias should have been alias, got %s instead", alias)
+ }
+ // short format definition
+ name, alias, err = ParseLink("name")
+ if err != nil {
+ t.Fatalf("Expected not to error out on a valid name only format but got: %v", err)
+ }
+ if name != "name" {
+ t.Fatalf("Link name should have been name, got %s instead", name)
+ }
+ if alias != "name" {
+ t.Fatalf("Link alias should have been name, got %s instead", alias)
+ }
+ // empty string link definition is not allowed
+ if _, _, err := ParseLink(""); err == nil || !strings.Contains(err.Error(), "empty string specified for links") {
+ t.Fatalf("Expected error 'empty string specified for links' but got: %v", err)
+ }
+ // more than two colons are not allowed
+ if _, _, err := ParseLink("link:alias:wrong"); err == nil || !strings.Contains(err.Error(), "bad format for links: link:alias:wrong") {
+ t.Fatalf("Expected error 'bad format for links: link:alias:wrong' but got: %v", err)
+ }
+}
diff --git a/pkg/pidfile/pidfile.go b/pkg/pidfile/pidfile.go
new file mode 100644
index 000000000..3e5707365
--- /dev/null
+++ b/pkg/pidfile/pidfile.go
@@ -0,0 +1,42 @@
+package pidfile
+
+import (
+ "fmt"
+ "io/ioutil"
+ "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) (*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
+ }
+
+ return &PidFile{path: path}, nil
+}
+
+func (file PidFile) Remove() error {
+ if err := os.Remove(file.path); err != nil {
+ 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/pkg/plugins/client.go b/pkg/plugins/client.go
new file mode 100644
index 000000000..619ff260b
--- /dev/null
+++ b/pkg/plugins/client.go
@@ -0,0 +1,113 @@
+package plugins
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "net/http"
+ "strings"
+ "time"
+
+ "github.com/Sirupsen/logrus"
+)
+
+const (
+ versionMimetype = "application/vnd.docker.plugins.v1+json"
+ defaultTimeOut = 30
+)
+
+func NewClient(addr string) *Client {
+ tr := &http.Transport{}
+ protoAndAddr := strings.Split(addr, "://")
+ configureTCPTransport(tr, protoAndAddr[0], protoAndAddr[1])
+ return &Client{&http.Client{Transport: tr}, protoAndAddr[1]}
+}
+
+type Client struct {
+ http *http.Client
+ addr string
+}
+
+func (c *Client) Call(serviceMethod string, args interface{}, ret interface{}) error {
+ return c.callWithRetry(serviceMethod, args, ret, true)
+}
+
+func (c *Client) callWithRetry(serviceMethod string, args interface{}, ret interface{}, retry bool) error {
+ var buf bytes.Buffer
+ if err := json.NewEncoder(&buf).Encode(args); err != nil {
+ return err
+ }
+
+ req, err := http.NewRequest("POST", "/"+serviceMethod, &buf)
+ if err != nil {
+ return err
+ }
+ req.Header.Add("Accept", versionMimetype)
+ req.URL.Scheme = "http"
+ req.URL.Host = c.addr
+
+ var retries int
+ start := time.Now()
+
+ for {
+ resp, err := c.http.Do(req)
+ if err != nil {
+ if !retry {
+ return err
+ }
+
+ timeOff := backoff(retries)
+ if abort(start, timeOff) {
+ return err
+ }
+ retries++
+ logrus.Warnf("Unable to connect to plugin: %s, retrying in %v", c.addr, timeOff)
+ time.Sleep(timeOff)
+ continue
+ }
+
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ remoteErr, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil
+ }
+ return fmt.Errorf("Plugin Error: %s", remoteErr)
+ }
+
+ return json.NewDecoder(resp.Body).Decode(&ret)
+ }
+}
+
+func backoff(retries int) time.Duration {
+ b, max := 1, defaultTimeOut
+ for b < max && retries > 0 {
+ b *= 2
+ retries--
+ }
+ if b > max {
+ b = max
+ }
+ return time.Duration(b) * time.Second
+}
+
+func abort(start time.Time, timeOff time.Duration) bool {
+ return timeOff+time.Since(start) > time.Duration(defaultTimeOut)*time.Second
+}
+
+func configureTCPTransport(tr *http.Transport, proto, addr string) {
+ // Why 32? See https://github.com/docker/docker/pull/8035.
+ timeout := 32 * time.Second
+ if proto == "unix" {
+ // No need for compression in local communications.
+ tr.DisableCompression = true
+ tr.Dial = func(_, _ string) (net.Conn, error) {
+ return net.DialTimeout(proto, addr, timeout)
+ }
+ } else {
+ tr.Proxy = http.ProxyFromEnvironment
+ tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
+ }
+}
diff --git a/pkg/plugins/client_test.go b/pkg/plugins/client_test.go
new file mode 100644
index 000000000..0f7cd34df
--- /dev/null
+++ b/pkg/plugins/client_test.go
@@ -0,0 +1,105 @@
+package plugins
+
+import (
+ "io"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "testing"
+ "time"
+)
+
+var (
+ mux *http.ServeMux
+ server *httptest.Server
+)
+
+func setupRemotePluginServer() string {
+ mux = http.NewServeMux()
+ server = httptest.NewServer(mux)
+ return server.URL
+}
+
+func teardownRemotePluginServer() {
+ if server != nil {
+ server.Close()
+ }
+}
+
+func TestFailedConnection(t *testing.T) {
+ c := NewClient("tcp://127.0.0.1:1")
+ err := c.callWithRetry("Service.Method", nil, nil, false)
+ if err == nil {
+ t.Fatal("Unexpected successful connection")
+ }
+}
+
+func TestEchoInputOutput(t *testing.T) {
+ addr := setupRemotePluginServer()
+ defer teardownRemotePluginServer()
+
+ m := Manifest{[]string{"VolumeDriver", "NetworkDriver"}}
+
+ mux.HandleFunc("/Test.Echo", func(w http.ResponseWriter, r *http.Request) {
+ if r.Method != "POST" {
+ t.Fatalf("Expected POST, got %s\n", r.Method)
+ }
+
+ header := w.Header()
+ header.Set("Content-Type", versionMimetype)
+
+ io.Copy(w, r.Body)
+ })
+
+ c := NewClient(addr)
+ var output Manifest
+ err := c.Call("Test.Echo", m, &output)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if !reflect.DeepEqual(output, m) {
+ t.Fatalf("Expected %v, was %v\n", m, output)
+ }
+}
+
+func TestBackoff(t *testing.T) {
+ cases := []struct {
+ retries int
+ expTimeOff time.Duration
+ }{
+ {0, time.Duration(1)},
+ {1, time.Duration(2)},
+ {2, time.Duration(4)},
+ {4, time.Duration(16)},
+ {6, time.Duration(30)},
+ {10, time.Duration(30)},
+ }
+
+ for _, c := range cases {
+ s := c.expTimeOff * time.Second
+ if d := backoff(c.retries); d != s {
+ t.Fatalf("Retry %v, expected %v, was %v\n", c.retries, s, d)
+ }
+ }
+}
+
+func TestAbortRetry(t *testing.T) {
+ cases := []struct {
+ timeOff time.Duration
+ expAbort bool
+ }{
+ {time.Duration(1), false},
+ {time.Duration(2), false},
+ {time.Duration(10), false},
+ {time.Duration(30), true},
+ {time.Duration(40), true},
+ }
+
+ for _, c := range cases {
+ s := c.timeOff * time.Second
+ if a := abort(time.Now(), s); a != c.expAbort {
+ t.Fatalf("Duration %v, expected %v, was %v\n", c.timeOff, s, a)
+ }
+ }
+}
diff --git a/pkg/plugins/discovery.go b/pkg/plugins/discovery.go
new file mode 100644
index 000000000..3a42ba6d1
--- /dev/null
+++ b/pkg/plugins/discovery.go
@@ -0,0 +1,78 @@
+package plugins
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "net/url"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+const defaultLocalRegistry = "/usr/share/docker/plugins"
+
+var (
+ ErrNotFound = errors.New("Plugin not found")
+)
+
+type Registry interface {
+ Plugins() ([]*Plugin, error)
+ Plugin(name string) (*Plugin, error)
+}
+
+type LocalRegistry struct {
+ path string
+}
+
+func newLocalRegistry(path string) *LocalRegistry {
+ if len(path) == 0 {
+ path = defaultLocalRegistry
+ }
+
+ return &LocalRegistry{path}
+}
+
+func (l *LocalRegistry) Plugin(name string) (*Plugin, error) {
+ filepath := filepath.Join(l.path, name)
+ specpath := filepath + ".spec"
+ if fi, err := os.Stat(specpath); err == nil {
+ return readPluginInfo(specpath, fi)
+ }
+ socketpath := filepath + ".sock"
+ if fi, err := os.Stat(socketpath); err == nil {
+ return readPluginInfo(socketpath, fi)
+ }
+ return nil, ErrNotFound
+}
+
+func readPluginInfo(path string, fi os.FileInfo) (*Plugin, error) {
+ name := strings.Split(fi.Name(), ".")[0]
+
+ if fi.Mode()&os.ModeSocket != 0 {
+ return &Plugin{
+ Name: name,
+ Addr: "unix://" + path,
+ }, nil
+ }
+
+ content, err := ioutil.ReadFile(path)
+ if err != nil {
+ return nil, err
+ }
+ addr := strings.TrimSpace(string(content))
+
+ u, err := url.Parse(addr)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(u.Scheme) == 0 {
+ return nil, fmt.Errorf("Unknown protocol")
+ }
+
+ return &Plugin{
+ Name: name,
+ Addr: addr,
+ }, nil
+}
diff --git a/pkg/plugins/discovery_test.go b/pkg/plugins/discovery_test.go
new file mode 100644
index 000000000..b6e66e289
--- /dev/null
+++ b/pkg/plugins/discovery_test.go
@@ -0,0 +1,108 @@
+package plugins
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "path"
+ "path/filepath"
+ "reflect"
+ "testing"
+)
+
+func TestUnknownLocalPath(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "docker-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+
+ l := newLocalRegistry(filepath.Join(tmpdir, "unknown"))
+ _, err = l.Plugin("foo")
+ if err == nil || err != ErrNotFound {
+ t.Fatalf("Expected error for unknown directory")
+ }
+}
+
+func TestLocalSocket(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "docker-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer os.RemoveAll(tmpdir)
+ l, err := net.Listen("unix", filepath.Join(tmpdir, "echo.sock"))
+ if err != nil {
+ t.Fatal(err)
+ }
+ defer l.Close()
+
+ r := newLocalRegistry(tmpdir)
+ p, err := r.Plugin("echo")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ pp, err := r.Plugin("echo")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !reflect.DeepEqual(p, pp) {
+ t.Fatalf("Expected %v, was %v\n", p, pp)
+ }
+
+ if p.Name != "echo" {
+ t.Fatalf("Expected plugin `echo`, got %s\n", p.Name)
+ }
+
+ addr := fmt.Sprintf("unix://%s/echo.sock", tmpdir)
+ if p.Addr != addr {
+ t.Fatalf("Expected plugin addr `%s`, got %s\n", addr, p.Addr)
+ }
+}
+
+func TestFileSpecPlugin(t *testing.T) {
+ tmpdir, err := ioutil.TempDir("", "docker-test")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ cases := []struct {
+ path string
+ name string
+ addr string
+ fail bool
+ }{
+ {filepath.Join(tmpdir, "echo.spec"), "echo", "unix://var/lib/docker/plugins/echo.sock", false},
+ {filepath.Join(tmpdir, "foo.spec"), "foo", "tcp://localhost:8080", false},
+ {filepath.Join(tmpdir, "bar.spec"), "bar", "localhost:8080", true}, // unknown transport
+ }
+
+ for _, c := range cases {
+ if err = os.MkdirAll(path.Dir(c.path), 0755); err != nil {
+ t.Fatal(err)
+ }
+ if err = ioutil.WriteFile(c.path, []byte(c.addr), 0644); err != nil {
+ t.Fatal(err)
+ }
+
+ r := newLocalRegistry(tmpdir)
+ p, err := r.Plugin(c.name)
+ if c.fail && err == nil {
+ continue
+ }
+
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if p.Name != c.name {
+ t.Fatalf("Expected plugin `%s`, got %s\n", c.name, p.Name)
+ }
+
+ if p.Addr != c.addr {
+ t.Fatalf("Expected plugin addr `%s`, got %s\n", c.addr, p.Addr)
+ }
+ os.Remove(c.path)
+ }
+}
diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go
new file mode 100644
index 000000000..47519486b
--- /dev/null
+++ b/pkg/plugins/plugins.go
@@ -0,0 +1,100 @@
+package plugins
+
+import (
+ "errors"
+ "sync"
+
+ "github.com/Sirupsen/logrus"
+)
+
+var (
+ ErrNotImplements = errors.New("Plugin does not implement the requested driver")
+)
+
+type plugins struct {
+ sync.Mutex
+ plugins map[string]*Plugin
+}
+
+var (
+ storage = plugins{plugins: make(map[string]*Plugin)}
+ extpointHandlers = make(map[string]func(string, *Client))
+)
+
+type Manifest struct {
+ Implements []string
+}
+
+type Plugin struct {
+ Name string
+ Addr string
+ Client *Client
+ Manifest *Manifest
+}
+
+func (p *Plugin) activate() error {
+ m := new(Manifest)
+ p.Client = NewClient(p.Addr)
+ err := p.Client.Call("Plugin.Activate", nil, m)
+ if err != nil {
+ return err
+ }
+
+ logrus.Debugf("%s's manifest: %v", p.Name, m)
+ p.Manifest = m
+ for _, iface := range m.Implements {
+ handler, handled := extpointHandlers[iface]
+ if !handled {
+ continue
+ }
+ handler(p.Name, p.Client)
+ }
+ return nil
+}
+
+func load(name string) (*Plugin, error) {
+ registry := newLocalRegistry("")
+ pl, err := registry.Plugin(name)
+ if err != nil {
+ return nil, err
+ }
+ if err := pl.activate(); err != nil {
+ return nil, err
+ }
+ return pl, nil
+}
+
+func get(name string) (*Plugin, error) {
+ storage.Lock()
+ defer storage.Unlock()
+ pl, ok := storage.plugins[name]
+ if ok {
+ return pl, nil
+ }
+ pl, err := load(name)
+ if err != nil {
+ return nil, err
+ }
+
+ logrus.Debugf("Plugin: %v", pl)
+ storage.plugins[name] = pl
+ return pl, nil
+}
+
+func Get(name, imp string) (*Plugin, error) {
+ pl, err := get(name)
+ if err != nil {
+ return nil, err
+ }
+ for _, driver := range pl.Manifest.Implements {
+ logrus.Debugf("%s implements: %s", name, driver)
+ if driver == imp {
+ return pl, nil
+ }
+ }
+ return nil, ErrNotImplements
+}
+
+func Handle(iface string, fn func(string, *Client)) {
+ extpointHandlers[iface] = fn
+}
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
- })
-}
diff --git a/pkg/progressreader/progressreader.go b/pkg/progressreader/progressreader.go
index f6ad06a3b..652831bff 100644
--- a/pkg/progressreader/progressreader.go
+++ b/pkg/progressreader/progressreader.go
@@ -2,36 +2,16 @@ package progressreader
import (
"io"
+
+ "github.com/docker/docker/pkg/jsonmessage"
+ "github.com/docker/docker/pkg/streamformatter"
)
-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 +34,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
@@ -65,6 +45,6 @@ func (config *Config) Read(p []byte) (n int, err error) {
}
func (config *Config) Close() error {
config.Current = config.Size
- 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/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/pubsub/publisher.go b/pkg/pubsub/publisher.go
index f017262ae..534b74ad4 100644
--- a/pkg/pubsub/publisher.go
+++ b/pkg/pubsub/publisher.go
@@ -68,6 +68,7 @@ func (p *Publisher) Publish(v interface{}) {
func (p *Publisher) Close() {
p.m.Lock()
for sub := range p.subscribers {
+ delete(p.subscribers, sub)
close(sub)
}
p.m.Unlock()
diff --git a/pkg/random/random.go b/pkg/random/random.go
new file mode 100644
index 000000000..05b7f7fb3
--- /dev/null
+++ b/pkg/random/random.go
@@ -0,0 +1,34 @@
+package random
+
+import (
+ "math/rand"
+ "sync"
+ "time"
+)
+
+// copypaste from standard math/rand
+type lockedSource struct {
+ lk sync.Mutex
+ src rand.Source
+}
+
+func (r *lockedSource) Int63() (n int64) {
+ r.lk.Lock()
+ n = r.src.Int63()
+ r.lk.Unlock()
+ return
+}
+
+func (r *lockedSource) Seed(seed int64) {
+ r.lk.Lock()
+ r.src.Seed(seed)
+ r.lk.Unlock()
+}
+
+// NewSource returns math/rand.Source safe for concurrent use and initialized
+// with current unix-nano timestamp
+func NewSource() rand.Source {
+ return &lockedSource{
+ src: rand.NewSource(time.Now().UnixNano()),
+ }
+}
diff --git a/pkg/random/random_test.go b/pkg/random/random_test.go
new file mode 100644
index 000000000..cf405f78c
--- /dev/null
+++ b/pkg/random/random_test.go
@@ -0,0 +1,22 @@
+package random
+
+import (
+ "math/rand"
+ "sync"
+ "testing"
+)
+
+// for go test -v -race
+func TestConcurrency(t *testing.T) {
+ rnd := rand.New(NewSource())
+ var wg sync.WaitGroup
+
+ for i := 0; i < 10; i++ {
+ wg.Add(1)
+ go func() {
+ rnd.Int63()
+ wg.Done()
+ }()
+ }
+ wg.Wait()
+}
diff --git a/pkg/reexec/command_unsupported.go b/pkg/reexec/command_unsupported.go
index a579318e8..4adcd8f13 100644
--- a/pkg/reexec/command_unsupported.go
+++ b/pkg/reexec/command_unsupported.go
@@ -1,4 +1,4 @@
-// +build !linux
+// +build !linux,!windows
package reexec
diff --git a/pkg/reexec/command_windows.go b/pkg/reexec/command_windows.go
new file mode 100644
index 000000000..124d42fc6
--- /dev/null
+++ b/pkg/reexec/command_windows.go
@@ -0,0 +1,14 @@
+// +build windows
+
+package reexec
+
+import (
+ "os/exec"
+)
+
+func Command(args ...string) *exec.Cmd {
+ return &exec.Cmd{
+ Path: Self(),
+ Args: args,
+ }
+}
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
diff --git a/pkg/signal/trap.go b/pkg/signal/trap.go
index 78a709b30..3772db5e1 100644
--- a/pkg/signal/trap.go
+++ b/pkg/signal/trap.go
@@ -3,10 +3,11 @@ package signal
import (
"os"
gosignal "os/signal"
+ "runtime"
"sync/atomic"
"syscall"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
)
// Trap sets up a simplified signal "trap", appropriate for common
@@ -14,41 +15,50 @@ import (
// (and the Docker engine in particular).
//
// * If SIGINT or SIGTERM are received, `cleanup` is called, then the process is terminated.
-// * If SIGINT or SIGTERM are repeated 3 times before cleanup is complete, then cleanup is
-// skipped and the process terminated directly.
-// * If "DEBUG" is set in the environment, SIGQUIT causes an exit without cleanup.
+// * If SIGINT or SIGTERM are received 3 times before cleanup is complete, then cleanup is
+// skipped and the process is terminated immediately (allows force quit of stuck daemon)
+// * A SIGQUIT always causes an exit without cleanup, with a goroutine dump preceding exit.
//
func Trap(cleanup func()) {
c := make(chan os.Signal, 1)
- signals := []os.Signal{os.Interrupt, syscall.SIGTERM}
- if os.Getenv("DEBUG") == "" {
- signals = append(signals, syscall.SIGQUIT)
- }
+ // we will handle INT, TERM, QUIT here
+ signals := []os.Signal{os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT}
gosignal.Notify(c, signals...)
go 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("Processing signal '%v'", sig)
switch sig {
case os.Interrupt, syscall.SIGTERM:
- // If the user really wants to interrupt, let him do so.
if atomic.LoadUint32(&interruptCount) < 3 {
// Initiate the cleanup only once
if atomic.AddUint32(&interruptCount, 1) == 1 {
- // Call cleanup handler
+ // Call the provided cleanup handler
cleanup()
os.Exit(0)
} else {
return
}
} else {
- log.Infof("Force shutdown of docker, interrupting cleanup")
+ // 3 SIGTERM/INT signals received; force exit without cleanup
+ logrus.Infof("Forcing docker daemon shutdown without cleanup; 3 interrupts received")
}
case syscall.SIGQUIT:
+ DumpStacks()
+ logrus.Infof("Forcing docker daemon shutdown without cleanup on SIGQUIT")
}
+ //for the SIGINT/TERM, and SIGQUIT non-clean shutdown case, exit with 128 + signal #
os.Exit(128 + int(sig.(syscall.Signal)))
}(sig)
}
}()
}
+
+func DumpStacks() {
+ buf := make([]byte, 16384)
+ buf = buf[:runtime.Stack(buf, true)]
+ // Note that if the daemon is started with a less-verbose log-level than "info" (the default), the goroutine
+ // traces won't show up in the log.
+ logrus.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf)
+}
diff --git a/pkg/sockets/README.md b/pkg/sockets/README.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/pkg/sockets/tcp_socket.go b/pkg/sockets/tcp_socket.go
new file mode 100644
index 000000000..ac9edaebd
--- /dev/null
+++ b/pkg/sockets/tcp_socket.go
@@ -0,0 +1,69 @@
+package sockets
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+
+ "github.com/docker/docker/pkg/listenbuffer"
+)
+
+type TlsConfig struct {
+ CA string
+ Certificate string
+ Key string
+ Verify bool
+}
+
+func NewTlsConfig(tlsCert, tlsKey, tlsCA string, verify bool) *TlsConfig {
+ return &TlsConfig{
+ Verify: verify,
+ Certificate: tlsCert,
+ Key: tlsKey,
+ CA: tlsCA,
+ }
+}
+
+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
+ }
+ 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/pkg/sockets/unix_socket.go b/pkg/sockets/unix_socket.go
new file mode 100644
index 000000000..0536382c8
--- /dev/null
+++ b/pkg/sockets/unix_socket.go
@@ -0,0 +1,80 @@
+// +build linux
+
+package sockets
+
+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, 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, activate)
+ 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/pkg/stdcopy/stdcopy.go b/pkg/stdcopy/stdcopy.go
index a61779ce5..dbb74e5a2 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 (
@@ -52,12 +52,8 @@ 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
- }
-
return &StdWriter{
Writer: w,
prefix: t,
@@ -95,13 +91,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 +113,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 +136,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 +150,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/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")
diff --git a/utils/streamformatter.go b/pkg/streamformatter/streamformatter.go
similarity index 67%
rename from utils/streamformatter.go
rename to pkg/streamformatter/streamformatter.go
index e5b15f983..792ce00f9 100644
--- a/utils/streamformatter.go
+++ b/pkg/streamformatter/streamformatter.go
@@ -1,18 +1,25 @@
-package utils
+package streamformatter
import (
"encoding/json"
"fmt"
- "github.com/docker/docker/pkg/progressreader"
"io"
+
+ "github.com/docker/docker/pkg/jsonmessage"
)
type StreamFormatter struct {
json bool
}
-func NewStreamFormatter(json bool) *StreamFormatter {
- return &StreamFormatter{json}
+// NewStreamFormatter returns a simple StreamFormatter
+func NewStreamFormatter() *StreamFormatter {
+ return &StreamFormatter{}
+}
+
+// NewJSONStreamFormatter returns a StreamFormatter configured to stream json
+func NewJSONStreamFormatter() *StreamFormatter {
+ return &StreamFormatter{true}
}
const streamNewline = "\r\n"
@@ -21,7 +28,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 +40,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 +51,24 @@ 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,
@@ -88,10 +86,6 @@ func (sf *StreamFormatter) FormatProgress(id, action string, progress *JSONProgr
return []byte(action + " " + progress.String() + endl)
}
-func (sf *StreamFormatter) Json() bool {
- return sf.json
-}
-
type StdoutFormater struct {
io.Writer
*StreamFormatter
diff --git a/utils/streamformatter_test.go b/pkg/streamformatter/streamformatter_test.go
similarity index 55%
rename from utils/streamformatter_test.go
rename to pkg/streamformatter/streamformatter_test.go
index 20610f6c0..acf81bef6 100644
--- a/utils/streamformatter_test.go
+++ b/pkg/streamformatter/streamformatter_test.go
@@ -1,54 +1,80 @@
-package utils
+package streamformatter
import (
"encoding/json"
"errors"
"reflect"
"testing"
+
+ "github.com/docker/docker/pkg/jsonmessage"
)
func TestFormatStream(t *testing.T) {
- sf := NewStreamFormatter(true)
+ sf := NewStreamFormatter()
+ res := sf.FormatStream("stream")
+ if string(res) != "stream"+"\r" {
+ t.Fatalf("%q", res)
+ }
+}
+
+func TestFormatJSONStatus(t *testing.T) {
+ sf := NewStreamFormatter()
+ res := sf.FormatStatus("ID", "%s%d", "a", 1)
+ if string(res) != "a1\r\n" {
+ t.Fatalf("%q", res)
+ }
+}
+
+func TestFormatSimpleError(t *testing.T) {
+ sf := NewStreamFormatter()
+ res := sf.FormatError(errors.New("Error for formatter"))
+ if string(res) != "Error: Error for formatter\r\n" {
+ t.Fatalf("%q", res)
+ }
+}
+
+func TestJSONFormatStream(t *testing.T) {
+ sf := NewJSONStreamFormatter()
res := sf.FormatStream("stream")
if string(res) != `{"stream":"stream"}`+"\r\n" {
t.Fatalf("%q", res)
}
}
-func TestFormatStatus(t *testing.T) {
- sf := NewStreamFormatter(true)
+func TestJSONFormatStatus(t *testing.T) {
+ sf := NewJSONStreamFormatter()
res := sf.FormatStatus("ID", "%s%d", "a", 1)
if string(res) != `{"status":"a1","id":"ID"}`+"\r\n" {
t.Fatalf("%q", res)
}
}
-func TestFormatSimpleError(t *testing.T) {
- sf := NewStreamFormatter(true)
+func TestJSONFormatSimpleError(t *testing.T) {
+ sf := NewJSONStreamFormatter()
res := sf.FormatError(errors.New("Error for formatter"))
if string(res) != `{"errorDetail":{"message":"Error for formatter"},"error":"Error for formatter"}`+"\r\n" {
t.Fatalf("%q", res)
}
}
-func TestFormatJSONError(t *testing.T) {
- sf := NewStreamFormatter(true)
- err := &JSONError{Code: 50, Message: "Json error"}
+func TestJSONFormatJSONError(t *testing.T) {
+ sf := NewJSONStreamFormatter()
+ 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)
}
}
-func TestFormatProgress(t *testing.T) {
- sf := NewStreamFormatter(true)
- progress := &JSONProgress{
+func TestJSONFormatProgress(t *testing.T) {
+ sf := NewJSONStreamFormatter()
+ 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/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 76%
rename from pkg/common/randomid.go
rename to pkg/stringid/stringid.go
index 5c6d5920e..6a683b686 100644
--- a/pkg/common/randomid.go
+++ b/pkg/stringid/stringid.go
@@ -1,22 +1,32 @@
-package common
+package stringid
import (
"crypto/rand"
"encoding/hex"
"io"
+ "regexp"
"strconv"
)
+const shortLen = 12
+
+var validShortID = regexp.MustCompile("^[a-z0-9]{12}$")
+
+// Determine if an arbitrary string *looks like* a short ID.
+func IsShortID(id string) bool {
+ return validShortID.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
@@ -36,12 +46,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 61%
rename from pkg/common/randomid_test.go
rename to pkg/stringid/stringid_test.go
index 1dba41254..bcb136549 100644
--- a/pkg/common/randomid_test.go
+++ b/pkg/stringid/stringid_test.go
@@ -1,9 +1,18 @@
-package common
+package stringid
import (
+ "strings"
"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()
truncID := TruncateID(id)
@@ -28,32 +37,20 @@ func TestShortenIdInvalid(t *testing.T) {
}
}
-func TestGenerateRandomID(t *testing.T) {
- id := GenerateRandomID()
-
- if len(id) != 64 {
- t.Fatalf("Id returned is incorrect: %s", id)
+func TestIsShortIDNonHex(t *testing.T) {
+ id := "some non-hex value"
+ if IsShortID(id) {
+ t.Fatalf("%s is not a short ID", id)
}
}
-func TestRandomString(t *testing.T) {
- id := RandomString()
- if len(id) != 64 {
- t.Fatalf("Id returned is incorrect: %s", id)
+func TestIsShortIDNotCorrectSize(t *testing.T) {
+ id := strings.Repeat("a", shortLen+1)
+ if IsShortID(id) {
+ t.Fatalf("%s is not a short ID", 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{}{}
+ id = strings.Repeat("a", shortLen-1)
+ if IsShortID(id) {
+ t.Fatalf("%s is not a short ID", id)
}
}
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..aee2648b2
--- /dev/null
+++ b/pkg/stringutils/stringutils.go
@@ -0,0 +1,87 @@
+package stringutils
+
+import (
+ "bytes"
+ "math/rand"
+ "strings"
+
+ "github.com/docker/docker/pkg/random"
+)
+
+// 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 := rand.New(random.NewSource())
+ 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[rand.Intn(len(chars))]
+ }
+ 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
new file mode 100644
index 000000000..8dcb4696b
--- /dev/null
+++ b/pkg/stringutils/stringutils_test.go
@@ -0,0 +1,87 @@
+package stringutils
+
+import "testing"
+
+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)
+ }
+}
+
+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/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 1d540d2e7..5b7eca245 100644
--- a/pkg/sysinfo/sysinfo.go
+++ b/pkg/sysinfo/sysinfo.go
@@ -1,47 +1,13 @@
package sysinfo
-import (
- "io/ioutil"
- "os"
- "path"
-
- log "github.com/Sirupsen/logrus"
- "github.com/docker/libcontainer/cgroups"
-)
-
+// SysInfo stores information about which features a kernel supports.
+// TODO Windows: Factor out platform specific capabilities.
type SysInfo struct {
MemoryLimit bool
SwapLimit bool
+ CpuCfsPeriod bool
+ CpuCfsQuota bool
IPv4ForwardingDisabled bool
AppArmor bool
-}
-
-func New(quiet bool) *SysInfo {
- sysInfo := &SysInfo{}
- if cgroupMemoryMountpoint, err := cgroups.FindCgroupMountpoint("memory"); err != nil {
- if !quiet {
- log.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.")
- }
-
- _, 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.")
- }
- }
-
- // Check if AppArmor seems to be enabled on this system.
- if _, err := os.Stat("/sys/kernel/security/apparmor"); os.IsNotExist(err) {
- sysInfo.AppArmor = false
- } else {
- sysInfo.AppArmor = true
- }
- return sysInfo
+ OomKillDisable bool
}
diff --git a/pkg/sysinfo/sysinfo_linux.go b/pkg/sysinfo/sysinfo_linux.go
new file mode 100644
index 000000000..396ea3b27
--- /dev/null
+++ b/pkg/sysinfo/sysinfo_linux.go
@@ -0,0 +1,79 @@
+package sysinfo
+
+import (
+ "io/ioutil"
+ "os"
+ "path"
+ "strconv"
+ "strings"
+
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/libcontainer/cgroups"
+)
+
+// 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 {
+ if !quiet {
+ logrus.Warnf("Your kernel does not support cgroup memory limit: %v", err)
+ }
+ } else {
+ // If memory cgroup is mounted, MemoryLimit is always enabled.
+ sysInfo.MemoryLimit = true
+
+ _, 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 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 {
+ if !quiet {
+ logrus.Warnf("%v", err)
+ }
+ } else {
+ _, err := ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_period_us"))
+ sysInfo.CpuCfsPeriod = err == nil
+ if !sysInfo.CpuCfsPeriod && !quiet {
+ logrus.Warn("Your kernel does not support cgroup cfs period")
+ }
+ _, err = ioutil.ReadFile(path.Join(cgroupCpuMountpoint, "cpu.cfs_quota_us"))
+ sysInfo.CpuCfsQuota = err == nil
+ if !sysInfo.CpuCfsQuota && !quiet {
+ logrus.Warn("Your kernel does not support cgroup cfs quotas")
+ }
+ }
+
+ // 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
+ } 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
+}
diff --git a/pkg/sysinfo/sysinfo_windows.go b/pkg/sysinfo/sysinfo_windows.go
new file mode 100644
index 000000000..b4d31519f
--- /dev/null
+++ b/pkg/sysinfo/sysinfo_windows.go
@@ -0,0 +1,7 @@
+package sysinfo
+
+// TODO Windows
+func New(quiet bool) *SysInfo {
+ sysInfo := &SysInfo{}
+ return sysInfo
+}
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
+}
diff --git a/pkg/system/lstat.go b/pkg/system/lstat.go
index 6c1ed2e38..d0e43b370 100644
--- a/pkg/system/lstat.go
+++ b/pkg/system/lstat.go
@@ -6,10 +6,13 @@ 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)
- if err != nil {
+ if err := syscall.Lstat(path, s); err != nil {
return nil, err
}
return fromStatT(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/lstat_windows.go b/pkg/system/lstat_windows.go
index 801e756d8..eee1be26e 100644
--- a/pkg/system/lstat_windows.go
+++ b/pkg/system/lstat_windows.go
@@ -2,7 +2,28 @@
package system
+import (
+ "os"
+)
+
+// Some explanation for my own sanity, and hopefully maintainers in the
+// future.
+//
+// Lstat calls os.Lstat to get a fileinfo interface back.
+// This is then copied into our own locally defined structure.
+// Note the Linux version uses fromStatT to do the copy back,
+// but that not strictly necessary when already in an OS specific module.
+
func Lstat(path string) (*Stat_t, error) {
- // should not be called on cli code path
- return nil, ErrNotSupportedPlatform
+ fi, err := os.Lstat(path)
+ if err != nil {
+ return nil, err
+ }
+
+ return &Stat_t{
+ name: fi.Name(),
+ size: fi.Size(),
+ mode: fi.Mode(),
+ modTime: fi.ModTime(),
+ isDir: fi.IsDir()}, nil
}
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/mknod_windows.go b/pkg/system/mknod_windows.go
index b4020c11b..1811542ab 100644
--- a/pkg/system/mknod_windows.go
+++ b/pkg/system/mknod_windows.go
@@ -3,10 +3,9 @@
package system
func Mknod(path string, mode uint32, dev int) error {
- // should not be called on cli code path
return ErrNotSupportedPlatform
}
func Mkdev(major int64, minor int64) uint32 {
- panic("Mkdev not implemented on windows, should not be called on cli code")
+ panic("Mkdev not implemented on Windows.")
}
diff --git a/pkg/system/stat.go b/pkg/system/stat.go
index 186e85287..e2ecfe52f 100644
--- a/pkg/system/stat.go
+++ b/pkg/system/stat.go
@@ -1,9 +1,13 @@
+// +build !windows
+
package system
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..3899b3e0e 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,10 +14,13 @@ 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)
- if err != nil {
+ if err := syscall.Stat(path, s); err != nil {
return nil, err
}
return fromStatT(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/stat_windows.go b/pkg/system/stat_windows.go
index 42d29d6cc..b1fd39e83 100644
--- a/pkg/system/stat_windows.go
+++ b/pkg/system/stat_windows.go
@@ -3,15 +3,34 @@
package system
import (
- "errors"
- "syscall"
+ "os"
+ "time"
)
-func fromStatT(s *syscall.Win32FileAttributeData) (*Stat_t, error) {
- return nil, errors.New("fromStatT should not be called on windows path")
+type Stat_t struct {
+ name string
+ size int64
+ mode os.FileMode
+ modTime time.Time
+ isDir bool
}
-func Stat(path string) (*Stat_t, error) {
- // should not be called on cli code path
- return nil, ErrNotSupportedPlatform
+func (s Stat_t) Name() string {
+ return s.name
+}
+
+func (s Stat_t) Size() int64 {
+ return s.size
+}
+
+func (s Stat_t) Mode() os.FileMode {
+ return s.mode
+}
+
+func (s Stat_t) ModTime() time.Time {
+ return s.modTime
+}
+
+func (s Stat_t) IsDir() bool {
+ return s.isDir
}
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 {
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/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
}
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/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
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)
-}
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/pkg/timeutils/utils.go b/pkg/timeutils/utils.go
new file mode 100644
index 000000000..6af16a1d7
--- /dev/null
+++ b/pkg/timeutils/utils.go
@@ -0,0 +1,29 @@
+package timeutils
+
+import (
+ "strconv"
+ "strings"
+ "time"
+)
+
+// GetTimestamp tries to parse given string as RFC3339 time
+// or Unix timestamp (with seconds precision), if successful
+//returns a Unix timestamp as string otherwise returns value back.
+func GetTimestamp(value string) string {
+ var format string
+ if strings.Contains(value, ".") {
+ format = time.RFC3339Nano
+ } else {
+ format = time.RFC3339
+ }
+
+ loc := time.FixedZone(time.Now().Zone())
+ if len(value) < len(format) {
+ format = format[:len(value)]
+ }
+ t, err := time.ParseInLocation(format, value, loc)
+ if err != nil {
+ return value
+ }
+ return strconv.FormatInt(t.Unix(), 10)
+}
diff --git a/pkg/timeutils/utils_test.go b/pkg/timeutils/utils_test.go
new file mode 100644
index 000000000..1d724fb2a
--- /dev/null
+++ b/pkg/timeutils/utils_test.go
@@ -0,0 +1,36 @@
+package timeutils
+
+import (
+ "testing"
+)
+
+func TestGetTimestamp(t *testing.T) {
+ cases := []struct{ in, expected string }{
+ {"0", "-62167305600"}, // 0 gets parsed year 0
+
+ // Partial RFC3339 strings get parsed with second precision
+ {"2006-01-02T15:04:05.999999999+07:00", "1136189045"},
+ {"2006-01-02T15:04:05.999999999Z", "1136214245"},
+ {"2006-01-02T15:04:05.999999999", "1136214245"},
+ {"2006-01-02T15:04:05", "1136214245"},
+ {"2006-01-02T15:04", "1136214240"},
+ {"2006-01-02T15", "1136214000"},
+ {"2006-01-02T", "1136160000"},
+ {"2006-01-02", "1136160000"},
+ {"2006", "1136073600"},
+ {"2015-05-13T20:39:09Z", "1431549549"},
+
+ // unix timestamps returned as is
+ {"1136073600", "1136073600"},
+
+ // String fallback
+ {"invalid", "invalid"},
+ }
+
+ for _, c := range cases {
+ o := GetTimestamp(c.in)
+ if o != c.expected {
+ t.Fatalf("wrong value for '%s'. expected:'%s' got:'%s'", c.in, c.expected, o)
+ }
+ }
+}
diff --git a/vendor/src/github.com/go-fsnotify/fsnotify/LICENSE b/pkg/transport/LICENSE
similarity index 92%
rename from vendor/src/github.com/go-fsnotify/fsnotify/LICENSE
rename to pkg/transport/LICENSE
index f21e54080..d02f24fd5 100644
--- a/vendor/src/github.com/go-fsnotify/fsnotify/LICENSE
+++ b/pkg/transport/LICENSE
@@ -1,5 +1,4 @@
-Copyright (c) 2012 The Go Authors. All rights reserved.
-Copyright (c) 2012 fsnotify Authors. All rights reserved.
+Copyright (c) 2009 The oauth2 Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
diff --git a/pkg/transport/transport.go b/pkg/transport/transport.go
new file mode 100644
index 000000000..510d8b4bc
--- /dev/null
+++ b/pkg/transport/transport.go
@@ -0,0 +1,148 @@
+package transport
+
+import (
+ "io"
+ "net/http"
+ "sync"
+)
+
+type RequestModifier interface {
+ ModifyRequest(*http.Request) error
+}
+
+type headerModifier http.Header
+
+// NewHeaderRequestModifier returns a RequestModifier that merges the HTTP headers
+// passed as an argument, with the HTTP headers of a request.
+//
+// If the same key is present in both, the modifying header values for that key,
+// are appended to the values for that same key in the request header.
+func NewHeaderRequestModifier(header http.Header) RequestModifier {
+ return headerModifier(header)
+}
+
+func (h headerModifier) ModifyRequest(req *http.Request) error {
+ for k, s := range http.Header(h) {
+ req.Header[k] = append(req.Header[k], s...)
+ }
+
+ return nil
+}
+
+// NewTransport returns an http.RoundTripper that modifies requests according to
+// the RequestModifiers passed in the arguments, before sending the requests to
+// the base http.RoundTripper (which, if nil, defaults to http.DefaultTransport).
+func NewTransport(base http.RoundTripper, modifiers ...RequestModifier) http.RoundTripper {
+ return &transport{
+ Modifiers: modifiers,
+ Base: base,
+ }
+}
+
+// transport is an http.RoundTripper that makes HTTP requests after
+// copying and modifying the request
+type transport struct {
+ Modifiers []RequestModifier
+ Base http.RoundTripper
+
+ mu sync.Mutex // guards modReq
+ modReq map[*http.Request]*http.Request // original -> modified
+}
+
+func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) {
+ req2 := CloneRequest(req)
+ for _, modifier := range t.Modifiers {
+ if err := modifier.ModifyRequest(req2); err != nil {
+ return nil, err
+ }
+ }
+
+ t.setModReq(req, req2)
+ res, err := t.base().RoundTrip(req2)
+ if err != nil {
+ t.setModReq(req, nil)
+ return nil, err
+ }
+ res.Body = &OnEOFReader{
+ Rc: res.Body,
+ Fn: func() { t.setModReq(req, nil) },
+ }
+ return res, nil
+}
+
+// CancelRequest cancels an in-flight request by closing its connection.
+func (t *transport) CancelRequest(req *http.Request) {
+ type canceler interface {
+ CancelRequest(*http.Request)
+ }
+ if cr, ok := t.base().(canceler); ok {
+ t.mu.Lock()
+ modReq := t.modReq[req]
+ delete(t.modReq, req)
+ t.mu.Unlock()
+ cr.CancelRequest(modReq)
+ }
+}
+
+func (t *transport) base() http.RoundTripper {
+ if t.Base != nil {
+ return t.Base
+ }
+ return http.DefaultTransport
+}
+
+func (t *transport) setModReq(orig, mod *http.Request) {
+ t.mu.Lock()
+ defer t.mu.Unlock()
+ if t.modReq == nil {
+ t.modReq = make(map[*http.Request]*http.Request)
+ }
+ if mod == nil {
+ delete(t.modReq, orig)
+ } else {
+ t.modReq[orig] = mod
+ }
+}
+
+// CloneRequest returns a clone of the provided *http.Request.
+// The clone is a shallow copy of the struct and its Header map.
+func CloneRequest(r *http.Request) *http.Request {
+ // shallow copy of the struct
+ r2 := new(http.Request)
+ *r2 = *r
+ // deep copy of the Header
+ r2.Header = make(http.Header, len(r.Header))
+ for k, s := range r.Header {
+ r2.Header[k] = append([]string(nil), s...)
+ }
+
+ return r2
+}
+
+// OnEOFReader ensures a callback function is called
+// on Close() and when the underlying Reader returns an io.EOF error
+type OnEOFReader struct {
+ Rc io.ReadCloser
+ Fn func()
+}
+
+func (r *OnEOFReader) Read(p []byte) (n int, err error) {
+ n, err = r.Rc.Read(p)
+ if err == io.EOF {
+ r.runFunc()
+ }
+ return
+}
+
+func (r *OnEOFReader) Close() error {
+ err := r.Rc.Close()
+ r.runFunc()
+ return err
+}
+
+func (r *OnEOFReader) runFunc() {
+ if fn := r.Fn; fn != nil {
+ fn()
+ r.Fn = nil
+ }
+}
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/pkg/truncindex/truncindex_test.go b/pkg/truncindex/truncindex_test.go
index 928653428..7253f6a4c 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.
@@ -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")
}
@@ -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/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..1e8c881f5 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 %q, but got %q", u1, u2)
+ }
+}
+
func TestParseInvalidLimitType(t *testing.T) {
if _, err := Parse("notarealtype=1024:1024"); err == nil {
t.Fatalf("expected error on invalid ulimit type")
@@ -39,3 +46,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)
+ }
+}
diff --git a/pkg/units/duration.go b/pkg/units/duration.go
index cd3312149..44012aafb 100644
--- a/pkg/units/duration.go
+++ b/pkg/units/duration.go
@@ -27,5 +27,5 @@ func HumanDuration(d time.Duration) string {
} else if hours < 24*365*2 {
return fmt.Sprintf("%d months", hours/24/30)
}
- return fmt.Sprintf("%f years", d.Hours()/24/365)
+ return fmt.Sprintf("%d years", int(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))
}
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
diff --git a/pkg/urlutil/git.go b/pkg/urlutil/git.go
deleted file mode 100644
index ba88ddf6e..000000000
--- a/pkg/urlutil/git.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package urlutil
-
-import "strings"
-
-var (
- validPrefixes = []string{
- "git://",
- "github.com/",
- "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") {
- return true
- }
- for _, prefix := range validPrefixes {
- if strings.HasPrefix(str, prefix) {
- return true
- }
- }
- return false
-}
-
-// IsGitTransport returns true if the provided str is a git transport by inspecting
-// the prefix of the string for known protocols used in git.
-func IsGitTransport(str string) bool {
- return IsURL(str) || strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "git@")
-}
diff --git a/pkg/urlutil/url.go b/pkg/urlutil/url.go
deleted file mode 100644
index eeae56efe..000000000
--- a/pkg/urlutil/url.go
+++ /dev/null
@@ -1,19 +0,0 @@
-package urlutil
-
-import "strings"
-
-var validUrlPrefixes = []string{
- "http://",
- "https://",
-}
-
-// IsURL returns true if the provided str is a valid URL by doing
-// a simple change for the transport of the url.
-func IsURL(str string) bool {
- for _, prefix := range validUrlPrefixes {
- if strings.HasPrefix(str, prefix) {
- return true
- }
- }
- return false
-}
diff --git a/pkg/urlutil/urlutil.go b/pkg/urlutil/urlutil.go
new file mode 100644
index 000000000..7250643dd
--- /dev/null
+++ b/pkg/urlutil/urlutil.go
@@ -0,0 +1,48 @@
+package urlutil
+
+import (
+ "regexp"
+ "strings"
+)
+
+var (
+ validPrefixes = map[string][]string{
+ "url": {"http://", "https://"},
+ "git": {"git://", "github.com/", "git@"},
+ "transport": {"tcp://", "udp://", "unix://"},
+ }
+ urlPathWithFragmentSuffix = regexp.MustCompile(".git(?:#.+)?$")
+)
+
+// IsURL returns true if the provided str is an HTTP(S) URL.
+func IsURL(str string) bool {
+ return checkURL(str, "url")
+}
+
+// IsGitURL returns true if the provided str is a git repository URL.
+func IsGitURL(str string) bool {
+ if IsURL(str) && urlPathWithFragmentSuffix.MatchString(str) {
+ return true
+ }
+ return checkURL(str, "git")
+}
+
+// IsGitTransport returns true if the provided str is a git transport by inspecting
+// the prefix of the string for known protocols used in git.
+func IsGitTransport(str string) bool {
+ return IsURL(str) || strings.HasPrefix(str, "git://") || strings.HasPrefix(str, "git@")
+}
+
+// IsTransportURL returns true if the provided str is a transport (tcp, udp, unix) URL.
+func IsTransportURL(str string) bool {
+ return checkURL(str, "transport")
+}
+
+func checkURL(str, kind string) bool {
+ for _, prefix := range validPrefixes[kind] {
+ if strings.HasPrefix(str, prefix) {
+ return true
+ }
+ }
+ return false
+}
diff --git a/pkg/urlutil/git_test.go b/pkg/urlutil/urlutil_test.go
similarity index 75%
rename from pkg/urlutil/git_test.go
rename to pkg/urlutil/urlutil_test.go
index 01dcea7da..bb89d8b5f 100644
--- a/pkg/urlutil/git_test.go
+++ b/pkg/urlutil/urlutil_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/pkg/useragent/README.md b/pkg/useragent/README.md
new file mode 100644
index 000000000..d9cb367d1
--- /dev/null
+++ b/pkg/useragent/README.md
@@ -0,0 +1 @@
+This package provides helper functions to pack version information into a single User-Agent header.
diff --git a/pkg/useragent/useragent.go b/pkg/useragent/useragent.go
new file mode 100644
index 000000000..9e35d1c70
--- /dev/null
+++ b/pkg/useragent/useragent.go
@@ -0,0 +1,60 @@
+// Package useragent provides helper functions to pack
+// version information into a single User-Agent header.
+package useragent
+
+import (
+ "errors"
+ "strings"
+)
+
+var (
+ ErrNilRequest = errors.New("request cannot be nil")
+)
+
+// VersionInfo is used to model UserAgent versions.
+type VersionInfo struct {
+ Name string
+ Version string
+}
+
+func (vi *VersionInfo) 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 VersionInfo 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.
+//
+// Example:
+// AppendVersions("base", VersionInfo{"foo", "1.0"}, VersionInfo{"bar", "2.0"})
+// results in "base foo/1.0 bar/2.0".
+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 !v.isValid() {
+ continue
+ }
+ verstrs = append(verstrs, v.Name+"/"+v.Version)
+ }
+ return strings.Join(verstrs, " ")
+}
diff --git a/pkg/useragent/useragent_test.go b/pkg/useragent/useragent_test.go
new file mode 100644
index 000000000..0ad7243a6
--- /dev/null
+++ b/pkg/useragent/useragent_test.go
@@ -0,0 +1,31 @@
+package useragent
+
+import "testing"
+
+func TestVersionInfo(t *testing.T) {
+ vi := VersionInfo{"foo", "bar"}
+ if !vi.isValid() {
+ t.Fatalf("VersionInfo should be valid")
+ }
+ vi = VersionInfo{"", "bar"}
+ if vi.isValid() {
+ t.Fatalf("Expected VersionInfo to be invalid")
+ }
+ vi = VersionInfo{"foo", ""}
+ if vi.isValid() {
+ t.Fatalf("Expected VersionInfo to be invalid")
+ }
+}
+
+func TestAppendVersions(t *testing.T) {
+ vis := []VersionInfo{
+ {"foo", "1.0"},
+ {"bar", "0.1"},
+ {"pi", "3.1.4"},
+ }
+ v := AppendVersions("base", vis...)
+ expect := "base foo/1.0 bar/0.1 pi/3.1.4"
+ if v != expect {
+ t.Fatalf("expected %q, got %q", expect, v)
+ }
+}
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
}
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/ISSUE-TRIAGE.md b/project/ISSUE-TRIAGE.md
index 528dea9c6..be87cd81e 100644
--- a/project/ISSUE-TRIAGE.md
+++ b/project/ISSUE-TRIAGE.md
@@ -22,7 +22,7 @@ Before triaging an issue very far, make sure that the issue's author provided th
- the output of `uname -a`
- a reproducible case if this is a bug, Dockerfiles FTW
- host distribution and version ( ubuntu 14.04, RHEL, fedora 21 )
-- page URL if this is a docs issue
+- page URL if this is a docs issue or the name of a man page
Depending on the issue, you might not feel all this information is needed. Use your best judgement. If you cannot triage an issue using what its author provided, explain kindly to the author that they must provide the above information to clarify the problem.
diff --git a/project/PACKAGERS.md b/project/PACKAGERS.md
index 701e552d5..fd2156c58 100644
--- a/project/PACKAGERS.md
+++ b/project/PACKAGERS.md
@@ -45,9 +45,9 @@ 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](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)
@@ -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.
@@ -244,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
@@ -310,6 +303,7 @@ by having support for them in the kernel or userspace. A few examples include:
* AUFS graph driver (requires AUFS patches/support enabled in the kernel, and at
least the "auplink" utility from aufs-tools)
* BTRFS graph driver (requires BTRFS support enabled in the kernel)
+* ZFS graph driver (requires userspace zfs-utils and a corresponding kernel module)
## Daemon Init Script
diff --git a/project/RELEASE-CHECKLIST.md b/project/RELEASE-CHECKLIST.md
index d9382b901..00409094b 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/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.
@@ -145,7 +155,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)
@@ -153,7 +163,7 @@ To make a shared test at http://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:
@@ -341,7 +351,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`.
@@ -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,7 +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. Rejoice and Evangelize!
+### 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").
+
+### 17. Rejoice and Evangelize!
Congratulations! You're done.
diff --git a/project/TOOLS.md b/project/TOOLS.md
index f057ccd2b..87c6cde8d 100644
--- a/project/TOOLS.md
+++ b/project/TOOLS.md
@@ -5,7 +5,7 @@ the Docker project.
### CI
-The Docker project uses [Jenkins](https://jenkins.dockerproject.com/) as our
+The Docker project uses [Jenkins](https://jenkins.dockerproject.org/) as our
continuous integration server. Each Pull Request to Docker is tested by running the
equivalent of `make all`. We chose Jenkins because we can host it ourselves and
we run Docker in Docker to test.
@@ -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
diff --git a/registry/auth.go b/registry/auth.go
index 4baf114c6..33f8fa068 100644
--- a/registry/auth.go
+++ b/registry/auth.go
@@ -1,46 +1,20 @@
package registry
import (
- "encoding/base64"
"encoding/json"
- "errors"
"fmt"
"io/ioutil"
"net/http"
- "os"
- "path"
"strings"
"sync"
"time"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/utils"
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/docker/cliconfig"
)
-const (
- // Where we store the config file
- CONFIGFILE = ".dockercfg"
-)
-
-var (
- ErrConfigFileMissing = errors.New("The Auth config file is missing")
-)
-
-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"`
-}
-
-type ConfigFile struct {
- Configs map[string]AuthConfig `json:"configs,omitempty"`
- rootPath string
-}
-
type RequestAuthorization struct {
- authConfig *AuthConfig
+ authConfig *cliconfig.AuthConfig
registryEndpoint *Endpoint
resource string
scope string
@@ -51,7 +25,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,
@@ -66,25 +40,22 @@ 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
}
- client := auth.registryEndpoint.HTTPClient()
- factory := HTTPRequestFactory(nil)
-
for _, challenge := range auth.registryEndpoint.AuthChallenges {
switch strings.ToLower(challenge.Scheme) {
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
}
params["scope"] = fmt.Sprintf("%s:%s:%s", auth.resource, auth.scope, strings.Join(auth.actions, ","))
- token, err := getToken(auth.authConfig.Username, auth.authConfig.Password, params, auth.registryEndpoint, client, factory)
+ token, err := getToken(auth.authConfig.Username, auth.authConfig.Password, params, auth.registryEndpoint)
if err != nil {
return "", err
}
@@ -93,7 +64,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)
}
}
@@ -116,136 +87,26 @@ 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(rootPath string) (*ConfigFile, error) {
- configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath}
- confFile := path.Join(rootPath, 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 {
- 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.Configs[IndexServerAddress()] = authConfig
- } else {
- for k, authConfig := range configFile.Configs {
- authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth)
- if err != nil {
- return &configFile, err
- }
- authConfig.Auth = ""
- authConfig.ServerAddress = k
- configFile.Configs[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 {
- authCopy := authConfig
-
- authCopy.Auth = encodeAuth(&authCopy)
- authCopy.Username = ""
- authCopy.Password = ""
- authCopy.ServerAddress = ""
- configs[k] = authCopy
- }
-
- b, err := json.MarshalIndent(configs, "", "\t")
- if err != nil {
- return err
- }
- err = ioutil.WriteFile(confFile, b, 0600)
- if err != nil {
- return err
- }
- return nil
-}
-
// Login tries to register/login to the registry server.
-func Login(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
+func Login(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
// Separates the v2 registry login logic from the v1 logic.
if registryEndpoint.Version == APIVersion2 {
- return loginV2(authConfig, registryEndpoint, factory)
+ return loginV2(authConfig, registryEndpoint)
}
-
- return loginV1(authConfig, registryEndpoint, factory)
+ return loginV1(authConfig, registryEndpoint)
}
// loginV1 tries to register/login to the v1 registry server.
-func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.HTTPRequestFactory) (string, error) {
+func loginV1(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
var (
status string
reqBody []byte
err error
- client = registryEndpoint.HTTPClient()
reqStatusCode = 0
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.")
@@ -264,7 +125,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 := client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
+ req1, err := registryEndpoint.client.Post(serverAddress+"users/", "application/json; charset=utf-8", b)
if err != nil {
return "", fmt.Errorf("Server Error: %s", err)
}
@@ -285,9 +146,9 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
}
} else if reqStatusCode == 400 {
if string(reqBody) == "\"Username or email already exists\"" {
- req, err := factory.NewRequest("GET", serverAddress+"users/", nil)
+ req, err := http.NewRequest("GET", serverAddress+"users/", nil)
req.SetBasicAuth(authConfig.Username, authConfig.Password)
- resp, err := client.Do(req)
+ resp, err := registryEndpoint.client.Do(req)
if err != nil {
return "", err
}
@@ -314,9 +175,9 @@ func loginV1(authConfig *AuthConfig, registryEndpoint *Endpoint, factory *utils.
} else if reqStatusCode == 401 {
// This case would happen with private registries where /v1/users is
// protected, so people can use `docker login` as an auth check.
- req, err := factory.NewRequest("GET", serverAddress+"users/", nil)
+ req, err := http.NewRequest("GET", serverAddress+"users/", nil)
req.SetBasicAuth(authConfig.Username, authConfig.Password)
- resp, err := client.Do(req)
+ resp, err := registryEndpoint.client.Do(req)
if err != nil {
return "", err
}
@@ -348,22 +209,21 @@ 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) {
- log.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
+func loginV2(authConfig *cliconfig.AuthConfig, registryEndpoint *Endpoint) (string, error) {
+ logrus.Debugf("attempting v2 login to registry endpoint %s", registryEndpoint)
var (
err error
allErrors []error
- client = registryEndpoint.HTTPClient()
)
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":
- err = tryV2BasicAuthLogin(authConfig, challenge.Parameters, registryEndpoint, client, factory)
+ err = tryV2BasicAuthLogin(authConfig, challenge.Parameters, registryEndpoint)
case "bearer":
- err = tryV2TokenAuthLogin(authConfig, challenge.Parameters, registryEndpoint, client, factory)
+ err = tryV2TokenAuthLogin(authConfig, challenge.Parameters, registryEndpoint)
default:
// Unsupported challenge types are explicitly skipped.
err = fmt.Errorf("unsupported auth scheme: %q", challenge.Scheme)
@@ -373,7 +233,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)
}
@@ -381,15 +241,15 @@ 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 {
- req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil)
+func tryV2BasicAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
+ req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
if err != nil {
return err
}
req.SetBasicAuth(authConfig.Username, authConfig.Password)
- resp, err := client.Do(req)
+ resp, err := registryEndpoint.client.Do(req)
if err != nil {
return err
}
@@ -402,20 +262,20 @@ 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 {
- token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint, client, factory)
+func tryV2TokenAuthLogin(authConfig *cliconfig.AuthConfig, params map[string]string, registryEndpoint *Endpoint) error {
+ token, err := getToken(authConfig.Username, authConfig.Password, params, registryEndpoint)
if err != nil {
return err
}
- req, err := factory.NewRequest("GET", registryEndpoint.Path(""), nil)
+ req, err := http.NewRequest("GET", registryEndpoint.Path(""), nil)
if err != nil {
return err
}
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
- resp, err := client.Do(req)
+ resp, err := registryEndpoint.client.Do(req)
if err != nil {
return err
}
@@ -429,10 +289,10 @@ 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.Configs[configKey]; found || index.Official {
+ if c, found := config.AuthConfigs[configKey]; found || index.Official {
return c
}
@@ -451,12 +311,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.Configs {
+ 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{}
+ return cliconfig.AuthConfig{}
}
diff --git a/registry/auth_test.go b/registry/auth_test.go
index 9cc299aab..71b963a1f 100644
--- a/registry/auth_test.go
+++ b/registry/auth_test.go
@@ -3,15 +3,18 @@ package registry
import (
"io/ioutil"
"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)
}
@@ -26,18 +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
}
- configFile := &ConfigFile{
- rootPath: root,
- Configs: make(map[string]AuthConfig),
- }
+ root = filepath.Join(root, cliconfig.CONFIGFILE)
+ configFile := cliconfig.NewConfigFile(root)
for _, registry := range []string{"testIndex", IndexServerAddress()} {
- configFile.Configs[registry] = AuthConfig{
+ configFile.AuthConfigs[registry] = cliconfig.AuthConfig{
Username: "docker-user",
Password: "docker-pass",
Email: "docker@docker.io",
@@ -52,14 +53,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 +80,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,
@@ -90,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()")
}
@@ -102,26 +103,26 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
if err != nil {
t.Fatal(err)
}
- defer os.RemoveAll(configFile.rootPath)
+ 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.Configs[IndexServerAddress()] = officialAuth
+ configFile.AuthConfigs[IndexServerAddress()] = officialAuth
- expectedAuths := map[string]AuthConfig{
+ expectedAuths := map[string]cliconfig.AuthConfig{
"registry.example.com": registryAuth,
"localhost:8000": localAuth,
"registry.com": localAuth,
@@ -157,13 +158,13 @@ func TestResolveAuthConfigFullURL(t *testing.T) {
Name: configKey,
}
for _, registry := range registries {
- configFile.Configs[registry] = configured
- resolved := configFile.ResolveAuthConfig(index)
+ configFile.AuthConfigs[registry] = configured
+ resolved := ResolveAuthConfig(configFile, index)
if resolved.Email != configured.Email {
t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email)
}
- delete(configFile.Configs, registry)
- resolved = configFile.ResolveAuthConfig(index)
+ delete(configFile.AuthConfigs, registry)
+ resolved = ResolveAuthConfig(configFile, index)
if resolved.Email == configured.Email {
t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email)
}
diff --git a/registry/config.go b/registry/config.go
index a706f17e6..92ef4d997 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.
@@ -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)
}
}
@@ -189,7 +189,7 @@ func ValidateMirror(val string) (string, error) {
return "", fmt.Errorf("Unsupported path/query/fragment at end of the URI")
}
- return fmt.Sprintf("%s://%s/v1/", uri.Scheme, uri.Host), nil
+ return fmt.Sprintf("%s://%s/", uri.Scheme, uri.Host), nil
}
// ValidateIndexName validates an index name.
@@ -198,6 +198,9 @@ func ValidateIndexName(val string) (string, error) {
if val == "index."+IndexServerName() {
val = IndexServerName()
}
+ if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") {
+ return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val)
+ }
// *TODO: Check if valid hostname[:port]/ip[:port]?
return val, nil
}
@@ -213,7 +216,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 {
@@ -235,6 +238,9 @@ func validateRemoteName(remoteName string) error {
if !validRepo.MatchString(name) {
return fmt.Errorf("Invalid repository name (%s), only [a-z0-9-_.] are allowed", name)
}
+ if strings.HasPrefix(name, "-") || strings.HasSuffix(name, "-") {
+ return fmt.Errorf("Invalid repository name (%s). Cannot begin or end with a hyphen.", name)
+ }
return nil
}
@@ -352,7 +358,9 @@ func (config *ServiceConfig) NewRepositoryInfo(reposName string) (*RepositoryInf
// *TODO: Decouple index name from hostname (via registry configuration?)
repoInfo.LocalName = repoInfo.Index.Name + "/" + repoInfo.RemoteName
repoInfo.CanonicalName = repoInfo.LocalName
+
}
+
return repoInfo, nil
}
diff --git a/registry/endpoint.go b/registry/endpoint.go
index 59ae4dd54..ce92668f4 100644
--- a/registry/endpoint.go
+++ b/registry/endpoint.go
@@ -1,7 +1,6 @@
package registry
import (
- "crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
@@ -10,9 +9,9 @@ import (
"net/url"
"strings"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/registry/v2"
- "github.com/docker/docker/utils"
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/distribution/registry/api/v2"
+ "github.com/docker/docker/pkg/transport"
)
// for mocking in unit tests
@@ -43,9 +42,9 @@ func scanForAPIVersion(address string) (string, APIVersion) {
}
// NewEndpoint parses the given address to return a registry endpoint.
-func NewEndpoint(index *IndexInfo) (*Endpoint, error) {
+func NewEndpoint(index *IndexInfo, metaHeaders http.Header) (*Endpoint, error) {
// *TODO: Allow per-registry configuration of endpoints.
- endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure)
+ endpoint, err := newEndpoint(index.GetAuthConfigKey(), index.Secure, metaHeaders)
if err != nil {
return nil, err
}
@@ -57,7 +56,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 +68,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
@@ -83,7 +82,7 @@ func validateEndpoint(endpoint *Endpoint) error {
return nil
}
-func newEndpoint(address string, secure bool) (*Endpoint, error) {
+func newEndpoint(address string, secure bool, metaHeaders http.Header) (*Endpoint, error) {
var (
endpoint = new(Endpoint)
trimmedAddress string
@@ -100,15 +99,18 @@ func newEndpoint(address string, secure bool) (*Endpoint, error) {
return nil, err
}
endpoint.IsSecure = secure
+ tr := NewTransport(ConnectTimeout, endpoint.IsSecure)
+ endpoint.client = HTTPClient(transport.NewTransport(tr, DockerHeaders(metaHeaders)...))
return endpoint, nil
}
-func (repoInfo *RepositoryInfo) GetEndpoint() (*Endpoint, error) {
- return NewEndpoint(repoInfo.Index)
+func (repoInfo *RepositoryInfo) GetEndpoint(metaHeaders http.Header) (*Endpoint, error) {
+ return NewEndpoint(repoInfo.Index, metaHeaders)
}
// Endpoint stores basic information about a registry endpoint.
type Endpoint struct {
+ client *http.Client
URL *url.URL
Version APIVersion
IsSecure bool
@@ -135,25 +137,24 @@ func (e *Endpoint) Path(path string) string {
func (e *Endpoint) Ping() (RegistryInfo, error) {
// The ping logic to use is determined by the registry endpoint version.
- factory := HTTPRequestFactory(nil)
switch e.Version {
case APIVersion1:
- return e.pingV1(factory)
+ return e.pingV1()
case APIVersion2:
- return e.pingV2(factory)
+ return e.pingV2()
}
// APIVersionUnknown
// We should try v2 first...
e.Version = APIVersion2
- regInfo, errV2 := e.pingV2(factory)
+ regInfo, errV2 := e.pingV2()
if errV2 == nil {
return regInfo, nil
}
// ... then fallback to v1.
e.Version = APIVersion1
- regInfo, errV1 := e.pingV1(factory)
+ regInfo, errV1 := e.pingV1()
if errV1 == nil {
return regInfo, nil
}
@@ -162,8 +163,8 @@ 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) {
- log.Debugf("attempting v1 ping for registry endpoint %s", e)
+func (e *Endpoint) pingV1() (RegistryInfo, error) {
+ logrus.Debugf("attempting v1 ping for registry endpoint %s", e)
if e.String() == IndexServerAddress() {
// Skip the check, we know this one is valid
@@ -171,12 +172,12 @@ func (e *Endpoint) pingV1(factory *utils.HTTPRequestFactory) (RegistryInfo, erro
return RegistryInfo{Standalone: false}, nil
}
- req, err := factory.NewRequest("GET", e.Path("_ping"), nil)
+ req, err := http.NewRequest("GET", e.Path("_ping"), nil)
if err != nil {
return RegistryInfo{Standalone: false}, err
}
- resp, _, err := doRequest(req, nil, ConnectTimeout, e.IsSecure)
+ resp, err := e.client.Do(req)
if err != nil {
return RegistryInfo{Standalone: false}, err
}
@@ -194,17 +195,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,19 +213,19 @@ 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)
+func (e *Endpoint) pingV2() (RegistryInfo, error) {
+ logrus.Debugf("attempting v2 ping for registry endpoint %s", e)
- req, err := factory.NewRequest("GET", e.Path(""), nil)
+ req, err := http.NewRequest("GET", e.Path(""), nil)
if err != nil {
return RegistryInfo{}, err
}
- resp, _, err := doRequest(req, nil, ConnectTimeout, e.IsSecure)
+ resp, err := e.client.Do(req)
if err != nil {
return RegistryInfo{}, err
}
@@ -263,20 +264,3 @@ 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/endpoint_test.go b/registry/endpoint_test.go
index 9567ba235..6f67867bb 100644
--- a/registry/endpoint_test.go
+++ b/registry/endpoint_test.go
@@ -19,7 +19,7 @@ func TestEndpointParse(t *testing.T) {
{"0.0.0.0:5000", "https://0.0.0.0:5000/v0/"},
}
for _, td := range testData {
- e, err := newEndpoint(td.str, false)
+ e, err := newEndpoint(td.str, false, nil)
if err != nil {
t.Errorf("%q: %s", td.str, err)
}
@@ -60,6 +60,7 @@ func TestValidateEndpointAmbiguousAPIVersion(t *testing.T) {
testEndpoint := Endpoint{
URL: testServerURL,
Version: APIVersionUnknown,
+ client: HTTPClient(NewTransport(ConnectTimeout, false)),
}
if err = validateEndpoint(&testEndpoint); err != nil {
diff --git a/registry/httpfactory.go b/registry/httpfactory.go
deleted file mode 100644
index a4fea3822..000000000
--- a/registry/httpfactory.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package registry
-
-import (
- "runtime"
-
- "github.com/docker/docker/autogen/dockerversion"
- "github.com/docker/docker/pkg/parsers/kernel"
- "github.com/docker/docker/utils"
-)
-
-func HTTPRequestFactory(metaHeaders map[string][]string) *utils.HTTPRequestFactory {
- // 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})
- if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
- httpVersion = append(httpVersion, &simpleVersionInfo{"kernel", kernelVersion.String()})
- }
- httpVersion = append(httpVersion, &simpleVersionInfo{"os", runtime.GOOS})
- httpVersion = append(httpVersion, &simpleVersionInfo{"arch", runtime.GOARCH})
- ud := utils.NewHTTPUserAgentDecorator(httpVersion...)
- md := &utils.HTTPMetaHeadersDecorator{
- Headers: metaHeaders,
- }
- factory := utils.NewHTTPRequestFactory(ud, md)
- 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.go b/registry/registry.go
index a8bb83318..47bd2553f 100644
--- a/registry/registry.go
+++ b/registry/registry.go
@@ -8,13 +8,21 @@ import (
"io/ioutil"
"net"
"net/http"
+ "net/http/httputil"
"os"
"path"
+ "path/filepath"
+ "runtime"
"strings"
+ "sync"
"time"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/docker/autogen/dockerversion"
+ "github.com/docker/docker/pkg/parsers/kernel"
"github.com/docker/docker/pkg/timeoutconn"
+ "github.com/docker/docker/pkg/transport"
+ "github.com/docker/docker/pkg/useragent"
)
var (
@@ -31,29 +39,123 @@ const (
ConnectTimeout
)
-func newClient(jar http.CookieJar, roots *x509.CertPool, certs []tls.Certificate, timeout TimeoutType, secure bool) *http.Client {
- tlsConfig := tls.Config{
- RootCAs: roots,
+// dockerUserAgent is the User-Agent the Docker client uses to identify itself.
+// It is populated on init(), comprising version information of different components.
+var dockerUserAgent string
+
+func init() {
+ httpVersion := make([]useragent.VersionInfo, 0, 6)
+ httpVersion = append(httpVersion, useragent.VersionInfo{"docker", dockerversion.VERSION})
+ httpVersion = append(httpVersion, useragent.VersionInfo{"go", runtime.Version()})
+ httpVersion = append(httpVersion, useragent.VersionInfo{"git-commit", dockerversion.GITCOMMIT})
+ if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
+ httpVersion = append(httpVersion, useragent.VersionInfo{"kernel", kernelVersion.String()})
+ }
+ httpVersion = append(httpVersion, useragent.VersionInfo{"os", runtime.GOOS})
+ httpVersion = append(httpVersion, useragent.VersionInfo{"arch", runtime.GOARCH})
+
+ dockerUserAgent = useragent.AppendVersions("", httpVersion...)
+}
+
+type httpsRequestModifier struct {
+ mu sync.Mutex
+ tlsConfig *tls.Config
+}
+
+// DRAGONS(tiborvass): If someone wonders why do we set tlsconfig in a roundtrip,
+// it's because it's so as to match the current behavior in master: we generate the
+// certpool on every-goddam-request. It's not great, but it allows people to just put
+// the certs in /etc/docker/certs.d/.../ and let docker "pick it up" immediately. Would
+// prefer an fsnotify implementation, but that was out of scope of my refactoring.
+func (m *httpsRequestModifier) ModifyRequest(req *http.Request) error {
+ var (
+ roots *x509.CertPool
+ certs []tls.Certificate
+ hostDir string
+ )
+
+ if req.URL.Scheme == "https" {
+ hasFile := func(files []os.FileInfo, name string) bool {
+ for _, f := range files {
+ if f.Name() == name {
+ return true
+ }
+ }
+ return false
+ }
+
+ if runtime.GOOS == "windows" {
+ hostDir = path.Join(os.TempDir(), "/docker/certs.d", req.URL.Host)
+ } else {
+ hostDir = path.Join("/etc/docker/certs.d", req.URL.Host)
+ }
+ logrus.Debugf("hostDir: %s", hostDir)
+ fs, err := ioutil.ReadDir(hostDir)
+ if err != nil && !os.IsNotExist(err) {
+ return nil
+ }
+
+ for _, f := range fs {
+ if strings.HasSuffix(f.Name(), ".crt") {
+ if roots == nil {
+ roots = x509.NewCertPool()
+ }
+ logrus.Debugf("crt: %s", hostDir+"/"+f.Name())
+ data, err := ioutil.ReadFile(filepath.Join(hostDir, f.Name()))
+ if err != nil {
+ return err
+ }
+ roots.AppendCertsFromPEM(data)
+ }
+ if strings.HasSuffix(f.Name(), ".cert") {
+ certName := f.Name()
+ keyName := certName[:len(certName)-5] + ".key"
+ logrus.Debugf("cert: %s", hostDir+"/"+f.Name())
+ if !hasFile(fs, keyName) {
+ return fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
+ }
+ cert, err := tls.LoadX509KeyPair(filepath.Join(hostDir, certName), path.Join(hostDir, keyName))
+ if err != nil {
+ return err
+ }
+ certs = append(certs, cert)
+ }
+ if strings.HasSuffix(f.Name(), ".key") {
+ keyName := f.Name()
+ certName := keyName[:len(keyName)-4] + ".cert"
+ logrus.Debugf("key: %s", hostDir+"/"+f.Name())
+ if !hasFile(fs, certName) {
+ return fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
+ }
+ }
+ }
+ m.mu.Lock()
+ m.tlsConfig.RootCAs = roots
+ m.tlsConfig.Certificates = certs
+ m.mu.Unlock()
+ }
+ return nil
+}
+
+func NewTransport(timeout TimeoutType, secure bool) http.RoundTripper {
+ tlsConfig := &tls.Config{
// Avoid fallback to SSL protocols < TLS1.0
- MinVersion: tls.VersionTLS10,
- Certificates: certs,
+ MinVersion: tls.VersionTLS10,
+ InsecureSkipVerify: !secure,
}
- if !secure {
- tlsConfig.InsecureSkipVerify = true
- }
-
- httpTransport := &http.Transport{
+ tr := &http.Transport{
DisableKeepAlives: true,
Proxy: http.ProxyFromEnvironment,
- TLSClientConfig: &tlsConfig,
+ TLSClientConfig: tlsConfig,
}
switch timeout {
case ConnectTimeout:
- httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
- // Set the connect timeout to 5 seconds
- d := net.Dialer{Timeout: 5 * time.Second, DualStack: true}
+ tr.Dial = func(proto string, addr string) (net.Conn, error) {
+ // Set the connect timeout to 30 seconds to allow for slower connection
+ // times...
+ d := net.Dialer{Timeout: 30 * time.Second, DualStack: true}
conn, err := d.Dial(proto, addr)
if err != nil {
@@ -64,7 +166,7 @@ func newClient(jar http.CookieJar, roots *x509.CertPool, certs []tls.Certificate
return conn, nil
}
case ReceiveTimeout:
- httpTransport.Dial = func(proto string, addr string) (net.Conn, error) {
+ tr.Dial = func(proto string, addr string) (net.Conn, error) {
d := net.Dialer{DualStack: true}
conn, err := d.Dial(proto, addr)
@@ -76,84 +178,57 @@ func newClient(jar http.CookieJar, roots *x509.CertPool, certs []tls.Certificate
}
}
- return &http.Client{
- Transport: httpTransport,
- CheckRedirect: AddRequiredHeadersToRedirectedRequests,
- Jar: jar,
+ if secure {
+ // note: httpsTransport also handles http transport
+ // but for HTTPS, it sets up the certs
+ return transport.NewTransport(tr, &httpsRequestModifier{tlsConfig: tlsConfig})
}
+
+ return tr
}
-func doRequest(req *http.Request, jar http.CookieJar, timeout TimeoutType, secure bool) (*http.Response, *http.Client, error) {
- var (
- pool *x509.CertPool
- certs []tls.Certificate
- )
+// DockerHeaders returns request modifiers that ensure requests have
+// the User-Agent header set to dockerUserAgent and that metaHeaders
+// are added.
+func DockerHeaders(metaHeaders http.Header) []transport.RequestModifier {
+ modifiers := []transport.RequestModifier{
+ transport.NewHeaderRequestModifier(http.Header{"User-Agent": []string{dockerUserAgent}}),
+ }
+ if metaHeaders != nil {
+ modifiers = append(modifiers, transport.NewHeaderRequestModifier(metaHeaders))
+ }
+ return modifiers
+}
- if secure && req.URL.Scheme == "https" {
- hasFile := func(files []os.FileInfo, name string) bool {
- for _, f := range files {
- if f.Name() == name {
- return true
- }
- }
- return false
- }
+type debugTransport struct{ http.RoundTripper }
- hostDir := path.Join("/etc/docker/certs.d", req.URL.Host)
- log.Debugf("hostDir: %s", hostDir)
- fs, err := ioutil.ReadDir(hostDir)
- if err != nil && !os.IsNotExist(err) {
- return nil, nil, err
- }
+func (tr debugTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+ dump, err := httputil.DumpRequestOut(req, false)
+ if err != nil {
+ fmt.Println("could not dump request")
+ }
+ fmt.Println(string(dump))
+ resp, err := tr.RoundTripper.RoundTrip(req)
+ if err != nil {
+ return nil, err
+ }
+ dump, err = httputil.DumpResponse(resp, false)
+ if err != nil {
+ fmt.Println("could not dump response")
+ }
+ fmt.Println(string(dump))
+ return resp, err
+}
- for _, f := range fs {
- if strings.HasSuffix(f.Name(), ".crt") {
- if pool == nil {
- pool = x509.NewCertPool()
- }
- log.Debugf("crt: %s", hostDir+"/"+f.Name())
- data, err := ioutil.ReadFile(path.Join(hostDir, f.Name()))
- if err != nil {
- return nil, nil, err
- }
- pool.AppendCertsFromPEM(data)
- }
- if strings.HasSuffix(f.Name(), ".cert") {
- certName := f.Name()
- keyName := certName[:len(certName)-5] + ".key"
- log.Debugf("cert: %s", hostDir+"/"+f.Name())
- if !hasFile(fs, keyName) {
- return nil, nil, fmt.Errorf("Missing key %s for certificate %s", keyName, certName)
- }
- cert, err := tls.LoadX509KeyPair(path.Join(hostDir, certName), path.Join(hostDir, keyName))
- if err != nil {
- return nil, nil, err
- }
- certs = append(certs, cert)
- }
- if strings.HasSuffix(f.Name(), ".key") {
- keyName := f.Name()
- certName := keyName[:len(keyName)-4] + ".cert"
- log.Debugf("key: %s", hostDir+"/"+f.Name())
- if !hasFile(fs, certName) {
- return nil, nil, fmt.Errorf("Missing certificate %s for key %s", certName, keyName)
- }
- }
- }
+func HTTPClient(transport http.RoundTripper) *http.Client {
+ if transport == nil {
+ transport = NewTransport(ConnectTimeout, true)
}
- if len(certs) == 0 {
- client := newClient(jar, pool, nil, timeout, secure)
- res, err := client.Do(req)
- if err != nil {
- return nil, nil, err
- }
- return res, client, nil
+ return &http.Client{
+ Transport: transport,
+ CheckRedirect: AddRequiredHeadersToRedirectedRequests,
}
-
- client := newClient(jar, pool, certs, timeout, secure)
- res, err := client.Do(req)
- return res, client, err
}
func trustedLocation(req *http.Request) bool {
diff --git a/registry/registry_mock_test.go b/registry/registry_mock_test.go
index 57233d7c7..d58904d03 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)
@@ -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)
}
}
@@ -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/registry_test.go b/registry/registry_test.go
index d96630d90..33e86ff43 100644
--- a/registry/registry_test.go
+++ b/registry/registry_test.go
@@ -7,7 +7,8 @@ import (
"strings"
"testing"
- "github.com/docker/docker/utils"
+ "github.com/docker/docker/cliconfig"
+ "github.com/docker/docker/pkg/transport"
)
var (
@@ -20,46 +21,35 @@ const (
)
func spawnTestRegistrySession(t *testing.T) *Session {
- authConfig := &AuthConfig{}
- endpoint, err := NewEndpoint(makeIndex("/v1/"))
+ authConfig := &cliconfig.AuthConfig{}
+ endpoint, err := NewEndpoint(makeIndex("/v1/"), nil)
if err != nil {
t.Fatal(err)
}
- r, err := NewSession(authConfig, utils.NewHTTPRequestFactory(), endpoint, true)
+ var tr http.RoundTripper = debugTransport{NewTransport(ReceiveTimeout, endpoint.IsSecure)}
+ tr = transport.NewTransport(AuthTransport(tr, authConfig, false), DockerHeaders(nil)...)
+ client := HTTPClient(tr)
+ r, err := NewSession(client, authConfig, endpoint)
if err != nil {
t.Fatal(err)
}
+ // In a normal scenario for the v1 registry, the client should send a `X-Docker-Token: true`
+ // header while authenticating, in order to retrieve a token that can be later used to
+ // perform authenticated actions.
+ //
+ // The mock v1 registry does not support that, (TODO(tiborvass): support it), instead,
+ // it will consider authenticated any request with the header `X-Docker-Token: fake-token`.
+ //
+ // Because we know that the client's transport is an `*authTransport` we simply cast it,
+ // in order to set the internal cached token to the fake token, and thus send that fake token
+ // upon every subsequent requests.
+ r.client.Transport.(*authTransport).token = token
return r
}
-func TestPublicSession(t *testing.T) {
- authConfig := &AuthConfig{}
-
- getSessionDecorators := func(index *IndexInfo) int {
- endpoint, err := NewEndpoint(index)
- if err != nil {
- t.Fatal(err)
- }
- r, err := NewSession(authConfig, utils.NewHTTPRequestFactory(), endpoint, true)
- if err != nil {
- t.Fatal(err)
- }
- return len(r.reqFactory.GetDecorators())
- }
-
- decorators := getSessionDecorators(makeIndex("/v1/"))
- assertEqual(t, decorators, 0, "Expected no decorator on http session")
-
- decorators = getSessionDecorators(makeHttpsIndex("/v1/"))
- assertNotEqual(t, decorators, 0, "Expected decorator on https session")
-
- decorators = getSessionDecorators(makePublicIndex())
- assertEqual(t, decorators, 0, "Expected no decorator on public session")
-}
-
func TestPingRegistryEndpoint(t *testing.T) {
testPing := func(index *IndexInfo, expectedStandalone bool, assertMessage string) {
- ep, err := NewEndpoint(index)
+ ep, err := NewEndpoint(index, nil)
if err != nil {
t.Fatal(err)
}
@@ -79,7 +69,7 @@ func TestPingRegistryEndpoint(t *testing.T) {
func TestEndpoint(t *testing.T) {
// Simple wrapper to fail test if err != nil
expandEndpoint := func(index *IndexInfo) *Endpoint {
- endpoint, err := NewEndpoint(index)
+ endpoint, err := NewEndpoint(index, nil)
if err != nil {
t.Fatal(err)
}
@@ -88,7 +78,7 @@ func TestEndpoint(t *testing.T) {
assertInsecureIndex := func(index *IndexInfo) {
index.Secure = true
- _, err := NewEndpoint(index)
+ _, err := NewEndpoint(index, nil)
assertNotEqual(t, err, nil, index.Name+": Expected error for insecure index")
assertEqual(t, strings.Contains(err.Error(), "insecure-registry"), true, index.Name+": Expected insecure-registry error for insecure index")
index.Secure = false
@@ -96,7 +86,7 @@ func TestEndpoint(t *testing.T) {
assertSecureIndex := func(index *IndexInfo) {
index.Secure = true
- _, err := NewEndpoint(index)
+ _, err := NewEndpoint(index, nil)
assertNotEqual(t, err, nil, index.Name+": Expected cert error for secure index")
assertEqual(t, strings.Contains(err.Error(), "certificate signed by unknown authority"), true, index.Name+": Expected cert error for secure index")
index.Secure = false
@@ -162,14 +152,14 @@ func TestEndpoint(t *testing.T) {
}
for _, address := range badEndpoints {
index.Name = address
- _, err := NewEndpoint(index)
+ _, err := NewEndpoint(index, nil)
checkNotEqual(t, err, nil, "Expected error while expanding bad endpoint")
}
}
func TestGetRemoteHistory(t *testing.T) {
r := spawnTestRegistrySession(t)
- hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/"), token)
+ hist, err := r.GetRemoteHistory(imageID, makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
@@ -181,16 +171,16 @@ func TestGetRemoteHistory(t *testing.T) {
func TestLookupRemoteImage(t *testing.T) {
r := spawnTestRegistrySession(t)
- err := r.LookupRemoteImage(imageID, makeURL("/v1/"), token)
+ err := r.LookupRemoteImage(imageID, makeURL("/v1/"))
assertEqual(t, err, nil, "Expected error of remote lookup to nil")
- if err := r.LookupRemoteImage("abcdef", makeURL("/v1/"), token); err == nil {
+ if err := r.LookupRemoteImage("abcdef", makeURL("/v1/")); err == nil {
t.Fatal("Expected error of remote lookup to not nil")
}
}
func TestGetRemoteImageJSON(t *testing.T) {
r := spawnTestRegistrySession(t)
- json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/"), token)
+ json, size, err := r.GetRemoteImageJSON(imageID, makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
@@ -199,7 +189,7 @@ func TestGetRemoteImageJSON(t *testing.T) {
t.Fatal("Expected non-empty json")
}
- _, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/"), token)
+ _, _, err = r.GetRemoteImageJSON("abcdef", makeURL("/v1/"))
if err == nil {
t.Fatal("Expected image not found error")
}
@@ -207,7 +197,7 @@ func TestGetRemoteImageJSON(t *testing.T) {
func TestGetRemoteImageLayer(t *testing.T) {
r := spawnTestRegistrySession(t)
- data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), token, 0)
+ data, err := r.GetRemoteImageLayer(imageID, makeURL("/v1/"), 0)
if err != nil {
t.Fatal(err)
}
@@ -215,7 +205,7 @@ func TestGetRemoteImageLayer(t *testing.T) {
t.Fatal("Expected non-nil data result")
}
- _, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), token, 0)
+ _, err = r.GetRemoteImageLayer("abcdef", makeURL("/v1/"), 0)
if err == nil {
t.Fatal("Expected image not found error")
}
@@ -223,14 +213,14 @@ func TestGetRemoteImageLayer(t *testing.T) {
func TestGetRemoteTags(t *testing.T) {
r := spawnTestRegistrySession(t)
- tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO, token)
+ tags, err := r.GetRemoteTags([]string{makeURL("/v1/")}, REPO)
if err != nil {
t.Fatal(err)
}
assertEqual(t, len(tags), 1, "Expected one tag")
assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID)
- _, err = r.GetRemoteTags([]string{makeURL("/v1/")}, "foo42/baz", token)
+ _, err = r.GetRemoteTags([]string{makeURL("/v1/")}, "foo42/baz")
if err == nil {
t.Fatal("Expected error when fetching tags for bogus repo")
}
@@ -264,7 +254,7 @@ func TestPushImageJSONRegistry(t *testing.T) {
Checksum: "sha256:1ac330d56e05eef6d438586545ceff7550d3bdcb6b19961f12c5ba714ee1bb37",
}
- err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/"), token)
+ err := r.PushImageJSONRegistry(imgData, []byte{0x42, 0xdf, 0x0}, makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
@@ -273,7 +263,7 @@ func TestPushImageJSONRegistry(t *testing.T) {
func TestPushImageLayerRegistry(t *testing.T) {
r := spawnTestRegistrySession(t)
layer := strings.NewReader("")
- _, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), token, []byte{})
+ _, _, err := r.PushImageLayerRegistry(imageID, layer, makeURL("/v1/"), []byte{})
if err != nil {
t.Fatal(err)
}
@@ -298,6 +288,9 @@ func TestValidateRepositoryName(t *testing.T) {
invalidRepoNames := []string{
"https://github.com/docker/docker",
"docker/Docker",
+ "-docker",
+ "-docker/docker",
+ "-docker.io/docker/docker",
"docker///docker",
"docker.io/docker/Docker",
"docker.io/docker///docker",
@@ -690,7 +683,7 @@ func TestNewIndexInfo(t *testing.T) {
func TestPushRegistryTag(t *testing.T) {
r := spawnTestRegistrySession(t)
- err := r.PushRegistryTag("foo42/bar", imageID, "stable", makeURL("/v1/"), token)
+ err := r.PushRegistryTag("foo42/bar", imageID, "stable", makeURL("/v1/"))
if err != nil {
t.Fatal(err)
}
@@ -735,7 +728,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/registry/service.go b/registry/service.go
index 048340224..681174927 100644
--- a/registry/service.go
+++ b/registry/service.go
@@ -1,18 +1,11 @@
package registry
import (
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
+ "net/http"
+
+ "github.com/docker/docker/cliconfig"
)
-// 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
}
@@ -25,201 +18,54 @@ 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) engine.Status {
- var (
- authConfig = new(AuthConfig)
- endpoint *Endpoint
- index *IndexInfo
- status string
- err error
- )
-
- job.GetenvJson("authConfig", authConfig)
-
+func (s *Service) Auth(authConfig *cliconfig.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 job.Error(err)
+ index, err := s.ResolveIndex(addr)
+ if err != nil {
+ return "", err
}
-
- if endpoint, err = NewEndpoint(index); err != nil {
- log.Errorf("unable to get new registry endpoint: %s", err)
- return job.Error(err)
+ endpoint, err := NewEndpoint(index, nil)
+ if err != nil {
+ 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)
- }
-
- log.Infof("successful registry login for endpoint %s: %s", endpoint, status)
- job.Printf("%s\n", status)
-
- return engine.StatusOK
+ return Login(authConfig, endpoint)
}
// 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) engine.Status {
- if n := len(job.Args); n != 1 {
- return job.Errorf("Usage: %s TERM", job.Name)
+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
}
- var (
- term = job.Args[0]
- metaHeaders = map[string][]string{}
- authConfig = &AuthConfig{}
- )
- job.GetenvJson("authConfig", authConfig)
- job.GetenvJson("metaHeaders", metaHeaders)
- repoInfo, err := ResolveRepositoryInfo(job, term)
- if err != nil {
- return job.Error(err)
- }
// *TODO: Search multiple indexes.
- endpoint, err := repoInfo.GetEndpoint()
+ endpoint, err := repoInfo.GetEndpoint(http.Header(headers))
if err != nil {
- return job.Error(err)
+ return nil, err
}
- r, err := NewSession(authConfig, HTTPRequestFactory(metaHeaders), endpoint, true)
+ r, err := NewSession(endpoint.client, authConfig, endpoint)
if err != nil {
- return job.Error(err)
+ return nil, err
}
- results, err := r.SearchRepositories(repoInfo.GetSearchTerm())
- if err != nil {
- return job.Error(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 job.Error(err)
- }
- return engine.StatusOK
+ 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) engine.Status {
- var (
- reposName = job.Args[0]
- )
-
- repoInfo, err := s.Config.NewRepositoryInfo(reposName)
- if err != nil {
- return job.Error(err)
- }
-
- out := engine.Env{}
- err = out.SetJson("repository", repoInfo)
- if err != nil {
- return job.Error(err)
- }
- out.WriteTo(job.Stdout)
-
- return engine.StatusOK
-}
-
-// 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) engine.Status {
- var (
- indexName = job.Args[0]
- )
-
- index, err := s.Config.NewIndexInfo(indexName)
- if err != nil {
- return job.Error(err)
- }
-
- out := engine.Env{}
- err = out.SetJson("index", index)
- if err != nil {
- return job.Error(err)
- }
- out.WriteTo(job.Stdout)
-
- return engine.StatusOK
-}
-
-// 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) engine.Status {
- out := engine.Env{}
- err := out.SetJson("config", s.Config)
- if err != nil {
- return job.Error(err)
- }
- out.WriteTo(job.Stdout)
-
- return engine.StatusOK
+func (s *Service) ResolveIndex(name string) (*IndexInfo, error) {
+ return s.Config.NewIndexInfo(name)
}
diff --git a/registry/session.go b/registry/session.go
index bf04b586d..ca1f8e495 100644
--- a/registry/session.go
+++ b/registry/session.go
@@ -3,6 +3,8 @@ package registry
import (
"bytes"
"crypto/sha256"
+ "errors"
+ "sync"
// this is required for some certificates
_ "crypto/sha512"
"encoding/hex"
@@ -17,66 +19,158 @@ import (
"strings"
"time"
- log "github.com/Sirupsen/logrus"
+ "github.com/Sirupsen/logrus"
+ "github.com/docker/docker/cliconfig"
"github.com/docker/docker/pkg/httputils"
"github.com/docker/docker/pkg/tarsum"
- "github.com/docker/docker/utils"
+ "github.com/docker/docker/pkg/transport"
)
type Session struct {
- authConfig *AuthConfig
- reqFactory *utils.HTTPRequestFactory
indexEndpoint *Endpoint
- jar *cookiejar.Jar
- timeout TimeoutType
+ client *http.Client
+ // TODO(tiborvass): remove authConfig
+ authConfig *cliconfig.AuthConfig
}
-func NewSession(authConfig *AuthConfig, factory *utils.HTTPRequestFactory, endpoint *Endpoint, timeout bool) (r *Session, err error) {
+type authTransport struct {
+ http.RoundTripper
+ *cliconfig.AuthConfig
+
+ alwaysSetBasicAuth bool
+ token []string
+
+ mu sync.Mutex // guards modReq
+ modReq map[*http.Request]*http.Request // original -> modified
+}
+
+// AuthTransport handles the auth layer when communicating with a v1 registry (private or official)
+//
+// For private v1 registries, set alwaysSetBasicAuth to true.
+//
+// For the official v1 registry, if there isn't already an Authorization header in the request,
+// but there is an X-Docker-Token header set to true, then Basic Auth will be used to set the Authorization header.
+// After sending the request with the provided base http.RoundTripper, if an X-Docker-Token header, representing
+// a token, is present in the response, then it gets cached and sent in the Authorization header of all subsequent
+// requests.
+//
+// If the server sends a token without the client having requested it, it is ignored.
+//
+// This RoundTripper also has a CancelRequest method important for correct timeout handling.
+func AuthTransport(base http.RoundTripper, authConfig *cliconfig.AuthConfig, alwaysSetBasicAuth bool) http.RoundTripper {
+ if base == nil {
+ base = http.DefaultTransport
+ }
+ return &authTransport{
+ RoundTripper: base,
+ AuthConfig: authConfig,
+ alwaysSetBasicAuth: alwaysSetBasicAuth,
+ modReq: make(map[*http.Request]*http.Request),
+ }
+}
+
+func (tr *authTransport) RoundTrip(orig *http.Request) (*http.Response, error) {
+ // Authorization should not be set on 302 redirect for untrusted locations.
+ // This logic mirrors the behavior in AddRequiredHeadersToRedirectedRequests.
+ // As the authorization logic is currently implemented in RoundTrip,
+ // a 302 redirect is detected by looking at the Referer header as go http package adds said header.
+ // This is safe as Docker doesn't set Referer in other scenarios.
+ if orig.Header.Get("Referer") != "" && !trustedLocation(orig) {
+ return tr.RoundTripper.RoundTrip(orig)
+ }
+
+ req := transport.CloneRequest(orig)
+ tr.mu.Lock()
+ tr.modReq[orig] = req
+ tr.mu.Unlock()
+
+ if tr.alwaysSetBasicAuth {
+ req.SetBasicAuth(tr.Username, tr.Password)
+ return tr.RoundTripper.RoundTrip(req)
+ }
+
+ // Don't override
+ if req.Header.Get("Authorization") == "" {
+ if req.Header.Get("X-Docker-Token") == "true" && len(tr.Username) > 0 {
+ req.SetBasicAuth(tr.Username, tr.Password)
+ } else if len(tr.token) > 0 {
+ req.Header.Set("Authorization", "Token "+strings.Join(tr.token, ","))
+ }
+ }
+ resp, err := tr.RoundTripper.RoundTrip(req)
+ if err != nil {
+ delete(tr.modReq, orig)
+ return nil, err
+ }
+ if len(resp.Header["X-Docker-Token"]) > 0 {
+ tr.token = resp.Header["X-Docker-Token"]
+ }
+ resp.Body = &transport.OnEOFReader{
+ Rc: resp.Body,
+ Fn: func() {
+ tr.mu.Lock()
+ delete(tr.modReq, orig)
+ tr.mu.Unlock()
+ },
+ }
+ return resp, nil
+}
+
+// CancelRequest cancels an in-flight request by closing its connection.
+func (tr *authTransport) CancelRequest(req *http.Request) {
+ type canceler interface {
+ CancelRequest(*http.Request)
+ }
+ if cr, ok := tr.RoundTripper.(canceler); ok {
+ tr.mu.Lock()
+ modReq := tr.modReq[req]
+ delete(tr.modReq, req)
+ tr.mu.Unlock()
+ cr.CancelRequest(modReq)
+ }
+}
+
+// TODO(tiborvass): remove authConfig param once registry client v2 is vendored
+func NewSession(client *http.Client, authConfig *cliconfig.AuthConfig, endpoint *Endpoint) (r *Session, err error) {
r = &Session{
authConfig: authConfig,
+ client: client,
indexEndpoint: endpoint,
}
- if timeout {
- r.timeout = ReceiveTimeout
- }
-
- r.jar, err = cookiejar.New(nil)
- if err != nil {
- return nil, err
- }
+ var alwaysSetBasicAuth bool
// If we're working with a standalone private registry over HTTPS, send Basic Auth headers
- // alongside our requests.
- if r.indexEndpoint.VersionString(1) != IndexServerAddress() && r.indexEndpoint.URL.Scheme == "https" {
- info, err := r.indexEndpoint.Ping()
+ // alongside all our requests.
+ if endpoint.VersionString(1) != IndexServerAddress() && endpoint.URL.Scheme == "https" {
+ info, err := endpoint.Ping()
if err != nil {
return nil, err
}
- if info.Standalone {
- log.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", r.indexEndpoint.String())
- dec := utils.NewHTTPAuthDecorator(authConfig.Username, authConfig.Password)
- factory.AddDecorator(dec)
+
+ if info.Standalone && authConfig != nil {
+ logrus.Debugf("Endpoint %s is eligible for private registry. Enabling decorator.", endpoint.String())
+ alwaysSetBasicAuth = true
}
}
- r.reqFactory = factory
- return r, nil
-}
+ if endpoint.Version == APIVersion1 {
+ client.Transport = AuthTransport(client.Transport, authConfig, alwaysSetBasicAuth)
+ }
-func (r *Session) doRequest(req *http.Request) (*http.Response, *http.Client, error) {
- return doRequest(req, r.jar, r.timeout, r.indexEndpoint.IsSecure)
+ jar, err := cookiejar.New(nil)
+ if err != nil {
+ return nil, errors.New("cookiejar.New is not supposed to return an error")
+ }
+ client.Jar = jar
+
+ return r, nil
}
// Retrieve the history of a given image from the Registry.
// Return a list of the parent's json (requested image included)
-func (r *Session) GetRemoteHistory(imgID, registry string, token []string) ([]string, error) {
- req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/ancestry", nil)
- if err != nil {
- return nil, err
- }
- setTokenAuth(req, token)
- res, _, err := r.doRequest(req)
+func (r *Session) GetRemoteHistory(imgID, registry string) ([]string, error) {
+ res, err := r.client.Get(registry + "images/" + imgID + "/ancestry")
if err != nil {
return nil, err
}
@@ -85,55 +179,40 @@ 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)
- if err != nil {
- return nil, fmt.Errorf("Error while reading the http response: %s", err)
+ var history []string
+ if err := json.NewDecoder(res.Body).Decode(&history); err != nil {
+ return nil, fmt.Errorf("Error while reading the http response: %v", err)
}
- log.Debugf("Ancestry: %s", jsonString)
- history := new([]string)
- if err := json.Unmarshal(jsonString, history); err != nil {
- return nil, err
- }
- return *history, nil
+ logrus.Debugf("Ancestry: %v", history)
+ return history, nil
}
// Check if an image exists in the Registry
-func (r *Session) LookupRemoteImage(imgID, registry string, token []string) error {
- req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
- if err != nil {
- return err
- }
- setTokenAuth(req, token)
- res, _, err := r.doRequest(req)
+func (r *Session) LookupRemoteImage(imgID, registry string) error {
+ res, err := r.client.Get(registry + "images/" + imgID + "/json")
if err != nil {
return err
}
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
}
// Retrieve an image from the Registry.
-func (r *Session) GetRemoteImageJSON(imgID, registry string, token []string) ([]byte, int, error) {
- // Get the JSON
- req, err := r.reqFactory.NewRequest("GET", registry+"images/"+imgID+"/json", nil)
- if err != nil {
- return nil, -1, fmt.Errorf("Failed to download json: %s", err)
- }
- setTokenAuth(req, token)
- res, _, err := r.doRequest(req)
+func (r *Session) GetRemoteImageJSON(imgID, registry string) ([]byte, int, error) {
+ res, err := r.client.Get(registry + "images/" + imgID + "/json")
if err != nil {
return nil, -1, fmt.Errorf("Failed to download json: %s", err)
}
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
@@ -146,44 +225,44 @@ func (r *Session) GetRemoteImageJSON(imgID, registry string, token []string) ([]
jsonString, err := ioutil.ReadAll(res.Body)
if err != nil {
- return nil, -1, fmt.Errorf("Failed to parse downloaded json: %s (%s)", err, jsonString)
+ return nil, -1, fmt.Errorf("Failed to parse downloaded json: %v (%s)", err, jsonString)
}
return jsonString, imageSize, nil
}
-func (r *Session) GetRemoteImageLayer(imgID, registry string, token []string, imgSize int64) (io.ReadCloser, error) {
+func (r *Session) GetRemoteImageLayer(imgID, registry string, imgSize int64) (io.ReadCloser, error) {
var (
retries = 5
statusCode = 0
- client *http.Client
res *http.Response
+ err error
imageURL = fmt.Sprintf("%simages/%s/layer", registry, imgID)
)
- req, err := r.reqFactory.NewRequest("GET", imageURL, nil)
+ req, err := http.NewRequest("GET", imageURL, nil)
if err != nil {
- return nil, fmt.Errorf("Error while getting from the server: %s\n", err)
+ return nil, fmt.Errorf("Error while getting from the server: %v", err)
}
- setTokenAuth(req, token)
+ // TODO: why are we doing retries at this level?
+ // These retries should be generic to both v1 and v2
for i := 1; i <= retries; i++ {
statusCode = 0
- res, client, err = r.doRequest(req)
- if err != nil {
- log.Debugf("Error contacting registry: %s", err)
- if res != nil {
- if res.Body != nil {
- res.Body.Close()
- }
- statusCode = res.StatusCode
- }
- if i == retries {
- return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
- statusCode, imgID)
- }
- time.Sleep(time.Duration(i) * 5 * time.Second)
- continue
+ res, err = r.client.Do(req)
+ if err == nil {
+ break
}
- break
+ logrus.Debugf("Error contacting registry %s: %v", registry, err)
+ if res != nil {
+ if res.Body != nil {
+ res.Body.Close()
+ }
+ statusCode = res.StatusCode
+ }
+ if i == retries {
+ return nil, fmt.Errorf("Server error: Status %d while fetching image layer (%s)",
+ statusCode, imgID)
+ }
+ time.Sleep(time.Duration(i) * 5 * time.Second)
}
if res.StatusCode != 200 {
@@ -193,14 +272,14 @@ func (r *Session) GetRemoteImageLayer(imgID, registry string, token []string, im
}
if res.Header.Get("Accept-Ranges") == "bytes" && imgSize > 0 {
- log.Debugf("server supports resume")
- return httputils.ResumableRequestReaderWithInitialResponse(client, req, 5, imgSize, res), nil
+ logrus.Debugf("server supports resume")
+ return httputils.ResumableRequestReaderWithInitialResponse(r.client, req, 5, imgSize, res), nil
}
- log.Debugf("server doesn't support resume")
+ logrus.Debugf("server doesn't support resume")
return res.Body, nil
}
-func (r *Session) GetRemoteTags(registries []string, repository string, token []string) (map[string]string, error) {
+func (r *Session) GetRemoteTags(registries []string, repository string) (map[string]string, error) {
if strings.Count(repository, "/") == 0 {
// This will be removed once the Registry supports auto-resolution on
// the "library" namespace
@@ -208,25 +287,20 @@ func (r *Session) GetRemoteTags(registries []string, repository string, token []
}
for _, host := range registries {
endpoint := fmt.Sprintf("%srepositories/%s/tags", host, repository)
- req, err := r.reqFactory.NewRequest("GET", endpoint, nil)
-
- if err != nil {
- return nil, err
- }
- setTokenAuth(req, token)
- res, _, err := r.doRequest(req)
+ res, err := r.client.Get(endpoint)
if err != nil {
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 {
- continue
- } else if res.StatusCode == 404 {
+ if res.StatusCode == 404 {
return nil, fmt.Errorf("Repository not found")
}
+ if res.StatusCode != 200 {
+ continue
+ }
result := make(map[string]string)
if err := json.NewDecoder(res.Body).Decode(&result); err != nil {
@@ -259,18 +333,15 @@ 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)
+ req, err := http.NewRequest("GET", repositoryTarget, nil)
if err != nil {
return nil, err
}
- if r.authConfig != nil && len(r.authConfig.Username) > 0 {
- req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
- }
+ // this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests
req.Header.Set("X-Docker-Token", "true")
-
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return nil, err
}
@@ -281,18 +352,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 {
- 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)
- }
-
- var tokens []string
- if res.Header.Get("X-Docker-Token") != "" {
- tokens = res.Header["X-Docker-Token"]
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to pull repository %s: %q", res.StatusCode, remote, errBody), res)
}
var endpoints []string
@@ -320,29 +386,29 @@ func (r *Session) GetRepositoryData(remote string) (*RepositoryData, error) {
return &RepositoryData{
ImgList: imgsData,
Endpoints: endpoints,
- Tokens: tokens,
}, nil
}
-func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string, token []string) error {
+func (r *Session) PushImageChecksumRegistry(imgData *ImgData, registry string) error {
- log.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/checksum")
+ u := registry + "images/" + imgData.ID + "/checksum"
- req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/checksum", nil)
+ logrus.Debugf("[registry] Calling PUT %s", u)
+
+ req, err := http.NewRequest("PUT", u, nil)
if err != nil {
return err
}
- setTokenAuth(req, token)
req.Header.Set("X-Docker-Checksum", imgData.Checksum)
req.Header.Set("X-Docker-Checksum-Payload", imgData.ChecksumPayload)
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
- return fmt.Errorf("Failed to upload metadata: %s", err)
+ return fmt.Errorf("Failed to upload metadata: %v", err)
}
defer res.Body.Close()
if len(res.Cookies()) > 0 {
- r.jar.SetCookies(req.URL, res.Cookies())
+ r.client.Jar.SetCookies(req.URL, res.Cookies())
}
if res.StatusCode != 200 {
errBody, err := ioutil.ReadAll(res.Body)
@@ -361,29 +427,30 @@ 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 {
+func (r *Session) PushImageJSONRegistry(imgData *ImgData, jsonRaw []byte, registry string) error {
- log.Debugf("[registry] Calling PUT %s", registry+"images/"+imgData.ID+"/json")
+ u := registry + "images/" + imgData.ID + "/json"
- req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgData.ID+"/json", bytes.NewReader(jsonRaw))
+ logrus.Debugf("[registry] Calling PUT %s", u)
+
+ req, err := http.NewRequest("PUT", u, bytes.NewReader(jsonRaw))
if err != nil {
return err
}
req.Header.Add("Content-type", "application/json")
- setTokenAuth(req, token)
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return fmt.Errorf("Failed to upload metadata: %s", err)
}
defer res.Body.Close()
if res.StatusCode == 401 && strings.HasPrefix(registry, "http://") {
- return utils.NewHTTPRequestError("HTTP code 401, Docker will not send auth headers over HTTP.", res)
+ 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 {
@@ -391,14 +458,16 @@ 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
}
-func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, token []string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
+func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry string, jsonRaw []byte) (checksum string, checksumPayload string, err error) {
- log.Debugf("[registry] Calling PUT %s", registry+"images/"+imgID+"/layer")
+ u := registry + "images/" + imgID + "/layer"
+
+ logrus.Debugf("[registry] Calling PUT %s", u)
tarsumLayer, err := tarsum.NewTarSum(layer, false, tarsum.Version0)
if err != nil {
@@ -409,17 +478,16 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry
h.Write([]byte{'\n'})
checksumLayer := io.TeeReader(tarsumLayer, h)
- req, err := r.reqFactory.NewRequest("PUT", registry+"images/"+imgID+"/layer", checksumLayer)
+ req, err := http.NewRequest("PUT", u, checksumLayer)
if err != nil {
return "", "", err
}
req.Header.Add("Content-Type", "application/octet-stream")
req.ContentLength = -1
req.TransferEncoding = []string{"chunked"}
- setTokenAuth(req, token)
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
- return "", "", fmt.Errorf("Failed to upload layer: %s", err)
+ return "", "", fmt.Errorf("Failed to upload layer: %v", err)
}
if rc, ok := layer.(io.Closer); ok {
if err := rc.Close(); err != nil {
@@ -431,9 +499,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))
@@ -442,25 +510,24 @@ func (r *Session) PushImageLayerRegistry(imgID string, layer io.Reader, registry
// push a tag on the registry.
// Remote has the format '/
-func (r *Session) PushRegistryTag(remote, revision, tag, registry string, token []string) error {
+func (r *Session) PushRegistryTag(remote, revision, tag, registry string) error {
// "jsonify" the string
revision = "\"" + revision + "\""
path := fmt.Sprintf("repositories/%s/tags/%s", remote, tag)
- req, err := r.reqFactory.NewRequest("PUT", registry+path, strings.NewReader(revision))
+ req, err := http.NewRequest("PUT", registry+path, strings.NewReader(revision))
if err != nil {
return err
}
req.Header.Add("Content-type", "application/json")
- setTokenAuth(req, token)
req.ContentLength = int64(len(revision))
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return err
}
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
}
@@ -486,10 +553,11 @@ 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"},
+ "Content-type": {"application/json"},
+ // this will set basic auth in r.client.Transport and send cached X-Docker-Token headers for all subsequent requests
"X-Docker-Token": {"true"},
}
if validate {
@@ -507,7 +575,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,53 +588,45 @@ 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)
- } else {
- return nil, fmt.Errorf("Index response didn't contain an access token")
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push repository %s: %q", res.StatusCode, remote, errBody), res)
}
+ 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")
}
- }
- if validate {
+ endpoints, err = buildEndpointsList(res.Header["X-Docker-Endpoints"], r.indexEndpoint.VersionString(1))
+ if err != nil {
+ return nil, err
+ }
+ } else {
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)
+ return nil, httputils.NewHTTPRequestError(fmt.Sprintf("Error: Status %d trying to push checksums %s: %q", res.StatusCode, remote, errBody), res)
}
}
return &RepositoryData{
- Tokens: tokens,
Endpoints: endpoints,
}, nil
}
func (r *Session) putImageRequest(u string, headers map[string][]string, body []byte) (*http.Response, error) {
- req, err := r.reqFactory.NewRequest("PUT", u, bytes.NewReader(body))
+ req, err := http.NewRequest("PUT", u, bytes.NewReader(body))
if err != nil {
return nil, err
}
- req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
req.ContentLength = int64(len(body))
for k, v := range headers {
req.Header[k] = v
}
- response, _, err := r.doRequest(req)
+ response, err := r.client.Do(req)
if err != nil {
return nil, err
}
@@ -578,43 +638,29 @@ 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 {
- return nil, err
- }
- if r.authConfig != nil && len(r.authConfig.Username) > 0 {
- req.SetBasicAuth(r.authConfig.Username, r.authConfig.Password)
- }
- req.Header.Set("X-Docker-Token", "true")
- res, _, err := r.doRequest(req)
+ res, err := r.client.Get(u)
if err != nil {
return nil, err
}
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)
- return result, err
+ return result, json.NewDecoder(res.Body).Decode(result)
}
-func (r *Session) GetAuthConfig(withPasswd bool) *AuthConfig {
+// TODO(tiborvass): remove this once registry client v2 is vendored
+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,
}
}
-
-func setTokenAuth(req *http.Request, token []string) {
- if req.Header.Get("Authorization") == "" { // Don't override
- req.Header.Set("Authorization", "Token "+strings.Join(token, ","))
- }
-}
diff --git a/registry/session_v2.go b/registry/session_v2.go
index 833abeed6..f2b21df43 100644
--- a/registry/session_v2.go
+++ b/registry/session_v2.go
@@ -9,10 +9,10 @@ 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"
+ "github.com/docker/distribution/registry/api/v2"
+ "github.com/docker/docker/pkg/httputils"
)
const DockerDigestHeader = "Docker-Content-Digest"
@@ -27,7 +27,7 @@ func getV2Builder(e *Endpoint) *v2.URLBuilder {
func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error) {
// TODO check if should use Mirror
if index.Official {
- ep, err = newEndpoint(REGISTRYSERVER, true)
+ ep, err = newEndpoint(REGISTRYSERVER, true, nil)
if err != nil {
return
}
@@ -38,7 +38,7 @@ func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error)
} else if r.indexEndpoint.String() == index.GetAuthConfigKey() {
ep = r.indexEndpoint
} else {
- ep, err = NewEndpoint(index)
+ ep, err = NewEndpoint(index, nil)
if err != nil {
return
}
@@ -49,7 +49,7 @@ func (r *Session) V2RegistryEndpoint(index *IndexInfo) (ep *Endpoint, err error)
}
// GetV2Authorization gets the authorization needed to the given image
-// If readonly access is requested, then only the authorization may
+// If readonly access is requested, then the authorization may
// only be used for Get operations.
func (r *Session) GetV2Authorization(ep *Endpoint, imageName string, readOnly bool) (auth *RequestAuthorization, err error) {
scopes := []string{"pull"}
@@ -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
}
@@ -68,64 +68,83 @@ func (r *Session) GetV2Authorization(ep *Endpoint, imageName string, readOnly bo
// 1.c) if anything else, err
// 2) PUT the created/signed manifest
//
-func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, auth *RequestAuthorization) ([]byte, string, error) {
+
+// GetV2ImageManifest simply fetches the bytes of a manifest and the remote
+// digest, if available in the request. Note that the application shouldn't
+// rely on the untrusted remoteDigest, and should also verify against a
+// locally provided digest, if applicable.
+func (r *Session) GetV2ImageManifest(ep *Endpoint, imageName, tagName string, auth *RequestAuthorization) (remoteDigest digest.Digest, p []byte, err error) {
routeURL, err := getV2Builder(ep).BuildManifestURL(imageName, tagName)
if err != nil {
- return nil, "", err
+ return "", nil, err
}
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)
+ req, err := http.NewRequest(method, routeURL, nil)
if err != nil {
- return nil, "", err
+ return "", nil, err
}
+
if err := auth.Authorize(req); err != nil {
- return nil, "", err
+ return "", nil, err
}
- res, _, err := r.doRequest(req)
+
+ res, err := r.client.Do(req)
if err != nil {
- return nil, "", err
+ return "", nil, err
}
defer res.Body.Close()
+
if res.StatusCode != 200 {
if res.StatusCode == 401 {
- return nil, "", errLoginRequired
+ return "", nil, errLoginRequired
} else if res.StatusCode == 404 {
- return nil, "", ErrDoesNotExist
+ 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)
+ p, err = ioutil.ReadAll(res.Body)
if err != nil {
- return nil, "", fmt.Errorf("Error while reading the http response: %s", err)
+ return "", nil, fmt.Errorf("Error while reading the http response: %s", err)
}
- return manifestBytes, res.Header.Get(DockerDigestHeader), nil
+ dgstHdr := res.Header.Get(DockerDigestHeader)
+ if dgstHdr != "" {
+ remoteDigest, err = digest.ParseDigest(dgstHdr)
+ if err != nil {
+ // NOTE(stevvooe): Including the remote digest is optional. We
+ // don't need to verify against it, but it is good practice.
+ remoteDigest = ""
+ logrus.Debugf("error parsing remote digest when fetching %v: %v", routeURL, err)
+ }
+ }
+
+ return
}
// - 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
}
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)
+ req, err := http.NewRequest(method, routeURL, nil)
if err != nil {
return false, err
}
if err := auth.Authorize(req); err != nil {
return false, err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return false, err
}
@@ -141,25 +160,25 @@ 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, 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, 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
}
method := "GET"
- log.Debugf("[registry] Calling %q %s", method, routeURL)
- req, err := r.reqFactory.NewRequest(method, routeURL, nil)
+ logrus.Debugf("[registry] Calling %q %s", method, routeURL)
+ req, err := http.NewRequest(method, routeURL, nil)
if err != nil {
return err
}
if err := auth.Authorize(req); err != nil {
return err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return err
}
@@ -168,29 +187,29 @@ func (r *Session) GetV2ImageBlob(ep *Endpoint, imageName, sumType, sum string, b
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)
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
}
method := "GET"
- log.Debugf("[registry] Calling %q %s", method, routeURL)
- req, err := r.reqFactory.NewRequest(method, routeURL, nil)
+ logrus.Debugf("[registry] Calling %q %s", method, routeURL)
+ req, err := http.NewRequest(method, routeURL, nil)
if err != nil {
return nil, 0, err
}
if err := auth.Authorize(req); err != nil {
return nil, 0, err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return nil, 0, err
}
@@ -198,7 +217,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, 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)
@@ -212,25 +231,25 @@ 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
}
method := "PUT"
- log.Debugf("[registry] Calling %q %s", method, location)
- req, err := r.reqFactory.NewRequest(method, location, ioutil.NopCloser(blobRdr))
+ logrus.Debugf("[registry] Calling %q %s", method, location)
+ req, err := http.NewRequest(method, location, ioutil.NopCloser(blobRdr))
if err != nil {
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
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return err
}
@@ -244,8 +263,8 @@ 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)
- return utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s:%s", res.StatusCode, imageName, sumType, sumStr), res)
+ logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
+ return httputils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s blob - %s", res.StatusCode, imageName, dgst), res)
}
return nil
@@ -258,8 +277,8 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque
return "", err
}
- log.Debugf("[registry] Calling %q %s", "POST", routeURL)
- req, err := r.reqFactory.NewRequest("POST", routeURL, nil)
+ logrus.Debugf("[registry] Calling %q %s", "POST", routeURL)
+ req, err := http.NewRequest("POST", routeURL, nil)
if err != nil {
return "", err
}
@@ -267,7 +286,7 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque
if err := auth.Authorize(req); err != nil {
return "", err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return "", err
}
@@ -285,8 +304,8 @@ func (r *Session) initiateBlobUpload(ep *Endpoint, imageName string, auth *Reque
return "", err
}
- log.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)
+ logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
+ 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 == "" {
@@ -304,15 +323,15 @@ func (r *Session) PutV2ImageManifest(ep *Endpoint, imageName, tagName string, si
}
method := "PUT"
- log.Debugf("[registry] Calling %q %s", method, routeURL)
- req, err := r.reqFactory.NewRequest(method, routeURL, bytes.NewReader(signedManifest))
+ logrus.Debugf("[registry] Calling %q %s", method, routeURL)
+ req, err := http.NewRequest(method, routeURL, bytes.NewReader(signedManifest))
if err != nil {
return "", err
}
if err := auth.Authorize(req); err != nil {
return "", err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return "", err
}
@@ -327,8 +346,8 @@ 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)
- return "", utils.NewHTTPRequestError(fmt.Sprintf("Server error: %d trying to push %s:%s manifest", res.StatusCode, imageName, tagName), res)
+ logrus.Debugf("Unexpected response from server: %q %#v", errBody, res.Header)
+ 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))
@@ -352,8 +371,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
@@ -364,16 +383,16 @@ 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)
+ req, err := http.NewRequest(method, routeURL, nil)
if err != nil {
return nil, err
}
if err := auth.Authorize(req); err != nil {
return nil, err
}
- res, _, err := r.doRequest(req)
+ res, err := r.client.Do(req)
if err != nil {
return nil, err
}
@@ -384,14 +403,12 @@ 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)
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
+ return remote.Tags, nil
}
diff --git a/registry/token.go b/registry/token.go
index c79a8ca6c..e27cb6f52 100644
--- a/registry/token.go
+++ b/registry/token.go
@@ -7,15 +7,13 @@ import (
"net/http"
"net/url"
"strings"
-
- "github.com/docker/docker/utils"
)
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) (token string, err error) {
realm, ok := params["realm"]
if !ok {
return "", errors.New("no realm specified for token auth challenge")
@@ -34,7 +32,7 @@ func getToken(username, password string, params map[string]string, registryEndpo
}
}
- req, err := factory.NewRequest("GET", realmURL.String(), nil)
+ req, err := http.NewRequest("GET", realmURL.String(), nil)
if err != nil {
return "", err
}
@@ -58,7 +56,7 @@ func getToken(username, password string, params map[string]string, registryEndpo
req.URL.RawQuery = reqParams.Encode()
- resp, err := client.Do(req)
+ resp, err := registryEndpoint.client.Do(req)
if err != nil {
return "", err
}
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"`
}
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/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_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)
- }
- }
- }
-}
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 45255e9b0..1d3d4acfc 100644
--- a/runconfig/config.go
+++ b/runconfig/config.go
@@ -1,10 +1,108 @@
package runconfig
import (
- "github.com/docker/docker/engine"
+ "encoding/json"
+ "io"
+ "strings"
+
"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) ToString() string {
+ return strings.Join(e.parts, " ")
+}
+
+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 +110,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 +119,42 @@ 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{}
+ VolumeDriver string
WorkingDir string
- Entrypoint []string
+ Entrypoint *Entrypoint
NetworkDisabled bool
MacAddress string
OnBuild []string
Labels map[string]string
}
-func ContainerConfigFromJob(job *engine.Job) *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"),
+type ContainerConfigWrapper struct {
+ *Config
+ *hostConfigWrapper
+}
+
+func (c ContainerConfigWrapper) HostConfig() *HostConfig {
+ if c.hostConfigWrapper == nil {
+ return new(HostConfig)
}
- job.GetenvJson("ExposedPorts", &config.ExposedPorts)
- job.GetenvJson("Volumes", &config.Volumes)
- if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil {
- config.PortSpecs = PortSpecs
- }
- if Env := job.GetenvList("Env"); Env != nil {
- config.Env = Env
- }
- if Cmd := job.GetenvList("Cmd"); Cmd != nil {
- config.Cmd = Cmd
- }
+
+ return c.hostConfigWrapper.GetHostConfig()
+}
- job.GetenvJson("Labels", &config.Labels)
+// DecodeContainerConfig decodes a json encoded config into a ContainerConfigWrapper
+// struct and returns both a Config and an HostConfig struct
+// Be aware this function is not checking whether the resulted structs are nil,
+// it's your business to do so
+func DecodeContainerConfig(src io.Reader) (*Config, *HostConfig, error) {
+ decoder := json.NewDecoder(src)
- if Entrypoint := job.GetenvList("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..27727a495 100644
--- a/runconfig/config_test.go
+++ b/runconfig/config_test.go
@@ -1,7 +1,9 @@
package runconfig
import (
+ "bytes"
"fmt"
+ "io/ioutil"
"strings"
"testing"
@@ -43,13 +45,6 @@ func TestParseRunLinks(t *testing.T) {
if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
}
-
- if _, _, err := parse(t, "--link a"); err == nil {
- t.Fatalf("Error parsing links. `--link a` should be an error but is not")
- }
- if _, _, err := parse(t, "--link"); err == nil {
- t.Fatalf("Error parsing links. `--link` should be an error but is not")
- }
}
func TestParseRunAttach(t *testing.T) {
@@ -102,7 +97,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)
}
@@ -119,6 +114,14 @@ func TestParseRunVolumes(t *testing.T) {
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
}
+ if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:roZ -v /hostVar:/containerVar:rwZ"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:roZ", "/hostVar:/containerVar:rwZ") != nil {
+ t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:roZ -v /hostVar:/containerVar:rwZ` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
+ }
+
+ if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:Z", "/hostVar:/containerVar:z") != nil {
+ t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
+ }
+
if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
} else if _, exists := config.Volumes["/containerVar"]; !exists {
@@ -260,5 +263,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/exec.go b/runconfig/exec.go
index 9390781a4..781cb3550 100644
--- a/runconfig/exec.go
+++ b/runconfig/exec.go
@@ -1,11 +1,7 @@
package runconfig
import (
- "fmt"
-
- "github.com/docker/docker/engine"
flag "github.com/docker/docker/pkg/mflag"
- "github.com/docker/docker/utils"
)
type ExecConfig struct {
@@ -20,37 +16,17 @@ type ExecConfig struct {
Cmd []string
}
-func ExecConfigFromJob(job *engine.Job) (*ExecConfig, error) {
- execConfig := &ExecConfig{
- // TODO(vishh): Expose 'User' once it is supported.
- //User: job.Getenv("User"),
- // TODO(vishh): Expose 'Privileged' once it is supported.
- //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")
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
)
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)
@@ -58,14 +34,13 @@ func ParseExec(cmd *flag.FlagSet, args []string) (*ExecConfig, error) {
execCmd = parsedArgs[1:]
execConfig := &ExecConfig{
- // TODO(vishh): Expose '-u' flag once it is supported.
- User: "",
- // TODO(vishh): Expose '-p' flag once it is supported.
- Privileged: false,
- Tty: *flTty,
- Cmd: execCmd,
- Container: container,
- Detach: *flDetach,
+ User: *flUser,
+ // TODO(vishh): Expose 'Privileged' once it is supported.
+ // + //Privileged: job.GetenvBool("Privileged"),
+ Tty: *flTty,
+ Cmd: execCmd,
+ Container: container,
+ Detach: *flDetach,
}
// If -d is not set, attach to everything by default
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 84d636b5c..1418dea4a 100644
--- a/runconfig/hostconfig.go
+++ b/runconfig/hostconfig.go
@@ -1,19 +1,28 @@
package runconfig
import (
+ "encoding/json"
+ "io"
"strings"
- "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
func (n NetworkMode) IsPrivate() bool {
- return !(n.IsHost() || n.IsContainer() || n.IsNone())
+ return !(n.IsHost() || n.IsContainer())
+}
+
+func (n NetworkMode) IsBridge() bool {
+ return n == "bridge"
}
func (n NetworkMode) IsHost() bool {
@@ -67,6 +76,27 @@ func (n IpcMode) Container() string {
return ""
}
+type UTSMode string
+
+// IsPrivate indicates whether container use it's private UTS namespace
+func (n UTSMode) IsPrivate() bool {
+ return !(n.IsHost())
+}
+
+func (n UTSMode) IsHost() bool {
+ return n == "host"
+}
+
+func (n UTSMode) Valid() bool {
+ parts := strings.Split(string(n), ":")
+ switch mode := parts[0]; mode {
+ case "", "host":
+ default:
+ return false
+ }
+ return true
+}
+
type PidMode string
// IsPrivate indicates whether container use it's private pid stack
@@ -99,19 +129,85 @@ type RestartPolicy struct {
MaximumRetryCount int
}
+func (rp *RestartPolicy) IsNone() bool {
+ return rp.Name == "no"
+}
+
+func (rp *RestartPolicy) IsAlways() bool {
+ return rp.Name == "always"
+}
+
+func (rp *RestartPolicy) IsOnFailure() bool {
+ return rp.Name == "on-failure"
+}
+
type LogConfig struct {
Type string
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 []utils.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)
+ 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)
+ CpuPeriod int64
CpusetCpus string // CpusetCpus 0-2, 0,1
+ CpusetMems string // CpusetMems 0-2, 0,1
+ CpuQuota int64
+ 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
@@ -124,6 +220,7 @@ type HostConfig struct {
NetworkMode NetworkMode
IpcMode IpcMode
PidMode PidMode
+ UTSMode UTSMode
CapAdd []string
CapDrop []string
RestartPolicy RestartPolicy
@@ -134,96 +231,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(job *engine.Job) *HostConfig {
- if job.EnvExists("HostConfig") {
- hostConfig := HostConfig{}
- job.GetenvJson("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 job.EnvExists("Memory") && hostConfig.Memory == 0 {
- hostConfig.Memory = job.GetenvInt64("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 job.EnvExists("MemorySwap") && hostConfig.MemorySwap == 0 {
- hostConfig.MemorySwap = job.GetenvInt64("MemorySwap")
+ if hc.MemorySwap != 0 && w.InnerHostConfig.MemorySwap == 0 {
+ w.InnerHostConfig.MemorySwap = hc.MemorySwap
}
- if job.EnvExists("CpuShares") && hostConfig.CpuShares == 0 {
- hostConfig.CpuShares = job.GetenvInt64("CpuShares")
+ if hc.CpuShares != 0 && w.InnerHostConfig.CpuShares == 0 {
+ w.InnerHostConfig.CpuShares = hc.CpuShares
}
- if job.EnvExists("Cpuset") && hostConfig.CpusetCpus == "" {
- hostConfig.CpusetCpus = job.Getenv("Cpuset")
- }
- return &hostConfig
+ hc = w.InnerHostConfig
}
- 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"),
+ 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 job.EnvExists("Cpuset") && hostConfig.CpusetCpus == "" {
- hostConfig.CpusetCpus = job.Getenv("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
}
- 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 {
- hostConfig.Binds = Binds
- }
- if Links := job.GetenvList("Links"); Links != nil {
- hostConfig.Links = Links
- }
- if Dns := job.GetenvList("Dns"); Dns != nil {
- hostConfig.Dns = Dns
- }
- if DnsSearch := job.GetenvList("DnsSearch"); DnsSearch != nil {
- hostConfig.DnsSearch = DnsSearch
- }
- if ExtraHosts := job.GetenvList("ExtraHosts"); ExtraHosts != nil {
- hostConfig.ExtraHosts = ExtraHosts
- }
- if VolumesFrom := job.GetenvList("VolumesFrom"); VolumesFrom != nil {
- hostConfig.VolumesFrom = VolumesFrom
- }
- if CapAdd := job.GetenvList("CapAdd"); CapAdd != nil {
- hostConfig.CapAdd = CapAdd
- }
- if CapDrop := job.GetenvList("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 9bbdc6ad2..9c9a3b436 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"
)
@@ -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 {
@@ -50,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.
- log.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)
}
@@ -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 ccd8056cf..46ec26785 100644
--- a/runconfig/parse.go
+++ b/runconfig/parse.go
@@ -2,7 +2,6 @@ package runconfig
import (
"fmt"
- "path"
"strconv"
"strings"
@@ -12,16 +11,15 @@ 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 (
- 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.")
+ ErrConflictContainerNetworkAndLinks = fmt.Errorf("Conflicting options: --net=container can't be used with links. This would result in undefined behavior")
+ 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.")
+ 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) {
@@ -49,13 +47,16 @@ 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")
flPidMode = cmd.String([]string{"-pid"}, "", "PID namespace to use")
+ flUTSMode = cmd.String([]string{"-uts"}, "", "UTS namespace to use")
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")
@@ -64,7 +65,11 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID (format: [:])")
flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
+ flCpuPeriod = cmd.Int64([]string{"-cpu-period"}, 0, "Limit CPU CFS (Completely Fair Scheduler) period")
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")
@@ -93,16 +98,49 @@ 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")
+
+ expFlags := attachExperimentalFlags(cmd)
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
}
- // Validate input params
- if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) {
- return nil, nil, cmd, ErrInvalidWorkingDirectory
+ var (
+ attachStdin = flAttach.Get("stdin")
+ attachStdout = flAttach.Get("stdout")
+ attachStderr = flAttach.Get("stderr")
+ )
+
+ 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 netMode.IsHost() && flLinks.Len() > 0 {
+ return nil, nil, cmd, ErrConflictHostNetworkAndLinks
+ }
+
+ if netMode.IsContainer() && flLinks.Len() > 0 {
+ return nil, nil, cmd, ErrConflictContainerNetworkAndLinks
+ }
+
+ if (netMode.IsHost() || netMode.IsContainer()) && flDns.Len() > 0 {
+ 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
@@ -111,31 +149,6 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
return nil, nil, cmd, fmt.Errorf("%s is not a valid mac address", *flMacAddress)
}
}
- var (
- attachStdin = flAttach.Get("stdin")
- attachStdout = flAttach.Get("stdout")
- attachStderr = flAttach.Get("stderr")
- )
-
- if *flNetMode != "bridge" && *flNetMode != "none" && *flHostname != "" {
- return nil, nil, cmd, ErrConflictNetworkHostname
- }
-
- if *flNetMode == "host" && flLinks.Len() > 0 {
- return nil, nil, cmd, ErrConflictHostNetworkAndLinks
- }
-
- if *flNetMode == "container" && flLinks.Len() > 0 {
- return nil, nil, cmd, ErrConflictContainerNetworkAndLinks
- }
-
- if *flNetMode == "host" && flDns.Len() > 0 {
- return nil, nil, cmd, ErrConflictHostNetworkAndDns
- }
-
- if *flNetMode == "container" && flDns.Len() > 0 {
- return nil, nil, cmd, ErrConflictContainerNetworkAndDns
- }
// If neither -d or -a are set, attach to everything by default
if flAttach.Len() == 0 {
@@ -186,21 +199,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
@@ -270,12 +284,17 @@ 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)
+ utsMode := UTSMode(*flUTSMode)
+ if !utsMode.Valid() {
+ return nil, nil, cmd, fmt.Errorf("--uts: invalid UTS mode")
}
- restartPolicy, err := parseRestartPolicy(*flRestartPolicy)
+ restartPolicy, err := ParseRestartPolicy(*flRestartPolicy)
+ if err != nil {
+ return nil, nil, cmd, err
+ }
+
+ loggingOpts, err := parseLoggingOpts(*flLoggingDriver, flLoggingOpts.GetAll())
if err != nil {
return nil, nil, cmd, err
}
@@ -289,10 +308,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,
@@ -313,7 +328,12 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
Memory: flMemory,
MemorySwap: MemorySwap,
CpuShares: *flCpuShares,
+ CpuPeriod: *flCpuPeriod,
CpusetCpus: *flCpusetCpus,
+ CpusetMems: *flCpusetMems,
+ CpuQuota: *flCpuQuota,
+ BlkioWeight: *flBlkioWeight,
+ OomKillDisable: *flOomKillDisable,
Privileged: *flPrivileged,
PortBindings: portBindings,
Links: flLinks.GetAll(),
@@ -325,6 +345,7 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
NetworkMode: netMode,
IpcMode: ipcMode,
PidMode: pidMode,
+ UTSMode: utsMode,
Devices: deviceMappings,
CapAdd: flCapAdd.GetAll(),
CapDrop: flCapDrop.GetAll(),
@@ -332,10 +353,12 @@ 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,
}
+ applyExperimentalFlags(expFlags, config, hostConfig)
+
// When allocating stdin in attached mode, close stdin at client disconnect
if config.OpenStdin && config.AttachStdin {
config.StdinOnce = true
@@ -374,8 +397,17 @@ 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) {
+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{}
if policy == "" {
@@ -430,14 +462,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/runconfig/parse_experimental.go b/runconfig/parse_experimental.go
new file mode 100644
index 000000000..886b377fa
--- /dev/null
+++ b/runconfig/parse_experimental.go
@@ -0,0 +1,19 @@
+// +build experimental
+
+package runconfig
+
+import flag "github.com/docker/docker/pkg/mflag"
+
+type experimentalFlags struct {
+ flags map[string]interface{}
+}
+
+func attachExperimentalFlags(cmd *flag.FlagSet) *experimentalFlags {
+ flags := make(map[string]interface{})
+ flags["volume-driver"] = cmd.String([]string{"-volume-driver"}, "", "Optional volume driver for the container")
+ return &experimentalFlags{flags: flags}
+}
+
+func applyExperimentalFlags(exp *experimentalFlags, config *Config, hostConfig *HostConfig) {
+ config.VolumeDriver = *(exp.flags["volume-driver"]).(*string)
+}
diff --git a/runconfig/parse_stub.go b/runconfig/parse_stub.go
new file mode 100644
index 000000000..391b6ed43
--- /dev/null
+++ b/runconfig/parse_stub.go
@@ -0,0 +1,14 @@
+// +build !experimental
+
+package runconfig
+
+import flag "github.com/docker/docker/pkg/mflag"
+
+type experimentalFlags struct{}
+
+func attachExperimentalFlags(cmd *flag.FlagSet) *experimentalFlags {
+ return nil
+}
+
+func applyExperimentalFlags(flags *experimentalFlags, config *Config, hostConfig *HostConfig) {
+}
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)
+ }
+}
diff --git a/trust/service.go b/trust/service.go
index 324a478f1..6a804faf5 100644
--- a/trust/service.go
+++ b/trust/service.go
@@ -4,71 +4,50 @@ import (
"fmt"
"time"
- log "github.com/Sirupsen/logrus"
- "github.com/docker/docker/engine"
+ "github.com/Sirupsen/logrus"
"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) engine.Status {
- if n := len(job.Args); n != 1 {
- return job.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 job.Errorf("Missing PublicKey")
- }
- pk, err := libtrust.UnmarshalPublicKeyJWK([]byte(keyBytes))
+ pk, err := libtrust.UnmarshalPublicKeyJWK(key)
if err != nil {
- return job.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 engine.StatusOK
+ 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 job.Errorf("Error verifying key to namespace: %s", namespace)
+ return false, fmt.Errorf("Error verifying key to namespace: %s", ns)
}
if !verified {
- log.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 engine.StatusOK
+ if t.expiration.Before(time.Now()) {
+ return false, NotVerifiedError("expired")
+ }
+ return true, nil
}
-func (t *TrustStore) CmdUpdateBase(job *engine.Job) engine.Status {
+func (t *TrustStore) UpdateBase() {
t.fetch()
-
- return engine.StatusOK
}
diff --git a/trust/trusts.go b/trust/trusts.go
index f5e317e9e..885127ee5 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"
)
@@ -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
}
@@ -93,7 +92,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 +105,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 +160,26 @@ 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)
+ if err := ioutil.WriteFile(path.Join(t.path, bg+".json"), b, 0600); err != nil {
+ 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)
+ if err := t.reload(); err != nil {
+ logrus.Infof("Reload of trust graph failed: %s", err)
}
}()
t.fetchTime = defaultFetchtime
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)
- }
-}
diff --git a/utils/experimental.go b/utils/experimental.go
new file mode 100644
index 000000000..b308a59fa
--- /dev/null
+++ b/utils/experimental.go
@@ -0,0 +1,7 @@
+// +build experimental
+
+package utils
+
+func ExperimentalBuild() bool {
+ return true
+}
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)
-}
diff --git a/utils/git.go b/utils/git.go
new file mode 100644
index 000000000..6c039bddb
--- /dev/null
+++ b/utils/git.go
@@ -0,0 +1,98 @@
+package utils
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/url"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+
+ "github.com/docker/docker/pkg/symlink"
+ "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
+ }
+
+ u, err := url.Parse(remoteURL)
+ if err != nil {
+ return "", err
+ }
+
+ 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 checkoutGit(fragment, root)
+}
+
+func cloneArgs(remoteURL *url.URL, root string) []string {
+ args := []string{"clone", "--recursive"}
+ shallow := len(remoteURL.Fragment) == 0
+
+ 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
+ }
+ }
+
+ if shallow {
+ args = append(args, "--depth", "1")
+ }
+
+ 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, err := symlink.FollowSymlinkInScope(filepath.Join(root, refAndDir[1]), root)
+ if err != nil {
+ return "", fmt.Errorf("Error setting git context, %q not within git root: %s", refAndDir[1], err)
+ }
+
+ 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
new file mode 100644
index 000000000..e9eb59563
--- /dev/null
+++ b/utils/git_test.go
@@ -0,0 +1,186 @@
+package utils
+
+import (
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "os"
+ "path/filepath"
+ "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(serverURL, "/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(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) {
+ 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 = os.Symlink("../subdir", filepath.Join(gitDir, "parentlink")); err != nil {
+ t.Fatal(err)
+ }
+
+ if err = os.Symlink("/subdir", filepath.Join(gitDir, "absolutelink")); 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},
+ {"master:parentlink", "FROM scratch\nEXPOSE 5000", false},
+ {"master:absolutelink", "FROM scratch\nEXPOSE 5000", false},
+ {"master:../subdir", "", true},
+ {"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))
+ }
+ }
+}
diff --git a/utils/http.go b/utils/http.go
deleted file mode 100644
index 24eaea56b..000000000
--- a/utils/http.go
+++ /dev/null
@@ -1,168 +0,0 @@
-package utils
-
-import (
- "io"
- "net/http"
- "strings"
-
- log "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
- }
- }
- log.Debugf("%v -- HEADERS: %v", req.URL, req.Header)
- return req, err
-}
diff --git a/utils/stubs.go b/utils/stubs.go
new file mode 100644
index 000000000..b376f0cfb
--- /dev/null
+++ b/utils/stubs.go
@@ -0,0 +1,7 @@
+// +build !experimental
+
+package utils
+
+func ExperimentalBuild() bool {
+ return false
+}
diff --git a/utils/tcp.go b/utils/tcp.go
new file mode 100644
index 000000000..75980ff69
--- /dev/null
+++ b/utils/tcp.go
@@ -0,0 +1,22 @@
+package utils
+
+import (
+ "net"
+ "net/http"
+ "time"
+)
+
+func ConfigureTCPTransport(tr *http.Transport, proto, addr string) {
+ // Why 32? See https://github.com/docker/docker/pull/8035.
+ timeout := 32 * time.Second
+ if proto == "unix" {
+ // No need for compression in local communications.
+ tr.DisableCompression = true
+ tr.Dial = func(_, _ string) (net.Conn, error) {
+ return net.DialTimeout(proto, addr, timeout)
+ }
+ } else {
+ tr.Proxy = http.ProxyFromEnvironment
+ tr.Dial = (&net.Dialer{Timeout: timeout}).Dial
+ }
+}
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
-}
diff --git a/utils/utils.go b/utils/utils.go
index 540ae6f57..cb1b7b34c 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -2,57 +2,23 @@ package utils
import (
"bufio"
- "bytes"
"crypto/sha1"
- "crypto/sha256"
"encoding/hex"
"fmt"
"io"
"io/ioutil"
- "net/http"
"os"
"os/exec"
"path/filepath"
- "regexp"
"runtime"
"strings"
- "sync"
- 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/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])
@@ -126,12 +92,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",
}
@@ -154,157 +120,6 @@ func DockerInitPath(localCopy string) string {
return ""
}
-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)
- } 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
-}
-
-type WriteFlusher struct {
- sync.Mutex
- w io.Writer
- flusher http.Flusher
-}
-
-func (wf *WriteFlusher) Write(b []byte) (n int, err error) {
- wf.Lock()
- defer wf.Unlock()
- n, err = wf.w.Write(b)
- wf.flusher.Flush()
- return n, err
-}
-
-// Flush the stream immediately.
-func (wf *WriteFlusher) Flush() {
- wf.Lock()
- defer wf.Unlock()
- wf.flusher.Flush()
-}
-
-func NewWriteFlusher(w io.Writer) *WriteFlusher {
- var flusher http.Flusher
- if f, ok := w.(http.Flusher); ok {
- flusher = f
- } else {
- flusher = &ioutils.NopFlusher{}
- }
- return &WriteFlusher{w: w, flusher: flusher}
-}
-
-func NewHTTPRequestError(msg string, res *http.Response) error {
- return &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.
@@ -312,7 +127,7 @@ var globalTestID string
// new directory.
func TestDirectory(templateDir string) (dir string, err error) {
if globalTestID == "" {
- globalTestID = common.RandomString()[:4]
+ globalTestID = stringid.GenerateRandomID()[:4]
}
prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, GetCallerName(2))
if prefix == "" {
@@ -342,26 +157,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 {
@@ -400,37 +195,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
-}
-
-// 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
@@ -475,15 +239,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.
@@ -515,27 +270,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_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)
- }
-
-}
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")
+ }
+}
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 fc899dc8d..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/gnu.tar and /dev/null differ
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 cc9cfaa33..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/nil-uid.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/pax.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/pax.tar
deleted file mode 100644
index 9bc24b658..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/pax.tar and /dev/null differ
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 8bd4e74d5..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/sparse-formats.tar and /dev/null differ
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 59e2d4e60..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/star.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/ustar.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/ustar.tar
deleted file mode 100644
index 29679d9a3..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/ustar.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/v7.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/v7.tar
deleted file mode 100644
index eb65fc941..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/v7.tar and /dev/null differ
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 5960ee824..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big-long.tar and /dev/null differ
diff --git a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big.tar b/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big.tar
deleted file mode 100644
index 753e883ce..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer-big.tar and /dev/null differ
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 e6d816ad0..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/writer.tar and /dev/null differ
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 9701950ed..000000000
Binary files a/vendor/src/code.google.com/p/go/src/pkg/archive/tar/testdata/xattrs.tar and /dev/null differ
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)
- }
- }
-}
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..eb72bff93
--- /dev/null
+++ b/vendor/src/github.com/Sirupsen/logrus/CHANGELOG.md
@@ -0,0 +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 512f26e5e..d55f90924 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
@@ -106,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")
}
```
@@ -187,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 0a06a1105..612417ff9 100644
--- a/vendor/src/github.com/Sirupsen/logrus/text_formatter.go
+++ b/vendor/src/github.com/Sirupsen/logrus/text_formatter.go
@@ -46,6 +46,9 @@ type TextFormatter struct {
// the time passed since beginning of execution.
FullTimestamp bool
+ // 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
// that log extremely frequently and don't use the JSON formatter this may not
// be desired.
@@ -68,11 +71,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 +109,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.
diff --git a/vendor/src/github.com/docker/distribution/digest/digest.go b/vendor/src/github.com/docker/distribution/digest/digest.go
index d640026cb..689916859 100644
--- a/vendor/src/github.com/docker/distribution/digest/digest.go
+++ b/vendor/src/github.com/docker/distribution/digest/digest.go
@@ -2,7 +2,6 @@ package digest
import (
"bytes"
- "crypto/sha256"
"fmt"
"hash"
"io"
@@ -16,6 +15,7 @@ import (
const (
// DigestTarSumV1EmptyTar is the digest for the empty tar file.
DigestTarSumV1EmptyTar = "tarsum.v1+sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
+
// DigestSha256EmptyTar is the canonical sha256 digest of empty data
DigestSha256EmptyTar = "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
)
@@ -39,7 +39,7 @@ const (
type Digest string
// NewDigest returns a Digest from alg and a hash.Hash object.
-func NewDigest(alg string, h hash.Hash) Digest {
+func NewDigest(alg Algorithm, h hash.Hash) Digest {
return Digest(fmt.Sprintf("%s:%x", alg, h.Sum(nil)))
}
@@ -72,13 +72,13 @@ func ParseDigest(s string) (Digest, error) {
// FromReader returns the most valid digest for the underlying content.
func FromReader(rd io.Reader) (Digest, error) {
- h := sha256.New()
+ digester := Canonical.New()
- if _, err := io.Copy(h, rd); err != nil {
+ if _, err := io.Copy(digester.Hash(), rd); err != nil {
return "", err
}
- return NewDigest("sha256", h), nil
+ return digester.Digest(), nil
}
// FromTarArchive produces a tarsum digest from reader rd.
@@ -131,8 +131,8 @@ func (d Digest) Validate() error {
return ErrDigestInvalidFormat
}
- switch s[:i] {
- case "sha256", "sha384", "sha512":
+ switch Algorithm(s[:i]) {
+ case SHA256, SHA384, SHA512:
break
default:
return ErrDigestUnsupported
@@ -143,8 +143,8 @@ func (d Digest) Validate() error {
// Algorithm returns the algorithm portion of the digest. This will panic if
// the underlying digest is not in a valid format.
-func (d Digest) Algorithm() string {
- return string(d[:d.sepIndex()])
+func (d Digest) Algorithm() Algorithm {
+ return Algorithm(d[:d.sepIndex()])
}
// Hex returns the hex digest portion of the digest. This will panic if the
diff --git a/vendor/src/github.com/docker/distribution/digest/digest_test.go b/vendor/src/github.com/docker/distribution/digest/digest_test.go
index 9e9ae3566..41c8bee83 100644
--- a/vendor/src/github.com/docker/distribution/digest/digest_test.go
+++ b/vendor/src/github.com/docker/distribution/digest/digest_test.go
@@ -10,7 +10,7 @@ func TestParseDigest(t *testing.T) {
for _, testcase := range []struct {
input string
err error
- algorithm string
+ algorithm Algorithm
hex string
}{
{
diff --git a/vendor/src/github.com/docker/distribution/digest/digester.go b/vendor/src/github.com/docker/distribution/digest/digester.go
index 9094d662e..556dd93ae 100644
--- a/vendor/src/github.com/docker/distribution/digest/digester.go
+++ b/vendor/src/github.com/docker/distribution/digest/digester.go
@@ -1,44 +1,95 @@
package digest
import (
- "crypto/sha256"
+ "crypto"
"hash"
)
-// Digester calculates the digest of written data. It is functionally
-// equivalent to hash.Hash but provides methods for returning the Digest type
-// rather than raw bytes.
-type Digester struct {
- alg string
- hash hash.Hash
+// Algorithm identifies and implementation of a digester by an identifier.
+// Note the that this defines both the hash algorithm used and the string
+// encoding.
+type Algorithm string
+
+// supported digest types
+const (
+ SHA256 Algorithm = "sha256" // sha256 with hex encoding
+ SHA384 Algorithm = "sha384" // sha384 with hex encoding
+ SHA512 Algorithm = "sha512" // sha512 with hex encoding
+ TarsumV1SHA256 Algorithm = "tarsum+v1+sha256" // supported tarsum version, verification only
+
+ // Canonical is the primary digest algorithm used with the distribution
+ // project. Other digests may be used but this one is the primary storage
+ // digest.
+ Canonical = SHA256
+)
+
+var (
+ // TODO(stevvooe): Follow the pattern of the standard crypto package for
+ // registration of digests. Effectively, we are a registerable set and
+ // common symbol access.
+
+ // algorithms maps values to hash.Hash implementations. Other algorithms
+ // may be available but they cannot be calculated by the digest package.
+ algorithms = map[Algorithm]crypto.Hash{
+ SHA256: crypto.SHA256,
+ SHA384: crypto.SHA384,
+ SHA512: crypto.SHA512,
+ }
+)
+
+// Available returns true if the digest type is available for use. If this
+// returns false, New and Hash will return nil.
+func (a Algorithm) Available() bool {
+ h, ok := algorithms[a]
+ if !ok {
+ return false
+ }
+
+ // check availability of the hash, as well
+ return h.Available()
}
-// NewDigester create a new Digester with the given hashing algorithm and instance
-// of that algo's hasher.
-func NewDigester(alg string, h hash.Hash) Digester {
- return Digester{
- alg: alg,
- hash: h,
+// New returns a new digester for the specified algorithm. If the algorithm
+// does not have a digester implementation, nil will be returned. This can be
+// checked by calling Available before calling New.
+func (a Algorithm) New() Digester {
+ return &digester{
+ alg: a,
+ hash: a.Hash(),
}
}
-// NewCanonicalDigester is a convenience function to create a new Digester with
-// out default settings.
-func NewCanonicalDigester() Digester {
- return NewDigester("sha256", sha256.New())
+// Hash returns a new hash as used by the algorithm. If not available, nil is
+// returned. Make sure to check Available before calling.
+func (a Algorithm) Hash() hash.Hash {
+ if !a.Available() {
+ return nil
+ }
+
+ return algorithms[a].New()
}
-// Write data to the digester. These writes cannot fail.
-func (d *Digester) Write(p []byte) (n int, err error) {
- return d.hash.Write(p)
+// TODO(stevvooe): Allow resolution of verifiers using the digest type and
+// this registration system.
+
+// Digester calculates the digest of written data. Writes should go directly
+// to the return value of Hash, while calling Digest will return the current
+// value of the digest.
+type Digester interface {
+ Hash() hash.Hash // provides direct access to underlying hash instance.
+ Digest() Digest
}
-// Digest returns the current digest for this digester.
-func (d *Digester) Digest() Digest {
+// digester provides a simple digester definition that embeds a hasher.
+type digester struct {
+ alg Algorithm
+ hash hash.Hash
+}
+
+func (d *digester) Hash() hash.Hash {
+ return d.hash
+}
+
+func (d *digester) Digest() Digest {
return NewDigest(d.alg, d.hash)
}
-
-// Reset the state of the digester.
-func (d *Digester) Reset() {
- d.hash.Reset()
-}
diff --git a/vendor/src/github.com/docker/distribution/digest/digester_resumable_test.go b/vendor/src/github.com/docker/distribution/digest/digester_resumable_test.go
new file mode 100644
index 000000000..6ba21c801
--- /dev/null
+++ b/vendor/src/github.com/docker/distribution/digest/digester_resumable_test.go
@@ -0,0 +1,21 @@
+// +build !noresumabledigest
+
+package digest
+
+import (
+ "testing"
+
+ "github.com/stevvooe/resumable"
+ _ "github.com/stevvooe/resumable/sha256"
+)
+
+// TestResumableDetection just ensures that the resumable capability of a hash
+// is exposed through the digester type, which is just a hash plus a Digest
+// method.
+func TestResumableDetection(t *testing.T) {
+ d := Canonical.New()
+
+ if _, ok := d.Hash().(resumable.Hash); !ok {
+ t.Fatalf("expected digester to implement resumable.Hash: %#v, %v", d, d.Hash())
+ }
+}
diff --git a/vendor/src/github.com/docker/distribution/digest/set.go b/vendor/src/github.com/docker/distribution/digest/set.go
new file mode 100644
index 000000000..271d35dbf
--- /dev/null
+++ b/vendor/src/github.com/docker/distribution/digest/set.go
@@ -0,0 +1,195 @@
+package digest
+
+import (
+ "errors"
+ "sort"
+ "strings"
+)
+
+var (
+ // ErrDigestNotFound is used when a matching digest
+ // could not be found in a set.
+ ErrDigestNotFound = errors.New("digest not found")
+
+ // ErrDigestAmbiguous is used when multiple digests
+ // are found in a set. None of the matching digests
+ // should be considered valid matches.
+ ErrDigestAmbiguous = errors.New("ambiguous digest string")
+)
+
+// Set is used to hold a unique set of digests which
+// may be easily referenced by easily referenced by a string
+// representation of the digest as well as short representation.
+// The uniqueness of the short representation is based on other
+// digests in the set. If digests are ommited from this set,
+// collisions in a larger set may not be detected, therefore it
+// is important to always do short representation lookups on
+// the complete set of digests. To mitigate collisions, an
+// appropriately long short code should be used.
+type Set struct {
+ entries digestEntries
+}
+
+// NewSet creates an empty set of digests
+// which may have digests added.
+func NewSet() *Set {
+ return &Set{
+ entries: digestEntries{},
+ }
+}
+
+// checkShortMatch checks whether two digests match as either whole
+// values or short values. This function does not test equality,
+// rather whether the second value could match against the first
+// value.
+func checkShortMatch(alg Algorithm, hex, shortAlg, shortHex string) bool {
+ if len(hex) == len(shortHex) {
+ if hex != shortHex {
+ return false
+ }
+ if len(shortAlg) > 0 && string(alg) != shortAlg {
+ return false
+ }
+ } else if !strings.HasPrefix(hex, shortHex) {
+ return false
+ } else if len(shortAlg) > 0 && string(alg) != shortAlg {
+ return false
+ }
+ return true
+}
+
+// Lookup looks for a digest matching the given string representation.
+// If no digests could be found ErrDigestNotFound will be returned
+// with an empty digest value. If multiple matches are found
+// ErrDigestAmbiguous will be returned with an empty digest value.
+func (dst *Set) Lookup(d string) (Digest, error) {
+ if len(dst.entries) == 0 {
+ return "", ErrDigestNotFound
+ }
+ var (
+ searchFunc func(int) bool
+ alg Algorithm
+ hex string
+ )
+ dgst, err := ParseDigest(d)
+ if err == ErrDigestInvalidFormat {
+ hex = d
+ searchFunc = func(i int) bool {
+ return dst.entries[i].val >= d
+ }
+ } else {
+ hex = dgst.Hex()
+ alg = dgst.Algorithm()
+ searchFunc = func(i int) bool {
+ if dst.entries[i].val == hex {
+ return dst.entries[i].alg >= alg
+ }
+ return dst.entries[i].val >= hex
+ }
+ }
+ idx := sort.Search(len(dst.entries), searchFunc)
+ if idx == len(dst.entries) || !checkShortMatch(dst.entries[idx].alg, dst.entries[idx].val, string(alg), hex) {
+ return "", ErrDigestNotFound
+ }
+ if dst.entries[idx].alg == alg && dst.entries[idx].val == hex {
+ return dst.entries[idx].digest, nil
+ }
+ if idx+1 < len(dst.entries) && checkShortMatch(dst.entries[idx+1].alg, dst.entries[idx+1].val, string(alg), hex) {
+ return "", ErrDigestAmbiguous
+ }
+
+ return dst.entries[idx].digest, nil
+}
+
+// Add adds the given digests to the set. An error will be returned
+// if the given digest is invalid. If the digest already exists in the
+// table, this operation will be a no-op.
+func (dst *Set) Add(d Digest) error {
+ if err := d.Validate(); err != nil {
+ return err
+ }
+ entry := &digestEntry{alg: d.Algorithm(), val: d.Hex(), digest: d}
+ searchFunc := func(i int) bool {
+ if dst.entries[i].val == entry.val {
+ return dst.entries[i].alg >= entry.alg
+ }
+ return dst.entries[i].val >= entry.val
+ }
+ idx := sort.Search(len(dst.entries), searchFunc)
+ if idx == len(dst.entries) {
+ dst.entries = append(dst.entries, entry)
+ return nil
+ } else if dst.entries[idx].digest == d {
+ return nil
+ }
+
+ entries := append(dst.entries, nil)
+ copy(entries[idx+1:], entries[idx:len(entries)-1])
+ entries[idx] = entry
+ dst.entries = entries
+ return nil
+}
+
+// ShortCodeTable returns a map of Digest to unique short codes. The
+// length represents the minimum value, the maximum length may be the
+// entire value of digest if uniqueness cannot be achieved without the
+// full value. This function will attempt to make short codes as short
+// as possible to be unique.
+func ShortCodeTable(dst *Set, length int) map[Digest]string {
+ m := make(map[Digest]string, len(dst.entries))
+ l := length
+ resetIdx := 0
+ for i := 0; i < len(dst.entries); i++ {
+ var short string
+ extended := true
+ for extended {
+ extended = false
+ if len(dst.entries[i].val) <= l {
+ short = dst.entries[i].digest.String()
+ } else {
+ short = dst.entries[i].val[:l]
+ for j := i + 1; j < len(dst.entries); j++ {
+ if checkShortMatch(dst.entries[j].alg, dst.entries[j].val, "", short) {
+ if j > resetIdx {
+ resetIdx = j
+ }
+ extended = true
+ } else {
+ break
+ }
+ }
+ if extended {
+ l++
+ }
+ }
+ }
+ m[dst.entries[i].digest] = short
+ if i >= resetIdx {
+ l = length
+ }
+ }
+ return m
+}
+
+type digestEntry struct {
+ alg Algorithm
+ val string
+ digest Digest
+}
+
+type digestEntries []*digestEntry
+
+func (d digestEntries) Len() int {
+ return len(d)
+}
+
+func (d digestEntries) Less(i, j int) bool {
+ if d[i].val != d[j].val {
+ return d[i].val < d[j].val
+ }
+ return d[i].alg < d[j].alg
+}
+
+func (d digestEntries) Swap(i, j int) {
+ d[i], d[j] = d[j], d[i]
+}
diff --git a/vendor/src/github.com/docker/distribution/digest/set_test.go b/vendor/src/github.com/docker/distribution/digest/set_test.go
new file mode 100644
index 000000000..faeba6d32
--- /dev/null
+++ b/vendor/src/github.com/docker/distribution/digest/set_test.go
@@ -0,0 +1,272 @@
+package digest
+
+import (
+ "crypto/sha256"
+ "encoding/binary"
+ "math/rand"
+ "testing"
+)
+
+func assertEqualDigests(t *testing.T, d1, d2 Digest) {
+ if d1 != d2 {
+ t.Fatalf("Digests do not match:\n\tActual: %s\n\tExpected: %s", d1, d2)
+ }
+}
+
+func TestLookup(t *testing.T) {
+ digests := []Digest{
+ "sha256:12345",
+ "sha256:1234",
+ "sha256:12346",
+ "sha256:54321",
+ "sha256:65431",
+ "sha256:64321",
+ "sha256:65421",
+ "sha256:65321",
+ }
+
+ dset := NewSet()
+ for i := range digests {
+ if err := dset.Add(digests[i]); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ dgst, err := dset.Lookup("54")
+ if err != nil {
+ t.Fatal(err)
+ }
+ assertEqualDigests(t, dgst, digests[3])
+
+ dgst, err = dset.Lookup("1234")
+ if err == nil {
+ t.Fatal("Expected ambiguous error looking up: 1234")
+ }
+ if err != ErrDigestAmbiguous {
+ t.Fatal(err)
+ }
+
+ dgst, err = dset.Lookup("9876")
+ if err == nil {
+ t.Fatal("Expected ambiguous error looking up: 9876")
+ }
+ if err != ErrDigestNotFound {
+ t.Fatal(err)
+ }
+
+ dgst, err = dset.Lookup("sha256:1234")
+ if err != nil {
+ t.Fatal(err)
+ }
+ assertEqualDigests(t, dgst, digests[1])
+
+ dgst, err = dset.Lookup("sha256:12345")
+ if err != nil {
+ t.Fatal(err)
+ }
+ assertEqualDigests(t, dgst, digests[0])
+
+ dgst, err = dset.Lookup("sha256:12346")
+ if err != nil {
+ t.Fatal(err)
+ }
+ assertEqualDigests(t, dgst, digests[2])
+
+ dgst, err = dset.Lookup("12346")
+ if err != nil {
+ t.Fatal(err)
+ }
+ assertEqualDigests(t, dgst, digests[2])
+
+ dgst, err = dset.Lookup("12345")
+ if err != nil {
+ t.Fatal(err)
+ }
+ assertEqualDigests(t, dgst, digests[0])
+}
+
+func TestAddDuplication(t *testing.T) {
+ digests := []Digest{
+ "sha256:1234",
+ "sha256:12345",
+ "sha256:12346",
+ "sha256:54321",
+ "sha256:65431",
+ "sha512:65431",
+ "sha512:65421",
+ "sha512:65321",
+ }
+
+ dset := NewSet()
+ for i := range digests {
+ if err := dset.Add(digests[i]); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ if len(dset.entries) != 8 {
+ t.Fatal("Invalid dset size")
+ }
+
+ if err := dset.Add(Digest("sha256:12345")); err != nil {
+ t.Fatal(err)
+ }
+
+ if len(dset.entries) != 8 {
+ t.Fatal("Duplicate digest insert allowed")
+ }
+
+ if err := dset.Add(Digest("sha384:12345")); err != nil {
+ t.Fatal(err)
+ }
+
+ if len(dset.entries) != 9 {
+ t.Fatal("Insert with different algorithm not allowed")
+ }
+}
+
+func assertEqualShort(t *testing.T, actual, expected string) {
+ if actual != expected {
+ t.Fatalf("Unexpected short value:\n\tExpected: %s\n\tActual: %s", expected, actual)
+ }
+}
+
+func TestShortCodeTable(t *testing.T) {
+ digests := []Digest{
+ "sha256:1234",
+ "sha256:12345",
+ "sha256:12346",
+ "sha256:54321",
+ "sha256:65431",
+ "sha256:64321",
+ "sha256:65421",
+ "sha256:65321",
+ }
+
+ dset := NewSet()
+ for i := range digests {
+ if err := dset.Add(digests[i]); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ dump := ShortCodeTable(dset, 2)
+
+ if len(dump) < len(digests) {
+ t.Fatalf("Error unexpected size: %d, expecting %d", len(dump), len(digests))
+ }
+
+ assertEqualShort(t, dump[digests[0]], "sha256:1234")
+ assertEqualShort(t, dump[digests[1]], "sha256:12345")
+ assertEqualShort(t, dump[digests[2]], "sha256:12346")
+ assertEqualShort(t, dump[digests[3]], "54")
+ assertEqualShort(t, dump[digests[4]], "6543")
+ assertEqualShort(t, dump[digests[5]], "64")
+ assertEqualShort(t, dump[digests[6]], "6542")
+ assertEqualShort(t, dump[digests[7]], "653")
+}
+
+func createDigests(count int) ([]Digest, error) {
+ r := rand.New(rand.NewSource(25823))
+ digests := make([]Digest, count)
+ for i := range digests {
+ h := sha256.New()
+ if err := binary.Write(h, binary.BigEndian, r.Int63()); err != nil {
+ return nil, err
+ }
+ digests[i] = NewDigest("sha256", h)
+ }
+ return digests, nil
+}
+
+func benchAddNTable(b *testing.B, n int) {
+ digests, err := createDigests(n)
+ if err != nil {
+ b.Fatal(err)
+ }
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
+ for j := range digests {
+ if err = dset.Add(digests[j]); err != nil {
+ b.Fatal(err)
+ }
+ }
+ }
+}
+
+func benchLookupNTable(b *testing.B, n int, shortLen int) {
+ digests, err := createDigests(n)
+ if err != nil {
+ b.Fatal(err)
+ }
+ dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
+ for i := range digests {
+ if err := dset.Add(digests[i]); err != nil {
+ b.Fatal(err)
+ }
+ }
+ shorts := make([]string, 0, n)
+ for _, short := range ShortCodeTable(dset, shortLen) {
+ shorts = append(shorts, short)
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ if _, err = dset.Lookup(shorts[i%n]); err != nil {
+ b.Fatal(err)
+ }
+ }
+}
+
+func benchShortCodeNTable(b *testing.B, n int, shortLen int) {
+ digests, err := createDigests(n)
+ if err != nil {
+ b.Fatal(err)
+ }
+ dset := &Set{entries: digestEntries(make([]*digestEntry, 0, n))}
+ for i := range digests {
+ if err := dset.Add(digests[i]); err != nil {
+ b.Fatal(err)
+ }
+ }
+
+ b.ResetTimer()
+ for i := 0; i < b.N; i++ {
+ ShortCodeTable(dset, shortLen)
+ }
+}
+
+func BenchmarkAdd10(b *testing.B) {
+ benchAddNTable(b, 10)
+}
+
+func BenchmarkAdd100(b *testing.B) {
+ benchAddNTable(b, 100)
+}
+
+func BenchmarkAdd1000(b *testing.B) {
+ benchAddNTable(b, 1000)
+}
+
+func BenchmarkLookup10(b *testing.B) {
+ benchLookupNTable(b, 10, 12)
+}
+
+func BenchmarkLookup100(b *testing.B) {
+ benchLookupNTable(b, 100, 12)
+}
+
+func BenchmarkLookup1000(b *testing.B) {
+ benchLookupNTable(b, 1000, 12)
+}
+
+func BenchmarkShortCode10(b *testing.B) {
+ benchShortCodeNTable(b, 10, 12)
+}
+func BenchmarkShortCode100(b *testing.B) {
+ benchShortCodeNTable(b, 100, 12)
+}
+func BenchmarkShortCode1000(b *testing.B) {
+ benchShortCodeNTable(b, 1000, 12)
+}
diff --git a/vendor/src/github.com/docker/distribution/digest/tarsum.go b/vendor/src/github.com/docker/distribution/digest/tarsum.go
index acf878b62..702d7dc3f 100644
--- a/vendor/src/github.com/docker/distribution/digest/tarsum.go
+++ b/vendor/src/github.com/docker/distribution/digest/tarsum.go
@@ -6,10 +6,10 @@ import (
"regexp"
)
-// TarSumRegexp defines a reguler expression to match tarsum identifiers.
+// TarSumRegexp defines a regular expression to match tarsum identifiers.
var TarsumRegexp = regexp.MustCompile("tarsum(?:.[a-z0-9]+)?\\+[a-zA-Z0-9]+:[A-Fa-f0-9]+")
-// TarsumRegexpCapturing defines a reguler expression to match tarsum identifiers with
+// TarsumRegexpCapturing defines a regular expression to match tarsum identifiers with
// capture groups corresponding to each component.
var TarsumRegexpCapturing = regexp.MustCompile("(tarsum)(.([a-z0-9]+))?\\+([a-zA-Z0-9]+):([A-Fa-f0-9]+)")
diff --git a/vendor/src/github.com/docker/distribution/digest/verifiers.go b/vendor/src/github.com/docker/distribution/digest/verifiers.go
index 11d9d7ae5..f8c75b53d 100644
--- a/vendor/src/github.com/docker/distribution/digest/verifiers.go
+++ b/vendor/src/github.com/docker/distribution/digest/verifiers.go
@@ -1,8 +1,6 @@
package digest
import (
- "crypto/sha256"
- "crypto/sha512"
"hash"
"io"
"io/ioutil"
@@ -33,7 +31,7 @@ func NewDigestVerifier(d Digest) (Verifier, error) {
switch alg {
case "sha256", "sha384", "sha512":
return hashVerifier{
- hash: newHash(alg),
+ hash: alg.Hash(),
digest: d,
}, nil
default:
@@ -95,19 +93,6 @@ func (lv *lengthVerifier) Verified() bool {
return lv.expected == lv.len
}
-func newHash(name string) hash.Hash {
- switch name {
- case "sha256":
- return sha256.New()
- case "sha384":
- return sha512.New384()
- case "sha512":
- return sha512.New()
- default:
- panic("unsupport algorithm: " + name)
- }
-}
-
type hashVerifier struct {
digest Digest
hash hash.Hash
diff --git a/vendor/src/github.com/docker/distribution/digest/verifiers_test.go b/vendor/src/github.com/docker/distribution/digest/verifiers_test.go
index 408720b5e..5ee79f347 100644
--- a/vendor/src/github.com/docker/distribution/digest/verifiers_test.go
+++ b/vendor/src/github.com/docker/distribution/digest/verifiers_test.go
@@ -80,7 +80,7 @@ func TestVerifierUnsupportedDigest(t *testing.T) {
}
if err != ErrDigestUnsupported {
- t.Fatalf("incorrect error for unsupported digest: %v %p %p", err, ErrDigestUnsupported, err)
+ t.Fatalf("incorrect error for unsupported digest: %v", err)
}
}
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..d7c4a880c
--- /dev/null
+++ b/vendor/src/github.com/docker/distribution/registry/api/v2/descriptors.go
@@ -0,0 +1,1493 @@
+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 characters that match `[a-zA-Z0-9-_.=]+`.",
+ }
+
+ 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: "