Compare commits
66 Commits
v18.02.0-c
...
v17.09.0-c
| Author | SHA1 | Date | |
|---|---|---|---|
| afdb6d44a8 | |||
| 3b0f381088 | |||
| 09d58a6cc0 | |||
| 8056485bad | |||
| 2357fb28b5 | |||
| 9661f00ed4 | |||
| db97c3db91 | |||
| fe19ba678a | |||
| d86b81fcce | |||
| 0a7586971f | |||
| 5fd57722d1 | |||
| 4856a9ec15 | |||
| 6a80ba3b8a | |||
| 508a2630a3 | |||
| 1ae6ae93c4 | |||
| ccdf90d524 | |||
| 79ca5680b8 | |||
| 1f4bf6c347 | |||
| 0f16d9b90a | |||
| a825d7bfdf | |||
| af4a1fc87e | |||
| a7f4500a2c | |||
| 5fb8e37ec7 | |||
| da6e49e1f7 | |||
| fc12f83f79 | |||
| 3d9fc736e9 | |||
| 390c0782bc | |||
| f238e40c68 | |||
| 2a4728f860 | |||
| 1ad05ae326 | |||
| 393d90ba3d | |||
| 2a3140fcb7 | |||
| 66a5055240 | |||
| 363a3e75cd | |||
| 22cec742b1 | |||
| 17db25d098 | |||
| 3e2dc53d19 | |||
| efac66c62b | |||
| 9e5777a749 | |||
| c6e21043d8 | |||
| 7034626ac6 | |||
| 1fa0e54ee7 | |||
| 5f31b291fb | |||
| 172695debf | |||
| 49f307c88f | |||
| e6067b8038 | |||
| 49c9ca25c0 | |||
| 21154792a5 | |||
| be7f90619c | |||
| 4231d771bb | |||
| b4a675c535 | |||
| dff328c8a6 | |||
| 2b763fa480 | |||
| 8227e4e739 | |||
| a0a9bbd3f2 | |||
| ae21824df8 | |||
| ec4c78a39b | |||
| aa56783035 | |||
| b33e8d49d4 | |||
| 5d1587e61e | |||
| 8574cd6b8a | |||
| 54ddbdea25 | |||
| efd69761fc | |||
| c973c430a9 | |||
| e3e65f6470 | |||
| f7ec4a69b3 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +0,0 @@
|
||||
.helpers/
|
||||
105
CHANGELOG.md
105
CHANGELOG.md
@ -1,71 +1,70 @@
|
||||
# Changelog
|
||||
|
||||
For more information on the list of deprecated flags and APIs, have a look at
|
||||
https://docs.docker.com/engine/deprecated/ where you can find target removal dates.
|
||||
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/engine/deprecated/ where target removal dates can also
|
||||
be found.
|
||||
|
||||
## 18.02.0-ce (2018-02-07)
|
||||
## 17.09.0-ce (2017-09-26)
|
||||
|
||||
### Builder
|
||||
|
||||
- Gitutils: fix checking out submodules [moby/moby#35737](https://github.com/moby/moby/pull/35737)
|
||||
+ Add `--chown` flag to `ADD/COPY` commands in Dockerfile [moby/moby#34263](https://github.com/moby/moby/pull/34263)
|
||||
* Fix cloning unneeded files while building from git repositories [moby/moby#33704](https://github.com/moby/moby/pull/33704)
|
||||
|
||||
### Client
|
||||
|
||||
* Attach: Ensure attach exit code matches container's [docker/cli#696](https://github.com/docker/cli/pull/696)
|
||||
+ Added support for tmpfs-mode in compose file [docker/cli#808](https://github.com/docker/cli/pull/808)
|
||||
+ Adds a new compose file version 3.6 [docker/cli#808](https://github.com/docker/cli/pull/808)
|
||||
- Fix issue of filter in `docker ps` where `health=starting` returns nothing [moby/moby#35940](https://github.com/moby/moby/pull/35940)
|
||||
+ Improve presentation of published port ranges [docker/cli#581](https://github.com/docker/cli/pull/581)
|
||||
* Bump Go to 1.9.3 [docker/cli#827](https://github.com/docker/cli/pull/827)
|
||||
- Fix broken Kubernetes stack flags [docker/cli#831](https://github.com/docker/cli/pull/831)
|
||||
* Annotate "stack" commands to be "swarm" and "kubernetes" [docker/cli#804](https://github.com/docker/cli/pull/804)
|
||||
|
||||
### Experimental
|
||||
|
||||
+ Add manifest command [docker/cli#138](https://github.com/docker/cli/pull/138)
|
||||
* LCOW remotefs - return error in Read() implementation [moby/moby#36051](https://github.com/moby/moby/pull/36051)
|
||||
+ LCOW: Coalesce daemon stores, allow dual LCOW and WCOW mode [moby/moby#34859](https://github.com/moby/moby/pull/34859)
|
||||
- LCOW: Fix OpenFile parameters [moby/moby#36043](https://github.com/moby/moby/pull/36043)
|
||||
* LCOW: Raise minimum requirement to Windows RS3 RTM build (16299) [moby/moby#36065](https://github.com/moby/moby/pull/36065)
|
||||
* Allow extension fields in the v3.4 version of the compose format [docker/cli#452](https://github.com/docker/cli/pull/452)
|
||||
* Make compose file allow to specify names for non-external volume [docker/cli#306](https://github.com/docker/cli/pull/306)
|
||||
* Support `--compose-file -` as stdin [docker/cli#347](https://github.com/docker/cli/pull/347)
|
||||
* Support `start_period` for healthcheck in Docker Compose [docker/cli#475](https://github.com/docker/cli/pull/475)
|
||||
+ Add support for `stop-signal` in docker stack commands [docker/cli#388](https://github.com/docker/cli/pull/388)
|
||||
+ Add support for update order in compose deployments [docker/cli#360](https://github.com/docker/cli/pull/360)
|
||||
+ Add ulimits to unsupported compose fields [docker/cli#482](https://github.com/docker/cli/pull/482)
|
||||
+ Add `--format` to `docker-search` [docker/cli#440](https://github.com/docker/cli/pull/440)
|
||||
* Show images digests when `{{.Digest}}` is in format [docker/cli#439](https://github.com/docker/cli/pull/439)
|
||||
* Print output of `docker stack rm` on `stdout` instead of `stderr` [docker/cli#491](https://github.com/docker/cli/pull/491)
|
||||
- Fix `docker history --format '{{json .}}'` printing human-readable timestamps instead of ISO8601 when `--human=true` [docker/cli#438](https://github.com/docker/cli/pull/438)
|
||||
- Fix idempotence of `docker stack deploy` when secrets or configs are used [docker/cli#509](https://github.com/docker/cli/pull/509)
|
||||
- Fix presentation of random host ports [docker/cli#404](https://github.com/docker/cli/pull/404)
|
||||
- Fix redundant service restarts when service created with multiple secrets [moby/moby#34746](https://github.com/moby/moby/issues/34746)
|
||||
|
||||
### Logging
|
||||
|
||||
* Improve daemon config reload; log active configuration [moby/moby#36019](https://github.com/moby/moby/pull/36019)
|
||||
- Fixed error detection using IsErrNotFound and IsErrNotImplemented for the ContainerLogs method [moby/moby#36000](https://github.com/moby/moby/pull/36000)
|
||||
+ Add journald tag as SYSLOG_IDENTIFIER [moby/moby#35570](https://github.com/moby/moby/pull/35570)
|
||||
* Splunk: limit the reader size on error responses [moby/moby#35509](https://github.com/moby/moby/pull/35509)
|
||||
|
||||
- Fix Splunk logger not transmitting log data when tag is empty and raw-mode is used [moby/moby#34520](https://github.com/moby/moby/pull/34520)
|
||||
|
||||
### Networking
|
||||
|
||||
* Disable service on release network results in zero-downtime deployments with rolling upgrades [moby/moby#35960](https://github.com/moby/moby/pull/35960)
|
||||
- Fix services failing to start if multiple networks with the same name exist in different spaces [moby/moby#30897](https://github.com/moby/moby/pull/30897)
|
||||
- Fix duplicate networks being added with `docker service update --network-add` [docker/cli#780](https://github.com/docker/cli/pull/780)
|
||||
- Fixing ingress network when upgrading from 17.09 to 17.12. [moby/moby#36003](https://github.com/moby/moby/pull/36003)
|
||||
- Fix ndots configuration [docker/libnetwork#1995](https://github.com/docker/libnetwork/pull/1995)
|
||||
- Fix IPV6 networking being deconfigured if live-restore is enabled [docker/libnetwork#2043](https://github.com/docker/libnetwork/pull/2043)
|
||||
+ Add support for MX type DNS queries in the embedded DNS server [docker/libnetwork#2041](https://github.com/docker/libnetwork/pull/2041)
|
||||
|
||||
### Packaging
|
||||
|
||||
+ Added packaging for Fedora 26, Fedora 27, and Centos 7 on aarch64 [docker/docker-ce-packaging#71](https://github.com/docker/docker-ce-packaging/pull/71)
|
||||
- Removed support for Ubuntu Zesty [docker/docker-ce-packaging#73](https://github.com/docker/docker-ce-packaging/pull/73)
|
||||
- Removed support for Fedora 25 [docker/docker-ce-packaging#72](https://github.com/docker/docker-ce-packaging/pull/72)
|
||||
|
||||
+ Add the control plane MTU option in the daemon config [moby/moby#34103](https://github.com/moby/moby/pull/34103)
|
||||
+ Add service virtual IP to sandbox's loopback address [docker/libnetwork#1877](https://github.com/docker/libnetwork/pull/1877)
|
||||
|
||||
### Runtime
|
||||
|
||||
- Fixes unexpected Docker Daemon shutdown based on pipe error [moby/moby#35968](https://github.com/moby/moby/pull/35968)
|
||||
- Fix some occurrences of hcsshim::ImportLayer failed in Win32: The system cannot find the path specified [moby/moby#35924](https://github.com/moby/moby/pull/35924)
|
||||
* Windows: increase the maximum layer size during build to 127GB [moby/moby#35925](https://github.com/moby/moby/pull/35925)
|
||||
- Fix Devicemapper: Error running DeleteDevice dm_task_run failed [moby/moby#35919](https://github.com/moby/moby/pull/35919)
|
||||
+ Introduce « exec_die » event [moby/moby#35744](https://github.com/moby/moby/pull/35744)
|
||||
* Update API to version 1.36 [moby/moby#35744](https://github.com/moby/moby/pull/35744)
|
||||
- Fix `docker update` not updating cpu quota, and cpu-period of a running container [moby/moby#36030](https://github.com/moby/moby/pull/36030)
|
||||
* Make container shm parent unbindable [moby/moby#35830](https://github.com/moby/moby/pull/35830)
|
||||
+ Make image (layer) downloads faster by using pigz [moby/moby#35697](https://github.com/moby/moby/pull/35697)
|
||||
+ Protect the daemon from volume plugins that are slow or deadlocked [moby/moby#35441](https://github.com/moby/moby/pull/35441)
|
||||
- Fix `DOCKER_RAMDISK` environment variable not being honoured [moby/moby#35957](https://github.com/moby/moby/pull/35957)
|
||||
* Bump containerd to 1.0.1 (9b55aab90508bd389d7654c4baf173a981477d55) [moby/moby#35986](https://github.com/moby/moby/pull/35986)
|
||||
* Update runc to fix hang during start and exec [moby/moby#36097](https://github.com/moby/moby/pull/36097)
|
||||
- Fix "--node-generic-resource" singular/plural [moby/moby#36125](https://github.com/moby/moby/pull/36125)
|
||||
* Graphdriver: promote overlay2 over aufs [moby/moby#34430](https://github.com/moby/moby/pull/34430)
|
||||
* LCOW: Additional flags for VHD boot [moby/moby#34451](https://github.com/moby/moby/pull/34451)
|
||||
* LCOW: Don't block export [moby/moby#34448](https://github.com/moby/moby/pull/34448)
|
||||
* LCOW: Dynamic sandbox management [moby/moby#34170](https://github.com/moby/moby/pull/34170)
|
||||
* LCOW: Force Hyper-V Isolation [moby/moby#34468](https://github.com/moby/moby/pull/34468)
|
||||
* LCOW: Move toolsScratchPath to /tmp [moby/moby#34396](https://github.com/moby/moby/pull/34396)
|
||||
* LCOW: Remove hard-coding [moby/moby#34398](https://github.com/moby/moby/pull/34398)
|
||||
* LCOW: WORKDIR correct handling [moby/moby#34405](https://github.com/moby/moby/pull/34405)
|
||||
* Windows: named pipe mounts [moby/moby#33852](https://github.com/moby/moby/pull/33852)
|
||||
- Fix "permission denied" errors when accessing volume with SELinux enforcing mode [moby/moby#34684](https://github.com/moby/moby/pull/34684)
|
||||
- Fix layers size reported as `0` in `docker system df` [moby/moby#34826](https://github.com/moby/moby/pull/34826)
|
||||
- Fix some "device or resource busy" errors when removing containers on RHEL 7.4 based kernels [moby/moby#34886](https://github.com/moby/moby/pull/34886)
|
||||
|
||||
### Swarm mode
|
||||
|
||||
* Include whether the managers in the swarm are autolocked as part of `docker info` [docker/cli#471](https://github.com/docker/cli/pull/471)
|
||||
+ Add 'docker service rollback' subcommand [docker/cli#205](https://github.com/docker/cli/pull/205)
|
||||
- Fix managers failing to join if the gRPC snapshot is larger than 4MB [docker/swarmkit#2375](https://github.com/docker/swarmkit/pull/2375)
|
||||
- Fix "permission denied" errors for configuration file in SELinux-enabled containers [moby/moby#34732](https://github.com/moby/moby/pull/34732)
|
||||
- Fix services failing to deploy on ARM nodes [moby/moby#34021](https://github.com/moby/moby/pull/34021)
|
||||
|
||||
### Packaging
|
||||
|
||||
+ Build scripts for ppc64el on Ubuntu [docker/docker-ce-packaging#43](https://github.com/docker/docker-ce-packaging/pull/43)
|
||||
|
||||
### Deprecation
|
||||
|
||||
+ Remove deprecated `--enable-api-cors` daemon flag [moby/moby#34821](https://github.com/moby/moby/pull/34821)
|
||||
|
||||
29
Makefile
29
Makefile
@ -1,56 +1,27 @@
|
||||
CLI_DIR:=$(CURDIR)/components/cli
|
||||
ENGINE_DIR:=$(CURDIR)/components/engine
|
||||
PACKAGING_DIR:=$(CURDIR)/components/packaging
|
||||
MOBY_COMPONENTS_SHA=ab7c118272b02d8672dc0255561d0c4015979780
|
||||
MOBY_COMPONENTS_URL=https://raw.githubusercontent.com/shykes/moby-extras/$(MOBY_COMPONENTS_SHA)/cmd/moby-components
|
||||
MOBY_COMPONENTS=.helpers/moby-components-$(MOBY_COMPONENTS_SHA)
|
||||
VERSION=$(shell cat VERSION)
|
||||
|
||||
.PHONY: help
|
||||
help: ## show make targets
|
||||
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {sub("\\\\n",sprintf("\n%22c"," "), $$2);printf " \033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)
|
||||
|
||||
.PHONY: test-integration-cli
|
||||
test-integration-cli: $(CLI_DIR)/build/docker ## test integration of cli and engine
|
||||
$(MAKE) -C $(ENGINE_DIR) DOCKER_CLI_PATH=$< test-integration-cli
|
||||
|
||||
$(CLI_DIR)/build/docker:
|
||||
$(MAKE) -C $(CLI_DIR) -f docker.Makefile build
|
||||
|
||||
.PHONY: deb
|
||||
deb: ## build deb packages
|
||||
$(MAKE) VERSION=$(VERSION) CLI_DIR=$(CLI_DIR) ENGINE_DIR=$(ENGINE_DIR) -C $(PACKAGING_DIR) deb
|
||||
|
||||
.PHONY: rpm
|
||||
rpm: ## build rpm packages
|
||||
$(MAKE) VERSION=$(VERSION) CLI_DIR=$(CLI_DIR) ENGINE_DIR=$(ENGINE_DIR) -C $(PACKAGING_DIR) rpm
|
||||
|
||||
.PHONY: static
|
||||
static: ## build static packages
|
||||
$(MAKE) VERSION=$(VERSION) CLI_DIR=$(CLI_DIR) ENGINE_DIR=$(ENGINE_DIR) -C $(PACKAGING_DIR) static
|
||||
|
||||
.PHONY: clean
|
||||
clean: ## clean the build artifacts
|
||||
-$(MAKE) -C $(CLI_DIR) clean
|
||||
-$(MAKE) -C $(ENGINE_DIR) clean
|
||||
-$(MAKE) -C $(PACKAGING_DIR) clean
|
||||
|
||||
$(MOBY_COMPONENTS):
|
||||
mkdir -p .helpers
|
||||
curl -fsSL $(MOBY_COMPONENTS_URL) > $(MOBY_COMPONENTS)
|
||||
chmod +x $(MOBY_COMPONENTS)
|
||||
|
||||
.PHONY: update-components
|
||||
update-components: update-components-cli update-components-engine update-components-packaging ## udpate components using moby extra tool
|
||||
|
||||
.PHONY: update-components-cli
|
||||
update-components-cli: $(MOBY_COMPONENTS)
|
||||
$(MOBY_COMPONENTS) update cli
|
||||
|
||||
.PHONY: update-components-engine
|
||||
update-components-engine: $(MOBY_COMPONENTS)
|
||||
$(MOBY_COMPONENTS) update engine
|
||||
|
||||
.PHONY: update-components-packaging
|
||||
update-components-packaging: $(MOBY_COMPONENTS)
|
||||
$(MOBY_COMPONENTS) update packaging
|
||||
|
||||
4
components/cli/.github/CODEOWNERS
vendored
4
components/cli/.github/CODEOWNERS
vendored
@ -1,9 +1,9 @@
|
||||
# GitHub code owners
|
||||
# Github code owners
|
||||
# See https://github.com/blog/2392-introducing-code-owners
|
||||
|
||||
cli/command/stack/** @dnephin @vdemeester
|
||||
cli/compose/** @dnephin @vdemeester
|
||||
contrib/completion/bash/** @albers
|
||||
contrib/completion/zsh/** @sdurrheimer
|
||||
docs/** @mistyhacks @vdemeester @thaJeztah
|
||||
docs/** @mstanleyjones @vdemeester @thaJeztah
|
||||
scripts/** @dnephin
|
||||
|
||||
@ -1,472 +0,0 @@
|
||||
# Generate AUTHORS: scripts/docs/generate-authors.sh
|
||||
|
||||
# Tip for finding duplicates (besides scanning the output of AUTHORS for name
|
||||
# duplicates that aren't also email duplicates): scan the output of:
|
||||
# git log --format='%aE - %aN' | sort -uf
|
||||
#
|
||||
# For explanation on this file format: man git-shortlog
|
||||
|
||||
Aaron L. Xu <liker.xu@foxmail.com>
|
||||
Abhinandan Prativadi <abhi@docker.com>
|
||||
Adrien Gallouët <adrien@gallouet.fr> <angt@users.noreply.github.com>
|
||||
Ahmed Kamal <email.ahmedkamal@googlemail.com>
|
||||
Ahmet Alp Balkan <ahmetb@microsoft.com> <ahmetalpbalkan@gmail.com>
|
||||
AJ Bowen <aj@gandi.net>
|
||||
AJ Bowen <aj@gandi.net> <amy@gandi.net>
|
||||
Akihiro Matsushima <amatsusbit@gmail.com> <amatsus@users.noreply.github.com>
|
||||
Akihiro Suda <suda.akihiro@lab.ntt.co.jp> <suda.kyoto@gmail.com>
|
||||
Aleksa Sarai <asarai@suse.de>
|
||||
Aleksa Sarai <asarai@suse.de> <asarai@suse.com>
|
||||
Aleksa Sarai <asarai@suse.de> <cyphar@cyphar.com>
|
||||
Aleksandrs Fadins <aleks@s-ko.net>
|
||||
Alessandro Boch <aboch@tetrationanalytics.com> <aboch@docker.com>
|
||||
Alex Chen <alexchenunix@gmail.com> <root@localhost.localdomain>
|
||||
Alex Ellis <alexellis2@gmail.com>
|
||||
Alexander Larsson <alexl@redhat.com> <alexander.larsson@gmail.com>
|
||||
Alexander Morozov <lk4d4@docker.com>
|
||||
Alexander Morozov <lk4d4@docker.com> <lk4d4math@gmail.com>
|
||||
Alexandre Beslic <alexandre.beslic@gmail.com> <abronan@docker.com>
|
||||
Alicia Lauerman <alicia@eta.im> <allydevour@me.com>
|
||||
Allen Sun <allensun.shl@alibaba-inc.com> <allen.sun@daocloud.io>
|
||||
Allen Sun <allensun.shl@alibaba-inc.com> <shlallen1990@gmail.com>
|
||||
Andrew Weiss <andrew.weiss@docker.com> <andrew.weiss@microsoft.com>
|
||||
Andrew Weiss <andrew.weiss@docker.com> <andrew.weiss@outlook.com>
|
||||
André Martins <aanm90@gmail.com> <martins@noironetworks.com>
|
||||
Andy Rothfusz <github@developersupport.net> <github@metaliveblog.com>
|
||||
Andy Smith <github@anarkystic.com>
|
||||
Ankush Agarwal <ankushagarwal11@gmail.com> <ankushagarwal@users.noreply.github.com>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com> <amurdaca@redhat.com>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com> <me@runcom.ninja>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@linux.com>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@redhat.com>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com> <runcom@users.noreply.github.com>
|
||||
Anuj Bahuguna <anujbahuguna.dev@gmail.com>
|
||||
Anuj Bahuguna <anujbahuguna.dev@gmail.com> <abahuguna@fiberlink.com>
|
||||
Anusha Ragunathan <anusha.ragunathan@docker.com> <anusha@docker.com>
|
||||
Arnaud Porterie <arnaud.porterie@docker.com>
|
||||
Arnaud Porterie <arnaud.porterie@docker.com> <icecrime@gmail.com>
|
||||
Arthur Gautier <baloo@gandi.net> <superbaloo+registrations.github@superbaloo.net>
|
||||
Avi Miller <avi.miller@oracle.com> <avi.miller@gmail.com>
|
||||
Ben Bonnefoy <frenchben@docker.com>
|
||||
Ben Golub <ben.golub@dotcloud.com>
|
||||
Ben Toews <mastahyeti@gmail.com> <mastahyeti@users.noreply.github.com>
|
||||
Benoit Chesneau <bchesneau@gmail.com>
|
||||
Bhiraj Butala <abhiraj.butala@gmail.com>
|
||||
Bhumika Bayani <bhumikabayani@gmail.com>
|
||||
Bilal Amarni <bilal.amarni@gmail.com> <bamarni@users.noreply.github.com>
|
||||
Bill Wang <ozbillwang@gmail.com> <SydOps@users.noreply.github.com>
|
||||
Bin Liu <liubin0329@gmail.com>
|
||||
Bin Liu <liubin0329@gmail.com> <liubin0329@users.noreply.github.com>
|
||||
Bingshen Wang <bingshen.wbs@alibaba-inc.com>
|
||||
Boaz Shuster <ripcurld.github@gmail.com>
|
||||
Brandon Philips <brandon.philips@coreos.com> <brandon@ifup.co>
|
||||
Brandon Philips <brandon.philips@coreos.com> <brandon@ifup.org>
|
||||
Brent Salisbury <brent.salisbury@docker.com> <brent@docker.com>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
Brian Goff <cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.home>
|
||||
Brian Goff <cpuguy83@gmail.com> <bgoff@cpuguy83-mbp.local>
|
||||
Chander Govindarajan <chandergovind@gmail.com>
|
||||
Chao Wang <wangchao.fnst@cn.fujitsu.com> <chaowang@localhost.localdomain>
|
||||
Charles Hooper <charles.hooper@dotcloud.com> <chooper@plumata.com>
|
||||
Chen Chao <cc272309126@gmail.com>
|
||||
Chen Chuanliang <chen.chuanliang@zte.com.cn>
|
||||
Chen Mingjie <chenmingjie0828@163.com>
|
||||
Chen Qiu <cheney-90@hotmail.com>
|
||||
Chen Qiu <cheney-90@hotmail.com> <21321229@zju.edu.cn>
|
||||
Chris Dias <cdias@microsoft.com>
|
||||
Chris McKinnel <chris.mckinnel@tangentlabs.co.uk>
|
||||
Christopher Biscardi <biscarch@sketcht.com>
|
||||
Christopher Latham <sudosurootdev@gmail.com>
|
||||
Chun Chen <ramichen@tencent.com> <chenchun.feed@gmail.com>
|
||||
Corbin Coleman <corbin.coleman@docker.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com> <unclejack@users.noreply.github.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com> <unclejacksons@gmail.com>
|
||||
CUI Wei <ghostplant@qq.com> cuiwei13 <cuiwei13@pku.edu.cn>
|
||||
Daehyeok Mun <daehyeok@gmail.com>
|
||||
Daehyeok Mun <daehyeok@gmail.com> <daehyeok@daehyeok-ui-MacBook-Air.local>
|
||||
Daehyeok Mun <daehyeok@gmail.com> <daehyeok@daehyeokui-MacBook-Air.local>
|
||||
Dan Feldman <danf@jfrog.com>
|
||||
Daniel Dao <dqminh@cloudflare.com>
|
||||
Daniel Dao <dqminh@cloudflare.com> <dqminh89@gmail.com>
|
||||
Daniel Garcia <daniel@danielgarcia.info>
|
||||
Daniel Gasienica <daniel@gasienica.ch> <dgasienica@zynga.com>
|
||||
Daniel Goosen <daniel.goosen@surveysampling.com> <djgoosen@users.noreply.github.com>
|
||||
Daniel Grunwell <mwgrunny@gmail.com>
|
||||
Daniel J Walsh <dwalsh@redhat.com>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> <daniel@dotcloud.com>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> <mzdaniel@glidelink.net>
|
||||
Daniel Mizyrycki <daniel.mizyrycki@dotcloud.com> <root@vagrant-ubuntu-12.10.vagrantup.com>
|
||||
Daniel Nephin <dnephin@docker.com> <dnephin@gmail.com>
|
||||
Daniel Norberg <dano@spotify.com> <daniel.norberg@gmail.com>
|
||||
Daniel Watkins <daniel@daniel-watkins.co.uk>
|
||||
Danny Yates <danny@codeaholics.org> <Danny.Yates@mailonline.co.uk>
|
||||
Darren Shepherd <darren.s.shepherd@gmail.com> <darren@rancher.com>
|
||||
Dattatraya Kumbhar <dattatraya.kumbhar@gslab.com>
|
||||
Dave Goodchild <buddhamagnet@gmail.com>
|
||||
Dave Henderson <dhenderson@gmail.com> <Dave.Henderson@ca.ibm.com>
|
||||
Dave Tucker <dt@docker.com> <dave@dtucker.co.uk>
|
||||
David M. Karr <davidmichaelkarr@gmail.com>
|
||||
David Sheets <dsheets@docker.com> <sheets@alum.mit.edu>
|
||||
David Sissitka <me@dsissitka.com>
|
||||
David Williamson <david.williamson@docker.com> <davidwilliamson@users.noreply.github.com>
|
||||
Deshi Xiao <dxiao@redhat.com> <dsxiao@dataman-inc.com>
|
||||
Deshi Xiao <dxiao@redhat.com> <xiaods@gmail.com>
|
||||
Diego Siqueira <dieg0@live.com>
|
||||
Diogo Monica <diogo@docker.com> <diogo.monica@gmail.com>
|
||||
Dominik Honnef <dominik@honnef.co> <dominikh@fork-bomb.org>
|
||||
Doug Davis <dug@us.ibm.com> <duglin@users.noreply.github.com>
|
||||
Doug Tangren <d.tangren@gmail.com>
|
||||
Elan Ruusamäe <glen@pld-linux.org>
|
||||
Elan Ruusamäe <glen@pld-linux.org> <glen@delfi.ee>
|
||||
Eric G. Noriega <enoriega@vizuri.com> <egnoriega@users.noreply.github.com>
|
||||
Eric Hanchrow <ehanchrow@ine.com> <eric.hanchrow@gmail.com>
|
||||
Eric Rosenberg <ehaydenr@gmail.com> <ehaydenr@users.noreply.github.com>
|
||||
Erica Windisch <erica@windisch.us> <eric@windisch.us>
|
||||
Erica Windisch <erica@windisch.us> <ewindisch@docker.com>
|
||||
Erik Hollensbe <github@hollensbe.org> <erik+github@hollensbe.org>
|
||||
Erwin van der Koogh <info@erronis.nl>
|
||||
Euan Kemp <euan.kemp@coreos.com> <euank@amazon.com>
|
||||
Eugen Krizo <eugen.krizo@gmail.com>
|
||||
Evan Hazlett <ejhazlett@gmail.com> <ehazlett@users.noreply.github.com>
|
||||
Evelyn Xu <evelynhsu21@gmail.com>
|
||||
Evgeny Shmarnev <shmarnev@gmail.com>
|
||||
Faiz Khan <faizkhan00@gmail.com>
|
||||
Felix Hupfeld <felix@quobyte.com> <quofelix@users.noreply.github.com>
|
||||
Felix Ruess <felix.ruess@gmail.com> <felix.ruess@roboception.de>
|
||||
Feng Yan <fy2462@gmail.com>
|
||||
Fengtu Wang <wangfengtu@huawei.com> <wangfengtu@huawei.com>
|
||||
Francisco Carriedo <fcarriedo@gmail.com>
|
||||
Frank Rosquin <frank.rosquin+github@gmail.com> <frank.rosquin@gmail.com>
|
||||
Frederick F. Kautz IV <fkautz@redhat.com> <fkautz@alumni.cmu.edu>
|
||||
Gabriel Nicolas Avellaneda <avellaneda.gabriel@gmail.com>
|
||||
Gaetan de Villele <gdevillele@gmail.com>
|
||||
Gang Qiao <qiaohai8866@gmail.com> <1373319223@qq.com>
|
||||
George Kontridze <george@bugsnag.com>
|
||||
Gerwim Feiken <g.feiken@tfe.nl> <gerwim@gmail.com>
|
||||
Giampaolo Mancini <giampaolo@trampolineup.com>
|
||||
Gopikannan Venugopalsamy <gopikannan.venugopalsamy@gmail.com>
|
||||
Gou Rao <gou@portworx.com> <gourao@users.noreply.github.com>
|
||||
Greg Stephens <greg@udon.org>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <charmes.guillaume@gmail.com>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume.charmes@dotcloud.com>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume@charmes.net>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume@docker.com>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com> <guillaume@dotcloud.com>
|
||||
Gurjeet Singh <gurjeet@singh.im> <singh.gurjeet@gmail.com>
|
||||
Gustav Sinder <gustav.sinder@gmail.com>
|
||||
Günther Jungbluth <gunther@gameslabs.net>
|
||||
Hakan Özler <hakan.ozler@kodcu.com>
|
||||
Hao Shu Wei <haosw@cn.ibm.com>
|
||||
Hao Shu Wei <haosw@cn.ibm.com> <haoshuwei1989@163.com>
|
||||
Harald Albers <github@albersweb.de> <albers@users.noreply.github.com>
|
||||
Harold Cooper <hrldcpr@gmail.com>
|
||||
Harry Zhang <harryz@hyper.sh> <harryzhang@zju.edu.cn>
|
||||
Harry Zhang <harryz@hyper.sh> <resouer@163.com>
|
||||
Harry Zhang <harryz@hyper.sh> <resouer@gmail.com>
|
||||
Harry Zhang <resouer@163.com>
|
||||
Harshal Patil <harshal.patil@in.ibm.com> <harche@users.noreply.github.com>
|
||||
Helen Xie <chenjg@harmonycloud.cn>
|
||||
Hollie Teal <hollie@docker.com>
|
||||
Hollie Teal <hollie@docker.com> <hollie.teal@docker.com>
|
||||
Hollie Teal <hollie@docker.com> <hollietealok@users.noreply.github.com>
|
||||
Hu Keping <hukeping@huawei.com>
|
||||
Huu Nguyen <huu@prismskylabs.com> <whoshuu@gmail.com>
|
||||
Hyzhou Zhy <hyzhou.zhy@alibaba-inc.com>
|
||||
Hyzhou Zhy <hyzhou.zhy@alibaba-inc.com> <1187766782@qq.com>
|
||||
Ilya Khlopotov <ilya.khlopotov@gmail.com>
|
||||
Jack Laxson <jackjrabbit@gmail.com>
|
||||
Jacob Atzen <jacob@jacobatzen.dk> <jatzen@gmail.com>
|
||||
Jacob Tomlinson <jacob@tom.linson.uk> <jacobtomlinson@users.noreply.github.com>
|
||||
Jaivish Kothari <janonymous.codevulture@gmail.com>
|
||||
Jamie Hannaford <jamie@limetree.org> <jamie.hannaford@rackspace.com>
|
||||
Jean-Baptiste Barth <jeanbaptiste.barth@gmail.com>
|
||||
Jean-Baptiste Dalido <jeanbaptiste@appgratis.com>
|
||||
Jean-Tiare Le Bigot <jt@yadutaf.fr> <admin@jtlebi.fr>
|
||||
Jeff Anderson <jeff@docker.com> <jefferya@programmerq.net>
|
||||
Jeff Nickoloff <jeff.nickoloff@gmail.com> <jeff@allingeek.com>
|
||||
Jeroen Franse <jeroenfranse@gmail.com>
|
||||
Jessica Frazelle <jessfraz@google.com>
|
||||
Jessica Frazelle <jessfraz@google.com> <acidburn@docker.com>
|
||||
Jessica Frazelle <jessfraz@google.com> <acidburn@google.com>
|
||||
Jessica Frazelle <jessfraz@google.com> <jess@docker.com>
|
||||
Jessica Frazelle <jessfraz@google.com> <jess@mesosphere.com>
|
||||
Jessica Frazelle <jessfraz@google.com> <jfrazelle@users.noreply.github.com>
|
||||
Jessica Frazelle <jessfraz@google.com> <me@jessfraz.com>
|
||||
Jessica Frazelle <jessfraz@google.com> <princess@docker.com>
|
||||
Jim Galasyn <jim.galasyn@docker.com>
|
||||
Jiuyue Ma <majiuyue@huawei.com>
|
||||
Joey Geiger <jgeiger@gmail.com>
|
||||
Joffrey F <joffrey@docker.com>
|
||||
Joffrey F <joffrey@docker.com> <f.joffrey@gmail.com>
|
||||
Joffrey F <joffrey@docker.com> <joffrey@dotcloud.com>
|
||||
Johan Euphrosine <proppy@google.com> <proppy@aminche.com>
|
||||
John Harris <john@johnharris.io>
|
||||
John Howard (VM) <John.Howard@microsoft.com>
|
||||
John Howard (VM) <John.Howard@microsoft.com> <jhoward@microsoft.com>
|
||||
John Howard (VM) <John.Howard@microsoft.com> <jhoward@ntdev.microsoft.com>
|
||||
John Howard (VM) <John.Howard@microsoft.com> <jhowardmsft@users.noreply.github.com>
|
||||
John Howard (VM) <John.Howard@microsoft.com> <john.howard@microsoft.com>
|
||||
John Stephens <johnstep@docker.com> <johnstep@users.noreply.github.com>
|
||||
Jordan Arentsen <blissdev@gmail.com>
|
||||
Jordan Jennings <jjn2009@gmail.com> <jjn2009@users.noreply.github.com>
|
||||
Jorit Kleine-Möllhoff <joppich@bricknet.de> <joppich@users.noreply.github.com>
|
||||
Jose Diaz-Gonzalez <jose@seatgeek.com> <josegonzalez@users.noreply.github.com>
|
||||
Josh Eveleth <joshe@opendns.com> <jeveleth@users.noreply.github.com>
|
||||
Josh Hawn <josh.hawn@docker.com> <jlhawn@berkeley.edu>
|
||||
Josh Horwitz <horwitz@addthis.com> <horwitzja@gmail.com>
|
||||
Josh Soref <jsoref@gmail.com> <jsoref@users.noreply.github.com>
|
||||
Josh Wilson <josh.wilson@fivestars.com> <jcwilson@users.noreply.github.com>
|
||||
Joyce Jang <mail@joycejang.com>
|
||||
Julien Bordellier <julienbordellier@gmail.com> <git@julienbordellier.com>
|
||||
Julien Bordellier <julienbordellier@gmail.com> <me@julienbordellier.com>
|
||||
Justin Cormack <justin.cormack@docker.com>
|
||||
Justin Cormack <justin.cormack@docker.com> <justin.cormack@unikernel.com>
|
||||
Justin Cormack <justin.cormack@docker.com> <justin@specialbusservice.com>
|
||||
Justin Simonelis <justin.p.simonelis@gmail.com> <justin.simonelis@PTS-JSIMON2.toronto.exclamation.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@docker.com> <jerome.petazzoni@dotcloud.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@docker.com> <jerome.petazzoni@gmail.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@docker.com> <jp@enix.org>
|
||||
K. Heller <pestophagous@gmail.com> <pestophagous@users.noreply.github.com>
|
||||
Kai Qiang Wu (Kennan) <wkq5325@gmail.com>
|
||||
Kai Qiang Wu (Kennan) <wkq5325@gmail.com> <wkqwu@cn.ibm.com>
|
||||
Kamil Domański <kamil@domanski.co>
|
||||
Kamjar Gerami <kami.gerami@gmail.com>
|
||||
Ken Cochrane <kencochrane@gmail.com> <KenCochrane@gmail.com>
|
||||
Ken Herner <kherner@progress.com> <chosenken@gmail.com>
|
||||
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
|
||||
Kevin Feyrer <kevin.feyrer@btinternet.com> <kevinfeyrer@users.noreply.github.com>
|
||||
Kevin Kern <kaiwentan@harmonycloud.cn>
|
||||
Kevin Meredith <kevin.m.meredith@gmail.com>
|
||||
Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
Kir Kolyshkin <kolyshkin@gmail.com> <kir@openvz.org>
|
||||
Kir Kolyshkin <kolyshkin@gmail.com> <kolyshkin@users.noreply.github.com>
|
||||
Konrad Kleine <konrad.wilhelm.kleine@gmail.com> <kwk@users.noreply.github.com>
|
||||
Konstantin Gribov <grossws@gmail.com>
|
||||
Konstantin Pelykh <kpelykh@zettaset.com>
|
||||
Kotaro Yoshimatsu <kotaro.yoshimatsu@gmail.com>
|
||||
Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp> <kunal.kushwaha@gmail.com>
|
||||
Lajos Papp <lajos.papp@sequenceiq.com> <lalyos@yahoo.com>
|
||||
Lei Jitang <leijitang@huawei.com>
|
||||
Lei Jitang <leijitang@huawei.com> <leijitang@gmail.com>
|
||||
Liang Mingqiang <mqliang.zju@gmail.com>
|
||||
Liang-Chi Hsieh <viirya@gmail.com>
|
||||
Liao Qingwei <liaoqingwei@huawei.com>
|
||||
Linus Heckemann <lheckemann@twig-world.com>
|
||||
Linus Heckemann <lheckemann@twig-world.com> <anonymouse2048@gmail.com>
|
||||
Lokesh Mandvekar <lsm5@fedoraproject.org> <lsm5@redhat.com>
|
||||
Lorenzo Fontana <lo@linux.com> <fontanalorenzo@me.com>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
Louis Opter <kalessin@kalessin.fr> <louis@dotcloud.com>
|
||||
Luca Favatella <luca.favatella@erlang-solutions.com> <lucafavatella@users.noreply.github.com>
|
||||
Luke Marsden <me@lukemarsden.net> <luke@digital-crocus.com>
|
||||
Lyn <energylyn@zju.edu.cn>
|
||||
Lynda O'Leary <lyndaoleary29@gmail.com>
|
||||
Lynda O'Leary <lyndaoleary29@gmail.com> <lyndaoleary@hotmail.com>
|
||||
Ma Müller <mueller-ma@users.noreply.github.com>
|
||||
Madhan Raj Mookkandy <MadhanRaj.Mookkandy@microsoft.com> <madhanm@microsoft.com>
|
||||
Madhu Venugopal <madhu@socketplane.io> <madhu@docker.com>
|
||||
Mageee <fangpuyi@foxmail.com> <21521230.zju.edu.cn>
|
||||
Mansi Nahar <mmn4185@rit.edu> <mansi.nahar@macbookpro-mansinahar.local>
|
||||
Mansi Nahar <mmn4185@rit.edu> <mansinahar@users.noreply.github.com>
|
||||
Marc Abramowitz <marc@marc-abramowitz.com> <msabramo@gmail.com>
|
||||
Marcelo Horacio Fortino <info@fortinux.com> <fortinux@users.noreply.github.com>
|
||||
Marcus Linke <marcus.linke@gmx.de>
|
||||
Marianna Tessel <mtesselh@gmail.com>
|
||||
Mark Oates <fl0yd@me.com>
|
||||
Markan Patel <mpatel678@gmail.com>
|
||||
Markus Kortlang <hyp3rdino@googlemail.com> <markus.kortlang@lhsystems.com>
|
||||
Martin Redmond <redmond.martin@gmail.com> <martin@tinychat.com>
|
||||
Martin Redmond <redmond.martin@gmail.com> <xgithub@redmond5.com>
|
||||
Mary Anthony <mary.anthony@docker.com> <mary@docker.com>
|
||||
Mary Anthony <mary.anthony@docker.com> <moxieandmore@gmail.com>
|
||||
Mary Anthony <mary.anthony@docker.com> moxiegirl <mary@docker.com>
|
||||
Matt Bentley <matt.bentley@docker.com> <mbentley@mbentley.net>
|
||||
Matt Schurenko <matt.schurenko@gmail.com>
|
||||
Matt Williams <mattyw@me.com>
|
||||
Matt Williams <mattyw@me.com> <gh@mattyw.net>
|
||||
Matthew Heon <mheon@redhat.com> <mheon@mheonlaptop.redhat.com>
|
||||
Matthew Mosesohn <raytrac3r@gmail.com>
|
||||
Matthew Mueller <mattmuelle@gmail.com>
|
||||
Matthias Kühnle <git.nivoc@neverbox.com> <kuehnle@online.de>
|
||||
Mauricio Garavaglia <mauricio@medallia.com> <mauriciogaravaglia@gmail.com>
|
||||
Michael Crosby <michael@docker.com> <crosby.michael@gmail.com>
|
||||
Michael Crosby <michael@docker.com> <crosbymichael@gmail.com>
|
||||
Michael Crosby <michael@docker.com> <michael@crosbymichael.com>
|
||||
Michael Hudson-Doyle <michael.hudson@canonical.com> <michael.hudson@linaro.org>
|
||||
Michael Huettermann <michael@huettermann.net>
|
||||
Michael Käufl <docker@c.michael-kaeufl.de> <michael-k@users.noreply.github.com>
|
||||
Michael Spetsiotis <michael_spets@hotmail.com>
|
||||
Michal Minář <miminar@redhat.com>
|
||||
Miguel Angel Alvarez Cabrerizo <doncicuto@gmail.com> <30386061+doncicuto@users.noreply.github.com>
|
||||
Miguel Angel Fernández <elmendalerenda@gmail.com>
|
||||
Mihai Borobocea <MihaiBorob@gmail.com> <MihaiBorobocea@gmail.com>
|
||||
Mike Casas <mkcsas0@gmail.com> <mikecasas@users.noreply.github.com>
|
||||
Mike Goelzer <mike.goelzer@docker.com> <mgoelzer@docker.com>
|
||||
Milind Chawre <milindchawre@gmail.com>
|
||||
Misty Stanley-Jones <misty@docker.com> <misty@apache.org>
|
||||
Mohit Soni <mosoni@ebay.com> <mohitsoni1989@gmail.com>
|
||||
Moorthy RS <rsmoorthy@gmail.com> <rsmoorthy@users.noreply.github.com>
|
||||
Moysés Borges <moysesb@gmail.com>
|
||||
Moysés Borges <moysesb@gmail.com> <moyses.furtado@wplex.com.br>
|
||||
Nace Oroz <orkica@gmail.com>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com> <nathan.leclaire@gmail.com>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com> <nathanleclaire@gmail.com>
|
||||
Neil Horman <nhorman@tuxdriver.com> <nhorman@hmswarspite.think-freely.org>
|
||||
Nick Russo <nicholasjamesrusso@gmail.com> <nicholasrusso@icloud.com>
|
||||
Nicolas Borboën <ponsfrilus@gmail.com> <ponsfrilus@users.noreply.github.com>
|
||||
Nigel Poulton <nigelpoulton@hotmail.com>
|
||||
Nik Nyby <nikolas@gnu.org> <nnyby@columbia.edu>
|
||||
Nolan Darilek <nolan@thewordnerd.info>
|
||||
O.S. Tezer <ostezer@gmail.com>
|
||||
O.S. Tezer <ostezer@gmail.com> <ostezer@users.noreply.github.com>
|
||||
Oh Jinkyun <tintypemolly@gmail.com> <tintypemolly@Ohui-MacBook-Pro.local>
|
||||
Ouyang Liduo <oyld0210@163.com>
|
||||
Patrick Stapleton <github@gdi2290.com>
|
||||
Paul Liljenberg <liljenberg.paul@gmail.com> <letters@paulnotcom.se>
|
||||
Pavel Tikhomirov <ptikhomirov@virtuozzo.com> <ptikhomirov@parallels.com>
|
||||
Pawel Konczalski <mail@konczalski.de>
|
||||
Peter Choi <phkchoi89@gmail.com> <reikani@Peters-MacBook-Pro.local>
|
||||
Peter Dave Hello <hsu@peterdavehello.org> <PeterDaveHello@users.noreply.github.com>
|
||||
Peter Jaffe <pjaffe@nevo.com>
|
||||
Peter Nagy <xificurC@gmail.com> <pnagy@gratex.com>
|
||||
Peter Waller <p@pwaller.net> <peter@scraperwiki.com>
|
||||
Phil Estes <estesp@linux.vnet.ibm.com> <estesp@gmail.com>
|
||||
Philip Alexander Etling <paetling@gmail.com>
|
||||
Philipp Gillé <philipp.gille@gmail.com> <philippgille@users.noreply.github.com>
|
||||
Qiang Huang <h.huangqiang@huawei.com>
|
||||
Qiang Huang <h.huangqiang@huawei.com> <qhuang@10.0.2.15>
|
||||
Ray Tsang <rayt@google.com> <saturnism@users.noreply.github.com>
|
||||
Renaud Gaubert <rgaubert@nvidia.com> <renaud.gaubert@gmail.com>
|
||||
Robert Terhaar <rterhaar@atlanticdynamic.com> <robbyt@users.noreply.github.com>
|
||||
Roberto G. Hashioka <roberto.hashioka@docker.com> <roberto_hashioka@hotmail.com>
|
||||
Roberto Muñoz Fernández <robertomf@gmail.com> <roberto.munoz.fernandez.contractor@bbva.com>
|
||||
Roman Dudin <katrmr@gmail.com> <decadent@users.noreply.github.com>
|
||||
Ross Boucher <rboucher@gmail.com>
|
||||
Runshen Zhu <runshen.zhu@gmail.com>
|
||||
Ryan Stelly <ryan.stelly@live.com>
|
||||
Sakeven Jiang <jc5930@sina.cn>
|
||||
Sandeep Bansal <sabansal@microsoft.com>
|
||||
Sandeep Bansal <sabansal@microsoft.com> <msabansal@microsoft.com>
|
||||
Sargun Dhillon <sargun@netflix.com> <sargun@sargun.me>
|
||||
Sean Lee <seanlee@tw.ibm.com> <scaleoutsean@users.noreply.github.com>
|
||||
Sebastiaan van Stijn <github@gone.nl> <sebastiaan@ws-key-sebas3.dpi1.dpi>
|
||||
Sebastiaan van Stijn <github@gone.nl> <thaJeztah@users.noreply.github.com>
|
||||
Shaun Kaasten <shaunk@gmail.com>
|
||||
Shawn Landden <shawn@churchofgit.com> <shawnlandden@gmail.com>
|
||||
Shengbo Song <thomassong@tencent.com>
|
||||
Shengbo Song <thomassong@tencent.com> <mymneo@163.com>
|
||||
Shih-Yuan Lee <fourdollars@gmail.com>
|
||||
Shishir Mahajan <shishir.mahajan@redhat.com> <smahajan@redhat.com>
|
||||
Shukui Yang <yangshukui@huawei.com>
|
||||
Shuwei Hao <haosw@cn.ibm.com>
|
||||
Shuwei Hao <haosw@cn.ibm.com> <haoshuwei24@gmail.com>
|
||||
Sidhartha Mani <sidharthamn@gmail.com>
|
||||
Sjoerd Langkemper <sjoerd-github@linuxonly.nl> <sjoerd@byte.nl>
|
||||
Solomon Hykes <solomon@docker.com> <s@docker.com>
|
||||
Solomon Hykes <solomon@docker.com> <solomon.hykes@dotcloud.com>
|
||||
Solomon Hykes <solomon@docker.com> <solomon@dotcloud.com>
|
||||
Soshi Katsuta <soshi.katsuta@gmail.com>
|
||||
Soshi Katsuta <soshi.katsuta@gmail.com> <katsuta_soshi@cyberagent.co.jp>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com>
|
||||
Sridhar Ratnakumar <sridharr@activestate.com> <github@srid.name>
|
||||
Srini Brahmaroutu <srbrahma@us.ibm.com> <sbrahma@us.ibm.com>
|
||||
Srinivasan Srivatsan <srinivasan.srivatsan@hpe.com> <srinsriv@users.noreply.github.com>
|
||||
Stefan Berger <stefanb@linux.vnet.ibm.com>
|
||||
Stefan Berger <stefanb@linux.vnet.ibm.com> <stefanb@us.ibm.com>
|
||||
Stefan J. Wernli <swernli@microsoft.com> <swernli@ntdev.microsoft.com>
|
||||
Stefan S. <tronicum@user.github.com>
|
||||
Stephen Day <stephen.day@docker.com>
|
||||
Stephen Day <stephen.day@docker.com> <stevvooe@users.noreply.github.com>
|
||||
Steve Desmond <steve@vtsv.ca> <stevedesmond-ca@users.noreply.github.com>
|
||||
Sun Gengze <690388648@qq.com>
|
||||
Sun Jianbo <wonderflow.sun@gmail.com>
|
||||
Sun Jianbo <wonderflow.sun@gmail.com> <wonderflow@zju.edu.cn>
|
||||
Sven Dowideit <SvenDowideit@home.org.au>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <sven@t440s.home.gateway>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@docker.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@fosiki.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@home.org.au>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <SvenDowideit@users.noreply.github.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au> <¨SvenDowideit@home.org.au¨>
|
||||
Sylvain Bellemare <sylvain@ascribe.io>
|
||||
Sylvain Bellemare <sylvain@ascribe.io> <sylvain.bellemare@ezeep.com>
|
||||
Tangi Colin <tangicolin@gmail.com>
|
||||
Tejesh Mehta <tejesh.mehta@gmail.com> <tj@init.me>
|
||||
Thatcher Peskens <thatcher@docker.com>
|
||||
Thatcher Peskens <thatcher@docker.com> <thatcher@dotcloud.com>
|
||||
Thatcher Peskens <thatcher@docker.com> <thatcher@gmx.net>
|
||||
Thomas Gazagnaire <thomas@gazagnaire.org> <thomas@gazagnaire.com>
|
||||
Thomas Léveil <thomasleveil@gmail.com>
|
||||
Thomas Léveil <thomasleveil@gmail.com> <thomasleveil@users.noreply.github.com>
|
||||
Tibor Vass <teabee89@gmail.com> <tibor@docker.com>
|
||||
Tibor Vass <teabee89@gmail.com> <tiborvass@users.noreply.github.com>
|
||||
Tim Bart <tim@fewagainstmany.com>
|
||||
Tim Bosse <taim@bosboot.org> <maztaim@users.noreply.github.com>
|
||||
Tim Ruffles <oi@truffles.me.uk> <timruffles@googlemail.com>
|
||||
Tim Terhorst <mynamewastaken+git@gmail.com>
|
||||
Tim Zju <21651152@zju.edu.cn>
|
||||
Timothy Hobbs <timothyhobbs@seznam.cz>
|
||||
Toli Kuznets <toli@docker.com>
|
||||
Tom Barlow <tomwbarlow@gmail.com>
|
||||
Tom Sweeney <tsweeney@redhat.com>
|
||||
Tõnis Tiigi <tonistiigi@gmail.com>
|
||||
Trishna Guha <trishnaguha17@gmail.com>
|
||||
Tristan Carel <tristan@cogniteev.com>
|
||||
Tristan Carel <tristan@cogniteev.com> <tristan.carel@gmail.com>
|
||||
Umesh Yadav <umesh4257@gmail.com>
|
||||
Umesh Yadav <umesh4257@gmail.com> <dungeonmaster18@users.noreply.github.com>
|
||||
Victor Lyuboslavsky <victor@victoreda.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <dev@vvieux.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <victor.vieux@dotcloud.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <victor@docker.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <victor@dotcloud.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <victorvieux@gmail.com>
|
||||
Victor Vieux <victor.vieux@docker.com> <vieux@docker.com>
|
||||
Viktor Vojnovski <viktor.vojnovski@amadeus.com> <vojnovski@gmail.com>
|
||||
Vincent Batts <vbatts@redhat.com> <vbatts@hashbangbash.com>
|
||||
Vincent Bernat <Vincent.Bernat@exoscale.ch> <bernat@luffy.cx>
|
||||
Vincent Bernat <Vincent.Bernat@exoscale.ch> <vincent@bernat.im>
|
||||
Vincent Demeester <vincent.demeester@docker.com> <vincent+github@demeester.fr>
|
||||
Vincent Demeester <vincent.demeester@docker.com> <vincent@demeester.fr>
|
||||
Vincent Demeester <vincent.demeester@docker.com> <vincent@sbr.pm>
|
||||
Vishnu Kannan <vishnuk@google.com>
|
||||
Vladimir Rutsky <altsysrq@gmail.com> <iamironbob@gmail.com>
|
||||
Walter Stanish <walter@pratyeka.org>
|
||||
Wang Guoliang <liangcszzu@163.com>
|
||||
Wang Jie <wangjie5@chinaskycloud.com>
|
||||
Wang Ping <present.wp@icloud.com>
|
||||
Wang Xing <hzwangxing@corp.netease.com> <root@localhost>
|
||||
Wang Yuexiao <wang.yuexiao@zte.com.cn>
|
||||
Wayne Chang <wayne@neverfear.org>
|
||||
Wayne Song <wsong@docker.com> <wsong@users.noreply.github.com>
|
||||
Wei Wu <wuwei4455@gmail.com> cizixs <cizixs@163.com>
|
||||
Wenjun Tang <tangwj2@lenovo.com> <dodia@163.com>
|
||||
Wewang Xiaorenfine <wang.xiaoren@zte.com.cn>
|
||||
Will Weaver <monkey@buildingbananas.com>
|
||||
Xianglin Gao <xlgao@zju.edu.cn>
|
||||
Xianlu Bird <xianlubird@gmail.com>
|
||||
Xiaoyu Zhang <zhang.xiaoyu33@zte.com.cn>
|
||||
Xuecong Liao <satorulogic@gmail.com>
|
||||
Yamasaki Masahide <masahide.y@gmail.com>
|
||||
Yao Zaiyong <yaozaiyong@hotmail.com>
|
||||
Yassine Tijani <yasstij11@gmail.com>
|
||||
Yazhong Liu <yorkiefixer@gmail.com>
|
||||
Yestin Sun <sunyi0804@gmail.com> <yestin.sun@polyera.com>
|
||||
Yi EungJun <eungjun.yi@navercorp.com> <semtlenori@gmail.com>
|
||||
Ying Li <ying.li@docker.com>
|
||||
Ying Li <ying.li@docker.com> <cyli@twistedmatrix.com>
|
||||
Yong Tang <yong.tang.github@outlook.com> <yongtang@users.noreply.github.com>
|
||||
Yosef Fertel <yfertel@gmail.com> <frosforever@users.noreply.github.com>
|
||||
Yu Changchun <yuchangchun1@huawei.com>
|
||||
Yu Chengxia <yuchengxia@huawei.com>
|
||||
Yu Peng <yu.peng36@zte.com.cn>
|
||||
Yu Peng <yu.peng36@zte.com.cn> <yupeng36@zte.com.cn>
|
||||
Zachary Jaffee <zjaffee@us.ibm.com> <zij@case.edu>
|
||||
Zachary Jaffee <zjaffee@us.ibm.com> <zjaffee@apache.org>
|
||||
ZhangHang <stevezhang2014@gmail.com>
|
||||
Zhenkun Bi <bi.zhenkun@zte.com.cn>
|
||||
Zhou Hao <zhouhao@cn.fujitsu.com>
|
||||
Zhu Kunjia <zhu.kunjia@zte.com.cn>
|
||||
Zou Yu <zouyu7@huawei.com>
|
||||
|
||||
@ -1,627 +0,0 @@
|
||||
# This file lists all individuals having contributed content to the repository.
|
||||
# For how it is generated, see `scripts/docs/generate-authors.sh`.
|
||||
|
||||
Aanand Prasad <aanand.prasad@gmail.com>
|
||||
Aaron L. Xu <liker.xu@foxmail.com>
|
||||
Aaron Lehmann <aaron.lehmann@docker.com>
|
||||
Aaron.L.Xu <likexu@harmonycloud.cn>
|
||||
Abdur Rehman <abdur_rehman@mentor.com>
|
||||
Abhinandan Prativadi <abhi@docker.com>
|
||||
Abin Shahab <ashahab@altiscale.com>
|
||||
Addam Hardy <addam.hardy@gmail.com>
|
||||
Adolfo Ochagavía <aochagavia92@gmail.com>
|
||||
Adrien Duermael <adrien@duermael.com>
|
||||
Adrien Folie <folie.adrien@gmail.com>
|
||||
Ahmet Alp Balkan <ahmetb@microsoft.com>
|
||||
Aidan Feldman <aidan.feldman@gmail.com>
|
||||
Aidan Hobson Sayers <aidanhs@cantab.net>
|
||||
AJ Bowen <aj@gandi.net>
|
||||
Akihiro Suda <suda.akihiro@lab.ntt.co.jp>
|
||||
Alan Thompson <cloojure@gmail.com>
|
||||
Albert Callarisa <shark234@gmail.com>
|
||||
Aleksa Sarai <asarai@suse.de>
|
||||
Alessandro Boch <aboch@tetrationanalytics.com>
|
||||
Alex Mavrogiannis <alex.mavrogiannis@docker.com>
|
||||
Alexander Boyd <alex@opengroove.org>
|
||||
Alexander Larsson <alexl@redhat.com>
|
||||
Alexander Morozov <lk4d4@docker.com>
|
||||
Alexandre González <agonzalezro@gmail.com>
|
||||
Alfred Landrum <alfred.landrum@docker.com>
|
||||
Alicia Lauerman <alicia@eta.im>
|
||||
Allen Sun <allensun.shl@alibaba-inc.com>
|
||||
Alvin Deng <alvin.q.deng@utexas.edu>
|
||||
Amen Belayneh <amenbelayneh@gmail.com>
|
||||
Amir Goldstein <amir73il@aquasec.com>
|
||||
Amit Krishnan <amit.krishnan@oracle.com>
|
||||
Amit Shukla <amit.shukla@docker.com>
|
||||
Amy Lindburg <amy.lindburg@docker.com>
|
||||
Andrea Luzzardi <aluzzardi@gmail.com>
|
||||
Andreas Köhler <andi5.py@gmx.net>
|
||||
Andrew France <andrew@avito.co.uk>
|
||||
Andrew Hsu <andrewhsu@docker.com>
|
||||
Andrew Macpherson <hopscotch23@gmail.com>
|
||||
Andrew McDonnell <bugs@andrewmcdonnell.net>
|
||||
Andrew Po <absourd.noise@gmail.com>
|
||||
Andrey Petrov <andrey.petrov@shazow.net>
|
||||
André Martins <aanm90@gmail.com>
|
||||
Andy Goldstein <agoldste@redhat.com>
|
||||
Andy Rothfusz <github@developersupport.net>
|
||||
Anil Madhavapeddy <anil@recoil.org>
|
||||
Ankush Agarwal <ankushagarwal11@gmail.com>
|
||||
Anton Polonskiy <anton.polonskiy@gmail.com>
|
||||
Antonio Murdaca <antonio.murdaca@gmail.com>
|
||||
Antonis Kalipetis <akalipetis@gmail.com>
|
||||
Anusha Ragunathan <anusha.ragunathan@docker.com>
|
||||
Arash Deshmeh <adeshmeh@ca.ibm.com>
|
||||
Arnaud Porterie <arnaud.porterie@docker.com>
|
||||
Ashwini Oruganti <ashwini.oruganti@gmail.com>
|
||||
Azat Khuyiyakhmetov <shadow_uz@mail.ru>
|
||||
Bardia Keyoumarsi <bkeyouma@ucsc.edu>
|
||||
Barnaby Gray <barnaby@pickle.me.uk>
|
||||
Bastiaan Bakker <bbakker@xebia.com>
|
||||
BastianHofmann <bastianhofmann@me.com>
|
||||
Ben Bonnefoy <frenchben@docker.com>
|
||||
Ben Firshman <ben@firshman.co.uk>
|
||||
Benjamin Boudreau <boudreau.benjamin@gmail.com>
|
||||
Bhumika Bayani <bhumikabayani@gmail.com>
|
||||
Bill Wang <ozbillwang@gmail.com>
|
||||
Bin Liu <liubin0329@gmail.com>
|
||||
Bingshen Wang <bingshen.wbs@alibaba-inc.com>
|
||||
Boaz Shuster <ripcurld.github@gmail.com>
|
||||
Boris Pruessmann <boris@pruessmann.org>
|
||||
Bradley Cicenas <bradley.cicenas@gmail.com>
|
||||
Brandon Philips <brandon.philips@coreos.com>
|
||||
Brent Salisbury <brent.salisbury@docker.com>
|
||||
Bret Fisher <bret@bretfisher.com>
|
||||
Brian (bex) Exelbierd <bexelbie@redhat.com>
|
||||
Brian Goff <cpuguy83@gmail.com>
|
||||
Bryan Bess <squarejaw@bsbess.com>
|
||||
Bryan Boreham <bjboreham@gmail.com>
|
||||
Bryan Murphy <bmurphy1976@gmail.com>
|
||||
bryfry <bryon.fryer@gmail.com>
|
||||
Cameron Spear <cameronspear@gmail.com>
|
||||
Cao Weiwei <cao.weiwei30@zte.com.cn>
|
||||
Carlo Mion <mion00@gmail.com>
|
||||
Carlos Alexandro Becker <caarlos0@gmail.com>
|
||||
Ce Gao <ce.gao@outlook.com>
|
||||
Cedric Davies <cedricda@microsoft.com>
|
||||
Cezar Sa Espinola <cezarsa@gmail.com>
|
||||
Chao Wang <wangchao.fnst@cn.fujitsu.com>
|
||||
Charles Chan <charleswhchan@users.noreply.github.com>
|
||||
Charles Law <claw@conduce.com>
|
||||
Charles Smith <charles.smith@docker.com>
|
||||
Charlie Drage <charlie@charliedrage.com>
|
||||
ChaYoung You <yousbe@gmail.com>
|
||||
Chen Chuanliang <chen.chuanliang@zte.com.cn>
|
||||
Chen Hanxiao <chenhanxiao@cn.fujitsu.com>
|
||||
Chen Mingjie <chenmingjie0828@163.com>
|
||||
Chen Qiu <cheney-90@hotmail.com>
|
||||
Chris Gavin <chris@chrisgavin.me>
|
||||
Chris Gibson <chris@chrisg.io>
|
||||
Chris McKinnel <chrismckinnel@gmail.com>
|
||||
Chris Snow <chsnow123@gmail.com>
|
||||
Chris Weyl <cweyl@alumni.drew.edu>
|
||||
Christian Persson <saser@live.se>
|
||||
Christian Stefanescu <st.chris@gmail.com>
|
||||
Christophe Robin <crobin@nekoo.com>
|
||||
Christophe Vidal <kriss@krizalys.com>
|
||||
Christopher Biscardi <biscarch@sketcht.com>
|
||||
Christopher Jones <tophj@linux.vnet.ibm.com>
|
||||
Chun Chen <ramichen@tencent.com>
|
||||
Clinton Kitson <clintonskitson@gmail.com>
|
||||
Coenraad Loubser <coenraad@wish.org.za>
|
||||
Colin Hebert <hebert.colin@gmail.com>
|
||||
Collin Guarino <collin.guarino@gmail.com>
|
||||
Colm Hally <colmhally@gmail.com>
|
||||
Corey Farrell <git@cfware.com>
|
||||
Cristian Staretu <cristian.staretu@gmail.com>
|
||||
Daehyeok Mun <daehyeok@gmail.com>
|
||||
Dafydd Crosby <dtcrsby@gmail.com>
|
||||
dalanlan <dalanlan925@gmail.com>
|
||||
Damien Nadé <github@livna.org>
|
||||
Dan Cotora <dan@bluevision.ro>
|
||||
Daniel Dao <dqminh@cloudflare.com>
|
||||
Daniel Farrell <dfarrell@redhat.com>
|
||||
Daniel Gasienica <daniel@gasienica.ch>
|
||||
Daniel Goosen <daniel.goosen@surveysampling.com>
|
||||
Daniel Hiltgen <daniel.hiltgen@docker.com>
|
||||
Daniel J Walsh <dwalsh@redhat.com>
|
||||
Daniel Nephin <dnephin@docker.com>
|
||||
Daniel Norberg <dano@spotify.com>
|
||||
Daniel Watkins <daniel@daniel-watkins.co.uk>
|
||||
Daniel Zhang <jmzwcn@gmail.com>
|
||||
Danny Berger <dpb587@gmail.com>
|
||||
Darren Shepherd <darren.s.shepherd@gmail.com>
|
||||
Darren Stahl <darst@microsoft.com>
|
||||
Dattatraya Kumbhar <dattatraya.kumbhar@gslab.com>
|
||||
Dave Goodchild <buddhamagnet@gmail.com>
|
||||
Dave Henderson <dhenderson@gmail.com>
|
||||
Dave Tucker <dt@docker.com>
|
||||
David Calavera <david.calavera@gmail.com>
|
||||
David Cramer <davcrame@cisco.com>
|
||||
David Dooling <dooling@gmail.com>
|
||||
David Gageot <david@gageot.net>
|
||||
David Lechner <david@lechnology.com>
|
||||
David Sheets <dsheets@docker.com>
|
||||
David Williamson <david.williamson@docker.com>
|
||||
David Xia <dxia@spotify.com>
|
||||
David Young <yangboh@cn.ibm.com>
|
||||
Deng Guangxing <dengguangxing@huawei.com>
|
||||
Denis Defreyne <denis@soundcloud.com>
|
||||
Denis Gladkikh <denis@gladkikh.email>
|
||||
Denis Ollier <larchunix@users.noreply.github.com>
|
||||
Dennis Docter <dennis@d23.nl>
|
||||
Derek McGowan <derek@mcgstyle.net>
|
||||
Deshi Xiao <dxiao@redhat.com>
|
||||
Dharmit Shah <shahdharmit@gmail.com>
|
||||
Dhawal Yogesh Bhanushali <dbhanushali@vmware.com>
|
||||
Dieter Reuter <dieter.reuter@me.com>
|
||||
Dima Stopel <dima@twistlock.com>
|
||||
Dimitry Andric <d.andric@activevideo.com>
|
||||
Ding Fei <dingfei@stars.org.cn>
|
||||
Diogo Monica <diogo@docker.com>
|
||||
Dmitry Gusev <dmitry.gusev@gmail.com>
|
||||
Dmitry Smirnov <onlyjob@member.fsf.org>
|
||||
Dmitry V. Krivenok <krivenok.dmitry@gmail.com>
|
||||
Don Kjer <don.kjer@gmail.com>
|
||||
Dong Chen <dongluo.chen@docker.com>
|
||||
Doug Davis <dug@us.ibm.com>
|
||||
Drew Erny <drew.erny@docker.com>
|
||||
Ed Costello <epc@epcostello.com>
|
||||
Eli Uriegas <eli.uriegas@docker.com>
|
||||
Eli Uriegas <seemethere101@gmail.com>
|
||||
Elias Faxö <elias.faxo@tre.se>
|
||||
Eric G. Noriega <enoriega@vizuri.com>
|
||||
Eric Rosenberg <ehaydenr@gmail.com>
|
||||
Eric Sage <eric.david.sage@gmail.com>
|
||||
Eric-Olivier Lamey <eo@lamey.me>
|
||||
Erica Windisch <erica@windisch.us>
|
||||
Erik Hollensbe <github@hollensbe.org>
|
||||
Erik St. Martin <alakriti@gmail.com>
|
||||
Eugene Yakubovich <eugene.yakubovich@coreos.com>
|
||||
Evan Allrich <evan@unguku.com>
|
||||
Evan Hazlett <ejhazlett@gmail.com>
|
||||
Evan Krall <krall@yelp.com>
|
||||
Evelyn Xu <evelynhsu21@gmail.com>
|
||||
Everett Toews <everett.toews@rackspace.com>
|
||||
Fabio Falci <fabiofalci@gmail.com>
|
||||
Fabrizio Soppelsa <fsoppelsa@mirantis.com>
|
||||
Felix Hupfeld <felix@quobyte.com>
|
||||
Felix Rabe <felix@rabe.io>
|
||||
Flavio Crisciani <flavio.crisciani@docker.com>
|
||||
Florian Klein <florian.klein@free.fr>
|
||||
Foysal Iqbal <foysal.iqbal.fb@gmail.com>
|
||||
Fred Lifton <fred.lifton@docker.com>
|
||||
Frederick F. Kautz IV <fkautz@redhat.com>
|
||||
Frederik Nordahl Jul Sabroe <frederikns@gmail.com>
|
||||
Frieder Bluemle <frieder.bluemle@gmail.com>
|
||||
Gabriel Nicolas Avellaneda <avellaneda.gabriel@gmail.com>
|
||||
Gaetan de Villele <gdevillele@gmail.com>
|
||||
Gang Qiao <qiaohai8866@gmail.com>
|
||||
Gary Schaetz <gary@schaetzkc.com>
|
||||
Genki Takiuchi <genki@s21g.com>
|
||||
George MacRorie <gmacr31@gmail.com>
|
||||
George Xie <georgexsh@gmail.com>
|
||||
Gianluca Borello <g.borello@gmail.com>
|
||||
Gildas Cuisinier <gildas.cuisinier@gcuisinier.net>
|
||||
Gou Rao <gou@portworx.com>
|
||||
Grant Reaber <grant.reaber@gmail.com>
|
||||
Greg Pflaum <gpflaum@users.noreply.github.com>
|
||||
Guilhem Lettron <guilhem+github@lettron.fr>
|
||||
Guillaume J. Charmes <guillaume.charmes@docker.com>
|
||||
gwx296173 <gaojing3@huawei.com>
|
||||
Günther Jungbluth <gunther@gameslabs.net>
|
||||
Hakan Özler <hakan.ozler@kodcu.com>
|
||||
Hao Zhang <21521210@zju.edu.cn>
|
||||
Harald Albers <github@albersweb.de>
|
||||
Harold Cooper <hrldcpr@gmail.com>
|
||||
Harry Zhang <harryz@hyper.sh>
|
||||
He Simei <hesimei@zju.edu.cn>
|
||||
Helen Xie <chenjg@harmonycloud.cn>
|
||||
Henning Sprang <henning.sprang@gmail.com>
|
||||
Hernan Garcia <hernandanielg@gmail.com>
|
||||
Hongbin Lu <hongbin034@gmail.com>
|
||||
Hu Keping <hukeping@huawei.com>
|
||||
Huayi Zhang <irachex@gmail.com>
|
||||
huqun <huqun@zju.edu.cn>
|
||||
Huu Nguyen <huu@prismskylabs.com>
|
||||
Hyzhou Zhy <hyzhou.zhy@alibaba-inc.com>
|
||||
Ian Campbell <ian.campbell@docker.com>
|
||||
Ian Philpot <ian.philpot@microsoft.com>
|
||||
Ignacio Capurro <icapurrofagian@gmail.com>
|
||||
Ilya Dmitrichenko <errordeveloper@gmail.com>
|
||||
Ilya Khlopotov <ilya.khlopotov@gmail.com>
|
||||
Ilya Sotkov <ilya@sotkov.com>
|
||||
Isabel Jimenez <contact.isabeljimenez@gmail.com>
|
||||
Ivan Grcic <igrcic@gmail.com>
|
||||
Jacob Atzen <jacob@jacobatzen.dk>
|
||||
Jacob Tomlinson <jacob@tom.linson.uk>
|
||||
Jaivish Kothari <janonymous.codevulture@gmail.com>
|
||||
Jake Sanders <jsand@google.com>
|
||||
James Nesbitt <james.nesbitt@wunderkraut.com>
|
||||
James Turnbull <james@lovedthanlost.net>
|
||||
Jamie Hannaford <jamie@limetree.org>
|
||||
Jan Koprowski <jan.koprowski@gmail.com>
|
||||
Jan Pazdziora <jpazdziora@redhat.com>
|
||||
Jan-Jaap Driessen <janjaapdriessen@gmail.com>
|
||||
Jana Radhakrishnan <mrjana@docker.com>
|
||||
Jared Hocutt <jaredh@netapp.com>
|
||||
Jasmine Hegman <jasmine@jhegman.com>
|
||||
Jason Heiss <jheiss@aput.net>
|
||||
Jason Plum <jplum@devonit.com>
|
||||
Jay Kamat <github@jgkamat.33mail.com>
|
||||
Jean-Pierre Huynh <jean-pierre.huynh@ounet.fr>
|
||||
Jean-Pierre Huynh <jp@moogsoft.com>
|
||||
Jeff Lindsay <progrium@gmail.com>
|
||||
Jeff Nickoloff <jeff.nickoloff@gmail.com>
|
||||
Jeff Silberman <jsilberm@gmail.com>
|
||||
Jeremy Chambers <jeremy@thehipbot.com>
|
||||
Jeremy Unruh <jeremybunruh@gmail.com>
|
||||
Jeremy Yallop <yallop@docker.com>
|
||||
Jeroen Franse <jeroenfranse@gmail.com>
|
||||
Jesse Adametz <jesseadametz@gmail.com>
|
||||
Jessica Frazelle <jessfraz@google.com>
|
||||
Jezeniel Zapanta <jpzapanta22@gmail.com>
|
||||
Jian Zhang <zhangjian.fnst@cn.fujitsu.com>
|
||||
Jie Luo <luo612@zju.edu.cn>
|
||||
Jilles Oldenbeuving <ojilles@gmail.com>
|
||||
Jim Galasyn <jim.galasyn@docker.com>
|
||||
Jimmy Leger <jimmy.leger@gmail.com>
|
||||
Jimmy Song <rootsongjc@gmail.com>
|
||||
jimmyxian <jimmyxian2004@yahoo.com.cn>
|
||||
Joao Fernandes <joao.fernandes@docker.com>
|
||||
Joe Doliner <jdoliner@pachyderm.io>
|
||||
Joe Gordon <joe.gordon0@gmail.com>
|
||||
Joel Handwell <joelhandwell@gmail.com>
|
||||
Joey Geiger <jgeiger@gmail.com>
|
||||
Joffrey F <joffrey@docker.com>
|
||||
Johan Euphrosine <proppy@google.com>
|
||||
Johannes 'fish' Ziemke <github@freigeist.org>
|
||||
John Feminella <jxf@jxf.me>
|
||||
John Harris <john@johnharris.io>
|
||||
John Howard (VM) <John.Howard@microsoft.com>
|
||||
John Laswell <john.n.laswell@gmail.com>
|
||||
John Maguire <jmaguire@duosecurity.com>
|
||||
John Mulhausen <john@docker.com>
|
||||
John Starks <jostarks@microsoft.com>
|
||||
John Stephens <johnstep@docker.com>
|
||||
John Tims <john.k.tims@gmail.com>
|
||||
John V. Martinez <jvmatl@gmail.com>
|
||||
John Willis <john.willis@docker.com>
|
||||
Jonathan Boulle <jonathanboulle@gmail.com>
|
||||
Jonathan Lee <jonjohn1232009@gmail.com>
|
||||
Jonathan Lomas <jonathan@floatinglomas.ca>
|
||||
Jonathan McCrohan <jmccrohan@gmail.com>
|
||||
Jonh Wendell <jonh.wendell@redhat.com>
|
||||
Jordan Jennings <jjn2009@gmail.com>
|
||||
Joseph Kern <jkern@semafour.net>
|
||||
Josh Bodah <jb3689@yahoo.com>
|
||||
Josh Chorlton <jchorlton@gmail.com>
|
||||
Josh Hawn <josh.hawn@docker.com>
|
||||
Josh Horwitz <horwitz@addthis.com>
|
||||
Josh Soref <jsoref@gmail.com>
|
||||
Julien Barbier <write0@gmail.com>
|
||||
Julien Kassar <github@kassisol.com>
|
||||
Julien Maitrehenry <julien.maitrehenry@me.com>
|
||||
Justas Brazauskas <brazauskasjustas@gmail.com>
|
||||
Justin Cormack <justin.cormack@docker.com>
|
||||
Justin Simonelis <justin.p.simonelis@gmail.com>
|
||||
Jyrki Puttonen <jyrkiput@gmail.com>
|
||||
Jérôme Petazzoni <jerome.petazzoni@docker.com>
|
||||
Jörg Thalheim <joerg@higgsboson.tk>
|
||||
Kai Blin <kai@samba.org>
|
||||
Kai Qiang Wu (Kennan) <wkq5325@gmail.com>
|
||||
Kara Alexandra <kalexandra@us.ibm.com>
|
||||
Kareem Khazem <karkhaz@karkhaz.com>
|
||||
Karthik Nayak <Karthik.188@gmail.com>
|
||||
Katie McLaughlin <katie@glasnt.com>
|
||||
Ke Xu <leonhartx.k@gmail.com>
|
||||
Kei Ohmura <ohmura.kei@gmail.com>
|
||||
Keith Hudgins <greenman@greenman.org>
|
||||
Ken Cochrane <kencochrane@gmail.com>
|
||||
Ken ICHIKAWA <ichikawa.ken@jp.fujitsu.com>
|
||||
Kenfe-Mickaël Laventure <mickael.laventure@gmail.com>
|
||||
Kevin Burke <kev@inburke.com>
|
||||
Kevin Feyrer <kevin.feyrer@btinternet.com>
|
||||
Kevin Kern <kaiwentan@harmonycloud.cn>
|
||||
Kevin Meredith <kevin.m.meredith@gmail.com>
|
||||
Kevin Richardson <kevin@kevinrichardson.co>
|
||||
khaled souf <khaled.souf@gmail.com>
|
||||
Kim Eik <kim@heldig.org>
|
||||
Kir Kolyshkin <kolyshkin@gmail.com>
|
||||
Kotaro Yoshimatsu <kotaro.yoshimatsu@gmail.com>
|
||||
Krasi Georgiev <krasi@vip-consult.solutions>
|
||||
Kris-Mikael Krister <krismikael@protonmail.com>
|
||||
Kun Zhang <zkazure@gmail.com>
|
||||
Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp>
|
||||
Kyle Spiers <kyle@spiers.me>
|
||||
Lachlan Cooper <lachlancooper@gmail.com>
|
||||
Lai Jiangshan <jiangshanlai@gmail.com>
|
||||
Lars Kellogg-Stedman <lars@redhat.com>
|
||||
Laura Frank <ljfrank@gmail.com>
|
||||
Laurent Erignoux <lerignoux@gmail.com>
|
||||
Lei Jitang <leijitang@huawei.com>
|
||||
Lennie <github@consolejunkie.net>
|
||||
Leo Gallucci <elgalu3@gmail.com>
|
||||
Lewis Daly <lewisdaly@me.com>
|
||||
Li Yi <denverdino@gmail.com>
|
||||
Li Yi <weiyuan.yl@alibaba-inc.com>
|
||||
Liang-Chi Hsieh <viirya@gmail.com>
|
||||
Lily Guo <lily.guo@docker.com>
|
||||
Lin Lu <doraalin@163.com>
|
||||
Linus Heckemann <lheckemann@twig-world.com>
|
||||
Liping Xue <lipingxue@gmail.com>
|
||||
Liron Levin <liron@twistlock.com>
|
||||
liwenqi <vikilwq@zju.edu.cn>
|
||||
lixiaobing10051267 <li.xiaobing1@zte.com.cn>
|
||||
Lloyd Dewolf <foolswisdom@gmail.com>
|
||||
Lorenzo Fontana <lo@linux.com>
|
||||
Louis Opter <kalessin@kalessin.fr>
|
||||
Luca Favatella <luca.favatella@erlang-solutions.com>
|
||||
Luca Marturana <lucamarturana@gmail.com>
|
||||
Lucas Chan <lucas-github@lucaschan.com>
|
||||
Luka Hartwig <mail@lukahartwig.de>
|
||||
Lukasz Zajaczkowski <Lukasz.Zajaczkowski@ts.fujitsu.com>
|
||||
Lénaïc Huard <lhuard@amadeus.com>
|
||||
Ma Shimiao <mashimiao.fnst@cn.fujitsu.com>
|
||||
Mabin <bin.ma@huawei.com>
|
||||
Madhav Puri <madhav.puri@gmail.com>
|
||||
Madhu Venugopal <madhu@socketplane.io>
|
||||
Malte Janduda <mail@janduda.net>
|
||||
Manjunath A Kumatagi <mkumatag@in.ibm.com>
|
||||
Mansi Nahar <mmn4185@rit.edu>
|
||||
mapk0y <mapk0y@gmail.com>
|
||||
Marc Bihlmaier <marc.bihlmaier@reddoxx.com>
|
||||
Marco Mariani <marco.mariani@alterway.fr>
|
||||
Marcus Martins <marcus@docker.com>
|
||||
Marianna Tessel <mtesselh@gmail.com>
|
||||
Marius Sturm <marius@graylog.com>
|
||||
Mark Oates <fl0yd@me.com>
|
||||
Martin Mosegaard Amdisen <martin.amdisen@praqma.com>
|
||||
Mary Anthony <mary.anthony@docker.com>
|
||||
Mason Malone <mason.malone@gmail.com>
|
||||
Matt Gucci <matt9ucci@gmail.com>
|
||||
Matt Robenolt <matt@ydekproductions.com>
|
||||
Matthew Heon <mheon@redhat.com>
|
||||
Matthieu Hauglustaine <matt.hauglustaine@gmail.com>
|
||||
Max Shytikov <mshytikov@gmail.com>
|
||||
Maxime Petazzoni <max@signalfuse.com>
|
||||
Mei ChunTao <mei.chuntao@zte.com.cn>
|
||||
Micah Zoltu <micah@newrelic.com>
|
||||
Michael A. Smith <michael@smith-li.com>
|
||||
Michael Bridgen <mikeb@squaremobius.net>
|
||||
Michael Crosby <michael@docker.com>
|
||||
Michael Friis <friism@gmail.com>
|
||||
Michael Irwin <mikesir87@gmail.com>
|
||||
Michael Käufl <docker@c.michael-kaeufl.de>
|
||||
Michael Prokop <github@michael-prokop.at>
|
||||
Michael Scharf <github@scharf.gr>
|
||||
Michael Spetsiotis <michael_spets@hotmail.com>
|
||||
Michael Steinert <mike.steinert@gmail.com>
|
||||
Michael West <mwest@mdsol.com>
|
||||
Michal Minář <miminar@redhat.com>
|
||||
Michał Czeraszkiewicz <czerasz@gmail.com>
|
||||
Miguel Angel Alvarez Cabrerizo <doncicuto@gmail.com>
|
||||
Mihai Borobocea <MihaiBorob@gmail.com>
|
||||
Mihuleacc Sergiu <mihuleac.sergiu@gmail.com>
|
||||
Mike Brown <brownwm@us.ibm.com>
|
||||
Mike Casas <mkcsas0@gmail.com>
|
||||
Mike Danese <mikedanese@google.com>
|
||||
Mike Dillon <mike@embody.org>
|
||||
Mike Goelzer <mike.goelzer@docker.com>
|
||||
Mike MacCana <mike.maccana@gmail.com>
|
||||
mikelinjie <294893458@qq.com>
|
||||
Mikhail Vasin <vasin@cloud-tv.ru>
|
||||
Milind Chawre <milindchawre@gmail.com>
|
||||
Misty Stanley-Jones <misty@docker.com>
|
||||
Mohammad Banikazemi <mb@us.ibm.com>
|
||||
Mohammed Aaqib Ansari <maaquib@gmail.com>
|
||||
Moorthy RS <rsmoorthy@gmail.com>
|
||||
Morgan Bauer <mbauer@us.ibm.com>
|
||||
Moysés Borges <moysesb@gmail.com>
|
||||
Mrunal Patel <mrunalp@gmail.com>
|
||||
Muthukumar R <muthur@gmail.com>
|
||||
Máximo Cuadros <mcuadros@gmail.com>
|
||||
Nace Oroz <orkica@gmail.com>
|
||||
Nahum Shalman <nshalman@omniti.com>
|
||||
Nalin Dahyabhai <nalin@redhat.com>
|
||||
Natalie Parker <nparker@omnifone.com>
|
||||
Nate Brennand <nate.brennand@clever.com>
|
||||
Nathan Hsieh <hsieh.nathan@gmail.com>
|
||||
Nathan LeClaire <nathan.leclaire@docker.com>
|
||||
Nathan McCauley <nathan.mccauley@docker.com>
|
||||
Neil Peterson <neilpeterson@outlook.com>
|
||||
Nicola Kabar <nicolaka@gmail.com>
|
||||
Nicolas Borboën <ponsfrilus@gmail.com>
|
||||
Nicolas De Loof <nicolas.deloof@gmail.com>
|
||||
Nikhil Chawla <chawlanikhil24@gmail.com>
|
||||
Nikolas Garofil <nikolas.garofil@uantwerpen.be>
|
||||
Nikolay Milovanov <nmil@itransformers.net>
|
||||
Nishant Totla <nishanttotla@gmail.com>
|
||||
NIWA Hideyuki <niwa.niwa@nifty.ne.jp>
|
||||
Noah Treuhaft <noah.treuhaft@docker.com>
|
||||
O.S. Tezer <ostezer@gmail.com>
|
||||
ohmystack <jun.jiang02@ele.me>
|
||||
Olle Jonsson <olle.jonsson@gmail.com>
|
||||
Otto Kekäläinen <otto@seravo.fi>
|
||||
Ovidio Mallo <ovidio.mallo@gmail.com>
|
||||
Pascal Borreli <pascal@borreli.com>
|
||||
Patrick Böänziger <patrick.baenziger@bsi-software.com>
|
||||
Patrick Hemmer <patrick.hemmer@gmail.com>
|
||||
Patrick Lang <plang@microsoft.com>
|
||||
Paul <paul9869@gmail.com>
|
||||
Paul Kehrer <paul.l.kehrer@gmail.com>
|
||||
Paul Lietar <paul@lietar.net>
|
||||
Paul Weaver <pauweave@cisco.com>
|
||||
Pavel Pospisil <pospispa@gmail.com>
|
||||
Paweł Szczekutowicz <pszczekutowicz@gmail.com>
|
||||
Peeyush Gupta <gpeeyush@linux.vnet.ibm.com>
|
||||
Peter Edge <peter.edge@gmail.com>
|
||||
Peter Jaffe <pjaffe@nevo.com>
|
||||
Peter Nagy <xificurC@gmail.com>
|
||||
Peter Salvatore <peter@psftw.com>
|
||||
Peter Waller <p@pwaller.net>
|
||||
Phil Estes <estesp@linux.vnet.ibm.com>
|
||||
Philip Alexander Etling <paetling@gmail.com>
|
||||
Philipp Gillé <philipp.gille@gmail.com>
|
||||
pidster <pid@pidster.com>
|
||||
pixelistik <pixelistik@users.noreply.github.com>
|
||||
Pratik Karki <prertik@outlook.com>
|
||||
Prayag Verma <prayag.verma@gmail.com>
|
||||
Pure White <daniel48@126.com>
|
||||
Qiang Huang <h.huangqiang@huawei.com>
|
||||
Qinglan Peng <qinglanpeng@zju.edu.cn>
|
||||
qudongfang <qudongfang@gmail.com>
|
||||
Raghavendra K T <raghavendra.kt@linux.vnet.ibm.com>
|
||||
Ray Tsang <rayt@google.com>
|
||||
Reficul <xuzhenglun@gmail.com>
|
||||
Remy Suen <remy.suen@gmail.com>
|
||||
Renaud Gaubert <rgaubert@nvidia.com>
|
||||
Ricardo N Feliciano <FelicianoTech@gmail.com>
|
||||
Rich Moyse <rich@moyse.us>
|
||||
Richard Mathie <richard.mathie@amey.co.uk>
|
||||
Richard Scothern <richard.scothern@gmail.com>
|
||||
Rick Wieman <git@rickw.nl>
|
||||
Ritesh H Shukla <sritesh@vmware.com>
|
||||
Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
|
||||
Robert Wallis <smilingrob@gmail.com>
|
||||
Robin Naundorf <r.naundorf@fh-muenster.de>
|
||||
Robin Speekenbrink <robin@kingsquare.nl>
|
||||
Rodolfo Ortiz <rodolfo.ortiz@definityfirst.com>
|
||||
Rogelio Canedo <rcanedo@mappy.priv>
|
||||
Roland Kammerer <roland.kammerer@linbit.com>
|
||||
Roman Dudin <katrmr@gmail.com>
|
||||
Rory Hunter <roryhunter2@gmail.com>
|
||||
Ross Boucher <rboucher@gmail.com>
|
||||
Rubens Figueiredo <r.figueiredo.52@gmail.com>
|
||||
Ryan Belgrave <rmb1993@gmail.com>
|
||||
Ryan Detzel <ryan.detzel@gmail.com>
|
||||
Ryan Stelly <ryan.stelly@live.com>
|
||||
Sainath Grandhi <sainath.grandhi@intel.com>
|
||||
Sakeven Jiang <jc5930@sina.cn>
|
||||
Sally O'Malley <somalley@redhat.com>
|
||||
Sam Neirinck <sam@samneirinck.com>
|
||||
Sambuddha Basu <sambuddhabasu1@gmail.com>
|
||||
Samuel Karp <skarp@amazon.com>
|
||||
Santhosh Manohar <santhosh@docker.com>
|
||||
Scott Collier <emailscottcollier@gmail.com>
|
||||
Sean Christopherson <sean.j.christopherson@intel.com>
|
||||
Sean Rodman <srodman7689@gmail.com>
|
||||
Sebastiaan van Stijn <github@gone.nl>
|
||||
Sergey Tryuber <Sergeant007@users.noreply.github.com>
|
||||
Serhat Gülçiçek <serhat25@gmail.com>
|
||||
Sevki Hasirci <s@sevki.org>
|
||||
Shaun Kaasten <shaunk@gmail.com>
|
||||
Sheng Yang <sheng@yasker.org>
|
||||
Shijiang Wei <mountkin@gmail.com>
|
||||
Shishir Mahajan <shishir.mahajan@redhat.com>
|
||||
Shoubhik Bose <sbose78@gmail.com>
|
||||
Shukui Yang <yangshukui@huawei.com>
|
||||
Sian Lerk Lau <kiawin@gmail.com>
|
||||
Sidhartha Mani <sidharthamn@gmail.com>
|
||||
sidharthamani <sid@rancher.com>
|
||||
Simei He <hesimei@zju.edu.cn>
|
||||
Simon Ferquel <simon.ferquel@docker.com>
|
||||
Sindhu S <sindhus@live.in>
|
||||
Slava Semushin <semushin@redhat.com>
|
||||
Solomon Hykes <solomon@docker.com>
|
||||
Song Gao <song@gao.io>
|
||||
Spencer Brown <spencer@spencerbrown.org>
|
||||
squeegels <1674195+squeegels@users.noreply.github.com>
|
||||
Srini Brahmaroutu <srbrahma@us.ibm.com>
|
||||
Stefan S. <tronicum@user.github.com>
|
||||
Stefan Scherer <scherer_stefan@icloud.com>
|
||||
Stefan Weil <sw@weilnetz.de>
|
||||
Stephen Day <stephen.day@docker.com>
|
||||
Stephen Rust <srust@blockbridge.com>
|
||||
Steve Durrheimer <s.durrheimer@gmail.com>
|
||||
Steven Burgess <steven.a.burgess@hotmail.com>
|
||||
Subhajit Ghosh <isubuz.g@gmail.com>
|
||||
Sun Jianbo <wonderflow.sun@gmail.com>
|
||||
Sven Dowideit <SvenDowideit@home.org.au>
|
||||
Sylvain Baubeau <sbaubeau@redhat.com>
|
||||
Sébastien HOUZÉ <cto@verylastroom.com>
|
||||
T K Sourabh <sourabhtk37@gmail.com>
|
||||
TAGOMORI Satoshi <tagomoris@gmail.com>
|
||||
Taylor Jones <monitorjbl@gmail.com>
|
||||
Thatcher Peskens <thatcher@docker.com>
|
||||
Thomas Gazagnaire <thomas@gazagnaire.org>
|
||||
Thomas Leonard <thomas.leonard@docker.com>
|
||||
Thomas Léveil <thomasleveil@gmail.com>
|
||||
Thomas Riccardi <riccardi@systran.fr>
|
||||
Thomas Swift <tgs242@gmail.com>
|
||||
Tianon Gravi <admwiggin@gmail.com>
|
||||
Tianyi Wang <capkurmagati@gmail.com>
|
||||
Tibor Vass <teabee89@gmail.com>
|
||||
Tim Dettrick <t.dettrick@uq.edu.au>
|
||||
Tim Hockin <thockin@google.com>
|
||||
Tim Smith <timbot@google.com>
|
||||
Tim Waugh <twaugh@redhat.com>
|
||||
Tim Wraight <tim.wraight@tangentlabs.co.uk>
|
||||
timfeirg <kkcocogogo@gmail.com>
|
||||
Timothy Hobbs <timothyhobbs@seznam.cz>
|
||||
Tobias Bradtke <webwurst@gmail.com>
|
||||
Tobias Gesellchen <tobias@gesellix.de>
|
||||
Todd Whiteman <todd.whiteman@joyent.com>
|
||||
Tom Denham <tom@tomdee.co.uk>
|
||||
Tom Fotherby <tom+github@peopleperhour.com>
|
||||
Tom X. Tobin <tomxtobin@tomxtobin.com>
|
||||
Tomas Tomecek <ttomecek@redhat.com>
|
||||
Tomasz Kopczynski <tomek@kopczynski.net.pl>
|
||||
Tomáš Hrčka <thrcka@redhat.com>
|
||||
Tony Abboud <tdabboud@hotmail.com>
|
||||
Tõnis Tiigi <tonistiigi@gmail.com>
|
||||
Trapier Marshall <trapier.marshall@docker.com>
|
||||
Travis Cline <travis.cline@gmail.com>
|
||||
Tristan Carel <tristan@cogniteev.com>
|
||||
Tycho Andersen <tycho@docker.com>
|
||||
Tycho Andersen <tycho@tycho.ws>
|
||||
uhayate <uhayate.gong@daocloud.io>
|
||||
Umesh Yadav <umesh4257@gmail.com>
|
||||
Valentin Lorentz <progval+git@progval.net>
|
||||
Veres Lajos <vlajos@gmail.com>
|
||||
Victor Vieux <victor.vieux@docker.com>
|
||||
Victoria Bialas <victoria.bialas@docker.com>
|
||||
Viktor Stanchev <me@viktorstanchev.com>
|
||||
Vincent Batts <vbatts@redhat.com>
|
||||
Vincent Bernat <Vincent.Bernat@exoscale.ch>
|
||||
Vincent Demeester <vincent.demeester@docker.com>
|
||||
Vincent Woo <me@vincentwoo.com>
|
||||
Vishnu Kannan <vishnuk@google.com>
|
||||
Vivek Goyal <vgoyal@redhat.com>
|
||||
Wang Jie <wangjie5@chinaskycloud.com>
|
||||
Wang Long <long.wanglong@huawei.com>
|
||||
Wang Ping <present.wp@icloud.com>
|
||||
Wang Xing <hzwangxing@corp.netease.com>
|
||||
Wang Yuexiao <wang.yuexiao@zte.com.cn>
|
||||
Wayne Song <wsong@docker.com>
|
||||
Wen Cheng Ma <wenchma@cn.ibm.com>
|
||||
Wenzhi Liang <wenzhi.liang@gmail.com>
|
||||
Wes Morgan <cap10morgan@gmail.com>
|
||||
Wewang Xiaorenfine <wang.xiaoren@zte.com.cn>
|
||||
William Henry <whenry@redhat.com>
|
||||
Xianglin Gao <xlgao@zju.edu.cn>
|
||||
Xinbo Weng <xihuanbo_0521@zju.edu.cn>
|
||||
Xuecong Liao <satorulogic@gmail.com>
|
||||
Yan Feng <yanfeng2@huawei.com>
|
||||
Yanqiang Miao <miao.yanqiang@zte.com.cn>
|
||||
Yassine Tijani <yasstij11@gmail.com>
|
||||
Yi EungJun <eungjun.yi@navercorp.com>
|
||||
Ying Li <ying.li@docker.com>
|
||||
Yong Tang <yong.tang.github@outlook.com>
|
||||
Yosef Fertel <yfertel@gmail.com>
|
||||
Yu Peng <yu.peng36@zte.com.cn>
|
||||
Yuan Sun <sunyuan3@huawei.com>
|
||||
Yunxiang Huang <hyxqshk@vip.qq.com>
|
||||
zebrilee <zebrilee@gmail.com>
|
||||
Zhang Kun <zkazure@gmail.com>
|
||||
Zhang Wei <zhangwei555@huawei.com>
|
||||
Zhang Wentao <zhangwentao234@huawei.com>
|
||||
ZhangHang <stevezhang2014@gmail.com>
|
||||
zhenghenghuo <zhenghenghuo@zju.edu.cn>
|
||||
Zhou Hao <zhouhao@cn.fujitsu.com>
|
||||
Zhu Guihua <zhugh.fnst@cn.fujitsu.com>
|
||||
Álex González <agonzalezro@gmail.com>
|
||||
Álvaro Lázaro <alvaro.lazaro.g@gmail.com>
|
||||
Átila Camurça Alves <camurca.home@gmail.com>
|
||||
徐俊杰 <paco.xu@daocloud.io>
|
||||
@ -27,7 +27,6 @@
|
||||
|
||||
people = [
|
||||
"aaronlehmann",
|
||||
"albers",
|
||||
"aluzzardi",
|
||||
"anusha",
|
||||
"cpuguy83",
|
||||
@ -85,11 +84,6 @@
|
||||
Email = "aaron.lehmann@docker.com"
|
||||
GitHub = "aaronlehmann"
|
||||
|
||||
[people.albers]
|
||||
Name = "Harald Albers"
|
||||
Email = "github@albersweb.de"
|
||||
GitHub = "albers"
|
||||
|
||||
[people.aluzzardi]
|
||||
Name = "Andrea Luzzardi"
|
||||
Email = "al@docker.com"
|
||||
@ -103,7 +97,7 @@
|
||||
[people.cpuguy83]
|
||||
Name = "Brian Goff"
|
||||
Email = "cpuguy83@gmail.com"
|
||||
GitHub = "cpuguy83"
|
||||
Github = "cpuguy83"
|
||||
|
||||
[people.crosbymichael]
|
||||
Name = "Michael Crosby"
|
||||
@ -133,7 +127,7 @@
|
||||
[people.misty]
|
||||
Name = "Misty Stanley-Jones"
|
||||
Email = "misty@docker.com"
|
||||
GitHub = "mistyhacks"
|
||||
GitHub = "mstanleyjones"
|
||||
|
||||
[people.mlaventure]
|
||||
Name = "Kenfe-Mickaël Laventure"
|
||||
|
||||
@ -34,14 +34,6 @@ binary: ## build executable for Linux
|
||||
cross: ## build executable for macOS and Windows
|
||||
./scripts/build/cross
|
||||
|
||||
.PHONY: binary-windows
|
||||
binary-windows: ## build executable for Windows
|
||||
./scripts/build/windows
|
||||
|
||||
.PHONY: binary-osx
|
||||
binary-osx: ## build executable for macOS
|
||||
./scripts/build/osx
|
||||
|
||||
.PHONY: dynbinary
|
||||
dynbinary: ## build dynamically linked binary
|
||||
./scripts/build/dynbinary
|
||||
@ -54,10 +46,6 @@ vendor: vendor.conf ## check that vendor matches vendor.conf
|
||||
vndr 2> /dev/null
|
||||
scripts/validate/check-git-diff vendor
|
||||
|
||||
.PHONY: authors
|
||||
authors: ## generate AUTHORS file from git history
|
||||
scripts/docs/generate-authors.sh
|
||||
|
||||
.PHONY: manpages
|
||||
manpages: ## generate man pages from go source and markdown
|
||||
scripts/docs/generate-man.sh
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
[](https://circleci.com/gh/docker/cli/tree/master) [](https://jenkins.dockerproject.org/job/docker/job/cli/job/master/)
|
||||
[](https://circleci.com/gh/docker/cli/tree/master)
|
||||
|
||||
docker/cli
|
||||
==========
|
||||
|
||||
@ -1 +1 @@
|
||||
18.02.0-ce
|
||||
17.09.0-ce
|
||||
|
||||
@ -17,7 +17,6 @@ func SetupRootCommand(rootCmd *cobra.Command) {
|
||||
cobra.AddTemplateFunc("operationSubCommands", operationSubCommands)
|
||||
cobra.AddTemplateFunc("managementSubCommands", managementSubCommands)
|
||||
cobra.AddTemplateFunc("wrappedFlagUsages", wrappedFlagUsages)
|
||||
cobra.AddTemplateFunc("useLine", UseLine)
|
||||
|
||||
rootCmd.SetUsageTemplate(usageTemplate)
|
||||
rootCmd.SetHelpTemplate(helpTemplate)
|
||||
@ -26,7 +25,6 @@ func SetupRootCommand(rootCmd *cobra.Command) {
|
||||
|
||||
rootCmd.PersistentFlags().BoolP("help", "h", false, "Print usage")
|
||||
rootCmd.PersistentFlags().MarkShorthandDeprecated("help", "please use --help")
|
||||
rootCmd.PersistentFlags().Lookup("help").Hidden = true
|
||||
}
|
||||
|
||||
// FlagErrorFunc prints an error message which matches the format of the
|
||||
@ -99,19 +97,9 @@ func managementSubCommands(cmd *cobra.Command) []*cobra.Command {
|
||||
return cmds
|
||||
}
|
||||
|
||||
// UseLine returns the usage line for a command. This implementation is different
|
||||
// from the default Command.UseLine in that it does not add a `[flags]` to the
|
||||
// end of the line.
|
||||
func UseLine(cmd *cobra.Command) string {
|
||||
if cmd.HasParent() {
|
||||
return cmd.Parent().CommandPath() + " " + cmd.Use
|
||||
}
|
||||
return cmd.Use
|
||||
}
|
||||
|
||||
var usageTemplate = `Usage:
|
||||
|
||||
{{- if not .HasSubCommands}} {{ useLine . }}{{end}}
|
||||
{{- if not .HasSubCommands}} {{.UseLine}}{{end}}
|
||||
{{- if .HasSubCommands}} {{ .CommandPath}} COMMAND{{end}}
|
||||
|
||||
{{ .Short | trim }}
|
||||
|
||||
@ -9,11 +9,11 @@ import (
|
||||
// NewCheckpointCommand returns the `checkpoint` subcommand (only in experimental)
|
||||
func NewCheckpointCommand(dockerCli command.Cli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "checkpoint",
|
||||
Short: "Manage checkpoints",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{"experimental": "", "version": "1.25"},
|
||||
Use: "checkpoint",
|
||||
Short: "Manage checkpoints",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Tags: map[string]string{"experimental": "", "version": "1.25"},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newCreateCommand(dockerCli),
|
||||
|
||||
@ -5,30 +5,21 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/config"
|
||||
cliconfig "github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
cliflags "github.com/docker/cli/cli/flags"
|
||||
manifeststore "github.com/docker/cli/cli/manifest/store"
|
||||
registryclient "github.com/docker/cli/cli/registry/client"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
dopts "github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/docker/notary/passphrase"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/theupdateframework/notary"
|
||||
notaryclient "github.com/theupdateframework/notary/client"
|
||||
"github.com/theupdateframework/notary/passphrase"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
@ -48,28 +39,23 @@ type Cli interface {
|
||||
SetIn(in *InStream)
|
||||
ConfigFile() *configfile.ConfigFile
|
||||
ServerInfo() ServerInfo
|
||||
ClientInfo() ClientInfo
|
||||
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
|
||||
DefaultVersion() string
|
||||
ManifestStore() manifeststore.Store
|
||||
RegistryClient(bool) registryclient.RegistryClient
|
||||
}
|
||||
|
||||
// DockerCli is an instance the docker command line client.
|
||||
// Instances of the client can be returned from NewDockerCli.
|
||||
type DockerCli struct {
|
||||
configFile *configfile.ConfigFile
|
||||
in *InStream
|
||||
out *OutStream
|
||||
err io.Writer
|
||||
client client.APIClient
|
||||
serverInfo ServerInfo
|
||||
clientInfo ClientInfo
|
||||
configFile *configfile.ConfigFile
|
||||
in *InStream
|
||||
out *OutStream
|
||||
err io.Writer
|
||||
client client.APIClient
|
||||
defaultVersion string
|
||||
server ServerInfo
|
||||
}
|
||||
|
||||
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
|
||||
func (cli *DockerCli) DefaultVersion() string {
|
||||
return cli.clientInfo.DefaultVersion
|
||||
return cli.defaultVersion
|
||||
}
|
||||
|
||||
// Client returns the APIClient
|
||||
@ -114,27 +100,7 @@ func (cli *DockerCli) ConfigFile() *configfile.ConfigFile {
|
||||
// ServerInfo returns the server version details for the host this client is
|
||||
// connected to
|
||||
func (cli *DockerCli) ServerInfo() ServerInfo {
|
||||
return cli.serverInfo
|
||||
}
|
||||
|
||||
// ClientInfo returns the client details for the cli
|
||||
func (cli *DockerCli) ClientInfo() ClientInfo {
|
||||
return cli.clientInfo
|
||||
}
|
||||
|
||||
// ManifestStore returns a store for local manifests
|
||||
func (cli *DockerCli) ManifestStore() manifeststore.Store {
|
||||
// TODO: support override default location from config file
|
||||
return manifeststore.NewStore(filepath.Join(config.Dir(), "manifests"))
|
||||
}
|
||||
|
||||
// RegistryClient returns a client for communicating with a Docker distribution
|
||||
// registry
|
||||
func (cli *DockerCli) RegistryClient(allowInsecure bool) registryclient.RegistryClient {
|
||||
resolver := func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
|
||||
return ResolveAuthConfig(ctx, cli, index)
|
||||
}
|
||||
return registryclient.NewRegistryClient(resolver, UserAgent(), allowInsecure)
|
||||
return cli.server
|
||||
}
|
||||
|
||||
// Initialize the dockerCli runs initialization that must happen after command
|
||||
@ -145,77 +111,44 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
|
||||
var err error
|
||||
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
|
||||
if tlsconfig.IsErrEncryptedKey(err) {
|
||||
var (
|
||||
passwd string
|
||||
giveup bool
|
||||
)
|
||||
passRetriever := passphrase.PromptRetrieverWithInOut(cli.In(), cli.Out(), nil)
|
||||
newClient := func(password string) (client.APIClient, error) {
|
||||
opts.Common.TLSOptions.Passphrase = password
|
||||
return NewAPIClientFromFlags(opts.Common, cli.configFile)
|
||||
|
||||
for attempts := 0; tlsconfig.IsErrEncryptedKey(err); attempts++ {
|
||||
// some code and comments borrowed from notary/trustmanager/keystore.go
|
||||
passwd, giveup, err = passRetriever("private", "encrypted TLS private", false, attempts)
|
||||
// Check if the passphrase retriever got an error or if it is telling us to give up
|
||||
if giveup || err != nil {
|
||||
return errors.Wrap(err, "private key is encrypted, but could not get passphrase")
|
||||
}
|
||||
|
||||
opts.Common.TLSOptions.Passphrase = passwd
|
||||
cli.client, err = NewAPIClientFromFlags(opts.Common, cli.configFile)
|
||||
}
|
||||
cli.client, err = getClientWithPassword(passRetriever, newClient)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hasExperimental, err := isEnabled(cli.configFile.Experimental)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Experimental field")
|
||||
}
|
||||
orchestrator := GetOrchestrator(hasExperimental, opts.Common.Orchestrator, cli.configFile.Orchestrator)
|
||||
cli.clientInfo = ClientInfo{
|
||||
DefaultVersion: cli.client.ClientVersion(),
|
||||
HasExperimental: hasExperimental,
|
||||
Orchestrator: orchestrator,
|
||||
}
|
||||
cli.initializeFromClient()
|
||||
return nil
|
||||
}
|
||||
|
||||
func isEnabled(value string) (bool, error) {
|
||||
switch value {
|
||||
case "enabled":
|
||||
return true, nil
|
||||
case "", "disabled":
|
||||
return false, nil
|
||||
default:
|
||||
return false, errors.Errorf("%q is not valid, should be either enabled or disabled", value)
|
||||
}
|
||||
}
|
||||
cli.defaultVersion = cli.client.ClientVersion()
|
||||
|
||||
func (cli *DockerCli) initializeFromClient() {
|
||||
ping, err := cli.client.Ping(context.Background())
|
||||
if err != nil {
|
||||
if ping, err := cli.client.Ping(context.Background()); err == nil {
|
||||
cli.server = ServerInfo{
|
||||
HasExperimental: ping.Experimental,
|
||||
OSType: ping.OSType,
|
||||
}
|
||||
|
||||
cli.client.NegotiateAPIVersionPing(ping)
|
||||
} else {
|
||||
// Default to true if we fail to connect to daemon
|
||||
cli.serverInfo = ServerInfo{HasExperimental: true}
|
||||
|
||||
if ping.APIVersion != "" {
|
||||
cli.client.NegotiateAPIVersionPing(ping)
|
||||
}
|
||||
return
|
||||
cli.server = ServerInfo{HasExperimental: true}
|
||||
}
|
||||
|
||||
cli.serverInfo = ServerInfo{
|
||||
HasExperimental: ping.Experimental,
|
||||
OSType: ping.OSType,
|
||||
}
|
||||
cli.client.NegotiateAPIVersionPing(ping)
|
||||
}
|
||||
|
||||
func getClientWithPassword(passRetriever notary.PassRetriever, newClient func(password string) (client.APIClient, error)) (client.APIClient, error) {
|
||||
for attempts := 0; ; attempts++ {
|
||||
passwd, giveup, err := passRetriever("private", "encrypted TLS private", false, attempts)
|
||||
if giveup || err != nil {
|
||||
return nil, errors.Wrap(err, "private key is encrypted, but could not get passphrase")
|
||||
}
|
||||
|
||||
apiclient, err := newClient(passwd)
|
||||
if !tlsconfig.IsErrEncryptedKey(err) {
|
||||
return apiclient, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NotaryClient provides a Notary Repository to interact with signed metadata for an image
|
||||
func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) {
|
||||
return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServerInfo stores details about the supported features and platform of the
|
||||
@ -225,18 +158,6 @@ type ServerInfo struct {
|
||||
OSType string
|
||||
}
|
||||
|
||||
// ClientInfo stores details about the supported features of the client
|
||||
type ClientInfo struct {
|
||||
HasExperimental bool
|
||||
DefaultVersion string
|
||||
Orchestrator Orchestrator
|
||||
}
|
||||
|
||||
// HasKubernetes checks if kubernetes orchestrator is enabled
|
||||
func (c ClientInfo) HasKubernetes() bool {
|
||||
return c.HasExperimental && c.Orchestrator == OrchestratorKubernetes
|
||||
}
|
||||
|
||||
// NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err.
|
||||
func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli {
|
||||
return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err}
|
||||
@ -268,8 +189,7 @@ func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.
|
||||
return client.NewClient(host, verStr, httpClient, customHeaders)
|
||||
}
|
||||
|
||||
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error) {
|
||||
var host string
|
||||
func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (host string, err error) {
|
||||
switch len(hosts) {
|
||||
case 0:
|
||||
host = os.Getenv("DOCKER_HOST")
|
||||
@ -279,7 +199,8 @@ func getServerHost(hosts []string, tlsOptions *tlsconfig.Options) (string, error
|
||||
return "", errors.New("Please specify only one -H")
|
||||
}
|
||||
|
||||
return dopts.ParseHost(tlsOptions != nil, host)
|
||||
host, err = dopts.ParseHost(tlsOptions != nil, host)
|
||||
return
|
||||
}
|
||||
|
||||
func newHTTPClient(host string, tlsOptions *tlsconfig.Options) (*http.Client, error) {
|
||||
|
||||
@ -1,22 +1,15 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
cliconfig "github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/gotestyourself/gotestyourself/fs"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestNewAPIClientFromFlags(t *testing.T) {
|
||||
@ -50,7 +43,7 @@ func TestNewAPIClientFromFlagsWithAPIVersionFromEnv(t *testing.T) {
|
||||
assert.Equal(t, customVersion, apiclient.ClientVersion())
|
||||
}
|
||||
|
||||
// TODO: use gotestyourself/env.Patch
|
||||
// TODO: move to gotestyourself
|
||||
func patchEnvVariable(t *testing.T, key, value string) func() {
|
||||
oldValue, ok := os.LookupEnv(key)
|
||||
require.NoError(t, os.Setenv(key, value))
|
||||
@ -62,280 +55,3 @@ func patchEnvVariable(t *testing.T, key, value string) func() {
|
||||
require.NoError(t, os.Setenv(key, oldValue))
|
||||
}
|
||||
}
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
pingFunc func() (types.Ping, error)
|
||||
version string
|
||||
negotiated bool
|
||||
}
|
||||
|
||||
func (c *fakeClient) Ping(_ context.Context) (types.Ping, error) {
|
||||
return c.pingFunc()
|
||||
}
|
||||
|
||||
func (c *fakeClient) ClientVersion() string {
|
||||
return c.version
|
||||
}
|
||||
|
||||
func (c *fakeClient) NegotiateAPIVersionPing(types.Ping) {
|
||||
c.negotiated = true
|
||||
}
|
||||
|
||||
func TestInitializeFromClient(t *testing.T) {
|
||||
defaultVersion := "v1.55"
|
||||
|
||||
var testcases = []struct {
|
||||
doc string
|
||||
pingFunc func() (types.Ping, error)
|
||||
expectedServer ServerInfo
|
||||
negotiated bool
|
||||
}{
|
||||
{
|
||||
doc: "successful ping",
|
||||
pingFunc: func() (types.Ping, error) {
|
||||
return types.Ping{Experimental: true, OSType: "linux", APIVersion: "v1.30"}, nil
|
||||
},
|
||||
expectedServer: ServerInfo{HasExperimental: true, OSType: "linux"},
|
||||
negotiated: true,
|
||||
},
|
||||
{
|
||||
doc: "failed ping, no API version",
|
||||
pingFunc: func() (types.Ping, error) {
|
||||
return types.Ping{}, errors.New("failed")
|
||||
},
|
||||
expectedServer: ServerInfo{HasExperimental: true},
|
||||
},
|
||||
{
|
||||
doc: "failed ping, with API version",
|
||||
pingFunc: func() (types.Ping, error) {
|
||||
return types.Ping{APIVersion: "v1.33"}, errors.New("failed")
|
||||
},
|
||||
expectedServer: ServerInfo{HasExperimental: true},
|
||||
negotiated: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
apiclient := &fakeClient{
|
||||
pingFunc: testcase.pingFunc,
|
||||
version: defaultVersion,
|
||||
}
|
||||
|
||||
cli := &DockerCli{client: apiclient}
|
||||
cli.initializeFromClient()
|
||||
assert.Equal(t, testcase.expectedServer, cli.serverInfo)
|
||||
assert.Equal(t, testcase.negotiated, apiclient.negotiated)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExperimentalCLI(t *testing.T) {
|
||||
defaultVersion := "v1.55"
|
||||
|
||||
var testcases = []struct {
|
||||
doc string
|
||||
configfile string
|
||||
expectedExperimentalCLI bool
|
||||
}{
|
||||
{
|
||||
doc: "default",
|
||||
configfile: `{}`,
|
||||
expectedExperimentalCLI: false,
|
||||
},
|
||||
{
|
||||
doc: "experimental",
|
||||
configfile: `{
|
||||
"experimental": "enabled"
|
||||
}`,
|
||||
expectedExperimentalCLI: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
|
||||
defer dir.Remove()
|
||||
apiclient := &fakeClient{
|
||||
version: defaultVersion,
|
||||
}
|
||||
|
||||
cli := &DockerCli{client: apiclient, err: os.Stderr}
|
||||
cliconfig.SetDir(dir.Path())
|
||||
err := cli.Initialize(flags.NewClientOptions())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testcase.expectedExperimentalCLI, cli.ClientInfo().HasExperimental)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchestratorSwitch(t *testing.T) {
|
||||
defaultVersion := "v0.00"
|
||||
|
||||
var testcases = []struct {
|
||||
doc string
|
||||
configfile string
|
||||
envOrchestrator string
|
||||
flagOrchestrator string
|
||||
expectedOrchestrator string
|
||||
expectedKubernetes bool
|
||||
}{
|
||||
{
|
||||
doc: "default",
|
||||
configfile: `{
|
||||
"experimental": "enabled"
|
||||
}`,
|
||||
expectedOrchestrator: "swarm",
|
||||
expectedKubernetes: false,
|
||||
},
|
||||
{
|
||||
doc: "kubernetesIsExperimental",
|
||||
configfile: `{
|
||||
"experimental": "disabled",
|
||||
"orchestrator": "kubernetes"
|
||||
}`,
|
||||
envOrchestrator: "kubernetes",
|
||||
flagOrchestrator: "kubernetes",
|
||||
expectedOrchestrator: "swarm",
|
||||
expectedKubernetes: false,
|
||||
},
|
||||
{
|
||||
doc: "kubernetesConfigFile",
|
||||
configfile: `{
|
||||
"experimental": "enabled",
|
||||
"orchestrator": "kubernetes"
|
||||
}`,
|
||||
expectedOrchestrator: "kubernetes",
|
||||
expectedKubernetes: true,
|
||||
},
|
||||
{
|
||||
doc: "kubernetesEnv",
|
||||
configfile: `{
|
||||
"experimental": "enabled"
|
||||
}`,
|
||||
envOrchestrator: "kubernetes",
|
||||
expectedOrchestrator: "kubernetes",
|
||||
expectedKubernetes: true,
|
||||
},
|
||||
{
|
||||
doc: "kubernetesFlag",
|
||||
configfile: `{
|
||||
"experimental": "enabled"
|
||||
}`,
|
||||
flagOrchestrator: "kubernetes",
|
||||
expectedOrchestrator: "kubernetes",
|
||||
expectedKubernetes: true,
|
||||
},
|
||||
{
|
||||
doc: "envOverridesConfigFile",
|
||||
configfile: `{
|
||||
"experimental": "enabled",
|
||||
"orchestrator": "kubernetes"
|
||||
}`,
|
||||
envOrchestrator: "swarm",
|
||||
expectedOrchestrator: "swarm",
|
||||
expectedKubernetes: false,
|
||||
},
|
||||
{
|
||||
doc: "flagOverridesEnv",
|
||||
configfile: `{
|
||||
"experimental": "enabled"
|
||||
}`,
|
||||
envOrchestrator: "kubernetes",
|
||||
flagOrchestrator: "swarm",
|
||||
expectedOrchestrator: "swarm",
|
||||
expectedKubernetes: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
|
||||
defer dir.Remove()
|
||||
apiclient := &fakeClient{
|
||||
version: defaultVersion,
|
||||
}
|
||||
if testcase.envOrchestrator != "" {
|
||||
defer patchEnvVariable(t, "DOCKER_ORCHESTRATOR", testcase.envOrchestrator)()
|
||||
}
|
||||
|
||||
cli := &DockerCli{client: apiclient, err: os.Stderr}
|
||||
cliconfig.SetDir(dir.Path())
|
||||
options := flags.NewClientOptions()
|
||||
if testcase.flagOrchestrator != "" {
|
||||
options.Common.Orchestrator = testcase.flagOrchestrator
|
||||
}
|
||||
err := cli.Initialize(options)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testcase.expectedKubernetes, cli.ClientInfo().HasKubernetes())
|
||||
assert.Equal(t, testcase.expectedOrchestrator, string(cli.ClientInfo().Orchestrator))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClientWithPassword(t *testing.T) {
|
||||
expected := "password"
|
||||
|
||||
var testcases = []struct {
|
||||
doc string
|
||||
password string
|
||||
retrieverErr error
|
||||
retrieverGiveup bool
|
||||
newClientErr error
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
doc: "successful connect",
|
||||
password: expected,
|
||||
},
|
||||
{
|
||||
doc: "password retriever exhausted",
|
||||
retrieverGiveup: true,
|
||||
retrieverErr: errors.New("failed"),
|
||||
expectedErr: "private key is encrypted, but could not get passphrase",
|
||||
},
|
||||
{
|
||||
doc: "password retriever error",
|
||||
retrieverErr: errors.New("failed"),
|
||||
expectedErr: "failed",
|
||||
},
|
||||
{
|
||||
doc: "newClient error",
|
||||
newClientErr: errors.New("failed to connect"),
|
||||
expectedErr: "failed to connect",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
passRetriever := func(_, _ string, _ bool, attempts int) (passphrase string, giveup bool, err error) {
|
||||
// Always return an invalid pass first to test iteration
|
||||
switch attempts {
|
||||
case 0:
|
||||
return "something else", false, nil
|
||||
default:
|
||||
return testcase.password, testcase.retrieverGiveup, testcase.retrieverErr
|
||||
}
|
||||
}
|
||||
|
||||
newClient := func(currentPassword string) (client.APIClient, error) {
|
||||
if testcase.newClientErr != nil {
|
||||
return nil, testcase.newClientErr
|
||||
}
|
||||
if currentPassword == expected {
|
||||
return &client.Client{}, nil
|
||||
}
|
||||
return &client.Client{}, x509.IncorrectPasswordError
|
||||
}
|
||||
|
||||
_, err := getClientWithPassword(passRetriever, newClient)
|
||||
if testcase.expectedErr != "" {
|
||||
testutil.ErrorContains(t, err, testcase.expectedErr)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/config"
|
||||
"github.com/docker/cli/cli/command/container"
|
||||
"github.com/docker/cli/cli/command/image"
|
||||
"github.com/docker/cli/cli/command/manifest"
|
||||
"github.com/docker/cli/cli/command/network"
|
||||
"github.com/docker/cli/cli/command/node"
|
||||
"github.com/docker/cli/cli/command/plugin"
|
||||
@ -18,7 +17,6 @@ import (
|
||||
"github.com/docker/cli/cli/command/stack"
|
||||
"github.com/docker/cli/cli/command/swarm"
|
||||
"github.com/docker/cli/cli/command/system"
|
||||
"github.com/docker/cli/cli/command/trust"
|
||||
"github.com/docker/cli/cli/command/volume"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -40,15 +38,12 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) {
|
||||
image.NewImageCommand(dockerCli),
|
||||
image.NewBuildCommand(dockerCli),
|
||||
|
||||
// manifest
|
||||
manifest.NewManifestCommand(dockerCli),
|
||||
// node
|
||||
node.NewNodeCommand(dockerCli),
|
||||
|
||||
// network
|
||||
network.NewNetworkCommand(dockerCli),
|
||||
|
||||
// node
|
||||
node.NewNodeCommand(dockerCli),
|
||||
|
||||
// plugin
|
||||
plugin.NewPluginCommand(dockerCli),
|
||||
|
||||
@ -74,9 +69,6 @@ func AddCommands(cmd *cobra.Command, dockerCli *command.DockerCli) {
|
||||
// swarm
|
||||
swarm.NewSwarmCommand(dockerCli),
|
||||
|
||||
// trust
|
||||
trust.NewTrustCommand(dockerCli),
|
||||
|
||||
// volume
|
||||
volume.NewVolumeCommand(dockerCli),
|
||||
|
||||
|
||||
@ -8,16 +8,14 @@ import (
|
||||
)
|
||||
|
||||
// NewConfigCommand returns a cobra command for `config` subcommands
|
||||
func NewConfigCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// nolint: interfacer
|
||||
func NewConfigCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Manage Docker configs",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{
|
||||
"version": "1.30",
|
||||
"swarm": "",
|
||||
},
|
||||
Tags: map[string]string{"version": "1.30"},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newConfigListCommand(dockerCli),
|
||||
|
||||
@ -28,7 +28,7 @@ func newConfigCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "create [OPTIONS] CONFIG file|-",
|
||||
Short: "Create a config from a file or STDIN",
|
||||
Short: "Create a configuration file from a file or STDIN as content",
|
||||
Args: cli.ExactArgs(2),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
createOpts.name = args[0]
|
||||
|
||||
@ -21,7 +21,7 @@ func newConfigInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
opts := inspectOptions{}
|
||||
cmd := &cobra.Command{
|
||||
Use: "inspect [OPTIONS] CONFIG [CONFIG...]",
|
||||
Short: "Display detailed information on one or more configs",
|
||||
Short: "Display detailed information on one or more configuration files",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts.names = args
|
||||
|
||||
@ -1,27 +1,15 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/command/formatter"
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
"vbom.ml/util/sortorder"
|
||||
)
|
||||
|
||||
type byConfigName []swarm.Config
|
||||
|
||||
func (r byConfigName) Len() int { return len(r) }
|
||||
func (r byConfigName) Swap(i, j int) { r[i], r[j] = r[j], r[i] }
|
||||
func (r byConfigName) Less(i, j int) bool {
|
||||
return sortorder.NaturalLess(r[i].Spec.Name, r[j].Spec.Name)
|
||||
}
|
||||
|
||||
type listOptions struct {
|
||||
quiet bool
|
||||
format string
|
||||
@ -67,8 +55,6 @@ func runConfigList(dockerCli command.Cli, options listOptions) error {
|
||||
}
|
||||
}
|
||||
|
||||
sort.Sort(byConfigName(configs))
|
||||
|
||||
configCtx := formatter.Context{
|
||||
Output: dockerCli.Out(),
|
||||
Format: formatter.NewConfigFormat(format, options.quiet),
|
||||
|
||||
@ -50,20 +50,14 @@ func TestConfigList(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
configListFunc: func(options types.ConfigListOptions) ([]swarm.Config, error) {
|
||||
return []swarm.Config{
|
||||
*Config(ConfigID("ID-1-foo"),
|
||||
ConfigName("1-foo"),
|
||||
*Config(ConfigID("ID-foo"),
|
||||
ConfigName("foo"),
|
||||
ConfigVersion(swarm.Version{Index: 10}),
|
||||
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
),
|
||||
*Config(ConfigID("ID-10-foo"),
|
||||
ConfigName("10-foo"),
|
||||
ConfigVersion(swarm.Version{Index: 11}),
|
||||
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
),
|
||||
*Config(ConfigID("ID-2-foo"),
|
||||
ConfigName("2-foo"),
|
||||
*Config(ConfigID("ID-bar"),
|
||||
ConfigName("bar"),
|
||||
ConfigVersion(swarm.Version{Index: 11}),
|
||||
ConfigCreatedAt(time.Now().Add(-2*time.Hour)),
|
||||
ConfigUpdatedAt(time.Now().Add(-1*time.Hour)),
|
||||
@ -72,8 +66,9 @@ func TestConfigList(t *testing.T) {
|
||||
},
|
||||
})
|
||||
cmd := newConfigListCommand(cli)
|
||||
cmd.SetOutput(cli.OutBuffer())
|
||||
assert.NoError(t, cmd.Execute())
|
||||
golden.Assert(t, cli.OutBuffer().String(), "config-list-sort.golden")
|
||||
golden.Assert(t, cli.OutBuffer().String(), "config-list.golden")
|
||||
}
|
||||
|
||||
func TestConfigListWithQuietOption(t *testing.T) {
|
||||
|
||||
@ -19,7 +19,7 @@ func newConfigRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "rm CONFIG [CONFIG...]",
|
||||
Aliases: []string{"remove"},
|
||||
Short: "Remove one or more configs",
|
||||
Short: "Remove one or more configuration files",
|
||||
Args: cli.RequiresMinArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
opts := removeOptions{
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
ID NAME CREATED UPDATED
|
||||
ID-1-foo 1-foo 2 hours ago About an hour ago
|
||||
ID-2-foo 2-foo 2 hours ago About an hour ago
|
||||
ID-10-foo 10-foo 2 hours ago About an hour ago
|
||||
@ -1,2 +1,2 @@
|
||||
bar label=label-bar
|
||||
foo
|
||||
bar label=label-bar
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
ID NAME CREATED UPDATED
|
||||
ID-bar bar 2 hours ago About an hour ago
|
||||
ID-foo foo 2 hours ago About an hour ago
|
||||
ID-bar bar 2 hours ago About an hour ago
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
bar label=label-bar
|
||||
foo
|
||||
bar label=label-bar
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
ID-bar
|
||||
ID-foo
|
||||
ID-bar
|
||||
|
||||
3
components/cli/cli/command/config/testdata/config-list.golden
vendored
Normal file
3
components/cli/cli/command/config/testdata/config-list.golden
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
ID NAME CREATED UPDATED
|
||||
ID-foo foo 2 hours ago About an hour ago
|
||||
ID-bar bar 2 hours ago About an hour ago
|
||||
@ -12,17 +12,12 @@ import (
|
||||
|
||||
type fakeClient struct {
|
||||
client.Client
|
||||
inspectFunc func(string) (types.ContainerJSON, error)
|
||||
execInspectFunc func(execID string) (types.ContainerExecInspect, error)
|
||||
execCreateFunc func(container string, config types.ExecConfig) (types.IDResponse, error)
|
||||
createContainerFunc func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error)
|
||||
imageCreateFunc func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
|
||||
infoFunc func() (types.Info, error)
|
||||
containerStatPathFunc func(container, path string) (types.ContainerPathStat, error)
|
||||
containerCopyFromFunc func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error)
|
||||
logFunc func(string, types.ContainerLogsOptions) (io.ReadCloser, error)
|
||||
waitFunc func(string) (<-chan container.ContainerWaitOKBody, <-chan error)
|
||||
Version string
|
||||
inspectFunc func(string) (types.ContainerJSON, error)
|
||||
execInspectFunc func(execID string) (types.ContainerExecInspect, error)
|
||||
execCreateFunc func(container string, config types.ExecConfig) (types.IDResponse, error)
|
||||
createContainerFunc func(config *container.Config, hostConfig *container.HostConfig, networkingConfig *network.NetworkingConfig, containerName string) (container.ContainerCreateCreatedBody, error)
|
||||
imageCreateFunc func(parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
|
||||
infoFunc func() (types.Info, error)
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerInspect(_ context.Context, containerID string) (types.ContainerJSON, error) {
|
||||
@ -76,35 +71,3 @@ func (f *fakeClient) Info(_ context.Context) (types.Info, error) {
|
||||
}
|
||||
return types.Info{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerStatPath(_ context.Context, container, path string) (types.ContainerPathStat, error) {
|
||||
if f.containerStatPathFunc != nil {
|
||||
return f.containerStatPathFunc(container, path)
|
||||
}
|
||||
return types.ContainerPathStat{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) CopyFromContainer(_ context.Context, container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
if f.containerCopyFromFunc != nil {
|
||||
return f.containerCopyFromFunc(container, srcPath)
|
||||
}
|
||||
return nil, types.ContainerPathStat{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerLogs(_ context.Context, container string, options types.ContainerLogsOptions) (io.ReadCloser, error) {
|
||||
if f.logFunc != nil {
|
||||
return f.logFunc(container, options)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *fakeClient) ClientVersion() string {
|
||||
return f.Version
|
||||
}
|
||||
|
||||
func (f *fakeClient) ContainerWait(_ context.Context, container string, _ container.WaitCondition) (<-chan container.ContainerWaitOKBody, <-chan error) {
|
||||
if f.waitFunc != nil {
|
||||
return f.waitFunc(container)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -7,7 +7,8 @@ import (
|
||||
)
|
||||
|
||||
// NewContainerCommand returns a cobra command for `container` subcommands
|
||||
func NewContainerCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// nolint: interfacer
|
||||
func NewContainerCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "container",
|
||||
Short: "Manage containers",
|
||||
|
||||
@ -22,7 +22,7 @@ type commitOptions struct {
|
||||
}
|
||||
|
||||
// NewCommitCommand creates a new cobra.Command for `docker commit`
|
||||
func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewCommitCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var options commitOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -51,7 +51,7 @@ func NewCommitCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runCommit(dockerCli command.Cli, options *commitOptions) error {
|
||||
func runCommit(dockerCli *command.DockerCli, options *commitOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
name := options.container
|
||||
|
||||
@ -26,21 +26,17 @@ type copyOptions struct {
|
||||
type copyDirection int
|
||||
|
||||
const (
|
||||
fromContainer copyDirection = 1 << iota
|
||||
fromContainer copyDirection = (1 << iota)
|
||||
toContainer
|
||||
acrossContainers = fromContainer | toContainer
|
||||
)
|
||||
|
||||
type cpConfig struct {
|
||||
followLink bool
|
||||
copyUIDGID bool
|
||||
sourcePath string
|
||||
destPath string
|
||||
container string
|
||||
}
|
||||
|
||||
// NewCopyCommand creates a new `docker cp` command
|
||||
func NewCopyCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewCopyCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts copyOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -69,57 +65,58 @@ func NewCopyCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.BoolVarP(&opts.followLink, "follow-link", "L", false, "Always follow symbol link in SRC_PATH")
|
||||
flags.BoolVarP(&opts.copyUIDGID, "archive", "a", false, "Archive mode (copy all uid/gid information)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runCopy(dockerCli command.Cli, opts copyOptions) error {
|
||||
func runCopy(dockerCli *command.DockerCli, opts copyOptions) error {
|
||||
srcContainer, srcPath := splitCpArg(opts.source)
|
||||
destContainer, destPath := splitCpArg(opts.destination)
|
||||
|
||||
copyConfig := cpConfig{
|
||||
followLink: opts.followLink,
|
||||
copyUIDGID: opts.copyUIDGID,
|
||||
sourcePath: srcPath,
|
||||
destPath: destPath,
|
||||
}
|
||||
dstContainer, dstPath := splitCpArg(opts.destination)
|
||||
|
||||
var direction copyDirection
|
||||
if srcContainer != "" {
|
||||
direction |= fromContainer
|
||||
copyConfig.container = srcContainer
|
||||
}
|
||||
if destContainer != "" {
|
||||
if dstContainer != "" {
|
||||
direction |= toContainer
|
||||
copyConfig.container = destContainer
|
||||
}
|
||||
|
||||
cpParam := &cpConfig{
|
||||
followLink: opts.followLink,
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
switch direction {
|
||||
case fromContainer:
|
||||
return copyFromContainer(ctx, dockerCli, copyConfig)
|
||||
return copyFromContainer(ctx, dockerCli, srcContainer, srcPath, dstPath, cpParam)
|
||||
case toContainer:
|
||||
return copyToContainer(ctx, dockerCli, copyConfig)
|
||||
return copyToContainer(ctx, dockerCli, srcPath, dstContainer, dstPath, cpParam, opts.copyUIDGID)
|
||||
case acrossContainers:
|
||||
// Copying between containers isn't supported.
|
||||
return errors.New("copying between containers is not supported")
|
||||
default:
|
||||
// User didn't specify any container.
|
||||
return errors.New("must specify at least one container source")
|
||||
}
|
||||
}
|
||||
|
||||
func statContainerPath(ctx context.Context, dockerCli *command.DockerCli, containerName, path string) (types.ContainerPathStat, error) {
|
||||
return dockerCli.Client().ContainerStatPath(ctx, containerName, path)
|
||||
}
|
||||
|
||||
func resolveLocalPath(localPath string) (absPath string, err error) {
|
||||
if absPath, err = filepath.Abs(localPath); err != nil {
|
||||
return
|
||||
}
|
||||
return archive.PreserveTrailingDotOrSeparator(absPath, localPath, filepath.Separator), nil
|
||||
|
||||
return archive.PreserveTrailingDotOrSeparator(absPath, localPath), nil
|
||||
}
|
||||
|
||||
func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpConfig) (err error) {
|
||||
dstPath := copyConfig.destPath
|
||||
srcPath := copyConfig.sourcePath
|
||||
|
||||
func copyFromContainer(ctx context.Context, dockerCli *command.DockerCli, srcContainer, srcPath, dstPath string, cpParam *cpConfig) (err error) {
|
||||
if dstPath != "-" {
|
||||
// Get an absolute destination path.
|
||||
dstPath, err = resolveLocalPath(dstPath)
|
||||
@ -128,11 +125,10 @@ func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cp
|
||||
}
|
||||
}
|
||||
|
||||
client := dockerCli.Client()
|
||||
// if client requests to follow symbol link, then must decide target file to be copied
|
||||
var rebaseName string
|
||||
if copyConfig.followLink {
|
||||
srcStat, err := client.ContainerStatPath(ctx, copyConfig.container, srcPath)
|
||||
if cpParam.followLink {
|
||||
srcStat, err := statContainerPath(ctx, dockerCli, srcContainer, srcPath)
|
||||
|
||||
// If the destination is a symbolic link, we should follow it.
|
||||
if err == nil && srcStat.Mode&os.ModeSymlink != 0 {
|
||||
@ -149,17 +145,20 @@ func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cp
|
||||
|
||||
}
|
||||
|
||||
content, stat, err := client.CopyFromContainer(ctx, copyConfig.container, srcPath)
|
||||
content, stat, err := dockerCli.Client().CopyFromContainer(ctx, srcContainer, srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer content.Close()
|
||||
|
||||
if dstPath == "-" {
|
||||
_, err = io.Copy(dockerCli.Out(), content)
|
||||
// Send the response to STDOUT.
|
||||
_, err = io.Copy(os.Stdout, content)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Prepare source copy info.
|
||||
srcInfo := archive.CopyInfo{
|
||||
Path: srcPath,
|
||||
Exists: true,
|
||||
@ -172,17 +171,13 @@ func copyFromContainer(ctx context.Context, dockerCli command.Cli, copyConfig cp
|
||||
_, srcBase := archive.SplitPathDirEntry(srcInfo.Path)
|
||||
preArchive = archive.RebaseArchiveEntries(content, srcBase, srcInfo.RebaseName)
|
||||
}
|
||||
// See comments in the implementation of `archive.CopyTo` for exactly what
|
||||
// goes into deciding how and whether the source archive needs to be
|
||||
// altered for the correct copy behavior.
|
||||
return archive.CopyTo(preArchive, srcInfo, dstPath)
|
||||
}
|
||||
|
||||
// In order to get the copy behavior right, we need to know information
|
||||
// about both the source and destination. The API is a simple tar
|
||||
// archive/extract API but we can use the stat info header about the
|
||||
// destination to be more informed about exactly what the destination is.
|
||||
func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpConfig) (err error) {
|
||||
srcPath := copyConfig.sourcePath
|
||||
dstPath := copyConfig.destPath
|
||||
|
||||
func copyToContainer(ctx context.Context, dockerCli *command.DockerCli, srcPath, dstContainer, dstPath string, cpParam *cpConfig, copyUIDGID bool) (err error) {
|
||||
if srcPath != "-" {
|
||||
// Get an absolute source path.
|
||||
srcPath, err = resolveLocalPath(srcPath)
|
||||
@ -191,10 +186,14 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo
|
||||
}
|
||||
}
|
||||
|
||||
client := dockerCli.Client()
|
||||
// In order to get the copy behavior right, we need to know information
|
||||
// about both the source and destination. The API is a simple tar
|
||||
// archive/extract API but we can use the stat info header about the
|
||||
// destination to be more informed about exactly what the destination is.
|
||||
|
||||
// Prepare destination copy info by stat-ing the container path.
|
||||
dstInfo := archive.CopyInfo{Path: dstPath}
|
||||
dstStat, err := client.ContainerStatPath(ctx, copyConfig.container, dstPath)
|
||||
dstStat, err := statContainerPath(ctx, dockerCli, dstContainer, dstPath)
|
||||
|
||||
// If the destination is a symbolic link, we should evaluate it.
|
||||
if err == nil && dstStat.Mode&os.ModeSymlink != 0 {
|
||||
@ -206,7 +205,7 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo
|
||||
}
|
||||
|
||||
dstInfo.Path = linkTarget
|
||||
dstStat, err = client.ContainerStatPath(ctx, copyConfig.container, linkTarget)
|
||||
dstStat, err = statContainerPath(ctx, dockerCli, dstContainer, linkTarget)
|
||||
}
|
||||
|
||||
// Ignore any error and assume that the parent directory of the destination
|
||||
@ -225,14 +224,15 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo
|
||||
)
|
||||
|
||||
if srcPath == "-" {
|
||||
// Use STDIN.
|
||||
content = os.Stdin
|
||||
resolvedDstPath = dstInfo.Path
|
||||
if !dstInfo.IsDir {
|
||||
return errors.Errorf("destination \"%s:%s\" must be a directory", copyConfig.container, dstPath)
|
||||
return errors.Errorf("destination \"%s:%s\" must be a directory", dstContainer, dstPath)
|
||||
}
|
||||
} else {
|
||||
// Prepare source copy info.
|
||||
srcInfo, err := archive.CopyInfoSourcePath(srcPath, copyConfig.followLink)
|
||||
srcInfo, err := archive.CopyInfoSourcePath(srcPath, cpParam.followLink)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -267,9 +267,10 @@ func copyToContainer(ctx context.Context, dockerCli command.Cli, copyConfig cpCo
|
||||
|
||||
options := types.CopyToContainerOptions{
|
||||
AllowOverwriteDirWithFile: false,
|
||||
CopyUIDGID: copyConfig.copyUIDGID,
|
||||
CopyUIDGID: copyUIDGID,
|
||||
}
|
||||
return client.CopyToContainer(ctx, copyConfig.container, resolvedDstPath, content, options)
|
||||
|
||||
return dockerCli.Client().CopyToContainer(ctx, dstContainer, resolvedDstPath, content, options)
|
||||
}
|
||||
|
||||
// We use `:` as a delimiter between CONTAINER and PATH, but `:` could also be
|
||||
|
||||
@ -1,160 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/gotestyourself/gotestyourself/fs"
|
||||
"github.com/gotestyourself/gotestyourself/skip"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRunCopyWithInvalidArguments(t *testing.T) {
|
||||
var testcases = []struct {
|
||||
doc string
|
||||
options copyOptions
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
doc: "copy between container",
|
||||
options: copyOptions{
|
||||
source: "first:/path",
|
||||
destination: "second:/path",
|
||||
},
|
||||
expectedErr: "copying between containers is not supported",
|
||||
},
|
||||
{
|
||||
doc: "copy without a container",
|
||||
options: copyOptions{
|
||||
source: "./source",
|
||||
destination: "./dest",
|
||||
},
|
||||
expectedErr: "must specify at least one container source",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
err := runCopy(test.NewFakeCli(nil), testcase.options)
|
||||
assert.EqualError(t, err, testcase.expectedErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunCopyFromContainerToStdout(t *testing.T) {
|
||||
tarContent := "the tar content"
|
||||
|
||||
fakeClient := &fakeClient{
|
||||
containerCopyFromFunc: func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
assert.Equal(t, "container", container)
|
||||
return ioutil.NopCloser(strings.NewReader(tarContent)), types.ContainerPathStat{}, nil
|
||||
},
|
||||
}
|
||||
options := copyOptions{source: "container:/path", destination: "-"}
|
||||
cli := test.NewFakeCli(fakeClient)
|
||||
err := runCopy(cli, options)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tarContent, cli.OutBuffer().String())
|
||||
assert.Equal(t, "", cli.ErrBuffer().String())
|
||||
}
|
||||
|
||||
func TestRunCopyFromContainerToFilesystem(t *testing.T) {
|
||||
destDir := fs.NewDir(t, "cp-test",
|
||||
fs.WithFile("file1", "content\n"))
|
||||
defer destDir.Remove()
|
||||
|
||||
fakeClient := &fakeClient{
|
||||
containerCopyFromFunc: func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
assert.Equal(t, "container", container)
|
||||
readCloser, err := archive.TarWithOptions(destDir.Path(), &archive.TarOptions{})
|
||||
return readCloser, types.ContainerPathStat{}, err
|
||||
},
|
||||
}
|
||||
options := copyOptions{source: "container:/path", destination: destDir.Path()}
|
||||
cli := test.NewFakeCli(fakeClient)
|
||||
err := runCopy(cli, options)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "", cli.OutBuffer().String())
|
||||
assert.Equal(t, "", cli.ErrBuffer().String())
|
||||
|
||||
content, err := ioutil.ReadFile(destDir.Join("file1"))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "content\n", string(content))
|
||||
}
|
||||
|
||||
func TestRunCopyFromContainerToFilesystemMissingDestinationDirectory(t *testing.T) {
|
||||
destDir := fs.NewDir(t, "cp-test",
|
||||
fs.WithFile("file1", "content\n"))
|
||||
defer destDir.Remove()
|
||||
|
||||
fakeClient := &fakeClient{
|
||||
containerCopyFromFunc: func(container, srcPath string) (io.ReadCloser, types.ContainerPathStat, error) {
|
||||
assert.Equal(t, "container", container)
|
||||
readCloser, err := archive.TarWithOptions(destDir.Path(), &archive.TarOptions{})
|
||||
return readCloser, types.ContainerPathStat{}, err
|
||||
},
|
||||
}
|
||||
|
||||
options := copyOptions{
|
||||
source: "container:/path",
|
||||
destination: destDir.Join("missing", "foo"),
|
||||
}
|
||||
cli := test.NewFakeCli(fakeClient)
|
||||
err := runCopy(cli, options)
|
||||
testutil.ErrorContains(t, err, destDir.Join("missing"))
|
||||
}
|
||||
|
||||
func TestSplitCpArg(t *testing.T) {
|
||||
var testcases = []struct {
|
||||
doc string
|
||||
path string
|
||||
os string
|
||||
expectedContainer string
|
||||
expectedPath string
|
||||
}{
|
||||
{
|
||||
doc: "absolute path with colon",
|
||||
os: "linux",
|
||||
path: "/abs/path:withcolon",
|
||||
expectedPath: "/abs/path:withcolon",
|
||||
},
|
||||
{
|
||||
doc: "relative path with colon",
|
||||
path: "./relative:path",
|
||||
expectedPath: "./relative:path",
|
||||
},
|
||||
{
|
||||
doc: "absolute path with drive",
|
||||
os: "windows",
|
||||
path: `d:\abs\path`,
|
||||
expectedPath: `d:\abs\path`,
|
||||
},
|
||||
{
|
||||
doc: "no separator",
|
||||
path: "relative/path",
|
||||
expectedPath: "relative/path",
|
||||
},
|
||||
{
|
||||
doc: "with separator",
|
||||
path: "container:/opt/foo",
|
||||
expectedPath: "/opt/foo",
|
||||
expectedContainer: "container",
|
||||
},
|
||||
}
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
skip.IfCondition(t, testcase.os != "" && testcase.os != runtime.GOOS)
|
||||
|
||||
container, path := splitCpArg(testcase.path)
|
||||
assert.Equal(t, testcase.expectedContainer, container)
|
||||
assert.Equal(t, testcase.expectedPath, path)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -21,8 +21,7 @@ import (
|
||||
)
|
||||
|
||||
type createOptions struct {
|
||||
name string
|
||||
platform string
|
||||
name string
|
||||
}
|
||||
|
||||
// NewCreateCommand creates a new cobra.Command for `docker create`
|
||||
@ -52,7 +51,6 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// with hostname
|
||||
flags.Bool("help", false, "Print usage")
|
||||
|
||||
command.AddPlatformFlag(flags, &opts.platform)
|
||||
command.AddTrustVerificationFlags(flags)
|
||||
copts = addFlags(flags)
|
||||
return cmd
|
||||
@ -64,7 +62,7 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *createOptions,
|
||||
reportError(dockerCli.Err(), "create", err.Error(), true)
|
||||
return cli.StatusError{StatusCode: 125}
|
||||
}
|
||||
response, err := createContainer(context.Background(), dockerCli, containerConfig, opts.name, opts.platform)
|
||||
response, err := createContainer(context.Background(), dockerCli, containerConfig, opts.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -72,7 +70,7 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *createOptions,
|
||||
return nil
|
||||
}
|
||||
|
||||
func pullImage(ctx context.Context, dockerCli command.Cli, image string, platform string, out io.Writer) error {
|
||||
func pullImage(ctx context.Context, dockerCli command.Cli, image string, out io.Writer) error {
|
||||
ref, err := reference.ParseNormalizedNamed(image)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -92,7 +90,6 @@ func pullImage(ctx context.Context, dockerCli command.Cli, image string, platfor
|
||||
|
||||
options := types.ImageCreateOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
Platform: platform,
|
||||
}
|
||||
|
||||
responseBody, err := dockerCli.Client().ImageCreate(ctx, image, options)
|
||||
@ -158,7 +155,7 @@ func newCIDFile(path string) (*cidFile, error) {
|
||||
return &cidFile{path: path, file: f}, nil
|
||||
}
|
||||
|
||||
func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, name string, platform string) (*container.ContainerCreateCreatedBody, error) {
|
||||
func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, name string) (*container.ContainerCreateCreatedBody, error) {
|
||||
config := containerConfig.Config
|
||||
hostConfig := containerConfig.HostConfig
|
||||
networkingConfig := containerConfig.NetworkingConfig
|
||||
@ -197,11 +194,11 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig
|
||||
|
||||
//if image not found try to pull it
|
||||
if err != nil {
|
||||
if apiclient.IsErrNotFound(err) && namedRef != nil {
|
||||
if apiclient.IsErrImageNotFound(err) && namedRef != nil {
|
||||
fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef))
|
||||
|
||||
// we don't want to write to stdout anything apart from container.ID
|
||||
if err := pullImage(ctx, dockerCli, config.Image, platform, stderr); err != nil {
|
||||
if err := pullImage(ctx, dockerCli, config.Image, stderr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil {
|
||||
|
||||
@ -5,7 +5,6 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@ -107,7 +106,7 @@ func TestCreateContainerPullsImageIfMissing(t *testing.T) {
|
||||
},
|
||||
HostConfig: &container.HostConfig{},
|
||||
}
|
||||
body, err := createContainer(context.Background(), cli, config, "name", runtime.GOOS)
|
||||
body, err := createContainer(context.Background(), cli, config, "name")
|
||||
require.NoError(t, err)
|
||||
expected := container.ContainerCreateCreatedBody{ID: containerID}
|
||||
assert.Equal(t, expected, *body)
|
||||
|
||||
@ -14,7 +14,7 @@ type diffOptions struct {
|
||||
}
|
||||
|
||||
// NewDiffCommand creates a new cobra.Command for `docker diff`
|
||||
func NewDiffCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewDiffCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts diffOptions
|
||||
|
||||
return &cobra.Command{
|
||||
@ -28,7 +28,7 @@ func NewDiffCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func runDiff(dockerCli command.Cli, opts *diffOptions) error {
|
||||
func runDiff(dockerCli *command.DockerCli, opts *diffOptions) error {
|
||||
if opts.container == "" {
|
||||
return errors.New("Container name cannot be empty")
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
apiclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/spf13/cobra"
|
||||
@ -24,7 +25,6 @@ type execOptions struct {
|
||||
user string
|
||||
privileged bool
|
||||
env opts.ListOpts
|
||||
workdir string
|
||||
container string
|
||||
command []string
|
||||
}
|
||||
@ -59,8 +59,6 @@ func NewExecCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.BoolVarP(&options.privileged, "privileged", "", false, "Give extended privileges to the command")
|
||||
flags.VarP(&options.env, "env", "e", "Set environment variables")
|
||||
flags.SetAnnotation("env", "version", []string{"1.25"})
|
||||
flags.StringVarP(&options.workdir, "workdir", "w", "", "Working directory inside the container")
|
||||
flags.SetAnnotation("workdir", "version", []string{"1.35"})
|
||||
|
||||
return cmd
|
||||
}
|
||||
@ -108,6 +106,7 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execConfig *typ
|
||||
var (
|
||||
out, stderr io.Writer
|
||||
in io.ReadCloser
|
||||
errCh chan error
|
||||
)
|
||||
|
||||
if execConfig.AttachStdin {
|
||||
@ -125,33 +124,24 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, execConfig *typ
|
||||
}
|
||||
|
||||
client := dockerCli.Client()
|
||||
execStartCheck := types.ExecStartCheck{
|
||||
Tty: execConfig.Tty,
|
||||
}
|
||||
resp, err := client.ContainerExecAttach(ctx, execID, execStartCheck)
|
||||
resp, err := client.ContainerExecAttach(ctx, execID, *execConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Close()
|
||||
errCh = promise.Go(func() error {
|
||||
streamer := hijackedIOStreamer{
|
||||
streams: dockerCli,
|
||||
inputStream: in,
|
||||
outputStream: out,
|
||||
errorStream: stderr,
|
||||
resp: resp,
|
||||
tty: execConfig.Tty,
|
||||
detachKeys: execConfig.DetachKeys,
|
||||
}
|
||||
|
||||
errCh := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
defer close(errCh)
|
||||
errCh <- func() error {
|
||||
streamer := hijackedIOStreamer{
|
||||
streams: dockerCli,
|
||||
inputStream: in,
|
||||
outputStream: out,
|
||||
errorStream: stderr,
|
||||
resp: resp,
|
||||
tty: execConfig.Tty,
|
||||
detachKeys: execConfig.DetachKeys,
|
||||
}
|
||||
|
||||
return streamer.stream(ctx)
|
||||
}()
|
||||
}()
|
||||
return streamer.stream(ctx)
|
||||
})
|
||||
|
||||
if execConfig.Tty && dockerCli.In().IsTerminal() {
|
||||
if err := MonitorTtySize(ctx, dockerCli, execID, true); err != nil {
|
||||
@ -193,7 +183,6 @@ func parseExec(opts execOptions, configFile *configfile.ConfigFile) *types.ExecC
|
||||
Cmd: opts.command,
|
||||
Detach: opts.detach,
|
||||
Env: opts.env.GetAll(),
|
||||
WorkingDir: opts.workdir,
|
||||
}
|
||||
|
||||
// If -d is not set, attach to everything by default
|
||||
|
||||
@ -16,7 +16,7 @@ type exportOptions struct {
|
||||
}
|
||||
|
||||
// NewExportCommand creates a new `docker export` command
|
||||
func NewExportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewExportCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts exportOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -36,7 +36,7 @@ func NewExportCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runExport(dockerCli command.Cli, opts exportOptions) error {
|
||||
func runExport(dockerCli *command.DockerCli, opts exportOptions) error {
|
||||
if opts.output == "" && dockerCli.Out().IsTerminal() {
|
||||
return errors.New("cowardly refusing to save to a terminal. Use the -o flag or redirect")
|
||||
}
|
||||
|
||||
@ -185,7 +185,6 @@ func setRawTerminal(streams command.Streams) error {
|
||||
return streams.Out().SetRawTerminal()
|
||||
}
|
||||
|
||||
// nolint: unparam
|
||||
func restoreTerminal(streams command.Streams, in io.Closer) error {
|
||||
streams.In().RestoreTerminal()
|
||||
streams.Out().RestoreTerminal()
|
||||
|
||||
@ -15,7 +15,7 @@ type inspectOptions struct {
|
||||
}
|
||||
|
||||
// newInspectCommand creates a new cobra.Command for `docker container inspect`
|
||||
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts inspectOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -35,7 +35,7 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
||||
func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
|
||||
client := dockerCli.Client()
|
||||
ctx := context.Background()
|
||||
|
||||
|
||||
@ -18,7 +18,7 @@ type killOptions struct {
|
||||
}
|
||||
|
||||
// NewKillCommand creates a new cobra.Command for `docker kill`
|
||||
func NewKillCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewKillCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts killOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -36,7 +36,7 @@ func NewKillCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runKill(dockerCli command.Cli, opts *killOptions) error {
|
||||
func runKill(dockerCli *command.DockerCli, opts *killOptions) error {
|
||||
var errs []string
|
||||
ctx := context.Background()
|
||||
errChan := parallelOperation(ctx, opts.containers, func(ctx context.Context, container string) error {
|
||||
|
||||
@ -25,7 +25,7 @@ type psOptions struct {
|
||||
}
|
||||
|
||||
// NewPsCommand creates a new cobra.Command for `docker ps`
|
||||
func NewPsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewPsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
options := psOptions{filter: opts.NewFilterOpt()}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -51,7 +51,7 @@ func NewPsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func newListCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
cmd := *NewPsCommand(dockerCli)
|
||||
cmd.Aliases = []string{"ps", "list"}
|
||||
cmd.Use = "ls [OPTIONS]"
|
||||
@ -109,7 +109,7 @@ func buildContainerListOptions(opts *psOptions) (*types.ContainerListOptions, er
|
||||
return options, nil
|
||||
}
|
||||
|
||||
func runPs(dockerCli command.Cli, options *psOptions) error {
|
||||
func runPs(dockerCli *command.DockerCli, options *psOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
listOptions, err := buildContainerListOptions(options)
|
||||
|
||||
@ -14,7 +14,6 @@ import (
|
||||
type logsOptions struct {
|
||||
follow bool
|
||||
since string
|
||||
until string
|
||||
timestamps bool
|
||||
details bool
|
||||
tail string
|
||||
@ -23,7 +22,7 @@ type logsOptions struct {
|
||||
}
|
||||
|
||||
// NewLogsCommand creates a new cobra.Command for `docker logs`
|
||||
func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewLogsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts logsOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -39,22 +38,19 @@ func NewLogsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.follow, "follow", "f", false, "Follow log output")
|
||||
flags.StringVar(&opts.since, "since", "", "Show logs since timestamp (e.g. 2013-01-02T13:23:37) or relative (e.g. 42m for 42 minutes)")
|
||||
flags.StringVar(&opts.until, "until", "", "Show logs before a timestamp (e.g. 2013-01-02T13:23:37) or relative (e.g. 42m for 42 minutes)")
|
||||
flags.SetAnnotation("until", "version", []string{"1.35"})
|
||||
flags.BoolVarP(&opts.timestamps, "timestamps", "t", false, "Show timestamps")
|
||||
flags.BoolVar(&opts.details, "details", false, "Show extra details provided to logs")
|
||||
flags.StringVar(&opts.tail, "tail", "all", "Number of lines to show from the end of the logs")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runLogs(dockerCli command.Cli, opts *logsOptions) error {
|
||||
func runLogs(dockerCli *command.DockerCli, opts *logsOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
options := types.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Since: opts.since,
|
||||
Until: opts.until,
|
||||
Timestamps: opts.timestamps,
|
||||
Follow: opts.follow,
|
||||
Tail: opts.tail,
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var logFn = func(expectedOut string) func(string, types.ContainerLogsOptions) (io.ReadCloser, error) {
|
||||
return func(container string, opts types.ContainerLogsOptions) (io.ReadCloser, error) {
|
||||
return ioutil.NopCloser(strings.NewReader(expectedOut)), nil
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunLogs(t *testing.T) {
|
||||
inspectFn := func(containerID string) (types.ContainerJSON, error) {
|
||||
return types.ContainerJSON{
|
||||
Config: &container.Config{Tty: true},
|
||||
ContainerJSONBase: &types.ContainerJSONBase{State: &types.ContainerState{Running: false}},
|
||||
}, nil
|
||||
}
|
||||
|
||||
var testcases = []struct {
|
||||
doc string
|
||||
options *logsOptions
|
||||
client fakeClient
|
||||
expectedError string
|
||||
expectedOut string
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
doc: "successful logs",
|
||||
expectedOut: "foo",
|
||||
options: &logsOptions{},
|
||||
client: fakeClient{logFunc: logFn("foo"), inspectFunc: inspectFn},
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&testcase.client)
|
||||
|
||||
err := runLogs(cli, testcase.options)
|
||||
if testcase.expectedError != "" {
|
||||
testutil.ErrorContains(t, err, testcase.expectedError)
|
||||
} else {
|
||||
if !assert.NoError(t, err) {
|
||||
return
|
||||
}
|
||||
}
|
||||
assert.Equal(t, testcase.expectedOut, cli.OutBuffer().String())
|
||||
assert.Equal(t, testcase.expectedErr, cli.ErrBuffer().String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -43,9 +43,11 @@ func TestValidateAttach(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: unparam
|
||||
func parseRun(args []string) (*container.Config, *container.HostConfig, *networktypes.NetworkingConfig, error) {
|
||||
flags, copts := setupRunFlags()
|
||||
flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
|
||||
flags.SetOutput(ioutil.Discard)
|
||||
flags.Usage = nil
|
||||
copts := addFlags(flags)
|
||||
if err := flags.Parse(args); err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
@ -57,14 +59,6 @@ func parseRun(args []string) (*container.Config, *container.HostConfig, *network
|
||||
return containerConfig.Config, containerConfig.HostConfig, containerConfig.NetworkingConfig, err
|
||||
}
|
||||
|
||||
func setupRunFlags() (*pflag.FlagSet, *containerOptions) {
|
||||
flags := pflag.NewFlagSet("run", pflag.ContinueOnError)
|
||||
flags.SetOutput(ioutil.Discard)
|
||||
flags.Usage = nil
|
||||
copts := addFlags(flags)
|
||||
return flags, copts
|
||||
}
|
||||
|
||||
func parseMustError(t *testing.T, args string) {
|
||||
_, _, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
|
||||
assert.Error(t, err, args)
|
||||
@ -232,21 +226,20 @@ func TestParseWithMacAddress(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunFlagsParseWithMemory(t *testing.T) {
|
||||
flags, _ := setupRunFlags()
|
||||
args := []string{"--memory=invalid", "img", "cmd"}
|
||||
err := flags.Parse(args)
|
||||
testutil.ErrorContains(t, err, `invalid argument "invalid" for "-m, --memory" flag`)
|
||||
func TestParseWithMemory(t *testing.T) {
|
||||
invalidMemory := "--memory=invalid"
|
||||
_, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"})
|
||||
testutil.ErrorContains(t, err, invalidMemory)
|
||||
|
||||
_, hostconfig := mustParse(t, "--memory=1G")
|
||||
assert.Equal(t, int64(1073741824), hostconfig.Memory)
|
||||
}
|
||||
|
||||
func TestParseWithMemorySwap(t *testing.T) {
|
||||
flags, _ := setupRunFlags()
|
||||
args := []string{"--memory-swap=invalid", "img", "cmd"}
|
||||
err := flags.Parse(args)
|
||||
testutil.ErrorContains(t, err, `invalid argument "invalid" for "--memory-swap" flag`)
|
||||
invalidMemory := "--memory-swap=invalid"
|
||||
|
||||
_, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"})
|
||||
testutil.ErrorContains(t, err, invalidMemory)
|
||||
|
||||
_, hostconfig := mustParse(t, "--memory-swap=1G")
|
||||
assert.Equal(t, int64(1073741824), hostconfig.MemorySwap)
|
||||
@ -371,10 +364,7 @@ func TestParseDevice(t *testing.T) {
|
||||
|
||||
func TestParseModes(t *testing.T) {
|
||||
// pid ko
|
||||
flags, copts := setupRunFlags()
|
||||
args := []string{"--pid=container:", "img", "cmd"}
|
||||
require.NoError(t, flags.Parse(args))
|
||||
_, err := parse(flags, copts)
|
||||
_, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"})
|
||||
testutil.ErrorContains(t, err, "--pid: invalid PID mode")
|
||||
|
||||
// pid ok
|
||||
@ -394,18 +384,14 @@ func TestParseModes(t *testing.T) {
|
||||
if !hostconfig.UTSMode.Valid() {
|
||||
t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunFlagsParseShmSize(t *testing.T) {
|
||||
// shm-size ko
|
||||
flags, _ := setupRunFlags()
|
||||
args := []string{"--shm-size=a128m", "img", "cmd"}
|
||||
expectedErr := `invalid argument "a128m" for "--shm-size" flag: invalid size: 'a128m'`
|
||||
err := flags.Parse(args)
|
||||
expectedErr := `invalid argument "a128m" for --shm-size=a128m: invalid size: 'a128m'`
|
||||
_, _, _, err = parseRun([]string{"--shm-size=a128m", "img", "cmd"})
|
||||
testutil.ErrorContains(t, err, expectedErr)
|
||||
|
||||
// shm-size ok
|
||||
_, hostconfig, _, err := parseRun([]string{"--shm-size=128m", "img", "cmd"})
|
||||
_, hostconfig, _, err = parseRun([]string{"--shm-size=128m", "img", "cmd"})
|
||||
require.NoError(t, err)
|
||||
if hostconfig.ShmSize != 134217728 {
|
||||
t.Fatalf("Expected a valid ShmSize, got %d", hostconfig.ShmSize)
|
||||
|
||||
@ -16,7 +16,7 @@ type pauseOptions struct {
|
||||
}
|
||||
|
||||
// NewPauseCommand creates a new cobra.Command for `docker pause`
|
||||
func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewPauseCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts pauseOptions
|
||||
|
||||
return &cobra.Command{
|
||||
@ -30,7 +30,7 @@ func NewPauseCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
func runPause(dockerCli command.Cli, opts *pauseOptions) error {
|
||||
func runPause(dockerCli *command.DockerCli, opts *pauseOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var errs []string
|
||||
|
||||
@ -19,7 +19,7 @@ type portOptions struct {
|
||||
}
|
||||
|
||||
// NewPortCommand creates a new cobra.Command for `docker port`
|
||||
func NewPortCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewPortCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts portOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -37,7 +37,7 @@ func NewPortCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPort(dockerCli command.Cli, opts *portOptions) error {
|
||||
func runPort(dockerCli *command.DockerCli, opts *portOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
c, err := dockerCli.Client().ContainerInspect(ctx, opts.container)
|
||||
|
||||
@ -35,7 +35,7 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
|
||||
return nil
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
Tags: map[string]string{"version": "1.25"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -52,12 +52,12 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
|
||||
pruneFilters := command.PruneFilters(dockerCli, options.filter.Value())
|
||||
|
||||
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return 0, "", nil
|
||||
return
|
||||
}
|
||||
|
||||
report, err := dockerCli.Client().ContainersPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
return
|
||||
}
|
||||
|
||||
if len(report.ContainersDeleted) > 0 {
|
||||
@ -68,7 +68,7 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
|
||||
spaceReclaimed = report.SpaceReclaimed
|
||||
}
|
||||
|
||||
return spaceReclaimed, output, nil
|
||||
return
|
||||
}
|
||||
|
||||
// RunPrune calls the Container Prune API
|
||||
|
||||
@ -17,7 +17,7 @@ type renameOptions struct {
|
||||
}
|
||||
|
||||
// NewRenameCommand creates a new cobra.Command for `docker rename`
|
||||
func NewRenameCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewRenameCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts renameOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -33,7 +33,7 @@ func NewRenameCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRename(dockerCli command.Cli, opts *renameOptions) error {
|
||||
func runRename(dockerCli *command.DockerCli, opts *renameOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
oldName := strings.TrimSpace(opts.oldName)
|
||||
|
||||
@ -20,7 +20,7 @@ type restartOptions struct {
|
||||
}
|
||||
|
||||
// NewRestartCommand creates a new cobra.Command for `docker restart`
|
||||
func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewRestartCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts restartOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -39,7 +39,7 @@ func NewRestartCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRestart(dockerCli command.Cli, opts *restartOptions) error {
|
||||
func runRestart(dockerCli *command.DockerCli, opts *restartOptions) error {
|
||||
ctx := context.Background()
|
||||
var errs []string
|
||||
var timeout *time.Duration
|
||||
|
||||
@ -21,7 +21,7 @@ type rmOptions struct {
|
||||
}
|
||||
|
||||
// NewRmCommand creates a new cobra.Command for `docker rm`
|
||||
func NewRmCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewRmCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts rmOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -41,7 +41,7 @@ func NewRmCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runRm(dockerCli command.Cli, opts *rmOptions) error {
|
||||
func runRm(dockerCli *command.DockerCli, opts *rmOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var errs []string
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
"github.com/docker/cli/opts"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/pkg/errors"
|
||||
@ -29,11 +30,10 @@ type runOptions struct {
|
||||
sigProxy bool
|
||||
name string
|
||||
detachKeys string
|
||||
platform string
|
||||
}
|
||||
|
||||
// NewRunCommand create a new `docker run` command
|
||||
func NewRunCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewRunCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts runOptions
|
||||
var copts *containerOptions
|
||||
|
||||
@ -63,7 +63,6 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// with hostname
|
||||
flags.Bool("help", false, "Print usage")
|
||||
|
||||
command.AddPlatformFlag(flags, &opts.platform)
|
||||
command.AddTrustVerificationFlags(flags)
|
||||
copts = addFlags(flags)
|
||||
return cmd
|
||||
@ -98,7 +97,7 @@ func isLocalhost(ip string) bool {
|
||||
return localhostIPRegexp.MatchString(ip)
|
||||
}
|
||||
|
||||
func runRun(dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
|
||||
func runRun(dockerCli *command.DockerCli, flags *pflag.FlagSet, ropts *runOptions, copts *containerOptions) error {
|
||||
proxyConfig := dockerCli.ConfigFile().ParseProxyConfig(dockerCli.Client().DaemonHost(), copts.env.GetAll())
|
||||
newEnv := []string{}
|
||||
for k, v := range proxyConfig {
|
||||
@ -119,7 +118,7 @@ func runRun(dockerCli command.Cli, flags *pflag.FlagSet, ropts *runOptions, copt
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptions, containerConfig *containerConfig) error {
|
||||
func runContainer(dockerCli *command.DockerCli, opts *runOptions, copts *containerOptions, containerConfig *containerConfig) error {
|
||||
config := containerConfig.Config
|
||||
hostConfig := containerConfig.HostConfig
|
||||
stdout, stderr := dockerCli.Out(), dockerCli.Err()
|
||||
@ -162,7 +161,7 @@ func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptio
|
||||
|
||||
ctx, cancelFun := context.WithCancel(context.Background())
|
||||
|
||||
createResponse, err := createContainer(ctx, dockerCli, containerConfig, opts.name, opts.platform)
|
||||
createResponse, err := createContainer(ctx, dockerCli, containerConfig, opts.name)
|
||||
if err != nil {
|
||||
reportError(stderr, cmdPath, err.Error(), true)
|
||||
return runStartContainerErr(err)
|
||||
@ -292,27 +291,22 @@ func attachContainer(
|
||||
return nil, errAttach
|
||||
}
|
||||
|
||||
ch := make(chan error, 1)
|
||||
*errCh = ch
|
||||
*errCh = promise.Go(func() error {
|
||||
streamer := hijackedIOStreamer{
|
||||
streams: dockerCli,
|
||||
inputStream: in,
|
||||
outputStream: out,
|
||||
errorStream: cerr,
|
||||
resp: resp,
|
||||
tty: config.Tty,
|
||||
detachKeys: options.DetachKeys,
|
||||
}
|
||||
|
||||
go func() {
|
||||
ch <- func() error {
|
||||
streamer := hijackedIOStreamer{
|
||||
streams: dockerCli,
|
||||
inputStream: in,
|
||||
outputStream: out,
|
||||
errorStream: cerr,
|
||||
resp: resp,
|
||||
tty: config.Tty,
|
||||
detachKeys: options.DetachKeys,
|
||||
}
|
||||
|
||||
if errHijack := streamer.stream(ctx); errHijack != nil {
|
||||
return errHijack
|
||||
}
|
||||
return errAttach
|
||||
}()
|
||||
}()
|
||||
if errHijack := streamer.stream(ctx); errHijack != nil {
|
||||
return errHijack
|
||||
}
|
||||
return errAttach
|
||||
})
|
||||
return resp.Close, nil
|
||||
}
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/promise"
|
||||
"github.com/docker/docker/pkg/signal"
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/pkg/errors"
|
||||
@ -27,7 +28,7 @@ type startOptions struct {
|
||||
}
|
||||
|
||||
// NewStartCommand creates a new cobra.Command for `docker start`
|
||||
func NewStartCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewStartCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts startOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -53,7 +54,7 @@ func NewStartCommand(dockerCli command.Cli) *cobra.Command {
|
||||
}
|
||||
|
||||
// nolint: gocyclo
|
||||
func runStart(dockerCli command.Cli, opts *startOptions) error {
|
||||
func runStart(dockerCli *command.DockerCli, opts *startOptions) error {
|
||||
ctx, cancelFun := context.WithCancel(context.Background())
|
||||
|
||||
if opts.attach || opts.openStdin {
|
||||
@ -102,28 +103,23 @@ func runStart(dockerCli command.Cli, opts *startOptions) error {
|
||||
return errAttach
|
||||
}
|
||||
defer resp.Close()
|
||||
cErr := promise.Go(func() error {
|
||||
streamer := hijackedIOStreamer{
|
||||
streams: dockerCli,
|
||||
inputStream: in,
|
||||
outputStream: dockerCli.Out(),
|
||||
errorStream: dockerCli.Err(),
|
||||
resp: resp,
|
||||
tty: c.Config.Tty,
|
||||
detachKeys: options.DetachKeys,
|
||||
}
|
||||
|
||||
cErr := make(chan error, 1)
|
||||
|
||||
go func() {
|
||||
cErr <- func() error {
|
||||
streamer := hijackedIOStreamer{
|
||||
streams: dockerCli,
|
||||
inputStream: in,
|
||||
outputStream: dockerCli.Out(),
|
||||
errorStream: dockerCli.Err(),
|
||||
resp: resp,
|
||||
tty: c.Config.Tty,
|
||||
detachKeys: options.DetachKeys,
|
||||
}
|
||||
|
||||
errHijack := streamer.stream(ctx)
|
||||
if errHijack == nil {
|
||||
return errAttach
|
||||
}
|
||||
return errHijack
|
||||
}()
|
||||
}()
|
||||
errHijack := streamer.stream(ctx)
|
||||
if errHijack == nil {
|
||||
return errAttach
|
||||
}
|
||||
return errHijack
|
||||
})
|
||||
|
||||
// 3. We should open a channel for receiving status code of the container
|
||||
// no matter it's detached, removed on daemon side(--rm) or exit normally.
|
||||
@ -181,7 +177,7 @@ func runStart(dockerCli command.Cli, opts *startOptions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func startContainersWithoutAttachments(ctx context.Context, dockerCli command.Cli, containers []string) error {
|
||||
func startContainersWithoutAttachments(ctx context.Context, dockerCli *command.DockerCli, containers []string) error {
|
||||
var failedContainers []string
|
||||
for _, container := range containers {
|
||||
if err := dockerCli.Client().ContainerStart(ctx, container, types.ContainerStartOptions{}); err != nil {
|
||||
|
||||
@ -21,13 +21,12 @@ import (
|
||||
type statsOptions struct {
|
||||
all bool
|
||||
noStream bool
|
||||
noTrunc bool
|
||||
format string
|
||||
containers []string
|
||||
}
|
||||
|
||||
// NewStatsCommand creates a new cobra.Command for `docker stats`
|
||||
func NewStatsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewStatsCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts statsOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -43,7 +42,6 @@ func NewStatsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
flags.BoolVarP(&opts.all, "all", "a", false, "Show all containers (default shows just running)")
|
||||
flags.BoolVar(&opts.noStream, "no-stream", false, "Disable streaming stats and only pull the first result")
|
||||
flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate output")
|
||||
flags.StringVar(&opts.format, "format", "", "Pretty-print images using a Go template")
|
||||
return cmd
|
||||
}
|
||||
@ -51,7 +49,7 @@ func NewStatsCommand(dockerCli command.Cli) *cobra.Command {
|
||||
// runStats displays a live stream of resource usage statistics for one or more containers.
|
||||
// This shows real-time information on CPU usage, memory usage, and network I/O.
|
||||
// nolint: gocyclo
|
||||
func runStats(dockerCli command.Cli, opts *statsOptions) error {
|
||||
func runStats(dockerCli *command.DockerCli, opts *statsOptions) error {
|
||||
showAll := len(opts.containers) == 0
|
||||
closeChan := make(chan error)
|
||||
|
||||
@ -216,7 +214,7 @@ func runStats(dockerCli command.Cli, opts *statsOptions) error {
|
||||
ccstats = append(ccstats, c.GetStatistics())
|
||||
}
|
||||
cStats.mu.Unlock()
|
||||
if err = formatter.ContainerStatsWrite(statsCtx, ccstats, daemonOSType, !opts.noTrunc); err != nil {
|
||||
if err = formatter.ContainerStatsWrite(statsCtx, ccstats, daemonOSType); err != nil {
|
||||
break
|
||||
}
|
||||
if len(cStats.cs) == 0 && !showAll {
|
||||
|
||||
@ -200,8 +200,7 @@ func calculateCPUPercentWindows(v *types.StatsJSON) float64 {
|
||||
return 0.00
|
||||
}
|
||||
|
||||
func calculateBlockIO(blkio types.BlkioStats) (uint64, uint64) {
|
||||
var blkRead, blkWrite uint64
|
||||
func calculateBlockIO(blkio types.BlkioStats) (blkRead uint64, blkWrite uint64) {
|
||||
for _, bioEntry := range blkio.IoServiceBytesRecursive {
|
||||
switch strings.ToLower(bioEntry.Op) {
|
||||
case "read":
|
||||
@ -210,7 +209,7 @@ func calculateBlockIO(blkio types.BlkioStats) (uint64, uint64) {
|
||||
blkWrite = blkWrite + bioEntry.Value
|
||||
}
|
||||
}
|
||||
return blkRead, blkWrite
|
||||
return
|
||||
}
|
||||
|
||||
func calculateNetwork(network map[string]types.NetworkStats) (float64, float64) {
|
||||
|
||||
@ -20,7 +20,7 @@ type stopOptions struct {
|
||||
}
|
||||
|
||||
// NewStopCommand creates a new cobra.Command for `docker stop`
|
||||
func NewStopCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewStopCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts stopOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -39,7 +39,7 @@ func NewStopCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runStop(dockerCli command.Cli, opts *stopOptions) error {
|
||||
func runStop(dockerCli *command.DockerCli, opts *stopOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var timeout *time.Duration
|
||||
|
||||
@ -18,7 +18,7 @@ type topOptions struct {
|
||||
}
|
||||
|
||||
// NewTopCommand creates a new cobra.Command for `docker top`
|
||||
func NewTopCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewTopCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts topOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -38,7 +38,7 @@ func NewTopCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runTop(dockerCli command.Cli, opts *topOptions) error {
|
||||
func runTop(dockerCli *command.DockerCli, opts *topOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
procList, err := dockerCli.Client().ContainerTop(ctx, opts.container, opts.args)
|
||||
|
||||
@ -16,7 +16,7 @@ type unpauseOptions struct {
|
||||
}
|
||||
|
||||
// NewUnpauseCommand creates a new cobra.Command for `docker unpause`
|
||||
func NewUnpauseCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewUnpauseCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts unpauseOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -31,7 +31,7 @@ func NewUnpauseCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runUnpause(dockerCli command.Cli, opts *unpauseOptions) error {
|
||||
func runUnpause(dockerCli *command.DockerCli, opts *unpauseOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var errs []string
|
||||
|
||||
@ -35,7 +35,7 @@ type updateOptions struct {
|
||||
}
|
||||
|
||||
// NewUpdateCommand creates a new cobra.Command for `docker update`
|
||||
func NewUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewUpdateCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var options updateOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -72,7 +72,7 @@ func NewUpdateCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runUpdate(dockerCli command.Cli, options *updateOptions) error {
|
||||
func runUpdate(dockerCli *command.DockerCli, options *updateOptions) error {
|
||||
var err error
|
||||
|
||||
if options.nFlag == 0 {
|
||||
|
||||
@ -13,7 +13,7 @@ import (
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func waitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID string, waitRemove bool) <-chan int {
|
||||
func waitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, containerID string, waitRemove bool) <-chan int {
|
||||
if len(containerID) == 0 {
|
||||
// containerID can never be empty
|
||||
panic("Internal Error: waitExitOrRemoved needs a containerID as parameter")
|
||||
@ -37,12 +37,7 @@ func waitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID s
|
||||
go func() {
|
||||
select {
|
||||
case result := <-resultC:
|
||||
if result.Error != nil {
|
||||
logrus.Errorf("Error waiting for container: %v", result.Error.Message)
|
||||
statusC <- 125
|
||||
} else {
|
||||
statusC <- int(result.StatusCode)
|
||||
}
|
||||
statusC <- int(result.StatusCode)
|
||||
case err := <-errC:
|
||||
logrus.Errorf("error waiting for container: %v", err)
|
||||
statusC <- 125
|
||||
@ -52,7 +47,7 @@ func waitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID s
|
||||
return statusC
|
||||
}
|
||||
|
||||
func legacyWaitExitOrRemoved(ctx context.Context, dockerCli command.Cli, containerID string, waitRemove bool) <-chan int {
|
||||
func legacyWaitExitOrRemoved(ctx context.Context, dockerCli *command.DockerCli, containerID string, waitRemove bool) <-chan int {
|
||||
var removeErr error
|
||||
statusChan := make(chan int)
|
||||
exitCode := 125
|
||||
|
||||
@ -1,69 +0,0 @@
|
||||
package container
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func waitFn(cid string) (<-chan container.ContainerWaitOKBody, <-chan error) {
|
||||
resC := make(chan container.ContainerWaitOKBody)
|
||||
errC := make(chan error, 1)
|
||||
var res container.ContainerWaitOKBody
|
||||
|
||||
go func() {
|
||||
switch {
|
||||
case strings.Contains(cid, "exit-code-42"):
|
||||
res.StatusCode = 42
|
||||
resC <- res
|
||||
case strings.Contains(cid, "non-existent"):
|
||||
err := errors.Errorf("No such container: %v", cid)
|
||||
errC <- err
|
||||
case strings.Contains(cid, "wait-error"):
|
||||
res.Error = &container.ContainerWaitOKBodyError{Message: "removal failed"}
|
||||
resC <- res
|
||||
default:
|
||||
// normal exit
|
||||
resC <- res
|
||||
}
|
||||
}()
|
||||
|
||||
return resC, errC
|
||||
}
|
||||
|
||||
func TestWaitExitOrRemoved(t *testing.T) {
|
||||
testcases := []struct {
|
||||
cid string
|
||||
exitCode int
|
||||
}{
|
||||
{
|
||||
cid: "normal-container",
|
||||
exitCode: 0,
|
||||
},
|
||||
{
|
||||
cid: "give-me-exit-code-42",
|
||||
exitCode: 42,
|
||||
},
|
||||
{
|
||||
cid: "i-want-a-wait-error",
|
||||
exitCode: 125,
|
||||
},
|
||||
{
|
||||
cid: "non-existent-container-id",
|
||||
exitCode: 125,
|
||||
},
|
||||
}
|
||||
|
||||
client := test.NewFakeCli(&fakeClient{waitFunc: waitFn, Version: api.DefaultVersion})
|
||||
for _, testcase := range testcases {
|
||||
statusC := waitExitOrRemoved(context.Background(), client, testcase.cid, true)
|
||||
exitCode := <-statusC
|
||||
assert.Equal(t, testcase.exitCode, exitCode)
|
||||
}
|
||||
}
|
||||
@ -16,7 +16,7 @@ type waitOptions struct {
|
||||
}
|
||||
|
||||
// NewWaitCommand creates a new cobra.Command for `docker wait`
|
||||
func NewWaitCommand(dockerCli command.Cli) *cobra.Command {
|
||||
func NewWaitCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
var opts waitOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -32,7 +32,7 @@ func NewWaitCommand(dockerCli command.Cli) *cobra.Command {
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runWait(dockerCli command.Cli, opts *waitOptions) error {
|
||||
func runWait(dockerCli *command.DockerCli, opts *waitOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
var errs []string
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/go-units"
|
||||
)
|
||||
|
||||
@ -164,7 +165,7 @@ func (c *containerContext) Image() string {
|
||||
func (c *containerContext) Command() string {
|
||||
command := c.c.Command
|
||||
if c.trunc {
|
||||
command = Ellipsis(command, 20)
|
||||
command = stringutils.Ellipsis(command, 20)
|
||||
}
|
||||
return strconv.Quote(command)
|
||||
}
|
||||
@ -226,7 +227,7 @@ func (c *containerContext) Mounts() string {
|
||||
name = m.Name
|
||||
}
|
||||
if c.trunc {
|
||||
name = Ellipsis(name, 15)
|
||||
name = stringutils.Ellipsis(name, 15)
|
||||
}
|
||||
mounts = append(mounts, name)
|
||||
}
|
||||
|
||||
@ -10,7 +10,6 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/gotestyourself/gotestyourself/golden"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -66,7 +65,7 @@ func TestContainerPsContext(t *testing.T) {
|
||||
Source: "/a/path",
|
||||
},
|
||||
},
|
||||
}, true, "this-is-a-long…", ctx.Mounts},
|
||||
}, true, "this-is-a-lo...", ctx.Mounts},
|
||||
{types.Container{
|
||||
Mounts: []types.MountPoint{
|
||||
{
|
||||
@ -231,7 +230,10 @@ size: 0B
|
||||
// Special headers for customized table format
|
||||
{
|
||||
Context{Format: NewContainerFormat(`table {{truncate .ID 5}}\t{{json .Image}} {{.RunningFor}}/{{title .Status}}/{{pad .Ports 2 2}}.{{upper .Names}} {{lower .Status}}`, false, true)},
|
||||
string(golden.Get(t, "container-context-write-special-headers.golden")),
|
||||
`CONTAINER ID IMAGE CREATED/STATUS/ PORTS .NAMES STATUS
|
||||
conta "ubuntu" 24 hours ago//.FOOBAR_BAZ
|
||||
conta "ubuntu" 24 hours ago//.FOOBAR_BAR
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -118,11 +118,11 @@ func (ctx *DiskUsageContext) Write() (err error) {
|
||||
return err
|
||||
}
|
||||
|
||||
func (ctx *DiskUsageContext) verboseWrite() error {
|
||||
func (ctx *DiskUsageContext) verboseWrite() (err error) {
|
||||
// First images
|
||||
tmpl, err := ctx.startSubsection(defaultDiskUsageImageTableFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
|
||||
ctx.Output.Write([]byte("Images space usage:\n\n"))
|
||||
@ -141,14 +141,14 @@ func (ctx *DiskUsageContext) verboseWrite() error {
|
||||
}
|
||||
}
|
||||
|
||||
err := ctx.contextFormat(tmpl, &imageContext{
|
||||
err = ctx.contextFormat(tmpl, &imageContext{
|
||||
repo: repo,
|
||||
tag: tag,
|
||||
trunc: true,
|
||||
i: *i,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.postFormat(tmpl, newImageContext())
|
||||
@ -157,14 +157,17 @@ func (ctx *DiskUsageContext) verboseWrite() error {
|
||||
ctx.Output.Write([]byte("\nContainers space usage:\n\n"))
|
||||
tmpl, err = ctx.startSubsection(defaultDiskUsageContainerTableFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
for _, c := range ctx.Containers {
|
||||
// Don't display the virtual size
|
||||
c.SizeRootFs = 0
|
||||
err := ctx.contextFormat(tmpl, &containerContext{trunc: true, c: *c})
|
||||
err = ctx.contextFormat(tmpl, &containerContext{
|
||||
trunc: true,
|
||||
c: *c,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.postFormat(tmpl, newContainerContext())
|
||||
@ -173,18 +176,21 @@ func (ctx *DiskUsageContext) verboseWrite() error {
|
||||
ctx.Output.Write([]byte("\nLocal Volumes space usage:\n\n"))
|
||||
tmpl, err = ctx.startSubsection(defaultDiskUsageVolumeTableFormat)
|
||||
if err != nil {
|
||||
return err
|
||||
return
|
||||
}
|
||||
for _, v := range ctx.Volumes {
|
||||
if err := ctx.contextFormat(tmpl, &volumeContext{v: *v}); err != nil {
|
||||
return err
|
||||
err = ctx.contextFormat(tmpl, &volumeContext{
|
||||
v: *v,
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
ctx.postFormat(tmpl, newVolumeContext())
|
||||
|
||||
// And build cache
|
||||
fmt.Fprintf(ctx.Output, "\nBuild cache usage: %s\n\n", units.HumanSize(float64(ctx.BuilderSize)))
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
type diskUsageImagesContext struct {
|
||||
|
||||
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/gotestyourself/gotestyourself/golden"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -84,7 +83,12 @@ Build Cache 0B
|
||||
Format: NewDiskUsageFormat("table {{.Type}}\t{{.Active}}"),
|
||||
},
|
||||
},
|
||||
string(golden.Get(t, "disk-usage-context-write-custom.golden")),
|
||||
`TYPE ACTIVE
|
||||
Images 0
|
||||
Containers 0
|
||||
Local Volumes 0
|
||||
Build Cache
|
||||
`,
|
||||
},
|
||||
// Raw Format
|
||||
{
|
||||
@ -93,7 +97,31 @@ Build Cache 0B
|
||||
Format: NewDiskUsageFormat("raw"),
|
||||
},
|
||||
},
|
||||
string(golden.Get(t, "disk-usage-raw-format.golden")),
|
||||
`type: Images
|
||||
total: 0
|
||||
active: 0
|
||||
size: 0B
|
||||
reclaimable: 0B
|
||||
|
||||
type: Containers
|
||||
total: 0
|
||||
active: 0
|
||||
size: 0B
|
||||
reclaimable: 0B
|
||||
|
||||
type: Local Volumes
|
||||
total: 0
|
||||
active: 0
|
||||
size: 0B
|
||||
reclaimable: 0B
|
||||
|
||||
type: Build Cache
|
||||
total:
|
||||
active:
|
||||
size: 0B
|
||||
reclaimable: 0B
|
||||
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -1,61 +0,0 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/text/width"
|
||||
)
|
||||
|
||||
// charWidth returns the number of horizontal positions a character occupies,
|
||||
// and is used to account for wide characters when displaying strings.
|
||||
//
|
||||
// In a broad sense, wide characters include East Asian Wide, East Asian Full-width,
|
||||
// (when not in East Asian context) see http://unicode.org/reports/tr11/.
|
||||
func charWidth(r rune) int {
|
||||
switch width.LookupRune(r).Kind() {
|
||||
case width.EastAsianWide, width.EastAsianFullwidth:
|
||||
return 2
|
||||
default:
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
// Ellipsis truncates a string to fit within maxDisplayWidth, and appends ellipsis (…).
|
||||
// For maxDisplayWidth of 1 and lower, no ellipsis is appended.
|
||||
// For maxDisplayWidth of 1, first char of string will return even if its width > 1.
|
||||
func Ellipsis(s string, maxDisplayWidth int) string {
|
||||
if maxDisplayWidth <= 0 {
|
||||
return ""
|
||||
}
|
||||
rs := []rune(s)
|
||||
if maxDisplayWidth == 1 {
|
||||
return string(rs[0])
|
||||
}
|
||||
|
||||
byteLen := len(s)
|
||||
if byteLen == utf8.RuneCountInString(s) {
|
||||
if byteLen <= maxDisplayWidth {
|
||||
return s
|
||||
}
|
||||
return string(rs[:maxDisplayWidth-1]) + "…"
|
||||
}
|
||||
|
||||
var (
|
||||
display []int
|
||||
displayWidth int
|
||||
)
|
||||
for _, r := range rs {
|
||||
cw := charWidth(r)
|
||||
displayWidth += cw
|
||||
display = append(display, displayWidth)
|
||||
}
|
||||
if displayWidth <= maxDisplayWidth {
|
||||
return s
|
||||
}
|
||||
for i := range display {
|
||||
if display[i] <= maxDisplayWidth-1 && display[i+1] > maxDisplayWidth-1 {
|
||||
return string(rs[:i+1]) + "…"
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEllipsis(t *testing.T) {
|
||||
var testcases = []struct {
|
||||
source string
|
||||
width int
|
||||
expected string
|
||||
}{
|
||||
{source: "t🐳ststring", width: 0, expected: ""},
|
||||
{source: "t🐳ststring", width: 1, expected: "t"},
|
||||
{source: "t🐳ststring", width: 2, expected: "t…"},
|
||||
{source: "t🐳ststring", width: 6, expected: "t🐳st…"},
|
||||
{source: "t🐳ststring", width: 20, expected: "t🐳ststring"},
|
||||
{source: "你好世界teststring", width: 0, expected: ""},
|
||||
{source: "你好世界teststring", width: 1, expected: "你"},
|
||||
{source: "你好世界teststring", width: 3, expected: "你…"},
|
||||
{source: "你好世界teststring", width: 6, expected: "你好…"},
|
||||
{source: "你好世界teststring", width: 20, expected: "你好世界teststring"},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
assert.Equal(t, testcase.expected, Ellipsis(testcase.source, testcase.width))
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
@ -92,7 +93,7 @@ func (c *historyContext) CreatedSince() string {
|
||||
func (c *historyContext) CreatedBy() string {
|
||||
createdBy := strings.Replace(c.h.CreatedBy, "\t", " ", -1)
|
||||
if c.trunc {
|
||||
return Ellipsis(createdBy, 45)
|
||||
return stringutils.Ellipsis(createdBy, 45)
|
||||
}
|
||||
return createdBy
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -95,7 +96,7 @@ func TestHistoryContext_CreatedBy(t *testing.T) {
|
||||
historyContext{
|
||||
h: image.HistoryResponseItem{CreatedBy: withTabs},
|
||||
trunc: true,
|
||||
}, Ellipsis(expected, 45), ctx.CreatedBy,
|
||||
}, stringutils.Ellipsis(expected, 45), ctx.CreatedBy,
|
||||
},
|
||||
}
|
||||
|
||||
@ -190,7 +191,7 @@ imageID3 24 hours ago /bin/bash ls
|
||||
imageID4 24 hours ago /bin/bash grep 183MB Hi
|
||||
`
|
||||
expectedTrunc := `IMAGE CREATED CREATED BY SIZE COMMENT
|
||||
imageID1 24 hours ago /bin/bash ls && npm i && npm run test && kar… 183MB Hi
|
||||
imageID1 24 hours ago /bin/bash ls && npm i && npm run test && k... 183MB Hi
|
||||
imageID2 24 hours ago /bin/bash echo 183MB Hi
|
||||
imageID3 24 hours ago /bin/bash ls 183MB Hi
|
||||
imageID4 24 hours ago /bin/bash grep 183MB Hi
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -79,7 +80,7 @@ func (c *pluginContext) Description() string {
|
||||
desc := strings.Replace(c.p.Config.Description, "\n", "", -1)
|
||||
desc = strings.Replace(desc, "\r", "", -1)
|
||||
if c.trunc {
|
||||
desc = Ellipsis(desc, 45)
|
||||
desc = stringutils.Ellipsis(desc, 45)
|
||||
}
|
||||
|
||||
return desc
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
|
||||
registry "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -72,7 +73,7 @@ func (c *searchContext) Description() string {
|
||||
desc := strings.Replace(c.s.Description, "\n", " ", -1)
|
||||
desc = strings.Replace(desc, "\r", " ", -1)
|
||||
if c.trunc {
|
||||
desc = Ellipsis(desc, 45)
|
||||
desc = stringutils.Ellipsis(desc, 45)
|
||||
}
|
||||
return desc
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/gotestyourself/gotestyourself/golden"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -78,7 +78,7 @@ func TestSearchContextDescription(t *testing.T) {
|
||||
{searchContext{
|
||||
s: registrytypes.SearchResult{Description: longDescription},
|
||||
trunc: true,
|
||||
}, Ellipsis(longDescription, 45), ctx.Description},
|
||||
}, stringutils.Ellipsis(longDescription, 45), ctx.Description},
|
||||
{searchContext{
|
||||
s: registrytypes.SearchResult{Description: descriptionWReturns},
|
||||
trunc: false,
|
||||
@ -86,7 +86,7 @@ func TestSearchContextDescription(t *testing.T) {
|
||||
{searchContext{
|
||||
s: registrytypes.SearchResult{Description: descriptionWReturns},
|
||||
trunc: true,
|
||||
}, Ellipsis(longDescription, 45), ctx.Description},
|
||||
}, stringutils.Ellipsis(longDescription, 45), ctx.Description},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
@ -120,7 +120,10 @@ func TestSearchContextWrite(t *testing.T) {
|
||||
// Table format
|
||||
{
|
||||
Context{Format: NewSearchFormat("table")},
|
||||
string(golden.Get(t, "search-context-write-table.golden")),
|
||||
`NAME DESCRIPTION STARS OFFICIAL AUTOMATED
|
||||
result1 Official build 5000 [OK]
|
||||
result2 Not official 5 [OK]
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{Format: NewSearchFormat("table {{.Name}}")},
|
||||
@ -207,7 +210,9 @@ func TestSearchContextWriteStars(t *testing.T) {
|
||||
// Table format
|
||||
{
|
||||
Context{Format: NewSearchFormat("table")},
|
||||
string(golden.Get(t, "search-context-write-stars-table.golden")),
|
||||
`NAME DESCRIPTION STARS OFFICIAL AUTOMATED
|
||||
result1 Official build 5000 [OK]
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{Format: NewSearchFormat("table {{.Name}}")},
|
||||
|
||||
@ -12,20 +12,19 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSecretTableFormat = "table {{.ID}}\t{{.Name}}\t{{.Driver}}\t{{.CreatedAt}}\t{{.UpdatedAt}}"
|
||||
defaultSecretTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CreatedAt}}\t{{.UpdatedAt}}"
|
||||
secretIDHeader = "ID"
|
||||
secretCreatedHeader = "CREATED"
|
||||
secretUpdatedHeader = "UPDATED"
|
||||
secretInspectPrettyTemplate Format = `ID: {{.ID}}
|
||||
Name: {{.Name}}
|
||||
secretInspectPrettyTemplate Format = `ID: {{.ID}}
|
||||
Name: {{.Name}}
|
||||
{{- if .Labels }}
|
||||
Labels:
|
||||
{{- range $k, $v := .Labels }}
|
||||
- {{ $k }}{{if $v }}={{ $v }}{{ end }}
|
||||
{{- end }}{{ end }}
|
||||
Driver: {{.Driver}}
|
||||
Created at: {{.CreatedAt}}
|
||||
Updated at: {{.UpdatedAt}}`
|
||||
Created at: {{.CreatedAt}}
|
||||
Updated at: {{.UpdatedAt}}`
|
||||
)
|
||||
|
||||
// NewSecretFormat returns a Format for rendering using a secret Context
|
||||
@ -62,7 +61,6 @@ func newSecretContext() *secretContext {
|
||||
sCtx.header = map[string]string{
|
||||
"ID": secretIDHeader,
|
||||
"Name": nameHeader,
|
||||
"Driver": driverHeader,
|
||||
"CreatedAt": secretCreatedHeader,
|
||||
"UpdatedAt": secretUpdatedHeader,
|
||||
"Labels": labelsHeader,
|
||||
@ -91,13 +89,6 @@ func (c *secretContext) CreatedAt() string {
|
||||
return units.HumanDuration(time.Now().UTC().Sub(c.s.Meta.CreatedAt)) + " ago"
|
||||
}
|
||||
|
||||
func (c *secretContext) Driver() string {
|
||||
if c.s.Spec.Driver == nil {
|
||||
return ""
|
||||
}
|
||||
return c.s.Spec.Driver.Name
|
||||
}
|
||||
|
||||
func (c *secretContext) UpdatedAt() string {
|
||||
return units.HumanDuration(time.Now().UTC().Sub(c.s.Meta.UpdatedAt)) + " ago"
|
||||
}
|
||||
@ -162,13 +153,6 @@ func (ctx *secretInspectContext) Labels() map[string]string {
|
||||
return ctx.Secret.Spec.Labels
|
||||
}
|
||||
|
||||
func (ctx *secretInspectContext) Driver() string {
|
||||
if ctx.Secret.Spec.Driver == nil {
|
||||
return ""
|
||||
}
|
||||
return ctx.Secret.Spec.Driver.Name
|
||||
}
|
||||
|
||||
func (ctx *secretInspectContext) CreatedAt() string {
|
||||
return command.PrettyPrint(ctx.Secret.CreatedAt)
|
||||
}
|
||||
|
||||
@ -28,9 +28,9 @@ func TestSecretContextFormatWrite(t *testing.T) {
|
||||
},
|
||||
// Table format
|
||||
{Context{Format: NewSecretFormat("table", false)},
|
||||
`ID NAME DRIVER CREATED UPDATED
|
||||
1 passwords Less than a second ago Less than a second ago
|
||||
2 id_rsa Less than a second ago Less than a second ago
|
||||
`ID NAME CREATED UPDATED
|
||||
1 passwords Less than a second ago Less than a second ago
|
||||
2 id_rsa Less than a second ago Less than a second ago
|
||||
`},
|
||||
{Context{Format: NewSecretFormat("table {{.Name}}", true)},
|
||||
`NAME
|
||||
|
||||
@ -2,7 +2,6 @@ package formatter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -521,95 +520,19 @@ func (c *serviceContext) Image() string {
|
||||
return image
|
||||
}
|
||||
|
||||
type portRange struct {
|
||||
pStart uint32
|
||||
pEnd uint32
|
||||
tStart uint32
|
||||
tEnd uint32
|
||||
protocol swarm.PortConfigProtocol
|
||||
}
|
||||
|
||||
func (pr portRange) String() string {
|
||||
var (
|
||||
pub string
|
||||
tgt string
|
||||
)
|
||||
|
||||
if pr.pEnd > pr.pStart {
|
||||
pub = fmt.Sprintf("%d-%d", pr.pStart, pr.pEnd)
|
||||
} else {
|
||||
pub = fmt.Sprintf("%d", pr.pStart)
|
||||
}
|
||||
if pr.tEnd > pr.tStart {
|
||||
tgt = fmt.Sprintf("%d-%d", pr.tStart, pr.tEnd)
|
||||
} else {
|
||||
tgt = fmt.Sprintf("%d", pr.tStart)
|
||||
}
|
||||
return fmt.Sprintf("*:%s->%s/%s", pub, tgt, pr.protocol)
|
||||
}
|
||||
|
||||
// Ports formats published ports on the ingress network for output.
|
||||
//
|
||||
// Where possible, ranges are grouped to produce a compact output:
|
||||
// - multiple ports mapped to a single port (80->80, 81->80); is formatted as *:80-81->80
|
||||
// - multiple consecutive ports on both sides; (80->80, 81->81) are formatted as: *:80-81->80-81
|
||||
//
|
||||
// The above should not be grouped together, i.e.:
|
||||
// - 80->80, 81->81, 82->80 should be presented as : *:80-81->80-81, *:82->80
|
||||
//
|
||||
// TODO improve:
|
||||
// - combine non-consecutive ports mapped to a single port (80->80, 81->80, 84->80, 86->80, 87->80); to be printed as *:80-81,84,86-87->80
|
||||
// - combine tcp and udp mappings if their port-mapping is exactly the same (*:80-81->80-81/tcp+udp instead of *:80-81->80-81/tcp, *:80-81->80-81/udp)
|
||||
func (c *serviceContext) Ports() string {
|
||||
if c.service.Endpoint.Ports == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
pr := portRange{}
|
||||
ports := []string{}
|
||||
|
||||
sort.Sort(byProtocolAndPublishedPort(c.service.Endpoint.Ports))
|
||||
|
||||
for _, p := range c.service.Endpoint.Ports {
|
||||
if p.PublishMode == swarm.PortConfigPublishModeIngress {
|
||||
prIsRange := pr.tEnd != pr.tStart
|
||||
tOverlaps := p.TargetPort <= pr.tEnd
|
||||
|
||||
// Start a new port-range if:
|
||||
// - the protocol is different from the current port-range
|
||||
// - published or target port are not consecutive to the current port-range
|
||||
// - the current port-range is a _range_, and the target port overlaps with the current range's target-ports
|
||||
if p.Protocol != pr.protocol || p.PublishedPort-pr.pEnd > 1 || p.TargetPort-pr.tEnd > 1 || prIsRange && tOverlaps {
|
||||
// start a new port-range, and print the previous port-range (if any)
|
||||
if pr.pStart > 0 {
|
||||
ports = append(ports, pr.String())
|
||||
}
|
||||
pr = portRange{
|
||||
pStart: p.PublishedPort,
|
||||
pEnd: p.PublishedPort,
|
||||
tStart: p.TargetPort,
|
||||
tEnd: p.TargetPort,
|
||||
protocol: p.Protocol,
|
||||
}
|
||||
continue
|
||||
}
|
||||
pr.pEnd = p.PublishedPort
|
||||
pr.tEnd = p.TargetPort
|
||||
for _, pConfig := range c.service.Endpoint.Ports {
|
||||
if pConfig.PublishMode == swarm.PortConfigPublishModeIngress {
|
||||
ports = append(ports, fmt.Sprintf("*:%d->%d/%s",
|
||||
pConfig.PublishedPort,
|
||||
pConfig.TargetPort,
|
||||
pConfig.Protocol,
|
||||
))
|
||||
}
|
||||
}
|
||||
if pr.pStart > 0 {
|
||||
ports = append(ports, pr.String())
|
||||
}
|
||||
return strings.Join(ports, ", ")
|
||||
}
|
||||
|
||||
type byProtocolAndPublishedPort []swarm.PortConfig
|
||||
|
||||
func (a byProtocolAndPublishedPort) Len() int { return len(a) }
|
||||
func (a byProtocolAndPublishedPort) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a byProtocolAndPublishedPort) Less(i, j int) bool {
|
||||
if a[i].Protocol == a[j].Protocol {
|
||||
return a[i].PublishedPort < a[j].PublishedPort
|
||||
}
|
||||
return a[i].Protocol < a[j].Protocol
|
||||
return strings.Join(ports, ",")
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/gotestyourself/gotestyourself/golden"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@ -60,7 +59,21 @@ bar
|
||||
// Raw Format
|
||||
{
|
||||
Context{Format: NewServiceListFormat("raw", false)},
|
||||
string(golden.Get(t, "service-context-write-raw.golden")),
|
||||
`id: id_baz
|
||||
name: baz
|
||||
mode: global
|
||||
replicas: 2/4
|
||||
image:
|
||||
ports: *:80->8080/tcp
|
||||
|
||||
id: id_bar
|
||||
name: bar
|
||||
mode: replicated
|
||||
replicas: 2/4
|
||||
image:
|
||||
ports: *:80->8080/tcp
|
||||
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{Format: NewServiceListFormat("raw", true)},
|
||||
@ -224,124 +237,3 @@ func TestServiceContextWriteJSONField(t *testing.T) {
|
||||
assert.Equal(t, services[i].Spec.Name, s, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceContext_Ports(t *testing.T) {
|
||||
c := serviceContext{
|
||||
service: swarm.Service{
|
||||
Endpoint: swarm.Endpoint{
|
||||
Ports: []swarm.PortConfig{
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 80,
|
||||
PublishedPort: 81,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 80,
|
||||
PublishedPort: 80,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 95,
|
||||
PublishedPort: 95,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 90,
|
||||
PublishedPort: 90,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 91,
|
||||
PublishedPort: 91,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 92,
|
||||
PublishedPort: 92,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 93,
|
||||
PublishedPort: 93,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 94,
|
||||
PublishedPort: 94,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "udp",
|
||||
TargetPort: 95,
|
||||
PublishedPort: 95,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "udp",
|
||||
TargetPort: 90,
|
||||
PublishedPort: 90,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "udp",
|
||||
TargetPort: 96,
|
||||
PublishedPort: 96,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "udp",
|
||||
TargetPort: 91,
|
||||
PublishedPort: 91,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "udp",
|
||||
TargetPort: 92,
|
||||
PublishedPort: 92,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "udp",
|
||||
TargetPort: 93,
|
||||
PublishedPort: 93,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "udp",
|
||||
TargetPort: 94,
|
||||
PublishedPort: 94,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 60,
|
||||
PublishedPort: 60,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 61,
|
||||
PublishedPort: 61,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
{
|
||||
Protocol: "tcp",
|
||||
TargetPort: 61,
|
||||
PublishedPort: 62,
|
||||
PublishMode: "ingress",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
assert.Equal(t, "*:60-61->60-61/tcp, *:62->61/tcp, *:80-81->80/tcp, *:90-95->90-95/tcp, *:90-96->90-96/udp", c.Ports())
|
||||
}
|
||||
|
||||
@ -4,14 +4,13 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
units "github.com/docker/go-units"
|
||||
)
|
||||
|
||||
const (
|
||||
winOSType = "windows"
|
||||
defaultStatsTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}"
|
||||
winDefaultStatsTableFormat = "table {{.ID}}\t{{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
|
||||
defaultStatsTableFormat = "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}\t{{.NetIO}}\t{{.BlockIO}}\t{{.PIDs}}"
|
||||
winDefaultStatsTableFormat = "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}"
|
||||
|
||||
containerHeader = "CONTAINER"
|
||||
cpuPercHeader = "CPU %"
|
||||
@ -115,13 +114,12 @@ func NewContainerStats(container string) *ContainerStats {
|
||||
}
|
||||
|
||||
// ContainerStatsWrite renders the context for a list of containers statistics
|
||||
func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string, trunc bool) error {
|
||||
func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string) error {
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
for _, cstats := range containerStats {
|
||||
containerStatsCtx := &containerStatsContext{
|
||||
s: cstats,
|
||||
os: osType,
|
||||
trunc: trunc,
|
||||
s: cstats,
|
||||
os: osType,
|
||||
}
|
||||
if err := format(containerStatsCtx); err != nil {
|
||||
return err
|
||||
@ -151,9 +149,8 @@ func ContainerStatsWrite(ctx Context, containerStats []StatsEntry, osType string
|
||||
|
||||
type containerStatsContext struct {
|
||||
HeaderContext
|
||||
s StatsEntry
|
||||
os string
|
||||
trunc bool
|
||||
s StatsEntry
|
||||
os string
|
||||
}
|
||||
|
||||
func (c *containerStatsContext) MarshalJSON() ([]byte, error) {
|
||||
@ -172,9 +169,6 @@ func (c *containerStatsContext) Name() string {
|
||||
}
|
||||
|
||||
func (c *containerStatsContext) ID() string {
|
||||
if c.trunc {
|
||||
return stringid.TruncateID(c.s.ID)
|
||||
}
|
||||
return c.s.ID
|
||||
}
|
||||
|
||||
|
||||
@ -114,7 +114,7 @@ container2 --
|
||||
}
|
||||
var out bytes.Buffer
|
||||
te.context.Output = &out
|
||||
err := ContainerStatsWrite(te.context, stats, "linux", false)
|
||||
err := ContainerStatsWrite(te.context, stats, "linux")
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, te.expected)
|
||||
} else {
|
||||
@ -180,7 +180,7 @@ container2 -- --
|
||||
}
|
||||
var out bytes.Buffer
|
||||
te.context.Output = &out
|
||||
err := ContainerStatsWrite(te.context, stats, "windows", false)
|
||||
err := ContainerStatsWrite(te.context, stats, "windows")
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, te.expected)
|
||||
} else {
|
||||
@ -220,7 +220,7 @@ func TestContainerStatsContextWriteWithNoStats(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, context := range contexts {
|
||||
ContainerStatsWrite(context.context, []StatsEntry{}, "linux", false)
|
||||
ContainerStatsWrite(context.context, []StatsEntry{}, "linux")
|
||||
assert.Equal(t, context.expected, out.String())
|
||||
// Clean buffer
|
||||
out.Reset()
|
||||
@ -258,41 +258,7 @@ func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, context := range contexts {
|
||||
ContainerStatsWrite(context.context, []StatsEntry{}, "windows", false)
|
||||
assert.Equal(t, context.expected, out.String())
|
||||
// Clean buffer
|
||||
out.Reset()
|
||||
}
|
||||
}
|
||||
|
||||
func TestContainerStatsContextWriteTrunc(t *testing.T) {
|
||||
var out bytes.Buffer
|
||||
|
||||
contexts := []struct {
|
||||
context Context
|
||||
trunc bool
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
Context{
|
||||
Format: "{{.ID}}",
|
||||
Output: &out,
|
||||
},
|
||||
false,
|
||||
"b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc\n",
|
||||
},
|
||||
{
|
||||
Context{
|
||||
Format: "{{.ID}}",
|
||||
Output: &out,
|
||||
},
|
||||
true,
|
||||
"b95a83497c91\n",
|
||||
},
|
||||
}
|
||||
|
||||
for _, context := range contexts {
|
||||
ContainerStatsWrite(context.context, []StatsEntry{{ID: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc"}}, "linux", context.trunc)
|
||||
ContainerStatsWrite(context.context, []StatsEntry{}, "windows")
|
||||
assert.Equal(t, context.expected, out.String())
|
||||
// Clean buffer
|
||||
out.Reset()
|
||||
|
||||
@ -7,7 +7,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
"github.com/gotestyourself/gotestyourself/golden"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@ -34,7 +33,10 @@ taskID2
|
||||
},
|
||||
{
|
||||
Context{Format: NewTaskFormat("table {{.Name}}\t{{.Node}}\t{{.Ports}}", false)},
|
||||
string(golden.Get(t, "task-context-write-table-custom.golden")),
|
||||
`NAME NODE PORTS
|
||||
foobar_baz foo1
|
||||
foobar_bar foo2
|
||||
`,
|
||||
},
|
||||
{
|
||||
Context{Format: NewTaskFormat("table {{.Name}}", true)},
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
CONTAINER ID IMAGE CREATED/STATUS/ PORTS .NAMES STATUS
|
||||
conta "ubuntu" 24 hours ago//.FOOBAR_BAZ
|
||||
conta "ubuntu" 24 hours ago//.FOOBAR_BAR
|
||||
@ -1,5 +0,0 @@
|
||||
TYPE ACTIVE
|
||||
Images 0
|
||||
Containers 0
|
||||
Local Volumes 0
|
||||
Build Cache
|
||||
@ -1,24 +0,0 @@
|
||||
type: Images
|
||||
total: 0
|
||||
active: 0
|
||||
size: 0B
|
||||
reclaimable: 0B
|
||||
|
||||
type: Containers
|
||||
total: 0
|
||||
active: 0
|
||||
size: 0B
|
||||
reclaimable: 0B
|
||||
|
||||
type: Local Volumes
|
||||
total: 0
|
||||
active: 0
|
||||
size: 0B
|
||||
reclaimable: 0B
|
||||
|
||||
type: Build Cache
|
||||
total:
|
||||
active:
|
||||
size: 0B
|
||||
reclaimable: 0B
|
||||
|
||||
@ -1,2 +0,0 @@
|
||||
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
|
||||
result1 Official build 5000 [OK]
|
||||
@ -1,3 +0,0 @@
|
||||
NAME DESCRIPTION STARS OFFICIAL AUTOMATED
|
||||
result1 Official build 5000 [OK]
|
||||
result2 Not official 5 [OK]
|
||||
@ -1,14 +0,0 @@
|
||||
id: id_baz
|
||||
name: baz
|
||||
mode: global
|
||||
replicas: 2/4
|
||||
image:
|
||||
ports: *:80->8080/tcp
|
||||
|
||||
id: id_bar
|
||||
name: bar
|
||||
mode: replicated
|
||||
replicas: 2/4
|
||||
image:
|
||||
ports: *:80->8080/tcp
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
NAME NODE PORTS
|
||||
foobar_baz foo1
|
||||
foobar_bar foo2
|
||||
@ -1,150 +0,0 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultTrustTagTableFormat = "table {{.SignedTag}}\t{{.Digest}}\t{{.Signers}}"
|
||||
signedTagNameHeader = "SIGNED TAG"
|
||||
trustedDigestHeader = "DIGEST"
|
||||
signersHeader = "SIGNERS"
|
||||
defaultSignerInfoTableFormat = "table {{.Signer}}\t{{.Keys}}"
|
||||
signerNameHeader = "SIGNER"
|
||||
keysHeader = "KEYS"
|
||||
)
|
||||
|
||||
// SignedTagInfo represents all formatted information needed to describe a signed tag:
|
||||
// Name: name of the signed tag
|
||||
// Digest: hex encoded digest of the contents
|
||||
// Signers: list of entities who signed the tag
|
||||
type SignedTagInfo struct {
|
||||
Name string
|
||||
Digest string
|
||||
Signers []string
|
||||
}
|
||||
|
||||
// SignerInfo represents all formatted information needed to describe a signer:
|
||||
// Name: name of the signer role
|
||||
// Keys: the keys associated with the signer
|
||||
type SignerInfo struct {
|
||||
Name string
|
||||
Keys []string
|
||||
}
|
||||
|
||||
// NewTrustTagFormat returns a Format for rendering using a trusted tag Context
|
||||
func NewTrustTagFormat() Format {
|
||||
return defaultTrustTagTableFormat
|
||||
}
|
||||
|
||||
// NewSignerInfoFormat returns a Format for rendering a signer role info Context
|
||||
func NewSignerInfoFormat() Format {
|
||||
return defaultSignerInfoTableFormat
|
||||
}
|
||||
|
||||
// TrustTagWrite writes the context
|
||||
func TrustTagWrite(ctx Context, signedTagInfoList []SignedTagInfo) error {
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
for _, signedTag := range signedTagInfoList {
|
||||
if err := format(&trustTagContext{s: signedTag}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
trustTagCtx := trustTagContext{}
|
||||
trustTagCtx.header = trustTagHeaderContext{
|
||||
"SignedTag": signedTagNameHeader,
|
||||
"Digest": trustedDigestHeader,
|
||||
"Signers": signersHeader,
|
||||
}
|
||||
return ctx.Write(&trustTagCtx, render)
|
||||
}
|
||||
|
||||
type trustTagHeaderContext map[string]string
|
||||
|
||||
type trustTagContext struct {
|
||||
HeaderContext
|
||||
s SignedTagInfo
|
||||
}
|
||||
|
||||
// SignedTag returns the name of the signed tag
|
||||
func (c *trustTagContext) SignedTag() string {
|
||||
return c.s.Name
|
||||
}
|
||||
|
||||
// Digest returns the hex encoded digest associated with this signed tag
|
||||
func (c *trustTagContext) Digest() string {
|
||||
return c.s.Digest
|
||||
}
|
||||
|
||||
// Signers returns the sorted list of entities who signed this tag
|
||||
func (c *trustTagContext) Signers() string {
|
||||
sort.Strings(c.s.Signers)
|
||||
return strings.Join(c.s.Signers, ", ")
|
||||
}
|
||||
|
||||
// SignerInfoWrite writes the context
|
||||
func SignerInfoWrite(ctx Context, signerInfoList []SignerInfo) error {
|
||||
render := func(format func(subContext subContext) error) error {
|
||||
for _, signerInfo := range signerInfoList {
|
||||
if err := format(&signerInfoContext{
|
||||
trunc: ctx.Trunc,
|
||||
s: signerInfo,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
signerInfoCtx := signerInfoContext{}
|
||||
signerInfoCtx.header = signerInfoHeaderContext{
|
||||
"Signer": signerNameHeader,
|
||||
"Keys": keysHeader,
|
||||
}
|
||||
return ctx.Write(&signerInfoCtx, render)
|
||||
}
|
||||
|
||||
type signerInfoHeaderContext map[string]string
|
||||
|
||||
type signerInfoContext struct {
|
||||
HeaderContext
|
||||
trunc bool
|
||||
s SignerInfo
|
||||
}
|
||||
|
||||
// Keys returns the sorted list of keys associated with the signer
|
||||
func (c *signerInfoContext) Keys() string {
|
||||
sort.Strings(c.s.Keys)
|
||||
truncatedKeys := []string{}
|
||||
if c.trunc {
|
||||
for _, keyID := range c.s.Keys {
|
||||
truncatedKeys = append(truncatedKeys, stringid.TruncateID(keyID))
|
||||
}
|
||||
return strings.Join(truncatedKeys, ", ")
|
||||
}
|
||||
return strings.Join(c.s.Keys, ", ")
|
||||
}
|
||||
|
||||
// Signer returns the name of the signer
|
||||
func (c *signerInfoContext) Signer() string {
|
||||
return c.s.Name
|
||||
}
|
||||
|
||||
// SignerInfoList helps sort []SignerInfo by signer names
|
||||
type SignerInfoList []SignerInfo
|
||||
|
||||
func (signerInfoComp SignerInfoList) Len() int {
|
||||
return len(signerInfoComp)
|
||||
}
|
||||
|
||||
func (signerInfoComp SignerInfoList) Less(i, j int) bool {
|
||||
return signerInfoComp[i].Name < signerInfoComp[j].Name
|
||||
}
|
||||
|
||||
func (signerInfoComp SignerInfoList) Swap(i, j int) {
|
||||
signerInfoComp[i], signerInfoComp[j] = signerInfoComp[j], signerInfoComp[i]
|
||||
}
|
||||
@ -1,238 +0,0 @@
|
||||
package formatter
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/pkg/stringid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTrustTag(t *testing.T) {
|
||||
digest := stringid.GenerateRandomID()
|
||||
trustedTag := "tag"
|
||||
|
||||
var ctx trustTagContext
|
||||
|
||||
cases := []struct {
|
||||
trustTagCtx trustTagContext
|
||||
expValue string
|
||||
call func() string
|
||||
}{
|
||||
{
|
||||
trustTagContext{
|
||||
s: SignedTagInfo{Name: trustedTag,
|
||||
Digest: digest,
|
||||
Signers: nil,
|
||||
},
|
||||
},
|
||||
digest,
|
||||
ctx.Digest,
|
||||
},
|
||||
{
|
||||
trustTagContext{
|
||||
s: SignedTagInfo{Name: trustedTag,
|
||||
Digest: digest,
|
||||
Signers: nil,
|
||||
},
|
||||
},
|
||||
trustedTag,
|
||||
ctx.SignedTag,
|
||||
},
|
||||
// Empty signers makes a row with empty string
|
||||
{
|
||||
trustTagContext{
|
||||
s: SignedTagInfo{Name: trustedTag,
|
||||
Digest: digest,
|
||||
Signers: nil,
|
||||
},
|
||||
},
|
||||
"",
|
||||
ctx.Signers,
|
||||
},
|
||||
{
|
||||
trustTagContext{
|
||||
s: SignedTagInfo{Name: trustedTag,
|
||||
Digest: digest,
|
||||
Signers: []string{"alice", "bob", "claire"},
|
||||
},
|
||||
},
|
||||
"alice, bob, claire",
|
||||
ctx.Signers,
|
||||
},
|
||||
// alphabetic signing on Signers
|
||||
{
|
||||
trustTagContext{
|
||||
s: SignedTagInfo{Name: trustedTag,
|
||||
Digest: digest,
|
||||
Signers: []string{"claire", "bob", "alice"},
|
||||
},
|
||||
},
|
||||
"alice, bob, claire",
|
||||
ctx.Signers,
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
ctx = c.trustTagCtx
|
||||
v := c.call()
|
||||
if v != c.expValue {
|
||||
t.Fatalf("Expected %s, was %s\n", c.expValue, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTrustTagContextWrite(t *testing.T) {
|
||||
|
||||
cases := []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: NewTrustTagFormat(),
|
||||
},
|
||||
`SIGNED TAG DIGEST SIGNERS
|
||||
tag1 deadbeef alice
|
||||
tag2 aaaaaaaa alice, bob
|
||||
tag3 bbbbbbbb
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range cases {
|
||||
signedTags := []SignedTagInfo{
|
||||
{Name: "tag1", Digest: "deadbeef", Signers: []string{"alice"}},
|
||||
{Name: "tag2", Digest: "aaaaaaaa", Signers: []string{"alice", "bob"}},
|
||||
{Name: "tag3", Digest: "bbbbbbbb", Signers: []string{}},
|
||||
}
|
||||
out := bytes.NewBufferString("")
|
||||
testcase.context.Output = out
|
||||
err := TrustTagWrite(testcase.context, signedTags)
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, testcase.expected)
|
||||
} else {
|
||||
assert.Equal(t, testcase.expected, out.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// With no trust data, the TrustTagWrite will print an empty table:
|
||||
// it's up to the caller to decide whether or not to print this versus an error
|
||||
func TestTrustTagContextEmptyWrite(t *testing.T) {
|
||||
|
||||
emptyCase := struct {
|
||||
context Context
|
||||
expected string
|
||||
}{
|
||||
Context{
|
||||
Format: NewTrustTagFormat(),
|
||||
},
|
||||
`SIGNED TAG DIGEST SIGNERS
|
||||
`,
|
||||
}
|
||||
|
||||
emptySignedTags := []SignedTagInfo{}
|
||||
out := bytes.NewBufferString("")
|
||||
emptyCase.context.Output = out
|
||||
err := TrustTagWrite(emptyCase.context, emptySignedTags)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, emptyCase.expected, out.String())
|
||||
}
|
||||
|
||||
func TestSignerInfoContextEmptyWrite(t *testing.T) {
|
||||
emptyCase := struct {
|
||||
context Context
|
||||
expected string
|
||||
}{
|
||||
Context{
|
||||
Format: NewSignerInfoFormat(),
|
||||
},
|
||||
`SIGNER KEYS
|
||||
`,
|
||||
}
|
||||
emptySignerInfo := []SignerInfo{}
|
||||
out := bytes.NewBufferString("")
|
||||
emptyCase.context.Output = out
|
||||
err := SignerInfoWrite(emptyCase.context, emptySignerInfo)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, emptyCase.expected, out.String())
|
||||
}
|
||||
|
||||
func TestSignerInfoContextWrite(t *testing.T) {
|
||||
cases := []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: NewSignerInfoFormat(),
|
||||
Trunc: true,
|
||||
},
|
||||
`SIGNER KEYS
|
||||
alice key11, key12
|
||||
bob key21
|
||||
eve foobarbazqux, key31, key32
|
||||
`,
|
||||
},
|
||||
// No truncation
|
||||
{
|
||||
Context{
|
||||
Format: NewSignerInfoFormat(),
|
||||
},
|
||||
`SIGNER KEYS
|
||||
alice key11, key12
|
||||
bob key21
|
||||
eve foobarbazquxquux, key31, key32
|
||||
`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range cases {
|
||||
signerInfo := SignerInfoList{
|
||||
{Name: "alice", Keys: []string{"key11", "key12"}},
|
||||
{Name: "bob", Keys: []string{"key21"}},
|
||||
{Name: "eve", Keys: []string{"key31", "key32", "foobarbazquxquux"}},
|
||||
}
|
||||
out := bytes.NewBufferString("")
|
||||
testcase.context.Output = out
|
||||
err := SignerInfoWrite(testcase.context, signerInfo)
|
||||
if err != nil {
|
||||
assert.EqualError(t, err, testcase.expected)
|
||||
} else {
|
||||
assert.Equal(t, testcase.expected, out.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,7 +21,6 @@ import (
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/pkg/progress"
|
||||
"github.com/docker/docker/pkg/streamformatter"
|
||||
@ -64,7 +63,6 @@ type buildOptions struct {
|
||||
target string
|
||||
imageIDFile string
|
||||
stream bool
|
||||
platform string
|
||||
}
|
||||
|
||||
// dockerfileFromStdin returns true when the user specified that the Dockerfile
|
||||
@ -136,7 +134,6 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file")
|
||||
|
||||
command.AddTrustVerificationFlags(flags)
|
||||
command.AddPlatformFlag(flags, &options.platform)
|
||||
|
||||
flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer")
|
||||
flags.SetAnnotation("squash", "experimental", nil)
|
||||
@ -246,7 +243,6 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
|
||||
excludes = build.TrimBuildFilesFromExcludes(excludes, relDockerfile, options.dockerfileFromStdin())
|
||||
buildCtx, err = archive.TarWithOptions(contextDir, &archive.TarOptions{
|
||||
ExcludePatterns: excludes,
|
||||
ChownOpts: &idtools.IDPair{UID: 0, GID: 0},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@ -307,8 +303,8 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
|
||||
progressOutput = &lastProgressOutput{output: progressOutput}
|
||||
}
|
||||
|
||||
// if up to this point nothing has set the context then we must have another
|
||||
// way for sending it(streaming) and set the context to the Dockerfile
|
||||
// if up to this point nothing has set the context then we must have have
|
||||
// another way for sending it(streaming) and set the context to the Dockerfile
|
||||
if dockerfileCtx != nil && buildCtx == nil {
|
||||
buildCtx = dockerfileCtx
|
||||
}
|
||||
@ -376,18 +372,17 @@ func runBuild(dockerCli command.Cli, options buildOptions) error {
|
||||
ExtraHosts: options.extraHosts.GetAll(),
|
||||
Target: options.target,
|
||||
RemoteContext: remote,
|
||||
Platform: options.platform,
|
||||
}
|
||||
|
||||
if s != nil {
|
||||
go func() {
|
||||
logrus.Debugf("running session: %v", s.ID())
|
||||
logrus.Debugf("running session: %v", s.UUID())
|
||||
if err := s.Run(ctx, dockerCli.Client().DialSession); err != nil {
|
||||
logrus.Error(err)
|
||||
cancel() // cancel progress context
|
||||
}
|
||||
}()
|
||||
buildOptions.SessionID = s.ID()
|
||||
buildOptions.SessionID = s.UUID()
|
||||
}
|
||||
|
||||
response, err := dockerCli.Client().ImageBuild(ctx, body, buildOptions)
|
||||
|
||||
@ -53,9 +53,7 @@ func addDirToSession(session *session.Session, contextDir string, progressOutput
|
||||
|
||||
p := &sizeProgress{out: progressOutput, action: "Streaming build context to Docker daemon"}
|
||||
|
||||
workdirProvider := filesync.NewFSSyncProvider([]filesync.SyncedDir{
|
||||
{Dir: contextDir, Excludes: excludes},
|
||||
})
|
||||
workdirProvider := filesync.NewFSSyncProvider(contextDir, excludes)
|
||||
session.Allow(workdirProvider)
|
||||
|
||||
// this will be replaced on parallel build jobs. keep the current
|
||||
@ -128,18 +126,18 @@ func getBuildSharedKey(dir string) (string, error) {
|
||||
return hex.EncodeToString(s[:]), nil
|
||||
}
|
||||
|
||||
func tryNodeIdentifier() string {
|
||||
out := cliconfig.Dir() // return config dir as default on permission error
|
||||
func tryNodeIdentifier() (out string) {
|
||||
out = cliconfig.Dir() // return config dir as default on permission error
|
||||
if err := os.MkdirAll(cliconfig.Dir(), 0700); err == nil {
|
||||
sessionFile := filepath.Join(cliconfig.Dir(), ".buildNodeID")
|
||||
if _, err := os.Lstat(sessionFile); err != nil {
|
||||
if os.IsNotExist(err) { // create a new file with stored randomness
|
||||
b := make([]byte, 32)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return out
|
||||
return
|
||||
}
|
||||
if err := ioutil.WriteFile(sessionFile, []byte(hex.EncodeToString(b)), 0600); err != nil {
|
||||
return out
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -149,5 +147,5 @@ func tryNodeIdentifier() string {
|
||||
return string(dt)
|
||||
}
|
||||
}
|
||||
return out
|
||||
return
|
||||
}
|
||||
|
||||
@ -6,57 +6,18 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/archive"
|
||||
"github.com/gotestyourself/gotestyourself/fs"
|
||||
"github.com/gotestyourself/gotestyourself/skip"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func TestRunBuildResetsUidAndGidInContext(t *testing.T) {
|
||||
skip.IfCondition(t, runtime.GOOS == "windows", "uid and gid not relevant on windows")
|
||||
dest := fs.NewDir(t, "test-build-context-dest")
|
||||
defer dest.Remove()
|
||||
|
||||
fakeImageBuild := func(_ context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) {
|
||||
assert.NoError(t, archive.Untar(context, dest.Path(), nil))
|
||||
|
||||
body := new(bytes.Buffer)
|
||||
return types.ImageBuildResponse{Body: ioutil.NopCloser(body)}, nil
|
||||
}
|
||||
cli := test.NewFakeCli(&fakeClient{imageBuildFunc: fakeImageBuild})
|
||||
|
||||
dir := fs.NewDir(t, "test-build-context",
|
||||
fs.WithFile("foo", "some content", fs.AsUser(65534, 65534)),
|
||||
fs.WithFile("Dockerfile", `
|
||||
FROM alpine:3.6
|
||||
COPY foo bar /
|
||||
`),
|
||||
)
|
||||
defer dir.Remove()
|
||||
|
||||
options := newBuildOptions()
|
||||
options.context = dir.Path()
|
||||
|
||||
err := runBuild(cli, options)
|
||||
require.NoError(t, err)
|
||||
|
||||
files, err := ioutil.ReadDir(dest.Path())
|
||||
require.NoError(t, err)
|
||||
for _, fileInfo := range files {
|
||||
assert.Equal(t, uint32(0), fileInfo.Sys().(*syscall.Stat_t).Uid)
|
||||
assert.Equal(t, uint32(0), fileInfo.Sys().(*syscall.Stat_t).Gid)
|
||||
}
|
||||
}
|
||||
func TestRunBuildDockerfileFromStdinWithCompress(t *testing.T) {
|
||||
dest, err := ioutil.TempDir("", "test-build-compress-dest")
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -37,7 +37,7 @@ func NewPruneCommand(dockerCli command.Cli) *cobra.Command {
|
||||
fmt.Fprintln(dockerCli.Out(), "Total reclaimed space:", units.HumanSize(float64(spaceReclaimed)))
|
||||
return nil
|
||||
},
|
||||
Annotations: map[string]string{"version": "1.25"},
|
||||
Tags: map[string]string{"version": "1.25"},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
@ -65,12 +65,12 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
|
||||
warning = allImageWarning
|
||||
}
|
||||
if !options.force && !command.PromptForConfirmation(dockerCli.In(), dockerCli.Out(), warning) {
|
||||
return 0, "", nil
|
||||
return
|
||||
}
|
||||
|
||||
report, err := dockerCli.Client().ImagesPrune(context.Background(), pruneFilters)
|
||||
if err != nil {
|
||||
return 0, "", err
|
||||
return
|
||||
}
|
||||
|
||||
if len(report.ImagesDeleted) > 0 {
|
||||
@ -85,7 +85,7 @@ func runPrune(dockerCli command.Cli, options pruneOptions) (spaceReclaimed uint6
|
||||
spaceReclaimed = report.SpaceReclaimed
|
||||
}
|
||||
|
||||
return spaceReclaimed, output, nil
|
||||
return
|
||||
}
|
||||
|
||||
// RunPrune calls the Image Prune API
|
||||
|
||||
@ -6,17 +6,16 @@ import (
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/registry"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type pullOptions struct {
|
||||
remote string
|
||||
all bool
|
||||
platform string
|
||||
remote string
|
||||
all bool
|
||||
}
|
||||
|
||||
// NewPullCommand creates a new `docker pull` command
|
||||
@ -36,39 +35,44 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command {
|
||||
flags := cmd.Flags()
|
||||
|
||||
flags.BoolVarP(&opts.all, "all-tags", "a", false, "Download all tagged images in the repository")
|
||||
|
||||
command.AddPlatformFlag(flags, &opts.platform)
|
||||
command.AddTrustVerificationFlags(flags)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runPull(cli command.Cli, opts pullOptions) error {
|
||||
func runPull(dockerCli command.Cli, opts pullOptions) error {
|
||||
distributionRef, err := reference.ParseNormalizedNamed(opts.remote)
|
||||
switch {
|
||||
case err != nil:
|
||||
if err != nil {
|
||||
return err
|
||||
case opts.all && !reference.IsNameOnly(distributionRef):
|
||||
}
|
||||
if opts.all && !reference.IsNameOnly(distributionRef) {
|
||||
return errors.New("tag can't be used with --all-tags/-a")
|
||||
case !opts.all && reference.IsNameOnly(distributionRef):
|
||||
}
|
||||
|
||||
if !opts.all && reference.IsNameOnly(distributionRef) {
|
||||
distributionRef = reference.TagNameOnly(distributionRef)
|
||||
if tagged, ok := distributionRef.(reference.Tagged); ok {
|
||||
fmt.Fprintf(cli.Out(), "Using default tag: %s\n", tagged.Tag())
|
||||
fmt.Fprintf(dockerCli.Out(), "Using default tag: %s\n", tagged.Tag())
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, AuthResolver(cli), distributionRef.String())
|
||||
// Resolve the Repository name from fqn to RepositoryInfo
|
||||
repoInfo, err := registry.ParseRepositoryInfo(distributionRef)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index)
|
||||
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "pull")
|
||||
|
||||
// Check if reference has a digest
|
||||
_, isCanonical := distributionRef.(reference.Canonical)
|
||||
if command.IsTrusted() && !isCanonical {
|
||||
err = trustedPull(ctx, cli, imgRefAndAuth, opts.platform)
|
||||
err = trustedPull(ctx, dockerCli, repoInfo, distributionRef, authConfig, requestPrivilege)
|
||||
} else {
|
||||
err = imagePullPrivileged(ctx, cli, imgRefAndAuth, opts.all, opts.platform)
|
||||
err = imagePullPrivileged(ctx, dockerCli, authConfig, reference.FamiliarString(distributionRef), requestPrivilege, opts.all)
|
||||
}
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "when fetching 'plugin'") {
|
||||
@ -76,5 +80,6 @@ func runPull(cli command.Cli, opts pullOptions) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -2,14 +2,11 @@ package image
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/docker/cli/internal/test"
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/gotestyourself/gotestyourself/golden"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -35,6 +32,11 @@ func TestNewPullCommandErrors(t *testing.T) {
|
||||
expectedError: "tag can't be used with --all-tags/-a",
|
||||
args: []string{"--all-tags", "image:tag"},
|
||||
},
|
||||
{
|
||||
name: "pull-error",
|
||||
args: []string{"--disable-content-trust=false", "image:tag"},
|
||||
expectedError: "you are not authorized to perform this operation: server returned 401.",
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
@ -47,28 +49,20 @@ func TestNewPullCommandErrors(t *testing.T) {
|
||||
|
||||
func TestNewPullCommandSuccess(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
args []string
|
||||
expectedTag string
|
||||
name string
|
||||
args []string
|
||||
}{
|
||||
{
|
||||
name: "simple",
|
||||
args: []string{"image:tag"},
|
||||
expectedTag: "image:tag",
|
||||
name: "simple",
|
||||
args: []string{"image:tag"},
|
||||
},
|
||||
{
|
||||
name: "simple-no-tag",
|
||||
args: []string{"image"},
|
||||
expectedTag: "image:latest",
|
||||
name: "simple-no-tag",
|
||||
args: []string{"image"},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
cli := test.NewFakeCli(&fakeClient{
|
||||
imagePullFunc: func(ref string, options types.ImagePullOptions) (io.ReadCloser, error) {
|
||||
assert.Equal(t, tc.expectedTag, ref, tc.name)
|
||||
return ioutil.NopCloser(strings.NewReader("")), nil
|
||||
},
|
||||
})
|
||||
cli := test.NewFakeCli(&fakeClient{})
|
||||
cmd := NewPullCommand(cli)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
|
||||
@ -48,7 +48,7 @@ func runPush(dockerCli command.Cli, remote string) error {
|
||||
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push")
|
||||
|
||||
if command.IsTrusted() {
|
||||
return TrustedPush(ctx, dockerCli, repoInfo, ref, authConfig, requestPrivilege)
|
||||
return trustedPush(ctx, dockerCli, repoInfo, ref, authConfig, requestPrivilege)
|
||||
}
|
||||
|
||||
responseBody, err := imagePushPrivileged(ctx, dockerCli, authConfig, ref, requestPrivilege)
|
||||
|
||||
@ -9,7 +9,6 @@ import (
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/docker/api/types"
|
||||
apiclient "github.com/docker/docker/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@ -57,13 +56,9 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error
|
||||
}
|
||||
|
||||
var errs []string
|
||||
var fatalErr = false
|
||||
for _, img := range images {
|
||||
dels, err := client.ImageRemove(ctx, img, options)
|
||||
if err != nil {
|
||||
if !apiclient.IsErrNotFound(err) {
|
||||
fatalErr = true
|
||||
}
|
||||
errs = append(errs, err.Error())
|
||||
} else {
|
||||
for _, del := range dels {
|
||||
@ -78,10 +73,10 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error
|
||||
|
||||
if len(errs) > 0 {
|
||||
msg := strings.Join(errs, "\n")
|
||||
if !opts.force || fatalErr {
|
||||
if !opts.force {
|
||||
return errors.New(msg)
|
||||
}
|
||||
fmt.Fprintln(dockerCli.Err(), msg)
|
||||
fmt.Fprintf(dockerCli.Err(), msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -13,18 +13,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type notFound struct {
|
||||
imageID string
|
||||
}
|
||||
|
||||
func (n notFound) Error() string {
|
||||
return fmt.Sprintf("Error: No such image: %s", n.imageID)
|
||||
}
|
||||
|
||||
func (n notFound) NotFound() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestNewRemoveCommandAlias(t *testing.T) {
|
||||
cmd := newRemoveCommand(test.NewFakeCli(&fakeClient{}))
|
||||
assert.True(t, cmd.HasAlias("rmi"))
|
||||
@ -43,15 +31,6 @@ func TestNewRemoveCommandErrors(t *testing.T) {
|
||||
name: "wrong args",
|
||||
expectedError: "requires at least 1 argument.",
|
||||
},
|
||||
{
|
||||
name: "ImageRemove fail with force option",
|
||||
args: []string{"-f", "image1"},
|
||||
expectedError: "error removing image",
|
||||
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
|
||||
assert.Equal(t, "image1", image)
|
||||
return []types.ImageDeleteResponseItem{}, errors.Errorf("error removing image")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ImageRemove fail",
|
||||
args: []string{"arg1"},
|
||||
@ -64,14 +43,12 @@ func TestNewRemoveCommandErrors(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
|
||||
imageRemoveFunc: tc.imageRemoveFunc,
|
||||
}))
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
})
|
||||
cmd := NewRemoveCommand(test.NewFakeCli(&fakeClient{
|
||||
imageRemoveFunc: tc.imageRemoveFunc,
|
||||
}))
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,7 +57,7 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
|
||||
name string
|
||||
args []string
|
||||
imageRemoveFunc func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error)
|
||||
expectedStderr string
|
||||
expectedErrMsg string
|
||||
}{
|
||||
{
|
||||
name: "Image Deleted",
|
||||
@ -91,16 +68,14 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Image not found with force option",
|
||||
name: "Image Deleted with force option",
|
||||
args: []string{"-f", "image1"},
|
||||
imageRemoveFunc: func(image string, options types.ImageRemoveOptions) ([]types.ImageDeleteResponseItem, error) {
|
||||
assert.Equal(t, "image1", image)
|
||||
assert.Equal(t, true, options.Force)
|
||||
return []types.ImageDeleteResponseItem{}, notFound{"image1"}
|
||||
return []types.ImageDeleteResponseItem{}, errors.Errorf("error removing image")
|
||||
},
|
||||
expectedStderr: "Error: No such image: image1\n",
|
||||
expectedErrMsg: "error removing image",
|
||||
},
|
||||
|
||||
{
|
||||
name: "Image Untagged",
|
||||
args: []string{"image1"},
|
||||
@ -121,14 +96,14 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
cli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc})
|
||||
cmd := NewRemoveCommand(cli)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
assert.NoError(t, cmd.Execute())
|
||||
assert.Equal(t, tc.expectedStderr, cli.ErrBuffer().String())
|
||||
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("remove-command-success.%s.golden", tc.name))
|
||||
})
|
||||
cli := test.NewFakeCli(&fakeClient{imageRemoveFunc: tc.imageRemoveFunc})
|
||||
cmd := NewRemoveCommand(cli)
|
||||
cmd.SetOutput(ioutil.Discard)
|
||||
cmd.SetArgs(tc.args)
|
||||
assert.NoError(t, cmd.Execute())
|
||||
if tc.expectedErrMsg != "" {
|
||||
assert.Equal(t, tc.expectedErrMsg, cli.ErrBuffer().String())
|
||||
}
|
||||
golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("remove-command-success.%s.golden", tc.name))
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,20 +5,20 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"path"
|
||||
"sort"
|
||||
|
||||
"github.com/docker/cli/cli/command"
|
||||
"github.com/docker/cli/cli/trust"
|
||||
"github.com/docker/distribution/reference"
|
||||
"github.com/docker/docker/api/types"
|
||||
registrytypes "github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/docker/docker/registry"
|
||||
digest "github.com/opencontainers/go-digest"
|
||||
"github.com/docker/notary/client"
|
||||
"github.com/docker/notary/tuf/data"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/theupdateframework/notary/client"
|
||||
"github.com/theupdateframework/notary/tuf/data"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
@ -28,8 +28,8 @@ type target struct {
|
||||
size int64
|
||||
}
|
||||
|
||||
// TrustedPush handles content trust pushing of an image
|
||||
func TrustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
|
||||
// trustedPush handles content trust pushing of an image
|
||||
func trustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
|
||||
responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -84,7 +84,7 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
|
||||
if err := jsonmessage.DisplayJSONMessagesToStream(in, streams.Out(), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Fprintln(streams.Err(), "No tag specified, skipping trust metadata push")
|
||||
fmt.Fprintln(streams.Out(), "No tag specified, skipping trust metadata push")
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -97,29 +97,31 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return errors.Errorf("no targets found, please provide a specific tag in order to sign it")
|
||||
fmt.Fprintln(streams.Out(), "No targets found, please provide a specific tag in order to sign it")
|
||||
return nil
|
||||
}
|
||||
|
||||
fmt.Fprintln(streams.Out(), "Signing and pushing trust metadata")
|
||||
|
||||
repo, err := trust.GetNotaryRepository(streams.In(), streams.Out(), command.UserAgent(), repoInfo, &authConfig, "push", "pull")
|
||||
repo, err := trust.GetNotaryRepository(streams, repoInfo, authConfig, "push", "pull")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error establishing connection to trust repository")
|
||||
fmt.Fprintf(streams.Out(), "Error establishing connection to notary repository: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// get the latest repository metadata so we can figure out which roles to sign
|
||||
_, err = repo.ListTargets()
|
||||
err = repo.Update(false)
|
||||
|
||||
switch err.(type) {
|
||||
case client.ErrRepoNotInitialized, client.ErrRepositoryNotExist:
|
||||
keys := repo.GetCryptoService().ListKeys(data.CanonicalRootRole)
|
||||
keys := repo.CryptoService.ListKeys(data.CanonicalRootRole)
|
||||
var rootKeyID string
|
||||
// always select the first root key
|
||||
if len(keys) > 0 {
|
||||
sort.Strings(keys)
|
||||
rootKeyID = keys[0]
|
||||
} else {
|
||||
rootPublicKey, err := repo.GetCryptoService().Create(data.CanonicalRootRole, "", data.ECDSAKey)
|
||||
rootPublicKey, err := repo.CryptoService.Create(data.CanonicalRootRole, "", data.ECDSAKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -134,7 +136,7 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
|
||||
err = repo.AddTarget(target, data.CanonicalTargetsRole)
|
||||
case nil:
|
||||
// already initialized and we have successfully downloaded the latest metadata
|
||||
err = AddTargetToAllSignableRoles(repo, target)
|
||||
err = addTargetToAllSignableRoles(repo, target)
|
||||
default:
|
||||
return trust.NotaryError(repoInfo.Name.Name(), err)
|
||||
}
|
||||
@ -144,24 +146,59 @@ func PushTrustedReference(streams command.Streams, repoInfo *registry.Repository
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "failed to sign %s:%s", repoInfo.Name.Name(), tag)
|
||||
fmt.Fprintf(streams.Out(), "Failed to sign %q:%s - %s\n", repoInfo.Name.Name(), tag, err.Error())
|
||||
return trust.NotaryError(repoInfo.Name.Name(), err)
|
||||
}
|
||||
|
||||
fmt.Fprintf(streams.Out(), "Successfully signed %s:%s\n", repoInfo.Name.Name(), tag)
|
||||
fmt.Fprintf(streams.Out(), "Successfully signed %q:%s\n", repoInfo.Name.Name(), tag)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddTargetToAllSignableRoles attempts to add the image target to all the top level delegation roles we can
|
||||
// Attempt to add the image target to all the top level delegation roles we can
|
||||
// (based on whether we have the signing key and whether the role's path allows
|
||||
// us to).
|
||||
// If there are no delegation roles, we add to the targets role.
|
||||
func AddTargetToAllSignableRoles(repo client.Repository, target *client.Target) error {
|
||||
signableRoles, err := trust.GetSignableRoles(repo, target)
|
||||
func addTargetToAllSignableRoles(repo *client.NotaryRepository, target *client.Target) error {
|
||||
var signableRoles []string
|
||||
|
||||
// translate the full key names, which includes the GUN, into just the key IDs
|
||||
allCanonicalKeyIDs := make(map[string]struct{})
|
||||
for fullKeyID := range repo.CryptoService.ListAllKeys() {
|
||||
allCanonicalKeyIDs[path.Base(fullKeyID)] = struct{}{}
|
||||
}
|
||||
|
||||
allDelegationRoles, err := repo.GetDelegationRoles()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// if there are no delegation roles, then just try to sign it into the targets role
|
||||
if len(allDelegationRoles) == 0 {
|
||||
return repo.AddTarget(target, data.CanonicalTargetsRole)
|
||||
}
|
||||
|
||||
// there are delegation roles, find every delegation role we have a key for, and
|
||||
// attempt to sign into into all those roles.
|
||||
for _, delegationRole := range allDelegationRoles {
|
||||
// We do not support signing any delegation role that isn't a direct child of the targets role.
|
||||
// Also don't bother checking the keys if we can't add the target
|
||||
// to this role due to path restrictions
|
||||
if path.Dir(delegationRole.Name) != data.CanonicalTargetsRole || !delegationRole.CheckPaths(target.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, canonicalKeyID := range delegationRole.KeyIDs {
|
||||
if _, ok := allCanonicalKeyIDs[canonicalKeyID]; ok {
|
||||
signableRoles = append(signableRoles, delegationRole.Name)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(signableRoles) == 0 {
|
||||
return errors.Errorf("no valid signing keys for delegation roles")
|
||||
}
|
||||
|
||||
return repo.AddTarget(target, signableRoles...)
|
||||
}
|
||||
|
||||
@ -180,13 +217,57 @@ func imagePushPrivileged(ctx context.Context, cli command.Cli, authConfig types.
|
||||
}
|
||||
|
||||
// trustedPull handles content trust pulling of an image
|
||||
func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, platform string) error {
|
||||
refs, err := getTrustedPullTargets(cli, imgRefAndAuth)
|
||||
func trustedPull(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
|
||||
var refs []target
|
||||
|
||||
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
ref := imgRefAndAuth.Reference()
|
||||
if tagged, isTagged := ref.(reference.NamedTagged); !isTagged {
|
||||
// List all targets
|
||||
targets, err := notaryRepo.ListTargets(trust.ReleasesRole, data.CanonicalTargetsRole)
|
||||
if err != nil {
|
||||
return trust.NotaryError(ref.Name(), err)
|
||||
}
|
||||
for _, tgt := range targets {
|
||||
t, err := convertTarget(tgt.Target)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.Out(), "Skipping target for %q\n", reference.FamiliarName(ref))
|
||||
continue
|
||||
}
|
||||
// Only list tags in the top level targets role or the releases delegation role - ignore
|
||||
// all other delegation roles
|
||||
if tgt.Role != trust.ReleasesRole && tgt.Role != data.CanonicalTargetsRole {
|
||||
continue
|
||||
}
|
||||
refs = append(refs, t)
|
||||
}
|
||||
if len(refs) == 0 {
|
||||
return trust.NotaryError(ref.Name(), errors.Errorf("No trusted tags for %s", ref.Name()))
|
||||
}
|
||||
} else {
|
||||
t, err := notaryRepo.GetTargetByName(tagged.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
|
||||
if err != nil {
|
||||
return trust.NotaryError(ref.Name(), err)
|
||||
}
|
||||
// Only get the tag if it's in the top level targets role or the releases delegation role
|
||||
// ignore it if it's in any other delegation roles
|
||||
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
|
||||
return trust.NotaryError(ref.Name(), errors.Errorf("No trust data for %s", tagged.Tag()))
|
||||
}
|
||||
|
||||
logrus.Debugf("retrieving target for %s role\n", t.Role)
|
||||
r, err := convertTarget(t.Target)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
}
|
||||
refs = append(refs, r)
|
||||
}
|
||||
|
||||
for i, r := range refs {
|
||||
displayTag := r.name
|
||||
if displayTag != "" {
|
||||
@ -198,11 +279,7 @@ func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.Image
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
updatedImgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, AuthResolver(cli), trustedRef.String())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := imagePullPrivileged(ctx, cli, updatedImgRefAndAuth, false, platform); err != nil {
|
||||
if err := imagePullPrivileged(ctx, cli, authConfig, reference.FamiliarString(trustedRef), requestPrivilege, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -218,70 +295,19 @@ func trustedPull(ctx context.Context, cli command.Cli, imgRefAndAuth trust.Image
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTrustedPullTargets(cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth) ([]target, error) {
|
||||
notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error establishing connection to trust repository")
|
||||
}
|
||||
|
||||
ref := imgRefAndAuth.Reference()
|
||||
tagged, isTagged := ref.(reference.NamedTagged)
|
||||
if !isTagged {
|
||||
// List all targets
|
||||
targets, err := notaryRepo.ListTargets(trust.ReleasesRole, data.CanonicalTargetsRole)
|
||||
if err != nil {
|
||||
return nil, trust.NotaryError(ref.Name(), err)
|
||||
}
|
||||
var refs []target
|
||||
for _, tgt := range targets {
|
||||
t, err := convertTarget(tgt.Target)
|
||||
if err != nil {
|
||||
fmt.Fprintf(cli.Err(), "Skipping target for %q\n", reference.FamiliarName(ref))
|
||||
continue
|
||||
}
|
||||
// Only list tags in the top level targets role or the releases delegation role - ignore
|
||||
// all other delegation roles
|
||||
if tgt.Role != trust.ReleasesRole && tgt.Role != data.CanonicalTargetsRole {
|
||||
continue
|
||||
}
|
||||
refs = append(refs, t)
|
||||
}
|
||||
if len(refs) == 0 {
|
||||
return nil, trust.NotaryError(ref.Name(), errors.Errorf("No trusted tags for %s", ref.Name()))
|
||||
}
|
||||
return refs, nil
|
||||
}
|
||||
|
||||
t, err := notaryRepo.GetTargetByName(tagged.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
|
||||
if err != nil {
|
||||
return nil, trust.NotaryError(ref.Name(), err)
|
||||
}
|
||||
// Only get the tag if it's in the top level targets role or the releases delegation role
|
||||
// ignore it if it's in any other delegation roles
|
||||
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
|
||||
return nil, trust.NotaryError(ref.Name(), errors.Errorf("No trust data for %s", tagged.Tag()))
|
||||
}
|
||||
|
||||
logrus.Debugf("retrieving target for %s role", t.Role)
|
||||
r, err := convertTarget(t.Target)
|
||||
return []target{r}, err
|
||||
}
|
||||
|
||||
// imagePullPrivileged pulls the image and displays it to the output
|
||||
func imagePullPrivileged(ctx context.Context, cli command.Cli, imgRefAndAuth trust.ImageRefAndAuth, all bool, platform string) error {
|
||||
ref := reference.FamiliarString(imgRefAndAuth.Reference())
|
||||
func imagePullPrivileged(ctx context.Context, cli command.Cli, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc, all bool) error {
|
||||
|
||||
encodedAuth, err := command.EncodeAuthToBase64(*imgRefAndAuth.AuthConfig())
|
||||
encodedAuth, err := command.EncodeAuthToBase64(authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(cli, imgRefAndAuth.RepoInfo().Index, "pull")
|
||||
options := types.ImagePullOptions{
|
||||
RegistryAuth: encodedAuth,
|
||||
PrivilegeFunc: requestPrivilege,
|
||||
All: all,
|
||||
Platform: platform,
|
||||
}
|
||||
|
||||
responseBody, err := cli.Client().ImagePull(ctx, ref, options)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -309,9 +335,10 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT
|
||||
// Resolve the Auth config relevant for this server
|
||||
authConfig := command.ResolveAuthConfig(ctx, cli, repoInfo.Index)
|
||||
|
||||
notaryRepo, err := trust.GetNotaryRepository(cli.In(), cli.Out(), command.UserAgent(), repoInfo, &authConfig, "pull")
|
||||
notaryRepo, err := trust.GetNotaryRepository(cli, repoInfo, authConfig, "pull")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error establishing connection to trust repository")
|
||||
fmt.Fprintf(cli.Out(), "Error establishing connection to trust repository: %s\n", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
|
||||
@ -321,13 +348,14 @@ func TrustedReference(ctx context.Context, cli command.Cli, ref reference.NamedT
|
||||
// Only list tags in the top level targets role or the releases delegation role - ignore
|
||||
// all other delegation roles
|
||||
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole {
|
||||
return nil, trust.NotaryError(repoInfo.Name.Name(), client.ErrNoSuchTarget(ref.Tag()))
|
||||
return nil, trust.NotaryError(repoInfo.Name.Name(), errors.Errorf("No trust data for %s", ref.Tag()))
|
||||
}
|
||||
r, err := convertTarget(t.Target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
}
|
||||
|
||||
return reference.WithDigest(reference.TrimNamed(ref), r.digest)
|
||||
}
|
||||
|
||||
@ -350,14 +378,7 @@ func TagTrusted(ctx context.Context, cli command.Cli, trustedRef reference.Canon
|
||||
familiarRef := reference.FamiliarString(ref)
|
||||
trustedFamiliarRef := reference.FamiliarString(trustedRef)
|
||||
|
||||
fmt.Fprintf(cli.Err(), "Tagging %s as %s\n", trustedFamiliarRef, familiarRef)
|
||||
fmt.Fprintf(cli.Out(), "Tagging %s as %s\n", trustedFamiliarRef, familiarRef)
|
||||
|
||||
return cli.Client().ImageTag(ctx, trustedFamiliarRef, familiarRef)
|
||||
}
|
||||
|
||||
// AuthResolver returns an auth resolver function from a command.Cli
|
||||
func AuthResolver(cli command.Cli) func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
|
||||
return func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig {
|
||||
return command.ResolveAuthConfig(ctx, cli, index)
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user