mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-21 13:18:00 +00:00
Compare commits
111 Commits
os-release
..
docs
| Author | SHA1 | Date | |
|---|---|---|---|
| 942363d3ca | |||
| 44f2104028 | |||
| 6117b05e87 | |||
| 7d7ef1188b | |||
| c6d31c934b | |||
| 8df037b7ca | |||
| 72b8e7e3ac | |||
| f9f2dbcb15 | |||
| 3d18cd851b | |||
| 7324d8dd8c | |||
| a3ae93dc2e | |||
| 385676735b | |||
| 3e2e0397f3 | |||
| 512f486e0c | |||
| 745722293d | |||
| 87f58529b2 | |||
| 8f7cdbb060 | |||
| 172c412ae8 | |||
| 67baebe9e7 | |||
| e1eee1290d | |||
| d3fe81f9ba | |||
| 551c4559ad | |||
| 2eaa994b24 | |||
| e9183b35a3 | |||
| 19c08cd7f4 | |||
| e6d84bddc4 | |||
| f5ab4142be | |||
| 7d71532829 | |||
| da5014009c | |||
| 3a2e6b3871 | |||
| e041a26365 | |||
| b7960c1e45 | |||
| 0d03096b65 | |||
| 55e9551aaa | |||
| c65afe6ba8 | |||
| 5745aaed22 | |||
| b6f0f93c94 | |||
| 33b16fef43 | |||
| 9705c349c5 | |||
| 3de34af5d1 | |||
| b3f3abfc94 | |||
| 783baec49c | |||
| c1d9e7c6fb | |||
| 83f6dbe30a | |||
| a97b89b585 | |||
| 29ea36a880 | |||
| ed672d1609 | |||
| 5916664220 | |||
| da4b336233 | |||
| 74df05ccaa | |||
| 2c875215b1 | |||
| be40a48c12 | |||
| 85f7f7cfc7 | |||
| e15f6fca3f | |||
| d3bbaa70cd | |||
| cc6f6cb2e2 | |||
| c967dd289f | |||
| 7895ec25ea | |||
| 5b06c94701 | |||
| 5851e2da60 | |||
| 9eff33735a | |||
| fc7697b050 | |||
| 1bf8954d0d | |||
| dfd9f5989a | |||
| d9581e861d | |||
| c383ceaf37 | |||
| 948912f692 | |||
| d19b1b927b | |||
| 53f5905379 | |||
| 60cbf4da6c | |||
| 183628388c | |||
| fbd2267e7d | |||
| a16ab243e5 | |||
| b3c3c4cddc | |||
| 0fe5aad984 | |||
| 0f5e2fd479 | |||
| 2f7145b1c5 | |||
| 81efe1f32e | |||
| 5ba75ac343 | |||
| 290987fcb4 | |||
| 98855c863d | |||
| b1f394a247 | |||
| a819a60a94 | |||
| 33cdc7f2c4 | |||
| 117860577c | |||
| b0ac5df367 | |||
| c109095a58 | |||
| d394113dfe | |||
| 2af7f63173 | |||
| f156fb7be5 | |||
| 559043b953 | |||
| ba8abcb3dd | |||
| ebf396c6e8 | |||
| 47d52fb872 | |||
| d167338876 | |||
| e6844381f0 | |||
| 589922adf0 | |||
| 689c4e6075 | |||
| 43da1adedb | |||
| 686fe02020 | |||
| 1d02be1c7a | |||
| edb60b950a | |||
| e0e852ee6f | |||
| b537508f8c | |||
| 37e886eb7b | |||
| 50f65742ef | |||
| 56d859d052 | |||
| 546a704c63 | |||
| fa85dc0030 | |||
| 36b6e5884d | |||
| 90991ddb9b |
+1
-2
@@ -30,9 +30,8 @@ docs/_build
|
|||||||
docs/_static
|
docs/_static
|
||||||
docs/_templates
|
docs/_templates
|
||||||
docs/changed-files
|
docs/changed-files
|
||||||
# generated by man/md2man-all.sh
|
# generated by man/man/md2man-all.sh
|
||||||
man/man1
|
man/man1
|
||||||
man/man5
|
man/man5
|
||||||
man/man8
|
|
||||||
pyenv
|
pyenv
|
||||||
vendor/pkg/
|
vendor/pkg/
|
||||||
|
|||||||
+2
-43
@@ -1,46 +1,5 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## 1.8.3 (2015-10-12)
|
|
||||||
|
|
||||||
### Distribution
|
|
||||||
|
|
||||||
- Fix layer IDs lead to local graph poisoning (CVE-2014-8178)
|
|
||||||
- Fix manifest validation and parsing logic errors allow pull-by-digest validation bypass (CVE-2014-8179)
|
|
||||||
+ Add `--disable-legacy-registry` to prevent a daemon from using a v1 registry
|
|
||||||
|
|
||||||
## 1.8.2 (2015-09-10)
|
|
||||||
|
|
||||||
### Distribution
|
|
||||||
|
|
||||||
- Fixes rare edge case of handling GNU LongLink and LongName entries.
|
|
||||||
- Fix ^C on docker pull.
|
|
||||||
- Fix docker pull issues on client disconnection.
|
|
||||||
- Fix issue that caused the daemon to panic when loggers weren't configured properly.
|
|
||||||
- Fix goroutine leak pulling images from registry V2.
|
|
||||||
|
|
||||||
### Runtime
|
|
||||||
|
|
||||||
- Fix a bug mounting cgroups for docker daemons running inside docker containers.
|
|
||||||
- Initialize log configuration properly.
|
|
||||||
|
|
||||||
### Client:
|
|
||||||
|
|
||||||
- Handle `-q` flag in `docker ps` properly when there is a default format.
|
|
||||||
|
|
||||||
### Networking
|
|
||||||
|
|
||||||
- Fix several corner cases with netlink.
|
|
||||||
|
|
||||||
### Contrib
|
|
||||||
|
|
||||||
- Fix several issues with bash completion.
|
|
||||||
|
|
||||||
## 1.8.1 (2015-08-12)
|
|
||||||
|
|
||||||
### Distribution
|
|
||||||
|
|
||||||
* Fix a bug where pushing multiple tags would result in invalid images
|
|
||||||
|
|
||||||
## 1.8.0 (2015-08-11)
|
## 1.8.0 (2015-08-11)
|
||||||
|
|
||||||
### Distribution
|
### Distribution
|
||||||
@@ -77,7 +36,7 @@
|
|||||||
+ Add logging driver for GELF via UDP
|
+ Add logging driver for GELF via UDP
|
||||||
+ Allow to copy files from host to containers with `docker cp`
|
+ Allow to copy files from host to containers with `docker cp`
|
||||||
+ Promote volume drivers from experimental to master
|
+ Promote volume drivers from experimental to master
|
||||||
+ Add rollover options to json-file log driver, and --log-driver-opts flag
|
+ Add rollover log driver, and --log-driver-opts flag
|
||||||
+ Add memory swappiness tuning options
|
+ Add memory swappiness tuning options
|
||||||
* Remove cgroup read-only flag when privileged
|
* Remove cgroup read-only flag when privileged
|
||||||
* Make /proc, /sys, & /dev readonly for readonly containers
|
* Make /proc, /sys, & /dev readonly for readonly containers
|
||||||
@@ -98,7 +57,7 @@
|
|||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
- Add missing 'Names' field to /containers/json API output
|
- Add missing 'Names' field to /containers/json API output
|
||||||
- Make `docker rmi` of dangling images safe while pulling
|
- Make `docker rmi --dangling` safe when pulling
|
||||||
- Devicemapper: Change default basesize to 100G
|
- Devicemapper: Change default basesize to 100G
|
||||||
- Go Scheduler issue with sync.Mutex and gcc
|
- Go Scheduler issue with sync.Mutex and gcc
|
||||||
- Fix issue where Search API endpoint would panic due to empty AuthConfig
|
- Fix issue where Search API endpoint would panic due to empty AuthConfig
|
||||||
|
|||||||
+4
-7
@@ -25,7 +25,7 @@ The Docker maintainers take security seriously. If you discover a security
|
|||||||
issue, please bring it to their attention right away!
|
issue, please bring it to their attention right away!
|
||||||
|
|
||||||
Please **DO NOT** file a public issue, instead send your report privately to
|
Please **DO NOT** file a public issue, instead send your report privately to
|
||||||
[security@docker.com](mailto:security@docker.com).
|
[security@docker.com](mailto:security@docker.com),
|
||||||
|
|
||||||
Security reports are greatly appreciated and we will publicly thank you for it.
|
Security reports are greatly appreciated and we will publicly thank you for it.
|
||||||
We also like to send gifts—if you're into Docker schwag, make sure to let
|
We also like to send gifts—if you're into Docker schwag, make sure to let
|
||||||
@@ -42,17 +42,14 @@ and will thank you for it!
|
|||||||
Check that [our issue database](https://github.com/docker/docker/issues)
|
Check that [our issue database](https://github.com/docker/docker/issues)
|
||||||
doesn't already include that problem or suggestion before submitting an issue.
|
doesn't already include that problem or suggestion before submitting an issue.
|
||||||
If you find a match, add a quick "+1" or "I have this problem too." Doing this
|
If you find a match, add a quick "+1" or "I have this problem too." Doing this
|
||||||
helps prioritize the most common problems and requests. **DO NOT DO THAT** to
|
helps prioritize the most common problems and requests.
|
||||||
subscribe to the issue unless you have something meaningful to add to the
|
|
||||||
conversation. The best way to subscribe the issue is by clicking Subscribe
|
|
||||||
button in top right of the page.
|
|
||||||
|
|
||||||
When reporting issues, please include your host OS (Ubuntu 12.04, Fedora 19,
|
When reporting issues, please include your host OS (Ubuntu 12.04, Fedora 19,
|
||||||
etc). Please include:
|
etc). Please include:
|
||||||
|
|
||||||
* The output of `uname -a`.
|
* The output of `uname -a`.
|
||||||
* The output of `docker version`.
|
* The output of `docker version`.
|
||||||
* The output of `docker info`.
|
* The output of `docker -D info`.
|
||||||
|
|
||||||
Please also include the steps required to reproduce the problem if possible and
|
Please also include the steps required to reproduce the problem if possible and
|
||||||
applicable. This information will help us review and fix your issue faster.
|
applicable. This information will help us review and fix your issue faster.
|
||||||
@@ -227,7 +224,7 @@ high majority of submissions should have a single commit, so if in doubt: squash
|
|||||||
down to one.
|
down to one.
|
||||||
|
|
||||||
After every commit, [make sure the test suite passes]
|
After every commit, [make sure the test suite passes]
|
||||||
(https://docs.docker.com/project/test-and-docs/). Include documentation
|
((https://docs.docker.com/project/test-and-docs/)). Include documentation
|
||||||
changes in the same pull request so that a revert would remove all traces of
|
changes in the same pull request so that a revert would remove all traces of
|
||||||
the feature or fix.
|
the feature or fix.
|
||||||
|
|
||||||
|
|||||||
+3
-6
@@ -40,16 +40,13 @@ RUN apt-get update && apt-get install -y \
|
|||||||
createrepo \
|
createrepo \
|
||||||
curl \
|
curl \
|
||||||
dpkg-sig \
|
dpkg-sig \
|
||||||
gcc-mingw-w64 \
|
|
||||||
git \
|
git \
|
||||||
iptables \
|
iptables \
|
||||||
libapparmor-dev \
|
libapparmor-dev \
|
||||||
libcap-dev \
|
libcap-dev \
|
||||||
libsqlite3-dev \
|
libsqlite3-dev \
|
||||||
libsystemd-journal-dev \
|
|
||||||
mercurial \
|
mercurial \
|
||||||
parallel \
|
parallel \
|
||||||
pkg-config \
|
|
||||||
python-mock \
|
python-mock \
|
||||||
python-pip \
|
python-pip \
|
||||||
python-websocket \
|
python-websocket \
|
||||||
@@ -83,7 +80,7 @@ RUN cd /usr/src/lxc \
|
|||||||
&& ldconfig
|
&& ldconfig
|
||||||
|
|
||||||
# Install Go
|
# Install Go
|
||||||
ENV GO_VERSION 1.4.3
|
ENV GO_VERSION 1.4.2
|
||||||
RUN curl -sSL https://golang.org/dl/go${GO_VERSION}.src.tar.gz | tar -v -C /usr/local -xz \
|
RUN curl -sSL https://golang.org/dl/go${GO_VERSION}.src.tar.gz | tar -v -C /usr/local -xz \
|
||||||
&& mkdir -p /go/bin
|
&& mkdir -p /go/bin
|
||||||
ENV PATH /go/bin:/usr/local/go/bin:$PATH
|
ENV PATH /go/bin:/usr/local/go/bin:$PATH
|
||||||
@@ -130,7 +127,7 @@ RUN git clone https://github.com/golang/lint.git /go/src/github.com/golang/lint
|
|||||||
RUN gem install --no-rdoc --no-ri fpm --version 1.3.2
|
RUN gem install --no-rdoc --no-ri fpm --version 1.3.2
|
||||||
|
|
||||||
# Install registry
|
# Install registry
|
||||||
ENV REGISTRY_COMMIT ec87e9b6971d831f0eff752ddb54fb64693e51cd
|
ENV REGISTRY_COMMIT 2317f721a3d8428215a2b65da4ae85212ed473b4
|
||||||
RUN set -x \
|
RUN set -x \
|
||||||
&& export GOPATH="$(mktemp -d)" \
|
&& export GOPATH="$(mktemp -d)" \
|
||||||
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
|
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
|
||||||
@@ -150,7 +147,7 @@ RUN set -x \
|
|||||||
&& rm -rf "$GOPATH"
|
&& rm -rf "$GOPATH"
|
||||||
|
|
||||||
# Get the "docker-py" source so we can run their integration tests
|
# Get the "docker-py" source so we can run their integration tests
|
||||||
ENV DOCKER_PY_COMMIT 139850f3f3b17357bab5ba3edfb745fb14043764
|
ENV DOCKER_PY_COMMIT 8a87001d09852058f08a807ab6e8491d57ca1e88
|
||||||
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
||||||
&& cd /docker-py \
|
&& cd /docker-py \
|
||||||
&& git checkout -q $DOCKER_PY_COMMIT
|
&& git checkout -q $DOCKER_PY_COMMIT
|
||||||
|
|||||||
@@ -1,72 +0,0 @@
|
|||||||
# This file describes the standard way to build Docker, using docker
|
|
||||||
#
|
|
||||||
# Usage:
|
|
||||||
#
|
|
||||||
# # Assemble the full dev environment. This is slow the first time.
|
|
||||||
# docker build -t docker -f Dockerfile.gccgo .
|
|
||||||
#
|
|
||||||
|
|
||||||
FROM gcc:5.2
|
|
||||||
|
|
||||||
# Packaged dependencies
|
|
||||||
RUN apt-get update && apt-get install -y \
|
|
||||||
apparmor \
|
|
||||||
aufs-tools \
|
|
||||||
btrfs-tools \
|
|
||||||
build-essential \
|
|
||||||
curl \
|
|
||||||
git \
|
|
||||||
iptables \
|
|
||||||
net-tools \
|
|
||||||
libapparmor-dev \
|
|
||||||
libcap-dev \
|
|
||||||
libsqlite3-dev \
|
|
||||||
mercurial \
|
|
||||||
parallel \
|
|
||||||
python-mock \
|
|
||||||
python-pip \
|
|
||||||
python-websocket \
|
|
||||||
--no-install-recommends
|
|
||||||
|
|
||||||
# Get lvm2 source for compiling statically
|
|
||||||
RUN git clone -b v2_02_103 https://git.fedorahosted.org/git/lvm2.git /usr/local/lvm2
|
|
||||||
# see https://git.fedorahosted.org/cgit/lvm2.git/refs/tags for release tags
|
|
||||||
|
|
||||||
# Compile and install lvm2
|
|
||||||
RUN cd /usr/local/lvm2 \
|
|
||||||
&& ./configure --enable-static_link \
|
|
||||||
&& make device-mapper \
|
|
||||||
&& make install_device-mapper
|
|
||||||
# see https://git.fedorahosted.org/cgit/lvm2.git/tree/INSTALL
|
|
||||||
|
|
||||||
# Install lxc
|
|
||||||
ENV LXC_VERSION 1.1.2
|
|
||||||
RUN mkdir -p /usr/src/lxc \
|
|
||||||
&& curl -sSL https://linuxcontainers.org/downloads/lxc/lxc-${LXC_VERSION}.tar.gz | tar -v -C /usr/src/lxc/ -xz --strip-components=1
|
|
||||||
RUN cd /usr/src/lxc \
|
|
||||||
&& ./configure \
|
|
||||||
&& make \
|
|
||||||
&& make install \
|
|
||||||
&& ldconfig
|
|
||||||
|
|
||||||
ENV GOPATH /go:/go/src/github.com/docker/docker/vendor
|
|
||||||
|
|
||||||
# Get the "docker-py" source so we can run their integration tests
|
|
||||||
ENV DOCKER_PY_COMMIT 139850f3f3b17357bab5ba3edfb745fb14043764
|
|
||||||
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
|
||||||
&& cd /docker-py \
|
|
||||||
&& git checkout -q $DOCKER_PY_COMMIT
|
|
||||||
|
|
||||||
# Add an unprivileged user to be used for tests which need it
|
|
||||||
RUN groupadd -r docker
|
|
||||||
RUN useradd --create-home --gid docker unprivilegeduser
|
|
||||||
|
|
||||||
VOLUME /var/lib/docker
|
|
||||||
WORKDIR /go/src/github.com/docker/docker
|
|
||||||
ENV DOCKER_BUILDTAGS apparmor selinux
|
|
||||||
|
|
||||||
# Wrap all commands in the "docker-in-docker" script to allow nested containers
|
|
||||||
ENTRYPOINT ["hack/dind"]
|
|
||||||
|
|
||||||
# Upload docker source
|
|
||||||
COPY . /go/src/github.com/docker/docker
|
|
||||||
+47
-28
@@ -141,7 +141,7 @@ editor, and thus asking them to `git commit --amend -s` is not the best way forw
|
|||||||
In this case, maintainers can update the commits based on clause (c) of the DCO. The
|
In this case, maintainers can update the commits based on clause (c) of the DCO. The
|
||||||
most trivial way for a contributor to allow the maintainer to do this, is to add
|
most trivial way for a contributor to allow the maintainer to do this, is to add
|
||||||
a DCO signature in a Pull Requests's comment, or a maintainer can simply note that
|
a DCO signature in a Pull Requests's comment, or a maintainer can simply note that
|
||||||
the change is sufficiently trivial that it does not substantially change the existing
|
the change is sufficiently trivial that it does not substantivly change the existing
|
||||||
contribution - i.e., a spelling change.
|
contribution - i.e., a spelling change.
|
||||||
|
|
||||||
When you add someone's DCO, please also add your own to keep a log.
|
When you add someone's DCO, please also add your own to keep a log.
|
||||||
@@ -183,6 +183,16 @@ made through a pull request.
|
|||||||
# be approved by the chief architect.
|
# be approved by the chief architect.
|
||||||
"Chief Architect" = "shykes"
|
"Chief Architect" = "shykes"
|
||||||
|
|
||||||
|
# The Chief Operator is responsible for the day-to-day operations of the project including:
|
||||||
|
# - facilitating communications amongst all the contributors;
|
||||||
|
# - tracking release schedules;
|
||||||
|
# - managing the relationship with downstream distributions and upstream dependencies;
|
||||||
|
# - helping new contributors to get involved and become successful contributors and maintainers
|
||||||
|
#
|
||||||
|
# The role is also responsible for managing and measuring the success of the overall project
|
||||||
|
# and ensuring it is governed properly working in concert with the Docker Governance Advisory Board (DGAB).
|
||||||
|
"Chief Operator" = "spf13"
|
||||||
|
|
||||||
[Org.Operators]
|
[Org.Operators]
|
||||||
|
|
||||||
# The operators make sure the trains run on time. They are responsible for overall operations
|
# The operators make sure the trains run on time. They are responsible for overall operations
|
||||||
@@ -254,6 +264,8 @@ made through a pull request.
|
|||||||
#
|
#
|
||||||
# It is common for core maintainers to "branch out" to join or start a subsystem.
|
# It is common for core maintainers to "branch out" to join or start a subsystem.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
people = [
|
people = [
|
||||||
"calavera",
|
"calavera",
|
||||||
"crosbymichael",
|
"crosbymichael",
|
||||||
@@ -266,11 +278,11 @@ made through a pull request.
|
|||||||
"tibor",
|
"tibor",
|
||||||
"unclejack",
|
"unclejack",
|
||||||
"vbatts",
|
"vbatts",
|
||||||
"vdemeester",
|
|
||||||
"vieux",
|
"vieux",
|
||||||
"vishh"
|
"vishh"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
[Org.Subsystems]
|
[Org.Subsystems]
|
||||||
|
|
||||||
# As the project grows, it gets separated into well-defined subsystems. Each subsystem
|
# As the project grows, it gets separated into well-defined subsystems. Each subsystem
|
||||||
@@ -297,7 +309,7 @@ made through a pull request.
|
|||||||
# - If the pull request has some small problems that need to be changed, make
|
# - If the pull request has some small problems that need to be changed, make
|
||||||
# a comment adressing the issues.
|
# a comment adressing the issues.
|
||||||
# - If the changes needed to a PR are small, you can add a "LGTM once the
|
# - If the changes needed to a PR are small, you can add a "LGTM once the
|
||||||
# following comments are addressed..." this will reduce needless back and
|
# following comments are adressed..." this will reduce needless back and
|
||||||
# forth.
|
# forth.
|
||||||
# - If the PR only needs a few changes before being merged, any MAINTAINER can
|
# - 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
|
# make a replacement PR that incorporates the existing commits and fixes the
|
||||||
@@ -324,10 +336,12 @@ made through a pull request.
|
|||||||
[Org.Subsystems.Documentation]
|
[Org.Subsystems.Documentation]
|
||||||
|
|
||||||
people = [
|
people = [
|
||||||
|
"fredlf",
|
||||||
"james",
|
"james",
|
||||||
"moxiegirl",
|
"moxiegirl",
|
||||||
"thaJeztah",
|
"thaJeztah",
|
||||||
"jamtur01",
|
"jamtur01",
|
||||||
|
"spf13",
|
||||||
"sven"
|
"sven"
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -488,6 +502,11 @@ made through a pull request.
|
|||||||
Email = "estesp@linux.vnet.ibm.com"
|
Email = "estesp@linux.vnet.ibm.com"
|
||||||
GitHub = "estesp"
|
GitHub = "estesp"
|
||||||
|
|
||||||
|
[people.fredlf]
|
||||||
|
Name = "Fred Lifton"
|
||||||
|
Email = "fred.lifton@docker.com"
|
||||||
|
GitHub = "fredlf"
|
||||||
|
|
||||||
[people.icecrime]
|
[people.icecrime]
|
||||||
Name = "Arnaud Porterie"
|
Name = "Arnaud Porterie"
|
||||||
Email = "arnaud@docker.com"
|
Email = "arnaud@docker.com"
|
||||||
@@ -503,11 +522,6 @@ made through a pull request.
|
|||||||
Email = "josh.hawn@docker.com"
|
Email = "josh.hawn@docker.com"
|
||||||
Github = "jlhawn"
|
Github = "jlhawn"
|
||||||
|
|
||||||
[people.jnagal]
|
|
||||||
Name = "Rohit Jnagal"
|
|
||||||
Email = "jnagal@google.com"
|
|
||||||
GitHub = "rjnagal"
|
|
||||||
|
|
||||||
[people.lk4d4]
|
[people.lk4d4]
|
||||||
Name = "Alexander Morozov"
|
Name = "Alexander Morozov"
|
||||||
Email = "lk4d4@docker.com"
|
Email = "lk4d4@docker.com"
|
||||||
@@ -518,11 +532,6 @@ made through a pull request.
|
|||||||
Email = "mary.anthony@docker.com"
|
Email = "mary.anthony@docker.com"
|
||||||
GitHub = "moxiegirl"
|
GitHub = "moxiegirl"
|
||||||
|
|
||||||
[people.mpatel]
|
|
||||||
Name = "Mrunal Patel"
|
|
||||||
Email = "mpatel@redhat.com"
|
|
||||||
GitHub = "mrunalp"
|
|
||||||
|
|
||||||
[people.nathanmccauley]
|
[people.nathanmccauley]
|
||||||
Name = "Nathan McCauley"
|
Name = "Nathan McCauley"
|
||||||
Email = "nathan.mccauley@docker.com"
|
Email = "nathan.mccauley@docker.com"
|
||||||
@@ -543,6 +552,11 @@ made through a pull request.
|
|||||||
Email = "solomon@docker.com"
|
Email = "solomon@docker.com"
|
||||||
GitHub = "shykes"
|
GitHub = "shykes"
|
||||||
|
|
||||||
|
[people.spf13]
|
||||||
|
Name = "Steve Francia"
|
||||||
|
Email = "steve.francia@gmail.com"
|
||||||
|
GitHub = "spf13"
|
||||||
|
|
||||||
[people.sven]
|
[people.sven]
|
||||||
Name = "Sven Dowideit"
|
Name = "Sven Dowideit"
|
||||||
Email = "SvenDowideit@home.org.au"
|
Email = "SvenDowideit@home.org.au"
|
||||||
@@ -568,32 +582,37 @@ made through a pull request.
|
|||||||
Email = "tibor@docker.com"
|
Email = "tibor@docker.com"
|
||||||
GitHub = "tiborvass"
|
GitHub = "tiborvass"
|
||||||
|
|
||||||
[people.unclejack]
|
|
||||||
Name = "Cristian Staretu"
|
|
||||||
Email = "cristian.staretu@gmail.com"
|
|
||||||
GitHub = "unclejack"
|
|
||||||
|
|
||||||
[people.vbatts]
|
[people.vbatts]
|
||||||
Name = "Vincent Batts"
|
Name = "Vincent Batts"
|
||||||
Email = "vbatts@redhat.com"
|
Email = "vbatts@redhat.com"
|
||||||
GitHub = "vbatts"
|
GitHub = "vbatts"
|
||||||
|
|
||||||
[people.vdemeester]
|
|
||||||
Name = "Vincent Demeester"
|
|
||||||
Email = "vincent@sbr.pm"
|
|
||||||
GitHub = "vdemeester"
|
|
||||||
|
|
||||||
[people.vieux]
|
[people.vieux]
|
||||||
Name = "Victor Vieux"
|
Name = "Victor Vieux"
|
||||||
Email = "vieux@docker.com"
|
Email = "vieux@docker.com"
|
||||||
GitHub = "vieux"
|
GitHub = "vieux"
|
||||||
|
|
||||||
[people.vishh]
|
|
||||||
Name = "Vishnu Kannan"
|
|
||||||
Email = "vishnuk@google.com"
|
|
||||||
GitHub = "vishh"
|
|
||||||
|
|
||||||
[people.vmarmol]
|
[people.vmarmol]
|
||||||
Name = "Victor Marmol"
|
Name = "Victor Marmol"
|
||||||
Email = "vmarmol@google.com"
|
Email = "vmarmol@google.com"
|
||||||
GitHub = "vmarmol"
|
GitHub = "vmarmol"
|
||||||
|
|
||||||
|
[people.jnagal]
|
||||||
|
Name = "Rohit Jnagal"
|
||||||
|
Email = "jnagal@google.com"
|
||||||
|
GitHub = "rjnagal"
|
||||||
|
|
||||||
|
[people.mpatel]
|
||||||
|
Name = "Mrunal Patel"
|
||||||
|
Email = "mpatel@redhat.com"
|
||||||
|
GitHub = "mrunalp"
|
||||||
|
|
||||||
|
[people.unclejack]
|
||||||
|
Name = "Cristian Staretu"
|
||||||
|
Email = "cristian.staretu@gmail.com"
|
||||||
|
GitHub = "unclejack"
|
||||||
|
|
||||||
|
[people.vishh]
|
||||||
|
Name = "Vishnu Kannan"
|
||||||
|
Email = "vishnuk@google.com"
|
||||||
|
GitHub = "vishh"
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ DOCKER_ENVS := \
|
|||||||
-e DOCKER_DEBUG \
|
-e DOCKER_DEBUG \
|
||||||
-e DOCKER_EXECDRIVER \
|
-e DOCKER_EXECDRIVER \
|
||||||
-e DOCKER_EXPERIMENTAL \
|
-e DOCKER_EXPERIMENTAL \
|
||||||
-e DOCKER_REMAP_ROOT \
|
|
||||||
-e DOCKER_GRAPHDRIVER \
|
-e DOCKER_GRAPHDRIVER \
|
||||||
-e DOCKER_STORAGE_OPTS \
|
-e DOCKER_STORAGE_OPTS \
|
||||||
-e DOCKER_USERLANDPROXY \
|
-e DOCKER_USERLANDPROXY \
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
Docker: the container engine [](https://github.com/docker/docker/releases/latest)
|
Docker: the Linux container engine
|
||||||
============================
|
==================================
|
||||||
|
|
||||||
Docker is an open source project to pack, ship and run any application
|
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*.
|
Docker containers are both *hardware-agnostic* and *platform-agnostic*.
|
||||||
This means they can run anywhere, from your laptop to the largest
|
This means they can run anywhere, from your laptop to the largest
|
||||||
cloud compute instance and everything in between - and they don't require
|
EC2 compute instance and everything in between - and they don't require
|
||||||
you to use a particular language, framework or packaging system. That
|
you to use a particular language, framework or packaging system. That
|
||||||
makes them great building blocks for deploying and scaling web apps,
|
makes them great building blocks for deploying and scaling web apps,
|
||||||
databases, and backend services without depending on a particular stack
|
databases, and backend services without depending on a particular stack
|
||||||
@@ -174,14 +174,13 @@ Under the hood, Docker is built on the following components:
|
|||||||
capabilities of the Linux kernel
|
capabilities of the Linux kernel
|
||||||
* The [Go](https://golang.org) programming language
|
* The [Go](https://golang.org) programming language
|
||||||
* The [Docker Image Specification](https://github.com/docker/docker/blob/master/image/spec/v1.md)
|
* The [Docker Image Specification](https://github.com/docker/docker/blob/master/image/spec/v1.md)
|
||||||
* The [Libcontainer Specification](https://github.com/opencontainers/runc/blob/master/libcontainer/SPEC.md)
|
* The [Libcontainer Specification](https://github.com/docker/libcontainer/blob/master/SPEC.md)
|
||||||
|
|
||||||
Contributing to Docker [](https://godoc.org/github.com/docker/docker)
|
Contributing to Docker
|
||||||
======================
|
======================
|
||||||
|
|
||||||
| **Master** (Linux) | **Experimental** (linux) | **Windows** | **FreeBSD** |
|
[](https://godoc.org/github.com/docker/docker)
|
||||||
|------------------|----------------------|---------|---------|
|
[](https://jenkins.dockerproject.org/job/Docker%20Master/)
|
||||||
| [](https://jenkins.dockerproject.org/view/Docker/job/Docker%20Master/) | [](https://jenkins.dockerproject.org/view/Docker/job/Docker%20Master%20%28experimental%29/) | [/badge/icon)](http://jenkins.dockerproject.org/job/Docker%20Master%20(windows)/) | [/badge/icon)](http://jenkins.dockerproject.org/job/Docker%20Master%20(freebsd)/) |
|
|
||||||
|
|
||||||
Want to hack on Docker? Awesome! We have [instructions to help you get
|
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/).
|
started contributing code or documentation](https://docs.docker.com/project/who-written-for/).
|
||||||
@@ -248,7 +247,7 @@ We are always open to suggestions on process improvements, and are always lookin
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Stack Overflow</td>
|
<td>Stack Overflow</td>
|
||||||
<td>
|
<td>
|
||||||
Stack Overflow has over 7000 Docker questions listed. We regularly
|
Stack Overflow has over 7000K Docker questions listed. We regularly
|
||||||
monitor <a href="https://stackoverflow.com/search?tab=newest&q=docker" target="_blank">Docker questions</a>
|
monitor <a href="https://stackoverflow.com/search?tab=newest&q=docker" target="_blank">Docker questions</a>
|
||||||
and so do many other knowledgeable Docker users.
|
and so do many other knowledgeable Docker users.
|
||||||
</td>
|
</td>
|
||||||
@@ -294,7 +293,3 @@ Docker on Mac and Windows
|
|||||||
|
|
||||||
If you know of another project underway that should be listed here, please help
|
If you know of another project underway that should be listed here, please help
|
||||||
us keep this list up-to-date by submitting a PR.
|
us keep this list up-to-date by submitting a PR.
|
||||||
|
|
||||||
Awesome-Docker
|
|
||||||
==============
|
|
||||||
You can find more projects, tools and articles related to Docker on the [awesome-docker list](https://github.com/veggiemonk/awesome-docker). Add your project there.
|
|
||||||
|
|||||||
+2
-2
@@ -130,11 +130,11 @@ we intend to take advantage of it in the Engine.
|
|||||||
## 2.1 Docker exec
|
## 2.1 Docker exec
|
||||||
|
|
||||||
We won't accept patches expanding the surface of `docker exec`, which we intend to keep as a
|
We won't accept patches expanding the surface of `docker exec`, which we intend to keep as a
|
||||||
*debugging* feature, as well as being strongly dependent on the Runtime ingredient effort.
|
*debugging* feature, as well as being strongly dependent on the the Runtime ingredient effort.
|
||||||
|
|
||||||
## 2.2 Dockerfile syntax
|
## 2.2 Dockerfile syntax
|
||||||
|
|
||||||
The Dockerfile syntax as we know it is simple, and has proven successful in supporting all our
|
The Dockerfile syntax as we know it is simple, and has proven succesful in supporting all our
|
||||||
[official images](https://github.com/docker-library/official-images). Although this is *not* a
|
[official images](https://github.com/docker-library/official-images). Although this is *not* a
|
||||||
definitive move, we temporarily won't accept more patches to the Dockerfile syntax for several
|
definitive move, we temporarily won't accept more patches to the Dockerfile syntax for several
|
||||||
reasons:
|
reasons:
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJsonContentType(t *testing.T) {
|
||||||
|
if !MatchesContentType("application/json", "application/json") {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !MatchesContentType("application/json; charset=utf-8", "application/json") {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if MatchesContentType("dockerapplication/json", "application/json") {
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker attach [OPTIONS] CONTAINER
|
// Usage: docker attach [OPTIONS] CONTAINER
|
||||||
func (cli *DockerCli) CmdAttach(args ...string) error {
|
func (cli *DockerCli) CmdAttach(args ...string) error {
|
||||||
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, Cli.DockerCommands["attach"].Description, true)
|
cmd := Cli.Subcmd("attach", []string{"CONTAINER"}, "Attach to a running container", true)
|
||||||
noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
|
noStdin := cmd.Bool([]string{"#nostdin", "-no-stdin"}, false, "Do not attach STDIN")
|
||||||
proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process")
|
proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process")
|
||||||
|
|
||||||
|
|||||||
+19
-59
@@ -12,6 +12,7 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
@@ -30,11 +31,11 @@ import (
|
|||||||
"github.com/docker/docker/pkg/parsers"
|
"github.com/docker/docker/pkg/parsers"
|
||||||
"github.com/docker/docker/pkg/progressreader"
|
"github.com/docker/docker/pkg/progressreader"
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
|
"github.com/docker/docker/pkg/symlink"
|
||||||
"github.com/docker/docker/pkg/ulimit"
|
"github.com/docker/docker/pkg/ulimit"
|
||||||
"github.com/docker/docker/pkg/units"
|
"github.com/docker/docker/pkg/units"
|
||||||
"github.com/docker/docker/pkg/urlutil"
|
"github.com/docker/docker/pkg/urlutil"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ const (
|
|||||||
//
|
//
|
||||||
// Usage: docker build [OPTIONS] PATH | URL | -
|
// Usage: docker build [OPTIONS] PATH | URL | -
|
||||||
func (cli *DockerCli) CmdBuild(args ...string) error {
|
func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||||
cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, Cli.DockerCommands["build"].Description, true)
|
cmd := Cli.Subcmd("build", []string{"PATH | URL | -"}, "Build a new image from the source code at PATH", true)
|
||||||
tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image")
|
tag := cmd.String([]string{"t", "-tag"}, "", "Repository name (and optionally a tag) for the image")
|
||||||
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
|
suppressOutput := cmd.Bool([]string{"q", "-quiet"}, false, "Suppress the verbose output generated by the containers")
|
||||||
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
noCache := cmd.Bool([]string{"#no-cache", "-no-cache"}, false, "Do not use cache when building the image")
|
||||||
@@ -58,14 +59,12 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
|
dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
|
||||||
flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
||||||
flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
|
flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
|
||||||
flCPUShares := cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
|
flCPUShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
|
||||||
flCPUPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
|
flCpuPeriod := cmd.Int64([]string{"-cpu-period"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) period")
|
||||||
flCPUQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
flCpuQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
||||||
flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
||||||
flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
|
flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
|
||||||
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
||||||
flBuildArg := opts.NewListOpts(opts.ValidateEnv)
|
|
||||||
cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables")
|
|
||||||
|
|
||||||
ulimits := make(map[string]*ulimit.Ulimit)
|
ulimits := make(map[string]*ulimit.Ulimit)
|
||||||
flUlimits := opts.NewUlimitOpt(&ulimits)
|
flUlimits := opts.NewUlimitOpt(&ulimits)
|
||||||
@@ -130,18 +129,12 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
|
return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := os.Open(filepath.Join(contextDir, ".dockerignore"))
|
var includes = []string{"."}
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var excludes []string
|
excludes, err := utils.ReadDockerIgnore(path.Join(contextDir, ".dockerignore"))
|
||||||
if err == nil {
|
|
||||||
excludes, err = utils.ReadDockerIgnore(f)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err := utils.ValidateContextDirectory(contextDir, excludes); err != nil {
|
if err := utils.ValidateContextDirectory(contextDir, excludes); err != nil {
|
||||||
return fmt.Errorf("Error checking context: '%s'.", err)
|
return fmt.Errorf("Error checking context: '%s'.", err)
|
||||||
@@ -154,7 +147,6 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
// removed. The deamon will remove them for us, if needed, after it
|
// removed. The deamon will remove them for us, if needed, after it
|
||||||
// parses the Dockerfile. Ignore errors here, as they will have been
|
// parses the Dockerfile. Ignore errors here, as they will have been
|
||||||
// caught by ValidateContextDirectory above.
|
// caught by ValidateContextDirectory above.
|
||||||
var includes = []string{"."}
|
|
||||||
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
|
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
|
||||||
keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
|
keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
|
||||||
if keepThem1 || keepThem2 {
|
if keepThem1 || keepThem2 {
|
||||||
@@ -251,8 +243,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
v.Set("cpusetcpus", *flCPUSetCpus)
|
v.Set("cpusetcpus", *flCPUSetCpus)
|
||||||
v.Set("cpusetmems", *flCPUSetMems)
|
v.Set("cpusetmems", *flCPUSetMems)
|
||||||
v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10))
|
v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10))
|
||||||
v.Set("cpuquota", strconv.FormatInt(*flCPUQuota, 10))
|
v.Set("cpuquota", strconv.FormatInt(*flCpuQuota, 10))
|
||||||
v.Set("cpuperiod", strconv.FormatInt(*flCPUPeriod, 10))
|
v.Set("cpuperiod", strconv.FormatInt(*flCpuPeriod, 10))
|
||||||
v.Set("memory", strconv.FormatInt(memory, 10))
|
v.Set("memory", strconv.FormatInt(memory, 10))
|
||||||
v.Set("memswap", strconv.FormatInt(memorySwap, 10))
|
v.Set("memswap", strconv.FormatInt(memorySwap, 10))
|
||||||
v.Set("cgroupparent", *flCgroupParent)
|
v.Set("cgroupparent", *flCgroupParent)
|
||||||
@@ -260,19 +252,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
v.Set("dockerfile", relDockerfile)
|
v.Set("dockerfile", relDockerfile)
|
||||||
|
|
||||||
ulimitsVar := flUlimits.GetList()
|
ulimitsVar := flUlimits.GetList()
|
||||||
ulimitsJSON, err := json.Marshal(ulimitsVar)
|
ulimitsJson, err := json.Marshal(ulimitsVar)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
v.Set("ulimits", string(ulimitsJSON))
|
v.Set("ulimits", string(ulimitsJson))
|
||||||
|
|
||||||
// collect all the build-time environment variables for the container
|
|
||||||
buildArgs := runconfig.ConvertKVStringsToMap(flBuildArg.GetAll())
|
|
||||||
buildArgsJSON, err := json.Marshal(buildArgs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.Set("buildargs", string(buildArgsJSON))
|
|
||||||
|
|
||||||
headers := http.Header(make(map[string][]string))
|
headers := http.Header(make(map[string][]string))
|
||||||
buf, err := json.Marshal(cli.configFile.AuthConfigs)
|
buf, err := json.Marshal(cli.configFile.AuthConfigs)
|
||||||
@@ -324,12 +308,6 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// isUNC returns true if the path is UNC (one starting \\). It always returns
|
|
||||||
// false on Linux.
|
|
||||||
func isUNC(path string) bool {
|
|
||||||
return runtime.GOOS == "windows" && strings.HasPrefix(path, `\\`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// getDockerfileRelPath uses the given context directory for a `docker build`
|
// getDockerfileRelPath uses the given context directory for a `docker build`
|
||||||
// and returns the absolute path to the context directory, the relative path of
|
// and returns the absolute path to the context directory, the relative path of
|
||||||
// the dockerfile in that context directory, and a non-nil error on success.
|
// the dockerfile in that context directory, and a non-nil error on success.
|
||||||
@@ -340,17 +318,10 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi
|
|||||||
|
|
||||||
// The context dir might be a symbolic link, so follow it to the actual
|
// The context dir might be a symbolic link, so follow it to the actual
|
||||||
// target directory.
|
// target directory.
|
||||||
//
|
|
||||||
// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
|
|
||||||
// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
|
|
||||||
// paths (those starting with \\). This hack means that when using links
|
|
||||||
// on UNC paths, they will not be followed.
|
|
||||||
if !isUNC(absContextDir) {
|
|
||||||
absContextDir, err = filepath.EvalSymlinks(absContextDir)
|
absContextDir, err = filepath.EvalSymlinks(absContextDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
|
return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
stat, err := os.Lstat(absContextDir)
|
stat, err := os.Lstat(absContextDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -383,22 +354,15 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi
|
|||||||
absDockerfile = filepath.Join(absContextDir, absDockerfile)
|
absDockerfile = filepath.Join(absContextDir, absDockerfile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate symlinks in the path to the Dockerfile too.
|
// Verify that 'filename' is within the build context
|
||||||
//
|
absDockerfile, err = symlink.FollowSymlinkInScope(absDockerfile, absContextDir)
|
||||||
// FIXME. We use isUNC (always false on non-Windows platforms) to workaround
|
|
||||||
// an issue in golang. On Windows, EvalSymLinks does not work on UNC file
|
|
||||||
// paths (those starting with \\). This hack means that when using links
|
|
||||||
// on UNC paths, they will not be followed.
|
|
||||||
if !isUNC(absDockerfile) {
|
|
||||||
absDockerfile, err = filepath.EvalSymlinks(absDockerfile)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err)
|
return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Lstat(absDockerfile); err != nil {
|
if _, err := os.Lstat(absDockerfile); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return "", "", fmt.Errorf("Cannot locate Dockerfile: %q", absDockerfile)
|
return "", "", fmt.Errorf("Cannot locate Dockerfile: absDockerfile: %q", absDockerfile)
|
||||||
}
|
}
|
||||||
return "", "", fmt.Errorf("unable to stat Dockerfile: %v", err)
|
return "", "", fmt.Errorf("unable to stat Dockerfile: %v", err)
|
||||||
}
|
}
|
||||||
@@ -407,10 +371,6 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi
|
|||||||
return "", "", fmt.Errorf("unable to get relative Dockerfile path: %v", err)
|
return "", "", fmt.Errorf("unable to get relative Dockerfile path: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.HasPrefix(relDockerfile, ".."+string(filepath.Separator)) {
|
|
||||||
return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
return absContextDir, relDockerfile, nil
|
return absContextDir, relDockerfile, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -462,7 +422,7 @@ func getContextFromReader(r io.Reader, dockerfileName string) (absContextDir, re
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := archive.Untar(buf, absContextDir, nil); err != nil {
|
if err := archive.Untar(buf, absContextDir, nil); err != nil {
|
||||||
return "", "", fmt.Errorf("unable to extract stdin to temporary context directory: %v", err)
|
return "", "", fmt.Errorf("unable to extract stdin to temporary context direcotry: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return getDockerfileRelPath(absContextDir, dockerfileName)
|
return getDockerfileRelPath(absContextDir, dockerfileName)
|
||||||
@@ -499,7 +459,7 @@ func getContextFromURL(out io.Writer, remoteURL, dockerfileName string) (absCont
|
|||||||
In: response.Body,
|
In: response.Body,
|
||||||
Out: out,
|
Out: out,
|
||||||
Formatter: streamformatter.NewStreamFormatter(),
|
Formatter: streamformatter.NewStreamFormatter(),
|
||||||
Size: response.ContentLength,
|
Size: int(response.ContentLength),
|
||||||
NewLines: true,
|
NewLines: true,
|
||||||
ID: "",
|
ID: "",
|
||||||
Action: fmt.Sprintf("Downloading build context from remote url: %s", remoteURL),
|
Action: fmt.Sprintf("Downloading build context from remote url: %s", remoteURL),
|
||||||
@@ -584,7 +544,7 @@ func rewriteDockerfileFrom(dockerfileName string, translator func(string, regist
|
|||||||
// Replace the line with a resolved "FROM repo@digest"
|
// Replace the line with a resolved "FROM repo@digest"
|
||||||
repo, tag := parsers.ParseRepositoryTag(matches[1])
|
repo, tag := parsers.ParseRepositoryTag(matches[1])
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
tag = tags.DefaultTag
|
tag = tags.DEFAULTTAG
|
||||||
}
|
}
|
||||||
|
|
||||||
repoInfo, err := registry.ParseRepositoryInfo(repo)
|
repoInfo, err := registry.ParseRepositoryInfo(repo)
|
||||||
|
|||||||
+10
-12
@@ -52,14 +52,12 @@ type DockerCli struct {
|
|||||||
outFd uintptr
|
outFd uintptr
|
||||||
// isTerminalIn indicates whether the client's STDIN is a TTY
|
// isTerminalIn indicates whether the client's STDIN is a TTY
|
||||||
isTerminalIn bool
|
isTerminalIn bool
|
||||||
// isTerminalOut indicates whether the client's STDOUT is a TTY
|
// isTerminalOut dindicates whether the client's STDOUT is a TTY
|
||||||
isTerminalOut bool
|
isTerminalOut bool
|
||||||
// transport holds the client transport instance.
|
// transport holds the client transport instance.
|
||||||
transport *http.Transport
|
transport *http.Transport
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize calls the init function that will setup the configuration for the client
|
|
||||||
// such as the TLS, tcp and other parameters used to run the client.
|
|
||||||
func (cli *DockerCli) Initialize() error {
|
func (cli *DockerCli) Initialize() error {
|
||||||
if cli.init == nil {
|
if cli.init == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -79,8 +77,6 @@ func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// PsFormat returns the format string specified in the configuration.
|
|
||||||
// String contains columns and format specification, for example {{ID}\t{{Name}}.
|
|
||||||
func (cli *DockerCli) PsFormat() string {
|
func (cli *DockerCli) PsFormat() string {
|
||||||
return cli.configFile.PsFormat
|
return cli.configFile.PsFormat
|
||||||
}
|
}
|
||||||
@@ -98,25 +94,27 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF
|
|||||||
}
|
}
|
||||||
|
|
||||||
cli.init = func() error {
|
cli.init = func() error {
|
||||||
|
|
||||||
clientFlags.PostParse()
|
clientFlags.PostParse()
|
||||||
|
|
||||||
hosts := clientFlags.Common.Hosts
|
hosts := clientFlags.Common.Hosts
|
||||||
|
|
||||||
switch len(hosts) {
|
switch len(hosts) {
|
||||||
case 0:
|
case 0:
|
||||||
hosts = []string{os.Getenv("DOCKER_HOST")}
|
defaultHost := os.Getenv("DOCKER_HOST")
|
||||||
|
if defaultHost == "" {
|
||||||
|
defaultHost = opts.DefaultHost
|
||||||
|
}
|
||||||
|
defaultHost, err := opts.ValidateHost(defaultHost)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
hosts = []string{defaultHost}
|
||||||
case 1:
|
case 1:
|
||||||
// only accept one host to talk to
|
// only accept one host to talk to
|
||||||
default:
|
default:
|
||||||
return errors.New("Please specify only one -H")
|
return errors.New("Please specify only one -H")
|
||||||
}
|
}
|
||||||
|
|
||||||
var e error
|
|
||||||
if hosts[0], e = opts.ParseHost(hosts[0]); e != nil {
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
protoAddrParts := strings.SplitN(hosts[0], "://", 2)
|
protoAddrParts := strings.SplitN(hosts[0], "://", 2)
|
||||||
cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1]
|
cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1]
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
|
// Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
|
||||||
func (cli *DockerCli) CmdCommit(args ...string) error {
|
func (cli *DockerCli) CmdCommit(args ...string) error {
|
||||||
cmd := Cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, Cli.DockerCommands["commit"].Description, true)
|
cmd := Cli.Subcmd("commit", []string{"CONTAINER [REPOSITORY[:TAG]]"}, "Create a new image from a container's changes", true)
|
||||||
flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
|
flPause := cmd.Bool([]string{"p", "-pause"}, true, "Pause container during commit")
|
||||||
flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
|
flComment := cmd.String([]string{"m", "-message"}, "", "Commit message")
|
||||||
flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
|
flAuthor := cmd.String([]string{"a", "#author", "-author"}, "", "Author (e.g., \"John Hannibal Smith <hannibal@a-team.com>\")")
|
||||||
|
|||||||
+4
-5
@@ -15,7 +15,6 @@ import (
|
|||||||
Cli "github.com/docker/docker/cli"
|
Cli "github.com/docker/docker/cli"
|
||||||
"github.com/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
"github.com/docker/docker/pkg/system"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type copyDirection int
|
type copyDirection int
|
||||||
@@ -43,8 +42,8 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
|||||||
"cp",
|
"cp",
|
||||||
[]string{"CONTAINER:PATH LOCALPATH|-", "LOCALPATH|- CONTAINER:PATH"},
|
[]string{"CONTAINER:PATH LOCALPATH|-", "LOCALPATH|- CONTAINER:PATH"},
|
||||||
strings.Join([]string{
|
strings.Join([]string{
|
||||||
Cli.DockerCommands["cp"].Description,
|
"Copy files/folders between a container and your host.\n",
|
||||||
"\nUse '-' as the source to read a tar archive from stdin\n",
|
"Use '-' as the source to read a tar archive from stdin\n",
|
||||||
"and extract it to a directory destination in a container.\n",
|
"and extract it to a directory destination in a container.\n",
|
||||||
"Use '-' as the destination to stream a tar archive of a\n",
|
"Use '-' as the destination to stream a tar archive of a\n",
|
||||||
"container source to stdout.",
|
"container source to stdout.",
|
||||||
@@ -102,7 +101,7 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
|||||||
// client, a `:` could be part of an absolute Windows path, in which case it
|
// client, a `:` could be part of an absolute Windows path, in which case it
|
||||||
// is immediately proceeded by a backslash.
|
// is immediately proceeded by a backslash.
|
||||||
func splitCpArg(arg string) (container, path string) {
|
func splitCpArg(arg string) (container, path string) {
|
||||||
if system.IsAbs(arg) {
|
if filepath.IsAbs(arg) {
|
||||||
// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
|
// Explicit local absolute path, e.g., `C:\foo` or `/foo`.
|
||||||
return "", arg
|
return "", arg
|
||||||
}
|
}
|
||||||
@@ -237,7 +236,7 @@ func (cli *DockerCli) copyToContainer(srcPath, dstContainer, dstPath string) (er
|
|||||||
// If the destination is a symbolic link, we should evaluate it.
|
// If the destination is a symbolic link, we should evaluate it.
|
||||||
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
|
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
|
||||||
linkTarget := dstStat.LinkTarget
|
linkTarget := dstStat.LinkTarget
|
||||||
if !system.IsAbs(linkTarget) {
|
if !filepath.IsAbs(linkTarget) {
|
||||||
// Join with the parent directory.
|
// Join with the parent directory.
|
||||||
dstParent, _ := archive.SplitPathDirEntry(dstPath)
|
dstParent, _ := archive.SplitPathDirEntry(dstPath)
|
||||||
linkTarget = filepath.Join(dstParent, linkTarget)
|
linkTarget = filepath.Join(dstParent, linkTarget)
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
|||||||
repos, tag := parsers.ParseRepositoryTag(image)
|
repos, tag := parsers.ParseRepositoryTag(image)
|
||||||
// pull only the image tagged 'latest' if no tag was specified
|
// pull only the image tagged 'latest' if no tag was specified
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
tag = tags.DefaultTag
|
tag = tags.DEFAULTTAG
|
||||||
}
|
}
|
||||||
v.Set("fromImage", repos)
|
v.Set("fromImage", repos)
|
||||||
v.Set("tag", tag)
|
v.Set("tag", tag)
|
||||||
@@ -96,7 +96,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
|||||||
|
|
||||||
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
||||||
if tag == "" {
|
if tag == "" {
|
||||||
tag = tags.DefaultTag
|
tag = tags.DEFAULTTAG
|
||||||
}
|
}
|
||||||
|
|
||||||
ref := registry.ParseReference(tag)
|
ref := registry.ParseReference(tag)
|
||||||
@@ -159,7 +159,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
|||||||
//
|
//
|
||||||
// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
|
// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
|
||||||
func (cli *DockerCli) CmdCreate(args ...string) error {
|
func (cli *DockerCli) CmdCreate(args ...string) error {
|
||||||
cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["create"].Description, true)
|
cmd := Cli.Subcmd("create", []string{"IMAGE [COMMAND] [ARG...]"}, "Create a new container", true)
|
||||||
addTrustedFlags(cmd, true)
|
addTrustedFlags(cmd, true)
|
||||||
|
|
||||||
// These are flags not stored in Config/HostConfig
|
// These are flags not stored in Config/HostConfig
|
||||||
|
|||||||
+1
-1
@@ -18,7 +18,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker diff CONTAINER
|
// Usage: docker diff CONTAINER
|
||||||
func (cli *DockerCli) CmdDiff(args ...string) error {
|
func (cli *DockerCli) CmdDiff(args ...string) error {
|
||||||
cmd := Cli.Subcmd("diff", []string{"CONTAINER"}, Cli.DockerCommands["diff"].Description, true)
|
cmd := Cli.Subcmd("diff", []string{"CONTAINER"}, "Inspect changes on a container's filesystem", true)
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker events [OPTIONS]
|
// Usage: docker events [OPTIONS]
|
||||||
func (cli *DockerCli) CmdEvents(args ...string) error {
|
func (cli *DockerCli) CmdEvents(args ...string) error {
|
||||||
cmd := Cli.Subcmd("events", nil, Cli.DockerCommands["events"].Description, true)
|
cmd := Cli.Subcmd("events", nil, "Get real time events from the server", true)
|
||||||
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
since := cmd.String([]string{"#since", "-since"}, "", "Show all events created since timestamp")
|
||||||
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
||||||
flFilter := opts.NewListOpts(nil)
|
flFilter := opts.NewListOpts(nil)
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
|
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
|
||||||
func (cli *DockerCli) CmdExec(args ...string) error {
|
func (cli *DockerCli) CmdExec(args ...string) error {
|
||||||
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, Cli.DockerCommands["exec"].Description, true)
|
cmd := Cli.Subcmd("exec", []string{"CONTAINER COMMAND [ARG...]"}, "Run a command in a running container", true)
|
||||||
|
|
||||||
execConfig, err := runconfig.ParseExec(cmd, args)
|
execConfig, err := runconfig.ParseExec(cmd, args)
|
||||||
// just in case the ParseExec does not exit
|
// just in case the ParseExec does not exit
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
Cli "github.com/docker/docker/cli"
|
||||||
@@ -14,14 +15,14 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker export [OPTIONS] CONTAINER
|
// Usage: docker export [OPTIONS] CONTAINER
|
||||||
func (cli *DockerCli) CmdExport(args ...string) error {
|
func (cli *DockerCli) CmdExport(args ...string) error {
|
||||||
cmd := Cli.Subcmd("export", []string{"CONTAINER"}, Cli.DockerCommands["export"].Description, true)
|
cmd := Cli.Subcmd("export", []string{"CONTAINER"}, "Export the contents of a container's filesystem as a tar archive", true)
|
||||||
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
output = cli.out
|
output io.Writer = cli.out
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
if *outfile != "" {
|
if *outfile != "" {
|
||||||
|
|||||||
+27
-24
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
"github.com/docker/docker/autogen/dockerversion"
|
||||||
|
"github.com/docker/docker/pkg/promise"
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
)
|
)
|
||||||
@@ -155,13 +156,6 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
req.Host = cli.addr
|
req.Host = cli.addr
|
||||||
|
|
||||||
dial, err := cli.dial()
|
dial, err := cli.dial()
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(err.Error(), "connection refused") {
|
|
||||||
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker daemon' running on this host?")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// When we set up a TCP connection for hijack, there could be long periods
|
// When we set up a TCP connection for hijack, there could be long periods
|
||||||
// of inactivity (a long running command with no output) that in certain
|
// of inactivity (a long running command with no output) that in certain
|
||||||
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
// network setups may cause ECONNTIMEOUT, leaving the client in an unknown
|
||||||
@@ -171,7 +165,12 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
tcpConn.SetKeepAlive(true)
|
tcpConn.SetKeepAlive(true)
|
||||||
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
tcpConn.SetKeepAlivePeriod(30 * time.Second)
|
||||||
}
|
}
|
||||||
|
if err != nil {
|
||||||
|
if strings.Contains(err.Error(), "connection refused") {
|
||||||
|
return fmt.Errorf("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
clientconn := httputil.NewClientConn(dial, nil)
|
clientconn := httputil.NewClientConn(dial, nil)
|
||||||
defer clientconn.Close()
|
defer clientconn.Close()
|
||||||
|
|
||||||
@@ -185,6 +184,8 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
started <- rwc
|
started <- rwc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var receiveStdout chan error
|
||||||
|
|
||||||
var oldState *term.State
|
var oldState *term.State
|
||||||
|
|
||||||
if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
|
if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
|
||||||
@@ -195,16 +196,20 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
defer term.RestoreTerminal(cli.inFd, oldState)
|
defer term.RestoreTerminal(cli.inFd, oldState)
|
||||||
}
|
}
|
||||||
|
|
||||||
receiveStdout := make(chan error, 1)
|
|
||||||
if stdout != nil || stderr != nil {
|
if stdout != nil || stderr != nil {
|
||||||
go func() {
|
receiveStdout = promise.Go(func() (err error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if in != nil {
|
if in != nil {
|
||||||
if setRawTerminal && cli.isTerminalIn {
|
if setRawTerminal && cli.isTerminalIn {
|
||||||
term.RestoreTerminal(cli.inFd, oldState)
|
term.RestoreTerminal(cli.inFd, oldState)
|
||||||
}
|
}
|
||||||
|
// For some reason this Close call blocks on darwin..
|
||||||
|
// As the client exists right after, simply discard the close
|
||||||
|
// until we find a better solution.
|
||||||
|
if runtime.GOOS != "darwin" {
|
||||||
in.Close()
|
in.Close()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
// When TTY is ON, use regular copy
|
// When TTY is ON, use regular copy
|
||||||
@@ -214,12 +219,11 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
_, err = stdcopy.StdCopy(stdout, stderr, br)
|
_, err = stdcopy.StdCopy(stdout, stderr, br)
|
||||||
}
|
}
|
||||||
logrus.Debugf("[hijack] End of stdout")
|
logrus.Debugf("[hijack] End of stdout")
|
||||||
receiveStdout <- err
|
return err
|
||||||
}()
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
stdinDone := make(chan struct{})
|
sendStdin := promise.Go(func() error {
|
||||||
go func() {
|
|
||||||
if in != nil {
|
if in != nil {
|
||||||
io.Copy(rwc, in)
|
io.Copy(rwc, in)
|
||||||
logrus.Debugf("[hijack] End of stdin")
|
logrus.Debugf("[hijack] End of stdin")
|
||||||
@@ -232,23 +236,22 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
|||||||
logrus.Debugf("Couldn't send EOF: %s", err)
|
logrus.Debugf("Couldn't send EOF: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
close(stdinDone)
|
// Discard errors due to pipe interruption
|
||||||
}()
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-receiveStdout:
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("Error receiveStdout: %s", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-stdinDone:
|
|
||||||
if stdout != nil || stderr != nil {
|
if stdout != nil || stderr != nil {
|
||||||
if err := <-receiveStdout; err != nil {
|
if err := <-receiveStdout; err != nil {
|
||||||
logrus.Debugf("Error receiveStdout: %s", err)
|
logrus.Debugf("Error receiveStdout: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
if !cli.isTerminalIn {
|
||||||
|
if err := <-sendStdin; err != nil {
|
||||||
|
logrus.Debugf("Error sendStdin: %s", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package client
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
"text/tabwriter"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -19,7 +18,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker history [OPTIONS] IMAGE
|
// Usage: docker history [OPTIONS] IMAGE
|
||||||
func (cli *DockerCli) CmdHistory(args ...string) error {
|
func (cli *DockerCli) CmdHistory(args ...string) error {
|
||||||
cmd := Cli.Subcmd("history", []string{"IMAGE"}, Cli.DockerCommands["history"].Description, true)
|
cmd := Cli.Subcmd("history", []string{"IMAGE"}, "Show the history of an image", true)
|
||||||
human := cmd.Bool([]string{"H", "-human"}, true, "Print sizes and dates in human readable format")
|
human := cmd.Bool([]string{"H", "-human"}, true, "Print sizes and dates in human readable format")
|
||||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
||||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||||
@@ -58,9 +57,9 @@ func (cli *DockerCli) CmdHistory(args ...string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if *noTrunc {
|
if *noTrunc {
|
||||||
fmt.Fprintf(w, "%s\t", strings.Replace(entry.CreatedBy, "\t", " ", -1))
|
fmt.Fprintf(w, "%s\t", entry.CreatedBy)
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(w, "%s\t", stringutils.Truncate(strings.Replace(entry.CreatedBy, "\t", " ", -1), 45))
|
fmt.Fprintf(w, "%s\t", stringutils.Truncate(entry.CreatedBy, 45))
|
||||||
}
|
}
|
||||||
|
|
||||||
if *human {
|
if *human {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker images [OPTIONS] [REPOSITORY]
|
// Usage: docker images [OPTIONS] [REPOSITORY]
|
||||||
func (cli *DockerCli) CmdImages(args ...string) error {
|
func (cli *DockerCli) CmdImages(args ...string) error {
|
||||||
cmd := Cli.Subcmd("images", []string{"[REPOSITORY[:TAG]]"}, Cli.DockerCommands["images"].Description, true)
|
cmd := Cli.Subcmd("images", []string{"[REPOSITORY]"}, "List images", true)
|
||||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
||||||
all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)")
|
all := cmd.Bool([]string{"a", "-all"}, false, "Show all images (default hides intermediate images)")
|
||||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||||
|
|||||||
@@ -20,10 +20,9 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
|
// Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
|
||||||
func (cli *DockerCli) CmdImport(args ...string) error {
|
func (cli *DockerCli) CmdImport(args ...string) error {
|
||||||
cmd := Cli.Subcmd("import", []string{"file|URL|- [REPOSITORY[:TAG]]"}, Cli.DockerCommands["import"].Description, true)
|
cmd := Cli.Subcmd("import", []string{"file|URL|- [REPOSITORY[:TAG]]"}, "Create an empty filesystem image and import the contents of the\ntarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz) into it, then\noptionally tag it.", true)
|
||||||
flChanges := opts.NewListOpts(nil)
|
flChanges := opts.NewListOpts(nil)
|
||||||
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
|
cmd.Var(&flChanges, []string{"c", "-change"}, "Apply Dockerfile instruction to the created image")
|
||||||
message := cmd.String([]string{"m", "-message"}, "", "Set commit message for imported image")
|
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
@@ -36,7 +35,6 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
|||||||
|
|
||||||
v.Set("fromSrc", src)
|
v.Set("fromSrc", src)
|
||||||
v.Set("repo", repository)
|
v.Set("repo", repository)
|
||||||
v.Set("message", *message)
|
|
||||||
for _, change := range flChanges.GetAll() {
|
for _, change := range flChanges.GetAll() {
|
||||||
v.Add("changes", change)
|
v.Add("changes", change)
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-11
@@ -16,7 +16,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker info
|
// Usage: docker info
|
||||||
func (cli *DockerCli) CmdInfo(args ...string) error {
|
func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||||
cmd := Cli.Subcmd("info", nil, Cli.DockerCommands["info"].Description, true)
|
cmd := Cli.Subcmd("info", nil, "Display system-wide information", true)
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
@@ -35,7 +35,6 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
|
|
||||||
fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers)
|
fmt.Fprintf(cli.out, "Containers: %d\n", info.Containers)
|
||||||
fmt.Fprintf(cli.out, "Images: %d\n", info.Images)
|
fmt.Fprintf(cli.out, "Images: %d\n", info.Images)
|
||||||
ioutils.FprintfIfNotEmpty(cli.out, "Server Version: %s\n", info.ServerVersion)
|
|
||||||
ioutils.FprintfIfNotEmpty(cli.out, "Storage Driver: %s\n", info.Driver)
|
ioutils.FprintfIfNotEmpty(cli.out, "Storage Driver: %s\n", info.Driver)
|
||||||
if info.DriverStatus != nil {
|
if info.DriverStatus != nil {
|
||||||
for _, pair := range info.DriverStatus {
|
for _, pair := range info.DriverStatus {
|
||||||
@@ -62,8 +61,8 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", info.DockerRootDir)
|
fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", info.DockerRootDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HTTPProxy)
|
ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HttpProxy)
|
||||||
ioutils.FprintfIfNotEmpty(cli.out, "Https Proxy: %s\n", info.HTTPSProxy)
|
ioutils.FprintfIfNotEmpty(cli.out, "Https Proxy: %s\n", info.HttpsProxy)
|
||||||
ioutils.FprintfIfNotEmpty(cli.out, "No Proxy: %s\n", info.NoProxy)
|
ioutils.FprintfIfNotEmpty(cli.out, "No Proxy: %s\n", info.NoProxy)
|
||||||
|
|
||||||
if info.IndexServerAddress != "" {
|
if info.IndexServerAddress != "" {
|
||||||
@@ -73,8 +72,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress)
|
fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Only output these warnings if the server supports these features
|
||||||
// Only output these warnings if the server does not support these features
|
|
||||||
if h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")); err == nil {
|
if h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")); err == nil {
|
||||||
if h.OS != "windows" {
|
if h.OS != "windows" {
|
||||||
if !info.MemoryLimit {
|
if !info.MemoryLimit {
|
||||||
@@ -84,12 +82,12 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
|
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
|
||||||
}
|
}
|
||||||
if !info.IPv4Forwarding {
|
if !info.IPv4Forwarding {
|
||||||
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled\n")
|
fmt.Fprintf(cli.err, "WARNING: IPv4 forwarding is disabled.\n")
|
||||||
}
|
}
|
||||||
if !info.BridgeNfIptables {
|
if !info.BridgeNfIptables {
|
||||||
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-iptables is disabled\n")
|
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-iptables is disabled\n")
|
||||||
}
|
}
|
||||||
if !info.BridgeNfIP6tables {
|
if !info.BridgeNfIp6tables {
|
||||||
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-ip6tables is disabled\n")
|
fmt.Fprintf(cli.err, "WARNING: bridge-nf-call-ip6tables is disabled\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -102,9 +100,8 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ioutils.FprintfIfTrue(cli.out, "Experimental: %v\n", info.ExperimentalBuild)
|
if info.ExperimentalBuild {
|
||||||
if info.ClusterStore != "" {
|
fmt.Fprintf(cli.out, "Experimental: true\n")
|
||||||
fmt.Fprintf(cli.out, "Cluster store: %s\n", info.ClusterStore)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/url"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
@@ -25,10 +24,9 @@ var funcMap = template.FuncMap{
|
|||||||
//
|
//
|
||||||
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
|
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
|
||||||
func (cli *DockerCli) CmdInspect(args ...string) error {
|
func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||||
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, Cli.DockerCommands["inspect"].Description, true)
|
cmd := Cli.Subcmd("inspect", []string{"CONTAINER|IMAGE [CONTAINER|IMAGE...]"}, "Return low-level information on a container or image", true)
|
||||||
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
||||||
inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)")
|
inspectType := cmd.String([]string{"-type"}, "", "Return JSON for specified type, (e.g image or container)")
|
||||||
size := cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes if the type is container")
|
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
@@ -53,15 +51,10 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
|||||||
status := 0
|
status := 0
|
||||||
isImage := false
|
isImage := false
|
||||||
|
|
||||||
v := url.Values{}
|
|
||||||
if *size {
|
|
||||||
v.Set("size", "1")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, name := range cmd.Args() {
|
for _, name := range cmd.Args() {
|
||||||
|
|
||||||
if *inspectType == "" || *inspectType == "container" {
|
if *inspectType == "" || *inspectType == "container" {
|
||||||
obj, _, err = readBody(cli.call("GET", "/containers/"+name+"/json?"+v.Encode(), nil, nil))
|
obj, _, err = readBody(cli.call("GET", "/containers/"+name+"/json", nil, nil))
|
||||||
if err != nil && *inspectType == "container" {
|
if err != nil && *inspectType == "container" {
|
||||||
if strings.Contains(err.Error(), "No such") {
|
if strings.Contains(err.Error(), "No such") {
|
||||||
fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
|
fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdKill(args ...string) error {
|
func (cli *DockerCli) CmdKill(args ...string) error {
|
||||||
cmd := Cli.Subcmd("kill", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["kill"].Description, true)
|
cmd := Cli.Subcmd("kill", []string{"CONTAINER [CONTAINER...]"}, "Kill a running container using SIGKILL or a specified signal", true)
|
||||||
signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
|
signal := cmd.String([]string{"s", "-signal"}, "KILL", "Signal to send to the container")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker load [OPTIONS]
|
// Usage: docker load [OPTIONS]
|
||||||
func (cli *DockerCli) CmdLoad(args ...string) error {
|
func (cli *DockerCli) CmdLoad(args ...string) error {
|
||||||
cmd := Cli.Subcmd("load", nil, Cli.DockerCommands["load"].Description, true)
|
cmd := Cli.Subcmd("load", nil, "Load an image from a tar archive or STDIN", true)
|
||||||
infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
|
infile := cmd.String([]string{"i", "-input"}, "", "Read from a tar archive file, instead of STDIN")
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -22,7 +22,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker login SERVER
|
// Usage: docker login SERVER
|
||||||
func (cli *DockerCli) CmdLogin(args ...string) error {
|
func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||||
cmd := Cli.Subcmd("login", []string{"[SERVER]"}, Cli.DockerCommands["login"].Description+".\nIf no server is specified \""+registry.IndexServer+"\" is the default.", true)
|
cmd := Cli.Subcmd("login", []string{"[SERVER]"}, "Register or log in to a Docker registry server, if no server is\nspecified \""+registry.IndexServer+"\" is the default.", true)
|
||||||
cmd.Require(flag.Max, 1)
|
cmd.Require(flag.Max, 1)
|
||||||
|
|
||||||
var username, password, email string
|
var username, password, email string
|
||||||
@@ -64,7 +64,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
|||||||
if username == "" {
|
if username == "" {
|
||||||
promptDefault("Username", authconfig.Username)
|
promptDefault("Username", authconfig.Username)
|
||||||
username = readInput(cli.in, cli.out)
|
username = readInput(cli.in, cli.out)
|
||||||
username = strings.TrimSpace(username)
|
username = strings.Trim(username, " ")
|
||||||
if username == "" {
|
if username == "" {
|
||||||
username = authconfig.Username
|
username = authconfig.Username
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker logout [SERVER]
|
// Usage: docker logout [SERVER]
|
||||||
func (cli *DockerCli) CmdLogout(args ...string) error {
|
func (cli *DockerCli) CmdLogout(args ...string) error {
|
||||||
cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, Cli.DockerCommands["logout"].Description+".\nIf no server is specified \""+registry.IndexServer+"\" is the default.", true)
|
cmd := Cli.Subcmd("logout", []string{"[SERVER]"}, "Log out from a Docker registry, if no server is\nspecified \""+registry.IndexServer+"\" is the default.", true)
|
||||||
cmd.Require(flag.Max, 1)
|
cmd.Require(flag.Max, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
@@ -26,14 +26,13 @@ func (cli *DockerCli) CmdLogout(args ...string) error {
|
|||||||
|
|
||||||
if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
|
if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
|
||||||
fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress)
|
fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress)
|
||||||
return nil
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress)
|
fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress)
|
||||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||||
|
|
||||||
if err := cli.configFile.Save(); err != nil {
|
if err := cli.configFile.Save(); err != nil {
|
||||||
return fmt.Errorf("Failed to save docker config: %v", err)
|
return fmt.Errorf("Failed to save docker config: %v", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -2,6 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -15,7 +16,7 @@ import (
|
|||||||
//
|
//
|
||||||
// docker logs [OPTIONS] CONTAINER
|
// docker logs [OPTIONS] CONTAINER
|
||||||
func (cli *DockerCli) CmdLogs(args ...string) error {
|
func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||||
cmd := Cli.Subcmd("logs", []string{"CONTAINER"}, Cli.DockerCommands["logs"].Description, true)
|
cmd := Cli.Subcmd("logs", []string{"CONTAINER"}, "Fetch the logs of a container", true)
|
||||||
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
follow := cmd.Bool([]string{"f", "-follow"}, false, "Follow log output")
|
||||||
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
|
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
|
||||||
times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
||||||
@@ -36,6 +37,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if logType := c.HostConfig.LogConfig.Type; logType != "json-file" {
|
||||||
|
return fmt.Errorf("\"logs\" command is supported only for \"json-file\" logging driver (got: %s)", logType)
|
||||||
|
}
|
||||||
|
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
v.Set("stdout", "1")
|
v.Set("stdout", "1")
|
||||||
v.Set("stderr", "1")
|
v.Set("stderr", "1")
|
||||||
|
|||||||
+7
-349
@@ -1,357 +1,15 @@
|
|||||||
|
// +build experimental
|
||||||
|
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"os"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
nwclient "github.com/docker/libnetwork/client"
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/daemon/network"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
|
||||||
"github.com/docker/docker/pkg/stringid"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdNetwork is the parent subcommand for all network commands
|
|
||||||
//
|
|
||||||
// Usage: docker network <COMMAND> [OPTIONS]
|
|
||||||
func (cli *DockerCli) CmdNetwork(args ...string) error {
|
func (cli *DockerCli) CmdNetwork(args ...string) error {
|
||||||
cmd := Cli.Subcmd("network", []string{"COMMAND [OPTIONS]"}, networkUsage(), false)
|
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
|
||||||
cmd.Require(flag.Min, 1)
|
args = append([]string{"network"}, args...)
|
||||||
err := cmd.ParseFlags(args, true)
|
return nCli.Cmd(os.Args[0], args...)
|
||||||
cmd.Usage()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdNetworkCreate creates a new network with a given name
|
|
||||||
//
|
|
||||||
// Usage: docker network create [OPTIONS] <NETWORK-NAME>
|
|
||||||
func (cli *DockerCli) CmdNetworkCreate(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("network create", []string{"NETWORK-NAME"}, "Creates a new network with a name specified by the user", false)
|
|
||||||
flDriver := cmd.String([]string{"d", "-driver"}, "bridge", "Driver to manage the Network")
|
|
||||||
flOpts := opts.NewMapOpts(nil, nil)
|
|
||||||
|
|
||||||
flIpamDriver := cmd.String([]string{"-ipam-driver"}, "default", "IP Address Management Driver")
|
|
||||||
flIpamSubnet := opts.NewListOpts(nil)
|
|
||||||
flIpamIPRange := opts.NewListOpts(nil)
|
|
||||||
flIpamGateway := opts.NewListOpts(nil)
|
|
||||||
flIpamAux := opts.NewMapOpts(nil, nil)
|
|
||||||
|
|
||||||
cmd.Var(&flIpamSubnet, []string{"-subnet"}, "subnet in CIDR format that represents a network segment")
|
|
||||||
cmd.Var(&flIpamIPRange, []string{"-ip-range"}, "allocate container ip from a sub-range")
|
|
||||||
cmd.Var(&flIpamGateway, []string{"-gateway"}, "ipv4 or ipv6 Gateway for the master subnet")
|
|
||||||
cmd.Var(flIpamAux, []string{"-aux-address"}, "auxiliary ipv4 or ipv6 addresses used by Network driver")
|
|
||||||
cmd.Var(flOpts, []string{"o", "-opt"}, "set driver specific options")
|
|
||||||
|
|
||||||
cmd.Require(flag.Exact, 1)
|
|
||||||
err := cmd.ParseFlags(args, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ipamCfg, err := consolidateIpam(flIpamSubnet.GetAll(), flIpamIPRange.GetAll(), flIpamGateway.GetAll(), flIpamAux.GetAll())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Construct network create request body
|
|
||||||
nc := types.NetworkCreate{
|
|
||||||
Name: cmd.Arg(0),
|
|
||||||
Driver: *flDriver,
|
|
||||||
IPAM: network.IPAM{Driver: *flIpamDriver, Config: ipamCfg},
|
|
||||||
Options: flOpts.GetAll(),
|
|
||||||
CheckDuplicate: true,
|
|
||||||
}
|
|
||||||
obj, _, err := readBody(cli.call("POST", "/networks/create", nc, nil))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var resp types.NetworkCreateResponse
|
|
||||||
err = json.Unmarshal(obj, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(cli.out, "%s\n", resp.ID)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdNetworkRm deletes a network
|
|
||||||
//
|
|
||||||
// Usage: docker network rm <NETWORK-NAME | NETWORK-ID>
|
|
||||||
func (cli *DockerCli) CmdNetworkRm(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("network rm", []string{"NETWORK"}, "Deletes a network", false)
|
|
||||||
cmd.Require(flag.Exact, 1)
|
|
||||||
err := cmd.ParseFlags(args, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, _, err = readBody(cli.call("DELETE", "/networks/"+cmd.Arg(0), nil, nil))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdNetworkConnect connects a container to a network
|
|
||||||
//
|
|
||||||
// Usage: docker network connect <NETWORK> <CONTAINER>
|
|
||||||
func (cli *DockerCli) CmdNetworkConnect(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("network connect", []string{"NETWORK CONTAINER"}, "Connects a container to a network", false)
|
|
||||||
cmd.Require(flag.Exact, 2)
|
|
||||||
err := cmd.ParseFlags(args, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
nc := types.NetworkConnect{Container: cmd.Arg(1)}
|
|
||||||
_, _, err = readBody(cli.call("POST", "/networks/"+cmd.Arg(0)+"/connect", nc, nil))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdNetworkDisconnect disconnects a container from a network
|
|
||||||
//
|
|
||||||
// Usage: docker network disconnect <NETWORK> <CONTAINER>
|
|
||||||
func (cli *DockerCli) CmdNetworkDisconnect(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("network disconnect", []string{"NETWORK CONTAINER"}, "Disconnects container from a network", false)
|
|
||||||
cmd.Require(flag.Exact, 2)
|
|
||||||
err := cmd.ParseFlags(args, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
nc := types.NetworkConnect{Container: cmd.Arg(1)}
|
|
||||||
_, _, err = readBody(cli.call("POST", "/networks/"+cmd.Arg(0)+"/disconnect", nc, nil))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdNetworkLs lists all the netorks managed by docker daemon
|
|
||||||
//
|
|
||||||
// Usage: docker network ls [OPTIONS]
|
|
||||||
func (cli *DockerCli) CmdNetworkLs(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("network ls", nil, "Lists networks", true)
|
|
||||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
|
||||||
noTrunc := cmd.Bool([]string{"-no-trunc"}, false, "Do not truncate the output")
|
|
||||||
|
|
||||||
cmd.Require(flag.Exact, 0)
|
|
||||||
err := cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
obj, _, err := readBody(cli.call("GET", "/networks", nil, nil))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var networkResources []types.NetworkResource
|
|
||||||
err = json.Unmarshal(obj, &networkResources)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
wr := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
|
||||||
|
|
||||||
// unless quiet (-q) is specified, print field titles
|
|
||||||
if !*quiet {
|
|
||||||
fmt.Fprintln(wr, "NETWORK ID\tNAME\tDRIVER")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, networkResource := range networkResources {
|
|
||||||
ID := networkResource.ID
|
|
||||||
netName := networkResource.Name
|
|
||||||
if !*noTrunc {
|
|
||||||
ID = stringid.TruncateID(ID)
|
|
||||||
}
|
|
||||||
if *quiet {
|
|
||||||
fmt.Fprintln(wr, ID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
driver := networkResource.Driver
|
|
||||||
fmt.Fprintf(wr, "%s\t%s\t%s\t",
|
|
||||||
ID,
|
|
||||||
netName,
|
|
||||||
driver)
|
|
||||||
fmt.Fprint(wr, "\n")
|
|
||||||
}
|
|
||||||
wr.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdNetworkInspect inspects the network object for more details
|
|
||||||
//
|
|
||||||
// Usage: docker network inspect <NETWORK>
|
|
||||||
// CmdNetworkInspect handles Network inspect UI
|
|
||||||
func (cli *DockerCli) CmdNetworkInspect(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("network inspect", []string{"NETWORK"}, "Displays detailed information on a network", false)
|
|
||||||
cmd.Require(flag.Exact, 1)
|
|
||||||
err := cmd.ParseFlags(args, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
obj, _, err := readBody(cli.call("GET", "/networks/"+cmd.Arg(0), nil, nil))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
networkResource := &types.NetworkResource{}
|
|
||||||
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(networkResource); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
indented := new(bytes.Buffer)
|
|
||||||
if err := json.Indent(indented, obj, "", " "); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := io.Copy(cli.out, indented); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Consolidates the ipam configuration as a group from differnt related configurations
|
|
||||||
// user can configure network with multiple non-overlapping subnets and hence it is
|
|
||||||
// possible to corelate the various related parameters and consolidate them.
|
|
||||||
// consoidateIpam consolidates subnets, ip-ranges, gateways and auxilary addresses into
|
|
||||||
// structured ipam data.
|
|
||||||
func consolidateIpam(subnets, ranges, gateways []string, auxaddrs map[string]string) ([]network.IPAMConfig, error) {
|
|
||||||
if len(subnets) < len(ranges) || len(subnets) < len(gateways) {
|
|
||||||
return nil, fmt.Errorf("every ip-range or gateway must have a corresponding subnet")
|
|
||||||
}
|
|
||||||
iData := map[string]*network.IPAMConfig{}
|
|
||||||
|
|
||||||
// Populate non-overlapping subnets into consolidation map
|
|
||||||
for _, s := range subnets {
|
|
||||||
for k := range iData {
|
|
||||||
ok1, err := subnetMatches(s, k)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ok2, err := subnetMatches(k, s)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ok1 || ok2 {
|
|
||||||
return nil, fmt.Errorf("multiple overlapping subnet configuration is not supported")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
iData[s] = &network.IPAMConfig{Subnet: s, AuxAddress: map[string]string{}}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate and add valid ip ranges
|
|
||||||
for _, r := range ranges {
|
|
||||||
match := false
|
|
||||||
for _, s := range subnets {
|
|
||||||
ok, err := subnetMatches(s, r)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if iData[s].IPRange != "" {
|
|
||||||
return nil, fmt.Errorf("cannot configure multiple ranges (%s, %s) on the same subnet (%s)", r, iData[s].IPRange, s)
|
|
||||||
}
|
|
||||||
d := iData[s]
|
|
||||||
d.IPRange = r
|
|
||||||
match = true
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
return nil, fmt.Errorf("no matching subnet for range %s", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate and add valid gateways
|
|
||||||
for _, g := range gateways {
|
|
||||||
match := false
|
|
||||||
for _, s := range subnets {
|
|
||||||
ok, err := subnetMatches(s, g)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if iData[s].Gateway != "" {
|
|
||||||
return nil, fmt.Errorf("cannot configure multiple gateways (%s, %s) for the same subnet (%s)", g, iData[s].Gateway, s)
|
|
||||||
}
|
|
||||||
d := iData[s]
|
|
||||||
d.Gateway = g
|
|
||||||
match = true
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
return nil, fmt.Errorf("no matching subnet for gateway %s", g)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate and add aux-addresses
|
|
||||||
for key, aa := range auxaddrs {
|
|
||||||
match := false
|
|
||||||
for _, s := range subnets {
|
|
||||||
ok, err := subnetMatches(s, aa)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
iData[s].AuxAddress[key] = aa
|
|
||||||
match = true
|
|
||||||
}
|
|
||||||
if !match {
|
|
||||||
return nil, fmt.Errorf("no matching subnet for aux-address %s", aa)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
idl := []network.IPAMConfig{}
|
|
||||||
for _, v := range iData {
|
|
||||||
idl = append(idl, *v)
|
|
||||||
}
|
|
||||||
return idl, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func subnetMatches(subnet, data string) (bool, error) {
|
|
||||||
var (
|
|
||||||
ip net.IP
|
|
||||||
)
|
|
||||||
|
|
||||||
_, s, err := net.ParseCIDR(subnet)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("Invalid subnet %s : %v", s, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(data, "/") {
|
|
||||||
ip, _, err = net.ParseCIDR(data)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("Invalid cidr %s : %v", data, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ip = net.ParseIP(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.Contains(ip), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func networkUsage() string {
|
|
||||||
networkCommands := map[string]string{
|
|
||||||
"create": "Create a network",
|
|
||||||
"connect": "Connect container to a network",
|
|
||||||
"disconnect": "Disconnect container from a network",
|
|
||||||
"inspect": "Display detailed network information",
|
|
||||||
"ls": "List all networks",
|
|
||||||
"rm": "Remove a network",
|
|
||||||
}
|
|
||||||
|
|
||||||
help := "Commands:\n"
|
|
||||||
|
|
||||||
for cmd, description := range networkCommands {
|
|
||||||
help += fmt.Sprintf(" %-25.25s%s\n", cmd, description)
|
|
||||||
}
|
|
||||||
|
|
||||||
help += fmt.Sprintf("\nRun 'docker network COMMAND --help' for more information on a command.")
|
|
||||||
return help
|
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker pause CONTAINER [CONTAINER...]
|
// Usage: docker pause CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdPause(args ...string) error {
|
func (cli *DockerCli) CmdPause(args ...string) error {
|
||||||
cmd := Cli.Subcmd("pause", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["pause"].Description, true)
|
cmd := Cli.Subcmd("pause", []string{"CONTAINER [CONTAINER...]"}, "Pause all processes within a container", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]]
|
// Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]]
|
||||||
func (cli *DockerCli) CmdPort(args ...string) error {
|
func (cli *DockerCli) CmdPort(args ...string) error {
|
||||||
cmd := Cli.Subcmd("port", []string{"CONTAINER [PRIVATE_PORT[/PROTO]]"}, Cli.DockerCommands["port"].Description, true)
|
cmd := Cli.Subcmd("port", []string{"CONTAINER [PRIVATE_PORT[/PROTO]]"}, "List port mappings for the CONTAINER, or lookup the public-facing port that\nis NAT-ed to the PRIVATE_PORT", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+2
-2
@@ -23,7 +23,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|||||||
psFilterArgs = filters.Args{}
|
psFilterArgs = filters.Args{}
|
||||||
v = url.Values{}
|
v = url.Values{}
|
||||||
|
|
||||||
cmd = Cli.Subcmd("ps", nil, Cli.DockerCommands["ps"].Description, true)
|
cmd = Cli.Subcmd("ps", nil, "List containers", true)
|
||||||
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
quiet = cmd.Bool([]string{"q", "-quiet"}, false, "Only display numeric IDs")
|
||||||
size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
|
size = cmd.Bool([]string{"s", "-size"}, false, "Display total file sizes")
|
||||||
all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
|
all = cmd.Bool([]string{"a", "-all"}, false, "Show all containers (default shows just running)")
|
||||||
@@ -95,7 +95,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
|||||||
|
|
||||||
f := *format
|
f := *format
|
||||||
if len(f) == 0 {
|
if len(f) == 0 {
|
||||||
if len(cli.PsFormat()) > 0 && !*quiet {
|
if len(cli.PsFormat()) > 0 {
|
||||||
f = cli.PsFormat()
|
f = cli.PsFormat()
|
||||||
} else {
|
} else {
|
||||||
f = "table"
|
f = "table"
|
||||||
|
|||||||
+65
-5
@@ -1,9 +1,12 @@
|
|||||||
package ps
|
package ps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/docker/docker/api"
|
"github.com/docker/docker/api"
|
||||||
@@ -61,11 +64,6 @@ func (c *containerContext) Image() string {
|
|||||||
if c.c.Image == "" {
|
if c.c.Image == "" {
|
||||||
return "<no image>"
|
return "<no image>"
|
||||||
}
|
}
|
||||||
if c.trunc {
|
|
||||||
if stringid.TruncateID(c.c.ImageID) == stringid.TruncateID(c.c.Image) {
|
|
||||||
return stringutils.Truncate(c.c.Image, 12)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c.c.Image
|
return c.c.Image
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,6 +149,68 @@ func (c *containerContext) addHeader(header string) {
|
|||||||
c.header = append(c.header, strings.ToUpper(header))
|
c.header = append(c.header, strings.ToUpper(header))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func customFormat(ctx Context, containers []types.Container) {
|
||||||
|
var (
|
||||||
|
table bool
|
||||||
|
header string
|
||||||
|
format = ctx.Format
|
||||||
|
buffer = bytes.NewBufferString("")
|
||||||
|
)
|
||||||
|
|
||||||
|
if strings.HasPrefix(ctx.Format, tableKey) {
|
||||||
|
table = true
|
||||||
|
format = format[len(tableKey):]
|
||||||
|
}
|
||||||
|
|
||||||
|
format = strings.Trim(format, " ")
|
||||||
|
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
|
||||||
|
format = r.Replace(format)
|
||||||
|
|
||||||
|
if table && ctx.Size {
|
||||||
|
format += "\t{{.Size}}"
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := template.New("").Parse(format)
|
||||||
|
if err != nil {
|
||||||
|
buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
|
||||||
|
buffer.WriteTo(ctx.Output)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, container := range containers {
|
||||||
|
containerCtx := &containerContext{
|
||||||
|
trunc: ctx.Trunc,
|
||||||
|
c: container,
|
||||||
|
}
|
||||||
|
if err := tmpl.Execute(buffer, containerCtx); err != nil {
|
||||||
|
buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
|
||||||
|
buffer.WriteTo(ctx.Output)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if table && len(header) == 0 {
|
||||||
|
header = containerCtx.fullHeader()
|
||||||
|
}
|
||||||
|
buffer.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
if table {
|
||||||
|
if len(header) == 0 {
|
||||||
|
// if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
|
||||||
|
containerCtx := &containerContext{}
|
||||||
|
tmpl.Execute(bytes.NewBufferString(""), containerCtx)
|
||||||
|
header = containerCtx.fullHeader()
|
||||||
|
}
|
||||||
|
|
||||||
|
t := tabwriter.NewWriter(ctx.Output, 20, 1, 3, ' ', 0)
|
||||||
|
t.Write([]byte(header))
|
||||||
|
t.Write([]byte("\n"))
|
||||||
|
buffer.WriteTo(t)
|
||||||
|
t.Flush()
|
||||||
|
} else {
|
||||||
|
buffer.WriteTo(ctx.Output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func stripNamePrefix(ss []string) []string {
|
func stripNamePrefix(ss []string) []string {
|
||||||
for i, s := range ss {
|
for i, s := range ss {
|
||||||
ss[i] = s[1:]
|
ss[i] = s[1:]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ps
|
package ps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestContainerPsContext(t *testing.T) {
|
func TestContainerPsContext(t *testing.T) {
|
||||||
containerID := stringid.GenerateRandomID()
|
containerId := stringid.GenerateRandomID()
|
||||||
unix := time.Now().Unix()
|
unix := time.Now().Unix()
|
||||||
|
|
||||||
var ctx containerContext
|
var ctx containerContext
|
||||||
@@ -22,40 +23,17 @@ func TestContainerPsContext(t *testing.T) {
|
|||||||
expHeader string
|
expHeader string
|
||||||
call func() string
|
call func() string
|
||||||
}{
|
}{
|
||||||
{types.Container{ID: containerID}, true, stringid.TruncateID(containerID), idHeader, ctx.ID},
|
{types.Container{ID: containerId}, true, stringid.TruncateID(containerId), idHeader, ctx.ID},
|
||||||
{types.Container{ID: containerID}, false, containerID, idHeader, ctx.ID},
|
|
||||||
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
|
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
|
||||||
{types.Container{Image: "ubuntu"}, true, "ubuntu", imageHeader, ctx.Image},
|
{types.Container{Image: "ubuntu"}, true, "ubuntu", imageHeader, ctx.Image},
|
||||||
{types.Container{Image: "verylongimagename"}, true, "verylongimagename", imageHeader, ctx.Image},
|
|
||||||
{types.Container{Image: "verylongimagename"}, false, "verylongimagename", imageHeader, ctx.Image},
|
|
||||||
{types.Container{
|
|
||||||
Image: "a5a665ff33eced1e0803148700880edab4",
|
|
||||||
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
"a5a665ff33ec",
|
|
||||||
imageHeader,
|
|
||||||
ctx.Image,
|
|
||||||
},
|
|
||||||
{types.Container{
|
|
||||||
Image: "a5a665ff33eced1e0803148700880edab4",
|
|
||||||
ImageID: "a5a665ff33eced1e0803148700880edab4269067ed77e27737a708d0d293fbf5",
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
"a5a665ff33eced1e0803148700880edab4",
|
|
||||||
imageHeader,
|
|
||||||
ctx.Image,
|
|
||||||
},
|
|
||||||
{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
|
{types.Container{Image: ""}, true, "<no image>", imageHeader, ctx.Image},
|
||||||
{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
|
{types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command},
|
||||||
{types.Container{Created: unix}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
|
{types.Container{Created: int(unix)}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt},
|
||||||
{types.Container{Ports: []types.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
|
{types.Container{Ports: []types.Port{{PrivatePort: 8080, PublicPort: 8080, Type: "tcp"}}}, true, "8080/tcp", portsHeader, ctx.Ports},
|
||||||
{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
|
{types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
|
||||||
{types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
|
{types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size},
|
||||||
{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
|
{types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size},
|
||||||
{types.Container{}, true, "", labelsHeader, ctx.Labels},
|
|
||||||
{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
|
{types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels},
|
||||||
{types.Container{Created: unix}, true, "Less than a second", runningForHeader, ctx.RunningFor},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, c := range cases {
|
for _, c := range cases {
|
||||||
@@ -90,8 +68,8 @@ func TestContainerPsContext(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
c1 := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
|
c := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
|
||||||
ctx = containerContext{c: c1, trunc: true}
|
ctx = containerContext{c: c, trunc: true}
|
||||||
|
|
||||||
sid := ctx.Label("com.docker.swarm.swarm-id")
|
sid := ctx.Label("com.docker.swarm.swarm-id")
|
||||||
node := ctx.Label("com.docker.swarm.node_name")
|
node := ctx.Label("com.docker.swarm.node_name")
|
||||||
@@ -108,19 +86,17 @@ func TestContainerPsContext(t *testing.T) {
|
|||||||
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
|
t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
c2 := types.Container{}
|
|
||||||
ctx = containerContext{c: c2, trunc: true}
|
|
||||||
|
|
||||||
label := ctx.Label("anything.really")
|
|
||||||
if label != "" {
|
|
||||||
t.Fatalf("Expected an empty string, was %s", label)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx = containerContext{c: c2, trunc: true}
|
func TestContainerPsFormatError(t *testing.T) {
|
||||||
fullHeader := ctx.fullHeader()
|
out := bytes.NewBufferString("")
|
||||||
if fullHeader != "" {
|
ctx := Context{
|
||||||
t.Fatalf("Expected fullHeader to be empty, was %s", fullHeader)
|
Format: "{{InvalidFunction}}",
|
||||||
|
Output: out,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
customFormat(ctx, make([]types.Container, 0))
|
||||||
|
if out.String() != "Template parsing error: template: :1: function \"InvalidFunction\" not defined\n" {
|
||||||
|
t.Fatalf("Expected format error, got `%v`\n", out.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
package ps
|
package ps
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
)
|
)
|
||||||
@@ -19,22 +14,14 @@ const (
|
|||||||
defaultQuietFormat = "{{.ID}}"
|
defaultQuietFormat = "{{.ID}}"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Context contains information required by the formatter to print the output as desired.
|
|
||||||
type Context struct {
|
type Context struct {
|
||||||
// Output is the output stream to which the formatted string is written.
|
|
||||||
Output io.Writer
|
Output io.Writer
|
||||||
// Format is used to choose raw, table or custom format for the output.
|
|
||||||
Format string
|
Format string
|
||||||
// Size when set to true will display the size of the output.
|
|
||||||
Size bool
|
Size bool
|
||||||
// Quiet when set to true will simply print minimal information.
|
|
||||||
Quiet bool
|
Quiet bool
|
||||||
// Trunc when set to true will truncate the output of certain fields such as Container ID.
|
|
||||||
Trunc bool
|
Trunc bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Format helps to format the output using the parameters set in the Context.
|
|
||||||
// Currently Format allow to display in raw, table or custom format the output.
|
|
||||||
func Format(ctx Context, containers []types.Container) {
|
func Format(ctx Context, containers []types.Container) {
|
||||||
switch ctx.Format {
|
switch ctx.Format {
|
||||||
case tableFormatKey:
|
case tableFormatKey:
|
||||||
@@ -76,65 +63,3 @@ func tableFormat(ctx Context, containers []types.Container) {
|
|||||||
|
|
||||||
customFormat(ctx, containers)
|
customFormat(ctx, containers)
|
||||||
}
|
}
|
||||||
|
|
||||||
func customFormat(ctx Context, containers []types.Container) {
|
|
||||||
var (
|
|
||||||
table bool
|
|
||||||
header string
|
|
||||||
format = ctx.Format
|
|
||||||
buffer = bytes.NewBufferString("")
|
|
||||||
)
|
|
||||||
|
|
||||||
if strings.HasPrefix(ctx.Format, tableKey) {
|
|
||||||
table = true
|
|
||||||
format = format[len(tableKey):]
|
|
||||||
}
|
|
||||||
|
|
||||||
format = strings.Trim(format, " ")
|
|
||||||
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
|
|
||||||
format = r.Replace(format)
|
|
||||||
|
|
||||||
if table && ctx.Size {
|
|
||||||
format += "\t{{.Size}}"
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpl, err := template.New("").Parse(format)
|
|
||||||
if err != nil {
|
|
||||||
buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err))
|
|
||||||
buffer.WriteTo(ctx.Output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, container := range containers {
|
|
||||||
containerCtx := &containerContext{
|
|
||||||
trunc: ctx.Trunc,
|
|
||||||
c: container,
|
|
||||||
}
|
|
||||||
if err := tmpl.Execute(buffer, containerCtx); err != nil {
|
|
||||||
buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err))
|
|
||||||
buffer.WriteTo(ctx.Output)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if table && len(header) == 0 {
|
|
||||||
header = containerCtx.fullHeader()
|
|
||||||
}
|
|
||||||
buffer.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
if table {
|
|
||||||
if len(header) == 0 {
|
|
||||||
// if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template
|
|
||||||
containerCtx := &containerContext{}
|
|
||||||
tmpl.Execute(bytes.NewBufferString(""), containerCtx)
|
|
||||||
header = containerCtx.fullHeader()
|
|
||||||
}
|
|
||||||
|
|
||||||
t := tabwriter.NewWriter(ctx.Output, 20, 1, 3, ' ', 0)
|
|
||||||
t.Write([]byte(header))
|
|
||||||
t.Write([]byte("\n"))
|
|
||||||
buffer.WriteTo(t)
|
|
||||||
t.Flush()
|
|
||||||
} else {
|
|
||||||
buffer.WriteTo(ctx.Output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,208 +0,0 @@
|
|||||||
package ps
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestFormat(t *testing.T) {
|
|
||||||
contexts := []struct {
|
|
||||||
context Context
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
// Errors
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "{{InvalidFunction}}",
|
|
||||||
},
|
|
||||||
`Template parsing error: template: :1: function "InvalidFunction" not defined
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "{{nil}}",
|
|
||||||
},
|
|
||||||
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
// Table Format
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "table",
|
|
||||||
},
|
|
||||||
`CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
|
||||||
containerID1 ubuntu "" 45 years ago foobar_baz
|
|
||||||
containerID2 ubuntu "" 45 years ago foobar_bar
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
},
|
|
||||||
"IMAGE\nubuntu\nubuntu\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"IMAGE SIZE\nubuntu 0 B\nubuntu 0 B\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
"IMAGE\nubuntu\nubuntu\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "table",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
"containerID1\ncontainerID2\n",
|
|
||||||
},
|
|
||||||
// Raw Format
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "raw",
|
|
||||||
},
|
|
||||||
`container_id: containerID1
|
|
||||||
image: ubuntu
|
|
||||||
command: ""
|
|
||||||
created_at: 1970-01-01 00:00:00 +0000 UTC
|
|
||||||
status:
|
|
||||||
names: foobar_baz
|
|
||||||
labels:
|
|
||||||
ports:
|
|
||||||
|
|
||||||
container_id: containerID2
|
|
||||||
image: ubuntu
|
|
||||||
command: ""
|
|
||||||
created_at: 1970-01-01 00:00:00 +0000 UTC
|
|
||||||
status:
|
|
||||||
names: foobar_bar
|
|
||||||
labels:
|
|
||||||
ports:
|
|
||||||
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "raw",
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
`container_id: containerID1
|
|
||||||
image: ubuntu
|
|
||||||
command: ""
|
|
||||||
created_at: 1970-01-01 00:00:00 +0000 UTC
|
|
||||||
status:
|
|
||||||
names: foobar_baz
|
|
||||||
labels:
|
|
||||||
ports:
|
|
||||||
size: 0 B
|
|
||||||
|
|
||||||
container_id: containerID2
|
|
||||||
image: ubuntu
|
|
||||||
command: ""
|
|
||||||
created_at: 1970-01-01 00:00:00 +0000 UTC
|
|
||||||
status:
|
|
||||||
names: foobar_bar
|
|
||||||
labels:
|
|
||||||
ports:
|
|
||||||
size: 0 B
|
|
||||||
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "raw",
|
|
||||||
Quiet: true,
|
|
||||||
},
|
|
||||||
"container_id: containerID1\ncontainer_id: containerID2\n",
|
|
||||||
},
|
|
||||||
// Custom Format
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "{{.Image}}",
|
|
||||||
},
|
|
||||||
"ubuntu\nubuntu\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "{{.Image}}",
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"ubuntu\nubuntu\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, context := range contexts {
|
|
||||||
containers := []types.Container{
|
|
||||||
{ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu"},
|
|
||||||
{ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu"},
|
|
||||||
}
|
|
||||||
out := bytes.NewBufferString("")
|
|
||||||
context.context.Output = out
|
|
||||||
Format(context.context, containers)
|
|
||||||
actual := out.String()
|
|
||||||
if actual != context.expected {
|
|
||||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
|
||||||
}
|
|
||||||
// Clean buffer
|
|
||||||
out.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCustomFormatNoContainers(t *testing.T) {
|
|
||||||
out := bytes.NewBufferString("")
|
|
||||||
containers := []types.Container{}
|
|
||||||
|
|
||||||
contexts := []struct {
|
|
||||||
context Context
|
|
||||||
expected string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "{{.Image}}",
|
|
||||||
Output: out,
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
Output: out,
|
|
||||||
},
|
|
||||||
"IMAGE\n",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "{{.Image}}",
|
|
||||||
Output: out,
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Context{
|
|
||||||
Format: "table {{.Image}}",
|
|
||||||
Output: out,
|
|
||||||
Size: true,
|
|
||||||
},
|
|
||||||
"IMAGE SIZE\n",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, context := range contexts {
|
|
||||||
customFormat(context.context, containers)
|
|
||||||
actual := out.String()
|
|
||||||
if actual != context.expected {
|
|
||||||
t.Fatalf("Expected \n%s, got \n%s", context.expected, actual)
|
|
||||||
}
|
|
||||||
// Clean buffer
|
|
||||||
out.Reset()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+2
-2
@@ -15,7 +15,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST]
|
// Usage: docker pull [OPTIONS] IMAGENAME[:TAG|@DIGEST]
|
||||||
func (cli *DockerCli) CmdPull(args ...string) error {
|
func (cli *DockerCli) CmdPull(args ...string) error {
|
||||||
cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, Cli.DockerCommands["pull"].Description, true)
|
cmd := Cli.Subcmd("pull", []string{"NAME[:TAG|@DIGEST]"}, "Pull an image or a repository from a registry", true)
|
||||||
allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
|
allTags := cmd.Bool([]string{"a", "-all-tags"}, false, "Download all tagged images in the repository")
|
||||||
addTrustedFlags(cmd, true)
|
addTrustedFlags(cmd, true)
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
@@ -25,7 +25,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
|||||||
|
|
||||||
taglessRemote, tag := parsers.ParseRepositoryTag(remote)
|
taglessRemote, tag := parsers.ParseRepositoryTag(remote)
|
||||||
if tag == "" && !*allTags {
|
if tag == "" && !*allTags {
|
||||||
tag = tags.DefaultTag
|
tag = tags.DEFAULTTAG
|
||||||
fmt.Fprintf(cli.out, "Using default tag: %s\n", tag)
|
fmt.Fprintf(cli.out, "Using default tag: %s\n", tag)
|
||||||
} else if tag != "" && *allTags {
|
} else if tag != "" && *allTags {
|
||||||
return fmt.Errorf("tag can't be used with --all-tags/-a")
|
return fmt.Errorf("tag can't be used with --all-tags/-a")
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker push NAME[:TAG]
|
// Usage: docker push NAME[:TAG]
|
||||||
func (cli *DockerCli) CmdPush(args ...string) error {
|
func (cli *DockerCli) CmdPush(args ...string) error {
|
||||||
cmd := Cli.Subcmd("push", []string{"NAME[:TAG]"}, Cli.DockerCommands["push"].Description, true)
|
cmd := Cli.Subcmd("push", []string{"NAME[:TAG]"}, "Push an image or a repository to a registry", true)
|
||||||
addTrustedFlags(cmd, false)
|
addTrustedFlags(cmd, false)
|
||||||
cmd.Require(flag.Exact, 1)
|
cmd.Require(flag.Exact, 1)
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
Cli "github.com/docker/docker/cli"
|
Cli "github.com/docker/docker/cli"
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
@@ -12,17 +11,13 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker rename OLD_NAME NEW_NAME
|
// Usage: docker rename OLD_NAME NEW_NAME
|
||||||
func (cli *DockerCli) CmdRename(args ...string) error {
|
func (cli *DockerCli) CmdRename(args ...string) error {
|
||||||
cmd := Cli.Subcmd("rename", []string{"OLD_NAME NEW_NAME"}, Cli.DockerCommands["rename"].Description, true)
|
cmd := Cli.Subcmd("rename", []string{"OLD_NAME NEW_NAME"}, "Rename a container", true)
|
||||||
cmd.Require(flag.Exact, 2)
|
cmd.Require(flag.Exact, 2)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
oldName := strings.TrimSpace(cmd.Arg(0))
|
oldName := cmd.Arg(0)
|
||||||
newName := strings.TrimSpace(cmd.Arg(1))
|
newName := cmd.Arg(1)
|
||||||
|
|
||||||
if oldName == "" || newName == "" {
|
|
||||||
return fmt.Errorf("Error: Neither old nor new names may be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName), nil, nil)); err != nil {
|
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/rename?name=%s", oldName, newName), nil, nil)); err != nil {
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
fmt.Fprintf(cli.err, "%s\n", err)
|
||||||
|
|||||||
@@ -9,11 +9,11 @@ import (
|
|||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdRestart restarts one or more containers.
|
// CmdRestart restarts one or more running containers.
|
||||||
//
|
//
|
||||||
// Usage: docker restart [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdRestart(args ...string) error {
|
func (cli *DockerCli) CmdRestart(args ...string) error {
|
||||||
cmd := Cli.Subcmd("restart", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["restart"].Description, true)
|
cmd := Cli.Subcmd("restart", []string{"CONTAINER [CONTAINER...]"}, "Restart a running container", true)
|
||||||
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container")
|
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing the container")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdRm(args ...string) error {
|
func (cli *DockerCli) CmdRm(args ...string) error {
|
||||||
cmd := Cli.Subcmd("rm", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["rm"].Description, true)
|
cmd := Cli.Subcmd("rm", []string{"CONTAINER [CONTAINER...]"}, "Remove one or more containers", true)
|
||||||
v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container")
|
v := cmd.Bool([]string{"v", "-volumes"}, false, "Remove the volumes associated with the container")
|
||||||
link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link")
|
link := cmd.Bool([]string{"l", "#link", "-link"}, false, "Remove the specified link")
|
||||||
force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
|
force := cmd.Bool([]string{"f", "-force"}, false, "Force the removal of a running container (uses SIGKILL)")
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
|
// Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
|
||||||
func (cli *DockerCli) CmdRmi(args ...string) error {
|
func (cli *DockerCli) CmdRmi(args ...string) error {
|
||||||
cmd := Cli.Subcmd("rmi", []string{"IMAGE [IMAGE...]"}, Cli.DockerCommands["rmi"].Description, true)
|
cmd := Cli.Subcmd("rmi", []string{"IMAGE [IMAGE...]"}, "Remove one or more images", true)
|
||||||
force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image")
|
force := cmd.Bool([]string{"f", "-force"}, false, "Force removal of the image")
|
||||||
noprune := cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
|
noprune := cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|||||||
+3
-3
@@ -40,7 +40,7 @@ func (cid *cidFile) Write(id string) error {
|
|||||||
//
|
//
|
||||||
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
|
// Usage: docker run [OPTIONS] IMAGE [COMMAND] [ARG...]
|
||||||
func (cli *DockerCli) CmdRun(args ...string) error {
|
func (cli *DockerCli) CmdRun(args ...string) error {
|
||||||
cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, Cli.DockerCommands["run"].Description, true)
|
cmd := Cli.Subcmd("run", []string{"IMAGE [COMMAND] [ARG...]"}, "Run a command in a new container", true)
|
||||||
addTrustedFlags(cmd, true)
|
addTrustedFlags(cmd, true)
|
||||||
|
|
||||||
// These are flags not stored in Config/HostConfig
|
// These are flags not stored in Config/HostConfig
|
||||||
@@ -63,11 +63,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
|||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(hostConfig.DNS) > 0 {
|
if len(hostConfig.Dns) > 0 {
|
||||||
// check the DNS settings passed via --dns against
|
// check the DNS settings passed via --dns against
|
||||||
// localhost regexp to warn if they are trying to
|
// localhost regexp to warn if they are trying to
|
||||||
// set a DNS to a localhost address
|
// set a DNS to a localhost address
|
||||||
for _, dnsIP := range hostConfig.DNS {
|
for _, dnsIP := range hostConfig.Dns {
|
||||||
if dns.IsLocalhost(dnsIP) {
|
if dns.IsLocalhost(dnsIP) {
|
||||||
fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
|
fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
|
||||||
break
|
break
|
||||||
|
|||||||
+15
-9
@@ -2,6 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -15,24 +16,23 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker save [OPTIONS] IMAGE [IMAGE...]
|
// Usage: docker save [OPTIONS] IMAGE [IMAGE...]
|
||||||
func (cli *DockerCli) CmdSave(args ...string) error {
|
func (cli *DockerCli) CmdSave(args ...string) error {
|
||||||
cmd := Cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, Cli.DockerCommands["save"].Description+" (streamed to STDOUT by default)", true)
|
cmd := Cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, "Save an image(s) to a tar archive (streamed to STDOUT by default)", true)
|
||||||
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
outfile := cmd.String([]string{"o", "-output"}, "", "Write to an file, instead of STDOUT")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
output = cli.out
|
output io.Writer = cli.out
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if *outfile == "" && cli.isTerminalOut {
|
|
||||||
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
|
||||||
}
|
|
||||||
if *outfile != "" {
|
if *outfile != "" {
|
||||||
if output, err = os.Create(*outfile); err != nil {
|
output, err = os.Create(*outfile)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
} else if cli.isTerminalOut {
|
||||||
|
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
||||||
}
|
}
|
||||||
|
|
||||||
sopts := &streamOpts{
|
sopts := &streamOpts{
|
||||||
@@ -40,6 +40,12 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
|||||||
out: output,
|
out: output,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(cmd.Args()) == 1 {
|
||||||
|
image := cmd.Arg(0)
|
||||||
|
if _, err := cli.stream("GET", "/images/"+image+"/get", sopts); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
v := url.Values{}
|
v := url.Values{}
|
||||||
for _, arg := range cmd.Args() {
|
for _, arg := range cmd.Args() {
|
||||||
v.Add("names", arg)
|
v.Add("names", arg)
|
||||||
@@ -47,6 +53,6 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
|||||||
if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func (r ByStars) Less(i, j int) bool { return r[i].StarCount < r[j].StarCount }
|
|||||||
//
|
//
|
||||||
// Usage: docker search [OPTIONS] TERM
|
// Usage: docker search [OPTIONS] TERM
|
||||||
func (cli *DockerCli) CmdSearch(args ...string) error {
|
func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||||
cmd := Cli.Subcmd("search", []string{"TERM"}, Cli.DockerCommands["search"].Description, true)
|
cmd := Cli.Subcmd("search", []string{"TERM"}, "Search the Docker Hub for images", true)
|
||||||
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
noTrunc := cmd.Bool([]string{"#notrunc", "-no-trunc"}, false, "Don't truncate output")
|
||||||
trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds")
|
trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted builds")
|
||||||
automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
|
automated := cmd.Bool([]string{"-automated"}, false, "Only show automated builds")
|
||||||
@@ -41,13 +41,12 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|||||||
|
|
||||||
// Resolve the Repository name from fqn to hostname + name
|
// Resolve the Repository name from fqn to hostname + name
|
||||||
taglessRemote, _ := parsers.ParseRepositoryTag(name)
|
taglessRemote, _ := parsers.ParseRepositoryTag(name)
|
||||||
|
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
|
||||||
indexInfo, err := registry.ParseIndexInfo(taglessRemote)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, indexInfo, "search")
|
rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -64,7 +63,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
|||||||
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
|
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
|
||||||
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
|
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
|
||||||
for _, res := range results {
|
for _, res := range results {
|
||||||
if (*automated && !res.IsAutomated) || (int(*stars) > res.StarCount) || (*trusted && !res.IsTrusted) {
|
if ((*automated || *trusted) && (!res.IsTrusted && !res.IsAutomated)) || (int(*stars) > res.StarCount) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
desc := strings.Replace(res.Description, "\n", " ", -1)
|
desc := strings.Replace(res.Description, "\n", " ", -1)
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
// +build experimental
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
nwclient "github.com/docker/libnetwork/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (cli *DockerCli) CmdService(args ...string) error {
|
||||||
|
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
|
||||||
|
args = append([]string{"service"}, args...)
|
||||||
|
return nCli.Cmd(os.Args[0], args...)
|
||||||
|
}
|
||||||
+2
-3
@@ -32,7 +32,6 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
|||||||
}
|
}
|
||||||
if sig == "" {
|
if sig == "" {
|
||||||
fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
|
fmt.Fprintf(cli.err, "Unsupported signal: %v. Discarding.\n", s)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, nil)); err != nil {
|
if _, _, err := readBody(cli.call("POST", fmt.Sprintf("/containers/%s/kill?signal=%s", cid, sig), nil, nil)); err != nil {
|
||||||
logrus.Debugf("Error sending signal: %s", err)
|
logrus.Debugf("Error sending signal: %s", err)
|
||||||
@@ -42,11 +41,11 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
|||||||
return sigc
|
return sigc
|
||||||
}
|
}
|
||||||
|
|
||||||
// CmdStart starts one or more containers.
|
// CmdStart starts one or more stopped containers.
|
||||||
//
|
//
|
||||||
// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdStart(args ...string) error {
|
func (cli *DockerCli) CmdStart(args ...string) error {
|
||||||
cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["start"].Description, true)
|
cmd := Cli.Subcmd("start", []string{"CONTAINER [CONTAINER...]"}, "Start one or more stopped containers", true)
|
||||||
attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
|
attach := cmd.Bool([]string{"a", "-attach"}, false, "Attach STDOUT/STDERR and forward signals")
|
||||||
openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
|
openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|||||||
+16
-54
@@ -25,8 +25,6 @@ type containerStats struct {
|
|||||||
MemoryPercentage float64
|
MemoryPercentage float64
|
||||||
NetworkRx float64
|
NetworkRx float64
|
||||||
NetworkTx float64
|
NetworkTx float64
|
||||||
BlockRead float64
|
|
||||||
BlockWrite float64
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
@@ -56,33 +54,25 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|||||||
)
|
)
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
var v *types.StatsJSON
|
var v *types.Stats
|
||||||
if err := dec.Decode(&v); err != nil {
|
if err := dec.Decode(&v); err != nil {
|
||||||
u <- err
|
u <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var (
|
||||||
var memPercent = 0.0
|
|
||||||
var cpuPercent = 0.0
|
|
||||||
|
|
||||||
// MemoryStats.Limit will never be 0 unless the container is not running and we havn't
|
|
||||||
// got any data from cgroup
|
|
||||||
if v.MemoryStats.Limit != 0 {
|
|
||||||
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
||||||
}
|
cpuPercent = 0.0
|
||||||
|
)
|
||||||
previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
|
previousCPU = v.PreCpuStats.CpuUsage.TotalUsage
|
||||||
previousSystem = v.PreCPUStats.SystemUsage
|
previousSystem = v.PreCpuStats.SystemUsage
|
||||||
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
||||||
blkRead, blkWrite := calculateBlockIO(v.BlkioStats)
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.CPUPercentage = cpuPercent
|
s.CPUPercentage = cpuPercent
|
||||||
s.Memory = float64(v.MemoryStats.Usage)
|
s.Memory = float64(v.MemoryStats.Usage)
|
||||||
s.MemoryLimit = float64(v.MemoryStats.Limit)
|
s.MemoryLimit = float64(v.MemoryStats.Limit)
|
||||||
s.MemoryPercentage = memPercent
|
s.MemoryPercentage = memPercent
|
||||||
s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
|
s.NetworkRx = float64(v.Network.RxBytes)
|
||||||
s.BlockRead = float64(blkRead)
|
s.NetworkTx = float64(v.Network.TxBytes)
|
||||||
s.BlockWrite = float64(blkWrite)
|
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
u <- nil
|
u <- nil
|
||||||
if !streamStats {
|
if !streamStats {
|
||||||
@@ -99,11 +89,6 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
|||||||
s.CPUPercentage = 0
|
s.CPUPercentage = 0
|
||||||
s.Memory = 0
|
s.Memory = 0
|
||||||
s.MemoryPercentage = 0
|
s.MemoryPercentage = 0
|
||||||
s.MemoryLimit = 0
|
|
||||||
s.NetworkRx = 0
|
|
||||||
s.NetworkTx = 0
|
|
||||||
s.BlockRead = 0
|
|
||||||
s.BlockWrite = 0
|
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
case err := <-u:
|
case err := <-u:
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -125,13 +110,12 @@ func (s *containerStats) Display(w io.Writer) error {
|
|||||||
if s.err != nil {
|
if s.err != nil {
|
||||||
return s.err
|
return s.err
|
||||||
}
|
}
|
||||||
fmt.Fprintf(w, "%s\t%.2f%%\t%s / %s\t%.2f%%\t%s / %s\t%s / %s\n",
|
fmt.Fprintf(w, "%s\t%.2f%%\t%s/%s\t%.2f%%\t%s/%s\n",
|
||||||
s.Name,
|
s.Name,
|
||||||
s.CPUPercentage,
|
s.CPUPercentage,
|
||||||
units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
|
units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
|
||||||
s.MemoryPercentage,
|
s.MemoryPercentage,
|
||||||
units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx),
|
units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx))
|
||||||
units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +125,7 @@ func (s *containerStats) Display(w io.Writer) error {
|
|||||||
//
|
//
|
||||||
// Usage: docker stats CONTAINER [CONTAINER...]
|
// Usage: docker stats CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdStats(args ...string) error {
|
func (cli *DockerCli) CmdStats(args ...string) error {
|
||||||
cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["stats"].Description, true)
|
cmd := Cli.Subcmd("stats", []string{"CONTAINER [CONTAINER...]"}, "Display a live stream of one or more containers' resource usage statistics", true)
|
||||||
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
|
noStream := cmd.Bool([]string{"-no-stream"}, false, "Disable streaming stats and only pull the first result")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
@@ -158,7 +142,7 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
|||||||
fmt.Fprint(cli.out, "\033[2J")
|
fmt.Fprint(cli.out, "\033[2J")
|
||||||
fmt.Fprint(cli.out, "\033[H")
|
fmt.Fprint(cli.out, "\033[H")
|
||||||
}
|
}
|
||||||
io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE / LIMIT\tMEM %\tNET I/O\tBLOCK I/O\n")
|
io.WriteString(w, "CONTAINER\tCPU %\tMEM USAGE/LIMIT\tMEM %\tNET I/O\n")
|
||||||
}
|
}
|
||||||
for _, n := range names {
|
for _, n := range names {
|
||||||
s := &containerStats{Name: n}
|
s := &containerStats{Name: n}
|
||||||
@@ -202,39 +186,17 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) float64 {
|
||||||
var (
|
var (
|
||||||
cpuPercent = 0.0
|
cpuPercent = 0.0
|
||||||
// calculate the change for the cpu usage of the container in between readings
|
// calculate the change for the cpu usage of the container in between readings
|
||||||
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage - previousCPU)
|
cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCPU)
|
||||||
// calculate the change for the entire system between readings
|
// calculate the change for the entire system between readings
|
||||||
systemDelta = float64(v.CPUStats.SystemUsage - previousSystem)
|
systemDelta = float64(v.CpuStats.SystemUsage - previousSystem)
|
||||||
)
|
)
|
||||||
|
|
||||||
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
if systemDelta > 0.0 && cpuDelta > 0.0 {
|
||||||
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CPUStats.CPUUsage.PercpuUsage)) * 100.0
|
cpuPercent = (cpuDelta / systemDelta) * float64(len(v.CpuStats.CpuUsage.PercpuUsage)) * 100.0
|
||||||
}
|
}
|
||||||
return cpuPercent
|
return cpuPercent
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
|
|
||||||
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
|
||||||
switch strings.ToLower(bioEntry.Op) {
|
|
||||||
case "read":
|
|
||||||
blkRead = blkRead + bioEntry.Value
|
|
||||||
case "write":
|
|
||||||
blkWrite = blkWrite + bioEntry.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
|
|
||||||
var rx, tx float64
|
|
||||||
|
|
||||||
for _, v := range network {
|
|
||||||
rx += float64(v.RxBytes)
|
|
||||||
tx += float64(v.TxBytes)
|
|
||||||
}
|
|
||||||
return rx, tx
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestDisplay(t *testing.T) {
|
func TestDisplay(t *testing.T) {
|
||||||
@@ -17,8 +15,6 @@ func TestDisplay(t *testing.T) {
|
|||||||
MemoryPercentage: 100.0 / 2048.0 * 100.0,
|
MemoryPercentage: 100.0 / 2048.0 * 100.0,
|
||||||
NetworkRx: 100 * 1024 * 1024,
|
NetworkRx: 100 * 1024 * 1024,
|
||||||
NetworkTx: 800 * 1024 * 1024,
|
NetworkTx: 800 * 1024 * 1024,
|
||||||
BlockRead: 100 * 1024 * 1024,
|
|
||||||
BlockWrite: 800 * 1024 * 1024,
|
|
||||||
mu: sync.RWMutex{},
|
mu: sync.RWMutex{},
|
||||||
}
|
}
|
||||||
var b bytes.Buffer
|
var b bytes.Buffer
|
||||||
@@ -26,21 +22,8 @@ func TestDisplay(t *testing.T) {
|
|||||||
t.Fatalf("c.Display() gave error: %s", err)
|
t.Fatalf("c.Display() gave error: %s", err)
|
||||||
}
|
}
|
||||||
got := b.String()
|
got := b.String()
|
||||||
want := "app\t30.00%\t104.9 MB / 2.147 GB\t4.88%\t104.9 MB / 838.9 MB\t104.9 MB / 838.9 MB\n"
|
want := "app\t30.00%\t104.9 MB/2.147 GB\t4.88%\t104.9 MB/838.9 MB\n"
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Fatalf("c.Display() = %q, want %q", got, want)
|
t.Fatalf("c.Display() = %q, want %q", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCalculBlockIO(t *testing.T) {
|
|
||||||
blkio := types.BlkioStats{
|
|
||||||
IoServiceBytesRecursive: []types.BlkioStatEntry{{8, 0, "read", 1234}, {8, 1, "read", 4567}, {8, 0, "write", 123}, {8, 1, "write", 456}},
|
|
||||||
}
|
|
||||||
blkRead, blkWrite := calculateBlockIO(blkio)
|
|
||||||
if blkRead != 5801 {
|
|
||||||
t.Fatalf("blkRead = %d, want 5801", blkRead)
|
|
||||||
}
|
|
||||||
if blkWrite != 579 {
|
|
||||||
t.Fatalf("blkWrite = %d, want 579", blkWrite)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-2
@@ -9,13 +9,13 @@ import (
|
|||||||
flag "github.com/docker/docker/pkg/mflag"
|
flag "github.com/docker/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CmdStop stops one or more containers.
|
// CmdStop stops one or more running containers.
|
||||||
//
|
//
|
||||||
// A running container is stopped by first sending SIGTERM and then SIGKILL if the container fails to stop within a grace period (the default is 10 seconds).
|
// A running container is stopped by first sending SIGTERM and then SIGKILL if the container fails to stop within a grace period (the default is 10 seconds).
|
||||||
//
|
//
|
||||||
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdStop(args ...string) error {
|
func (cli *DockerCli) CmdStop(args ...string) error {
|
||||||
cmd := Cli.Subcmd("stop", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["stop"].Description+".\nSending SIGTERM and then SIGKILL after a grace period", true)
|
cmd := Cli.Subcmd("stop", []string{"CONTAINER [CONTAINER...]"}, "Stop a running container by sending SIGTERM and then SIGKILL after a\ngrace period", true)
|
||||||
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it")
|
nSeconds := cmd.Int([]string{"t", "-time"}, 10, "Seconds to wait for stop before killing it")
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
|
// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
|
||||||
func (cli *DockerCli) CmdTag(args ...string) error {
|
func (cli *DockerCli) CmdTag(args ...string) error {
|
||||||
cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, Cli.DockerCommands["tag"].Description, true)
|
cmd := Cli.Subcmd("tag", []string{"IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]"}, "Tag an image into a repository", true)
|
||||||
force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
|
force := cmd.Bool([]string{"f", "#force", "-force"}, false, "Force")
|
||||||
cmd.Require(flag.Exact, 2)
|
cmd.Require(flag.Exact, 2)
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker top CONTAINER
|
// Usage: docker top CONTAINER
|
||||||
func (cli *DockerCli) CmdTop(args ...string) error {
|
func (cli *DockerCli) CmdTop(args ...string) error {
|
||||||
cmd := Cli.Subcmd("top", []string{"CONTAINER [ps OPTIONS]"}, Cli.DockerCommands["top"].Description, true)
|
cmd := Cli.Subcmd("top", []string{"CONTAINER [ps OPTIONS]"}, "Display the running processes of a container", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+21
-44
@@ -15,6 +15,7 @@ import (
|
|||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
@@ -78,19 +79,17 @@ func (cli *DockerCli) certificateDirectory(server string) (string, error) {
|
|||||||
return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
|
return filepath.Join(cliconfig.ConfigDir(), "tls", u.Host), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func trustServer(index *registry.IndexInfo) (string, error) {
|
func trustServer(index *registry.IndexInfo) string {
|
||||||
if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
|
if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
|
||||||
urlObj, err := url.Parse(s)
|
if !strings.HasPrefix(s, "https://") {
|
||||||
if err != nil || urlObj.Scheme != "https" {
|
return "https://" + s
|
||||||
return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
|
|
||||||
}
|
}
|
||||||
|
return s
|
||||||
return s, nil
|
|
||||||
}
|
}
|
||||||
if index.Official {
|
if index.Official {
|
||||||
return registry.NotaryServer, nil
|
return registry.NotaryServer
|
||||||
}
|
}
|
||||||
return "https://" + index.Name, nil
|
return "https://" + index.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
type simpleCredentialStore struct {
|
type simpleCredentialStore struct {
|
||||||
@@ -102,9 +101,9 @@ func (scs simpleCredentialStore) Basic(u *url.URL) (string, string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig cliconfig.AuthConfig) (*client.NotaryRepository, error) {
|
func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, authConfig cliconfig.AuthConfig) (*client.NotaryRepository, error) {
|
||||||
server, err := trustServer(repoInfo.Index)
|
server := trustServer(repoInfo.Index)
|
||||||
if err != nil {
|
if !strings.HasPrefix(server, "https://") {
|
||||||
return nil, err
|
return nil, errors.New("unsupported scheme: https required for trust server")
|
||||||
}
|
}
|
||||||
|
|
||||||
var cfg = tlsconfig.ClientDefault
|
var cfg = tlsconfig.ClientDefault
|
||||||
@@ -145,21 +144,15 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
challengeManager := auth.NewSimpleChallengeManager()
|
|
||||||
|
|
||||||
resp, err := pingClient.Do(req)
|
resp, err := pingClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Ignore error on ping to operate in offline mode
|
|
||||||
logrus.Debugf("Error pinging notary server %q: %s", endpointStr, err)
|
|
||||||
} else {
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// Add response to the challenge manager to parse out
|
|
||||||
// authentication header and register authentication method
|
|
||||||
if err := challengeManager.AddResponse(resp); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
challengeManager := auth.NewSimpleChallengeManager()
|
||||||
|
if err := challengeManager.AddResponse(resp); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
creds := simpleCredentialStore{auth: authConfig}
|
creds := simpleCredentialStore{auth: authConfig}
|
||||||
@@ -185,30 +178,16 @@ func convertTarget(t client.Target) (target, error) {
|
|||||||
|
|
||||||
func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
|
func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
|
||||||
aliasMap := map[string]string{
|
aliasMap := map[string]string{
|
||||||
"root": "root",
|
"root": "offline",
|
||||||
"snapshot": "repository",
|
"snapshot": "tagging",
|
||||||
"targets": "repository",
|
"targets": "tagging",
|
||||||
}
|
}
|
||||||
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
|
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
|
||||||
env := map[string]string{
|
env := map[string]string{
|
||||||
"root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
|
"root": os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"),
|
||||||
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"),
|
||||||
"targets": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
"targets": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Backwards compatibility with old env names. We should remove this in 1.10
|
|
||||||
if env["root"] == "" {
|
|
||||||
env["root"] = os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE")
|
|
||||||
fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE\n")
|
|
||||||
|
|
||||||
}
|
|
||||||
if env["snapshot"] == "" || env["targets"] == "" {
|
|
||||||
env["snapshot"] = os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE")
|
|
||||||
env["targets"] = os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE")
|
|
||||||
fmt.Fprintf(cli.err, "[DEPRECATED] The environment variable DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE has been deprecated and will be removed in v1.10. Please use DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE\n")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
|
return func(keyName string, alias string, createNew bool, numAttempts int) (string, bool, error) {
|
||||||
if v := env[alias]; v != "" {
|
if v := env[alias]; v != "" {
|
||||||
return v, numAttempts > 1, nil
|
return v, numAttempts > 1, nil
|
||||||
@@ -269,8 +248,6 @@ func notaryError(err error) error {
|
|||||||
return fmt.Errorf("remote repository out-of-date: %v", err)
|
return fmt.Errorf("remote repository out-of-date: %v", err)
|
||||||
case trustmanager.ErrKeyNotFound:
|
case trustmanager.ErrKeyNotFound:
|
||||||
return fmt.Errorf("signing keys not found: %v", err)
|
return fmt.Errorf("signing keys not found: %v", err)
|
||||||
case *net.OpError:
|
|
||||||
return fmt.Errorf("error contacting notary server: %v", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
)
|
|
||||||
|
|
||||||
func unsetENV() {
|
|
||||||
os.Unsetenv("DOCKER_CONTENT_TRUST")
|
|
||||||
os.Unsetenv("DOCKER_CONTENT_TRUST_SERVER")
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestENVTrustServer(t *testing.T) {
|
|
||||||
defer unsetENV()
|
|
||||||
indexInfo := ®istry.IndexInfo{Name: "testserver"}
|
|
||||||
if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "https://notary-test.com:5000"); err != nil {
|
|
||||||
t.Fatal("Failed to set ENV variable")
|
|
||||||
}
|
|
||||||
output, err := trustServer(indexInfo)
|
|
||||||
expectedStr := "https://notary-test.com:5000"
|
|
||||||
if err != nil || output != expectedStr {
|
|
||||||
t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestHTTPENVTrustServer(t *testing.T) {
|
|
||||||
defer unsetENV()
|
|
||||||
indexInfo := ®istry.IndexInfo{Name: "testserver"}
|
|
||||||
if err := os.Setenv("DOCKER_CONTENT_TRUST_SERVER", "http://notary-test.com:5000"); err != nil {
|
|
||||||
t.Fatal("Failed to set ENV variable")
|
|
||||||
}
|
|
||||||
_, err := trustServer(indexInfo)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("Expected error with invalid scheme")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestOfficialTrustServer(t *testing.T) {
|
|
||||||
indexInfo := ®istry.IndexInfo{Name: "testserver", Official: true}
|
|
||||||
output, err := trustServer(indexInfo)
|
|
||||||
if err != nil || output != registry.NotaryServer {
|
|
||||||
t.Fatalf("Expected server to be %s, got %s", registry.NotaryServer, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNonOfficialTrustServer(t *testing.T) {
|
|
||||||
indexInfo := ®istry.IndexInfo{Name: "testserver", Official: false}
|
|
||||||
output, err := trustServer(indexInfo)
|
|
||||||
expectedStr := "https://" + indexInfo.Name
|
|
||||||
if err != nil || output != expectedStr {
|
|
||||||
t.Fatalf("Expected server to be %s, got %s", expectedStr, output)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker unpause CONTAINER [CONTAINER...]
|
// Usage: docker unpause CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdUnpause(args ...string) error {
|
func (cli *DockerCli) CmdUnpause(args ...string) error {
|
||||||
cmd := Cli.Subcmd("unpause", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["unpause"].Description, true)
|
cmd := Cli.Subcmd("unpause", []string{"CONTAINER [CONTAINER...]"}, "Unpause all processes within a container", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+11
-16
@@ -27,11 +27,10 @@ import (
|
|||||||
"github.com/docker/docker/pkg/stdcopy"
|
"github.com/docker/docker/pkg/stdcopy"
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/docker/docker/registry"
|
"github.com/docker/docker/registry"
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errConnectionFailed = errors.New("Cannot connect to the Docker daemon. Is the docker daemon running on this host?")
|
errConnectionRefused = errors.New("Cannot connect to the Docker daemon. Is 'docker -d' running on this host?")
|
||||||
)
|
)
|
||||||
|
|
||||||
type serverResponse struct {
|
type serverResponse struct {
|
||||||
@@ -95,14 +94,13 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m
|
|||||||
if resp != nil {
|
if resp != nil {
|
||||||
serverResp.statusCode = resp.StatusCode
|
serverResp.statusCode = resp.StatusCode
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if utils.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
|
if strings.Contains(err.Error(), "connection refused") {
|
||||||
return serverResp, errConnectionFailed
|
return serverResp, errConnectionRefused
|
||||||
}
|
}
|
||||||
|
|
||||||
if cli.tlsConfig == nil && strings.Contains(err.Error(), "malformed HTTP response") {
|
if cli.tlsConfig == nil {
|
||||||
return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?\n* Is your docker daemon up and running?", err)
|
||||||
}
|
}
|
||||||
if cli.tlsConfig != nil && strings.Contains(err.Error(), "remote error: bad certificate") {
|
if cli.tlsConfig != nil && strings.Contains(err.Error(), "remote error: bad certificate") {
|
||||||
return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
|
return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
|
||||||
@@ -127,9 +125,8 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m
|
|||||||
return serverResp, nil
|
return serverResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// cmdAttempt builds the corresponding registry Auth Header from the given
|
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
|
||||||
// authConfig. It returns the servers body, status, error response
|
cmdAttempt := func(authConfig cliconfig.AuthConfig) (io.ReadCloser, int, error) {
|
||||||
func (cli *DockerCli) cmdAttempt(authConfig cliconfig.AuthConfig, method, path string, in io.Reader, out io.Writer) (io.ReadCloser, int, error) {
|
|
||||||
buf, err := json.Marshal(authConfig)
|
buf, err := json.Marshal(authConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
@@ -160,18 +157,16 @@ func (cli *DockerCli) cmdAttempt(authConfig cliconfig.AuthConfig, method, path s
|
|||||||
return serverResp.body, serverResp.statusCode, err
|
return serverResp.body, serverResp.statusCode, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *DockerCli) clientRequestAttemptLogin(method, path string, in io.Reader, out io.Writer, index *registry.IndexInfo, cmdName string) (io.ReadCloser, int, error) {
|
|
||||||
|
|
||||||
// Resolve the Auth config relevant for this server
|
// Resolve the Auth config relevant for this server
|
||||||
authConfig := registry.ResolveAuthConfig(cli.configFile, index)
|
authConfig := registry.ResolveAuthConfig(cli.configFile, index)
|
||||||
body, statusCode, err := cli.cmdAttempt(authConfig, method, path, in, out)
|
body, statusCode, err := cmdAttempt(authConfig)
|
||||||
if statusCode == http.StatusUnauthorized {
|
if statusCode == http.StatusUnauthorized {
|
||||||
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
|
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
|
||||||
if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
|
if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
|
||||||
return nil, -1, err
|
return nil, -1, err
|
||||||
}
|
}
|
||||||
authConfig = registry.ResolveAuthConfig(cli.configFile, index)
|
authConfig = registry.ResolveAuthConfig(cli.configFile, index)
|
||||||
return cli.cmdAttempt(authConfig, method, path, in, out)
|
return cmdAttempt(authConfig)
|
||||||
}
|
}
|
||||||
return body, statusCode, err
|
return body, statusCode, err
|
||||||
}
|
}
|
||||||
@@ -282,7 +277,7 @@ func getExitCode(cli *DockerCli, containerID string) (bool, int, error) {
|
|||||||
serverResp, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
|
serverResp, err := cli.call("GET", "/containers/"+containerID+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we can't connect, then the daemon probably died.
|
// If we can't connect, then the daemon probably died.
|
||||||
if err != errConnectionFailed {
|
if err != errConnectionRefused {
|
||||||
return false, -1, err
|
return false, -1, err
|
||||||
}
|
}
|
||||||
return false, -1, nil
|
return false, -1, nil
|
||||||
@@ -304,7 +299,7 @@ func getExecExitCode(cli *DockerCli, execID string) (bool, int, error) {
|
|||||||
serverResp, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
|
serverResp, err := cli.call("GET", "/exec/"+execID+"/json", nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If we can't connect, then the daemon probably died.
|
// If we can't connect, then the daemon probably died.
|
||||||
if err != errConnectionFailed {
|
if err != errConnectionRefused {
|
||||||
return false, -1, err
|
return false, -1, err
|
||||||
}
|
}
|
||||||
return false, -1, nil
|
return false, -1, nil
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import (
|
|||||||
"github.com/docker/docker/utils"
|
"github.com/docker/docker/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var versionTemplate = `Client:
|
var VersionTemplate = `Client:
|
||||||
Version: {{.Client.Version}}
|
Version: {{.Client.Version}}
|
||||||
API version: {{.Client.APIVersion}}
|
API version: {{.Client.ApiVersion}}
|
||||||
Go version: {{.Client.GoVersion}}
|
Go version: {{.Client.GoVersion}}
|
||||||
Git commit: {{.Client.GitCommit}}
|
Git commit: {{.Client.GitCommit}}
|
||||||
Built: {{.Client.BuildTime}}
|
Built: {{.Client.BuildTime}}
|
||||||
@@ -24,14 +24,14 @@ var versionTemplate = `Client:
|
|||||||
|
|
||||||
Server:
|
Server:
|
||||||
Version: {{.Server.Version}}
|
Version: {{.Server.Version}}
|
||||||
API version: {{.Server.APIVersion}}
|
API version: {{.Server.ApiVersion}}
|
||||||
Go version: {{.Server.GoVersion}}
|
Go version: {{.Server.GoVersion}}
|
||||||
Git commit: {{.Server.GitCommit}}
|
Git commit: {{.Server.GitCommit}}
|
||||||
Built: {{.Server.BuildTime}}
|
Built: {{.Server.BuildTime}}
|
||||||
OS/Arch: {{.Server.Os}}/{{.Server.Arch}}{{if .Server.Experimental}}
|
OS/Arch: {{.Server.Os}}/{{.Server.Arch}}{{if .Server.Experimental}}
|
||||||
Experimental: {{.Server.Experimental}}{{end}}{{end}}`
|
Experimental: {{.Server.Experimental}}{{end}}{{end}}`
|
||||||
|
|
||||||
type versionData struct {
|
type VersionData struct {
|
||||||
Client types.Version
|
Client types.Version
|
||||||
ServerOK bool
|
ServerOK bool
|
||||||
Server types.Version
|
Server types.Version
|
||||||
@@ -43,13 +43,13 @@ type versionData struct {
|
|||||||
//
|
//
|
||||||
// Usage: docker version
|
// Usage: docker version
|
||||||
func (cli *DockerCli) CmdVersion(args ...string) (err error) {
|
func (cli *DockerCli) CmdVersion(args ...string) (err error) {
|
||||||
cmd := Cli.Subcmd("version", nil, Cli.DockerCommands["version"].Description, true)
|
cmd := Cli.Subcmd("version", nil, "Show the Docker version information.", true)
|
||||||
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
tmplStr := cmd.String([]string{"f", "#format", "-format"}, "", "Format the output using the given go template")
|
||||||
cmd.Require(flag.Exact, 0)
|
cmd.Require(flag.Exact, 0)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
if *tmplStr == "" {
|
if *tmplStr == "" {
|
||||||
*tmplStr = versionTemplate
|
*tmplStr = VersionTemplate
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmpl *template.Template
|
var tmpl *template.Template
|
||||||
@@ -58,10 +58,10 @@ func (cli *DockerCli) CmdVersion(args ...string) (err error) {
|
|||||||
Status: "Template parsing error: " + err.Error()}
|
Status: "Template parsing error: " + err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
vd := versionData{
|
vd := VersionData{
|
||||||
Client: types.Version{
|
Client: types.Version{
|
||||||
Version: dockerversion.VERSION,
|
Version: dockerversion.VERSION,
|
||||||
APIVersion: api.Version,
|
ApiVersion: api.Version,
|
||||||
GoVersion: runtime.Version(),
|
GoVersion: runtime.Version(),
|
||||||
GitCommit: dockerversion.GITCOMMIT,
|
GitCommit: dockerversion.GITCOMMIT,
|
||||||
BuildTime: dockerversion.BUILDTIME,
|
BuildTime: dockerversion.BUILDTIME,
|
||||||
|
|||||||
@@ -1,234 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/url"
|
|
||||||
"text/tabwriter"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
Cli "github.com/docker/docker/cli"
|
|
||||||
"github.com/docker/docker/opts"
|
|
||||||
flag "github.com/docker/docker/pkg/mflag"
|
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CmdVolume is the parent subcommand for all volume commands
|
|
||||||
//
|
|
||||||
// Usage: docker volume <COMMAND> <OPTS>
|
|
||||||
func (cli *DockerCli) CmdVolume(args ...string) error {
|
|
||||||
description := Cli.DockerCommands["volume"].Description + "\n\nCommands:\n"
|
|
||||||
commands := [][]string{
|
|
||||||
{"create", "Create a volume"},
|
|
||||||
{"inspect", "Return low-level information on a volume"},
|
|
||||||
{"ls", "List volumes"},
|
|
||||||
{"rm", "Remove a volume"},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, cmd := range commands {
|
|
||||||
description += fmt.Sprintf(" %-25.25s%s\n", cmd[0], cmd[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
description += "\nRun 'docker volume COMMAND --help' for more information on a command"
|
|
||||||
cmd := Cli.Subcmd("volume", []string{"[COMMAND]"}, description, false)
|
|
||||||
|
|
||||||
cmd.Require(flag.Exact, 0)
|
|
||||||
err := cmd.ParseFlags(args, true)
|
|
||||||
cmd.Usage()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdVolumeLs outputs a list of Docker volumes.
|
|
||||||
//
|
|
||||||
// Usage: docker volume ls [OPTIONS]
|
|
||||||
func (cli *DockerCli) CmdVolumeLs(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("volume ls", nil, "List volumes", true)
|
|
||||||
|
|
||||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only display volume names")
|
|
||||||
flFilter := opts.NewListOpts(nil)
|
|
||||||
cmd.Var(&flFilter, []string{"f", "-filter"}, "Provide filter values (i.e. 'dangling=true')")
|
|
||||||
|
|
||||||
cmd.Require(flag.Exact, 0)
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
volFilterArgs := filters.Args{}
|
|
||||||
for _, f := range flFilter.GetAll() {
|
|
||||||
var err error
|
|
||||||
volFilterArgs, err = filters.ParseFlag(f, volFilterArgs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
v := url.Values{}
|
|
||||||
if len(volFilterArgs) > 0 {
|
|
||||||
filterJSON, err := filters.ToParam(volFilterArgs)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
v.Set("filters", filterJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := cli.call("GET", "/volumes?"+v.Encode(), nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var volumes types.VolumesListResponse
|
|
||||||
if err := json.NewDecoder(resp.body).Decode(&volumes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w := tabwriter.NewWriter(cli.out, 20, 1, 3, ' ', 0)
|
|
||||||
if !*quiet {
|
|
||||||
fmt.Fprintf(w, "DRIVER \tVOLUME NAME")
|
|
||||||
fmt.Fprintf(w, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, vol := range volumes.Volumes {
|
|
||||||
if *quiet {
|
|
||||||
fmt.Fprintln(w, vol.Name)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Fprintf(w, "%s\t%s\n", vol.Driver, vol.Name)
|
|
||||||
}
|
|
||||||
w.Flush()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdVolumeInspect displays low-level information on one or more volumes.
|
|
||||||
//
|
|
||||||
// Usage: docker volume inspect [OPTIONS] VOLUME [VOLUME...]
|
|
||||||
func (cli *DockerCli) CmdVolumeInspect(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("volume inspect", []string{"VOLUME [VOLUME...]"}, "Return low-level information on a volume", true)
|
|
||||||
tmplStr := cmd.String([]string{"f", "-format"}, "", "Format the output using the given go template")
|
|
||||||
|
|
||||||
cmd.Require(flag.Min, 1)
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
if err := cmd.Parse(args); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmpl *template.Template
|
|
||||||
if *tmplStr != "" {
|
|
||||||
var err error
|
|
||||||
tmpl, err = template.New("").Funcs(funcMap).Parse(*tmplStr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var status = 0
|
|
||||||
var volumes []*types.Volume
|
|
||||||
for _, name := range cmd.Args() {
|
|
||||||
resp, err := cli.call("GET", "/volumes/"+name, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var volume types.Volume
|
|
||||||
if err := json.NewDecoder(resp.body).Decode(&volume); err != nil {
|
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
|
||||||
status = 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmpl == nil {
|
|
||||||
volumes = append(volumes, &volume)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := tmpl.Execute(cli.out, &volume); err != nil {
|
|
||||||
if err := tmpl.Execute(cli.out, &volume); err != nil {
|
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
|
||||||
status = 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
io.WriteString(cli.out, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmpl != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := json.MarshalIndent(volumes, "", " ")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = io.Copy(cli.out, bytes.NewReader(b))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
io.WriteString(cli.out, "\n")
|
|
||||||
|
|
||||||
if status != 0 {
|
|
||||||
return Cli.StatusError{StatusCode: status}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdVolumeCreate creates a new container from a given image.
|
|
||||||
//
|
|
||||||
// Usage: docker volume create [OPTIONS]
|
|
||||||
func (cli *DockerCli) CmdVolumeCreate(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("volume create", nil, "Create a volume", true)
|
|
||||||
flDriver := cmd.String([]string{"d", "-driver"}, "local", "Specify volume driver name")
|
|
||||||
flName := cmd.String([]string{"-name"}, "", "Specify volume name")
|
|
||||||
|
|
||||||
flDriverOpts := opts.NewMapOpts(nil, nil)
|
|
||||||
cmd.Var(flDriverOpts, []string{"o", "-opt"}, "Set driver specific options")
|
|
||||||
|
|
||||||
cmd.Require(flag.Exact, 0)
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
volReq := &types.VolumeCreateRequest{
|
|
||||||
Driver: *flDriver,
|
|
||||||
DriverOpts: flDriverOpts.GetAll(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if *flName != "" {
|
|
||||||
volReq.Name = *flName
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := cli.call("POST", "/volumes", volReq, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var vol types.Volume
|
|
||||||
if err := json.NewDecoder(resp.body).Decode(&vol); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(cli.out, "%s\n", vol.Name)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CmdVolumeRm removes one or more containers.
|
|
||||||
//
|
|
||||||
// Usage: docker volume rm VOLUME [VOLUME...]
|
|
||||||
func (cli *DockerCli) CmdVolumeRm(args ...string) error {
|
|
||||||
cmd := Cli.Subcmd("volume rm", []string{"VOLUME [VOLUME...]"}, "Remove a volume", true)
|
|
||||||
cmd.Require(flag.Min, 1)
|
|
||||||
cmd.ParseFlags(args, true)
|
|
||||||
|
|
||||||
var status = 0
|
|
||||||
for _, name := range cmd.Args() {
|
|
||||||
_, err := cli.call("DELETE", "/volumes/"+name, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(cli.err, "%s\n", err)
|
|
||||||
status = 1
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Fprintf(cli.out, "%s\n", name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if status != 0 {
|
|
||||||
return Cli.StatusError{StatusCode: status}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
|||||||
//
|
//
|
||||||
// Usage: docker wait CONTAINER [CONTAINER...]
|
// Usage: docker wait CONTAINER [CONTAINER...]
|
||||||
func (cli *DockerCli) CmdWait(args ...string) error {
|
func (cli *DockerCli) CmdWait(args ...string) error {
|
||||||
cmd := Cli.Subcmd("wait", []string{"CONTAINER [CONTAINER...]"}, Cli.DockerCommands["wait"].Description, true)
|
cmd := Cli.Subcmd("wait", []string{"CONTAINER [CONTAINER...]"}, "Block until a container stops, then print its exit code.", true)
|
||||||
cmd.Require(flag.Min, 1)
|
cmd.Require(flag.Min, 1)
|
||||||
|
|
||||||
cmd.ParseFlags(args, true)
|
cmd.ParseFlags(args, true)
|
||||||
|
|||||||
+48
-42
@@ -5,7 +5,6 @@ import (
|
|||||||
"mime"
|
"mime"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
@@ -17,38 +16,39 @@ import (
|
|||||||
|
|
||||||
// Common constants for daemon and client.
|
// Common constants for daemon and client.
|
||||||
const (
|
const (
|
||||||
// Version of Current REST API
|
// Current REST API version
|
||||||
Version version.Version = "1.22"
|
Version version.Version = "1.20"
|
||||||
|
|
||||||
// MinVersion represents Minimun REST API version supported
|
// Minimun REST API version supported
|
||||||
MinVersion version.Version = "1.12"
|
MinVersion version.Version = "1.12"
|
||||||
|
|
||||||
// DefaultDockerfileName is the Default filename with Docker commands, read by docker build
|
// Default filename with Docker commands, read by docker build
|
||||||
DefaultDockerfileName string = "Dockerfile"
|
DefaultDockerfileName string = "Dockerfile"
|
||||||
)
|
)
|
||||||
|
|
||||||
// byPrivatePort is temporary type used to sort types.Port by PrivatePort
|
type ByPrivatePort []types.Port
|
||||||
type byPrivatePort []types.Port
|
|
||||||
|
|
||||||
func (r byPrivatePort) Len() int { return len(r) }
|
func (r ByPrivatePort) Len() int { return len(r) }
|
||||||
func (r byPrivatePort) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
func (r ByPrivatePort) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||||
func (r byPrivatePort) Less(i, j int) bool { return r[i].PrivatePort < r[j].PrivatePort }
|
func (r ByPrivatePort) Less(i, j int) bool { return r[i].PrivatePort < r[j].PrivatePort }
|
||||||
|
|
||||||
// DisplayablePorts returns formatted string representing open ports of container
|
|
||||||
// e.g. "0.0.0.0:80->9090/tcp, 9988/tcp"
|
|
||||||
// it's used by command 'docker ps'
|
|
||||||
func DisplayablePorts(ports []types.Port) string {
|
func DisplayablePorts(ports []types.Port) string {
|
||||||
type portGroup struct {
|
var (
|
||||||
first int
|
result = []string{}
|
||||||
last int
|
hostMappings = []string{}
|
||||||
}
|
firstInGroupMap map[string]int
|
||||||
groupMap := make(map[string]*portGroup)
|
lastInGroupMap map[string]int
|
||||||
var result []string
|
)
|
||||||
var hostMappings []string
|
firstInGroupMap = make(map[string]int)
|
||||||
sort.Sort(byPrivatePort(ports))
|
lastInGroupMap = make(map[string]int)
|
||||||
|
sort.Sort(ByPrivatePort(ports))
|
||||||
for _, port := range ports {
|
for _, port := range ports {
|
||||||
current := port.PrivatePort
|
var (
|
||||||
portKey := port.Type
|
current = port.PrivatePort
|
||||||
|
portKey = port.Type
|
||||||
|
firstInGroup int
|
||||||
|
lastInGroup int
|
||||||
|
)
|
||||||
if port.IP != "" {
|
if port.IP != "" {
|
||||||
if port.PublicPort != current {
|
if port.PublicPort != current {
|
||||||
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
||||||
@@ -56,38 +56,45 @@ func DisplayablePorts(ports []types.Port) string {
|
|||||||
}
|
}
|
||||||
portKey = fmt.Sprintf("%s/%s", port.IP, port.Type)
|
portKey = fmt.Sprintf("%s/%s", port.IP, port.Type)
|
||||||
}
|
}
|
||||||
group := groupMap[portKey]
|
firstInGroup = firstInGroupMap[portKey]
|
||||||
|
lastInGroup = lastInGroupMap[portKey]
|
||||||
|
|
||||||
if group == nil {
|
if firstInGroup == 0 {
|
||||||
groupMap[portKey] = &portGroup{first: current, last: current}
|
firstInGroupMap[portKey] = current
|
||||||
continue
|
lastInGroupMap[portKey] = current
|
||||||
}
|
|
||||||
if current == (group.last + 1) {
|
|
||||||
group.last = current
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
result = append(result, formGroup(portKey, group.first, group.last))
|
if current == (lastInGroup + 1) {
|
||||||
groupMap[portKey] = &portGroup{first: current, last: current}
|
lastInGroupMap[portKey] = current
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
for portKey, g := range groupMap {
|
result = append(result, FormGroup(portKey, firstInGroup, lastInGroup))
|
||||||
result = append(result, formGroup(portKey, g.first, g.last))
|
firstInGroupMap[portKey] = current
|
||||||
|
lastInGroupMap[portKey] = current
|
||||||
|
}
|
||||||
|
for portKey, firstInGroup := range firstInGroupMap {
|
||||||
|
result = append(result, FormGroup(portKey, firstInGroup, lastInGroupMap[portKey]))
|
||||||
}
|
}
|
||||||
result = append(result, hostMappings...)
|
result = append(result, hostMappings...)
|
||||||
return strings.Join(result, ", ")
|
return strings.Join(result, ", ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func formGroup(key string, start, last int) string {
|
func FormGroup(key string, start, last int) string {
|
||||||
parts := strings.Split(key, "/")
|
var (
|
||||||
groupType := parts[0]
|
group string
|
||||||
var ip string
|
parts = strings.Split(key, "/")
|
||||||
|
groupType = parts[0]
|
||||||
|
ip = ""
|
||||||
|
)
|
||||||
if len(parts) > 1 {
|
if len(parts) > 1 {
|
||||||
ip = parts[0]
|
ip = parts[0]
|
||||||
groupType = parts[1]
|
groupType = parts[1]
|
||||||
}
|
}
|
||||||
group := strconv.Itoa(start)
|
if start == last {
|
||||||
if start != last {
|
group = fmt.Sprintf("%d", start)
|
||||||
group = fmt.Sprintf("%s-%d", group, last)
|
} else {
|
||||||
|
group = fmt.Sprintf("%d-%d", start, last)
|
||||||
}
|
}
|
||||||
if ip != "" {
|
if ip != "" {
|
||||||
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
|
group = fmt.Sprintf("%s:%s->%s", ip, group, group)
|
||||||
@@ -95,7 +102,6 @@ func formGroup(key string, start, last int) string {
|
|||||||
return fmt.Sprintf("%s/%s", group, groupType)
|
return fmt.Sprintf("%s/%s", group, groupType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MatchesContentType validates the content type against the expected one
|
|
||||||
func MatchesContentType(contentType, expectedType string) bool {
|
func MatchesContentType(contentType, expectedType string) bool {
|
||||||
mimetype, _, err := mime.ParseMediaType(contentType)
|
mimetype, _, err := mime.ParseMediaType(contentType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,282 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ports struct {
|
|
||||||
ports []types.Port
|
|
||||||
expected string
|
|
||||||
}
|
|
||||||
|
|
||||||
// DisplayablePorts
|
|
||||||
func TestDisplayablePorts(t *testing.T) {
|
|
||||||
cases := []ports{
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
PrivatePort: 9988,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9988/tcp"},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
PrivatePort: 9988,
|
|
||||||
Type: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9988/udp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "0.0.0.0",
|
|
||||||
PrivatePort: 9988,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"0.0.0.0:0->9988/tcp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 8899,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9988/tcp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "4.3.2.1",
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 8899,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"4.3.2.1:8899->9988/tcp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "4.3.2.1",
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 9988,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"4.3.2.1:9988->9988/tcp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
PrivatePort: 9988,
|
|
||||||
Type: "udp",
|
|
||||||
}, {
|
|
||||||
PrivatePort: 9988,
|
|
||||||
Type: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9988/udp, 9988/udp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PublicPort: 9998,
|
|
||||||
PrivatePort: 9998,
|
|
||||||
Type: "udp",
|
|
||||||
}, {
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PublicPort: 9999,
|
|
||||||
PrivatePort: 9999,
|
|
||||||
Type: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"1.2.3.4:9998-9999->9998-9999/udp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PublicPort: 8887,
|
|
||||||
PrivatePort: 9998,
|
|
||||||
Type: "udp",
|
|
||||||
}, {
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PublicPort: 8888,
|
|
||||||
PrivatePort: 9999,
|
|
||||||
Type: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"1.2.3.4:8887->9998/udp, 1.2.3.4:8888->9999/udp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
PrivatePort: 9998,
|
|
||||||
Type: "udp",
|
|
||||||
}, {
|
|
||||||
PrivatePort: 9999,
|
|
||||||
Type: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9998-9999/udp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PrivatePort: 6677,
|
|
||||||
PublicPort: 7766,
|
|
||||||
Type: "tcp",
|
|
||||||
}, {
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 8899,
|
|
||||||
Type: "udp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9988/udp, 1.2.3.4:7766->6677/tcp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 8899,
|
|
||||||
Type: "udp",
|
|
||||||
}, {
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 8899,
|
|
||||||
Type: "tcp",
|
|
||||||
}, {
|
|
||||||
IP: "4.3.2.1",
|
|
||||||
PrivatePort: 2233,
|
|
||||||
PublicPort: 3322,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"4.3.2.1:3322->2233/tcp, 1.2.3.4:8899->9988/udp, 1.2.3.4:8899->9988/tcp",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
[]types.Port{
|
|
||||||
{
|
|
||||||
PrivatePort: 9988,
|
|
||||||
PublicPort: 8899,
|
|
||||||
Type: "udp",
|
|
||||||
}, {
|
|
||||||
IP: "1.2.3.4",
|
|
||||||
PrivatePort: 6677,
|
|
||||||
PublicPort: 7766,
|
|
||||||
Type: "tcp",
|
|
||||||
}, {
|
|
||||||
IP: "4.3.2.1",
|
|
||||||
PrivatePort: 2233,
|
|
||||||
PublicPort: 3322,
|
|
||||||
Type: "tcp",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"9988/udp, 4.3.2.1:3322->2233/tcp, 1.2.3.4:7766->6677/tcp",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, port := range cases {
|
|
||||||
actual := DisplayablePorts(port.ports)
|
|
||||||
if port.expected != actual {
|
|
||||||
t.Fatalf("Expected %s, got %s.", port.expected, actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatchesContentType
|
|
||||||
func TestJsonContentType(t *testing.T) {
|
|
||||||
if !MatchesContentType("application/json", "application/json") {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !MatchesContentType("application/json; charset=utf-8", "application/json") {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
|
|
||||||
if MatchesContentType("dockerapplication/json", "application/json") {
|
|
||||||
t.Fail()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadOrCreateTrustKey
|
|
||||||
func TestLoadOrCreateTrustKeyInvalidKeyFile(t *testing.T) {
|
|
||||||
tmpKeyFolderPath, err := ioutil.TempDir("", "api-trustkey-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpKeyFolderPath)
|
|
||||||
|
|
||||||
tmpKeyFile, err := ioutil.TempFile(tmpKeyFolderPath, "keyfile")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := LoadOrCreateTrustKey(tmpKeyFile.Name()); err == nil {
|
|
||||||
t.Fatalf("expected an error, got nothing.")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadOrCreateTrustKeyCreateKey(t *testing.T) {
|
|
||||||
tmpKeyFolderPath, err := ioutil.TempDir("", "api-trustkey-test")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(tmpKeyFolderPath)
|
|
||||||
|
|
||||||
// Without the need to create the folder hierarchy
|
|
||||||
tmpKeyFile := filepath.Join(tmpKeyFolderPath, "keyfile")
|
|
||||||
|
|
||||||
if key, err := LoadOrCreateTrustKey(tmpKeyFile); err != nil || key == nil {
|
|
||||||
t.Fatalf("expected a new key file, got : %v and %v", err, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(tmpKeyFile); err != nil {
|
|
||||||
t.Fatalf("Expected to find a file %s, got %v", tmpKeyFile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With the need to create the folder hierarchy as tmpKeyFie is in a path
|
|
||||||
// where some folder do not exists.
|
|
||||||
tmpKeyFile = filepath.Join(tmpKeyFolderPath, "folder/hierarchy/keyfile")
|
|
||||||
|
|
||||||
if key, err := LoadOrCreateTrustKey(tmpKeyFile); err != nil || key == nil {
|
|
||||||
t.Fatalf("expected a new key file, got : %v and %v", err, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(tmpKeyFile); err != nil {
|
|
||||||
t.Fatalf("Expected to find a file %s, got %v", tmpKeyFile, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// With no path at all
|
|
||||||
defer os.Remove("keyfile")
|
|
||||||
if key, err := LoadOrCreateTrustKey("keyfile"); err != nil || key == nil {
|
|
||||||
t.Fatalf("expected a new key file, got : %v and %v", err, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat("keyfile"); err != nil {
|
|
||||||
t.Fatalf("Expected to find a file keyfile, got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestLoadOrCreateTrustKeyLoadValidKey(t *testing.T) {
|
|
||||||
tmpKeyFile := filepath.Join("fixtures", "keyfile")
|
|
||||||
|
|
||||||
if key, err := LoadOrCreateTrustKey(tmpKeyFile); err != nil || key == nil {
|
|
||||||
t.Fatalf("expected a key file, got : %v and %v", err, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
-----BEGIN EC PRIVATE KEY-----
|
|
||||||
keyID: AWX2:I27X:WQFX:IOMK:CNAK:O7PW:VYNB:ZLKC:CVAE:YJP2:SI4A:XXAY
|
|
||||||
|
|
||||||
MHcCAQEEILHTRWdcpKWsnORxSFyBnndJ4ROU41hMtr/GCiLVvwBQoAoGCCqGSM49
|
|
||||||
AwEHoUQDQgAElpVFbQ2V2UQKajqdE3fVxJ+/pE/YuEFOxWbOxF2be19BY209/iky
|
|
||||||
NzeFFK7SLpQ4CBJ7zDVXOHsMzrkY/GquGA==
|
|
||||||
-----END EC PRIVATE KEY-----
|
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func boolValue(r *http.Request, k string) bool {
|
||||||
|
s := strings.ToLower(strings.TrimSpace(r.FormValue(k)))
|
||||||
|
return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
|
||||||
|
}
|
||||||
|
|
||||||
|
// boolValueOrDefault returns the default bool passed if the query param is
|
||||||
|
// missing, otherwise it's just a proxy to boolValue above
|
||||||
|
func boolValueOrDefault(r *http.Request, k string, d bool) bool {
|
||||||
|
if _, ok := r.Form[k]; !ok {
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
return boolValue(r, k)
|
||||||
|
}
|
||||||
|
|
||||||
|
func int64ValueOrZero(r *http.Request, k string) int64 {
|
||||||
|
val, err := strconv.ParseInt(r.FormValue(k), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
type archiveOptions struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func archiveFormValues(r *http.Request, vars map[string]string) (archiveOptions, error) {
|
||||||
|
if vars == nil {
|
||||||
|
return archiveOptions{}, fmt.Errorf("Missing parameter")
|
||||||
|
}
|
||||||
|
if err := parseForm(r); err != nil {
|
||||||
|
return archiveOptions{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
name := vars["name"]
|
||||||
|
path := r.Form.Get("path")
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case name == "":
|
||||||
|
return archiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty")
|
||||||
|
case path == "":
|
||||||
|
return archiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return archiveOptions{name, path}, nil
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package httputils
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -26,7 +26,7 @@ func TestBoolValue(t *testing.T) {
|
|||||||
r, _ := http.NewRequest("POST", "", nil)
|
r, _ := http.NewRequest("POST", "", nil)
|
||||||
r.Form = v
|
r.Form = v
|
||||||
|
|
||||||
a := BoolValue(r, "test")
|
a := boolValue(r, "test")
|
||||||
if a != e {
|
if a != e {
|
||||||
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ func TestBoolValue(t *testing.T) {
|
|||||||
|
|
||||||
func TestBoolValueOrDefault(t *testing.T) {
|
func TestBoolValueOrDefault(t *testing.T) {
|
||||||
r, _ := http.NewRequest("GET", "", nil)
|
r, _ := http.NewRequest("GET", "", nil)
|
||||||
if !BoolValueOrDefault(r, "queryparam", true) {
|
if !boolValueOrDefault(r, "queryparam", true) {
|
||||||
t.Fatal("Expected to get true default value, got false")
|
t.Fatal("Expected to get true default value, got false")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +43,7 @@ func TestBoolValueOrDefault(t *testing.T) {
|
|||||||
v.Set("param", "")
|
v.Set("param", "")
|
||||||
r, _ = http.NewRequest("GET", "", nil)
|
r, _ = http.NewRequest("GET", "", nil)
|
||||||
r.Form = v
|
r.Form = v
|
||||||
if BoolValueOrDefault(r, "param", true) {
|
if boolValueOrDefault(r, "param", true) {
|
||||||
t.Fatal("Expected not to get true")
|
t.Fatal("Expected not to get true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -62,44 +62,9 @@ func TestInt64ValueOrZero(t *testing.T) {
|
|||||||
r, _ := http.NewRequest("POST", "", nil)
|
r, _ := http.NewRequest("POST", "", nil)
|
||||||
r.Form = v
|
r.Form = v
|
||||||
|
|
||||||
a := Int64ValueOrZero(r, "test")
|
a := int64ValueOrZero(r, "test")
|
||||||
if a != e {
|
if a != e {
|
||||||
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInt64ValueOrDefault(t *testing.T) {
|
|
||||||
cases := map[string]int64{
|
|
||||||
"": -1,
|
|
||||||
"-1": -1,
|
|
||||||
"42": 42,
|
|
||||||
}
|
|
||||||
|
|
||||||
for c, e := range cases {
|
|
||||||
v := url.Values{}
|
|
||||||
v.Set("test", c)
|
|
||||||
r, _ := http.NewRequest("POST", "", nil)
|
|
||||||
r.Form = v
|
|
||||||
|
|
||||||
a, err := Int64ValueOrDefault(r, "test", -1)
|
|
||||||
if a != e {
|
|
||||||
t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error should be nil, but received: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestInt64ValueOrDefaultWithError(t *testing.T) {
|
|
||||||
v := url.Values{}
|
|
||||||
v.Set("test", "invalid")
|
|
||||||
r, _ := http.NewRequest("POST", "", nil)
|
|
||||||
r.Form = v
|
|
||||||
|
|
||||||
_, err := Int64ValueOrDefault(r, "test", -1)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("Expected an error.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
package httputils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BoolValue transforms a form value in different formats into a boolean type.
|
|
||||||
func BoolValue(r *http.Request, k string) bool {
|
|
||||||
s := strings.ToLower(strings.TrimSpace(r.FormValue(k)))
|
|
||||||
return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none")
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoolValueOrDefault returns the default bool passed if the query param is
|
|
||||||
// missing, otherwise it's just a proxy to boolValue above
|
|
||||||
func BoolValueOrDefault(r *http.Request, k string, d bool) bool {
|
|
||||||
if _, ok := r.Form[k]; !ok {
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
return BoolValue(r, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64ValueOrZero parses a form value into an int64 type.
|
|
||||||
// It returns 0 if the parsing fails.
|
|
||||||
func Int64ValueOrZero(r *http.Request, k string) int64 {
|
|
||||||
val, err := Int64ValueOrDefault(r, k, 0)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
|
|
||||||
// Int64ValueOrDefault parses a form value into an int64 type. If there is an
|
|
||||||
// error, returns the error. If there is no value returns the default value.
|
|
||||||
func Int64ValueOrDefault(r *http.Request, field string, def int64) (int64, error) {
|
|
||||||
if r.Form.Get(field) != "" {
|
|
||||||
value, err := strconv.ParseInt(r.Form.Get(field), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return value, err
|
|
||||||
}
|
|
||||||
return value, nil
|
|
||||||
}
|
|
||||||
return def, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArchiveOptions stores archive information for different operations.
|
|
||||||
type ArchiveOptions struct {
|
|
||||||
Name string
|
|
||||||
Path string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ArchiveFormValues parses form values and turns them into ArchiveOptions.
|
|
||||||
// It fails if the archive name and path are not in the request.
|
|
||||||
func ArchiveFormValues(r *http.Request, vars map[string]string) (ArchiveOptions, error) {
|
|
||||||
if err := ParseForm(r); err != nil {
|
|
||||||
return ArchiveOptions{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
path := filepath.FromSlash(r.Form.Get("path"))
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case name == "":
|
|
||||||
return ArchiveOptions{}, fmt.Errorf("bad parameter: 'name' cannot be empty")
|
|
||||||
case path == "":
|
|
||||||
return ArchiveOptions{}, fmt.Errorf("bad parameter: 'path' cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return ArchiveOptions{name, path}, nil
|
|
||||||
}
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
package httputils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
|
||||||
"github.com/docker/docker/api"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// APIVersionKey is the client's requested API version.
|
|
||||||
const APIVersionKey = "api-version"
|
|
||||||
|
|
||||||
// APIFunc is an adapter to allow the use of ordinary functions as Docker API endpoints.
|
|
||||||
// Any function that has the appropriate signature can be register as a API endpoint (e.g. getVersion).
|
|
||||||
type APIFunc func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error
|
|
||||||
|
|
||||||
// HijackConnection interrupts the http response writer to get the
|
|
||||||
// underlying connection and operate with it.
|
|
||||||
func HijackConnection(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
|
||||||
conn, _, err := w.(http.Hijacker).Hijack()
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
// Flush the options to make sure the client sets the raw mode
|
|
||||||
conn.Write([]byte{})
|
|
||||||
return conn, conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CloseStreams ensures that a list for http streams are properly closed.
|
|
||||||
func CloseStreams(streams ...interface{}) {
|
|
||||||
for _, stream := range streams {
|
|
||||||
if tcpc, ok := stream.(interface {
|
|
||||||
CloseWrite() error
|
|
||||||
}); ok {
|
|
||||||
tcpc.CloseWrite()
|
|
||||||
} else if closer, ok := stream.(io.Closer); ok {
|
|
||||||
closer.Close()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckForJSON makes sure that the request's Content-Type is application/json.
|
|
||||||
func CheckForJSON(r *http.Request) error {
|
|
||||||
ct := r.Header.Get("Content-Type")
|
|
||||||
|
|
||||||
// No Content-Type header is ok as long as there's no Body
|
|
||||||
if ct == "" {
|
|
||||||
if r.Body == nil || r.ContentLength == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise it better be json
|
|
||||||
if api.MatchesContentType(ct, "application/json") {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Content-Type specified (%s) must be 'application/json'", ct)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseForm ensures the request form is parsed even with invalid content types.
|
|
||||||
// If we don't do this, POST method without Content-type (even with empty body) will fail.
|
|
||||||
func ParseForm(r *http.Request) error {
|
|
||||||
if r == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if err := r.ParseForm(); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseMultipartForm ensure the request form is parsed, even with invalid content types.
|
|
||||||
func ParseMultipartForm(r *http.Request) error {
|
|
||||||
if err := r.ParseMultipartForm(4096); err != nil && !strings.HasPrefix(err.Error(), "mime:") {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteError decodes a specific docker error and sends it in the response.
|
|
||||||
func WriteError(w http.ResponseWriter, err error) {
|
|
||||||
if err == nil || w == nil {
|
|
||||||
logrus.WithFields(logrus.Fields{"error": err, "writer": w}).Error("unexpected HTTP error handling")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
statusCode := http.StatusInternalServerError
|
|
||||||
errMsg := err.Error()
|
|
||||||
|
|
||||||
// Based on the type of error we get we need to process things
|
|
||||||
// slightly differently to extract the error message.
|
|
||||||
// In the 'errcode.*' cases there are two different type of
|
|
||||||
// error that could be returned. errocode.ErrorCode is the base
|
|
||||||
// type of error object - it is just an 'int' that can then be
|
|
||||||
// used as the look-up key to find the message. errorcode.Error
|
|
||||||
// extends errorcode.Error by adding error-instance specific
|
|
||||||
// data, like 'details' or variable strings to be inserted into
|
|
||||||
// the message.
|
|
||||||
//
|
|
||||||
// Ideally, we should just be able to call err.Error() for all
|
|
||||||
// cases but the errcode package doesn't support that yet.
|
|
||||||
//
|
|
||||||
// Additionally, in both errcode cases, there might be an http
|
|
||||||
// status code associated with it, and if so use it.
|
|
||||||
switch err.(type) {
|
|
||||||
case errcode.ErrorCode:
|
|
||||||
daError, _ := err.(errcode.ErrorCode)
|
|
||||||
statusCode = daError.Descriptor().HTTPStatusCode
|
|
||||||
errMsg = daError.Message()
|
|
||||||
|
|
||||||
case errcode.Error:
|
|
||||||
// For reference, if you're looking for a particular error
|
|
||||||
// then you can do something like :
|
|
||||||
// import ( derr "github.com/docker/docker/errors" )
|
|
||||||
// if daError.ErrorCode() == derr.ErrorCodeNoSuchContainer { ... }
|
|
||||||
|
|
||||||
daError, _ := err.(errcode.Error)
|
|
||||||
statusCode = daError.ErrorCode().Descriptor().HTTPStatusCode
|
|
||||||
errMsg = daError.Message
|
|
||||||
|
|
||||||
default:
|
|
||||||
// This part of will be removed once we've
|
|
||||||
// converted everything over to use the errcode package
|
|
||||||
|
|
||||||
// FIXME: this is brittle and should not be necessary.
|
|
||||||
// If we need to differentiate between different possible error types,
|
|
||||||
// we should create appropriate error types with clearly defined meaning
|
|
||||||
errStr := strings.ToLower(err.Error())
|
|
||||||
for keyword, status := range map[string]int{
|
|
||||||
"not found": http.StatusNotFound,
|
|
||||||
"no such": http.StatusNotFound,
|
|
||||||
"bad parameter": http.StatusBadRequest,
|
|
||||||
"conflict": http.StatusConflict,
|
|
||||||
"impossible": http.StatusNotAcceptable,
|
|
||||||
"wrong login/password": http.StatusUnauthorized,
|
|
||||||
"hasn't been activated": http.StatusForbidden,
|
|
||||||
} {
|
|
||||||
if strings.Contains(errStr, keyword) {
|
|
||||||
statusCode = status
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if statusCode == 0 {
|
|
||||||
statusCode = http.StatusInternalServerError
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.WithFields(logrus.Fields{"statusCode": statusCode, "err": utils.GetErrorMessage(err)}).Error("HTTP Error")
|
|
||||||
http.Error(w, errMsg, statusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteJSON writes the value v to the http response stream as json with standard json encoding.
|
|
||||||
func WriteJSON(w http.ResponseWriter, code int, v interface{}) error {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(code)
|
|
||||||
return json.NewEncoder(w).Encode(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VersionFromContext returns an API version from the context using APIVersionKey.
|
|
||||||
// It panics if the context value does not have version.Version type.
|
|
||||||
func VersionFromContext(ctx context.Context) (ver version.Version) {
|
|
||||||
if ctx == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val := ctx.Value(APIVersionKey)
|
|
||||||
if val == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return val.(version.Version)
|
|
||||||
}
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api"
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
|
||||||
"github.com/docker/docker/errors"
|
|
||||||
"github.com/docker/docker/pkg/version"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// middleware is an adapter to allow the use of ordinary functions as Docker API filters.
|
|
||||||
// Any function that has the appropriate signature can be register as a middleware.
|
|
||||||
type middleware func(handler httputils.APIFunc) httputils.APIFunc
|
|
||||||
|
|
||||||
// loggingMiddleware logs each request when logging is enabled.
|
|
||||||
func (s *Server) loggingMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
|
||||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if s.cfg.Logging {
|
|
||||||
logrus.Infof("%s %s", r.Method, r.RequestURI)
|
|
||||||
}
|
|
||||||
return handler(ctx, w, r, vars)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// userAgentMiddleware checks the User-Agent header looking for a valid docker client spec.
|
|
||||||
func (s *Server) userAgentMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
|
||||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if strings.Contains(r.Header.Get("User-Agent"), "Docker-Client/") {
|
|
||||||
dockerVersion := version.Version(s.cfg.Version)
|
|
||||||
|
|
||||||
userAgent := strings.Split(r.Header.Get("User-Agent"), "/")
|
|
||||||
|
|
||||||
// v1.20 onwards includes the GOOS of the client after the version
|
|
||||||
// such as Docker/1.7.0 (linux)
|
|
||||||
if len(userAgent) == 2 && strings.Contains(userAgent[1], " ") {
|
|
||||||
userAgent[1] = strings.Split(userAgent[1], " ")[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(userAgent) == 2 && !dockerVersion.Equal(version.Version(userAgent[1])) {
|
|
||||||
logrus.Debugf("Warning: client and server don't have the same version (client: %s, server: %s)", userAgent[1], dockerVersion)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return handler(ctx, w, r, vars)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// corsMiddleware sets the CORS header expectations in the server.
|
|
||||||
func (s *Server) corsMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
|
||||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
// If "api-cors-header" is not given, but "api-enable-cors" is true, we set cors to "*"
|
|
||||||
// otherwise, all head values will be passed to HTTP handler
|
|
||||||
corsHeaders := s.cfg.CorsHeaders
|
|
||||||
if corsHeaders == "" && s.cfg.EnableCors {
|
|
||||||
corsHeaders = "*"
|
|
||||||
}
|
|
||||||
|
|
||||||
if corsHeaders != "" {
|
|
||||||
writeCorsHeaders(w, r, corsHeaders)
|
|
||||||
}
|
|
||||||
return handler(ctx, w, r, vars)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// versionMiddleware checks the api version requirements before passing the request to the server handler.
|
|
||||||
func versionMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
|
||||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
apiVersion := version.Version(vars["version"])
|
|
||||||
if apiVersion == "" {
|
|
||||||
apiVersion = api.Version
|
|
||||||
}
|
|
||||||
|
|
||||||
if apiVersion.GreaterThan(api.Version) {
|
|
||||||
return errors.ErrorCodeNewerClientVersion.WithArgs(apiVersion, api.Version)
|
|
||||||
}
|
|
||||||
if apiVersion.LessThan(api.MinVersion) {
|
|
||||||
return errors.ErrorCodeOldClientVersion.WithArgs(apiVersion, api.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Server", "Docker/"+dockerversion.VERSION+" ("+runtime.GOOS+")")
|
|
||||||
ctx = context.WithValue(ctx, httputils.APIVersionKey, apiVersion)
|
|
||||||
return handler(ctx, w, r, vars)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleWithGlobalMiddlwares wraps the handler function for a request with
|
|
||||||
// the server's global middlewares. The order of the middlewares is backwards,
|
|
||||||
// meaning that the first in the list will be evaludated last.
|
|
||||||
//
|
|
||||||
// Example: handleWithGlobalMiddlewares(s.getContainersName)
|
|
||||||
//
|
|
||||||
// s.loggingMiddleware(
|
|
||||||
// s.userAgentMiddleware(
|
|
||||||
// s.corsMiddleware(
|
|
||||||
// versionMiddleware(s.getContainersName)
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
// )
|
|
||||||
func (s *Server) handleWithGlobalMiddlewares(handler httputils.APIFunc) httputils.APIFunc {
|
|
||||||
middlewares := []middleware{
|
|
||||||
versionMiddleware,
|
|
||||||
s.corsMiddleware,
|
|
||||||
s.userAgentMiddleware,
|
|
||||||
s.loggingMiddleware,
|
|
||||||
}
|
|
||||||
|
|
||||||
h := handler
|
|
||||||
for _, m := range middlewares {
|
|
||||||
h = m(h)
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
"github.com/docker/docker/errors"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestVersionMiddleware(t *testing.T) {
|
|
||||||
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if httputils.VersionFromContext(ctx) == "" {
|
|
||||||
t.Fatalf("Expected version, got empty string")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
h := versionMiddleware(handler)
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "/containers/json", nil)
|
|
||||||
resp := httptest.NewRecorder()
|
|
||||||
ctx := context.Background()
|
|
||||||
if err := h(ctx, resp, req, map[string]string{}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestVersionMiddlewareWithErrors(t *testing.T) {
|
|
||||||
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if httputils.VersionFromContext(ctx) == "" {
|
|
||||||
t.Fatalf("Expected version, got empty string")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
h := versionMiddleware(handler)
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "/containers/json", nil)
|
|
||||||
resp := httptest.NewRecorder()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
vars := map[string]string{"version": "0.1"}
|
|
||||||
err := h(ctx, resp, req, vars)
|
|
||||||
if derr, ok := err.(errcode.Error); !ok || derr.ErrorCode() != errors.ErrorCodeOldClientVersion {
|
|
||||||
t.Fatalf("Expected ErrorCodeOldClientVersion, got %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
vars["version"] = "100000"
|
|
||||||
err = h(ctx, resp, req, vars)
|
|
||||||
if derr, ok := err.(errcode.Error); !ok || derr.ErrorCode() != errors.ErrorCodeNewerClientVersion {
|
|
||||||
t.Fatalf("Expected ErrorCodeNewerClientVersion, got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
func profilerSetup(mainRouter *mux.Router, path string) {
|
func ProfilerSetup(mainRouter *mux.Router, path string) {
|
||||||
var r = mainRouter.PathPrefix(path).Subrouter()
|
var r = mainRouter.PathPrefix(path).Subrouter()
|
||||||
r.HandleFunc("/vars", expVars)
|
r.HandleFunc("/vars", expVars)
|
||||||
r.HandleFunc("/pprof/", pprof.Index)
|
r.HandleFunc("/pprof/", pprof.Index)
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/cliconfig"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *router) postAuth(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
var config *cliconfig.AuthConfig
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&config)
|
|
||||||
r.Body.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
status, err := s.daemon.AuthenticateToRegistry(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, &types.AuthResponse{
|
|
||||||
Status: status,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,452 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/distribution/registry/api/errcode"
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/daemon"
|
|
||||||
derr "github.com/docker/docker/errors"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/signal"
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
"golang.org/x/net/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *router) getContainersJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &daemon.ContainersConfig{
|
|
||||||
All: httputils.BoolValue(r, "all"),
|
|
||||||
Size: httputils.BoolValue(r, "size"),
|
|
||||||
Since: r.Form.Get("since"),
|
|
||||||
Before: r.Form.Get("before"),
|
|
||||||
Filters: r.Form.Get("filters"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if tmpLimit := r.Form.Get("limit"); tmpLimit != "" {
|
|
||||||
limit, err := strconv.Atoi(tmpLimit)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
config.Limit = limit
|
|
||||||
}
|
|
||||||
|
|
||||||
containers, err := s.daemon.Containers(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, containers)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getContainersStats(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
stream := httputils.BoolValueOrDefault(r, "stream", true)
|
|
||||||
var out io.Writer
|
|
||||||
if !stream {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
out = w
|
|
||||||
} else {
|
|
||||||
out = ioutils.NewWriteFlusher(w)
|
|
||||||
}
|
|
||||||
|
|
||||||
var closeNotifier <-chan bool
|
|
||||||
if notifier, ok := w.(http.CloseNotifier); ok {
|
|
||||||
closeNotifier = notifier.CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
config := &daemon.ContainerStatsConfig{
|
|
||||||
Stream: stream,
|
|
||||||
OutStream: out,
|
|
||||||
Stop: closeNotifier,
|
|
||||||
Version: httputils.VersionFromContext(ctx),
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.daemon.ContainerStats(vars["name"], config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getContainersLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Args are validated before the stream starts because when it starts we're
|
|
||||||
// sending HTTP 200 by writing an empty chunk of data to tell the client that
|
|
||||||
// daemon is going to stream. By sending this initial HTTP 200 we can't report
|
|
||||||
// any error after the stream starts (i.e. container not found, wrong parameters)
|
|
||||||
// with the appropriate status code.
|
|
||||||
stdout, stderr := httputils.BoolValue(r, "stdout"), httputils.BoolValue(r, "stderr")
|
|
||||||
if !(stdout || stderr) {
|
|
||||||
return fmt.Errorf("Bad parameters: you must choose at least one stream")
|
|
||||||
}
|
|
||||||
|
|
||||||
var since time.Time
|
|
||||||
if r.Form.Get("since") != "" {
|
|
||||||
s, err := strconv.ParseInt(r.Form.Get("since"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
since = time.Unix(s, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
var closeNotifier <-chan bool
|
|
||||||
if notifier, ok := w.(http.CloseNotifier); ok {
|
|
||||||
closeNotifier = notifier.CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
containerName := vars["name"]
|
|
||||||
|
|
||||||
if !s.daemon.Exists(containerName) {
|
|
||||||
return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
|
|
||||||
}
|
|
||||||
|
|
||||||
outStream := ioutils.NewWriteFlusher(w)
|
|
||||||
// write an empty chunk of data (this is to ensure that the
|
|
||||||
// HTTP Response is sent immediately, even if the container has
|
|
||||||
// not yet produced any data)
|
|
||||||
outStream.Write(nil)
|
|
||||||
|
|
||||||
logsConfig := &daemon.ContainerLogsConfig{
|
|
||||||
Follow: httputils.BoolValue(r, "follow"),
|
|
||||||
Timestamps: httputils.BoolValue(r, "timestamps"),
|
|
||||||
Since: since,
|
|
||||||
Tail: r.Form.Get("tail"),
|
|
||||||
UseStdout: stdout,
|
|
||||||
UseStderr: stderr,
|
|
||||||
OutStream: outStream,
|
|
||||||
Stop: closeNotifier,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerLogs(containerName, logsConfig); err != nil {
|
|
||||||
// The client may be expecting all of the data we're sending to
|
|
||||||
// be multiplexed, so send it through OutStream, which will
|
|
||||||
// have been set up to handle that if needed.
|
|
||||||
fmt.Fprintf(logsConfig.OutStream, "Error running logs job: %s\n", utils.GetErrorMessage(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getContainersExport(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
return s.daemon.ContainerExport(vars["name"], w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainersStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
// If contentLength is -1, we can assumed chunked encoding
|
|
||||||
// or more technically that the length is unknown
|
|
||||||
// https://golang.org/src/pkg/net/http/request.go#L139
|
|
||||||
// net/http otherwise seems to swallow any headers related to chunked encoding
|
|
||||||
// including r.TransferEncoding
|
|
||||||
// allow a nil body for backwards compatibility
|
|
||||||
var hostConfig *runconfig.HostConfig
|
|
||||||
if r.Body != nil && (r.ContentLength > 0 || r.ContentLength == -1) {
|
|
||||||
if err := httputils.CheckForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
c, err := runconfig.DecodeHostConfig(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
hostConfig = c
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerStart(vars["name"], hostConfig); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainersStop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
seconds, _ := strconv.Atoi(r.Form.Get("t"))
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerStop(vars["name"], seconds); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainersKill(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var sig syscall.Signal
|
|
||||||
name := vars["name"]
|
|
||||||
|
|
||||||
// If we have a signal, look at it. Otherwise, do nothing
|
|
||||||
if sigStr := r.Form.Get("signal"); sigStr != "" {
|
|
||||||
var err error
|
|
||||||
if sig, err = signal.ParseSignal(sigStr); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerKill(name, uint64(sig)); err != nil {
|
|
||||||
theErr, isDerr := err.(errcode.ErrorCoder)
|
|
||||||
isStopped := isDerr && theErr.ErrorCode() == derr.ErrorCodeNotRunning
|
|
||||||
|
|
||||||
// Return error that's not caused because the container is stopped.
|
|
||||||
// Return error if the container is not running and the api is >= 1.20
|
|
||||||
// to keep backwards compatibility.
|
|
||||||
version := httputils.VersionFromContext(ctx)
|
|
||||||
if version.GreaterThanOrEqualTo("1.20") || !isStopped {
|
|
||||||
return fmt.Errorf("Cannot kill container %s: %v", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainersRestart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout, _ := strconv.Atoi(r.Form.Get("t"))
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerRestart(vars["name"], timeout); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainersPause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerPause(vars["name"]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainersUnpause(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerUnpause(vars["name"]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainersWait(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
status, err := s.daemon.ContainerWait(vars["name"], -1*time.Second)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, &types.ContainerWaitResponse{
|
|
||||||
StatusCode: status,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getContainersChanges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
changes, err := s.daemon.ContainerChanges(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, changes)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getContainersTop(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
procList, err := s.daemon.ContainerTop(vars["name"], r.Form.Get("ps_args"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, procList)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainerRename(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
newName := r.Form.Get("name")
|
|
||||||
if err := s.daemon.ContainerRename(name, newName); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainersCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := httputils.CheckForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
name := r.Form.Get("name")
|
|
||||||
|
|
||||||
config, hostConfig, err := runconfig.DecodeContainerConfig(r.Body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
version := httputils.VersionFromContext(ctx)
|
|
||||||
adjustCPUShares := version.LessThan("1.19")
|
|
||||||
|
|
||||||
ccr, err := s.daemon.ContainerCreate(name, config, hostConfig, adjustCPUShares)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusCreated, ccr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) deleteContainers(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
config := &daemon.ContainerRmConfig{
|
|
||||||
ForceRemove: httputils.BoolValue(r, "force"),
|
|
||||||
RemoveVolume: httputils.BoolValue(r, "v"),
|
|
||||||
RemoveLink: httputils.BoolValue(r, "link"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerRm(name, config); err != nil {
|
|
||||||
// Force a 404 for the empty string
|
|
||||||
if strings.Contains(strings.ToLower(err.Error()), "prefix can't be empty") {
|
|
||||||
return fmt.Errorf("no such id: \"\"")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainersResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
height, err := strconv.Atoi(r.Form.Get("h"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
width, err := strconv.Atoi(r.Form.Get("w"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.daemon.ContainerResize(vars["name"], height, width)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
containerName := vars["name"]
|
|
||||||
|
|
||||||
if !s.daemon.Exists(containerName) {
|
|
||||||
return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
|
|
||||||
}
|
|
||||||
|
|
||||||
inStream, outStream, err := httputils.HijackConnection(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer httputils.CloseStreams(inStream, outStream)
|
|
||||||
|
|
||||||
if _, ok := r.Header["Upgrade"]; ok {
|
|
||||||
fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
attachWithLogsConfig := &daemon.ContainerAttachWithLogsConfig{
|
|
||||||
InStream: inStream,
|
|
||||||
OutStream: outStream,
|
|
||||||
UseStdin: httputils.BoolValue(r, "stdin"),
|
|
||||||
UseStdout: httputils.BoolValue(r, "stdout"),
|
|
||||||
UseStderr: httputils.BoolValue(r, "stderr"),
|
|
||||||
Logs: httputils.BoolValue(r, "logs"),
|
|
||||||
Stream: httputils.BoolValue(r, "stream"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerAttachWithLogs(containerName, attachWithLogsConfig); err != nil {
|
|
||||||
fmt.Fprintf(outStream, "Error attaching: %s\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) wsContainersAttach(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
containerName := vars["name"]
|
|
||||||
|
|
||||||
if !s.daemon.Exists(containerName) {
|
|
||||||
return derr.ErrorCodeNoSuchContainer.WithArgs(containerName)
|
|
||||||
}
|
|
||||||
|
|
||||||
h := websocket.Handler(func(ws *websocket.Conn) {
|
|
||||||
defer ws.Close()
|
|
||||||
|
|
||||||
wsAttachWithLogsConfig := &daemon.ContainerWsAttachWithLogsConfig{
|
|
||||||
InStream: ws,
|
|
||||||
OutStream: ws,
|
|
||||||
ErrStream: ws,
|
|
||||||
Logs: httputils.BoolValue(r, "logs"),
|
|
||||||
Stream: httputils.BoolValue(r, "stream"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ContainerWsAttachWithLogs(containerName, wsAttachWithLogsConfig); err != nil {
|
|
||||||
logrus.Errorf("Error attaching websocket: %s", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
ws := websocket.Server{Handler: h, Handshake: nil}
|
|
||||||
ws.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// postContainersCopy is deprecated in favor of getContainersArchive.
|
|
||||||
func (s *router) postContainersCopy(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.CheckForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := types.CopyConfig{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if cfg.Resource == "" {
|
|
||||||
return fmt.Errorf("Path cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
data, err := s.daemon.ContainerCopy(vars["name"], cfg.Resource)
|
|
||||||
if err != nil {
|
|
||||||
if strings.Contains(strings.ToLower(err.Error()), "no such id") {
|
|
||||||
w.WriteHeader(http.StatusNotFound)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if os.IsNotExist(err) {
|
|
||||||
return fmt.Errorf("Could not find the file %s in container %s", cfg.Resource, vars["name"])
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer data.Close()
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/x-tar")
|
|
||||||
if _, err := io.Copy(w, data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// // Encode the stat to JSON, base64 encode, and place in a header.
|
|
||||||
func setContainerPathStatHeader(stat *types.ContainerPathStat, header http.Header) error {
|
|
||||||
statJSON, err := json.Marshal(stat)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
header.Set(
|
|
||||||
"X-Docker-Container-Path-Stat",
|
|
||||||
base64.StdEncoding.EncodeToString(statJSON),
|
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) headContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
v, err := httputils.ArchiveFormValues(r, vars)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
stat, err := s.daemon.ContainerStatPath(v.Name, v.Path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return setContainerPathStatHeader(stat, w.Header())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
v, err := httputils.ArchiveFormValues(r, vars)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tarArchive, stat, err := s.daemon.ContainerArchivePath(v.Name, v.Path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer tarArchive.Close()
|
|
||||||
|
|
||||||
if err := setContainerPathStatHeader(stat, w.Header()); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/x-tar")
|
|
||||||
_, err = io.Copy(w, tarArchive)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) putContainersArchive(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
v, err := httputils.ArchiveFormValues(r, vars)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
noOverwriteDirNonDir := httputils.BoolValue(r, "noOverwriteDirNonDir")
|
|
||||||
return s.daemon.ContainerExtractToDir(v.Name, v.Path, noOverwriteDirNonDir, r.Body)
|
|
||||||
}
|
|
||||||
@@ -1,127 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/pkg/stdcopy"
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *router) getExecByID(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
eConfig, err := s.daemon.ContainerExecInspect(vars["id"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, eConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainerExecCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := httputils.CheckForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
name := vars["name"]
|
|
||||||
|
|
||||||
execConfig := &runconfig.ExecConfig{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(execConfig); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
execConfig.Container = name
|
|
||||||
|
|
||||||
if len(execConfig.Cmd) == 0 {
|
|
||||||
return fmt.Errorf("No exec command specified")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register an instance of Exec in container.
|
|
||||||
id, err := s.daemon.ContainerExecCreate(execConfig)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Error setting up exec command in container %s: %s", name, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerExecCreateResponse{
|
|
||||||
ID: id,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(vishh): Refactor the code to avoid having to specify stream config as part of both create and start.
|
|
||||||
func (s *router) postContainerExecStart(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
execName = vars["name"]
|
|
||||||
stdin, inStream io.ReadCloser
|
|
||||||
stdout, stderr, outStream io.Writer
|
|
||||||
)
|
|
||||||
|
|
||||||
execStartCheck := &types.ExecStartCheck{}
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(execStartCheck); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if exists, err := s.daemon.ExecExists(execName); !exists {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !execStartCheck.Detach {
|
|
||||||
var err error
|
|
||||||
// Setting up the streaming http interface.
|
|
||||||
inStream, outStream, err = httputils.HijackConnection(w)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer httputils.CloseStreams(inStream, outStream)
|
|
||||||
|
|
||||||
if _, ok := r.Header["Upgrade"]; ok {
|
|
||||||
fmt.Fprintf(outStream, "HTTP/1.1 101 UPGRADED\r\nContent-Type: application/vnd.docker.raw-stream\r\nConnection: Upgrade\r\nUpgrade: tcp\r\n\r\n")
|
|
||||||
} else {
|
|
||||||
fmt.Fprintf(outStream, "HTTP/1.1 200 OK\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
stdin = inStream
|
|
||||||
stdout = outStream
|
|
||||||
if !execStartCheck.Tty {
|
|
||||||
stderr = stdcopy.NewStdWriter(outStream, stdcopy.Stderr)
|
|
||||||
stdout = stdcopy.NewStdWriter(outStream, stdcopy.Stdout)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
outStream = w
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now run the user process in container.
|
|
||||||
if err := s.daemon.ContainerExecStart(execName, stdin, stdout, stderr); err != nil {
|
|
||||||
if execStartCheck.Detach {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
logrus.Errorf("Error running exec in container: %v\n", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postContainerExecResize(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
height, err := strconv.Atoi(r.Form.Get("h"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
width, err := strconv.Atoi(r.Form.Get("w"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return s.daemon.ContainerExecResize(vars["name"], height, width)
|
|
||||||
}
|
|
||||||
@@ -1,498 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/builder"
|
|
||||||
"github.com/docker/docker/builder/dockerfile"
|
|
||||||
"github.com/docker/docker/cliconfig"
|
|
||||||
"github.com/docker/docker/daemon/daemonbuilder"
|
|
||||||
derr "github.com/docker/docker/errors"
|
|
||||||
"github.com/docker/docker/graph"
|
|
||||||
"github.com/docker/docker/graph/tags"
|
|
||||||
"github.com/docker/docker/pkg/archive"
|
|
||||||
"github.com/docker/docker/pkg/chrootarchive"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/parsers"
|
|
||||||
"github.com/docker/docker/pkg/progressreader"
|
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
|
||||||
"github.com/docker/docker/pkg/ulimit"
|
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *router) postCommit(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := httputils.CheckForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
cname := r.Form.Get("container")
|
|
||||||
|
|
||||||
pause := httputils.BoolValue(r, "pause")
|
|
||||||
version := httputils.VersionFromContext(ctx)
|
|
||||||
if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") {
|
|
||||||
pause = true
|
|
||||||
}
|
|
||||||
|
|
||||||
c, _, err := runconfig.DecodeContainerConfig(r.Body)
|
|
||||||
if err != nil && err != io.EOF { //Do not fail if body is empty.
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
commitCfg := &dockerfile.CommitConfig{
|
|
||||||
Pause: pause,
|
|
||||||
Repo: r.Form.Get("repo"),
|
|
||||||
Tag: r.Form.Get("tag"),
|
|
||||||
Author: r.Form.Get("author"),
|
|
||||||
Comment: r.Form.Get("comment"),
|
|
||||||
Changes: r.Form["changes"],
|
|
||||||
Config: c,
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.daemon.Exists(cname) {
|
|
||||||
return derr.ErrorCodeNoSuchContainer.WithArgs(cname)
|
|
||||||
}
|
|
||||||
|
|
||||||
imgID, err := dockerfile.Commit(cname, s.daemon, commitCfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusCreated, &types.ContainerCommitResponse{
|
|
||||||
ID: string(imgID),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates an image from Pull or from Import
|
|
||||||
func (s *router) postImagesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
image = r.Form.Get("fromImage")
|
|
||||||
repo = r.Form.Get("repo")
|
|
||||||
tag = r.Form.Get("tag")
|
|
||||||
message = r.Form.Get("message")
|
|
||||||
)
|
|
||||||
authEncoded := r.Header.Get("X-Registry-Auth")
|
|
||||||
authConfig := &cliconfig.AuthConfig{}
|
|
||||||
if authEncoded != "" {
|
|
||||||
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
|
||||||
if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
|
|
||||||
// for a pull it is not an error if no auth was given
|
|
||||||
// to increase compatibility with the existing api it is defaulting to be empty
|
|
||||||
authConfig = &cliconfig.AuthConfig{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
output = ioutils.NewWriteFlusher(w)
|
|
||||||
)
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if image != "" { //pull
|
|
||||||
if tag == "" {
|
|
||||||
image, tag = parsers.ParseRepositoryTag(image)
|
|
||||||
}
|
|
||||||
metaHeaders := map[string][]string{}
|
|
||||||
for k, v := range r.Header {
|
|
||||||
if strings.HasPrefix(k, "X-Meta-") {
|
|
||||||
metaHeaders[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
imagePullConfig := &graph.ImagePullConfig{
|
|
||||||
MetaHeaders: metaHeaders,
|
|
||||||
AuthConfig: authConfig,
|
|
||||||
OutStream: output,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.daemon.PullImage(image, tag, imagePullConfig)
|
|
||||||
} else { //import
|
|
||||||
if tag == "" {
|
|
||||||
repo, tag = parsers.ParseRepositoryTag(repo)
|
|
||||||
}
|
|
||||||
|
|
||||||
src := r.Form.Get("fromSrc")
|
|
||||||
|
|
||||||
// 'err' MUST NOT be defined within this block, we need any error
|
|
||||||
// generated from the download to be available to the output
|
|
||||||
// stream processing below
|
|
||||||
var newConfig *runconfig.Config
|
|
||||||
newConfig, err = dockerfile.BuildFromConfig(&runconfig.Config{}, r.Form["changes"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.daemon.ImportImage(src, repo, tag, message, r.Body, output, newConfig)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
if !output.Flushed() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sf := streamformatter.NewJSONStreamFormatter()
|
|
||||||
output.Write(sf.FormatError(err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postImagesPush(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
metaHeaders := map[string][]string{}
|
|
||||||
for k, v := range r.Header {
|
|
||||||
if strings.HasPrefix(k, "X-Meta-") {
|
|
||||||
metaHeaders[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
authConfig := &cliconfig.AuthConfig{}
|
|
||||||
|
|
||||||
authEncoded := r.Header.Get("X-Registry-Auth")
|
|
||||||
if authEncoded != "" {
|
|
||||||
// the new format is to handle the authConfig as a header
|
|
||||||
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
|
||||||
if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil {
|
|
||||||
// to increase compatibility to existing api it is defaulting to be empty
|
|
||||||
authConfig = &cliconfig.AuthConfig{}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// the old format is supported for compatibility if there was no authConfig header
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil {
|
|
||||||
return fmt.Errorf("Bad parameters and missing X-Registry-Auth: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
output := ioutils.NewWriteFlusher(w)
|
|
||||||
imagePushConfig := &graph.ImagePushConfig{
|
|
||||||
MetaHeaders: metaHeaders,
|
|
||||||
AuthConfig: authConfig,
|
|
||||||
Tag: r.Form.Get("tag"),
|
|
||||||
OutStream: output,
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
if err := s.daemon.PushImage(name, imagePushConfig); err != nil {
|
|
||||||
if !output.Flushed() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sf := streamformatter.NewJSONStreamFormatter()
|
|
||||||
output.Write(sf.FormatError(err))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getImagesGet(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/x-tar")
|
|
||||||
|
|
||||||
output := ioutils.NewWriteFlusher(w)
|
|
||||||
var names []string
|
|
||||||
if name, ok := vars["name"]; ok {
|
|
||||||
names = []string{name}
|
|
||||||
} else {
|
|
||||||
names = r.Form["names"]
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.daemon.ExportImage(names, output); err != nil {
|
|
||||||
if !output.Flushed() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
sf := streamformatter.NewJSONStreamFormatter()
|
|
||||||
output.Write(sf.FormatError(err))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postImagesLoad(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
return s.daemon.LoadImage(r.Body, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) deleteImages(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
name := vars["name"]
|
|
||||||
|
|
||||||
if name == "" {
|
|
||||||
return fmt.Errorf("image name cannot be blank")
|
|
||||||
}
|
|
||||||
|
|
||||||
force := httputils.BoolValue(r, "force")
|
|
||||||
prune := !httputils.BoolValue(r, "noprune")
|
|
||||||
|
|
||||||
list, err := s.daemon.ImageDelete(name, force, prune)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, list)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getImagesByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
imageInspect, err := s.daemon.LookupImage(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, imageInspect)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
var (
|
|
||||||
authConfigs = map[string]cliconfig.AuthConfig{}
|
|
||||||
authConfigsEncoded = r.Header.Get("X-Registry-Config")
|
|
||||||
buildConfig = &dockerfile.Config{}
|
|
||||||
)
|
|
||||||
|
|
||||||
if authConfigsEncoded != "" {
|
|
||||||
authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
|
|
||||||
if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
|
|
||||||
// for a pull it is not an error if no auth was given
|
|
||||||
// to increase compatibility with the existing api it is defaulting
|
|
||||||
// to be empty.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
version := httputils.VersionFromContext(ctx)
|
|
||||||
output := ioutils.NewWriteFlusher(w)
|
|
||||||
sf := streamformatter.NewJSONStreamFormatter()
|
|
||||||
errf := func(err error) error {
|
|
||||||
// Do not write the error in the http output if it's still empty.
|
|
||||||
// This prevents from writing a 200(OK) when there is an interal error.
|
|
||||||
if !output.Flushed() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
|
|
||||||
if err != nil {
|
|
||||||
logrus.Warnf("could not write error response: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
|
|
||||||
buildConfig.Remove = true
|
|
||||||
} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
|
|
||||||
buildConfig.Remove = true
|
|
||||||
} else {
|
|
||||||
buildConfig.Remove = httputils.BoolValue(r, "rm")
|
|
||||||
}
|
|
||||||
if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
|
|
||||||
buildConfig.Pull = true
|
|
||||||
}
|
|
||||||
|
|
||||||
repoName, tag := parsers.ParseRepositoryTag(r.FormValue("t"))
|
|
||||||
if repoName != "" {
|
|
||||||
if err := registry.ValidateRepositoryName(repoName); err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
if len(tag) > 0 {
|
|
||||||
if err := tags.ValidateTagName(tag); err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildConfig.DockerfileName = r.FormValue("dockerfile")
|
|
||||||
buildConfig.Verbose = !httputils.BoolValue(r, "q")
|
|
||||||
buildConfig.UseCache = !httputils.BoolValue(r, "nocache")
|
|
||||||
buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm")
|
|
||||||
buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
|
|
||||||
buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory")
|
|
||||||
buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
|
|
||||||
buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
|
|
||||||
buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
|
|
||||||
buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
|
|
||||||
buildConfig.CPUSetMems = r.FormValue("cpusetmems")
|
|
||||||
buildConfig.CgroupParent = r.FormValue("cgroupparent")
|
|
||||||
|
|
||||||
var buildUlimits = []*ulimit.Ulimit{}
|
|
||||||
ulimitsJSON := r.FormValue("ulimits")
|
|
||||||
if ulimitsJSON != "" {
|
|
||||||
if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
buildConfig.Ulimits = buildUlimits
|
|
||||||
}
|
|
||||||
|
|
||||||
var buildArgs = map[string]string{}
|
|
||||||
buildArgsJSON := r.FormValue("buildargs")
|
|
||||||
if buildArgsJSON != "" {
|
|
||||||
if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
buildConfig.BuildArgs = buildArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteURL := r.FormValue("remote")
|
|
||||||
|
|
||||||
// Currently, only used if context is from a remote url.
|
|
||||||
// The field `In` is set by DetectContextFromRemoteURL.
|
|
||||||
// Look at code in DetectContextFromRemoteURL for more information.
|
|
||||||
pReader := &progressreader.Config{
|
|
||||||
// TODO: make progressreader streamformatter-agnostic
|
|
||||||
Out: output,
|
|
||||||
Formatter: sf,
|
|
||||||
Size: r.ContentLength,
|
|
||||||
NewLines: true,
|
|
||||||
ID: "Downloading context",
|
|
||||||
Action: remoteURL,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
context builder.ModifiableContext
|
|
||||||
dockerfileName string
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, pReader)
|
|
||||||
if err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := context.Close(); err != nil {
|
|
||||||
logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
uidMaps, gidMaps := s.daemon.GetUIDGIDMaps()
|
|
||||||
defaultArchiver := &archive.Archiver{
|
|
||||||
Untar: chrootarchive.Untar,
|
|
||||||
UIDMaps: uidMaps,
|
|
||||||
GIDMaps: gidMaps,
|
|
||||||
}
|
|
||||||
docker := daemonbuilder.Docker{s.daemon, output, authConfigs, defaultArchiver}
|
|
||||||
|
|
||||||
b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{context}, nil)
|
|
||||||
if err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
|
|
||||||
b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf}
|
|
||||||
|
|
||||||
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
|
||||||
finished := make(chan struct{})
|
|
||||||
defer close(finished)
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-finished:
|
|
||||||
case <-closeNotifier.CloseNotify():
|
|
||||||
logrus.Infof("Client disconnected, cancelling job: build")
|
|
||||||
b.Cancel()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(dockerfileName) > 0 {
|
|
||||||
b.DockerfileName = dockerfileName
|
|
||||||
}
|
|
||||||
|
|
||||||
imgID, err := b.Build()
|
|
||||||
if err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if repoName != "" {
|
|
||||||
if err := s.daemon.TagImage(repoName, tag, string(imgID), true); err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: The filter parameter could just be a match filter
|
|
||||||
images, err := s.daemon.ListImages(r.Form.Get("filters"), r.Form.Get("filter"), httputils.BoolValue(r, "all"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, images)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getImagesHistory(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
name := vars["name"]
|
|
||||||
history, err := s.daemon.ImageHistory(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, history)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postImagesTag(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
repo := r.Form.Get("repo")
|
|
||||||
tag := r.Form.Get("tag")
|
|
||||||
name := vars["name"]
|
|
||||||
force := httputils.BoolValue(r, "force")
|
|
||||||
if err := s.daemon.TagImage(repo, tag, name, force); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.daemon.EventsService.Log("tag", utils.ImageReference(repo, tag), "")
|
|
||||||
w.WriteHeader(http.StatusCreated)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getImagesSearch(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
config *cliconfig.AuthConfig
|
|
||||||
authEncoded = r.Header.Get("X-Registry-Auth")
|
|
||||||
headers = map[string][]string{}
|
|
||||||
)
|
|
||||||
|
|
||||||
if authEncoded != "" {
|
|
||||||
authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded))
|
|
||||||
if err := json.NewDecoder(authJSON).Decode(&config); err != nil {
|
|
||||||
// for a search it is not an error if no auth was given
|
|
||||||
// to increase compatibility with the existing api it is defaulting to be empty
|
|
||||||
config = &cliconfig.AuthConfig{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for k, v := range r.Header {
|
|
||||||
if strings.HasPrefix(k, "X-Meta-") {
|
|
||||||
headers[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
query, err := s.daemon.SearchRegistryForImages(r.Form.Get("term"), config, headers)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, query.Results)
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api"
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/autogen/dockerversion"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
|
||||||
"github.com/docker/docker/pkg/parsers/kernel"
|
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *router) getVersion(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
v := &types.Version{
|
|
||||||
Version: dockerversion.VERSION,
|
|
||||||
APIVersion: api.Version,
|
|
||||||
GitCommit: dockerversion.GITCOMMIT,
|
|
||||||
GoVersion: runtime.Version(),
|
|
||||||
Os: runtime.GOOS,
|
|
||||||
Arch: runtime.GOARCH,
|
|
||||||
BuildTime: dockerversion.BUILDTIME,
|
|
||||||
}
|
|
||||||
|
|
||||||
version := httputils.VersionFromContext(ctx)
|
|
||||||
|
|
||||||
if version.GreaterThanOrEqualTo("1.19") {
|
|
||||||
v.Experimental = utils.ExperimentalBuild()
|
|
||||||
}
|
|
||||||
|
|
||||||
if kernelVersion, err := kernel.GetKernelVersion(); err == nil {
|
|
||||||
v.KernelVersion = kernelVersion.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getInfo(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
info, err := s.daemon.SystemInfo()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, info)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOutputEncoder(w http.ResponseWriter) *json.Encoder {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
outStream := ioutils.NewWriteFlusher(w)
|
|
||||||
// Write an empty chunk of data.
|
|
||||||
// This is to ensure that the HTTP status code is sent immediately,
|
|
||||||
// so that it will not block the receiver.
|
|
||||||
outStream.Write(nil)
|
|
||||||
return json.NewEncoder(outStream)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getEvents(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
since, err := httputils.Int64ValueOrDefault(r, "since", -1)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
until, err := httputils.Int64ValueOrDefault(r, "until", -1)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
timer := time.NewTimer(0)
|
|
||||||
timer.Stop()
|
|
||||||
if until > 0 {
|
|
||||||
dur := time.Unix(until, 0).Sub(time.Now())
|
|
||||||
timer = time.NewTimer(dur)
|
|
||||||
}
|
|
||||||
|
|
||||||
ef, err := filters.FromParam(r.Form.Get("filters"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
enc := buildOutputEncoder(w)
|
|
||||||
d := s.daemon
|
|
||||||
es := d.EventsService
|
|
||||||
current, l := es.Subscribe()
|
|
||||||
defer es.Evict(l)
|
|
||||||
|
|
||||||
eventFilter := d.GetEventFilter(ef)
|
|
||||||
handleEvent := func(ev *jsonmessage.JSONMessage) error {
|
|
||||||
if eventFilter.Include(ev) {
|
|
||||||
if err := enc.Encode(ev); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if since == -1 {
|
|
||||||
current = nil
|
|
||||||
}
|
|
||||||
for _, ev := range current {
|
|
||||||
if ev.Time < since {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := handleEvent(ev); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var closeNotify <-chan bool
|
|
||||||
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
|
||||||
closeNotify = closeNotifier.CloseNotify()
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case ev := <-l:
|
|
||||||
jev, ok := ev.(*jsonmessage.JSONMessage)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := handleEvent(jev); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
case <-timer.C:
|
|
||||||
return nil
|
|
||||||
case <-closeNotify:
|
|
||||||
logrus.Debug("Client disconnected, stop sending events")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// getContainersByName inspects containers configuration and serializes it as json.
|
|
||||||
func (s *router) getContainersByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
displaySize := httputils.BoolValue(r, "size")
|
|
||||||
|
|
||||||
var json interface{}
|
|
||||||
var err error
|
|
||||||
|
|
||||||
version := httputils.VersionFromContext(ctx)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case version.LessThan("1.20"):
|
|
||||||
json, err = s.daemon.ContainerInspectPre120(vars["name"])
|
|
||||||
case version.Equal("1.20"):
|
|
||||||
json, err = s.daemon.ContainerInspect120(vars["name"])
|
|
||||||
default:
|
|
||||||
json, err = s.daemon.ContainerInspect(vars["name"], displaySize)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, json)
|
|
||||||
}
|
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
dkrouter "github.com/docker/docker/api/server/router"
|
|
||||||
"github.com/docker/docker/daemon"
|
|
||||||
)
|
|
||||||
|
|
||||||
// router is a docker router that talks with the local docker daemon.
|
|
||||||
type router struct {
|
|
||||||
daemon *daemon.Daemon
|
|
||||||
routes []dkrouter.Route
|
|
||||||
}
|
|
||||||
|
|
||||||
// localRoute defines an individual API route to connect with the docker daemon.
|
|
||||||
// It implements router.Route.
|
|
||||||
type localRoute struct {
|
|
||||||
method string
|
|
||||||
path string
|
|
||||||
handler httputils.APIFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler returns the APIFunc to let the server wrap it in middlewares
|
|
||||||
func (l localRoute) Handler() httputils.APIFunc {
|
|
||||||
return l.handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// Method returns the http method that the route responds to.
|
|
||||||
func (l localRoute) Method() string {
|
|
||||||
return l.method
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path returns the subpath where the route responds to.
|
|
||||||
func (l localRoute) Path() string {
|
|
||||||
return l.path
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRoute initialies a new local route for the reouter
|
|
||||||
func NewRoute(method, path string, handler httputils.APIFunc) dkrouter.Route {
|
|
||||||
return localRoute{method, path, handler}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGetRoute initializes a new route with the http method GET.
|
|
||||||
func NewGetRoute(path string, handler httputils.APIFunc) dkrouter.Route {
|
|
||||||
return NewRoute("GET", path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPostRoute initializes a new route with the http method POST.
|
|
||||||
func NewPostRoute(path string, handler httputils.APIFunc) dkrouter.Route {
|
|
||||||
return NewRoute("POST", path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPutRoute initializes a new route with the http method PUT.
|
|
||||||
func NewPutRoute(path string, handler httputils.APIFunc) dkrouter.Route {
|
|
||||||
return NewRoute("PUT", path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDeleteRoute initializes a new route with the http method DELETE.
|
|
||||||
func NewDeleteRoute(path string, handler httputils.APIFunc) dkrouter.Route {
|
|
||||||
return NewRoute("DELETE", path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOptionsRoute initializes a new route with the http method OPTIONS
|
|
||||||
func NewOptionsRoute(path string, handler httputils.APIFunc) dkrouter.Route {
|
|
||||||
return NewRoute("OPTIONS", path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHeadRoute initializes a new route with the http method HEAD.
|
|
||||||
func NewHeadRoute(path string, handler httputils.APIFunc) dkrouter.Route {
|
|
||||||
return NewRoute("HEAD", path, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRouter initializes a local router with a new daemon.
|
|
||||||
func NewRouter(daemon *daemon.Daemon) dkrouter.Router {
|
|
||||||
r := &router{
|
|
||||||
daemon: daemon,
|
|
||||||
}
|
|
||||||
r.initRoutes()
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routes returns the list of routes registered in the router.
|
|
||||||
func (r *router) Routes() []dkrouter.Route {
|
|
||||||
return r.routes
|
|
||||||
}
|
|
||||||
|
|
||||||
// initRoutes initializes the routes in this router
|
|
||||||
func (r *router) initRoutes() {
|
|
||||||
r.routes = []dkrouter.Route{
|
|
||||||
// HEAD
|
|
||||||
NewHeadRoute("/containers/{name:.*}/archive", r.headContainersArchive),
|
|
||||||
// OPTIONS
|
|
||||||
NewOptionsRoute("/", optionsHandler),
|
|
||||||
// GET
|
|
||||||
NewGetRoute("/_ping", pingHandler),
|
|
||||||
NewGetRoute("/events", r.getEvents),
|
|
||||||
NewGetRoute("/info", r.getInfo),
|
|
||||||
NewGetRoute("/version", r.getVersion),
|
|
||||||
NewGetRoute("/images/json", r.getImagesJSON),
|
|
||||||
NewGetRoute("/images/search", r.getImagesSearch),
|
|
||||||
NewGetRoute("/images/get", r.getImagesGet),
|
|
||||||
NewGetRoute("/images/{name:.*}/get", r.getImagesGet),
|
|
||||||
NewGetRoute("/images/{name:.*}/history", r.getImagesHistory),
|
|
||||||
NewGetRoute("/images/{name:.*}/json", r.getImagesByName),
|
|
||||||
NewGetRoute("/containers/json", r.getContainersJSON),
|
|
||||||
NewGetRoute("/containers/{name:.*}/export", r.getContainersExport),
|
|
||||||
NewGetRoute("/containers/{name:.*}/changes", r.getContainersChanges),
|
|
||||||
NewGetRoute("/containers/{name:.*}/json", r.getContainersByName),
|
|
||||||
NewGetRoute("/containers/{name:.*}/top", r.getContainersTop),
|
|
||||||
NewGetRoute("/containers/{name:.*}/logs", r.getContainersLogs),
|
|
||||||
NewGetRoute("/containers/{name:.*}/stats", r.getContainersStats),
|
|
||||||
NewGetRoute("/containers/{name:.*}/attach/ws", r.wsContainersAttach),
|
|
||||||
NewGetRoute("/exec/{id:.*}/json", r.getExecByID),
|
|
||||||
NewGetRoute("/containers/{name:.*}/archive", r.getContainersArchive),
|
|
||||||
NewGetRoute("/volumes", r.getVolumesList),
|
|
||||||
NewGetRoute("/volumes/{name:.*}", r.getVolumeByName),
|
|
||||||
// POST
|
|
||||||
NewPostRoute("/auth", r.postAuth),
|
|
||||||
NewPostRoute("/commit", r.postCommit),
|
|
||||||
NewPostRoute("/build", r.postBuild),
|
|
||||||
NewPostRoute("/images/create", r.postImagesCreate),
|
|
||||||
NewPostRoute("/images/load", r.postImagesLoad),
|
|
||||||
NewPostRoute("/images/{name:.*}/push", r.postImagesPush),
|
|
||||||
NewPostRoute("/images/{name:.*}/tag", r.postImagesTag),
|
|
||||||
NewPostRoute("/containers/create", r.postContainersCreate),
|
|
||||||
NewPostRoute("/containers/{name:.*}/kill", r.postContainersKill),
|
|
||||||
NewPostRoute("/containers/{name:.*}/pause", r.postContainersPause),
|
|
||||||
NewPostRoute("/containers/{name:.*}/unpause", r.postContainersUnpause),
|
|
||||||
NewPostRoute("/containers/{name:.*}/restart", r.postContainersRestart),
|
|
||||||
NewPostRoute("/containers/{name:.*}/start", r.postContainersStart),
|
|
||||||
NewPostRoute("/containers/{name:.*}/stop", r.postContainersStop),
|
|
||||||
NewPostRoute("/containers/{name:.*}/wait", r.postContainersWait),
|
|
||||||
NewPostRoute("/containers/{name:.*}/resize", r.postContainersResize),
|
|
||||||
NewPostRoute("/containers/{name:.*}/attach", r.postContainersAttach),
|
|
||||||
NewPostRoute("/containers/{name:.*}/copy", r.postContainersCopy),
|
|
||||||
NewPostRoute("/containers/{name:.*}/exec", r.postContainerExecCreate),
|
|
||||||
NewPostRoute("/exec/{name:.*}/start", r.postContainerExecStart),
|
|
||||||
NewPostRoute("/exec/{name:.*}/resize", r.postContainerExecResize),
|
|
||||||
NewPostRoute("/containers/{name:.*}/rename", r.postContainerRename),
|
|
||||||
NewPostRoute("/volumes", r.postVolumesCreate),
|
|
||||||
// PUT
|
|
||||||
NewPutRoute("/containers/{name:.*}/archive", r.putContainersArchive),
|
|
||||||
// DELETE
|
|
||||||
NewDeleteRoute("/containers/{name:.*}", r.deleteContainers),
|
|
||||||
NewDeleteRoute("/images/{name:.*}", r.deleteImages),
|
|
||||||
NewDeleteRoute("/volumes/{name:.*}", r.deleteVolumes),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func optionsHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func pingHandler(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
_, err := w.Write([]byte{'O', 'K'})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package local
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (s *router) getVolumesList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
volumes, err := s.daemon.Volumes(r.Form.Get("filters"))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, &types.VolumesListResponse{Volumes: volumes})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getVolumeByName(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := s.daemon.VolumeInspect(vars["name"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) postVolumesCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := httputils.CheckForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var req types.VolumeCreateRequest
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
volume, err := s.daemon.VolumeCreate(req.Name, req.Driver, req.DriverOpts)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return httputils.WriteJSON(w, http.StatusCreated, volume)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) deleteVolumes(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.daemon.VolumeRm(vars["name"]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.WriteHeader(http.StatusNoContent)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/docker/api/server/router"
|
|
||||||
"github.com/docker/docker/api/server/router/local"
|
|
||||||
"github.com/docker/docker/daemon"
|
|
||||||
)
|
|
||||||
|
|
||||||
// networkRouter is a router to talk with the network controller
|
|
||||||
type networkRouter struct {
|
|
||||||
daemon *daemon.Daemon
|
|
||||||
routes []router.Route
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewRouter initializes a new network router
|
|
||||||
func NewRouter(d *daemon.Daemon) router.Router {
|
|
||||||
r := &networkRouter{
|
|
||||||
daemon: d,
|
|
||||||
}
|
|
||||||
r.initRoutes()
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Routes returns the available routes to the network controller
|
|
||||||
func (r *networkRouter) Routes() []router.Route {
|
|
||||||
return r.routes
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *networkRouter) initRoutes() {
|
|
||||||
r.routes = []router.Route{
|
|
||||||
// GET
|
|
||||||
local.NewGetRoute("/networks", r.getNetworksList),
|
|
||||||
local.NewGetRoute("/networks/{id:.*}", r.getNetwork),
|
|
||||||
// POST
|
|
||||||
local.NewPostRoute("/networks/create", r.postNetworkCreate),
|
|
||||||
local.NewPostRoute("/networks/{id:.*}/connect", r.postNetworkConnect),
|
|
||||||
local.NewPostRoute("/networks/{id:.*}/disconnect", r.postNetworkDisconnect),
|
|
||||||
// DELETE
|
|
||||||
local.NewDeleteRoute("/networks/{id:.*}", r.deleteNetwork),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
package network
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/daemon"
|
|
||||||
"github.com/docker/docker/daemon/network"
|
|
||||||
"github.com/docker/docker/pkg/parsers/filters"
|
|
||||||
"github.com/docker/libnetwork"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (n *networkRouter) getNetworksList(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
filter := r.Form.Get("filters")
|
|
||||||
netFilters, err := filters.FromParam(filter)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
list := []*types.NetworkResource{}
|
|
||||||
var nameFilter, idFilter bool
|
|
||||||
var names, ids []string
|
|
||||||
if names, nameFilter = netFilters["name"]; nameFilter {
|
|
||||||
for _, name := range names {
|
|
||||||
if nw, err := n.daemon.GetNetwork(name, daemon.NetworkByName); err == nil {
|
|
||||||
list = append(list, buildNetworkResource(nw))
|
|
||||||
} else {
|
|
||||||
logrus.Errorf("failed to get network for filter=%s : %v", name, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ids, idFilter = netFilters["id"]; idFilter {
|
|
||||||
for _, id := range ids {
|
|
||||||
for _, nw := range n.daemon.GetNetworksByID(id) {
|
|
||||||
list = append(list, buildNetworkResource(nw))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !nameFilter && !idFilter {
|
|
||||||
nwList := n.daemon.GetNetworksByID("")
|
|
||||||
for _, nw := range nwList {
|
|
||||||
list = append(list, buildNetworkResource(nw))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, list)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
nw, err := n.daemon.FindNetwork(vars["id"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return httputils.WriteJSON(w, http.StatusOK, buildNetworkResource(nw))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *networkRouter) postNetworkCreate(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
var create types.NetworkCreate
|
|
||||||
var warning string
|
|
||||||
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := httputils.CheckForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&create); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
nw, err := n.daemon.GetNetwork(create.Name, daemon.NetworkByName)
|
|
||||||
if _, ok := err.(libnetwork.ErrNoSuchNetwork); err != nil && !ok {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if nw != nil {
|
|
||||||
if create.CheckDuplicate {
|
|
||||||
return libnetwork.NetworkNameError(create.Name)
|
|
||||||
}
|
|
||||||
warning = fmt.Sprintf("Network with name %s (id : %s) already exists", nw.Name(), nw.ID())
|
|
||||||
}
|
|
||||||
|
|
||||||
nw, err = n.daemon.CreateNetwork(create.Name, create.Driver, create.IPAM, create.Options)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return httputils.WriteJSON(w, http.StatusCreated, &types.NetworkCreateResponse{
|
|
||||||
ID: nw.ID(),
|
|
||||||
Warning: warning,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *networkRouter) postNetworkConnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
var connect types.NetworkConnect
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := httputils.CheckForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&connect); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
nw, err := n.daemon.FindNetwork(vars["id"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return n.daemon.ConnectContainerToNetwork(connect.Container, nw.Name())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *networkRouter) postNetworkDisconnect(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
var disconnect types.NetworkDisconnect
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := httputils.CheckForJSON(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&disconnect); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
nw, err := n.daemon.FindNetwork(vars["id"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return n.daemon.DisconnectContainerFromNetwork(disconnect.Container, nw)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *networkRouter) deleteNetwork(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
nw, err := n.daemon.FindNetwork(vars["id"])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nw.Delete()
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildNetworkResource(nw libnetwork.Network) *types.NetworkResource {
|
|
||||||
r := &types.NetworkResource{}
|
|
||||||
if nw == nil {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Name = nw.Name()
|
|
||||||
r.ID = nw.ID()
|
|
||||||
r.Scope = nw.Info().Scope()
|
|
||||||
r.Driver = nw.Type()
|
|
||||||
r.Options = nw.Info().DriverOptions()
|
|
||||||
r.Containers = make(map[string]types.EndpointResource)
|
|
||||||
buildIpamResources(r, nw)
|
|
||||||
|
|
||||||
epl := nw.Endpoints()
|
|
||||||
for _, e := range epl {
|
|
||||||
sb := e.Info().Sandbox()
|
|
||||||
if sb == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Containers[sb.ContainerID()] = buildEndpointResource(e)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildIpamResources(r *types.NetworkResource, nw libnetwork.Network) {
|
|
||||||
id, ipv4conf, ipv6conf := nw.Info().IpamConfig()
|
|
||||||
|
|
||||||
r.IPAM.Driver = id
|
|
||||||
|
|
||||||
r.IPAM.Config = []network.IPAMConfig{}
|
|
||||||
for _, ip4 := range ipv4conf {
|
|
||||||
iData := network.IPAMConfig{}
|
|
||||||
iData.Subnet = ip4.PreferredPool
|
|
||||||
iData.IPRange = ip4.SubPool
|
|
||||||
iData.Gateway = ip4.Gateway
|
|
||||||
iData.AuxAddress = ip4.AuxAddresses
|
|
||||||
r.IPAM.Config = append(r.IPAM.Config, iData)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ip6 := range ipv6conf {
|
|
||||||
iData := network.IPAMConfig{}
|
|
||||||
iData.Subnet = ip6.PreferredPool
|
|
||||||
iData.IPRange = ip6.SubPool
|
|
||||||
iData.Gateway = ip6.Gateway
|
|
||||||
iData.AuxAddress = ip6.AuxAddresses
|
|
||||||
r.IPAM.Config = append(r.IPAM.Config, iData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildEndpointResource(e libnetwork.Endpoint) types.EndpointResource {
|
|
||||||
er := types.EndpointResource{}
|
|
||||||
if e == nil {
|
|
||||||
return er
|
|
||||||
}
|
|
||||||
|
|
||||||
er.EndpointID = e.ID()
|
|
||||||
if iface := e.Info().Iface(); iface != nil {
|
|
||||||
if mac := iface.MacAddress(); mac != nil {
|
|
||||||
er.MacAddress = mac.String()
|
|
||||||
}
|
|
||||||
if ip := iface.Address(); ip != nil && len(ip.IP) > 0 {
|
|
||||||
er.IPv4Address = ip.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
if ipv6 := iface.AddressIPv6(); ipv6 != nil && len(ipv6.IP) > 0 {
|
|
||||||
er.IPv6Address = ipv6.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return er
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package router
|
|
||||||
|
|
||||||
import "github.com/docker/docker/api/server/httputils"
|
|
||||||
|
|
||||||
// Router defines an interface to specify a group of routes to add the the docker server.
|
|
||||||
type Router interface {
|
|
||||||
Routes() []Route
|
|
||||||
}
|
|
||||||
|
|
||||||
// Route defines an individual API route in the docker server.
|
|
||||||
type Route interface {
|
|
||||||
// Handler returns the raw function to create the http handler.
|
|
||||||
Handler() httputils.APIFunc
|
|
||||||
// Method returns the http method that the route responds to.
|
|
||||||
Method() string
|
|
||||||
// Path returns the subpath where the route responds to.
|
|
||||||
Path() string
|
|
||||||
}
|
|
||||||
+1622
-118
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,17 @@
|
|||||||
|
// +build experimental
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
func (s *Server) registerSubRouter() {
|
||||||
|
httpHandler := s.daemon.NetworkApiRouter()
|
||||||
|
|
||||||
|
subrouter := s.router.PathPrefix("/v{version:[0-9.]+}/networks").Subrouter()
|
||||||
|
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
|
||||||
|
subrouter = s.router.PathPrefix("/networks").Subrouter()
|
||||||
|
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
|
||||||
|
|
||||||
|
subrouter = s.router.PathPrefix("/v{version:[0-9.]+}/services").Subrouter()
|
||||||
|
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
|
||||||
|
subrouter = s.router.PathPrefix("/services").Subrouter()
|
||||||
|
subrouter.Methods("GET", "POST", "PUT", "DELETE").HandlerFunc(httpHandler)
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/daemon"
|
||||||
|
"github.com/docker/docker/pkg/sockets"
|
||||||
|
"github.com/docker/docker/pkg/systemd"
|
||||||
|
"github.com/docker/docker/pkg/version"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
"github.com/docker/libnetwork/portallocator"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// See http://git.kernel.org/cgit/linux/kernel/git/tip/tip.git/tree/kernel/sched/sched.h?id=8cd9234c64c584432f6992fe944ca9e46ca8ea76#n269
|
||||||
|
linuxMinCpuShares = 2
|
||||||
|
linuxMaxCpuShares = 262144
|
||||||
|
)
|
||||||
|
|
||||||
|
// newServer sets up the required serverClosers and does protocol specific checking.
|
||||||
|
func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
ls []net.Listener
|
||||||
|
)
|
||||||
|
switch proto {
|
||||||
|
case "fd":
|
||||||
|
ls, err = systemd.ListenFD(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// We don't want to start serving on these sockets until the
|
||||||
|
// daemon is initialized and installed. Otherwise required handlers
|
||||||
|
// won't be ready.
|
||||||
|
<-s.start
|
||||||
|
case "tcp":
|
||||||
|
l, err := s.initTcpSocket(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ls = append(ls, l)
|
||||||
|
case "unix":
|
||||||
|
l, err := sockets.NewUnixSocket(addr, s.cfg.SocketGroup, s.start)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ls = append(ls, l)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("Invalid protocol format: %q", proto)
|
||||||
|
}
|
||||||
|
var res []serverCloser
|
||||||
|
for _, l := range ls {
|
||||||
|
res = append(res, &HttpServer{
|
||||||
|
&http.Server{
|
||||||
|
Addr: addr,
|
||||||
|
Handler: s.router,
|
||||||
|
},
|
||||||
|
l,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
||||||
|
// Tell the init daemon we are accepting requests
|
||||||
|
s.daemon = d
|
||||||
|
s.registerSubRouter()
|
||||||
|
go systemd.SdNotify("READY=1")
|
||||||
|
// close the lock so the listeners start accepting connections
|
||||||
|
select {
|
||||||
|
case <-s.start:
|
||||||
|
default:
|
||||||
|
close(s.start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func allocateDaemonPort(addr string) error {
|
||||||
|
host, port, err := net.SplitHostPort(addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
intPort, err := strconv.Atoi(port)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var hostIPs []net.IP
|
||||||
|
if parsedIP := net.ParseIP(host); parsedIP != nil {
|
||||||
|
hostIPs = append(hostIPs, parsedIP)
|
||||||
|
} else if hostIPs, err = net.LookupIP(host); err != nil {
|
||||||
|
return fmt.Errorf("failed to lookup %s address in host specification", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
pa := portallocator.Get()
|
||||||
|
for _, hostIP := range hostIPs {
|
||||||
|
if _, err := pa.RequestPort(hostIP, "tcp", intPort); err != nil {
|
||||||
|
return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) {
|
||||||
|
if version.LessThan("1.19") {
|
||||||
|
if hostConfig != nil && hostConfig.CpuShares > 0 {
|
||||||
|
// Handle unsupported CpuShares
|
||||||
|
if hostConfig.CpuShares < linuxMinCpuShares {
|
||||||
|
logrus.Warnf("Changing requested CpuShares of %d to minimum allowed of %d", hostConfig.CpuShares, linuxMinCpuShares)
|
||||||
|
hostConfig.CpuShares = linuxMinCpuShares
|
||||||
|
} else if hostConfig.CpuShares > linuxMaxCpuShares {
|
||||||
|
logrus.Warnf("Changing requested CpuShares of %d to maximum allowed of %d", hostConfig.CpuShares, linuxMaxCpuShares)
|
||||||
|
hostConfig.CpuShares = linuxMaxCpuShares
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// +build linux
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/docker/docker/pkg/version"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAdjustCpuSharesOldApi(t *testing.T) {
|
||||||
|
apiVersion := version.Version("1.18")
|
||||||
|
hostConfig := &runconfig.HostConfig{
|
||||||
|
CpuShares: linuxMinCpuShares - 1,
|
||||||
|
}
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != linuxMinCpuShares {
|
||||||
|
t.Errorf("Expected CpuShares to be %d", linuxMinCpuShares)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = linuxMaxCpuShares + 1
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != linuxMaxCpuShares {
|
||||||
|
t.Errorf("Expected CpuShares to be %d", linuxMaxCpuShares)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = 0
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != 0 {
|
||||||
|
t.Error("Expected CpuShares to be unchanged")
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = 1024
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != 1024 {
|
||||||
|
t.Error("Expected CpuShares to be unchanged")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdjustCpuSharesNoAdjustment(t *testing.T) {
|
||||||
|
apiVersion := version.Version("1.19")
|
||||||
|
hostConfig := &runconfig.HostConfig{
|
||||||
|
CpuShares: linuxMinCpuShares - 1,
|
||||||
|
}
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != linuxMinCpuShares-1 {
|
||||||
|
t.Errorf("Expected CpuShares to be %d", linuxMinCpuShares-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = linuxMaxCpuShares + 1
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != linuxMaxCpuShares+1 {
|
||||||
|
t.Errorf("Expected CpuShares to be %d", linuxMaxCpuShares+1)
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = 0
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != 0 {
|
||||||
|
t.Error("Expected CpuShares to be unchanged")
|
||||||
|
}
|
||||||
|
|
||||||
|
hostConfig.CpuShares = 1024
|
||||||
|
adjustCpuShares(apiVersion, hostConfig)
|
||||||
|
if hostConfig.CpuShares != 1024 {
|
||||||
|
t.Error("Expected CpuShares to be unchanged")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// +build !experimental
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
func (s *Server) registerSubRouter() {
|
||||||
|
}
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/server/httputils"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestMiddlewares(t *testing.T) {
|
|
||||||
cfg := &Config{}
|
|
||||||
srv := &Server{
|
|
||||||
cfg: cfg,
|
|
||||||
}
|
|
||||||
|
|
||||||
req, _ := http.NewRequest("GET", "/containers/json", nil)
|
|
||||||
resp := httptest.NewRecorder()
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
localHandler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
if httputils.VersionFromContext(ctx) == "" {
|
|
||||||
t.Fatalf("Expected version, got empty string")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
handlerFunc := srv.handleWithGlobalMiddlewares(localHandler)
|
|
||||||
if err := handlerFunc(ctx, resp, req, map[string]string{}); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
// +build freebsd linux
|
|
||||||
|
|
||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/docker/pkg/sockets"
|
|
||||||
"github.com/docker/libnetwork/portallocator"
|
|
||||||
|
|
||||||
systemdActivation "github.com/coreos/go-systemd/activation"
|
|
||||||
)
|
|
||||||
|
|
||||||
// newServer sets up the required HTTPServers and does protocol specific checking.
|
|
||||||
// newServer does not set any muxers, you should set it later to Handler field
|
|
||||||
func (s *Server) newServer(proto, addr string) ([]*HTTPServer, error) {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
ls []net.Listener
|
|
||||||
)
|
|
||||||
switch proto {
|
|
||||||
case "fd":
|
|
||||||
ls, err = listenFD(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case "tcp":
|
|
||||||
l, err := s.initTCPSocket(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ls = append(ls, l)
|
|
||||||
case "unix":
|
|
||||||
l, err := sockets.NewUnixSocket(addr, s.cfg.SocketGroup, s.start)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ls = append(ls, l)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Invalid protocol format: %q", proto)
|
|
||||||
}
|
|
||||||
var res []*HTTPServer
|
|
||||||
for _, l := range ls {
|
|
||||||
res = append(res, &HTTPServer{
|
|
||||||
&http.Server{
|
|
||||||
Addr: addr,
|
|
||||||
},
|
|
||||||
l,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func allocateDaemonPort(addr string) error {
|
|
||||||
host, port, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
intPort, err := strconv.Atoi(port)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var hostIPs []net.IP
|
|
||||||
if parsedIP := net.ParseIP(host); parsedIP != nil {
|
|
||||||
hostIPs = append(hostIPs, parsedIP)
|
|
||||||
} else if hostIPs, err = net.LookupIP(host); err != nil {
|
|
||||||
return fmt.Errorf("failed to lookup %s address in host specification", host)
|
|
||||||
}
|
|
||||||
|
|
||||||
pa := portallocator.Get()
|
|
||||||
for _, hostIP := range hostIPs {
|
|
||||||
if _, err := pa.RequestPort(hostIP, "tcp", intPort); err != nil {
|
|
||||||
return fmt.Errorf("failed to allocate daemon listening port %d (err: %v)", intPort, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// listenFD returns the specified socket activated files as a slice of
|
|
||||||
// net.Listeners or all of the activated files if "*" is given.
|
|
||||||
func listenFD(addr string) ([]net.Listener, error) {
|
|
||||||
// socket activation
|
|
||||||
listeners, err := systemdActivation.Listeners(false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(listeners) == 0 {
|
|
||||||
return nil, fmt.Errorf("No sockets found")
|
|
||||||
}
|
|
||||||
|
|
||||||
// default to all fds just like unix:// and tcp://
|
|
||||||
if addr == "" || addr == "*" {
|
|
||||||
return listeners, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
fdNum, err := strconv.Atoi(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to parse systemd address, should be number: %v", err)
|
|
||||||
}
|
|
||||||
fdOffset := fdNum - 3
|
|
||||||
if len(listeners) < int(fdOffset)+1 {
|
|
||||||
return nil, fmt.Errorf("Too few socket activated files passed in")
|
|
||||||
}
|
|
||||||
if listeners[fdOffset] == nil {
|
|
||||||
return nil, fmt.Errorf("failed to listen on systemd activated file at fd %d", fdOffset+3)
|
|
||||||
}
|
|
||||||
for i, ls := range listeners {
|
|
||||||
if i == fdOffset || ls == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := ls.Close(); err != nil {
|
|
||||||
logrus.Errorf("Failed to close systemd activated file at fd %d: %v", fdOffset+3, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []net.Listener{listeners[fdOffset]}, nil
|
|
||||||
}
|
|
||||||
@@ -6,16 +6,20 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/docker/docker/daemon"
|
||||||
|
"github.com/docker/docker/pkg/version"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewServer sets up the required Server and does protocol specific checking.
|
// NewServer sets up the required Server and does protocol specific checking.
|
||||||
func (s *Server) newServer(proto, addr string) ([]*HTTPServer, error) {
|
func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
||||||
var (
|
var (
|
||||||
ls []net.Listener
|
ls []net.Listener
|
||||||
)
|
)
|
||||||
switch proto {
|
switch proto {
|
||||||
case "tcp":
|
case "tcp":
|
||||||
l, err := s.initTCPSocket(addr)
|
l, err := s.initTcpSocket(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -25,11 +29,12 @@ func (s *Server) newServer(proto, addr string) ([]*HTTPServer, error) {
|
|||||||
return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
|
return nil, errors.New("Invalid protocol format. Windows only supports tcp.")
|
||||||
}
|
}
|
||||||
|
|
||||||
var res []*HTTPServer
|
var res []serverCloser
|
||||||
for _, l := range ls {
|
for _, l := range ls {
|
||||||
res = append(res, &HTTPServer{
|
res = append(res, &HttpServer{
|
||||||
&http.Server{
|
&http.Server{
|
||||||
Addr: addr,
|
Addr: addr,
|
||||||
|
Handler: s.router,
|
||||||
},
|
},
|
||||||
l,
|
l,
|
||||||
})
|
})
|
||||||
@@ -38,6 +43,20 @@ func (s *Server) newServer(proto, addr string) ([]*HTTPServer, error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Server) AcceptConnections(d *daemon.Daemon) {
|
||||||
|
s.daemon = d
|
||||||
|
s.registerSubRouter()
|
||||||
|
// close the lock so the listeners start accepting connections
|
||||||
|
select {
|
||||||
|
case <-s.start:
|
||||||
|
default:
|
||||||
|
close(s.start)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func allocateDaemonPort(addr string) error {
|
func allocateDaemonPort(addr string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func adjustCpuShares(version version.Version, hostConfig *runconfig.HostConfig) {
|
||||||
|
}
|
||||||
|
|||||||
+9
-23
@@ -1,10 +1,9 @@
|
|||||||
// Package types is used for API stability in the types and response to the
|
// This package is used for API stability in the types and response to the
|
||||||
// consumers of the API stats endpoint.
|
// consumers of the API stats endpoint.
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// ThrottlingData stores CPU throttling stats of one running container
|
|
||||||
type ThrottlingData struct {
|
type ThrottlingData struct {
|
||||||
// Number of periods with throttling active
|
// Number of periods with throttling active
|
||||||
Periods uint64 `json:"periods"`
|
Periods uint64 `json:"periods"`
|
||||||
@@ -14,8 +13,8 @@ type ThrottlingData struct {
|
|||||||
ThrottledTime uint64 `json:"throttled_time"`
|
ThrottledTime uint64 `json:"throttled_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPUUsage stores All CPU stats aggregated since container inception.
|
// All CPU stats are aggregated since container inception.
|
||||||
type CPUUsage struct {
|
type CpuUsage struct {
|
||||||
// Total CPU time consumed.
|
// Total CPU time consumed.
|
||||||
// Units: nanoseconds.
|
// Units: nanoseconds.
|
||||||
TotalUsage uint64 `json:"total_usage"`
|
TotalUsage uint64 `json:"total_usage"`
|
||||||
@@ -30,14 +29,12 @@ type CPUUsage struct {
|
|||||||
UsageInUsermode uint64 `json:"usage_in_usermode"`
|
UsageInUsermode uint64 `json:"usage_in_usermode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CPUStats aggregates and wraps all CPU related info of container
|
type CpuStats struct {
|
||||||
type CPUStats struct {
|
CpuUsage CpuUsage `json:"cpu_usage"`
|
||||||
CPUUsage CPUUsage `json:"cpu_usage"`
|
|
||||||
SystemUsage uint64 `json:"system_cpu_usage"`
|
SystemUsage uint64 `json:"system_cpu_usage"`
|
||||||
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
|
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MemoryStats aggregates All memory stats since container inception
|
|
||||||
type MemoryStats struct {
|
type MemoryStats struct {
|
||||||
// current res_counter usage for memory
|
// current res_counter usage for memory
|
||||||
Usage uint64 `json:"usage"`
|
Usage uint64 `json:"usage"`
|
||||||
@@ -51,7 +48,6 @@ type MemoryStats struct {
|
|||||||
Limit uint64 `json:"limit"`
|
Limit uint64 `json:"limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlkioStatEntry is one small entity to store a piece of Blkio stats
|
|
||||||
// TODO Windows: This can be factored out
|
// TODO Windows: This can be factored out
|
||||||
type BlkioStatEntry struct {
|
type BlkioStatEntry struct {
|
||||||
Major uint64 `json:"major"`
|
Major uint64 `json:"major"`
|
||||||
@@ -60,7 +56,6 @@ type BlkioStatEntry struct {
|
|||||||
Value uint64 `json:"value"`
|
Value uint64 `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlkioStats stores All IO service stats for data read and write
|
|
||||||
// TODO Windows: This can be factored out
|
// TODO Windows: This can be factored out
|
||||||
type BlkioStats struct {
|
type BlkioStats struct {
|
||||||
// number of bytes tranferred to and from the block device
|
// number of bytes tranferred to and from the block device
|
||||||
@@ -74,9 +69,8 @@ type BlkioStats struct {
|
|||||||
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"`
|
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworkStats aggregates All network stats of one container
|
|
||||||
// TODO Windows: This will require refactoring
|
// TODO Windows: This will require refactoring
|
||||||
type NetworkStats struct {
|
type Network struct {
|
||||||
RxBytes uint64 `json:"rx_bytes"`
|
RxBytes uint64 `json:"rx_bytes"`
|
||||||
RxPackets uint64 `json:"rx_packets"`
|
RxPackets uint64 `json:"rx_packets"`
|
||||||
RxErrors uint64 `json:"rx_errors"`
|
RxErrors uint64 `json:"rx_errors"`
|
||||||
@@ -87,19 +81,11 @@ type NetworkStats struct {
|
|||||||
TxDropped uint64 `json:"tx_dropped"`
|
TxDropped uint64 `json:"tx_dropped"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stats is Ultimate struct aggregating all types of stats of one container
|
|
||||||
type Stats struct {
|
type Stats struct {
|
||||||
Read time.Time `json:"read"`
|
Read time.Time `json:"read"`
|
||||||
PreCPUStats CPUStats `json:"precpu_stats,omitempty"`
|
Network Network `json:"network,omitempty"`
|
||||||
CPUStats CPUStats `json:"cpu_stats,omitempty"`
|
PreCpuStats CpuStats `json:"precpu_stats,omitempty"`
|
||||||
|
CpuStats CpuStats `json:"cpu_stats,omitempty"`
|
||||||
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
MemoryStats MemoryStats `json:"memory_stats,omitempty"`
|
||||||
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
BlkioStats BlkioStats `json:"blkio_stats,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatsJSON is newly used Networks
|
|
||||||
type StatsJSON struct {
|
|
||||||
Stats
|
|
||||||
|
|
||||||
// Networks request version >=1.21
|
|
||||||
Networks map[string]NetworkStats `json:"networks,omitempty"`
|
|
||||||
}
|
|
||||||
|
|||||||
+41
-119
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
"github.com/docker/docker/daemon/network"
|
"github.com/docker/docker/daemon/network"
|
||||||
"github.com/docker/docker/pkg/version"
|
"github.com/docker/docker/pkg/version"
|
||||||
"github.com/docker/docker/registry"
|
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -20,41 +19,35 @@ type ContainerCreateResponse struct {
|
|||||||
Warnings []string `json:"Warnings"`
|
Warnings []string `json:"Warnings"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerExecCreateResponse contains response of Remote API:
|
// POST /containers/{name:.*}/exec
|
||||||
// POST "/containers/{name:.*}/exec"
|
|
||||||
type ContainerExecCreateResponse struct {
|
type ContainerExecCreateResponse struct {
|
||||||
// ID is the exec ID.
|
// ID is the exec ID.
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AuthResponse contains response of Remote API:
|
// POST /auth
|
||||||
// POST "/auth"
|
|
||||||
type AuthResponse struct {
|
type AuthResponse struct {
|
||||||
// Status is the authentication status
|
// Status is the authentication status
|
||||||
Status string `json:"Status"`
|
Status string `json:"Status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerWaitResponse contains response of Remote API:
|
|
||||||
// POST "/containers/"+containerID+"/wait"
|
// POST "/containers/"+containerID+"/wait"
|
||||||
type ContainerWaitResponse struct {
|
type ContainerWaitResponse struct {
|
||||||
// StatusCode is the status code of the wait job
|
// StatusCode is the status code of the wait job
|
||||||
StatusCode int `json:"StatusCode"`
|
StatusCode int `json:"StatusCode"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerCommitResponse contains response of Remote API:
|
|
||||||
// POST "/commit?container="+containerID
|
// POST "/commit?container="+containerID
|
||||||
type ContainerCommitResponse struct {
|
type ContainerCommitResponse struct {
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerChange contains response of Remote API:
|
|
||||||
// GET "/containers/{name:.*}/changes"
|
// GET "/containers/{name:.*}/changes"
|
||||||
type ContainerChange struct {
|
type ContainerChange struct {
|
||||||
Kind int
|
Kind int
|
||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageHistory contains response of Remote API:
|
|
||||||
// GET "/images/{name:.*}/history"
|
// GET "/images/{name:.*}/history"
|
||||||
type ImageHistory struct {
|
type ImageHistory struct {
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
@@ -65,38 +58,32 @@ type ImageHistory struct {
|
|||||||
Comment string
|
Comment string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageDelete contains response of Remote API:
|
|
||||||
// DELETE "/images/{name:.*}"
|
// DELETE "/images/{name:.*}"
|
||||||
type ImageDelete struct {
|
type ImageDelete struct {
|
||||||
Untagged string `json:",omitempty"`
|
Untagged string `json:",omitempty"`
|
||||||
Deleted string `json:",omitempty"`
|
Deleted string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Image contains response of Remote API:
|
|
||||||
// GET "/images/json"
|
// GET "/images/json"
|
||||||
type Image struct {
|
type Image struct {
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
ParentID string `json:"ParentId"`
|
ParentId string
|
||||||
RepoTags []string
|
RepoTags []string
|
||||||
RepoDigests []string
|
RepoDigests []string
|
||||||
Created int64
|
Created int
|
||||||
Size int64
|
Size int
|
||||||
VirtualSize int64
|
VirtualSize int
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GraphDriverData returns Image's graph driver config info
|
|
||||||
// when calling inspect command
|
|
||||||
type GraphDriverData struct {
|
type GraphDriverData struct {
|
||||||
Name string
|
Name string
|
||||||
Data map[string]string
|
Data map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImageInspect contains response of Remote API:
|
|
||||||
// GET "/images/{name:.*}/json"
|
// GET "/images/{name:.*}/json"
|
||||||
type ImageInspect struct {
|
type ImageInspect struct {
|
||||||
ID string `json:"Id"`
|
Id string
|
||||||
Tags []string
|
|
||||||
Parent string
|
Parent string
|
||||||
Comment string
|
Comment string
|
||||||
Created string
|
Created string
|
||||||
@@ -112,8 +99,7 @@ type ImageInspect struct {
|
|||||||
GraphDriver GraphDriverData
|
GraphDriver GraphDriverData
|
||||||
}
|
}
|
||||||
|
|
||||||
// Port stores open ports info of container
|
// GET "/containers/json"
|
||||||
// e.g. {"PrivatePort": 8080, "PublicPort": 80, "Type": "tcp"}
|
|
||||||
type Port struct {
|
type Port struct {
|
||||||
IP string `json:",omitempty"`
|
IP string `json:",omitempty"`
|
||||||
PrivatePort int
|
PrivatePort int
|
||||||
@@ -121,18 +107,15 @@ type Port struct {
|
|||||||
Type string
|
Type string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Container contains response of Remote API:
|
|
||||||
// GET "/containers/json"
|
|
||||||
type Container struct {
|
type Container struct {
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
Names []string
|
Names []string
|
||||||
Image string
|
Image string
|
||||||
ImageID string
|
|
||||||
Command string
|
Command string
|
||||||
Created int64
|
Created int
|
||||||
Ports []Port
|
Ports []Port
|
||||||
SizeRw int64 `json:",omitempty"`
|
SizeRw int `json:",omitempty"`
|
||||||
SizeRootFs int64 `json:",omitempty"`
|
SizeRootFs int `json:",omitempty"`
|
||||||
Labels map[string]string
|
Labels map[string]string
|
||||||
Status string
|
Status string
|
||||||
HostConfig struct {
|
HostConfig struct {
|
||||||
@@ -140,15 +123,14 @@ type Container struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CopyConfig contains request body of Remote API:
|
|
||||||
// POST "/containers/"+containerID+"/copy"
|
// POST "/containers/"+containerID+"/copy"
|
||||||
type CopyConfig struct {
|
type CopyConfig struct {
|
||||||
Resource string
|
Resource string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerPathStat is used to encode the header from
|
// ContainerPathStat is used to encode the header from
|
||||||
// GET "/containers/{name:.*}/archive"
|
// GET /containers/{name:.*}/archive
|
||||||
// "Name" is the file or directory name.
|
// "name" is basename of the resource.
|
||||||
type ContainerPathStat struct {
|
type ContainerPathStat struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"`
|
||||||
@@ -157,18 +139,15 @@ type ContainerPathStat struct {
|
|||||||
LinkTarget string `json:"linkTarget"`
|
LinkTarget string `json:"linkTarget"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerProcessList contains response of Remote API:
|
|
||||||
// GET "/containers/{name:.*}/top"
|
// GET "/containers/{name:.*}/top"
|
||||||
type ContainerProcessList struct {
|
type ContainerProcessList struct {
|
||||||
Processes [][]string
|
Processes [][]string
|
||||||
Titles []string
|
Titles []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Version contains response of Remote API:
|
|
||||||
// GET "/version"
|
|
||||||
type Version struct {
|
type Version struct {
|
||||||
Version string
|
Version string
|
||||||
APIVersion version.Version `json:"ApiVersion"`
|
ApiVersion version.Version
|
||||||
GitCommit string
|
GitCommit string
|
||||||
GoVersion string
|
GoVersion string
|
||||||
Os string
|
Os string
|
||||||
@@ -178,7 +157,6 @@ type Version struct {
|
|||||||
BuildTime string `json:",omitempty"`
|
BuildTime string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Info contains response of Remote API:
|
|
||||||
// GET "/info"
|
// GET "/info"
|
||||||
type Info struct {
|
type Info struct {
|
||||||
ID string
|
ID string
|
||||||
@@ -188,11 +166,11 @@ type Info struct {
|
|||||||
DriverStatus [][2]string
|
DriverStatus [][2]string
|
||||||
MemoryLimit bool
|
MemoryLimit bool
|
||||||
SwapLimit bool
|
SwapLimit bool
|
||||||
CPUCfsPeriod bool `json:"CpuCfsPeriod"`
|
CpuCfsPeriod bool
|
||||||
CPUCfsQuota bool `json:"CpuCfsQuota"`
|
CpuCfsQuota bool
|
||||||
IPv4Forwarding bool
|
IPv4Forwarding bool
|
||||||
BridgeNfIptables bool
|
BridgeNfIptables bool
|
||||||
BridgeNfIP6tables bool `json:"BridgeNfIp6tables"`
|
BridgeNfIp6tables bool
|
||||||
Debug bool
|
Debug bool
|
||||||
NFd int
|
NFd int
|
||||||
OomKillDisable bool
|
OomKillDisable bool
|
||||||
@@ -204,23 +182,21 @@ type Info struct {
|
|||||||
KernelVersion string
|
KernelVersion string
|
||||||
OperatingSystem string
|
OperatingSystem string
|
||||||
IndexServerAddress string
|
IndexServerAddress string
|
||||||
RegistryConfig *registry.ServiceConfig
|
RegistryConfig interface{}
|
||||||
InitSha1 string
|
InitSha1 string
|
||||||
InitPath string
|
InitPath string
|
||||||
NCPU int
|
NCPU int
|
||||||
MemTotal int64
|
MemTotal int64
|
||||||
DockerRootDir string
|
DockerRootDir string
|
||||||
HTTPProxy string `json:"HttpProxy"`
|
HttpProxy string
|
||||||
HTTPSProxy string `json:"HttpsProxy"`
|
HttpsProxy string
|
||||||
NoProxy string
|
NoProxy string
|
||||||
Name string
|
Name string
|
||||||
Labels []string
|
Labels []string
|
||||||
ExperimentalBuild bool
|
ExperimentalBuild bool
|
||||||
ServerVersion string
|
|
||||||
ClusterStore string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExecStartCheck is a temp struct used by execStart
|
// This struct is a temp struct used by execStart
|
||||||
// Config fields is part of ExecConfig in runconfig package
|
// Config fields is part of ExecConfig in runconfig package
|
||||||
type ExecStartCheck struct {
|
type ExecStartCheck struct {
|
||||||
// ExecStart will first check if it's detached
|
// ExecStart will first check if it's detached
|
||||||
@@ -229,10 +205,7 @@ type ExecStartCheck struct {
|
|||||||
Tty bool
|
Tty bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerState stores container's running state
|
|
||||||
// it's part of ContainerJSONBase and will return by "inspect" command
|
|
||||||
type ContainerState struct {
|
type ContainerState struct {
|
||||||
Status string
|
|
||||||
Running bool
|
Running bool
|
||||||
Paused bool
|
Paused bool
|
||||||
Restarting bool
|
Restarting bool
|
||||||
@@ -245,10 +218,9 @@ type ContainerState struct {
|
|||||||
FinishedAt string
|
FinishedAt string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerJSONBase contains response of Remote API:
|
|
||||||
// GET "/containers/{name:.*}/json"
|
// GET "/containers/{name:.*}/json"
|
||||||
type ContainerJSONBase struct {
|
type ContainerJSONBase struct {
|
||||||
ID string `json:"Id"`
|
Id string
|
||||||
Created string
|
Created string
|
||||||
Path string
|
Path string
|
||||||
Args []string
|
Args []string
|
||||||
@@ -269,88 +241,38 @@ type ContainerJSONBase struct {
|
|||||||
ExecIDs []string
|
ExecIDs []string
|
||||||
HostConfig *runconfig.HostConfig
|
HostConfig *runconfig.HostConfig
|
||||||
GraphDriver GraphDriverData
|
GraphDriver GraphDriverData
|
||||||
SizeRw *int64 `json:",omitempty"`
|
|
||||||
SizeRootFs *int64 `json:",omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerJSON is newly used struct along with MountPoint
|
|
||||||
type ContainerJSON struct {
|
type ContainerJSON struct {
|
||||||
*ContainerJSONBase
|
*ContainerJSONBase
|
||||||
Mounts []MountPoint
|
Mounts []MountPoint
|
||||||
Config *runconfig.Config
|
Config *runconfig.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// backcompatibility struct along with ContainerConfig
|
||||||
|
type ContainerJSONPre120 struct {
|
||||||
|
*ContainerJSONBase
|
||||||
|
Volumes map[string]string
|
||||||
|
VolumesRW map[string]bool
|
||||||
|
Config *ContainerConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type ContainerConfig struct {
|
||||||
|
*runconfig.Config
|
||||||
|
|
||||||
|
// backward compatibility, they now live in HostConfig
|
||||||
|
Memory int64
|
||||||
|
MemorySwap int64
|
||||||
|
CpuShares int64
|
||||||
|
Cpuset string
|
||||||
|
}
|
||||||
|
|
||||||
// MountPoint represents a mount point configuration inside the container.
|
// MountPoint represents a mount point configuration inside the container.
|
||||||
type MountPoint struct {
|
type MountPoint struct {
|
||||||
Name string `json:",omitempty"`
|
Name string `json:",omitempty"`
|
||||||
Source string
|
Source string
|
||||||
Destination string
|
Destination string
|
||||||
Driver string `json:",omitempty"`
|
Driver string `json:",omitempty"`
|
||||||
Mode string
|
Mode string // this is internally named `Relabel`
|
||||||
RW bool
|
RW bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Volume represents the configuration of a volume for the remote API
|
|
||||||
type Volume struct {
|
|
||||||
Name string // Name is the name of the volume
|
|
||||||
Driver string // Driver is the Driver name used to create the volume
|
|
||||||
Mountpoint string // Mountpoint is the location on disk of the volume
|
|
||||||
}
|
|
||||||
|
|
||||||
// VolumesListResponse contains the response for the remote API:
|
|
||||||
// GET "/volumes"
|
|
||||||
type VolumesListResponse struct {
|
|
||||||
Volumes []*Volume // Volumes is the list of volumes being returned
|
|
||||||
}
|
|
||||||
|
|
||||||
// VolumeCreateRequest contains the response for the remote API:
|
|
||||||
// POST "/volumes"
|
|
||||||
type VolumeCreateRequest struct {
|
|
||||||
Name string // Name is the requested name of the volume
|
|
||||||
Driver string // Driver is the name of the driver that should be used to create the volume
|
|
||||||
DriverOpts map[string]string // DriverOpts holds the driver specific options to use for when creating the volume.
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkResource is the body of the "get network" http response message
|
|
||||||
type NetworkResource struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
ID string `json:"id"`
|
|
||||||
Scope string `json:"scope"`
|
|
||||||
Driver string `json:"driver"`
|
|
||||||
IPAM network.IPAM `json:"ipam"`
|
|
||||||
Containers map[string]EndpointResource `json:"containers"`
|
|
||||||
Options map[string]string `json:"options"`
|
|
||||||
}
|
|
||||||
|
|
||||||
//EndpointResource contains network resources allocated and usd for a container in a network
|
|
||||||
type EndpointResource struct {
|
|
||||||
EndpointID string `json:"endpoint"`
|
|
||||||
MacAddress string `json:"mac_address"`
|
|
||||||
IPv4Address string `json:"ipv4_address"`
|
|
||||||
IPv6Address string `json:"ipv6_address"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkCreate is the expected body of the "create network" http request message
|
|
||||||
type NetworkCreate struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
CheckDuplicate bool `json:"check_duplicate"`
|
|
||||||
Driver string `json:"driver"`
|
|
||||||
IPAM network.IPAM `json:"ipam"`
|
|
||||||
Options map[string]string `json:"options"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkCreateResponse is the response message sent by the server for network create call
|
|
||||||
type NetworkCreateResponse struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Warning string `json:"warning"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkConnect represents the data to be used to connect a container to the network
|
|
||||||
type NetworkConnect struct {
|
|
||||||
Container string `json:"container"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// NetworkDisconnect represents the data to be used to disconnect a container from the network
|
|
||||||
type NetworkDisconnect struct {
|
|
||||||
Container string `json:"container"`
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
## Legacy API type versions
|
|
||||||
|
|
||||||
This package includes types for legacy API versions. The stable version of the API types live in `api/types/*.go`.
|
|
||||||
|
|
||||||
Consider moving a type here when you need to keep backwards compatibility in the API. This legacy types are organized by the latest API version they appear in. For instance, types in the `v1p19` package are valid for API versions below or equal `1.19`. Types in the `v1p20` package are valid for the API version `1.20`, since the versions below that will use the legacy types in `v1p19`.
|
|
||||||
|
|
||||||
### Package name conventions
|
|
||||||
|
|
||||||
The package name convention is to use `v` as a prefix for the version number and `p`(patch) as a separator. We use this nomenclature due to a few restrictions in the Go package name convention:
|
|
||||||
|
|
||||||
1. We cannot use `.` because it's interpreted by the language, think of `v1.20.CallFunction`.
|
|
||||||
2. We cannot use `_` because golint complains abount it. The code is actually valid, but it looks probably more weird: `v1_20.CallFunction`.
|
|
||||||
|
|
||||||
For instance, if you want to modify a type that was available in the version `1.21` of the API but it will have different fields in the version `1.22`, you want to create a new package under `api/types/versions/v1p21`.
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// Package v1p19 provides specific API types for the API version 1, patch 19.
|
|
||||||
package v1p19
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ContainerJSON is a backcompatibility struct for APIs prior to 1.20.
|
|
||||||
// Note this is not used by the Windows daemon.
|
|
||||||
type ContainerJSON struct {
|
|
||||||
*types.ContainerJSONBase
|
|
||||||
Volumes map[string]string
|
|
||||||
VolumesRW map[string]bool
|
|
||||||
Config *ContainerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerConfig is a backcompatibility struct for APIs prior to 1.20.
|
|
||||||
type ContainerConfig struct {
|
|
||||||
*runconfig.Config
|
|
||||||
|
|
||||||
// backward compatibility, they now live in HostConfig
|
|
||||||
VolumeDriver string
|
|
||||||
Memory int64
|
|
||||||
MemorySwap int64
|
|
||||||
CPUShares int64 `json:"CpuShares"`
|
|
||||||
CPUSet string `json:"Cpuset"`
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// Package v1p20 provides specific API types for the API version 1, patch 20.
|
|
||||||
package v1p20
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/runconfig"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ContainerJSON is a backcompatibility struct for the API 1.20
|
|
||||||
type ContainerJSON struct {
|
|
||||||
*types.ContainerJSONBase
|
|
||||||
Mounts []types.MountPoint
|
|
||||||
Config *ContainerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// ContainerConfig is a backcompatibility struct used in ContainerJSON for the API 1.20
|
|
||||||
type ContainerConfig struct {
|
|
||||||
*runconfig.Config
|
|
||||||
|
|
||||||
// backward compatibility, they now live in HostConfig
|
|
||||||
VolumeDriver string
|
|
||||||
}
|
|
||||||
|
|
||||||
// StatsJSON is a backcompatibility struct used in Stats for API prior to 1.21
|
|
||||||
type StatsJSON struct {
|
|
||||||
types.Stats
|
|
||||||
Network types.NetworkStats `json:"network,omitempty"`
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package dockerfile
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -38,7 +38,6 @@ func NewBFlags() *BFlags {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddBool adds a bool flag to BFlags
|
// AddBool adds a bool flag to BFlags
|
||||||
// Note, any error will be generated when Parse() is called (see Parse).
|
|
||||||
func (bf *BFlags) AddBool(name string, def bool) *Flag {
|
func (bf *BFlags) AddBool(name string, def bool) *Flag {
|
||||||
flag := bf.addFlag(name, boolType)
|
flag := bf.addFlag(name, boolType)
|
||||||
if flag == nil {
|
if flag == nil {
|
||||||
@@ -53,7 +52,6 @@ func (bf *BFlags) AddBool(name string, def bool) *Flag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddString adds a string flag to BFlags
|
// AddString adds a string flag to BFlags
|
||||||
// Note, any error will be generated when Parse() is called (see Parse).
|
|
||||||
func (bf *BFlags) AddString(name string, def string) *Flag {
|
func (bf *BFlags) AddString(name string, def string) *Flag {
|
||||||
flag := bf.addFlag(name, stringType)
|
flag := bf.addFlag(name, stringType)
|
||||||
if flag == nil {
|
if flag == nil {
|
||||||
@@ -63,9 +61,6 @@ func (bf *BFlags) AddString(name string, def string) *Flag {
|
|||||||
return flag
|
return flag
|
||||||
}
|
}
|
||||||
|
|
||||||
// addFlag is a generic func used by the other AddXXX() func
|
|
||||||
// to add a new flag to the BFlags struct.
|
|
||||||
// Note, any error will be generated when Parse() is called (see Parse).
|
|
||||||
func (bf *BFlags) addFlag(name string, flagType FlagType) *Flag {
|
func (bf *BFlags) addFlag(name string, flagType FlagType) *Flag {
|
||||||
if _, ok := bf.flags[name]; ok {
|
if _, ok := bf.flags[name]; ok {
|
||||||
bf.Err = fmt.Errorf("Duplicate flag defined: %s", name)
|
bf.Err = fmt.Errorf("Duplicate flag defined: %s", name)
|
||||||
@@ -99,14 +94,7 @@ func (fl *Flag) IsTrue() bool {
|
|||||||
return fl.Value == "true"
|
return fl.Value == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse parses and checks if the BFlags is valid.
|
// Parse parses and checks if the BFlags is valid
|
||||||
// Any error noticed during the AddXXX() funcs will be generated/returned
|
|
||||||
// here. We do this because an error during AddXXX() is more like a
|
|
||||||
// compile time error so it doesn't matter too much when we stop our
|
|
||||||
// processing as long as we do stop it, so this allows the code
|
|
||||||
// around AddXXX() to be just:
|
|
||||||
// defFlag := AddString("desription", "")
|
|
||||||
// w/o needing to add an if-statement around each one.
|
|
||||||
func (bf *BFlags) Parse() error {
|
func (bf *BFlags) Parse() error {
|
||||||
// If there was an error while defining the possible flags
|
// If there was an error while defining the possible flags
|
||||||
// go ahead and bubble it back up here since we didn't do it
|
// go ahead and bubble it back up here since we didn't do it
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package dockerfile
|
package builder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user