mirror of
https://github.com/clearlinux/docker.git
synced 2026-08-19 20:26:54 +00:00
Compare commits
111 Commits
| 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/_templates
|
||||
docs/changed-files
|
||||
# generated by man/md2man-all.sh
|
||||
# generated by man/man/md2man-all.sh
|
||||
man/man1
|
||||
man/man5
|
||||
man/man8
|
||||
pyenv
|
||||
vendor/pkg/
|
||||
|
||||
+6
-155
@@ -1,154 +1,5 @@
|
||||
# Changelog
|
||||
|
||||
Items starting with `DEPRECATE` are important deprecation notices. For more
|
||||
information on the list of deprecated flags and APIs please have a look at
|
||||
https://docs.docker.com/misc/deprecated/ where target removal dates can also
|
||||
be found.
|
||||
|
||||
## 1.9.0 (2015-11-03)
|
||||
|
||||
## Runtime
|
||||
|
||||
+ `docker stats` now returns block IO metrics (#15005)
|
||||
+ `docker stats` now details network stats per interface (#15786)
|
||||
+ Add `ancestor=<image>` filter to `docker ps --filter` flag to filter
|
||||
containers based on their ancestor images (#14570)
|
||||
+ Add `label=<somelabel>` filter to `docker ps --filter` to filter containers
|
||||
based on label (#16530)
|
||||
+ Add `--kernel-memory` flag to `docker run` (#14006)
|
||||
+ Add `--message` flag to `docker import` allowing to specify an optional
|
||||
message (#15711)
|
||||
+ Add `--privileged` flag to `docker exec` (#14113)
|
||||
+ Add `--stop-signal` flag to `docker run` allowing to replace the container
|
||||
process stopping signal (#15307)
|
||||
+ Add a new `unless-stopped` restart policy (#15348)
|
||||
+ Inspecting an image now returns tags (#13185)
|
||||
+ Add container size information to `docker inspect` (#15796)
|
||||
+ Add `RepoTags` and `RepoDigests` field to `/images/{name:.*}/json` (#17275)
|
||||
- Remove the deprecated `/container/ps` endpoint from the API (#15972)
|
||||
- Send and document correct HTTP codes for `/exec/<name>/start` (#16250)
|
||||
- Share shm and mqueue between containers sharing IPC namespace (#15862)
|
||||
- Event stream now shows OOM status when `--oom-kill-disable` is set (#16235)
|
||||
- Ensure special network files (/etc/hosts etc.) are read-only if bind-mounted
|
||||
with `ro` option (#14965)
|
||||
- Improve `rmi` performance (#16890)
|
||||
- Do not update /etc/hosts for the default bridge network, except for links (#17325)
|
||||
- Fix conflict with duplicate container names (#17389)
|
||||
- Fix an issue with incorrect template execution in `docker inspect` (#17284)
|
||||
- DEPRECATE `-c` short flag variant for `--cpu-shares` in docker run (#16271)
|
||||
|
||||
## Client
|
||||
|
||||
+ Allow `docker import` to import from local files (#11907)
|
||||
|
||||
## Builder
|
||||
|
||||
+ Add a `STOPSIGNAL` Dockerfile instruction allowing to set a different
|
||||
stop-signal for the container process (#15307)
|
||||
+ Add an `ARG` Dockerfile instruction and a `--build-arg` flag to `docker build`
|
||||
that allows to add build-time environment variables (#15182)
|
||||
- Improve cache miss performance (#16890)
|
||||
|
||||
## Storage
|
||||
|
||||
- devicemapper: Implement deferred deletion capability (#16381)
|
||||
|
||||
## Networking
|
||||
|
||||
+ `docker network` exits experimental and is part of standard release (#16645)
|
||||
+ New network top-level concept, with associated subcommands and API (#16645)
|
||||
WARNING: the API is different from the experimental API
|
||||
+ Support for multiple isolated/micro-segmented networks (#16645)
|
||||
+ Built-in multihost networking using VXLAN based overlay driver (#14071)
|
||||
+ Support for third-party network plugins (#13424)
|
||||
+ Ability to dynamically connect containers to multiple networks (#16645)
|
||||
+ Support for user-defined IP address management via pluggable IPAM drivers (#16910)
|
||||
+ Add daemon flags `--cluster-store` and `--cluster-advertise` for built-in nodes discovery (#16229)
|
||||
+ Add `--cluster-store-opt` for setting up TLS settings (#16644)
|
||||
+ Add `--dns-opt` to the daemon (#16031)
|
||||
- DEPRECATE following container `NetworkSettings` fields in API v1.21: `EndpointID`, `Gateway`,
|
||||
`GlobalIPv6Address`, `GlobalIPv6PrefixLen`, `IPAddress`, `IPPrefixLen`, `IPv6Gateway` and `MacAddress`.
|
||||
Those are now specific to the `bridge` network. Use `NetworkSettings.Networks` to inspect
|
||||
the networking settings of a container per network.
|
||||
|
||||
## Volumes
|
||||
|
||||
+ New top-level `volume` subcommand and API (#14242)
|
||||
- Move API volume driver settings to host-specific config (#15798)
|
||||
- Print an error message if volume name is not unique (#16009)
|
||||
- Ensure volumes created from Dockerfiles always use the local volume driver
|
||||
(#15507)
|
||||
- DEPRECATE auto-creating missing host paths for bind mounts (#16349)
|
||||
|
||||
## Logging
|
||||
|
||||
+ Add `awslogs` logging driver for Amazon CloudWatch (#15495)
|
||||
+ Add generic `tag` log option to allow customizing container/image
|
||||
information passed to driver (e.g. show container names) (#15384)
|
||||
- Implement the `docker logs` endpoint for the journald driver (#13707)
|
||||
- DEPRECATE driver-specific log tags (e.g. `syslog-tag`, etc.) (#15384)
|
||||
|
||||
## Distribution
|
||||
|
||||
+ `docker search` now works with partial names (#16509)
|
||||
- Push optimization: avoid buffering to file (#15493)
|
||||
- The daemon will display progress for images that were already being pulled
|
||||
by another client (#15489)
|
||||
- Only permissions required for the current action being performed are requested (#)
|
||||
+ Renaming trust keys (and respective environment variables) from `offline` to
|
||||
`root` and `tagging` to `repository` (#16894)
|
||||
- DEPRECATE trust key environment variables
|
||||
`DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE` and
|
||||
`DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE` (#16894)
|
||||
|
||||
## Security
|
||||
|
||||
+ Add SELinux profiles to the rpm package (#15832)
|
||||
- Fix various issues with AppArmor profiles provided in the deb package
|
||||
(#14609)
|
||||
- Add AppArmor policy that prevents writing to /proc (#15571)
|
||||
|
||||
## 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)
|
||||
|
||||
### Distribution
|
||||
@@ -185,7 +36,7 @@ by another client (#15489)
|
||||
+ Add logging driver for GELF via UDP
|
||||
+ Allow to copy files from host to containers with `docker cp`
|
||||
+ 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
|
||||
* Remove cgroup read-only flag when privileged
|
||||
* Make /proc, /sys, & /dev readonly for readonly containers
|
||||
@@ -206,7 +57,7 @@ by another client (#15489)
|
||||
### Bug fixes
|
||||
|
||||
- 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
|
||||
- Go Scheduler issue with sync.Mutex and gcc
|
||||
- Fix issue where Search API endpoint would panic due to empty AuthConfig
|
||||
@@ -384,7 +235,7 @@ by another client (#15489)
|
||||
#### Notable Features since 1.3.0
|
||||
+ Set key=value labels to the daemon (displayed in `docker info`), applied with
|
||||
new `-label` daemon flag
|
||||
+ Add support for `ENV` in Dockerfile of the form:
|
||||
+ Add support for `ENV` in Dockerfile of the form:
|
||||
`ENV name=value name2=value2...`
|
||||
+ New Overlayfs Storage Driver
|
||||
+ `docker info` now returns an `ID` and `Name` field
|
||||
@@ -846,7 +697,7 @@ by another client (#15489)
|
||||
- Fix broken images API for version less than 1.7
|
||||
- Use the right encoding for all API endpoints which return JSON
|
||||
- Move remote api client to api/
|
||||
- Queue calls to the API using generic socket wait
|
||||
- Queue calls to the API using generic socket wait
|
||||
|
||||
#### Runtime
|
||||
|
||||
@@ -926,7 +777,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
|
||||
- Do not add hostname when networking is disabled
|
||||
* Return most recent image from the cache by date
|
||||
- Return all errors from docker wait
|
||||
* Add Content-Type Header "application/json" to GET /version and /info responses
|
||||
* Add Content-Type Header "application/json" to GET /version and /info responses
|
||||
|
||||
#### Other
|
||||
|
||||
@@ -954,7 +805,7 @@ With the ongoing changes to the networking and execution subsystems of docker te
|
||||
#### Runtime
|
||||
|
||||
- Only get the image's rootfs when we need to calculate the image size
|
||||
- Correctly handle unmapping UDP ports
|
||||
- Correctly handle unmapping UDP ports
|
||||
* Make CopyFileWithTar use a pipe instead of a buffer to save memory on docker build
|
||||
- Fix login message to say pull instead of push
|
||||
- Fix "docker load" help by removing "SOURCE" prompt and mentioning STDIN
|
||||
|
||||
+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!
|
||||
|
||||
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.
|
||||
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)
|
||||
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
|
||||
helps prioritize the most common problems and requests. **DO NOT DO THAT** to
|
||||
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.
|
||||
helps prioritize the most common problems and requests.
|
||||
|
||||
When reporting issues, please include your host OS (Ubuntu 12.04, Fedora 19,
|
||||
etc). Please include:
|
||||
|
||||
* The output of `uname -a`.
|
||||
* 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
|
||||
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.
|
||||
|
||||
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
|
||||
the feature or fix.
|
||||
|
||||
|
||||
+4
-14
@@ -40,16 +40,13 @@ RUN apt-get update && apt-get install -y \
|
||||
createrepo \
|
||||
curl \
|
||||
dpkg-sig \
|
||||
gcc-mingw-w64 \
|
||||
git \
|
||||
iptables \
|
||||
libapparmor-dev \
|
||||
libcap-dev \
|
||||
libsqlite3-dev \
|
||||
libsystemd-journal-dev \
|
||||
mercurial \
|
||||
parallel \
|
||||
pkg-config \
|
||||
python-mock \
|
||||
python-pip \
|
||||
python-websocket \
|
||||
@@ -61,12 +58,6 @@ RUN apt-get update && apt-get install -y \
|
||||
libzfs-dev \
|
||||
--no-install-recommends
|
||||
|
||||
# clr
|
||||
RUN echo deb http://download.opensuse.org/repositories/home:/clearlinux:/preview/xUbuntu_15.04/ / > /etc/apt/sources.list.d/clear-containers-docker.list
|
||||
RUN apt-get --allow-unauthenticated update && apt-get install -y --force-yes \
|
||||
kvmtool\
|
||||
linux-container
|
||||
|
||||
# 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
|
||||
@@ -89,7 +80,7 @@ RUN cd /usr/src/lxc \
|
||||
&& ldconfig
|
||||
|
||||
# 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 \
|
||||
&& mkdir -p /go/bin
|
||||
ENV PATH /go/bin:/usr/local/go/bin:$PATH
|
||||
@@ -136,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
|
||||
|
||||
# Install registry
|
||||
ENV REGISTRY_COMMIT ec87e9b6971d831f0eff752ddb54fb64693e51cd
|
||||
ENV REGISTRY_COMMIT 2317f721a3d8428215a2b65da4ae85212ed473b4
|
||||
RUN set -x \
|
||||
&& export GOPATH="$(mktemp -d)" \
|
||||
&& git clone https://github.com/docker/distribution.git "$GOPATH/src/github.com/docker/distribution" \
|
||||
@@ -156,11 +147,10 @@ RUN set -x \
|
||||
&& rm -rf "$GOPATH"
|
||||
|
||||
# Get the "docker-py" source so we can run their integration tests
|
||||
ENV DOCKER_PY_COMMIT 47ab89ec2bd3bddf1221b856ffbaff333edeabb4
|
||||
ENV DOCKER_PY_COMMIT 8a87001d09852058f08a807ab6e8491d57ca1e88
|
||||
RUN git clone https://github.com/docker/docker-py.git /docker-py \
|
||||
&& cd /docker-py \
|
||||
&& git checkout -q $DOCKER_PY_COMMIT \
|
||||
&& pip install -r test-requirements.txt
|
||||
&& git checkout -q $DOCKER_PY_COMMIT
|
||||
|
||||
# Setup s3cmd config
|
||||
RUN { \
|
||||
|
||||
@@ -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
|
||||
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
|
||||
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.
|
||||
|
||||
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.
|
||||
"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]
|
||||
|
||||
# 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.
|
||||
|
||||
|
||||
|
||||
people = [
|
||||
"calavera",
|
||||
"crosbymichael",
|
||||
@@ -266,11 +278,11 @@ made through a pull request.
|
||||
"tibor",
|
||||
"unclejack",
|
||||
"vbatts",
|
||||
"vdemeester",
|
||||
"vieux",
|
||||
"vishh"
|
||||
]
|
||||
|
||||
|
||||
[Org.Subsystems]
|
||||
|
||||
# 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
|
||||
# a comment adressing the issues.
|
||||
# - 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.
|
||||
# - 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
|
||||
@@ -324,10 +336,12 @@ made through a pull request.
|
||||
[Org.Subsystems.Documentation]
|
||||
|
||||
people = [
|
||||
"fredlf",
|
||||
"james",
|
||||
"moxiegirl",
|
||||
"thaJeztah",
|
||||
"jamtur01",
|
||||
"spf13",
|
||||
"sven"
|
||||
]
|
||||
|
||||
@@ -488,6 +502,11 @@ made through a pull request.
|
||||
Email = "estesp@linux.vnet.ibm.com"
|
||||
GitHub = "estesp"
|
||||
|
||||
[people.fredlf]
|
||||
Name = "Fred Lifton"
|
||||
Email = "fred.lifton@docker.com"
|
||||
GitHub = "fredlf"
|
||||
|
||||
[people.icecrime]
|
||||
Name = "Arnaud Porterie"
|
||||
Email = "arnaud@docker.com"
|
||||
@@ -503,11 +522,6 @@ made through a pull request.
|
||||
Email = "josh.hawn@docker.com"
|
||||
Github = "jlhawn"
|
||||
|
||||
[people.jnagal]
|
||||
Name = "Rohit Jnagal"
|
||||
Email = "jnagal@google.com"
|
||||
GitHub = "rjnagal"
|
||||
|
||||
[people.lk4d4]
|
||||
Name = "Alexander Morozov"
|
||||
Email = "lk4d4@docker.com"
|
||||
@@ -518,11 +532,6 @@ made through a pull request.
|
||||
Email = "mary.anthony@docker.com"
|
||||
GitHub = "moxiegirl"
|
||||
|
||||
[people.mpatel]
|
||||
Name = "Mrunal Patel"
|
||||
Email = "mpatel@redhat.com"
|
||||
GitHub = "mrunalp"
|
||||
|
||||
[people.nathanmccauley]
|
||||
Name = "Nathan McCauley"
|
||||
Email = "nathan.mccauley@docker.com"
|
||||
@@ -543,6 +552,11 @@ made through a pull request.
|
||||
Email = "solomon@docker.com"
|
||||
GitHub = "shykes"
|
||||
|
||||
[people.spf13]
|
||||
Name = "Steve Francia"
|
||||
Email = "steve.francia@gmail.com"
|
||||
GitHub = "spf13"
|
||||
|
||||
[people.sven]
|
||||
Name = "Sven Dowideit"
|
||||
Email = "SvenDowideit@home.org.au"
|
||||
@@ -568,32 +582,37 @@ made through a pull request.
|
||||
Email = "tibor@docker.com"
|
||||
GitHub = "tiborvass"
|
||||
|
||||
[people.unclejack]
|
||||
Name = "Cristian Staretu"
|
||||
Email = "cristian.staretu@gmail.com"
|
||||
GitHub = "unclejack"
|
||||
|
||||
[people.vbatts]
|
||||
Name = "Vincent Batts"
|
||||
Email = "vbatts@redhat.com"
|
||||
GitHub = "vbatts"
|
||||
|
||||
[people.vdemeester]
|
||||
Name = "Vincent Demeester"
|
||||
Email = "vincent@sbr.pm"
|
||||
GitHub = "vdemeester"
|
||||
|
||||
[people.vieux]
|
||||
Name = "Victor Vieux"
|
||||
Email = "vieux@docker.com"
|
||||
GitHub = "vieux"
|
||||
|
||||
[people.vishh]
|
||||
Name = "Vishnu Kannan"
|
||||
Email = "vishnuk@google.com"
|
||||
GitHub = "vishh"
|
||||
|
||||
[people.vmarmol]
|
||||
Name = "Victor Marmol"
|
||||
Email = "vmarmol@google.com"
|
||||
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_EXECDRIVER \
|
||||
-e DOCKER_EXPERIMENTAL \
|
||||
-e DOCKER_REMAP_ROOT \
|
||||
-e DOCKER_GRAPHDRIVER \
|
||||
-e DOCKER_STORAGE_OPTS \
|
||||
-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
|
||||
as a lightweight container.
|
||||
|
||||
Docker containers are both *hardware-agnostic* and *platform-agnostic*.
|
||||
This means they can run anywhere, from your laptop to the largest
|
||||
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
|
||||
makes them great building blocks for deploying and scaling web apps,
|
||||
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
|
||||
* The [Go](https://golang.org) programming language
|
||||
* The [Docker Image Specification](https://github.com/docker/docker/blob/master/image/spec/v1.md)
|
||||
* The [Libcontainer Specification](https://github.com/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://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)/) |
|
||||
[](https://godoc.org/github.com/docker/docker)
|
||||
[](https://jenkins.dockerproject.org/job/Docker%20Master/)
|
||||
|
||||
Want to hack on Docker? Awesome! We have [instructions to help you get
|
||||
started contributing code or documentation](https://docs.docker.com/project/who-written-for/).
|
||||
@@ -248,7 +247,7 @@ We are always open to suggestions on process improvements, and are always lookin
|
||||
<tr>
|
||||
<td>Stack Overflow</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>
|
||||
and so do many other knowledgeable Docker users.
|
||||
</td>
|
||||
@@ -294,7 +293,3 @@ Docker on Mac and Windows
|
||||
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
definitive move, we temporarily won't accept more patches to the Dockerfile syntax for several
|
||||
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
|
||||
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")
|
||||
proxy := cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxy all received signals to the process")
|
||||
|
||||
|
||||
+25
-65
@@ -12,6 +12,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
@@ -30,11 +31,11 @@ import (
|
||||
"github.com/docker/docker/pkg/parsers"
|
||||
"github.com/docker/docker/pkg/progressreader"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
"github.com/docker/docker/pkg/symlink"
|
||||
"github.com/docker/docker/pkg/ulimit"
|
||||
"github.com/docker/docker/pkg/units"
|
||||
"github.com/docker/docker/pkg/urlutil"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/runconfig"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
@@ -48,7 +49,7 @@ const (
|
||||
//
|
||||
// Usage: docker build [OPTIONS] PATH | URL | -
|
||||
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")
|
||||
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")
|
||||
@@ -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')")
|
||||
flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
|
||||
flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
|
||||
flCPUShares := cmd.Int64([]string{"#c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
|
||||
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")
|
||||
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")
|
||||
flCpuQuota := cmd.Int64([]string{"-cpu-quota"}, 0, "Limit the CPU CFS (Completely Fair Scheduler) quota")
|
||||
flCPUSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
|
||||
flCPUSetMems := cmd.String([]string{"-cpuset-mems"}, "", "MEMs in which to allow execution (0-3, 0,1)")
|
||||
flCgroupParent := cmd.String([]string{"-cgroup-parent"}, "", "Optional parent cgroup for the container")
|
||||
flBuildArg := opts.NewListOpts(opts.ValidateEnv)
|
||||
cmd.Var(&flBuildArg, []string{"-build-arg"}, "Set build-time variables")
|
||||
|
||||
ulimits := make(map[string]*ulimit.Ulimit)
|
||||
flUlimits := opts.NewUlimitOpt(&ulimits)
|
||||
@@ -130,17 +129,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
return fmt.Errorf("cannot canonicalize dockerfile path %s: %v", relDockerfile, err)
|
||||
}
|
||||
|
||||
f, err := os.Open(filepath.Join(contextDir, ".dockerignore"))
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
var includes = []string{"."}
|
||||
|
||||
var excludes []string
|
||||
if err == nil {
|
||||
excludes, err = utils.ReadDockerIgnore(f)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
excludes, err := utils.ReadDockerIgnore(path.Join(contextDir, ".dockerignore"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := utils.ValidateContextDirectory(contextDir, excludes); err != nil {
|
||||
@@ -154,7 +147,6 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
// removed. The deamon will remove them for us, if needed, after it
|
||||
// parses the Dockerfile. Ignore errors here, as they will have been
|
||||
// caught by ValidateContextDirectory above.
|
||||
var includes = []string{"."}
|
||||
keepThem1, _ := fileutils.Matches(".dockerignore", excludes)
|
||||
keepThem2, _ := fileutils.Matches(relDockerfile, excludes)
|
||||
if keepThem1 || keepThem2 {
|
||||
@@ -251,8 +243,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
v.Set("cpusetcpus", *flCPUSetCpus)
|
||||
v.Set("cpusetmems", *flCPUSetMems)
|
||||
v.Set("cpushares", strconv.FormatInt(*flCPUShares, 10))
|
||||
v.Set("cpuquota", strconv.FormatInt(*flCPUQuota, 10))
|
||||
v.Set("cpuperiod", strconv.FormatInt(*flCPUPeriod, 10))
|
||||
v.Set("cpuquota", strconv.FormatInt(*flCpuQuota, 10))
|
||||
v.Set("cpuperiod", strconv.FormatInt(*flCpuPeriod, 10))
|
||||
v.Set("memory", strconv.FormatInt(memory, 10))
|
||||
v.Set("memswap", strconv.FormatInt(memorySwap, 10))
|
||||
v.Set("cgroupparent", *flCgroupParent)
|
||||
@@ -260,19 +252,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
v.Set("dockerfile", relDockerfile)
|
||||
|
||||
ulimitsVar := flUlimits.GetList()
|
||||
ulimitsJSON, err := json.Marshal(ulimitsVar)
|
||||
ulimitsJson, err := json.Marshal(ulimitsVar)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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))
|
||||
v.Set("ulimits", string(ulimitsJson))
|
||||
|
||||
headers := http.Header(make(map[string][]string))
|
||||
buf, err := json.Marshal(cli.configFile.AuthConfigs)
|
||||
@@ -324,12 +308,6 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
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`
|
||||
// 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.
|
||||
@@ -340,16 +318,9 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi
|
||||
|
||||
// The context dir might be a symbolic link, so follow it to the actual
|
||||
// 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)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
|
||||
}
|
||||
absContextDir, err = filepath.EvalSymlinks(absContextDir)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("unable to evaluate symlinks in context path: %v", err)
|
||||
}
|
||||
|
||||
stat, err := os.Lstat(absContextDir)
|
||||
@@ -383,22 +354,15 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi
|
||||
absDockerfile = filepath.Join(absContextDir, absDockerfile)
|
||||
}
|
||||
|
||||
// Evaluate symlinks in the path to the Dockerfile too.
|
||||
//
|
||||
// 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 {
|
||||
return "", "", fmt.Errorf("unable to evaluate symlinks in Dockerfile path: %v", err)
|
||||
}
|
||||
// Verify that 'filename' is within the build context
|
||||
absDockerfile, err = symlink.FollowSymlinkInScope(absDockerfile, absContextDir)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("The Dockerfile (%s) must be within the build context (%s)", givenDockerfile, givenContextDir)
|
||||
}
|
||||
|
||||
if _, err := os.Lstat(absDockerfile); err != nil {
|
||||
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)
|
||||
}
|
||||
@@ -407,10 +371,6 @@ func getDockerfileRelPath(givenContextDir, givenDockerfile string) (absContextDi
|
||||
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
|
||||
}
|
||||
|
||||
@@ -462,7 +422,7 @@ func getContextFromReader(r io.Reader, dockerfileName string) (absContextDir, re
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -499,7 +459,7 @@ func getContextFromURL(out io.Writer, remoteURL, dockerfileName string) (absCont
|
||||
In: response.Body,
|
||||
Out: out,
|
||||
Formatter: streamformatter.NewStreamFormatter(),
|
||||
Size: response.ContentLength,
|
||||
Size: int(response.ContentLength),
|
||||
NewLines: true,
|
||||
ID: "",
|
||||
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"
|
||||
repo, tag := parsers.ParseRepositoryTag(matches[1])
|
||||
if tag == "" {
|
||||
tag = tags.DefaultTag
|
||||
tag = tags.DEFAULTTAG
|
||||
}
|
||||
|
||||
repoInfo, err := registry.ParseRepositoryInfo(repo)
|
||||
|
||||
+10
-17
@@ -52,14 +52,12 @@ type DockerCli struct {
|
||||
outFd uintptr
|
||||
// isTerminalIn indicates whether the client's STDIN is a TTY
|
||||
isTerminalIn bool
|
||||
// isTerminalOut indicates whether the client's STDOUT is a TTY
|
||||
// isTerminalOut dindicates whether the client's STDOUT is a TTY
|
||||
isTerminalOut bool
|
||||
// transport holds the client transport instance.
|
||||
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 {
|
||||
if cli.init == nil {
|
||||
return nil
|
||||
@@ -79,8 +77,6 @@ func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error {
|
||||
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 {
|
||||
return cli.configFile.PsFormat
|
||||
}
|
||||
@@ -98,30 +94,27 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, clientFlags *cli.ClientF
|
||||
}
|
||||
|
||||
cli.init = func() error {
|
||||
|
||||
clientFlags.PostParse()
|
||||
|
||||
hosts := clientFlags.Common.Hosts
|
||||
|
||||
switch len(hosts) {
|
||||
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:
|
||||
// only accept one host to talk to
|
||||
default:
|
||||
return errors.New("Please specify only one -H")
|
||||
}
|
||||
|
||||
defaultHost := opts.DefaultTCPHost
|
||||
if clientFlags.Common.TLSOptions != nil {
|
||||
defaultHost = opts.DefaultTLSHost
|
||||
}
|
||||
|
||||
var e error
|
||||
if hosts[0], e = opts.ParseHost(defaultHost, hosts[0]); e != nil {
|
||||
return e
|
||||
}
|
||||
|
||||
protoAddrParts := strings.SplitN(hosts[0], "://", 2)
|
||||
cli.proto, cli.addr = protoAddrParts[0], protoAddrParts[1]
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
//
|
||||
// Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
|
||||
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")
|
||||
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>\")")
|
||||
|
||||
+4
-5
@@ -15,7 +15,6 @@ import (
|
||||
Cli "github.com/docker/docker/cli"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
)
|
||||
|
||||
type copyDirection int
|
||||
@@ -43,8 +42,8 @@ func (cli *DockerCli) CmdCp(args ...string) error {
|
||||
"cp",
|
||||
[]string{"CONTAINER:PATH LOCALPATH|-", "LOCALPATH|- CONTAINER:PATH"},
|
||||
strings.Join([]string{
|
||||
Cli.DockerCommands["cp"].Description,
|
||||
"\nUse '-' as the source to read a tar archive from stdin\n",
|
||||
"Copy files/folders between a container and your host.\n",
|
||||
"Use '-' as the source to read a tar archive from stdin\n",
|
||||
"and extract it to a directory destination in a container.\n",
|
||||
"Use '-' as the destination to stream a tar archive of a\n",
|
||||
"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
|
||||
// is immediately proceeded by a backslash.
|
||||
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`.
|
||||
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 err == nil && dstStat.Mode&os.ModeSymlink != 0 {
|
||||
linkTarget := dstStat.LinkTarget
|
||||
if !system.IsAbs(linkTarget) {
|
||||
if !filepath.IsAbs(linkTarget) {
|
||||
// Join with the parent directory.
|
||||
dstParent, _ := archive.SplitPathDirEntry(dstPath)
|
||||
linkTarget = filepath.Join(dstParent, linkTarget)
|
||||
|
||||
@@ -26,7 +26,7 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error {
|
||||
repos, tag := parsers.ParseRepositoryTag(image)
|
||||
// pull only the image tagged 'latest' if no tag was specified
|
||||
if tag == "" {
|
||||
tag = tags.DefaultTag
|
||||
tag = tags.DEFAULTTAG
|
||||
}
|
||||
v.Set("fromImage", repos)
|
||||
v.Set("tag", tag)
|
||||
@@ -96,7 +96,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
||||
|
||||
repo, tag := parsers.ParseRepositoryTag(config.Image)
|
||||
if tag == "" {
|
||||
tag = tags.DefaultTag
|
||||
tag = tags.DEFAULTTAG
|
||||
}
|
||||
|
||||
ref := registry.ParseReference(tag)
|
||||
@@ -159,7 +159,7 @@ func (cli *DockerCli) createContainer(config *runconfig.Config, hostConfig *runc
|
||||
//
|
||||
// Usage: docker create [OPTIONS] IMAGE [COMMAND] [ARG...]
|
||||
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)
|
||||
|
||||
// These are flags not stored in Config/HostConfig
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ import (
|
||||
//
|
||||
// Usage: docker diff CONTAINER
|
||||
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.ParseFlags(args, true)
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
//
|
||||
// Usage: docker events [OPTIONS]
|
||||
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")
|
||||
until := cmd.String([]string{"-until"}, "", "Stream events until this timestamp")
|
||||
flFilter := opts.NewListOpts(nil)
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ import (
|
||||
//
|
||||
// Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
|
||||
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)
|
||||
// just in case the ParseExec does not exit
|
||||
|
||||
@@ -2,6 +2,7 @@ package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
@@ -14,14 +15,14 @@ import (
|
||||
//
|
||||
// Usage: docker export [OPTIONS] CONTAINER
|
||||
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")
|
||||
cmd.Require(flag.Exact, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var (
|
||||
output = cli.out
|
||||
output io.Writer = cli.out
|
||||
err error
|
||||
)
|
||||
if *outfile != "" {
|
||||
|
||||
+30
-27
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/autogen/dockerversion"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"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
|
||||
|
||||
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
|
||||
// of inactivity (a long running command with no output) that in certain
|
||||
// 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.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)
|
||||
defer clientconn.Close()
|
||||
|
||||
@@ -185,6 +184,8 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||
started <- rwc
|
||||
}
|
||||
|
||||
var receiveStdout chan error
|
||||
|
||||
var oldState *term.State
|
||||
|
||||
if in != nil && setRawTerminal && cli.isTerminalIn && os.Getenv("NORAW") == "" {
|
||||
@@ -195,15 +196,19 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||
defer term.RestoreTerminal(cli.inFd, oldState)
|
||||
}
|
||||
|
||||
receiveStdout := make(chan error, 1)
|
||||
if stdout != nil || stderr != nil {
|
||||
go func() {
|
||||
receiveStdout = promise.Go(func() (err error) {
|
||||
defer func() {
|
||||
if in != nil {
|
||||
if setRawTerminal && cli.isTerminalIn {
|
||||
term.RestoreTerminal(cli.inFd, oldState)
|
||||
}
|
||||
in.Close()
|
||||
// For some reason this Close call blocks on darwin..
|
||||
// As the client exists right after, simply discard the close
|
||||
// until we find a better solution.
|
||||
if runtime.GOOS != "darwin" {
|
||||
in.Close()
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -214,12 +219,11 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea
|
||||
_, err = stdcopy.StdCopy(stdout, stderr, br)
|
||||
}
|
||||
logrus.Debugf("[hijack] End of stdout")
|
||||
receiveStdout <- err
|
||||
}()
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
stdinDone := make(chan struct{})
|
||||
go func() {
|
||||
sendStdin := promise.Go(func() error {
|
||||
if in != nil {
|
||||
io.Copy(rwc, in)
|
||||
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)
|
||||
}
|
||||
}
|
||||
close(stdinDone)
|
||||
}()
|
||||
// Discard errors due to pipe interruption
|
||||
return nil
|
||||
})
|
||||
|
||||
select {
|
||||
case err := <-receiveStdout:
|
||||
if err != nil {
|
||||
if stdout != nil || stderr != nil {
|
||||
if err := <-receiveStdout; err != nil {
|
||||
logrus.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
}
|
||||
case <-stdinDone:
|
||||
if stdout != nil || stderr != nil {
|
||||
if err := <-receiveStdout; err != nil {
|
||||
logrus.Debugf("Error receiveStdout: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !cli.isTerminalIn {
|
||||
if err := <-sendStdin; err != nil {
|
||||
logrus.Debugf("Error sendStdin: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package client
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"time"
|
||||
|
||||
@@ -19,7 +18,7 @@ import (
|
||||
//
|
||||
// Usage: docker history [OPTIONS] IMAGE
|
||||
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")
|
||||
quiet := cmd.Bool([]string{"q", "-quiet"}, false, "Only show numeric IDs")
|
||||
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 {
|
||||
fmt.Fprintf(w, "%s\t", strings.Replace(entry.CreatedBy, "\t", " ", -1))
|
||||
fmt.Fprintf(w, "%s\t", entry.CreatedBy)
|
||||
} 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 {
|
||||
|
||||
@@ -22,7 +22,7 @@ import (
|
||||
//
|
||||
// Usage: docker images [OPTIONS] [REPOSITORY]
|
||||
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")
|
||||
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")
|
||||
|
||||
@@ -20,10 +20,9 @@ import (
|
||||
//
|
||||
// Usage: docker import [OPTIONS] file|URL|- [REPOSITORY[:TAG]]
|
||||
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)
|
||||
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.ParseFlags(args, true)
|
||||
@@ -36,7 +35,6 @@ func (cli *DockerCli) CmdImport(args ...string) error {
|
||||
|
||||
v.Set("fromSrc", src)
|
||||
v.Set("repo", repository)
|
||||
v.Set("message", *message)
|
||||
for _, change := range flChanges.GetAll() {
|
||||
v.Add("changes", change)
|
||||
}
|
||||
|
||||
+15
-21
@@ -16,7 +16,7 @@ import (
|
||||
//
|
||||
// Usage: docker info
|
||||
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.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, "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)
|
||||
if info.DriverStatus != nil {
|
||||
for _, pair := range info.DriverStatus {
|
||||
@@ -53,17 +52,17 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
|
||||
if info.Debug {
|
||||
fmt.Fprintf(cli.out, "Debug mode (server): %v\n", info.Debug)
|
||||
fmt.Fprintf(cli.out, " File Descriptors: %d\n", info.NFd)
|
||||
fmt.Fprintf(cli.out, " Goroutines: %d\n", info.NGoroutines)
|
||||
fmt.Fprintf(cli.out, " System Time: %s\n", info.SystemTime)
|
||||
fmt.Fprintf(cli.out, " EventsListeners: %d\n", info.NEventsListener)
|
||||
fmt.Fprintf(cli.out, " Init SHA1: %s\n", info.InitSha1)
|
||||
fmt.Fprintf(cli.out, " Init Path: %s\n", info.InitPath)
|
||||
fmt.Fprintf(cli.out, " Docker Root Dir: %s\n", info.DockerRootDir)
|
||||
fmt.Fprintf(cli.out, "File Descriptors: %d\n", info.NFd)
|
||||
fmt.Fprintf(cli.out, "Goroutines: %d\n", info.NGoroutines)
|
||||
fmt.Fprintf(cli.out, "System Time: %s\n", info.SystemTime)
|
||||
fmt.Fprintf(cli.out, "EventsListeners: %d\n", info.NEventsListener)
|
||||
fmt.Fprintf(cli.out, "Init SHA1: %s\n", info.InitSha1)
|
||||
fmt.Fprintf(cli.out, "Init Path: %s\n", info.InitPath)
|
||||
fmt.Fprintf(cli.out, "Docker Root Dir: %s\n", info.DockerRootDir)
|
||||
}
|
||||
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HTTPProxy)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Https Proxy: %s\n", info.HTTPSProxy)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Http Proxy: %s\n", info.HttpProxy)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "Https Proxy: %s\n", info.HttpsProxy)
|
||||
ioutils.FprintfIfNotEmpty(cli.out, "No Proxy: %s\n", info.NoProxy)
|
||||
|
||||
if info.IndexServerAddress != "" {
|
||||
@@ -73,8 +72,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress)
|
||||
}
|
||||
}
|
||||
|
||||
// Only output these warnings if the server does not support these features
|
||||
// Only output these warnings if the server supports these features
|
||||
if h, err := httputils.ParseServerHeader(serverResp.header.Get("Server")); err == nil {
|
||||
if h.OS != "windows" {
|
||||
if !info.MemoryLimit {
|
||||
@@ -84,12 +82,12 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
fmt.Fprintf(cli.err, "WARNING: No swap limit support\n")
|
||||
}
|
||||
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 {
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -102,13 +100,9 @@ func (cli *DockerCli) CmdInfo(args ...string) error {
|
||||
}
|
||||
}
|
||||
|
||||
ioutils.FprintfIfTrue(cli.out, "Experimental: %v\n", info.ExperimentalBuild)
|
||||
if info.ClusterStore != "" {
|
||||
fmt.Fprintf(cli.out, "Cluster store: %s\n", info.ClusterStore)
|
||||
if info.ExperimentalBuild {
|
||||
fmt.Fprintf(cli.out, "Experimental: true\n")
|
||||
}
|
||||
|
||||
if info.ClusterAdvertise != "" {
|
||||
fmt.Fprintf(cli.out, "Cluster advertise: %s\n", info.ClusterAdvertise)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+21
-60
@@ -5,7 +5,6 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
@@ -25,10 +24,9 @@ var funcMap = template.FuncMap{
|
||||
//
|
||||
// Usage: docker inspect [OPTIONS] CONTAINER|IMAGE [CONTAINER|IMAGE...]
|
||||
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")
|
||||
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.ParseFlags(args, true)
|
||||
@@ -53,14 +51,10 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
status := 0
|
||||
isImage := false
|
||||
|
||||
v := url.Values{}
|
||||
if *size {
|
||||
v.Set("size", "1")
|
||||
}
|
||||
|
||||
for _, name := range cmd.Args() {
|
||||
|
||||
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 strings.Contains(err.Error(), "No such") {
|
||||
fmt.Fprintf(cli.err, "Error: No such container: %s\n", name)
|
||||
@@ -100,45 +94,42 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
} else {
|
||||
rdr := bytes.NewReader(obj)
|
||||
dec := json.NewDecoder(rdr)
|
||||
buf := bytes.NewBufferString("")
|
||||
|
||||
if isImage {
|
||||
inspPtr := types.ImageInspect{}
|
||||
if err := dec.Decode(&inspPtr); err != nil {
|
||||
fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
status = 1
|
||||
break
|
||||
continue
|
||||
}
|
||||
if err := tmpl.Execute(buf, inspPtr); err != nil {
|
||||
if err := tmpl.Execute(cli.out, inspPtr); err != nil {
|
||||
rdr.Seek(0, 0)
|
||||
var ok bool
|
||||
|
||||
if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok {
|
||||
fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
|
||||
status = 1
|
||||
break
|
||||
var raw interface{}
|
||||
if err := dec.Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = tmpl.Execute(cli.out, raw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
inspPtr := types.ContainerJSON{}
|
||||
if err := dec.Decode(&inspPtr); err != nil {
|
||||
fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", err)
|
||||
fmt.Fprintf(cli.err, "%s\n", err)
|
||||
status = 1
|
||||
break
|
||||
continue
|
||||
}
|
||||
if err := tmpl.Execute(buf, inspPtr); err != nil {
|
||||
if err := tmpl.Execute(cli.out, inspPtr); err != nil {
|
||||
rdr.Seek(0, 0)
|
||||
var ok bool
|
||||
|
||||
if buf, ok = cli.decodeRawInspect(tmpl, dec); !ok {
|
||||
fmt.Fprintf(cli.err, "Template parsing error: %v\n", err)
|
||||
status = 1
|
||||
break
|
||||
var raw interface{}
|
||||
if err := dec.Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = tmpl.Execute(cli.out, raw); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cli.out.Write(buf.Bytes())
|
||||
cli.out.Write([]byte{'\n'})
|
||||
}
|
||||
indented.WriteString(",")
|
||||
@@ -164,33 +155,3 @@ func (cli *DockerCli) CmdInspect(args ...string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeRawInspect executes the inspect template with a raw interface.
|
||||
// This allows docker cli to parse inspect structs injected with Swarm fields.
|
||||
// Unfortunately, go 1.4 doesn't fail executing invalid templates when the input is an interface.
|
||||
// It doesn't allow to modify this behavior either, sending <no value> messages to the output.
|
||||
// We assume that the template is invalid when there is a <no value>, if the template was valid
|
||||
// we'd get <nil> or "" values. In that case we fail with the original error raised executing the
|
||||
// template with the typed input.
|
||||
//
|
||||
// TODO: Go 1.5 allows to customize the error behavior, we can probably get rid of this as soon as
|
||||
// we build Docker with that version:
|
||||
// https://golang.org/pkg/text/template/#Template.Option
|
||||
func (cli *DockerCli) decodeRawInspect(tmpl *template.Template, dec *json.Decoder) (*bytes.Buffer, bool) {
|
||||
var raw interface{}
|
||||
buf := bytes.NewBufferString("")
|
||||
|
||||
if rawErr := dec.Decode(&raw); rawErr != nil {
|
||||
fmt.Fprintf(cli.err, "Unable to read inspect data: %v\n", rawErr)
|
||||
return buf, false
|
||||
}
|
||||
|
||||
if rawErr := tmpl.Execute(buf, raw); rawErr != nil {
|
||||
return buf, false
|
||||
}
|
||||
|
||||
if strings.Contains(buf.String(), "<no value>") {
|
||||
return buf, false
|
||||
}
|
||||
return buf, true
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
||||
//
|
||||
// Usage: docker kill [OPTIONS] CONTAINER [CONTAINER...]
|
||||
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")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
||||
//
|
||||
// Usage: docker load [OPTIONS]
|
||||
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")
|
||||
cmd.Require(flag.Exact, 0)
|
||||
|
||||
|
||||
+2
-2
@@ -22,7 +22,7 @@ import (
|
||||
//
|
||||
// Usage: docker login SERVER
|
||||
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)
|
||||
|
||||
var username, password, email string
|
||||
@@ -64,7 +64,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error {
|
||||
if username == "" {
|
||||
promptDefault("Username", authconfig.Username)
|
||||
username = readInput(cli.in, cli.out)
|
||||
username = strings.TrimSpace(username)
|
||||
username = strings.Trim(username, " ")
|
||||
if username == "" {
|
||||
username = authconfig.Username
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
//
|
||||
// Usage: docker logout [SERVER]
|
||||
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.ParseFlags(args, true)
|
||||
@@ -26,14 +26,13 @@ func (cli *DockerCli) CmdLogout(args ...string) error {
|
||||
|
||||
if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok {
|
||||
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)
|
||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||
|
||||
fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress)
|
||||
delete(cli.configFile.AuthConfigs, serverAddress)
|
||||
if err := cli.configFile.Save(); err != nil {
|
||||
return fmt.Errorf("Failed to save docker config: %v", err)
|
||||
if err := cli.configFile.Save(); err != nil {
|
||||
return fmt.Errorf("Failed to save docker config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
+6
-1
@@ -2,6 +2,7 @@ package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
@@ -15,7 +16,7 @@ import (
|
||||
//
|
||||
// docker logs [OPTIONS] CONTAINER
|
||||
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")
|
||||
since := cmd.String([]string{"-since"}, "", "Show logs since timestamp")
|
||||
times := cmd.Bool([]string{"t", "-timestamps"}, false, "Show timestamps")
|
||||
@@ -36,6 +37,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
||||
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.Set("stdout", "1")
|
||||
v.Set("stderr", "1")
|
||||
|
||||
+7
-373
@@ -1,381 +1,15 @@
|
||||
// +build experimental
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"os"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
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"
|
||||
nwclient "github.com/docker/libnetwork/client"
|
||||
)
|
||||
|
||||
// CmdNetwork is the parent subcommand for all network commands
|
||||
//
|
||||
// Usage: docker network <COMMAND> [OPTIONS]
|
||||
func (cli *DockerCli) CmdNetwork(args ...string) error {
|
||||
cmd := Cli.Subcmd("network", []string{"COMMAND [OPTIONS]"}, networkUsage(), false)
|
||||
cmd.Require(flag.Min, 1)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
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
|
||||
}
|
||||
|
||||
// Set the default driver to "" if the user didn't set the value.
|
||||
// That way we can know whether it was user input or not.
|
||||
driver := *flDriver
|
||||
if !cmd.IsSet("-driver") && !cmd.IsSet("d") {
|
||||
driver = ""
|
||||
}
|
||||
|
||||
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: driver,
|
||||
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 [OPTIONS] <NETWORK> [NETWORK...]
|
||||
func (cli *DockerCli) CmdNetworkInspect(args ...string) error {
|
||||
cmd := Cli.Subcmd("network inspect", []string{"NETWORK [NETWORK...]"}, "Displays detailed information on a network", false)
|
||||
cmd.Require(flag.Min, 1)
|
||||
err := cmd.ParseFlags(args, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
status := 0
|
||||
var networks []*types.NetworkResource
|
||||
for _, name := range cmd.Args() {
|
||||
obj, _, err := readBody(cli.call("GET", "/networks/"+name, nil, nil))
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
fmt.Fprintf(cli.err, "Error: No such network: %s\n", name)
|
||||
} else {
|
||||
fmt.Fprintf(cli.err, "%s", err)
|
||||
}
|
||||
status = 1
|
||||
continue
|
||||
}
|
||||
networkResource := types.NetworkResource{}
|
||||
if err := json.NewDecoder(bytes.NewReader(obj)).Decode(&networkResource); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
networks = append(networks, &networkResource)
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(networks, "", " ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := io.Copy(cli.out, bytes.NewReader(b)); err != nil {
|
||||
return err
|
||||
}
|
||||
io.WriteString(cli.out, "\n")
|
||||
|
||||
if status != 0 {
|
||||
return Cli.StatusError{StatusCode: status}
|
||||
}
|
||||
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
|
||||
nCli := nwclient.NewNetworkCli(cli.out, cli.err, nwclient.CallFunc(cli.callWrapper))
|
||||
args = append([]string{"network"}, args...)
|
||||
return nCli.Cmd(os.Args[0], args...)
|
||||
}
|
||||
|
||||
+1
-1
@@ -11,7 +11,7 @@ import (
|
||||
//
|
||||
// Usage: docker pause CONTAINER [CONTAINER...]
|
||||
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.ParseFlags(args, true)
|
||||
|
||||
+1
-1
@@ -15,7 +15,7 @@ import (
|
||||
//
|
||||
// Usage: docker port CONTAINER [PRIVATE_PORT[/PROTO]]
|
||||
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.ParseFlags(args, true)
|
||||
|
||||
+2
-2
@@ -23,7 +23,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
psFilterArgs = filters.Args{}
|
||||
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")
|
||||
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)")
|
||||
@@ -95,7 +95,7 @@ func (cli *DockerCli) CmdPs(args ...string) error {
|
||||
|
||||
f := *format
|
||||
if len(f) == 0 {
|
||||
if len(cli.PsFormat()) > 0 && !*quiet {
|
||||
if len(cli.PsFormat()) > 0 {
|
||||
f = cli.PsFormat()
|
||||
} else {
|
||||
f = "table"
|
||||
|
||||
+65
-5
@@ -1,9 +1,12 @@
|
||||
package ps
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api"
|
||||
@@ -61,11 +64,6 @@ func (c *containerContext) Image() string {
|
||||
if c.c.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
|
||||
}
|
||||
|
||||
@@ -151,6 +149,68 @@ func (c *containerContext) addHeader(header string) {
|
||||
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 {
|
||||
for i, s := range ss {
|
||||
ss[i] = s[1:]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ps
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func TestContainerPsContext(t *testing.T) {
|
||||
containerID := stringid.GenerateRandomID()
|
||||
containerId := stringid.GenerateRandomID()
|
||||
unix := time.Now().Unix()
|
||||
|
||||
var ctx containerContext
|
||||
@@ -22,40 +23,17 @@ func TestContainerPsContext(t *testing.T) {
|
||||
expHeader string
|
||||
call func() string
|
||||
}{
|
||||
{types.Container{ID: containerID}, true, stringid.TruncateID(containerID), idHeader, ctx.ID},
|
||||
{types.Container{ID: containerID}, false, containerID, idHeader, ctx.ID},
|
||||
{types.Container{ID: containerId}, true, stringid.TruncateID(containerId), idHeader, ctx.ID},
|
||||
{types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names},
|
||||
{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{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{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status},
|
||||
{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{}, true, "", 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 {
|
||||
@@ -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"}}
|
||||
ctx = containerContext{c: c1, trunc: true}
|
||||
c := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}}
|
||||
ctx = containerContext{c: c, trunc: true}
|
||||
|
||||
sid := ctx.Label("com.docker.swarm.swarm-id")
|
||||
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)
|
||||
|
||||
}
|
||||
|
||||
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}
|
||||
fullHeader := ctx.fullHeader()
|
||||
if fullHeader != "" {
|
||||
t.Fatalf("Expected fullHeader to be empty, was %s", fullHeader)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestContainerPsFormatError(t *testing.T) {
|
||||
out := bytes.NewBufferString("")
|
||||
ctx := Context{
|
||||
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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
"text/template"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
@@ -19,22 +14,14 @@ const (
|
||||
defaultQuietFormat = "{{.ID}}"
|
||||
)
|
||||
|
||||
// Context contains information required by the formatter to print the output as desired.
|
||||
type Context struct {
|
||||
// Output is the output stream to which the formatted string is written.
|
||||
Output io.Writer
|
||||
// Format is used to choose raw, table or custom format for the output.
|
||||
Format string
|
||||
// Size when set to true will display the size of the output.
|
||||
Size bool
|
||||
// Quiet when set to true will simply print minimal information.
|
||||
Quiet bool
|
||||
// Trunc when set to true will truncate the output of certain fields such as Container ID.
|
||||
Trunc bool
|
||||
Size bool
|
||||
Quiet 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) {
|
||||
switch ctx.Format {
|
||||
case tableFormatKey:
|
||||
@@ -76,65 +63,3 @@ func tableFormat(ctx Context, containers []types.Container) {
|
||||
|
||||
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]
|
||||
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")
|
||||
addTrustedFlags(cmd, true)
|
||||
cmd.Require(flag.Exact, 1)
|
||||
@@ -25,7 +25,7 @@ func (cli *DockerCli) CmdPull(args ...string) error {
|
||||
|
||||
taglessRemote, tag := parsers.ParseRepositoryTag(remote)
|
||||
if tag == "" && !*allTags {
|
||||
tag = tags.DefaultTag
|
||||
tag = tags.DEFAULTTAG
|
||||
fmt.Fprintf(cli.out, "Using default tag: %s\n", tag)
|
||||
} else if tag != "" && *allTags {
|
||||
return fmt.Errorf("tag can't be used with --all-tags/-a")
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
||||
//
|
||||
// Usage: docker push NAME[:TAG]
|
||||
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)
|
||||
cmd.Require(flag.Exact, 1)
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
Cli "github.com/docker/docker/cli"
|
||||
flag "github.com/docker/docker/pkg/mflag"
|
||||
@@ -12,17 +11,13 @@ import (
|
||||
//
|
||||
// Usage: docker rename OLD_NAME NEW_NAME
|
||||
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.ParseFlags(args, true)
|
||||
|
||||
oldName := strings.TrimSpace(cmd.Arg(0))
|
||||
newName := strings.TrimSpace(cmd.Arg(1))
|
||||
|
||||
if oldName == "" || newName == "" {
|
||||
return fmt.Errorf("Error: Neither old nor new names may be empty")
|
||||
}
|
||||
oldName := cmd.Arg(0)
|
||||
newName := cmd.Arg(1)
|
||||
|
||||
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)
|
||||
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
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 {
|
||||
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")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
||||
//
|
||||
// Usage: docker rm [OPTIONS] CONTAINER [CONTAINER...]
|
||||
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")
|
||||
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)")
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ import (
|
||||
//
|
||||
// Usage: docker rmi [OPTIONS] IMAGE [IMAGE...]
|
||||
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")
|
||||
noprune := cmd.Bool([]string{"-no-prune"}, false, "Do not delete untagged parents")
|
||||
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...]
|
||||
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)
|
||||
|
||||
// These are flags not stored in Config/HostConfig
|
||||
@@ -63,11 +63,11 @@ func (cli *DockerCli) CmdRun(args ...string) error {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if len(hostConfig.DNS) > 0 {
|
||||
if len(hostConfig.Dns) > 0 {
|
||||
// check the DNS settings passed via --dns against
|
||||
// localhost regexp to warn if they are trying to
|
||||
// set a DNS to a localhost address
|
||||
for _, dnsIP := range hostConfig.DNS {
|
||||
for _, dnsIP := range hostConfig.Dns {
|
||||
if dns.IsLocalhost(dnsIP) {
|
||||
fmt.Fprintf(cli.err, "WARNING: Localhost DNS setting (--dns=%s) may fail in containers.\n", dnsIP)
|
||||
break
|
||||
|
||||
+21
-15
@@ -2,6 +2,7 @@ package client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
|
||||
@@ -15,24 +16,23 @@ import (
|
||||
//
|
||||
// Usage: docker save [OPTIONS] IMAGE [IMAGE...]
|
||||
func (cli *DockerCli) CmdSave(args ...string) error {
|
||||
cmd := Cli.Subcmd("save", []string{"IMAGE [IMAGE...]"}, Cli.DockerCommands["save"].Description+" (streamed to STDOUT by default)", true)
|
||||
outfile := cmd.String([]string{"o", "-output"}, "", "Write to a file, instead of STDOUT")
|
||||
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 an file, instead of STDOUT")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
|
||||
var (
|
||||
output = cli.out
|
||||
output io.Writer = cli.out
|
||||
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 output, err = os.Create(*outfile); err != nil {
|
||||
output, err = os.Create(*outfile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if cli.isTerminalOut {
|
||||
return errors.New("Cowardly refusing to save to a terminal. Use the -o flag or redirect.")
|
||||
}
|
||||
|
||||
sopts := &streamOpts{
|
||||
@@ -40,13 +40,19 @@ func (cli *DockerCli) CmdSave(args ...string) error {
|
||||
out: output,
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
for _, arg := range cmd.Args() {
|
||||
v.Add("names", arg)
|
||||
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{}
|
||||
for _, arg := range cmd.Args() {
|
||||
v.Add("names", arg)
|
||||
}
|
||||
if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if _, err := cli.stream("GET", "/images/get?"+v.Encode(), sopts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
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")
|
||||
trusted := cmd.Bool([]string{"#t", "#trusted", "#-trusted"}, false, "Only show trusted 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
|
||||
taglessRemote, _ := parsers.ParseRepositoryTag(name)
|
||||
|
||||
indexInfo, err := registry.ParseIndexInfo(taglessRemote)
|
||||
repoInfo, err := registry.ParseRepositoryInfo(taglessRemote)
|
||||
if err != nil {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@@ -64,7 +63,7 @@ func (cli *DockerCli) CmdSearch(args ...string) error {
|
||||
w := tabwriter.NewWriter(cli.out, 10, 1, 3, ' ', 0)
|
||||
fmt.Fprintf(w, "NAME\tDESCRIPTION\tSTARS\tOFFICIAL\tAUTOMATED\n")
|
||||
for _, 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
|
||||
}
|
||||
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 == "" {
|
||||
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 {
|
||||
logrus.Debugf("Error sending signal: %s", err)
|
||||
@@ -42,11 +41,11 @@ func (cli *DockerCli) forwardAllSignals(cid string) chan os.Signal {
|
||||
return sigc
|
||||
}
|
||||
|
||||
// CmdStart starts one or more containers.
|
||||
// CmdStart starts one or more stopped containers.
|
||||
//
|
||||
// Usage: docker start [OPTIONS] CONTAINER [CONTAINER...]
|
||||
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")
|
||||
openStdin := cmd.Bool([]string{"i", "-interactive"}, false, "Attach container's STDIN")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
+16
-54
@@ -25,8 +25,6 @@ type containerStats struct {
|
||||
MemoryPercentage float64
|
||||
NetworkRx float64
|
||||
NetworkTx float64
|
||||
BlockRead float64
|
||||
BlockWrite float64
|
||||
mu sync.RWMutex
|
||||
err error
|
||||
}
|
||||
@@ -56,33 +54,25 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
||||
)
|
||||
go func() {
|
||||
for {
|
||||
var v *types.StatsJSON
|
||||
var v *types.Stats
|
||||
if err := dec.Decode(&v); err != nil {
|
||||
u <- err
|
||||
return
|
||||
}
|
||||
|
||||
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 {
|
||||
var (
|
||||
memPercent = float64(v.MemoryStats.Usage) / float64(v.MemoryStats.Limit) * 100.0
|
||||
}
|
||||
|
||||
previousCPU = v.PreCPUStats.CPUUsage.TotalUsage
|
||||
previousSystem = v.PreCPUStats.SystemUsage
|
||||
cpuPercent = 0.0
|
||||
)
|
||||
previousCPU = v.PreCpuStats.CpuUsage.TotalUsage
|
||||
previousSystem = v.PreCpuStats.SystemUsage
|
||||
cpuPercent = calculateCPUPercent(previousCPU, previousSystem, v)
|
||||
blkRead, blkWrite := calculateBlockIO(v.BlkioStats)
|
||||
s.mu.Lock()
|
||||
s.CPUPercentage = cpuPercent
|
||||
s.Memory = float64(v.MemoryStats.Usage)
|
||||
s.MemoryLimit = float64(v.MemoryStats.Limit)
|
||||
s.MemoryPercentage = memPercent
|
||||
s.NetworkRx, s.NetworkTx = calculateNetwork(v.Networks)
|
||||
s.BlockRead = float64(blkRead)
|
||||
s.BlockWrite = float64(blkWrite)
|
||||
s.NetworkRx = float64(v.Network.RxBytes)
|
||||
s.NetworkTx = float64(v.Network.TxBytes)
|
||||
s.mu.Unlock()
|
||||
u <- nil
|
||||
if !streamStats {
|
||||
@@ -99,11 +89,6 @@ func (s *containerStats) Collect(cli *DockerCli, streamStats bool) {
|
||||
s.CPUPercentage = 0
|
||||
s.Memory = 0
|
||||
s.MemoryPercentage = 0
|
||||
s.MemoryLimit = 0
|
||||
s.NetworkRx = 0
|
||||
s.NetworkTx = 0
|
||||
s.BlockRead = 0
|
||||
s.BlockWrite = 0
|
||||
s.mu.Unlock()
|
||||
case err := <-u:
|
||||
if err != nil {
|
||||
@@ -125,13 +110,12 @@ func (s *containerStats) Display(w io.Writer) error {
|
||||
if s.err != nil {
|
||||
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.CPUPercentage,
|
||||
units.HumanSize(s.Memory), units.HumanSize(s.MemoryLimit),
|
||||
s.MemoryPercentage,
|
||||
units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx),
|
||||
units.HumanSize(s.BlockRead), units.HumanSize(s.BlockWrite))
|
||||
units.HumanSize(s.NetworkRx), units.HumanSize(s.NetworkTx))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -141,7 +125,7 @@ func (s *containerStats) Display(w io.Writer) error {
|
||||
//
|
||||
// Usage: docker stats CONTAINER [CONTAINER...]
|
||||
func (cli *DockerCli) CmdStats(args ...string) error {
|
||||
cmd := Cli.Subcmd("stats", []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")
|
||||
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[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 {
|
||||
s := &containerStats{Name: n}
|
||||
@@ -202,39 +186,17 @@ func (cli *DockerCli) CmdStats(args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.StatsJSON) float64 {
|
||||
func calculateCPUPercent(previousCPU, previousSystem uint64, v *types.Stats) float64 {
|
||||
var (
|
||||
cpuPercent = 0.0
|
||||
// calculate the change for the cpu usage of the container in between readings
|
||||
cpuDelta = float64(v.CPUStats.CPUUsage.TotalUsage - previousCPU)
|
||||
cpuDelta = float64(v.CpuStats.CpuUsage.TotalUsage - previousCPU)
|
||||
// calculate the change for the entire system between readings
|
||||
systemDelta = float64(v.CPUStats.SystemUsage - previousSystem)
|
||||
systemDelta = float64(v.CpuStats.SystemUsage - previousSystem)
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
)
|
||||
|
||||
func TestDisplay(t *testing.T) {
|
||||
@@ -17,8 +15,6 @@ func TestDisplay(t *testing.T) {
|
||||
MemoryPercentage: 100.0 / 2048.0 * 100.0,
|
||||
NetworkRx: 100 * 1024 * 1024,
|
||||
NetworkTx: 800 * 1024 * 1024,
|
||||
BlockRead: 100 * 1024 * 1024,
|
||||
BlockWrite: 800 * 1024 * 1024,
|
||||
mu: sync.RWMutex{},
|
||||
}
|
||||
var b bytes.Buffer
|
||||
@@ -26,21 +22,8 @@ func TestDisplay(t *testing.T) {
|
||||
t.Fatalf("c.Display() gave error: %s", err)
|
||||
}
|
||||
got := b.String()
|
||||
want := "app\t30.00%\t104.9 MB / 2.147 GB\t4.88%\t104.9 MB / 838.9 MB\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 {
|
||||
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"
|
||||
)
|
||||
|
||||
// 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).
|
||||
//
|
||||
// Usage: docker stop [OPTIONS] CONTAINER [CONTAINER...]
|
||||
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")
|
||||
cmd.Require(flag.Min, 1)
|
||||
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ import (
|
||||
//
|
||||
// Usage: docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
|
||||
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")
|
||||
cmd.Require(flag.Exact, 2)
|
||||
|
||||
|
||||
+1
-1
@@ -16,7 +16,7 @@ import (
|
||||
//
|
||||
// Usage: docker top CONTAINER
|
||||
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.ParseFlags(args, true)
|
||||
|
||||
+22
-47
@@ -15,6 +15,7 @@ import (
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
func trustServer(index *registry.IndexInfo) (string, error) {
|
||||
func trustServer(index *registry.IndexInfo) string {
|
||||
if s := os.Getenv("DOCKER_CONTENT_TRUST_SERVER"); s != "" {
|
||||
urlObj, err := url.Parse(s)
|
||||
if err != nil || urlObj.Scheme != "https" {
|
||||
return "", fmt.Errorf("valid https URL required for trust server, got %s", s)
|
||||
if !strings.HasPrefix(s, "https://") {
|
||||
return "https://" + s
|
||||
}
|
||||
|
||||
return s, nil
|
||||
return s
|
||||
}
|
||||
if index.Official {
|
||||
return registry.NotaryServer, nil
|
||||
return registry.NotaryServer
|
||||
}
|
||||
return "https://" + index.Name, nil
|
||||
return "https://" + index.Name
|
||||
}
|
||||
|
||||
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) {
|
||||
server, err := trustServer(repoInfo.Index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
server := trustServer(repoInfo.Index)
|
||||
if !strings.HasPrefix(server, "https://") {
|
||||
return nil, errors.New("unsupported scheme: https required for trust server")
|
||||
}
|
||||
|
||||
var cfg = tlsconfig.ClientDefault
|
||||
@@ -145,21 +144,15 @@ func (cli *DockerCli) getNotaryRepository(repoInfo *registry.RepositoryInfo, aut
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
challengeManager := auth.NewSimpleChallengeManager()
|
||||
|
||||
resp, err := pingClient.Do(req)
|
||||
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()
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
challengeManager := auth.NewSimpleChallengeManager()
|
||||
if err := challengeManager.AddResponse(resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
creds := simpleCredentialStore{auth: authConfig}
|
||||
@@ -185,32 +178,16 @@ func convertTarget(t client.Target) (target, error) {
|
||||
|
||||
func (cli *DockerCli) getPassphraseRetriever() passphrase.Retriever {
|
||||
aliasMap := map[string]string{
|
||||
"root": "root",
|
||||
"snapshot": "repository",
|
||||
"targets": "repository",
|
||||
"root": "offline",
|
||||
"snapshot": "tagging",
|
||||
"targets": "tagging",
|
||||
}
|
||||
baseRetriever := passphrase.PromptRetrieverWithInOut(cli.in, cli.out, aliasMap)
|
||||
env := map[string]string{
|
||||
"root": os.Getenv("DOCKER_CONTENT_TRUST_ROOT_PASSPHRASE"),
|
||||
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
||||
"targets": os.Getenv("DOCKER_CONTENT_TRUST_REPOSITORY_PASSPHRASE"),
|
||||
"root": os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"),
|
||||
"snapshot": os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_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"] == "" {
|
||||
if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_OFFLINE_PASSPHRASE"); passphrase != "" {
|
||||
env["root"] = 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"] == "" {
|
||||
if passphrase := os.Getenv("DOCKER_CONTENT_TRUST_TAGGING_PASSPHRASE"); passphrase != "" {
|
||||
env["snapshot"] = passphrase
|
||||
env["targets"] = 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) {
|
||||
if v := env[alias]; v != "" {
|
||||
return v, numAttempts > 1, nil
|
||||
@@ -271,8 +248,6 @@ func notaryError(err error) error {
|
||||
return fmt.Errorf("remote repository out-of-date: %v", err)
|
||||
case trustmanager.ErrKeyNotFound:
|
||||
return fmt.Errorf("signing keys not found: %v", err)
|
||||
case *net.OpError:
|
||||
return fmt.Errorf("error contacting notary server: %v", 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...]
|
||||
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.ParseFlags(args, true)
|
||||
|
||||
+39
-44
@@ -27,11 +27,10 @@ import (
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
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 {
|
||||
@@ -95,14 +94,13 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m
|
||||
if resp != nil {
|
||||
serverResp.statusCode = resp.StatusCode
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if utils.IsTimeout(err) || strings.Contains(err.Error(), "connection refused") || strings.Contains(err.Error(), "dial unix") {
|
||||
return serverResp, errConnectionFailed
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return serverResp, errConnectionRefused
|
||||
}
|
||||
|
||||
if cli.tlsConfig == nil && strings.Contains(err.Error(), "malformed HTTP response") {
|
||||
return serverResp, fmt.Errorf("%v.\n* Are you trying to connect to a TLS-enabled daemon without TLS?", err)
|
||||
if cli.tlsConfig == nil {
|
||||
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") {
|
||||
return serverResp, fmt.Errorf("The server probably has client authentication (--tlsverify) enabled. Please check your TLS client certification settings: %v", err)
|
||||
@@ -127,51 +125,48 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m
|
||||
return serverResp, nil
|
||||
}
|
||||
|
||||
// cmdAttempt builds the corresponding registry Auth Header from the given
|
||||
// authConfig. It returns the servers body, status, error response
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
|
||||
// begin the request
|
||||
serverResp, err := cli.clientRequest(method, path, in, map[string][]string{
|
||||
"X-Registry-Auth": registryAuthHeader,
|
||||
})
|
||||
if err == nil && out != nil {
|
||||
// If we are streaming output, complete the stream since
|
||||
// errors may not appear until later.
|
||||
err = cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), true, out, nil)
|
||||
}
|
||||
if err != nil {
|
||||
// Since errors in a stream appear after status 200 has been written,
|
||||
// we may need to change the status code.
|
||||
if strings.Contains(err.Error(), "Authentication is required") ||
|
||||
strings.Contains(err.Error(), "Status 401") ||
|
||||
strings.Contains(err.Error(), "401 Unauthorized") ||
|
||||
strings.Contains(err.Error(), "status code 401") {
|
||||
serverResp.statusCode = http.StatusUnauthorized
|
||||
}
|
||||
}
|
||||
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) {
|
||||
cmdAttempt := func(authConfig cliconfig.AuthConfig) (io.ReadCloser, int, error) {
|
||||
buf, err := json.Marshal(authConfig)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
registryAuthHeader := []string{
|
||||
base64.URLEncoding.EncodeToString(buf),
|
||||
}
|
||||
|
||||
// begin the request
|
||||
serverResp, err := cli.clientRequest(method, path, in, map[string][]string{
|
||||
"X-Registry-Auth": registryAuthHeader,
|
||||
})
|
||||
if err == nil && out != nil {
|
||||
// If we are streaming output, complete the stream since
|
||||
// errors may not appear until later.
|
||||
err = cli.streamBody(serverResp.body, serverResp.header.Get("Content-Type"), true, out, nil)
|
||||
}
|
||||
if err != nil {
|
||||
// Since errors in a stream appear after status 200 has been written,
|
||||
// we may need to change the status code.
|
||||
if strings.Contains(err.Error(), "Authentication is required") ||
|
||||
strings.Contains(err.Error(), "Status 401") ||
|
||||
strings.Contains(err.Error(), "401 Unauthorized") ||
|
||||
strings.Contains(err.Error(), "status code 401") {
|
||||
serverResp.statusCode = http.StatusUnauthorized
|
||||
}
|
||||
}
|
||||
return serverResp.body, serverResp.statusCode, err
|
||||
}
|
||||
|
||||
// Resolve the Auth config relevant for this server
|
||||
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 {
|
||||
fmt.Fprintf(cli.out, "\nPlease login prior to %s:\n", cmdName)
|
||||
if err = cli.CmdLogin(index.GetAuthConfigKey()); err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
authConfig = registry.ResolveAuthConfig(cli.configFile, index)
|
||||
return cli.cmdAttempt(authConfig, method, path, in, out)
|
||||
return cmdAttempt(authConfig)
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if err != errConnectionFailed {
|
||||
if err != errConnectionRefused {
|
||||
return false, -1, err
|
||||
}
|
||||
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)
|
||||
if err != nil {
|
||||
// If we can't connect, then the daemon probably died.
|
||||
if err != errConnectionFailed {
|
||||
if err != errConnectionRefused {
|
||||
return false, -1, err
|
||||
}
|
||||
return false, -1, nil
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
"github.com/docker/docker/utils"
|
||||
)
|
||||
|
||||
var versionTemplate = `Client:
|
||||
var VersionTemplate = `Client:
|
||||
Version: {{.Client.Version}}
|
||||
API version: {{.Client.APIVersion}}
|
||||
API version: {{.Client.ApiVersion}}
|
||||
Go version: {{.Client.GoVersion}}
|
||||
Git commit: {{.Client.GitCommit}}
|
||||
Built: {{.Client.BuildTime}}
|
||||
@@ -24,14 +24,14 @@ var versionTemplate = `Client:
|
||||
|
||||
Server:
|
||||
Version: {{.Server.Version}}
|
||||
API version: {{.Server.APIVersion}}
|
||||
API version: {{.Server.ApiVersion}}
|
||||
Go version: {{.Server.GoVersion}}
|
||||
Git commit: {{.Server.GitCommit}}
|
||||
Built: {{.Server.BuildTime}}
|
||||
OS/Arch: {{.Server.Os}}/{{.Server.Arch}}{{if .Server.Experimental}}
|
||||
Experimental: {{.Server.Experimental}}{{end}}{{end}}`
|
||||
|
||||
type versionData struct {
|
||||
type VersionData struct {
|
||||
Client types.Version
|
||||
ServerOK bool
|
||||
Server types.Version
|
||||
@@ -43,13 +43,13 @@ type versionData struct {
|
||||
//
|
||||
// Usage: docker version
|
||||
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")
|
||||
cmd.Require(flag.Exact, 0)
|
||||
|
||||
cmd.ParseFlags(args, true)
|
||||
if *tmplStr == "" {
|
||||
*tmplStr = versionTemplate
|
||||
*tmplStr = VersionTemplate
|
||||
}
|
||||
|
||||
var tmpl *template.Template
|
||||
@@ -58,10 +58,10 @@ func (cli *DockerCli) CmdVersion(args ...string) (err error) {
|
||||
Status: "Template parsing error: " + err.Error()}
|
||||
}
|
||||
|
||||
vd := versionData{
|
||||
vd := VersionData{
|
||||
Client: types.Version{
|
||||
Version: dockerversion.VERSION,
|
||||
APIVersion: api.Version,
|
||||
ApiVersion: api.Version,
|
||||
GoVersion: runtime.Version(),
|
||||
GitCommit: dockerversion.GITCOMMIT,
|
||||
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/create", 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...]
|
||||
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.ParseFlags(args, true)
|
||||
|
||||
+48
-42
@@ -5,7 +5,6 @@ import (
|
||||
"mime"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
@@ -17,38 +16,39 @@ import (
|
||||
|
||||
// Common constants for daemon and client.
|
||||
const (
|
||||
// Version of Current REST API
|
||||
Version version.Version = "1.21"
|
||||
// Current REST API version
|
||||
Version version.Version = "1.20"
|
||||
|
||||
// MinVersion represents Minimun REST API version supported
|
||||
// Minimun REST API version supported
|
||||
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"
|
||||
)
|
||||
|
||||
// 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) 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) Len() int { return len(r) }
|
||||
func (r ByPrivatePort) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||
func (r ByPrivatePort) Less(i, j int) bool { return r[i].PrivatePort < r[j].PrivatePort }
|
||||
|
||||
// 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 {
|
||||
type portGroup struct {
|
||||
first int
|
||||
last int
|
||||
}
|
||||
groupMap := make(map[string]*portGroup)
|
||||
var result []string
|
||||
var hostMappings []string
|
||||
sort.Sort(byPrivatePort(ports))
|
||||
var (
|
||||
result = []string{}
|
||||
hostMappings = []string{}
|
||||
firstInGroupMap map[string]int
|
||||
lastInGroupMap map[string]int
|
||||
)
|
||||
firstInGroupMap = make(map[string]int)
|
||||
lastInGroupMap = make(map[string]int)
|
||||
sort.Sort(ByPrivatePort(ports))
|
||||
for _, port := range ports {
|
||||
current := port.PrivatePort
|
||||
portKey := port.Type
|
||||
var (
|
||||
current = port.PrivatePort
|
||||
portKey = port.Type
|
||||
firstInGroup int
|
||||
lastInGroup int
|
||||
)
|
||||
if port.IP != "" {
|
||||
if port.PublicPort != current {
|
||||
hostMappings = append(hostMappings, fmt.Sprintf("%s:%d->%d/%s", port.IP, port.PublicPort, port.PrivatePort, port.Type))
|
||||
@@ -56,38 +56,45 @@ func DisplayablePorts(ports []types.Port) string {
|
||||
}
|
||||
portKey = fmt.Sprintf("%s/%s", port.IP, port.Type)
|
||||
}
|
||||
group := groupMap[portKey]
|
||||
firstInGroup = firstInGroupMap[portKey]
|
||||
lastInGroup = lastInGroupMap[portKey]
|
||||
|
||||
if group == nil {
|
||||
groupMap[portKey] = &portGroup{first: current, last: current}
|
||||
continue
|
||||
}
|
||||
if current == (group.last + 1) {
|
||||
group.last = current
|
||||
if firstInGroup == 0 {
|
||||
firstInGroupMap[portKey] = current
|
||||
lastInGroupMap[portKey] = current
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, formGroup(portKey, group.first, group.last))
|
||||
groupMap[portKey] = &portGroup{first: current, last: current}
|
||||
if current == (lastInGroup + 1) {
|
||||
lastInGroupMap[portKey] = current
|
||||
continue
|
||||
}
|
||||
result = append(result, FormGroup(portKey, firstInGroup, lastInGroup))
|
||||
firstInGroupMap[portKey] = current
|
||||
lastInGroupMap[portKey] = current
|
||||
}
|
||||
for portKey, g := range groupMap {
|
||||
result = append(result, formGroup(portKey, g.first, g.last))
|
||||
for portKey, firstInGroup := range firstInGroupMap {
|
||||
result = append(result, FormGroup(portKey, firstInGroup, lastInGroupMap[portKey]))
|
||||
}
|
||||
result = append(result, hostMappings...)
|
||||
return strings.Join(result, ", ")
|
||||
}
|
||||
|
||||
func formGroup(key string, start, last int) string {
|
||||
parts := strings.Split(key, "/")
|
||||
groupType := parts[0]
|
||||
var ip string
|
||||
func FormGroup(key string, start, last int) string {
|
||||
var (
|
||||
group string
|
||||
parts = strings.Split(key, "/")
|
||||
groupType = parts[0]
|
||||
ip = ""
|
||||
)
|
||||
if len(parts) > 1 {
|
||||
ip = parts[0]
|
||||
groupType = parts[1]
|
||||
}
|
||||
group := strconv.Itoa(start)
|
||||
if start != last {
|
||||
group = fmt.Sprintf("%s-%d", group, last)
|
||||
if start == last {
|
||||
group = fmt.Sprintf("%d", start)
|
||||
} else {
|
||||
group = fmt.Sprintf("%d-%d", start, last)
|
||||
}
|
||||
if ip != "" {
|
||||
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)
|
||||
}
|
||||
|
||||
// MatchesContentType validates the content type against the expected one
|
||||
func MatchesContentType(contentType, expectedType string) bool {
|
||||
mimetype, _, err := mime.ParseMediaType(contentType)
|
||||
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 (
|
||||
"net/http"
|
||||
@@ -26,7 +26,7 @@ func TestBoolValue(t *testing.T) {
|
||||
r, _ := http.NewRequest("POST", "", nil)
|
||||
r.Form = v
|
||||
|
||||
a := BoolValue(r, "test")
|
||||
a := boolValue(r, "test")
|
||||
if a != e {
|
||||
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) {
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func TestBoolValueOrDefault(t *testing.T) {
|
||||
v.Set("param", "")
|
||||
r, _ = http.NewRequest("GET", "", nil)
|
||||
r.Form = v
|
||||
if BoolValueOrDefault(r, "param", true) {
|
||||
if boolValueOrDefault(r, "param", true) {
|
||||
t.Fatal("Expected not to get true")
|
||||
}
|
||||
}
|
||||
@@ -62,44 +62,9 @@ func TestInt64ValueOrZero(t *testing.T) {
|
||||
r, _ := http.NewRequest("POST", "", nil)
|
||||
r.Form = v
|
||||
|
||||
a := Int64ValueOrZero(r, "test")
|
||||
a := int64ValueOrZero(r, "test")
|
||||
if a != e {
|
||||
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,76 +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 vars == nil {
|
||||
return ArchiveOptions{}, fmt.Errorf("Missing parameter")
|
||||
}
|
||||
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"
|
||||
)
|
||||
|
||||
func profilerSetup(mainRouter *mux.Router, path string) {
|
||||
func ProfilerSetup(mainRouter *mux.Router, path string) {
|
||||
var r = mainRouter.PathPrefix(path).Subrouter()
|
||||
r.HandleFunc("/vars", expVars)
|
||||
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,508 +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
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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 vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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 vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
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
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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 vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
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 vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
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 {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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 {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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 vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
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
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
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,116 +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 vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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,135 +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 {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter 'id'")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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,520 +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"
|
||||
"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,
|
||||
}
|
||||
|
||||
container, err := s.daemon.Get(cname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
imgID, err := dockerfile.Commit(container, 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 {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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 vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
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
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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 {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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 {
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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,37 +0,0 @@
|
||||
package local
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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")
|
||||
if vars == nil {
|
||||
return fmt.Errorf("Missing parameter")
|
||||
}
|
||||
|
||||
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/create", 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,57 +0,0 @@
|
||||
package network
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/server/httputils"
|
||||
"github.com/docker/docker/api/server/router"
|
||||
"github.com/docker/docker/api/server/router/local"
|
||||
"github.com/docker/docker/daemon"
|
||||
"github.com/docker/docker/errors"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// 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.controllerEnabledMiddleware(r.getNetworksList)),
|
||||
local.NewGetRoute("/networks/{id:.*}", r.controllerEnabledMiddleware(r.getNetwork)),
|
||||
// POST
|
||||
local.NewPostRoute("/networks/create", r.controllerEnabledMiddleware(r.postNetworkCreate)),
|
||||
local.NewPostRoute("/networks/{id:.*}/connect", r.controllerEnabledMiddleware(r.postNetworkConnect)),
|
||||
local.NewPostRoute("/networks/{id:.*}/disconnect", r.controllerEnabledMiddleware(r.postNetworkDisconnect)),
|
||||
// DELETE
|
||||
local.NewDeleteRoute("/networks/{id:.*}", r.controllerEnabledMiddleware(r.deleteNetwork)),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *networkRouter) controllerEnabledMiddleware(handler httputils.APIFunc) httputils.APIFunc {
|
||||
if r.daemon.NetworkControllerEnabled() {
|
||||
return handler
|
||||
}
|
||||
return networkControllerDisabled
|
||||
}
|
||||
|
||||
func networkControllerDisabled(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
return errors.ErrorNetworkControllerNotEnabled.WithArgs()
|
||||
}
|
||||
@@ -1,257 +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/docker/runconfig"
|
||||
"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
|
||||
}
|
||||
|
||||
if runconfig.IsPreDefinedNetwork(create.Name) {
|
||||
return httputils.WriteJSON(w, http.StatusForbidden,
|
||||
fmt.Sprintf("%s is a pre-defined network and cannot be created", create.Name))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
container, err := n.daemon.Get(connect.Container)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid container %s : %v", container, err)
|
||||
}
|
||||
return container.ConnectToNetwork(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
|
||||
}
|
||||
|
||||
container, err := n.daemon.Get(disconnect.Container)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid container %s : %v", container, err)
|
||||
}
|
||||
return container.DisconnectFromNetwork(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
|
||||
}
|
||||
|
||||
if runconfig.IsPreDefinedNetwork(nw.Name()) {
|
||||
return httputils.WriteJSON(w, http.StatusForbidden,
|
||||
fmt.Sprintf("%s is a pre-defined network and cannot be removed", nw.Name()))
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+1626
-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"
|
||||
"net"
|
||||
"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.
|
||||
func (s *Server) newServer(proto, addr string) ([]*HTTPServer, error) {
|
||||
func (s *Server) newServer(proto, addr string) ([]serverCloser, error) {
|
||||
var (
|
||||
ls []net.Listener
|
||||
)
|
||||
switch proto {
|
||||
case "tcp":
|
||||
l, err := s.initTCPSocket(addr)
|
||||
l, err := s.initTcpSocket(addr)
|
||||
if err != nil {
|
||||
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.")
|
||||
}
|
||||
|
||||
var res []*HTTPServer
|
||||
var res []serverCloser
|
||||
for _, l := range ls {
|
||||
res = append(res, &HTTPServer{
|
||||
res = append(res, &HttpServer{
|
||||
&http.Server{
|
||||
Addr: addr,
|
||||
Addr: addr,
|
||||
Handler: s.router,
|
||||
},
|
||||
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 {
|
||||
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.
|
||||
package types
|
||||
|
||||
import "time"
|
||||
|
||||
// ThrottlingData stores CPU throttling stats of one running container
|
||||
type ThrottlingData struct {
|
||||
// Number of periods with throttling active
|
||||
Periods uint64 `json:"periods"`
|
||||
@@ -14,8 +13,8 @@ type ThrottlingData struct {
|
||||
ThrottledTime uint64 `json:"throttled_time"`
|
||||
}
|
||||
|
||||
// CPUUsage stores All CPU stats aggregated since container inception.
|
||||
type CPUUsage struct {
|
||||
// All CPU stats are aggregated since container inception.
|
||||
type CpuUsage struct {
|
||||
// Total CPU time consumed.
|
||||
// Units: nanoseconds.
|
||||
TotalUsage uint64 `json:"total_usage"`
|
||||
@@ -30,14 +29,12 @@ type CPUUsage struct {
|
||||
UsageInUsermode uint64 `json:"usage_in_usermode"`
|
||||
}
|
||||
|
||||
// CPUStats aggregates and wraps all CPU related info of container
|
||||
type CPUStats struct {
|
||||
CPUUsage CPUUsage `json:"cpu_usage"`
|
||||
type CpuStats struct {
|
||||
CpuUsage CpuUsage `json:"cpu_usage"`
|
||||
SystemUsage uint64 `json:"system_cpu_usage"`
|
||||
ThrottlingData ThrottlingData `json:"throttling_data,omitempty"`
|
||||
}
|
||||
|
||||
// MemoryStats aggregates All memory stats since container inception
|
||||
type MemoryStats struct {
|
||||
// current res_counter usage for memory
|
||||
Usage uint64 `json:"usage"`
|
||||
@@ -51,7 +48,6 @@ type MemoryStats struct {
|
||||
Limit uint64 `json:"limit"`
|
||||
}
|
||||
|
||||
// BlkioStatEntry is one small entity to store a piece of Blkio stats
|
||||
// TODO Windows: This can be factored out
|
||||
type BlkioStatEntry struct {
|
||||
Major uint64 `json:"major"`
|
||||
@@ -60,7 +56,6 @@ type BlkioStatEntry struct {
|
||||
Value uint64 `json:"value"`
|
||||
}
|
||||
|
||||
// BlkioStats stores All IO service stats for data read and write
|
||||
// TODO Windows: This can be factored out
|
||||
type BlkioStats struct {
|
||||
// number of bytes tranferred to and from the block device
|
||||
@@ -74,9 +69,8 @@ type BlkioStats struct {
|
||||
SectorsRecursive []BlkioStatEntry `json:"sectors_recursive"`
|
||||
}
|
||||
|
||||
// NetworkStats aggregates All network stats of one container
|
||||
// TODO Windows: This will require refactoring
|
||||
type NetworkStats struct {
|
||||
type Network struct {
|
||||
RxBytes uint64 `json:"rx_bytes"`
|
||||
RxPackets uint64 `json:"rx_packets"`
|
||||
RxErrors uint64 `json:"rx_errors"`
|
||||
@@ -87,19 +81,11 @@ type NetworkStats struct {
|
||||
TxDropped uint64 `json:"tx_dropped"`
|
||||
}
|
||||
|
||||
// Stats is Ultimate struct aggregating all types of stats of one container
|
||||
type Stats struct {
|
||||
Read time.Time `json:"read"`
|
||||
PreCPUStats CPUStats `json:"precpu_stats,omitempty"`
|
||||
CPUStats CPUStats `json:"cpu_stats,omitempty"`
|
||||
Network Network `json:"network,omitempty"`
|
||||
PreCpuStats CpuStats `json:"precpu_stats,omitempty"`
|
||||
CpuStats CpuStats `json:"cpu_stats,omitempty"`
|
||||
MemoryStats MemoryStats `json:"memory_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"`
|
||||
}
|
||||
|
||||
+39
-154
@@ -5,9 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/daemon/network"
|
||||
"github.com/docker/docker/pkg/nat"
|
||||
"github.com/docker/docker/pkg/version"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/docker/docker/runconfig"
|
||||
)
|
||||
|
||||
@@ -21,41 +19,35 @@ type ContainerCreateResponse struct {
|
||||
Warnings []string `json:"Warnings"`
|
||||
}
|
||||
|
||||
// ContainerExecCreateResponse contains response of Remote API:
|
||||
// POST "/containers/{name:.*}/exec"
|
||||
// POST /containers/{name:.*}/exec
|
||||
type ContainerExecCreateResponse struct {
|
||||
// ID is the exec ID.
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
||||
// AuthResponse contains response of Remote API:
|
||||
// POST "/auth"
|
||||
// POST /auth
|
||||
type AuthResponse struct {
|
||||
// Status is the authentication status
|
||||
Status string `json:"Status"`
|
||||
}
|
||||
|
||||
// ContainerWaitResponse contains response of Remote API:
|
||||
// POST "/containers/"+containerID+"/wait"
|
||||
type ContainerWaitResponse struct {
|
||||
// StatusCode is the status code of the wait job
|
||||
StatusCode int `json:"StatusCode"`
|
||||
}
|
||||
|
||||
// ContainerCommitResponse contains response of Remote API:
|
||||
// POST "/commit?container="+containerID
|
||||
type ContainerCommitResponse struct {
|
||||
ID string `json:"Id"`
|
||||
}
|
||||
|
||||
// ContainerChange contains response of Remote API:
|
||||
// GET "/containers/{name:.*}/changes"
|
||||
type ContainerChange struct {
|
||||
Kind int
|
||||
Path string
|
||||
}
|
||||
|
||||
// ImageHistory contains response of Remote API:
|
||||
// GET "/images/{name:.*}/history"
|
||||
type ImageHistory struct {
|
||||
ID string `json:"Id"`
|
||||
@@ -66,39 +58,32 @@ type ImageHistory struct {
|
||||
Comment string
|
||||
}
|
||||
|
||||
// ImageDelete contains response of Remote API:
|
||||
// DELETE "/images/{name:.*}"
|
||||
type ImageDelete struct {
|
||||
Untagged string `json:",omitempty"`
|
||||
Deleted string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Image contains response of Remote API:
|
||||
// GET "/images/json"
|
||||
type Image struct {
|
||||
ID string `json:"Id"`
|
||||
ParentID string `json:"ParentId"`
|
||||
ParentId string
|
||||
RepoTags []string
|
||||
RepoDigests []string
|
||||
Created int64
|
||||
Size int64
|
||||
VirtualSize int64
|
||||
Created int
|
||||
Size int
|
||||
VirtualSize int
|
||||
Labels map[string]string
|
||||
}
|
||||
|
||||
// GraphDriverData returns Image's graph driver config info
|
||||
// when calling inspect command
|
||||
type GraphDriverData struct {
|
||||
Name string
|
||||
Data map[string]string
|
||||
}
|
||||
|
||||
// ImageInspect contains response of Remote API:
|
||||
// GET "/images/{name:.*}/json"
|
||||
type ImageInspect struct {
|
||||
ID string `json:"Id"`
|
||||
RepoTags []string
|
||||
RepoDigests []string
|
||||
Id string
|
||||
Parent string
|
||||
Comment string
|
||||
Created string
|
||||
@@ -114,8 +99,7 @@ type ImageInspect struct {
|
||||
GraphDriver GraphDriverData
|
||||
}
|
||||
|
||||
// Port stores open ports info of container
|
||||
// e.g. {"PrivatePort": 8080, "PublicPort": 80, "Type": "tcp"}
|
||||
// GET "/containers/json"
|
||||
type Port struct {
|
||||
IP string `json:",omitempty"`
|
||||
PrivatePort int
|
||||
@@ -123,18 +107,15 @@ type Port struct {
|
||||
Type string
|
||||
}
|
||||
|
||||
// Container contains response of Remote API:
|
||||
// GET "/containers/json"
|
||||
type Container struct {
|
||||
ID string `json:"Id"`
|
||||
Names []string
|
||||
Image string
|
||||
ImageID string
|
||||
Command string
|
||||
Created int64
|
||||
Created int
|
||||
Ports []Port
|
||||
SizeRw int64 `json:",omitempty"`
|
||||
SizeRootFs int64 `json:",omitempty"`
|
||||
SizeRw int `json:",omitempty"`
|
||||
SizeRootFs int `json:",omitempty"`
|
||||
Labels map[string]string
|
||||
Status string
|
||||
HostConfig struct {
|
||||
@@ -142,15 +123,14 @@ type Container struct {
|
||||
}
|
||||
}
|
||||
|
||||
// CopyConfig contains request body of Remote API:
|
||||
// POST "/containers/"+containerID+"/copy"
|
||||
type CopyConfig struct {
|
||||
Resource string
|
||||
}
|
||||
|
||||
// ContainerPathStat is used to encode the header from
|
||||
// GET "/containers/{name:.*}/archive"
|
||||
// "Name" is the file or directory name.
|
||||
// GET /containers/{name:.*}/archive
|
||||
// "name" is basename of the resource.
|
||||
type ContainerPathStat struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
@@ -159,18 +139,15 @@ type ContainerPathStat struct {
|
||||
LinkTarget string `json:"linkTarget"`
|
||||
}
|
||||
|
||||
// ContainerProcessList contains response of Remote API:
|
||||
// GET "/containers/{name:.*}/top"
|
||||
type ContainerProcessList struct {
|
||||
Processes [][]string
|
||||
Titles []string
|
||||
}
|
||||
|
||||
// Version contains response of Remote API:
|
||||
// GET "/version"
|
||||
type Version struct {
|
||||
Version string
|
||||
APIVersion version.Version `json:"ApiVersion"`
|
||||
ApiVersion version.Version
|
||||
GitCommit string
|
||||
GoVersion string
|
||||
Os string
|
||||
@@ -180,7 +157,6 @@ type Version struct {
|
||||
BuildTime string `json:",omitempty"`
|
||||
}
|
||||
|
||||
// Info contains response of Remote API:
|
||||
// GET "/info"
|
||||
type Info struct {
|
||||
ID string
|
||||
@@ -190,11 +166,11 @@ type Info struct {
|
||||
DriverStatus [][2]string
|
||||
MemoryLimit bool
|
||||
SwapLimit bool
|
||||
CPUCfsPeriod bool `json:"CpuCfsPeriod"`
|
||||
CPUCfsQuota bool `json:"CpuCfsQuota"`
|
||||
CpuCfsPeriod bool
|
||||
CpuCfsQuota bool
|
||||
IPv4Forwarding bool
|
||||
BridgeNfIptables bool
|
||||
BridgeNfIP6tables bool `json:"BridgeNfIp6tables"`
|
||||
BridgeNfIp6tables bool
|
||||
Debug bool
|
||||
NFd int
|
||||
OomKillDisable bool
|
||||
@@ -206,24 +182,21 @@ type Info struct {
|
||||
KernelVersion string
|
||||
OperatingSystem string
|
||||
IndexServerAddress string
|
||||
RegistryConfig *registry.ServiceConfig
|
||||
RegistryConfig interface{}
|
||||
InitSha1 string
|
||||
InitPath string
|
||||
NCPU int
|
||||
MemTotal int64
|
||||
DockerRootDir string
|
||||
HTTPProxy string `json:"HttpProxy"`
|
||||
HTTPSProxy string `json:"HttpsProxy"`
|
||||
HttpProxy string
|
||||
HttpsProxy string
|
||||
NoProxy string
|
||||
Name string
|
||||
Labels []string
|
||||
ExperimentalBuild bool
|
||||
ServerVersion string
|
||||
ClusterStore string
|
||||
ClusterAdvertise 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
|
||||
type ExecStartCheck struct {
|
||||
// ExecStart will first check if it's detached
|
||||
@@ -232,10 +205,7 @@ type ExecStartCheck struct {
|
||||
Tty bool
|
||||
}
|
||||
|
||||
// ContainerState stores container's running state
|
||||
// it's part of ContainerJSONBase and will return by "inspect" command
|
||||
type ContainerState struct {
|
||||
Status string
|
||||
Running bool
|
||||
Paused bool
|
||||
Restarting bool
|
||||
@@ -248,15 +218,15 @@ type ContainerState struct {
|
||||
FinishedAt string
|
||||
}
|
||||
|
||||
// ContainerJSONBase contains response of Remote API:
|
||||
// GET "/containers/{name:.*}/json"
|
||||
type ContainerJSONBase struct {
|
||||
ID string `json:"Id"`
|
||||
Id string
|
||||
Created string
|
||||
Path string
|
||||
Args []string
|
||||
State *ContainerState
|
||||
Image string
|
||||
NetworkSettings *network.Settings
|
||||
ResolvConfPath string
|
||||
HostnamePath string
|
||||
HostsPath string
|
||||
@@ -271,50 +241,30 @@ type ContainerJSONBase struct {
|
||||
ExecIDs []string
|
||||
HostConfig *runconfig.HostConfig
|
||||
GraphDriver GraphDriverData
|
||||
SizeRw *int64 `json:",omitempty"`
|
||||
SizeRootFs *int64 `json:",omitempty"`
|
||||
}
|
||||
|
||||
// ContainerJSON is newly used struct along with MountPoint
|
||||
type ContainerJSON struct {
|
||||
*ContainerJSONBase
|
||||
Mounts []MountPoint
|
||||
Config *runconfig.Config
|
||||
NetworkSettings *NetworkSettings
|
||||
Mounts []MountPoint
|
||||
Config *runconfig.Config
|
||||
}
|
||||
|
||||
// NetworkSettings exposes the network settings in the api
|
||||
type NetworkSettings struct {
|
||||
NetworkSettingsBase
|
||||
DefaultNetworkSettings
|
||||
Networks map[string]*network.EndpointSettings
|
||||
// backcompatibility struct along with ContainerConfig
|
||||
type ContainerJSONPre120 struct {
|
||||
*ContainerJSONBase
|
||||
Volumes map[string]string
|
||||
VolumesRW map[string]bool
|
||||
Config *ContainerConfig
|
||||
}
|
||||
|
||||
// NetworkSettingsBase holds basic information about networks
|
||||
type NetworkSettingsBase struct {
|
||||
Bridge string
|
||||
SandboxID string
|
||||
HairpinMode bool
|
||||
LinkLocalIPv6Address string
|
||||
LinkLocalIPv6PrefixLen int
|
||||
Ports nat.PortMap
|
||||
SandboxKey string
|
||||
SecondaryIPAddresses []network.Address
|
||||
SecondaryIPv6Addresses []network.Address
|
||||
}
|
||||
type ContainerConfig struct {
|
||||
*runconfig.Config
|
||||
|
||||
// DefaultNetworkSettings holds network information
|
||||
// during the 2 release deprecation period.
|
||||
// It will be removed in Docker 1.11.
|
||||
type DefaultNetworkSettings struct {
|
||||
EndpointID string
|
||||
Gateway string
|
||||
GlobalIPv6Address string
|
||||
GlobalIPv6PrefixLen int
|
||||
IPAddress string
|
||||
IPPrefixLen int
|
||||
IPv6Gateway string
|
||||
MacAddress string
|
||||
// backward compatibility, they now live in HostConfig
|
||||
Memory int64
|
||||
MemorySwap int64
|
||||
CpuShares int64
|
||||
Cpuset string
|
||||
}
|
||||
|
||||
// MountPoint represents a mount point configuration inside the container.
|
||||
@@ -323,71 +273,6 @@ type MountPoint struct {
|
||||
Source string
|
||||
Destination string
|
||||
Driver string `json:",omitempty"`
|
||||
Mode string
|
||||
Mode string // this is internally named `Relabel`
|
||||
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/create"
|
||||
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
|
||||
ID string `json:"Id"`
|
||||
Scope string
|
||||
Driver string
|
||||
IPAM network.IPAM
|
||||
Containers map[string]EndpointResource
|
||||
Options map[string]string
|
||||
}
|
||||
|
||||
//EndpointResource contains network resources allocated and usd for a container in a network
|
||||
type EndpointResource struct {
|
||||
EndpointID string
|
||||
MacAddress string
|
||||
IPv4Address string
|
||||
IPv6Address string
|
||||
}
|
||||
|
||||
// NetworkCreate is the expected body of the "create network" http request message
|
||||
type NetworkCreate struct {
|
||||
Name string
|
||||
CheckDuplicate bool
|
||||
Driver string
|
||||
IPAM network.IPAM
|
||||
Options map[string]string
|
||||
}
|
||||
|
||||
// NetworkCreateResponse is the response message sent by the server for network create call
|
||||
type NetworkCreateResponse struct {
|
||||
ID string `json:"Id"`
|
||||
Warning string
|
||||
}
|
||||
|
||||
// NetworkConnect represents the data to be used to connect a container to the network
|
||||
type NetworkConnect struct {
|
||||
Container string
|
||||
}
|
||||
|
||||
// NetworkDisconnect represents the data to be used to disconnect a container from the network
|
||||
type NetworkDisconnect struct {
|
||||
Container string
|
||||
}
|
||||
|
||||
@@ -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,35 +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/api/types/versions/v1p20"
|
||||
"github.com/docker/docker/pkg/nat"
|
||||
"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
|
||||
NetworkSettings *v1p20.NetworkSettings
|
||||
}
|
||||
|
||||
// ContainerConfig is a backcompatibility struct for APIs prior to 1.20.
|
||||
type ContainerConfig struct {
|
||||
*runconfig.Config
|
||||
|
||||
MacAddress string
|
||||
NetworkDisabled bool
|
||||
ExposedPorts map[nat.Port]struct{}
|
||||
|
||||
// backward compatibility, they now live in HostConfig
|
||||
VolumeDriver string
|
||||
Memory int64
|
||||
MemorySwap int64
|
||||
CPUShares int64 `json:"CpuShares"`
|
||||
CPUSet string `json:"Cpuset"`
|
||||
}
|
||||
@@ -1,40 +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/pkg/nat"
|
||||
"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
|
||||
NetworkSettings *NetworkSettings
|
||||
}
|
||||
|
||||
// ContainerConfig is a backcompatibility struct used in ContainerJSON for the API 1.20
|
||||
type ContainerConfig struct {
|
||||
*runconfig.Config
|
||||
|
||||
MacAddress string
|
||||
NetworkDisabled bool
|
||||
ExposedPorts map[nat.Port]struct{}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// NetworkSettings is a backward compatible struct for APIs prior to 1.21
|
||||
type NetworkSettings struct {
|
||||
types.NetworkSettingsBase
|
||||
types.DefaultNetworkSettings
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package dockerfile
|
||||
package builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -38,7 +38,6 @@ func NewBFlags() *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 {
|
||||
flag := bf.addFlag(name, boolType)
|
||||
if flag == nil {
|
||||
@@ -53,7 +52,6 @@ func (bf *BFlags) AddBool(name string, def bool) *Flag {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
flag := bf.addFlag(name, stringType)
|
||||
if flag == nil {
|
||||
@@ -63,9 +61,6 @@ func (bf *BFlags) AddString(name string, def string) *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 {
|
||||
if _, ok := bf.flags[name]; ok {
|
||||
bf.Err = fmt.Errorf("Duplicate flag defined: %s", name)
|
||||
@@ -99,14 +94,7 @@ func (fl *Flag) IsTrue() bool {
|
||||
return fl.Value == "true"
|
||||
}
|
||||
|
||||
// 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.
|
||||
// Parse parses and checks if the BFlags is valid
|
||||
func (bf *BFlags) Parse() error {
|
||||
// If there was an error while defining the possible flags
|
||||
// go ahead and bubble it back up here since we didn't do it
|
||||
@@ -1,4 +1,4 @@
|
||||
package dockerfile
|
||||
package builder
|
||||
|
||||
import (
|
||||
"testing"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user