Compare commits

..

21 Commits

Author SHA1 Message Date
f9cde631a2 Merge pull request #347 from seemethere/cherry_pick_packaging_70
[17.12] Pass VERSION to engine static builds
2017-12-11 16:20:25 -08:00
264ab31364 Pass VERSION to engine static builds
VERSION file does not exist anymore for moby/moby so we need to
compensate

Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
(cherry picked from commit c411321b8870101c227b03e64930260984c862fd)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-12-12 00:17:04 +00:00
5d7e8f3778 Merge pull request #345 from jose-bigio/17.12_bump
[17.12] Bump version to 17.12.0-ce-rc2
2017-12-11 13:34:57 -08:00
74e076ab06 Merge pull request #346 from jose-bigio/17.12_changelog
[17.12] update changelog for 17.12.0 rc2
2017-12-11 13:34:26 -08:00
184c27d785 Merge pull request #342 from thaJeztah/17.12-backport-vendor-archive
[17.12] vendor: update archive/tar
2017-12-11 13:32:05 -08:00
33d771ed94 Update to 17.12 changelog for rc2
Signed-off-by: jose-bigio <jose.bigio@docker.com>
2017-12-11 11:27:18 -08:00
50e759c017 Bump version to 17.12.0-ce-rc2
Signed-off-by: jose-bigio <jose.bigio@docker.com>
2017-12-11 10:34:24 -08:00
bde1eb8281 Merge pull request #340 from seemethere/cherry_pick_version_packaging
[17.12] Defaults Makefile VERSION variable to 0.0.0-dev
2017-12-11 10:08:35 -08:00
fb8384a555 Merge pull request #343 from thaJeztah/17.12-backport-update-api-changelog
[17.12] Update API version-history for 1.35
2017-12-08 20:46:27 -08:00
86127edfd5 Merge pull request #341 from seemethere/cherry_pick_fix_f27
[17.12] Do not terminate on missing build-ids
2017-12-08 20:15:20 -08:00
d2b3575a03 Merge pull request #344 from andrewhsu/tt
[17.12] skip DockerTrustSuite tests for 17.12
2017-12-08 18:00:29 -08:00
d256539bf4 update integration-cli tests for stderr output
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
(cherry picked from commit 250b84ee88)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-12-08 16:49:02 -08:00
fbfecebc0a Blacklist tests, will be rewritten later on
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
(cherry picked from commit 4e81e4fa4e)
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
(cherry picked from commit ec6b0a1a4a)
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-12-08 16:49:01 -08:00
f73a0ff35c Update API version-history for 1.35
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
(cherry picked from commit 8a9d926b553345be530ddc51374c9817fcbea784)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-12-07 16:21:40 -08:00
5f5c42593a vendor: update archive/tar
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit 13954b0a6282d919c91626a23967377c19ee7aa2)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2017-12-07 16:20:52 -08:00
fcde1eb2b7 Do not terminate on missing build-ids
Relates to an upgrade in `rpm` to `14.4.0`, where missing build-ids now
cause builds to self-terminate. May be needed in other distros if/when
the `rpm` package is updated in those repos.

Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
(cherry picked from commit dcd681da54f2c2b2286cc09d053dd6cb865d3f98)
Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-12-08 00:11:46 +00:00
b52085fc2e Defaults Makefile VERSION variable to 0.0.0-dev (#67)
When building we should default to a dummy version unless otherwise
specified so we don't get ourselves confused over what is official and
what is not.

Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
(cherry picked from commit ffcd040b5c3bd4f81e1d1f11c32f9bb81e235ed1)

Signed-off-by: Eli Uriegas <eli.uriegas@docker.com>
2017-12-07 22:58:01 +00:00
ee2f9437b6 Merge pull request #337 from andrewhsu/v12
[17.12] bump version to 17.12.0-ce-rc1
2017-12-06 23:33:02 -08:00
8e51e59c8a bump version to 17.12.0-ce-rc1
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-12-06 23:29:37 -08:00
a4b9862912 Merge pull request #335 from vieux/CHANGELOG-17.12
[17.12] add changelog for 17.12.0-ce-rc1
2017-12-06 22:50:06 -08:00
718cc1a071 changelog for 17.12.0-ce-rc1
Signed-off-by: Victor Vieux <victorvieux@gmail.com>
Signed-off-by: Andrew Hsu <andrewhsu@docker.com>
2017-12-06 22:48:27 -08:00
1692 changed files with 12345 additions and 335367 deletions

View File

@ -1,71 +1,88 @@
# 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.
## 17.12.0-ce (2017-12-DD)
## 18.02.0-ce (2018-02-DD)
### Builder
- Gitutils: fix checking out submodules [moby/moby#35737](https://github.com/moby/moby/pull/35737)
- Fix build cache hash for broken symlink [moby/moby#34271](https://github.com/moby/moby/pull/34271)
- Fix long stream sync [moby/moby#35404](https://github.com/moby/moby/pull/35404)
- Fix dockerfile parser failing silently on long tokens [moby/moby#35429](https://github.com/moby/moby/pull/35429)
### 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)
* Remove secret/config duplication in cli/compose [docker/cli#671](https://github.com/docker/cli/pull/671)
* Add `--local` flag to `docker trust sign` [docker/cli#575](https://github.com/docker/cli/pull/575)
* Add `docker trust inspect` [docker/cli#694](https://github.com/docker/cli/pull/694)
+ Add `name` field to secrets and configs to allow interpolation in Compose files [docker/cli#668](https://github.com/docker/cli/pull/668)
+ Add `--isolation` for setting swarm service isolation mode [docker/cli#426](https://github.com/docker/cli/pull/426)
* Remove deprecated "daemon" subcommand [docker/cli#689](https://github.com/docker/cli/pull/689)
- Fix behaviour of `rmi -f` with unexpected errors [docker/cli#654](https://github.com/docker/cli/pull/654)
* Integrated Generic resource in service create [docker/cli#429](https://github.com/docker/cli/pull/429)
### Experimental
### Documentation
+ 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)
* Update API version history for 1.35 [moby/moby#35724](https://github.com/moby/moby/pull/35724)
### 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)
* Logentries driver line-only=true []byte output fix [moby/moby#35612](https://github.com/moby/moby/pull/35612)
* Logentries line-only logopt fix to maintain backwards compatibility [moby/moby#35628](https://github.com/moby/moby/pull/35628)
+ Add `--until` flag for docker logs [moby/moby#32914](https://github.com/moby/moby/pull/32914)
+ Add gelf log driver plugin to Windows build [moby/moby#35073](https://github.com/moby/moby/pull/35073)
* Set timeout on splunk batch send [moby/moby#35496](https://github.com/moby/moby/pull/35496)
### 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)
* Move load balancer sandbox creation/deletion into libnetwork [moby/moby#35422](https://github.com/moby/moby/pull/35422)
* Only chown network files within container metadata [moby/moby#34224](https://github.com/moby/moby/pull/34224)
* Restore error type in FindNetwork [moby/moby#35634](https://github.com/moby/moby/pull/35634)
- Fix consumes MIME type for NetworkConnect [moby/moby#35542](https://github.com/moby/moby/pull/35542)
+ Added support for persisting Windows network driver specific options [moby/moby#35563](https://github.com/moby/moby/pull/35563)
- Fix timeout on netlink sockets and watchmiss leak [moby/moby#35677](https://github.com/moby/moby/pull/35677)
+ New daemon config for networking diagnosis [moby/moby#35677](https://github.com/moby/moby/pull/35677)
### 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)
* Update to containerd v1.0.0 [moby/moby#35707](https://github.com/moby/moby/pull/35707)
* Have VFS graphdriver use accelerated in-kernel copy [moby/moby#35537](https://github.com/moby/moby/pull/35537)
* Introduce `workingdir` option for docker exec [moby/moby#35661](https://github.com/moby/moby/pull/35661)
* Bump Go to 1.9.2 [moby/moby#33892](https://github.com/moby/moby/pull/33892) [docker/cli#716](https://github.com/docker/cli/pull/716)
* `/dev` should not be readonly with `--readonly` flag [moby/moby#35344](https://github.com/moby/moby/pull/35344)
+ Add custom build-time Graphdrivers priority list [moby/moby#35522](https://github.com/moby/moby/pull/35522)
* LCOW: CLI changes to add platform flag - pull, run, create and build [docker/cli#474](https://github.com/docker/cli/pull/474)
* Fix width/height on Windoes for `docker exec` [moby/moby#35631](https://github.com/moby/moby/pull/35631)
* Detect overlay2 support on pre-4.0 kernels [moby/moby#35527](https://github.com/moby/moby/pull/35527)
* Devicemapper: remove container rootfs mountPath after umount [moby/moby#34573](https://github.com/moby/moby/pull/34573)
* Disallow overlay/overlay2 on top of NFS [moby/moby#35483](https://github.com/moby/moby/pull/35483)
- Fix potential panic during plugin set. [moby/moby#35632](https://github.com/moby/moby/pull/35632)
- Fix some issues with locking on the container [moby/moby#35501](https://github.com/moby/moby/pull/35501)
- Fixup some issues with plugin refcounting [moby/moby#35265](https://github.com/moby/moby/pull/35265)
+ Add missing lock in ProcessEvent [moby/moby#35516](https://github.com/moby/moby/pull/35516)
+ Add vfs quota support [moby/moby#35231](https://github.com/moby/moby/pull/35231)
* Skip empty directories on prior graphdriver detection [moby/moby#35528](https://github.com/moby/moby/pull/35528)
* Skip xfs quota tests when running in user namespace [moby/moby#35526](https://github.com/moby/moby/pull/35526)
+ Added SubSecondPrecision to config option. [moby/moby#35529](https://github.com/moby/moby/pull/35529)
* Update fsnotify to fix deadlock in removing watch [moby/moby#35453](https://github.com/moby/moby/pull/35453)
- Fix "duplicate mount point" when `--tmpfs /dev/shm` is used [moby/moby#35467](https://github.com/moby/moby/pull/35467)
- Fix honoring tmpfs-size for user `/dev/shm` mount [moby/moby#35316](https://github.com/moby/moby/pull/35316)
- Fix EBUSY errors under overlayfs and v4.13+ kernels [moby/moby#34948](https://github.com/moby/moby/pull/34948)
* Container: protect health monitor channel [moby/moby#35482](https://github.com/moby/moby/pull/35482)
* Container: protect the health status with mutex [moby/moby#35517](https://github.com/moby/moby/pull/35517)
* Container: update real-time resources [moby/moby#33731](https://github.com/moby/moby/pull/33731)
* Create labels when volume exists only remotely [moby/moby#34896](https://github.com/moby/moby/pull/34896)
* Fix leaking container/exec state [moby/moby#35484](https://github.com/moby/moby/pull/35484)
### Swarm Mode
+ Added support for swarm service isolation mode [moby/moby#34424](https://github.com/moby/moby/pull/34424)
### Packaging
+ Added Packaging for Fedora 27 [docker/docker-ce-packaging#59](https://github.com/docker/docker-ce-packaging/pull/59)
* Changed default versioning scheme to 0.0.0-dev unless specified for packaging [docker/docker-ce-packaging#67](https://github.com/docker/docker-ce-packaging/pull/67)

View File

@ -1 +1 @@
18.02.0-ce-rc2
17.12.0-ce-rc2

View File

@ -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>

View File

@ -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>

View File

@ -54,10 +54,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

View File

@ -1 +1 @@
18.02.0-ce-rc2
17.12.0-ce-rc2

View File

@ -5,22 +5,16 @@ 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"
@ -48,28 +42,24 @@ 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 +104,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
@ -155,36 +125,17 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
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)
}
}
func (cli *DockerCli) initializeFromClient() {
cli.defaultVersion = cli.client.ClientVersion()
ping, err := cli.client.Ping(context.Background())
if err != nil {
// Default to true if we fail to connect to daemon
cli.serverInfo = ServerInfo{HasExperimental: true}
cli.server = ServerInfo{HasExperimental: true}
if ping.APIVersion != "" {
cli.client.NegotiateAPIVersionPing(ping)
@ -192,7 +143,7 @@ func (cli *DockerCli) initializeFromClient() {
return
}
cli.serverInfo = ServerInfo{
cli.server = ServerInfo{
HasExperimental: ping.Experimental,
OSType: ping.OSType,
}
@ -225,18 +176,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}

View File

@ -1,18 +1,17 @@
package command
import (
"crypto/x509"
"os"
"testing"
cliconfig "github.com/docker/cli/cli/config"
"crypto/x509"
"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"
@ -125,155 +124,13 @@ func TestInitializeFromClient(t *testing.T) {
cli := &DockerCli{client: apiclient}
cli.initializeFromClient()
assert.Equal(t, testcase.expectedServer, cli.serverInfo)
assert.Equal(t, defaultVersion, cli.defaultVersion)
assert.Equal(t, testcase.expectedServer, cli.server)
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"

View File

@ -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"
@ -40,15 +39,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),

View File

@ -10,14 +10,11 @@ import (
// NewConfigCommand returns a cobra command for `config` subcommands
func NewConfigCommand(dockerCli command.Cli) *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": "",
},
Use: "config",
Short: "Manage Docker configs",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"version": "1.30"},
}
cmd.AddCommand(
newConfigListCommand(dockerCli),

View File

@ -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]

View File

@ -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

View File

@ -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{

View File

@ -21,8 +21,6 @@ type fakeClient struct {
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
}
func (f *fakeClient) ContainerInspect(_ context.Context, containerID string) (types.ContainerJSON, error) {
@ -97,14 +95,3 @@ func (f *fakeClient) ContainerLogs(_ context.Context, container string, 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
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -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, ",")
}

View File

@ -224,124 +224,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())
}

View File

@ -81,7 +81,7 @@ func runRemove(dockerCli command.Cli, opts removeOptions, images []string) error
if !opts.force || fatalErr {
return errors.New(msg)
}
fmt.Fprintln(dockerCli.Err(), msg)
fmt.Fprintf(dockerCli.Err(), msg)
}
return nil
}

View File

@ -98,7 +98,7 @@ func TestNewRemoveCommandSuccess(t *testing.T) {
assert.Equal(t, true, options.Force)
return []types.ImageDeleteResponseItem{}, notFound{"image1"}
},
expectedStderr: "Error: No such image: image1\n",
expectedStderr: "Error: No such image: image1",
},
{

View File

@ -1,93 +0,0 @@
package manifest
import (
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/store"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
type annotateOptions struct {
target string // the target manifest list name (also transaction ID)
image string // the manifest to annotate within the list
variant string // an architecture variant
os string
arch string
osFeatures []string
}
// NewAnnotateCommand creates a new `docker manifest annotate` command
func newAnnotateCommand(dockerCli command.Cli) *cobra.Command {
var opts annotateOptions
cmd := &cobra.Command{
Use: "annotate [OPTIONS] MANIFEST_LIST MANIFEST",
Short: "Add additional information to a local image manifest",
Args: cli.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
opts.target = args[0]
opts.image = args[1]
return runManifestAnnotate(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.StringVar(&opts.os, "os", "", "Set operating system")
flags.StringVar(&opts.arch, "arch", "", "Set architecture")
flags.StringSliceVar(&opts.osFeatures, "os-features", []string{}, "Set operating system feature")
flags.StringVar(&opts.variant, "variant", "", "Set architecture variant")
return cmd
}
func runManifestAnnotate(dockerCli command.Cli, opts annotateOptions) error {
targetRef, err := normalizeReference(opts.target)
if err != nil {
return errors.Wrapf(err, "annotate: error parsing name for manifest list %s", opts.target)
}
imgRef, err := normalizeReference(opts.image)
if err != nil {
return errors.Wrapf(err, "annotate: error parsing name for manifest %s", opts.image)
}
manifestStore := dockerCli.ManifestStore()
imageManifest, err := manifestStore.Get(targetRef, imgRef)
switch {
case store.IsNotFound(err):
return fmt.Errorf("manifest for image %s does not exist in %s", opts.image, opts.target)
case err != nil:
return err
}
// Update the mf
if opts.os != "" {
imageManifest.Platform.OS = opts.os
}
if opts.arch != "" {
imageManifest.Platform.Architecture = opts.arch
}
for _, osFeature := range opts.osFeatures {
imageManifest.Platform.OSFeatures = appendIfUnique(imageManifest.Platform.OSFeatures, osFeature)
}
if opts.variant != "" {
imageManifest.Platform.Variant = opts.variant
}
if !isValidOSArch(imageManifest.Platform.OS, imageManifest.Platform.Architecture) {
return errors.Errorf("manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
}
return manifestStore.Save(targetRef, imgRef, imageManifest)
}
func appendIfUnique(list []string, str string) []string {
for _, s := range list {
if s == str {
return list
}
}
return append(list, str)
}

View File

@ -1,78 +0,0 @@
package manifest
import (
"io/ioutil"
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/testutil"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestManifestAnnotateError(t *testing.T) {
testCases := []struct {
args []string
expectedError string
}{
{
args: []string{"too-few-arguments"},
expectedError: "requires exactly 2 arguments",
},
{
args: []string{"th!si'sa/fa!ke/li$t/name", "example.com/alpine:3.0"},
expectedError: "error parsing name for manifest list",
},
{
args: []string{"example.com/list:v1", "th!si'sa/fa!ke/im@ge/nam32"},
expectedError: "error parsing name for manifest",
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(nil)
cmd := newAnnotateCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
func TestManifestAnnotate(t *testing.T) {
store, cleanup := newTempManifestStore(t)
defer cleanup()
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
namedRef := ref(t, "alpine:3.0")
imageManifest := fullImageManifest(t, namedRef)
err := store.Save(ref(t, "list:v1"), namedRef, imageManifest)
require.NoError(t, err)
cmd := newAnnotateCommand(cli)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/fake:0.0"})
cmd.SetOutput(ioutil.Discard)
expectedError := "manifest for image example.com/fake:0.0 does not exist"
testutil.ErrorContains(t, cmd.Execute(), expectedError)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
cmd.Flags().Set("os", "freebsd")
cmd.Flags().Set("arch", "fake")
cmd.Flags().Set("os-features", "feature1")
cmd.Flags().Set("variant", "v7")
expectedError = "manifest entry for image has unsupported os/arch combination"
testutil.ErrorContains(t, cmd.Execute(), expectedError)
cmd.Flags().Set("arch", "arm")
require.NoError(t, cmd.Execute())
cmd = newInspectCommand(cli)
err = cmd.Flags().Set("verbose", "true")
require.NoError(t, err)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
require.NoError(t, cmd.Execute())
actual := cli.OutBuffer()
expected := golden.Get(t, "inspect-annotate.golden")
assert.Equal(t, string(expected), actual.String())
}

View File

@ -1,46 +0,0 @@
package manifest
import (
manifesttypes "github.com/docker/cli/cli/manifest/types"
"github.com/docker/cli/cli/registry/client"
"github.com/docker/distribution"
"github.com/docker/distribution/reference"
"github.com/opencontainers/go-digest"
"golang.org/x/net/context"
)
type fakeRegistryClient struct {
client.RegistryClient
getManifestFunc func(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error)
getManifestListFunc func(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error)
mountBlobFunc func(ctx context.Context, source reference.Canonical, target reference.Named) error
putManifestFunc func(ctx context.Context, source reference.Named, mf distribution.Manifest) (digest.Digest, error)
}
func (c *fakeRegistryClient) GetManifest(ctx context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
if c.getManifestFunc != nil {
return c.getManifestFunc(ctx, ref)
}
return manifesttypes.ImageManifest{}, nil
}
func (c *fakeRegistryClient) GetManifestList(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
if c.getManifestListFunc != nil {
return c.getManifestListFunc(ctx, ref)
}
return nil, nil
}
func (c *fakeRegistryClient) MountBlob(ctx context.Context, source reference.Canonical, target reference.Named) error {
if c.mountBlobFunc != nil {
return c.mountBlobFunc(ctx, source, target)
}
return nil
}
func (c *fakeRegistryClient) PutManifest(ctx context.Context, ref reference.Named, mf distribution.Manifest) (digest.Digest, error) {
if c.putManifestFunc != nil {
return c.putManifestFunc(ctx, ref, mf)
}
return digest.Digest(""), nil
}

View File

@ -1,45 +0,0 @@
package manifest
import (
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
)
// NewManifestCommand returns a cobra command for `manifest` subcommands
func NewManifestCommand(dockerCli command.Cli) *cobra.Command {
// use dockerCli as command.Cli
cmd := &cobra.Command{
Use: "manifest COMMAND",
Short: "Manage Docker image manifests and manifest lists",
Long: manifestDescription,
Args: cli.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
fmt.Fprintf(dockerCli.Err(), "\n"+cmd.UsageString())
},
Annotations: map[string]string{"experimentalCLI": ""},
}
cmd.AddCommand(
newCreateListCommand(dockerCli),
newInspectCommand(dockerCli),
newAnnotateCommand(dockerCli),
newPushListCommand(dockerCli),
)
return cmd
}
var manifestDescription = `
The **docker manifest** command has subcommands for managing image manifests and
manifest lists. A manifest list allows you to use one name to refer to the same image
built for multiple architectures.
To see help for a subcommand, use:
docker manifest CMD --help
For full details on using docker manifest lists, see the registry v2 specification.
`

View File

@ -1,82 +0,0 @@
package manifest
import (
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/store"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type createOpts struct {
amend bool
insecure bool
}
func newCreateListCommand(dockerCli command.Cli) *cobra.Command {
opts := createOpts{}
cmd := &cobra.Command{
Use: "create MANFEST_LIST MANIFEST [MANIFEST...]",
Short: "Create a local manifest list for annotating and pushing to a registry",
Args: cli.RequiresMinArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return createManifestList(dockerCli, args, opts)
},
}
flags := cmd.Flags()
flags.BoolVar(&opts.insecure, "insecure", false, "allow communication with an insecure registry")
flags.BoolVarP(&opts.amend, "amend", "a", false, "Amend an existing manifest list")
return cmd
}
func createManifestList(dockerCli command.Cli, args []string, opts createOpts) error {
newRef := args[0]
targetRef, err := normalizeReference(newRef)
if err != nil {
return errors.Wrapf(err, "error parsing name for manifest list %s", newRef)
}
_, err = registry.ParseRepositoryInfo(targetRef)
if err != nil {
return errors.Wrapf(err, "error parsing repository name for manifest list %s", newRef)
}
manifestStore := dockerCli.ManifestStore()
_, err = manifestStore.GetList(targetRef)
switch {
case store.IsNotFound(err):
// New manifest list
case err != nil:
return err
case !opts.amend:
return errors.Errorf("refusing to amend an existing manifest list with no --amend flag")
}
ctx := context.Background()
// Now create the local manifest list transaction by looking up the manifest schemas
// for the constituent images:
manifests := args[1:]
for _, manifestRef := range manifests {
namedRef, err := normalizeReference(manifestRef)
if err != nil {
// TODO: wrap error?
return err
}
manifest, err := getManifest(ctx, dockerCli, targetRef, namedRef, opts.insecure)
if err != nil {
return err
}
if err := manifestStore.Save(targetRef, namedRef, manifest); err != nil {
return err
}
}
fmt.Fprintf(dockerCli.Out(), "Created manifest list %s\n", targetRef.String())
return nil
}

View File

@ -1,117 +0,0 @@
package manifest
import (
"io/ioutil"
"testing"
manifesttypes "github.com/docker/cli/cli/manifest/types"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/testutil"
"github.com/docker/distribution/reference"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func TestManifestCreateErrors(t *testing.T) {
testCases := []struct {
args []string
expectedError string
}{
{
args: []string{"too-few-arguments"},
expectedError: "requires at least 2 arguments",
},
{
args: []string{"th!si'sa/fa!ke/li$t/name", "example.com/alpine:3.0"},
expectedError: "error parsing name for manifest list",
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(nil)
cmd := newCreateListCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
// create a manifest list, then overwrite it, and inspect to see if the old one is still there
func TestManifestCreateAmend(t *testing.T) {
store, cleanup := newTempManifestStore(t)
defer cleanup()
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
namedRef := ref(t, "alpine:3.0")
imageManifest := fullImageManifest(t, namedRef)
err := store.Save(ref(t, "list:v1"), namedRef, imageManifest)
require.NoError(t, err)
namedRef = ref(t, "alpine:3.1")
imageManifest = fullImageManifest(t, namedRef)
err = store.Save(ref(t, "list:v1"), namedRef, imageManifest)
require.NoError(t, err)
cmd := newCreateListCommand(cli)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.1"})
cmd.Flags().Set("amend", "true")
cmd.SetOutput(ioutil.Discard)
err = cmd.Execute()
require.NoError(t, err)
// make a new cli to clear the buffers
cli = test.NewFakeCli(nil)
cli.SetManifestStore(store)
inspectCmd := newInspectCommand(cli)
inspectCmd.SetArgs([]string{"example.com/list:v1"})
require.NoError(t, inspectCmd.Execute())
actual := cli.OutBuffer()
expected := golden.Get(t, "inspect-manifest-list.golden")
assert.Equal(t, string(expected), actual.String())
}
// attempt to overwrite a saved manifest and get refused
func TestManifestCreateRefuseAmend(t *testing.T) {
store, cleanup := newTempManifestStore(t)
defer cleanup()
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
namedRef := ref(t, "alpine:3.0")
imageManifest := fullImageManifest(t, namedRef)
err := store.Save(ref(t, "list:v1"), namedRef, imageManifest)
require.NoError(t, err)
cmd := newCreateListCommand(cli)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
cmd.SetOutput(ioutil.Discard)
err = cmd.Execute()
assert.EqualError(t, err, "refusing to amend an existing manifest list with no --amend flag")
}
// attempt to make a manifest list without valid images
func TestManifestCreateNoManifest(t *testing.T) {
store, cleanup := newTempManifestStore(t)
defer cleanup()
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
cli.SetRegistryClient(&fakeRegistryClient{
getManifestFunc: func(_ context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
return manifesttypes.ImageManifest{}, errors.Errorf("No such image: %v", ref)
},
getManifestListFunc: func(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
return nil, errors.Errorf("No such manifest: %s", ref)
},
})
cmd := newCreateListCommand(cli)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
cmd.SetOutput(ioutil.Discard)
err := cmd.Execute()
assert.EqualError(t, err, "No such image: example.com/alpine:3.0")
}

View File

@ -1,147 +0,0 @@
package manifest
import (
"bytes"
"encoding/json"
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/types"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/reference"
"github.com/docker/docker/registry"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type inspectOptions struct {
ref string
list string
verbose bool
insecure bool
}
// NewInspectCommand creates a new `docker manifest inspect` command
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
var opts inspectOptions
cmd := &cobra.Command{
Use: "inspect [OPTIONS] [MANIFEST_LIST] MANIFEST",
Short: "Display an image manifest, or manifest list",
Args: cli.RequiresRangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
switch len(args) {
case 1:
opts.ref = args[0]
case 2:
opts.list = args[0]
opts.ref = args[1]
}
return runInspect(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.BoolVar(&opts.insecure, "insecure", false, "allow communication with an insecure registry")
flags.BoolVarP(&opts.verbose, "verbose", "v", false, "Output additional info including layers and platform")
return cmd
}
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
namedRef, err := normalizeReference(opts.ref)
if err != nil {
return err
}
// If list reference is provided, display the local manifest in a list
if opts.list != "" {
listRef, err := normalizeReference(opts.list)
if err != nil {
return err
}
imageManifest, err := dockerCli.ManifestStore().Get(listRef, namedRef)
if err != nil {
return err
}
return printManifest(dockerCli, imageManifest, opts)
}
// Try a local manifest list first
localManifestList, err := dockerCli.ManifestStore().GetList(namedRef)
if err == nil {
return printManifestList(dockerCli, namedRef, localManifestList, opts)
}
// Next try a remote manifest
ctx := context.Background()
registryClient := dockerCli.RegistryClient(opts.insecure)
imageManifest, err := registryClient.GetManifest(ctx, namedRef)
if err == nil {
return printManifest(dockerCli, imageManifest, opts)
}
// Finally try a remote manifest list
manifestList, err := registryClient.GetManifestList(ctx, namedRef)
if err != nil {
return err
}
return printManifestList(dockerCli, namedRef, manifestList, opts)
}
func printManifest(dockerCli command.Cli, manifest types.ImageManifest, opts inspectOptions) error {
buffer := new(bytes.Buffer)
if !opts.verbose {
_, raw, err := manifest.Payload()
if err != nil {
return err
}
if err := json.Indent(buffer, raw, "", "\t"); err != nil {
return err
}
fmt.Fprintln(dockerCli.Out(), buffer.String())
return nil
}
jsonBytes, err := json.MarshalIndent(manifest, "", "\t")
if err != nil {
return err
}
dockerCli.Out().Write(append(jsonBytes, '\n'))
return nil
}
func printManifestList(dockerCli command.Cli, namedRef reference.Named, list []types.ImageManifest, opts inspectOptions) error {
if !opts.verbose {
targetRepo, err := registry.ParseRepositoryInfo(namedRef)
if err != nil {
return err
}
manifests := []manifestlist.ManifestDescriptor{}
// More than one response. This is a manifest list.
for _, img := range list {
mfd, err := buildManifestDescriptor(targetRepo, img)
if err != nil {
return fmt.Errorf("error assembling ManifestDescriptor")
}
manifests = append(manifests, mfd)
}
deserializedML, err := manifestlist.FromDescriptors(manifests)
if err != nil {
return err
}
jsonBytes, err := deserializedML.MarshalJSON()
if err != nil {
return err
}
fmt.Fprintln(dockerCli.Out(), string(jsonBytes))
return nil
}
jsonBytes, err := json.MarshalIndent(list, "", "\t")
if err != nil {
return err
}
dockerCli.Out().Write(append(jsonBytes, '\n'))
return nil
}

View File

@ -1,131 +0,0 @@
package manifest
import (
"io/ioutil"
"os"
"testing"
"github.com/docker/cli/cli/manifest/store"
"github.com/docker/cli/cli/manifest/types"
manifesttypes "github.com/docker/cli/cli/manifest/types"
"github.com/docker/cli/internal/test"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/schema2"
"github.com/docker/distribution/reference"
"github.com/gotestyourself/gotestyourself/golden"
"github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func newTempManifestStore(t *testing.T) (store.Store, func()) {
tmpdir, err := ioutil.TempDir("", "test-manifest-storage")
require.NoError(t, err)
return store.NewStore(tmpdir), func() { os.RemoveAll(tmpdir) }
}
func ref(t *testing.T, name string) reference.Named {
named, err := reference.ParseNamed("example.com/" + name)
require.NoError(t, err)
return named
}
func fullImageManifest(t *testing.T, ref reference.Named) types.ImageManifest {
man, err := schema2.FromStruct(schema2.Manifest{
Versioned: schema2.SchemaVersion,
Config: distribution.Descriptor{
Digest: "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d624560",
Size: 1520,
MediaType: schema2.MediaTypeImageConfig,
},
Layers: []distribution.Descriptor{
{
MediaType: schema2.MediaTypeLayer,
Size: 1990402,
Digest: "sha256:88286f41530e93dffd4b964e1db22ce4939fffa4a4c665dab8591fbab03d4926",
},
},
})
require.NoError(t, err)
// TODO: include image data for verbose inspect
return types.NewImageManifest(ref, digest.Digest("sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d62abcd"), types.Image{OS: "linux", Architecture: "amd64"}, man)
}
func TestInspectCommandLocalManifestNotFound(t *testing.T) {
store, cleanup := newTempManifestStore(t)
defer cleanup()
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
cmd := newInspectCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
err := cmd.Execute()
assert.EqualError(t, err, "No such manifest: example.com/alpine:3.0")
}
func TestInspectCommandNotFound(t *testing.T) {
store, cleanup := newTempManifestStore(t)
defer cleanup()
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
cli.SetRegistryClient(&fakeRegistryClient{
getManifestFunc: func(_ context.Context, _ reference.Named) (manifesttypes.ImageManifest, error) {
return manifesttypes.ImageManifest{}, errors.New("missing")
},
getManifestListFunc: func(ctx context.Context, ref reference.Named) ([]manifesttypes.ImageManifest, error) {
return nil, errors.Errorf("No such manifest: %s", ref)
},
})
cmd := newInspectCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"example.com/alpine:3.0"})
err := cmd.Execute()
assert.EqualError(t, err, "No such manifest: example.com/alpine:3.0")
}
func TestInspectCommandLocalManifest(t *testing.T) {
store, cleanup := newTempManifestStore(t)
defer cleanup()
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
namedRef := ref(t, "alpine:3.0")
imageManifest := fullImageManifest(t, namedRef)
err := store.Save(ref(t, "list:v1"), namedRef, imageManifest)
require.NoError(t, err)
cmd := newInspectCommand(cli)
cmd.SetArgs([]string{"example.com/list:v1", "example.com/alpine:3.0"})
require.NoError(t, cmd.Execute())
actual := cli.OutBuffer()
expected := golden.Get(t, "inspect-manifest.golden")
assert.Equal(t, string(expected), actual.String())
}
func TestInspectcommandRemoteManifest(t *testing.T) {
store, cleanup := newTempManifestStore(t)
defer cleanup()
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
cli.SetRegistryClient(&fakeRegistryClient{
getManifestFunc: func(_ context.Context, ref reference.Named) (manifesttypes.ImageManifest, error) {
return fullImageManifest(t, ref), nil
},
})
cmd := newInspectCommand(cli)
cmd.SetOutput(ioutil.Discard)
cmd.SetArgs([]string{"example.com/alpine:3.0"})
require.NoError(t, cmd.Execute())
actual := cli.OutBuffer()
expected := golden.Get(t, "inspect-manifest.golden")
assert.Equal(t, string(expected), actual.String())
}

View File

@ -1,273 +0,0 @@
package manifest
import (
"encoding/json"
"fmt"
"io"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/types"
registryclient "github.com/docker/cli/cli/registry/client"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema2"
"github.com/docker/distribution/reference"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type pushOpts struct {
insecure bool
purge bool
target string
}
type mountRequest struct {
ref reference.Named
manifest types.ImageManifest
}
type manifestBlob struct {
canonical reference.Canonical
os string
}
type pushRequest struct {
targetRef reference.Named
list *manifestlist.DeserializedManifestList
mountRequests []mountRequest
manifestBlobs []manifestBlob
insecure bool
}
func newPushListCommand(dockerCli command.Cli) *cobra.Command {
opts := pushOpts{}
cmd := &cobra.Command{
Use: "push [OPTIONS] MANIFEST_LIST",
Short: "Push a manifest list to a repository",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.target = args[0]
return runPush(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.purge, "purge", "p", false, "Remove the local manifest list after push")
flags.BoolVar(&opts.insecure, "insecure", false, "Allow push to an insecure registry")
return cmd
}
func runPush(dockerCli command.Cli, opts pushOpts) error {
targetRef, err := normalizeReference(opts.target)
if err != nil {
return err
}
manifests, err := dockerCli.ManifestStore().GetList(targetRef)
if err != nil {
return err
}
if len(manifests) == 0 {
return errors.Errorf("%s not found", targetRef)
}
pushRequest, err := buildPushRequest(manifests, targetRef, opts.insecure)
if err != nil {
return err
}
ctx := context.Background()
if err := pushList(ctx, dockerCli, pushRequest); err != nil {
return err
}
if opts.purge {
return dockerCli.ManifestStore().Remove(targetRef)
}
return nil
}
func buildPushRequest(manifests []types.ImageManifest, targetRef reference.Named, insecure bool) (pushRequest, error) {
req := pushRequest{targetRef: targetRef, insecure: insecure}
var err error
req.list, err = buildManifestList(manifests, targetRef)
if err != nil {
return req, err
}
targetRepo, err := registry.ParseRepositoryInfo(targetRef)
if err != nil {
return req, err
}
targetRepoName, err := registryclient.RepoNameForReference(targetRepo.Name)
if err != nil {
return req, err
}
for _, imageManifest := range manifests {
manifestRepoName, err := registryclient.RepoNameForReference(imageManifest.Ref)
if err != nil {
return req, err
}
repoName, _ := reference.WithName(manifestRepoName)
if repoName.Name() != targetRepoName {
blobs, err := buildBlobRequestList(imageManifest, repoName)
if err != nil {
return req, err
}
req.manifestBlobs = append(req.manifestBlobs, blobs...)
manifestPush, err := buildPutManifestRequest(imageManifest, targetRef)
if err != nil {
return req, err
}
req.mountRequests = append(req.mountRequests, manifestPush)
}
}
return req, nil
}
func buildManifestList(manifests []types.ImageManifest, targetRef reference.Named) (*manifestlist.DeserializedManifestList, error) {
targetRepoInfo, err := registry.ParseRepositoryInfo(targetRef)
if err != nil {
return nil, err
}
descriptors := []manifestlist.ManifestDescriptor{}
for _, imageManifest := range manifests {
if imageManifest.Platform.Architecture == "" || imageManifest.Platform.OS == "" {
return nil, errors.Errorf(
"manifest %s must have an OS and Architecture to be pushed to a registry", imageManifest.Ref)
}
descriptor, err := buildManifestDescriptor(targetRepoInfo, imageManifest)
if err != nil {
return nil, err
}
descriptors = append(descriptors, descriptor)
}
return manifestlist.FromDescriptors(descriptors)
}
func buildManifestDescriptor(targetRepo *registry.RepositoryInfo, imageManifest types.ImageManifest) (manifestlist.ManifestDescriptor, error) {
repoInfo, err := registry.ParseRepositoryInfo(imageManifest.Ref)
if err != nil {
return manifestlist.ManifestDescriptor{}, err
}
manifestRepoHostname := reference.Domain(repoInfo.Name)
targetRepoHostname := reference.Domain(targetRepo.Name)
if manifestRepoHostname != targetRepoHostname {
return manifestlist.ManifestDescriptor{}, errors.Errorf("cannot use source images from a different registry than the target image: %s != %s", manifestRepoHostname, targetRepoHostname)
}
mediaType, raw, err := imageManifest.Payload()
if err != nil {
return manifestlist.ManifestDescriptor{}, err
}
manifest := manifestlist.ManifestDescriptor{
Platform: imageManifest.Platform,
}
manifest.Descriptor.Digest = imageManifest.Digest
manifest.Size = int64(len(raw))
manifest.MediaType = mediaType
if err = manifest.Descriptor.Digest.Validate(); err != nil {
return manifestlist.ManifestDescriptor{}, errors.Wrapf(err,
"digest parse of image %q failed", imageManifest.Ref)
}
return manifest, nil
}
func buildBlobRequestList(imageManifest types.ImageManifest, repoName reference.Named) ([]manifestBlob, error) {
var blobReqs []manifestBlob
for _, blobDigest := range imageManifest.Blobs() {
canonical, err := reference.WithDigest(repoName, blobDigest)
if err != nil {
return nil, err
}
blobReqs = append(blobReqs, manifestBlob{canonical: canonical, os: imageManifest.Platform.OS})
}
return blobReqs, nil
}
// nolint: interfacer
func buildPutManifestRequest(imageManifest types.ImageManifest, targetRef reference.Named) (mountRequest, error) {
refWithoutTag, err := reference.WithName(targetRef.Name())
if err != nil {
return mountRequest{}, err
}
mountRef, err := reference.WithDigest(refWithoutTag, imageManifest.Digest)
if err != nil {
return mountRequest{}, err
}
// This indentation has to be added to ensure sha parity with the registry
v2ManifestBytes, err := json.MarshalIndent(imageManifest.SchemaV2Manifest, "", " ")
if err != nil {
return mountRequest{}, err
}
// indent only the DeserializedManifest portion of this, in order to maintain parity with the registry
// and not alter the sha
var v2Manifest schema2.DeserializedManifest
if err = v2Manifest.UnmarshalJSON(v2ManifestBytes); err != nil {
return mountRequest{}, err
}
imageManifest.SchemaV2Manifest = &v2Manifest
return mountRequest{ref: mountRef, manifest: imageManifest}, err
}
func pushList(ctx context.Context, dockerCli command.Cli, req pushRequest) error {
rclient := dockerCli.RegistryClient(req.insecure)
if err := mountBlobs(ctx, rclient, req.targetRef, req.manifestBlobs); err != nil {
return err
}
if err := pushReferences(ctx, dockerCli.Out(), rclient, req.mountRequests); err != nil {
return err
}
dgst, err := rclient.PutManifest(ctx, req.targetRef, req.list)
if err != nil {
return err
}
fmt.Fprintln(dockerCli.Out(), dgst.String())
return nil
}
func pushReferences(ctx context.Context, out io.Writer, client registryclient.RegistryClient, mounts []mountRequest) error {
for _, mount := range mounts {
newDigest, err := client.PutManifest(ctx, mount.ref, mount.manifest)
if err != nil {
return err
}
fmt.Fprintf(out, "Pushed ref %s with digest: %s\n", mount.ref, newDigest)
}
return nil
}
func mountBlobs(ctx context.Context, client registryclient.RegistryClient, ref reference.Named, blobs []manifestBlob) error {
for _, blob := range blobs {
err := client.MountBlob(ctx, blob.canonical, ref)
switch err.(type) {
case nil:
case registryclient.ErrBlobCreated:
if blob.os != "windows" {
return fmt.Errorf("error mounting %s to %s", blob.canonical, ref)
}
default:
return err
}
}
return nil
}

View File

@ -1,73 +0,0 @@
package manifest
import (
"io/ioutil"
"testing"
manifesttypes "github.com/docker/cli/cli/manifest/types"
"github.com/docker/cli/internal/test"
"github.com/docker/cli/internal/test/testutil"
"github.com/docker/distribution/reference"
"github.com/pkg/errors"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
func newFakeRegistryClient(t *testing.T) *fakeRegistryClient {
require.NoError(t, nil)
return &fakeRegistryClient{
getManifestFunc: func(_ context.Context, _ reference.Named) (manifesttypes.ImageManifest, error) {
return manifesttypes.ImageManifest{}, errors.New("")
},
getManifestListFunc: func(_ context.Context, _ reference.Named) ([]manifesttypes.ImageManifest, error) {
return nil, errors.Errorf("")
},
}
}
func TestManifestPushErrors(t *testing.T) {
testCases := []struct {
args []string
expectedError string
}{
{
args: []string{"one-arg", "extra-arg"},
expectedError: "requires exactly 1 argument",
},
{
args: []string{"th!si'sa/fa!ke/li$t/-name"},
expectedError: "invalid reference format",
},
}
for _, tc := range testCases {
cli := test.NewFakeCli(nil)
cmd := newPushListCommand(cli)
cmd.SetArgs(tc.args)
cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), tc.expectedError)
}
}
// store a one-image manifest list and puah it
func TestManifestPush(t *testing.T) {
store, sCleanup := newTempManifestStore(t)
defer sCleanup()
registry := newFakeRegistryClient(t)
cli := test.NewFakeCli(nil)
cli.SetManifestStore(store)
cli.SetRegistryClient(registry)
namedRef := ref(t, "alpine:3.0")
imageManifest := fullImageManifest(t, namedRef)
err := store.Save(ref(t, "list:v1"), namedRef, imageManifest)
require.NoError(t, err)
cmd := newPushListCommand(cli)
cmd.SetArgs([]string{"example.com/list:v1"})
err = cmd.Execute()
require.NoError(t, err)
}

View File

@ -1,28 +0,0 @@
{
"Ref": "example.com/alpine:3.0",
"Digest": "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d62abcd",
"SchemaV2Manifest": {
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1520,
"digest": "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d624560"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 1990402,
"digest": "sha256:88286f41530e93dffd4b964e1db22ce4939fffa4a4c665dab8591fbab03d4926"
}
]
},
"Platform": {
"architecture": "arm",
"os": "freebsd",
"os.features": [
"feature1"
],
"variant": "v7"
}
}

View File

@ -1,24 +0,0 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.list.v2+json",
"manifests": [
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 428,
"digest": "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d62abcd",
"platform": {
"architecture": "amd64",
"os": "linux"
}
},
{
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"size": 428,
"digest": "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d62abcd",
"platform": {
"architecture": "amd64",
"os": "linux"
}
}
]
}

View File

@ -1,16 +0,0 @@
{
"schemaVersion": 2,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"config": {
"mediaType": "application/vnd.docker.container.image.v1+json",
"size": 1520,
"digest": "sha256:7328f6f8b41890597575cbaadc884e7386ae0acc53b747401ebce5cf0d624560"
},
"layers": [
{
"mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
"size": 1990402,
"digest": "sha256:88286f41530e93dffd4b964e1db22ce4939fffa4a4c665dab8591fbab03d4926"
}
]
}

View File

@ -1,79 +0,0 @@
package manifest
import (
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/manifest/store"
"github.com/docker/cli/cli/manifest/types"
"github.com/docker/distribution/reference"
"golang.org/x/net/context"
)
type osArch struct {
os string
arch string
}
// Remove any unsupported os/arch combo
// list of valid os/arch values (see "Optional Environment Variables" section
// of https://golang.org/doc/install/source
// Added linux/s390x as we know System z support already exists
var validOSArches = map[osArch]bool{
{os: "darwin", arch: "386"}: true,
{os: "darwin", arch: "amd64"}: true,
{os: "darwin", arch: "arm"}: true,
{os: "darwin", arch: "arm64"}: true,
{os: "dragonfly", arch: "amd64"}: true,
{os: "freebsd", arch: "386"}: true,
{os: "freebsd", arch: "amd64"}: true,
{os: "freebsd", arch: "arm"}: true,
{os: "linux", arch: "386"}: true,
{os: "linux", arch: "amd64"}: true,
{os: "linux", arch: "arm"}: true,
{os: "linux", arch: "arm64"}: true,
{os: "linux", arch: "ppc64le"}: true,
{os: "linux", arch: "mips64"}: true,
{os: "linux", arch: "mips64le"}: true,
{os: "linux", arch: "s390x"}: true,
{os: "netbsd", arch: "386"}: true,
{os: "netbsd", arch: "amd64"}: true,
{os: "netbsd", arch: "arm"}: true,
{os: "openbsd", arch: "386"}: true,
{os: "openbsd", arch: "amd64"}: true,
{os: "openbsd", arch: "arm"}: true,
{os: "plan9", arch: "386"}: true,
{os: "plan9", arch: "amd64"}: true,
{os: "solaris", arch: "amd64"}: true,
{os: "windows", arch: "386"}: true,
{os: "windows", arch: "amd64"}: true,
}
func isValidOSArch(os string, arch string) bool {
// check for existence of this combo
_, ok := validOSArches[osArch{os, arch}]
return ok
}
func normalizeReference(ref string) (reference.Named, error) {
namedRef, err := reference.ParseNormalizedNamed(ref)
if err != nil {
return nil, err
}
if _, isDigested := namedRef.(reference.Canonical); !isDigested {
return reference.TagNameOnly(namedRef), nil
}
return namedRef, nil
}
// getManifest from the local store, and fallback to the remote registry if it
// doesn't exist locally
func getManifest(ctx context.Context, dockerCli command.Cli, listRef, namedRef reference.Named, insecure bool) (types.ImageManifest, error) {
data, err := dockerCli.ManifestStore().Get(listRef, namedRef)
switch {
case store.IsNotFound(err):
return dockerCli.RegistryClient(insecure).GetManifest(ctx, namedRef)
case err != nil:
return types.ImageManifest{}, err
default:
return data, nil
}
}

View File

@ -14,14 +14,11 @@ import (
// NewNodeCommand returns a cobra command for `node` subcommands
func NewNodeCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "node",
Short: "Manage Swarm nodes",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{
"version": "1.24",
"swarm": "",
},
Use: "node",
Short: "Manage Swarm nodes",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"version": "1.24"},
}
cmd.AddCommand(
newDemoteCommand(dockerCli),

View File

@ -1,59 +0,0 @@
package command
import (
"fmt"
"os"
)
// Orchestrator type acts as an enum describing supported orchestrators.
type Orchestrator string
const (
// OrchestratorKubernetes orchestrator
OrchestratorKubernetes = Orchestrator("kubernetes")
// OrchestratorSwarm orchestrator
OrchestratorSwarm = Orchestrator("swarm")
orchestratorUnset = Orchestrator("unset")
defaultOrchestrator = OrchestratorSwarm
envVarDockerOrchestrator = "DOCKER_ORCHESTRATOR"
)
func normalize(flag string) Orchestrator {
switch flag {
case "kubernetes":
return OrchestratorKubernetes
case "swarm":
return OrchestratorSwarm
default:
return orchestratorUnset
}
}
// GetOrchestrator checks DOCKER_ORCHESTRATOR environment variable and configuration file
// orchestrator value and returns user defined Orchestrator.
func GetOrchestrator(isExperimental bool, flagValue, value string) Orchestrator {
// Non experimental CLI has kubernetes disabled
if !isExperimental {
return defaultOrchestrator
}
// Check flag
if o := normalize(flagValue); o != orchestratorUnset {
return o
}
// Check environment variable
env := os.Getenv(envVarDockerOrchestrator)
if o := normalize(env); o != orchestratorUnset {
return o
}
// Check specified orchestrator
if o := normalize(value); o != orchestratorUnset {
return o
}
if value != "" {
fmt.Fprintf(os.Stderr, "Specified orchestrator %q is invalid. Please use either kubernetes or swarm\n", value)
}
// Nothing set, use default orchestrator
return defaultOrchestrator
}

View File

@ -10,13 +10,14 @@ import (
"runtime"
"strings"
"golang.org/x/net/context"
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/pkg/term"
"github.com/docker/docker/registry"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
// ElectAuthServer returns the default registry to use (by asking the daemon)

View File

@ -10,14 +10,11 @@ import (
// NewSecretCommand returns a cobra command for `secret` subcommands
func NewSecretCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "secret",
Short: "Manage Docker secrets",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{
"version": "1.25",
"swarm": "",
},
Use: "secret",
Short: "Manage Docker secrets",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"version": "1.25"},
}
cmd.AddCommand(
newSecretListCommand(dockerCli),

View File

@ -16,7 +16,6 @@ type fakeClient struct {
serviceListFunc func(context.Context, types.ServiceListOptions) ([]swarm.Service, error)
taskListFunc func(context.Context, types.TaskListOptions) ([]swarm.Task, error)
infoFunc func(ctx context.Context) (types.Info, error)
networkInspectFunc func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error)
}
func (f *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
@ -61,13 +60,6 @@ func (f *fakeClient) Info(ctx context.Context) (types.Info, error) {
return f.infoFunc(ctx)
}
func (f *fakeClient) NetworkInspect(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
if f.networkInspectFunc != nil {
return f.networkInspectFunc(ctx, networkID, options)
}
return types.NetworkResource{}, nil
}
func newService(id string, name string) swarm.Service {
return swarm.Service{
ID: id,

View File

@ -10,14 +10,11 @@ import (
// NewServiceCommand returns a cobra command for `service` subcommands
func NewServiceCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "service",
Short: "Manage services",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{
"version": "1.24",
"swarm": "",
},
Use: "service",
Short: "Manage services",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"version": "1.24"},
}
cmd.AddCommand(
newCreateCommand(dockerCli),

View File

@ -353,21 +353,22 @@ func (c *credentialSpecOpt) Value() *swarm.CredentialSpec {
return c.value
}
func resolveNetworkID(ctx context.Context, apiClient client.NetworkAPIClient, networkIDOrName string) (string, error) {
nw, err := apiClient.NetworkInspect(ctx, networkIDOrName, types.NetworkInspectOptions{Scope: "swarm"})
return nw.ID, err
}
func convertNetworks(networks opts.NetworkOpt) []swarm.NetworkAttachmentConfig {
func convertNetworks(ctx context.Context, apiClient client.NetworkAPIClient, networks opts.NetworkOpt) ([]swarm.NetworkAttachmentConfig, error) {
var netAttach []swarm.NetworkAttachmentConfig
for _, net := range networks.Value() {
networkIDOrName := net.Target
_, err := apiClient.NetworkInspect(ctx, networkIDOrName, types.NetworkInspectOptions{Scope: "swarm"})
if err != nil {
return nil, err
}
netAttach = append(netAttach, swarm.NetworkAttachmentConfig{
Target: net.Target,
Aliases: net.Aliases,
DriverOpts: net.DriverOpts,
})
}
return netAttach
sort.Sort(byNetworkTarget(netAttach))
return netAttach, nil
}
type endpointOptions struct {
@ -589,15 +590,10 @@ func (options *serviceOptions) ToService(ctx context.Context, apiClient client.N
return service, err
}
networks := convertNetworks(options.networks)
for i, net := range networks {
nwID, err := resolveNetworkID(ctx, apiClient, net.Target)
if err != nil {
return service, err
}
networks[i].Target = nwID
networks, err := convertNetworks(ctx, apiClient, options.networks)
if err != nil {
return service, err
}
sort.Sort(byNetworkTarget(networks))
resources, err := options.resources.ToResourceRequirements()
if err != nil {

View File

@ -1,17 +1,12 @@
package service
import (
"context"
"fmt"
"testing"
"time"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/swarm"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMemBytesString(t *testing.T) {
@ -128,37 +123,3 @@ func TestResourceOptionsToResourceRequirements(t *testing.T) {
}
}
func TestToServiceNetwork(t *testing.T) {
nws := []types.NetworkResource{
{Name: "aaa-network", ID: "id555"},
{Name: "mmm-network", ID: "id999"},
{Name: "zzz-network", ID: "id111"},
}
client := &fakeClient{
networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
for _, network := range nws {
if network.ID == networkID || network.Name == networkID {
return network, nil
}
}
return types.NetworkResource{}, fmt.Errorf("network not found: %s", networkID)
},
}
nwo := opts.NetworkOpt{}
nwo.Set("zzz-network")
nwo.Set("mmm-network")
nwo.Set("aaa-network")
o := newServiceOptions()
o.mode = "replicated"
o.networks = nwo
ctx := context.Background()
flags := newCreateCommand(nil).Flags()
service, err := o.ToService(ctx, client, flags)
require.NoError(t, err)
assert.Equal(t, []swarm.NetworkAttachmentConfig{{Target: "id111"}, {Target: "id555"}, {Target: "id999"}}, service.TaskTemplate.Networks)
}

View File

@ -1119,16 +1119,14 @@ func updateNetworks(ctx context.Context, apiClient client.NetworkAPIClient, flag
if flags.Changed(flagNetworkAdd) {
values := flags.Lookup(flagNetworkAdd).Value.(*opts.NetworkOpt)
networks := convertNetworks(*values)
networks, err := convertNetworks(ctx, apiClient, *values)
if err != nil {
return err
}
for _, network := range networks {
nwID, err := resolveNetworkID(ctx, apiClient, network.Target)
if err != nil {
return err
}
if _, exists := existingNetworks[nwID]; exists {
if _, exists := existingNetworks[network.Target]; exists {
return errors.Errorf("service is already attached to network %s", network.Target)
}
network.Target = nwID
newNetworks = append(newNetworks, network)
existingNetworks[network.Target] = struct{}{}
}

View File

@ -1,7 +1,6 @@
package service
import (
"fmt"
"reflect"
"sort"
"testing"
@ -587,69 +586,3 @@ func TestRemoveGenericResources(t *testing.T) {
assert.NoError(t, removeGenericResources(flags, task))
assert.Len(t, task.Resources.Reservations.GenericResources, 1)
}
func TestUpdateNetworks(t *testing.T) {
ctx := context.Background()
nws := []types.NetworkResource{
{Name: "aaa-network", ID: "id555"},
{Name: "mmm-network", ID: "id999"},
{Name: "zzz-network", ID: "id111"},
}
client := &fakeClient{
networkInspectFunc: func(ctx context.Context, networkID string, options types.NetworkInspectOptions) (types.NetworkResource, error) {
for _, network := range nws {
if network.ID == networkID || network.Name == networkID {
return network, nil
}
}
return types.NetworkResource{}, fmt.Errorf("network not found: %s", networkID)
},
}
svc := swarm.ServiceSpec{
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{},
Networks: []swarm.NetworkAttachmentConfig{
{Target: "id999"},
},
},
}
flags := newUpdateCommand(nil).Flags()
err := flags.Set(flagNetworkAdd, "aaa-network")
require.NoError(t, err)
err = updateService(ctx, client, flags, &svc)
require.NoError(t, err)
assert.Equal(t, []swarm.NetworkAttachmentConfig{{Target: "id555"}, {Target: "id999"}}, svc.TaskTemplate.Networks)
flags = newUpdateCommand(nil).Flags()
err = flags.Set(flagNetworkAdd, "aaa-network")
require.NoError(t, err)
err = updateService(ctx, client, flags, &svc)
assert.EqualError(t, err, "service is already attached to network aaa-network")
assert.Equal(t, []swarm.NetworkAttachmentConfig{{Target: "id555"}, {Target: "id999"}}, svc.TaskTemplate.Networks)
flags = newUpdateCommand(nil).Flags()
err = flags.Set(flagNetworkAdd, "id555")
require.NoError(t, err)
err = updateService(ctx, client, flags, &svc)
assert.EqualError(t, err, "service is already attached to network id555")
assert.Equal(t, []swarm.NetworkAttachmentConfig{{Target: "id555"}, {Target: "id999"}}, svc.TaskTemplate.Networks)
flags = newUpdateCommand(nil).Flags()
err = flags.Set(flagNetworkRemove, "id999")
require.NoError(t, err)
err = updateService(ctx, client, flags, &svc)
assert.NoError(t, err)
assert.Equal(t, []swarm.NetworkAttachmentConfig{{Target: "id555"}}, svc.TaskTemplate.Networks)
flags = newUpdateCommand(nil).Flags()
err = flags.Set(flagNetworkAdd, "mmm-network")
require.NoError(t, err)
err = flags.Set(flagNetworkRemove, "aaa-network")
require.NoError(t, err)
err = updateService(ctx, client, flags, &svc)
assert.NoError(t, err)
assert.Equal(t, []swarm.NetworkAttachmentConfig{{Target: "id999"}}, svc.TaskTemplate.Networks)
}

View File

@ -9,30 +9,19 @@ import (
// NewStackCommand returns a cobra command for `stack` subcommands
func NewStackCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "stack",
Short: "Manage Docker stacks",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{
"kubernetes": "",
"swarm": "",
"version": "1.25",
},
Use: "stack",
Short: "Manage Docker stacks",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"version": "1.25"},
}
cmd.AddCommand(
newDeployCommand(dockerCli),
newListCommand(dockerCli),
newPsCommand(dockerCli),
newRemoveCommand(dockerCli),
newServicesCommand(dockerCli),
newPsCommand(dockerCli),
)
flags := cmd.PersistentFlags()
flags.String("namespace", "default", "Kubernetes namespace to use")
flags.SetAnnotation("namespace", "kubernetes", nil)
flags.SetAnnotation("namespace", "experimentalCLI", nil)
flags.String("kubeconfig", "", "Kubernetes config file")
flags.SetAnnotation("kubeconfig", "kubernetes", nil)
flags.SetAnnotation("kubeconfig", "experimentalCLI", nil)
return cmd
}
@ -41,10 +30,6 @@ func NewTopLevelDeployCommand(dockerCli command.Cli) *cobra.Command {
cmd := newDeployCommand(dockerCli)
// Remove the aliases at the top level
cmd.Aliases = []string{}
cmd.Annotations = map[string]string{
"experimental": "",
"swarm": "",
"version": "1.25",
}
cmd.Annotations = map[string]string{"experimental": "", "version": "1.25"}
return cmd
}

View File

@ -1,4 +1,4 @@
package swarm
package stack
import (
"github.com/docker/cli/cli/compose/convert"

View File

@ -1,16 +1,36 @@
package stack
import (
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/kubernetes"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/command/stack/swarm"
"github.com/docker/cli/cli/compose/convert"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
const (
defaultNetworkDriver = "overlay"
resolveImageAlways = "always"
resolveImageChanged = "changed"
resolveImageNever = "never"
)
type deployOptions struct {
bundlefile string
composefile string
namespace string
resolveImage string
sendRegistryAuth bool
prune bool
}
func newDeployCommand(dockerCli command.Cli) *cobra.Command {
var opts options.Deploy
var opts deployOptions
cmd := &cobra.Command{
Use: "deploy [OPTIONS] STACK",
@ -18,32 +38,85 @@ func newDeployCommand(dockerCli command.Cli) *cobra.Command {
Short: "Deploy a new stack or update an existing stack",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespace = args[0]
if dockerCli.ClientInfo().HasKubernetes() {
kli, err := kubernetes.WrapCli(dockerCli, cmd)
if err != nil {
return err
}
return kubernetes.RunDeploy(kli, opts)
}
return swarm.RunDeploy(dockerCli, opts)
opts.namespace = args[0]
return runDeploy(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.StringVar(&opts.Bundlefile, "bundle-file", "", "Path to a Distributed Application Bundle file")
flags.SetAnnotation("bundle-file", "experimental", nil)
flags.SetAnnotation("bundle-file", "swarm", nil)
flags.StringVarP(&opts.Composefile, "compose-file", "c", "", "Path to a Compose file")
flags.SetAnnotation("compose-file", "version", []string{"1.25"})
flags.BoolVar(&opts.SendRegistryAuth, "with-registry-auth", false, "Send registry authentication details to Swarm agents")
flags.SetAnnotation("with-registry-auth", "swarm", nil)
flags.BoolVar(&opts.Prune, "prune", false, "Prune services that are no longer referenced")
addBundlefileFlag(&opts.bundlefile, flags)
addComposefileFlag(&opts.composefile, flags)
addRegistryAuthFlag(&opts.sendRegistryAuth, flags)
flags.BoolVar(&opts.prune, "prune", false, "Prune services that are no longer referenced")
flags.SetAnnotation("prune", "version", []string{"1.27"})
flags.SetAnnotation("prune", "swarm", nil)
flags.StringVar(&opts.ResolveImage, "resolve-image", swarm.ResolveImageAlways,
`Query the registry to resolve image digest and supported platforms ("`+swarm.ResolveImageAlways+`"|"`+swarm.ResolveImageChanged+`"|"`+swarm.ResolveImageNever+`")`)
flags.StringVar(&opts.resolveImage, "resolve-image", resolveImageAlways,
`Query the registry to resolve image digest and supported platforms ("`+resolveImageAlways+`"|"`+resolveImageChanged+`"|"`+resolveImageNever+`")`)
flags.SetAnnotation("resolve-image", "version", []string{"1.30"})
flags.SetAnnotation("resolve-image", "swarm", nil)
return cmd
}
func runDeploy(dockerCli command.Cli, opts deployOptions) error {
ctx := context.Background()
if err := validateResolveImageFlag(dockerCli, &opts); err != nil {
return err
}
switch {
case opts.bundlefile == "" && opts.composefile == "":
return errors.Errorf("Please specify either a bundle file (with --bundle-file) or a Compose file (with --compose-file).")
case opts.bundlefile != "" && opts.composefile != "":
return errors.Errorf("You cannot specify both a bundle file and a Compose file.")
case opts.bundlefile != "":
return deployBundle(ctx, dockerCli, opts)
default:
return deployCompose(ctx, dockerCli, opts)
}
}
// validateResolveImageFlag validates the opts.resolveImage command line option
// and also turns image resolution off if the version is older than 1.30
func validateResolveImageFlag(dockerCli command.Cli, opts *deployOptions) error {
if opts.resolveImage != resolveImageAlways && opts.resolveImage != resolveImageChanged && opts.resolveImage != resolveImageNever {
return errors.Errorf("Invalid option %s for flag --resolve-image", opts.resolveImage)
}
// client side image resolution should not be done when the supported
// server version is older than 1.30
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") {
opts.resolveImage = resolveImageNever
}
return nil
}
// checkDaemonIsSwarmManager does an Info API call to verify that the daemon is
// a swarm manager. This is necessary because we must create networks before we
// create services, but the API call for creating a network does not return a
// proper status code when it can't create a network in the "global" scope.
func checkDaemonIsSwarmManager(ctx context.Context, dockerCli command.Cli) error {
info, err := dockerCli.Client().Info(ctx)
if err != nil {
return err
}
if !info.Swarm.ControlAvailable {
return errors.New("this node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again")
}
return nil
}
// pruneServices removes services that are no longer referenced in the source
func pruneServices(ctx context.Context, dockerCli command.Cli, namespace convert.Namespace, services map[string]struct{}) {
client := dockerCli.Client()
oldServices, err := getServices(ctx, client, namespace.Name())
if err != nil {
fmt.Fprintf(dockerCli.Err(), "Failed to list services: %s", err)
}
pruneServices := []swarm.Service{}
for _, service := range oldServices {
if _, exists := services[namespace.Descope(service.Spec.Name)]; !exists {
pruneServices = append(pruneServices, service)
}
}
removeServices(ctx, dockerCli, pruneServices)
}

View File

@ -1,23 +1,16 @@
package swarm
package stack
import (
"fmt"
"io"
"os"
"golang.org/x/net/context"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/bundlefile"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/compose/convert"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/pkg/errors"
)
func deployBundle(ctx context.Context, dockerCli command.Cli, opts options.Deploy) error {
bundle, err := loadBundlefile(dockerCli.Err(), opts.Namespace, opts.Bundlefile)
func deployBundle(ctx context.Context, dockerCli command.Cli, opts deployOptions) error {
bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
if err != nil {
return err
}
@ -26,9 +19,9 @@ func deployBundle(ctx context.Context, dockerCli command.Cli, opts options.Deplo
return err
}
namespace := convert.NewNamespace(opts.Namespace)
namespace := convert.NewNamespace(opts.namespace)
if opts.Prune {
if opts.prune {
services := map[string]struct{}{}
for service := range bundle.Services {
services[service] = struct{}{}
@ -94,31 +87,5 @@ func deployBundle(ctx context.Context, dockerCli command.Cli, opts options.Deplo
if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil {
return err
}
return deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
}
func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefile.Bundlefile, error) {
defaultPath := fmt.Sprintf("%s.dab", namespace)
if path == "" {
path = defaultPath
}
if _, err := os.Stat(path); err != nil {
return nil, errors.Errorf(
"Bundle %s not found. Specify the path with --file",
path)
}
fmt.Fprintf(stderr, "Loading bundle from %s\n", path)
reader, err := os.Open(path)
if err != nil {
return nil, err
}
defer reader.Close()
bundle, err := bundlefile.LoadFile(reader)
if err != nil {
return nil, errors.Errorf("Error reading %s: %v\n", path, err)
}
return bundle, err
return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth, opts.resolveImage)
}

View File

@ -1,4 +1,4 @@
package swarm
package stack
import (
"fmt"
@ -10,7 +10,6 @@ import (
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/compose/convert"
"github.com/docker/cli/cli/compose/loader"
composetypes "github.com/docker/cli/cli/compose/types"
@ -23,8 +22,8 @@ import (
"golang.org/x/net/context"
)
func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Deploy) error {
configDetails, err := getConfigDetails(opts.Composefile, dockerCli.In())
func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOptions) error {
configDetails, err := getConfigDetails(opts.composefile, dockerCli.In())
if err != nil {
return err
}
@ -55,9 +54,9 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Depl
return err
}
namespace := convert.NewNamespace(opts.Namespace)
namespace := convert.NewNamespace(opts.namespace)
if opts.Prune {
if opts.prune {
services := map[string]struct{}{}
for _, service := range config.Services {
services[service.Name] = struct{}{}
@ -94,7 +93,7 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Depl
if err != nil {
return err
}
return deployServices(ctx, dockerCli, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
return deployServices(ctx, dockerCli, services, namespace, opts.sendRegistryAuth, opts.resolveImage)
}
func getServicesDeclaredNetworks(serviceConfigs []composetypes.ServiceConfig) map[string]struct{} {
@ -249,13 +248,13 @@ func createConfigs(
case err == nil:
// config already exists, then we update that
if err := client.ConfigUpdate(ctx, config.ID, config.Meta.Version, configSpec); err != nil {
return errors.Wrapf(err, "failed to update config %s", configSpec.Name)
errors.Wrapf(err, "failed to update config %s", configSpec.Name)
}
case apiclient.IsErrNotFound(err):
// config does not exist, then we create a new one.
fmt.Fprintf(dockerCli.Out(), "Creating config %s\n", configSpec.Name)
if _, err := client.ConfigCreate(ctx, configSpec); err != nil {
return errors.Wrapf(err, "failed to create config %s", configSpec.Name)
errors.Wrapf(err, "failed to create config %s", configSpec.Name)
}
default:
return err
@ -340,7 +339,7 @@ func deployServices(
updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
switch {
case resolveImage == ResolveImageAlways || (resolveImage == ResolveImageChanged && image != service.Spec.Labels[convert.LabelImage]):
case resolveImage == resolveImageAlways || (resolveImage == resolveImageChanged && image != service.Spec.Labels[convert.LabelImage]):
// image should be updated by the server using QueryRegistry
updateOpts.QueryRegistry = true
case image == service.Spec.Labels[convert.LabelImage]:
@ -370,7 +369,7 @@ func deployServices(
createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
// query registry if flag disabling it was not set
if resolveImage == ResolveImageAlways || resolveImage == ResolveImageChanged {
if resolveImage == resolveImageAlways || resolveImage == resolveImageChanged {
createOpts.QueryRegistry = true
}

View File

@ -1,4 +1,4 @@
package swarm
package stack
import (
"testing"
@ -92,7 +92,7 @@ func TestServiceUpdateResolveImageChanged(t *testing.T) {
},
},
}
err := deployServices(ctx, client, spec, namespace, false, ResolveImageChanged)
err := deployServices(ctx, client, spec, namespace, false, resolveImageChanged)
assert.NoError(t, err)
assert.Equal(t, testcase.expectedQueryRegistry, receivedOptions.QueryRegistry)
assert.Equal(t, testcase.expectedImage, receivedService.TaskTemplate.ContainerSpec.Image)

View File

@ -1,32 +0,0 @@
package kubernetes
import (
"fmt"
apiv1beta1 "github.com/docker/cli/kubernetes/compose/v1beta1"
log "github.com/sirupsen/logrus"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
// APIPresent checks that an API is installed.
func APIPresent(config *rest.Config) error {
log.Debugf("check API present at %s", config.Host)
clients, err := kubernetes.NewForConfig(config)
if err != nil {
return err
}
groups, err := clients.Discovery().ServerGroups()
if err != nil {
return err
}
for _, group := range groups.Groups {
if group.Name == apiv1beta1.SchemeGroupVersion.Group {
return nil
}
}
return fmt.Errorf("could not find %s api. Install it on your cluster first", apiv1beta1.SchemeGroupVersion.Group)
}

View File

@ -1,76 +0,0 @@
package kubernetes
import (
"fmt"
"os"
"path/filepath"
"github.com/docker/cli/cli/command"
composev1beta1 "github.com/docker/cli/kubernetes/client/clientset_generated/clientset/typed/compose/v1beta1"
"github.com/docker/docker/pkg/homedir"
"github.com/spf13/cobra"
restclient "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
// KubeCli holds kubernetes specifics (client, namespace) with the command.Cli
type KubeCli struct {
command.Cli
kubeConfig *restclient.Config
kubeNamespace string
}
// WrapCli wraps command.Cli with kubernetes specifics
func WrapCli(dockerCli command.Cli, cmd *cobra.Command) (*KubeCli, error) {
var err error
cli := &KubeCli{
Cli: dockerCli,
kubeNamespace: "default",
}
if cmd.Flags().Changed("namespace") {
cli.kubeNamespace, err = cmd.Flags().GetString("namespace")
if err != nil {
return nil, err
}
}
kubeConfig := ""
if cmd.Flags().Changed("kubeconfig") {
kubeConfig, err = cmd.Flags().GetString("kubeconfig")
if err != nil {
return nil, err
}
}
if kubeConfig == "" {
if config := os.Getenv("KUBECONFIG"); config != "" {
kubeConfig = config
} else {
kubeConfig = filepath.Join(homedir.Get(), ".kube/config")
}
}
config, err := clientcmd.BuildConfigFromFlags("", kubeConfig)
if err != nil {
return nil, fmt.Errorf("Failed to load kubernetes configuration file '%s'", kubeConfig)
}
cli.kubeConfig = config
return cli, nil
}
func (c *KubeCli) composeClient() (*Factory, error) {
return NewFactory(c.kubeNamespace, c.kubeConfig)
}
func (c *KubeCli) stacks() (composev1beta1.StackInterface, error) {
err := APIPresent(c.kubeConfig)
if err != nil {
return nil, err
}
clientSet, err := composev1beta1.NewForConfig(c.kubeConfig)
if err != nil {
return nil, err
}
return clientSet.Stacks(c.kubeNamespace), nil
}

View File

@ -1,71 +0,0 @@
package kubernetes
import (
appsv1beta2 "k8s.io/client-go/kubernetes/typed/apps/v1beta2"
typesappsv1beta2 "k8s.io/client-go/kubernetes/typed/apps/v1beta2"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
restclient "k8s.io/client-go/rest"
)
// Factory is the kubernetes client factory
type Factory struct {
namespace string
config *restclient.Config
coreClientSet *corev1.CoreV1Client
appsClientSet *appsv1beta2.AppsV1beta2Client
}
// NewFactory creates a kubernetes client factory
func NewFactory(namespace string, config *restclient.Config) (*Factory, error) {
coreClientSet, err := corev1.NewForConfig(config)
if err != nil {
return nil, err
}
appsClientSet, err := appsv1beta2.NewForConfig(config)
if err != nil {
return nil, err
}
return &Factory{
namespace: namespace,
config: config,
coreClientSet: coreClientSet,
appsClientSet: appsClientSet,
}, nil
}
// ConfigMaps returns a client for kubernetes's config maps
func (s *Factory) ConfigMaps() corev1.ConfigMapInterface {
return s.coreClientSet.ConfigMaps(s.namespace)
}
// Secrets returns a client for kubernetes's secrets
func (s *Factory) Secrets() corev1.SecretInterface {
return s.coreClientSet.Secrets(s.namespace)
}
// Services returns a client for kubernetes's secrets
func (s *Factory) Services() corev1.ServiceInterface {
return s.coreClientSet.Services(s.namespace)
}
// Pods returns a client for kubernetes's pods
func (s *Factory) Pods() corev1.PodInterface {
return s.coreClientSet.Pods(s.namespace)
}
// Nodes returns a client for kubernetes's nodes
func (s *Factory) Nodes() corev1.NodeInterface {
return s.coreClientSet.Nodes()
}
// ReplicationControllers returns a client for kubernetes replication controllers
func (s *Factory) ReplicationControllers() corev1.ReplicationControllerInterface {
return s.coreClientSet.ReplicationControllers(s.namespace)
}
// ReplicaSets return a client for kubernetes replace sets
func (s *Factory) ReplicaSets() typesappsv1beta2.ReplicaSetInterface {
return s.appsClientSet.ReplicaSets(s.namespace)
}

View File

@ -1,54 +0,0 @@
package kubernetes
import (
"fmt"
"sort"
composetypes "github.com/docker/cli/cli/compose/types"
apiv1beta1 "github.com/docker/cli/kubernetes/compose/v1beta1"
"github.com/docker/cli/kubernetes/labels"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
// IsColliding verify that services defined in the stack collides with already deployed services
func IsColliding(services corev1.ServiceInterface, stack *apiv1beta1.Stack, cfg *composetypes.Config) error {
stackObjects := getServices(cfg)
for _, srv := range stackObjects {
if err := verify(services, stack.Name, srv); err != nil {
return err
}
}
return nil
}
// verify checks wether the service is already present in kubernetes.
// If we find the service by name but it doesn't have our label or it has a different value
// than the stack name for the label, we fail (i.e. it will collide)
func verify(services corev1.ServiceInterface, stackName string, service string) error {
svc, err := services.Get(service, metav1.GetOptions{})
if err == nil {
if key, ok := svc.ObjectMeta.Labels[labels.ForStackName]; ok {
if key != stackName {
return fmt.Errorf("service %s already present in stack named %s", service, key)
}
return nil
}
return fmt.Errorf("service %s already present in the cluster", service)
}
return nil
}
func getServices(cfg *composetypes.Config) []string {
services := make([]string, len(cfg.Services))
for i := range cfg.Services {
services[i] = cfg.Services[i].Name
}
sort.Strings(services)
return services
}

View File

@ -1,174 +0,0 @@
package kubernetes
import (
"fmt"
"time"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/kubernetes/labels"
"github.com/docker/docker/api/types/swarm"
appsv1beta2 "k8s.io/api/apps/v1beta2"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
// Pod conversion
func podToTask(pod apiv1.Pod) swarm.Task {
var startTime time.Time
if pod.Status.StartTime != nil {
startTime = (*pod.Status.StartTime).Time
}
task := swarm.Task{
ID: string(pod.UID),
NodeID: pod.Spec.NodeName,
Spec: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{
Image: getContainerImage(pod.Spec.Containers),
},
},
DesiredState: podPhaseToState(pod.Status.Phase),
Status: swarm.TaskStatus{
State: podPhaseToState(pod.Status.Phase),
Timestamp: startTime,
PortStatus: swarm.PortStatus{
Ports: getPorts(pod.Spec.Containers),
},
},
}
return task
}
func podPhaseToState(phase apiv1.PodPhase) swarm.TaskState {
switch phase {
case apiv1.PodPending:
return swarm.TaskStatePending
case apiv1.PodRunning:
return swarm.TaskStateRunning
case apiv1.PodSucceeded:
return swarm.TaskStateComplete
case apiv1.PodFailed:
return swarm.TaskStateFailed
default:
return swarm.TaskState("unknown")
}
}
func toSwarmProtocol(protocol apiv1.Protocol) swarm.PortConfigProtocol {
switch protocol {
case apiv1.ProtocolTCP:
return swarm.PortConfigProtocolTCP
case apiv1.ProtocolUDP:
return swarm.PortConfigProtocolUDP
}
return swarm.PortConfigProtocol("unknown")
}
func fetchPods(namespace string, pods corev1.PodInterface) ([]apiv1.Pod, error) {
labelSelector := labels.SelectorForStack(namespace)
podsList, err := pods.List(metav1.ListOptions{LabelSelector: labelSelector})
if err != nil {
return nil, err
}
return podsList.Items, nil
}
func getContainerImage(containers []apiv1.Container) string {
if len(containers) == 0 {
return ""
}
return containers[0].Image
}
func getPorts(containers []apiv1.Container) []swarm.PortConfig {
if len(containers) == 0 || len(containers[0].Ports) == 0 {
return nil
}
ports := make([]swarm.PortConfig, len(containers[0].Ports))
for i, port := range containers[0].Ports {
ports[i] = swarm.PortConfig{
PublishedPort: uint32(port.HostPort),
TargetPort: uint32(port.ContainerPort),
Protocol: toSwarmProtocol(port.Protocol),
}
}
return ports
}
type tasksBySlot []swarm.Task
func (t tasksBySlot) Len() int {
return len(t)
}
func (t tasksBySlot) Swap(i, j int) {
t[i], t[j] = t[j], t[i]
}
func (t tasksBySlot) Less(i, j int) bool {
// Sort by slot.
if t[i].Slot != t[j].Slot {
return t[i].Slot < t[j].Slot
}
// If same slot, sort by most recent.
return t[j].Meta.CreatedAt.Before(t[i].CreatedAt)
}
// Replicas conversion
func replicasToServices(replicas *appsv1beta2.ReplicaSetList, services *apiv1.ServiceList) ([]swarm.Service, map[string]formatter.ServiceListInfo, error) {
result := make([]swarm.Service, len(replicas.Items))
infos := make(map[string]formatter.ServiceListInfo, len(replicas.Items))
for i, r := range replicas.Items {
service, ok := findService(services, r.Labels[labels.ForServiceName])
if !ok {
return nil, nil, fmt.Errorf("could not find service '%s'", r.Labels[labels.ForServiceName])
}
stack, ok := service.Labels[labels.ForStackName]
if ok {
stack += "_"
}
uid := string(service.UID)
s := swarm.Service{
ID: uid,
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{
Name: stack + service.Name,
},
TaskTemplate: swarm.TaskSpec{
ContainerSpec: &swarm.ContainerSpec{
Image: getContainerImage(r.Spec.Template.Spec.Containers),
},
},
},
}
if service.Spec.Type == apiv1.ServiceTypeLoadBalancer {
configs := make([]swarm.PortConfig, len(service.Spec.Ports))
for i, p := range service.Spec.Ports {
configs[i] = swarm.PortConfig{
PublishMode: swarm.PortConfigPublishModeIngress,
PublishedPort: uint32(p.Port),
TargetPort: uint32(p.TargetPort.IntValue()),
Protocol: toSwarmProtocol(p.Protocol),
}
}
s.Endpoint = swarm.Endpoint{Ports: configs}
}
result[i] = s
infos[uid] = formatter.ServiceListInfo{
Mode: "replicated",
Replicas: fmt.Sprintf("%d/%d", r.Status.AvailableReplicas, r.Status.Replicas),
}
}
return result, infos, nil
}
func findService(services *apiv1.ServiceList, name string) (apiv1.Service, bool) {
for _, s := range services.Items {
if s.Name == name {
return s, true
}
}
return apiv1.Service{}, false
}

View File

@ -1,41 +0,0 @@
package kubernetes
import (
"github.com/docker/cli/kubernetes/labels"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// toConfigMap converts a Compose Config to a Kube ConfigMap.
func toConfigMap(stackName, name, key string, content []byte) *apiv1.ConfigMap {
return &apiv1.ConfigMap{
TypeMeta: metav1.TypeMeta{
Kind: "ConfigMap",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: map[string]string{
labels.ForStackName: stackName,
},
},
Data: map[string]string{
key: string(content),
},
}
}
// toSecret converts a Compose Secret to a Kube Secret.
func toSecret(stackName, name, key string, content []byte) *apiv1.Secret {
return &apiv1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Labels: map[string]string{
labels.ForStackName: stackName,
},
},
Data: map[string][]byte{
key: content,
},
}
}

View File

@ -1,134 +0,0 @@
package kubernetes
import (
"fmt"
"io/ioutil"
"path"
"github.com/docker/cli/cli/command/stack/options"
composeTypes "github.com/docker/cli/cli/compose/types"
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
// RunDeploy is the kubernetes implementation of docker stack deploy
func RunDeploy(dockerCli *KubeCli, opts options.Deploy) error {
cmdOut := dockerCli.Out()
// Check arguments
if opts.Composefile == "" {
return errors.Errorf("Please specify a Compose file (with --compose-file).")
}
// Initialize clients
stacks, err := dockerCli.stacks()
if err != nil {
return err
}
composeClient, err := dockerCli.composeClient()
if err != nil {
return err
}
configMaps := composeClient.ConfigMaps()
secrets := composeClient.Secrets()
services := composeClient.Services()
pods := composeClient.Pods()
watcher := DeployWatcher{
Pods: pods,
}
// Parse the compose file
stack, cfg, err := LoadStack(opts.Namespace, opts.Composefile)
if err != nil {
return err
}
// FIXME(vdemeester) handle warnings server-side
if err = IsColliding(services, stack, cfg); err != nil {
return err
}
if err = createFileBasedConfigMaps(stack.Name, cfg.Configs, configMaps); err != nil {
return err
}
if err = createFileBasedSecrets(stack.Name, cfg.Secrets, secrets); err != nil {
return err
}
if in, err := stacks.Get(stack.Name, metav1.GetOptions{}); err == nil {
in.Spec = stack.Spec
if _, err = stacks.Update(in); err != nil {
return err
}
fmt.Printf("Stack %s was updated\n", stack.Name)
} else {
if _, err = stacks.Create(stack); err != nil {
return err
}
fmt.Fprintf(cmdOut, "Stack %s was created\n", stack.Name)
}
fmt.Fprintln(cmdOut, "Waiting for the stack to be stable and running...")
<-watcher.Watch(stack, serviceNames(cfg))
fmt.Fprintf(cmdOut, "Stack %s is stable and running\n\n", stack.Name)
// TODO: fmt.Fprintf(cmdOut, "Read the logs with:\n $ %s stack logs %s\n", filepath.Base(os.Args[0]), stack.Name)
return nil
}
// createFileBasedConfigMaps creates a Kubernetes ConfigMap for each Compose global file-based config.
func createFileBasedConfigMaps(stackName string, globalConfigs map[string]composeTypes.ConfigObjConfig, configMaps corev1.ConfigMapInterface) error {
for name, config := range globalConfigs {
if config.File == "" {
continue
}
fileName := path.Base(config.File)
content, err := ioutil.ReadFile(config.File)
if err != nil {
return err
}
if _, err := configMaps.Create(toConfigMap(stackName, name, fileName, content)); err != nil {
return err
}
}
return nil
}
func serviceNames(cfg *composeTypes.Config) []string {
names := []string{}
for _, service := range cfg.Services {
names = append(names, service.Name)
}
return names
}
// createFileBasedSecrets creates a Kubernetes Secret for each Compose global file-based secret.
func createFileBasedSecrets(stackName string, globalSecrets map[string]composeTypes.SecretConfig, secrets corev1.SecretInterface) error {
for name, secret := range globalSecrets {
if secret.File == "" {
continue
}
fileName := path.Base(secret.File)
content, err := ioutil.ReadFile(secret.File)
if err != nil {
return err
}
if _, err := secrets.Create(toSecret(stackName, name, fileName, content)); err != nil {
return err
}
}
return nil
}

View File

@ -1,74 +0,0 @@
package kubernetes
import (
"sort"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/compose/loader"
composetypes "github.com/docker/cli/cli/compose/types"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"vbom.ml/util/sortorder"
)
// RunList is the kubernetes implementation of docker stack ls
func RunList(dockerCli *KubeCli, opts options.List) error {
stacks, err := getStacks(dockerCli)
if err != nil {
return err
}
format := opts.Format
if len(format) == 0 {
format = formatter.TableFormatKey
}
stackCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewStackFormat(format),
}
sort.Sort(byName(stacks))
return formatter.StackWrite(stackCtx, stacks)
}
type byName []*formatter.Stack
func (n byName) Len() int { return len(n) }
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n byName) Less(i, j int) bool { return sortorder.NaturalLess(n[i].Name, n[j].Name) }
func getStacks(kubeCli *KubeCli) ([]*formatter.Stack, error) {
stackSvc, err := kubeCli.stacks()
if err != nil {
return nil, err
}
stacks, err := stackSvc.List(metav1.ListOptions{})
if err != nil {
return nil, err
}
var formattedStacks []*formatter.Stack
for _, stack := range stacks.Items {
cfg, err := loadStack(stack.Spec.ComposeFile)
if err != nil {
return nil, err
}
formattedStacks = append(formattedStacks, &formatter.Stack{
Name: stack.Name,
Services: len(getServices(cfg)),
})
}
return formattedStacks, nil
}
func loadStack(composefile string) (*composetypes.Config, error) {
parsed, err := loader.ParseYAML([]byte(composefile))
if err != nil {
return nil, err
}
return loader.Load(composetypes.ConfigDetails{
ConfigFiles: []composetypes.ConfigFile{
{
Config: parsed,
},
},
})
}

View File

@ -1,169 +0,0 @@
package kubernetes
import (
"bufio"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/docker/cli/cli/compose/loader"
"github.com/docker/cli/cli/compose/template"
composetypes "github.com/docker/cli/cli/compose/types"
apiv1beta1 "github.com/docker/cli/kubernetes/compose/v1beta1"
"github.com/pkg/errors"
yaml "gopkg.in/yaml.v2"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// LoadStack loads a stack from a Compose file, with a given name.
// FIXME(vdemeester) remove this and use cli/compose/loader for both swarm and kubernetes
func LoadStack(name, composeFile string) (*apiv1beta1.Stack, *composetypes.Config, error) {
if composeFile == "" {
return nil, nil, errors.New("compose-file must be set")
}
workingDir, err := os.Getwd()
if err != nil {
return nil, nil, err
}
composePath := composeFile
if !strings.HasPrefix(composePath, "/") {
composePath = filepath.Join(workingDir, composeFile)
}
if _, err := os.Stat(composePath); os.IsNotExist(err) {
return nil, nil, errors.Errorf("no compose file found in %s", filepath.Dir(composePath))
}
binary, err := ioutil.ReadFile(composePath)
if err != nil {
return nil, nil, errors.Wrap(err, "cannot read compose file")
}
env := env(workingDir)
return load(name, binary, workingDir, env)
}
func load(name string, binary []byte, workingDir string, env map[string]string) (*apiv1beta1.Stack, *composetypes.Config, error) {
processed, err := template.Substitute(string(binary), func(key string) (string, bool) { return env[key], true })
if err != nil {
return nil, nil, errors.Wrap(err, "cannot load compose file")
}
parsed, err := loader.ParseYAML([]byte(processed))
if err != nil {
return nil, nil, errors.Wrapf(err, "cannot load compose file")
}
cfg, err := loader.Load(composetypes.ConfigDetails{
WorkingDir: workingDir,
ConfigFiles: []composetypes.ConfigFile{
{
Config: parsed,
},
},
})
if err != nil {
return nil, nil, errors.Wrapf(err, "cannot load compose file")
}
result, err := processEnvFiles(processed, parsed, cfg)
if err != nil {
return nil, nil, errors.Wrapf(err, "cannot load compose file")
}
return &apiv1beta1.Stack{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: apiv1beta1.StackSpec{
ComposeFile: result,
},
}, cfg, nil
}
type iMap = map[string]interface{}
func processEnvFiles(input string, parsed map[string]interface{}, config *composetypes.Config) (string, error) {
changed := false
for _, svc := range config.Services {
if len(svc.EnvFile) == 0 {
continue
}
// Load() processed the env_file for us, we just need to inject back into
// the intermediate representation
env := iMap{}
for k, v := range svc.Environment {
env[k] = v
}
parsed["services"].(iMap)[svc.Name].(iMap)["environment"] = env
delete(parsed["services"].(iMap)[svc.Name].(iMap), "env_file")
changed = true
}
if !changed {
return input, nil
}
res, err := yaml.Marshal(parsed)
if err != nil {
return "", err
}
return string(res), nil
}
func env(workingDir string) map[string]string {
// Apply .env file first
config := readEnvFile(filepath.Join(workingDir, ".env"))
// Apply env variables
for k, v := range envToMap(os.Environ()) {
config[k] = v
}
return config
}
func readEnvFile(path string) map[string]string {
config := map[string]string{}
file, err := os.Open(path)
if err != nil {
return config // Ignore
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
if strings.HasPrefix(strings.TrimSpace(line), "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) == 2 {
key := parts[0]
value := parts[1]
config[key] = value
}
}
return config
}
func envToMap(env []string) map[string]string {
config := map[string]string{}
for _, value := range env {
parts := strings.SplitN(value, "=", 2)
key := parts[0]
value := parts[1]
config[key] = value
}
return config
}

View File

@ -1,34 +0,0 @@
package kubernetes
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPlaceholders(t *testing.T) {
env := map[string]string{
"TAG": "_latest_",
"K1": "V1",
"K2": "V2",
}
prefix := "version: '3'\nvolumes:\n data:\n external:\n name: "
var tests = []struct {
input string
expectedOutput string
}{
{prefix + "BEFORE${TAG}AFTER", prefix + "BEFORE_latest_AFTER"},
{prefix + "BEFORE${K1}${K2}AFTER", prefix + "BEFOREV1V2AFTER"},
{prefix + "BEFORE$TAG AFTER", prefix + "BEFORE_latest_ AFTER"},
{prefix + "BEFORE$$TAG AFTER", prefix + "BEFORE$TAG AFTER"},
{prefix + "BEFORE $UNKNOWN AFTER", prefix + "BEFORE AFTER"},
}
for _, test := range tests {
output, _, err := load("stack", []byte(test.input), ".", env)
require.NoError(t, err)
assert.Equal(t, test.expectedOutput, output.Spec.ComposeFile)
}
}

View File

@ -1,106 +0,0 @@
package kubernetes
import (
"fmt"
"sort"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/command/task"
"github.com/docker/docker/api/types/swarm"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/kubernetes/pkg/api"
)
// RunPS is the kubernetes implementation of docker stack ps
func RunPS(dockerCli *KubeCli, options options.PS) error {
namespace := options.Namespace
// Initialize clients
client, err := dockerCli.composeClient()
if err != nil {
return err
}
stacks, err := dockerCli.stacks()
if err != nil {
return err
}
podsClient := client.Pods()
// Fetch pods
if _, err := stacks.Get(namespace, metav1.GetOptions{}); err != nil {
return fmt.Errorf("nothing found in stack: %s", namespace)
}
pods, err := fetchPods(namespace, podsClient)
if err != nil {
return err
}
if len(pods) == 0 {
return fmt.Errorf("nothing found in stack: %s", namespace)
}
format := options.Format
if len(format) == 0 {
format = task.DefaultFormat(dockerCli.ConfigFile(), options.Quiet)
}
nodeResolver := makeNodeResolver(options.NoResolve, client.Nodes())
tasks := make([]swarm.Task, len(pods))
for i, pod := range pods {
tasks[i] = podToTask(pod)
}
return print(dockerCli, namespace, tasks, pods, nodeResolver, !options.NoTrunc, options.Quiet, format)
}
type idResolver func(name string) (string, error)
func print(dockerCli command.Cli, namespace string, tasks []swarm.Task, pods []apiv1.Pod, nodeResolver idResolver, trunc, quiet bool, format string) error {
sort.Stable(tasksBySlot(tasks))
names := map[string]string{}
nodes := map[string]string{}
tasksCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewTaskFormat(format, quiet),
Trunc: trunc,
}
for i, task := range tasks {
nodeValue, err := nodeResolver(pods[i].Spec.NodeName)
if err != nil {
return err
}
names[task.ID] = fmt.Sprintf("%s_%s", namespace, pods[i].Name)
nodes[task.ID] = nodeValue
}
return formatter.TaskWrite(tasksCtx, tasks, names, nodes)
}
func makeNodeResolver(noResolve bool, nodes corev1.NodeInterface) func(string) (string, error) {
// Here we have a name and we need to resolve its identifier. To mimic swarm behavior
// we need to resolve the id when noresolve is set, otherwise we return the name.
if noResolve {
return func(name string) (string, error) {
n, err := nodes.List(metav1.ListOptions{
FieldSelector: fields.OneTermEqualSelector(api.ObjectNameField, name).String(),
})
if err != nil {
return "", err
}
if len(n.Items) != 1 {
return "", fmt.Errorf("could not find node '%s'", name)
}
return string(n.Items[0].UID), nil
}
}
return func(name string) (string, error) { return name, nil }
}

View File

@ -1,25 +0,0 @@
package kubernetes
import (
"fmt"
"github.com/docker/cli/cli/command/stack/options"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// RunRemove is the kubernetes implementation of docker stack remove
func RunRemove(dockerCli *KubeCli, opts options.Remove) error {
stacks, err := dockerCli.stacks()
if err != nil {
return err
}
for _, stack := range opts.Namespaces {
fmt.Fprintf(dockerCli.Out(), "Removing stack: %s\n", stack)
err := stacks.Delete(stack, &metav1.DeleteOptions{})
if err != nil {
fmt.Fprintf(dockerCli.Out(), "Failed to remove stack %s: %s\n", stack, err)
return err
}
}
return nil
}

View File

@ -1,64 +0,0 @@
package kubernetes
import (
"fmt"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/kubernetes/labels"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// RunServices is the kubernetes implementation of docker stack services
func RunServices(dockerCli *KubeCli, opts options.Services) error {
// Initialize clients
client, err := dockerCli.composeClient()
if err != nil {
return nil
}
stacks, err := dockerCli.stacks()
if err != nil {
return err
}
replicas := client.ReplicaSets()
if _, err := stacks.Get(opts.Namespace, metav1.GetOptions{}); err != nil {
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", opts.Namespace)
return nil
}
replicasList, err := replicas.List(metav1.ListOptions{LabelSelector: labels.SelectorForStack(opts.Namespace)})
if err != nil {
return err
}
servicesList, err := client.Services().List(metav1.ListOptions{LabelSelector: labels.SelectorForStack(opts.Namespace)})
if err != nil {
return err
}
// Convert Replicas sets and kubernetes services to swam services and formatter informations
services, info, err := replicasToServices(replicasList, servicesList)
if err != nil {
return err
}
if opts.Quiet {
info = map[string]formatter.ServiceListInfo{}
}
format := opts.Format
if len(format) == 0 {
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.Quiet {
format = dockerCli.ConfigFile().ServicesFormat
} else {
format = formatter.TableFormatKey
}
}
servicesCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewServiceListFormat(format, opts.Quiet),
}
return formatter.ServiceListWrite(servicesCtx, services, info)
}

View File

@ -1,117 +0,0 @@
package kubernetes
import (
"fmt"
"time"
apiv1beta1 "github.com/docker/cli/kubernetes/compose/v1beta1"
"github.com/docker/cli/kubernetes/labels"
apiv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)
// DeployWatcher watches a stack deployement
type DeployWatcher struct {
Pods corev1.PodInterface
}
// Watch watches a stuck deployement and return a chan that will holds the state of the stack
func (w DeployWatcher) Watch(stack *apiv1beta1.Stack, serviceNames []string) chan bool {
stop := make(chan bool)
go w.waitForPods(stack.Name, serviceNames, stop)
return stop
}
func (w DeployWatcher) waitForPods(stackName string, serviceNames []string, stop chan bool) {
starts := map[string]int32{}
for {
time.Sleep(1 * time.Second)
list, err := w.Pods.List(metav1.ListOptions{
LabelSelector: labels.SelectorForStack(stackName),
IncludeUninitialized: true,
})
if err != nil {
stop <- true
return
}
for i := range list.Items {
pod := list.Items[i]
if pod.Status.Phase != apiv1.PodRunning {
continue
}
startCount := startCount(pod)
serviceName := pod.Labels[labels.ForServiceName]
if startCount != starts[serviceName] {
if startCount == 1 {
fmt.Printf(" - Service %s has one container running\n", serviceName)
} else {
fmt.Printf(" - Service %s was restarted %d %s\n", serviceName, startCount-1, timeTimes(startCount-1))
}
starts[serviceName] = startCount
}
}
if allReady(list.Items, serviceNames) {
stop <- true
return
}
}
}
func startCount(pod apiv1.Pod) int32 {
restart := int32(0)
for _, status := range pod.Status.ContainerStatuses {
restart += status.RestartCount
}
return 1 + restart
}
func allReady(pods []apiv1.Pod, serviceNames []string) bool {
serviceUp := map[string]bool{}
for _, pod := range pods {
if time.Since(pod.GetCreationTimestamp().Time) < 10*time.Second {
return false
}
ready := false
for _, cond := range pod.Status.Conditions {
if cond.Type == apiv1.PodReady && cond.Status == apiv1.ConditionTrue {
ready = true
}
}
if !ready {
return false
}
serviceName := pod.Labels[labels.ForServiceName]
serviceUp[serviceName] = true
}
for _, serviceName := range serviceNames {
if !serviceUp[serviceName] {
return false
}
}
return true
}
func timeTimes(n int32) string {
if n == 1 {
return "time"
}
return "times"
}

View File

@ -1,16 +1,26 @@
package stack
import (
"sort"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/kubernetes"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/command/stack/swarm"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/compose/convert"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
"vbom.ml/util/sortorder"
)
type listOptions struct {
format string
}
func newListCommand(dockerCli command.Cli) *cobra.Command {
opts := options.List{}
opts := listOptions{}
cmd := &cobra.Command{
Use: "ls",
@ -18,18 +28,69 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
Short: "List stacks",
Args: cli.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
if dockerCli.ClientInfo().HasKubernetes() {
kli, err := kubernetes.WrapCli(dockerCli, cmd)
if err != nil {
return err
}
return kubernetes.RunList(kli, opts)
}
return swarm.RunList(dockerCli, opts)
return runList(dockerCli, opts)
},
}
flags := cmd.Flags()
flags.StringVar(&opts.Format, "format", "", "Pretty-print stacks using a Go template")
flags.StringVar(&opts.format, "format", "", "Pretty-print stacks using a Go template")
return cmd
}
func runList(dockerCli command.Cli, opts listOptions) error {
client := dockerCli.Client()
ctx := context.Background()
stacks, err := getStacks(ctx, client)
if err != nil {
return err
}
format := opts.format
if len(format) == 0 {
format = formatter.TableFormatKey
}
stackCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewStackFormat(format),
}
sort.Sort(byName(stacks))
return formatter.StackWrite(stackCtx, stacks)
}
type byName []*formatter.Stack
func (n byName) Len() int { return len(n) }
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n byName) Less(i, j int) bool { return sortorder.NaturalLess(n[i].Name, n[j].Name) }
func getStacks(ctx context.Context, apiclient client.APIClient) ([]*formatter.Stack, error) {
services, err := apiclient.ServiceList(
ctx,
types.ServiceListOptions{Filters: getAllStacksFilter()})
if err != nil {
return nil, err
}
m := make(map[string]*formatter.Stack)
for _, service := range services {
labels := service.Spec.Labels
name, ok := labels[convert.LabelNamespace]
if !ok {
return nil, errors.Errorf("cannot get label %s for service %s",
convert.LabelNamespace, service.ID)
}
ztack, ok := m[name]
if !ok {
m[name] = &formatter.Stack{
Name: name,
Services: 1,
}
} else {
ztack.Services++
}
}
var stacks []*formatter.Stack
for _, stack := range m {
stacks = append(stacks, stack)
}
return stacks, nil
}

View File

@ -1,41 +0,0 @@
package options
import "github.com/docker/cli/opts"
// Deploy holds docker stack deploy options
type Deploy struct {
Bundlefile string
Composefile string
Namespace string
ResolveImage string
SendRegistryAuth bool
Prune bool
}
// List holds docker stack ls options
type List struct {
Format string
}
// PS holds docker stack ps options
type PS struct {
Filter opts.FilterOpt
NoTrunc bool
Namespace string
NoResolve bool
Quiet bool
Format string
}
// Remove holds docker stack remove options
type Remove struct {
Namespaces []string
}
// Services holds docker stack services options
type Services struct {
Quiet bool
Format string
Filter opts.FilterOpt
Namespace string
}

View File

@ -0,0 +1,51 @@
package stack
import (
"fmt"
"io"
"os"
"github.com/docker/cli/cli/command/bundlefile"
"github.com/pkg/errors"
"github.com/spf13/pflag"
)
func addComposefileFlag(opt *string, flags *pflag.FlagSet) {
flags.StringVarP(opt, "compose-file", "c", "", "Path to a Compose file")
flags.SetAnnotation("compose-file", "version", []string{"1.25"})
}
func addBundlefileFlag(opt *string, flags *pflag.FlagSet) {
flags.StringVar(opt, "bundle-file", "", "Path to a Distributed Application Bundle file")
flags.SetAnnotation("bundle-file", "experimental", nil)
}
func addRegistryAuthFlag(opt *bool, flags *pflag.FlagSet) {
flags.BoolVar(opt, "with-registry-auth", false, "Send registry authentication details to Swarm agents")
}
func loadBundlefile(stderr io.Writer, namespace string, path string) (*bundlefile.Bundlefile, error) {
defaultPath := fmt.Sprintf("%s.dab", namespace)
if path == "" {
path = defaultPath
}
if _, err := os.Stat(path); err != nil {
return nil, errors.Errorf(
"Bundle %s not found. Specify the path with --file",
path)
}
fmt.Fprintf(stderr, "Loading bundle from %s\n", path)
reader, err := os.Open(path)
if err != nil {
return nil, err
}
defer reader.Close()
bundle, err := bundlefile.LoadFile(reader)
if err != nil {
return nil, errors.Errorf("Error reading %s: %v\n", path, err)
}
return bundle, err
}

View File

@ -1,41 +1,69 @@
package stack
import (
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/kubernetes"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/command/stack/swarm"
cliopts "github.com/docker/cli/opts"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/command/task"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type psOptions struct {
filter opts.FilterOpt
noTrunc bool
namespace string
noResolve bool
quiet bool
format string
}
func newPsCommand(dockerCli command.Cli) *cobra.Command {
opts := options.PS{Filter: cliopts.NewFilterOpt()}
options := psOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "ps [OPTIONS] STACK",
Short: "List the tasks in the stack",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespace = args[0]
if dockerCli.ClientInfo().HasKubernetes() {
kli, err := kubernetes.WrapCli(dockerCli, cmd)
if err != nil {
return err
}
return kubernetes.RunPS(kli, opts)
}
return swarm.RunPS(dockerCli, opts)
options.namespace = args[0]
return runPS(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVar(&opts.NoTrunc, "no-trunc", false, "Do not truncate output")
flags.BoolVar(&opts.NoResolve, "no-resolve", false, "Do not map IDs to Names")
flags.VarP(&opts.Filter, "filter", "f", "Filter output based on conditions provided")
flags.SetAnnotation("filter", "swarm", nil)
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display task IDs")
flags.StringVar(&opts.Format, "format", "", "Pretty-print tasks using a Go template")
flags.BoolVar(&options.noTrunc, "no-trunc", false, "Do not truncate output")
flags.BoolVar(&options.noResolve, "no-resolve", false, "Do not map IDs to Names")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display task IDs")
flags.StringVar(&options.format, "format", "", "Pretty-print tasks using a Go template")
return cmd
}
func runPS(dockerCli command.Cli, options psOptions) error {
namespace := options.namespace
client := dockerCli.Client()
ctx := context.Background()
filter := getStackFilterFromOpt(options.namespace, options.filter)
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
if err != nil {
return err
}
if len(tasks) == 0 {
return fmt.Errorf("nothing found in stack: %s", namespace)
}
format := options.format
if len(format) == 0 {
format = task.DefaultFormat(dockerCli.ConfigFile(), options.quiet)
}
return task.Print(ctx, dockerCli, tasks, idresolver.New(client, options.noResolve), !options.noTrunc, options.quiet, format)
}

View File

@ -1,16 +1,26 @@
package stack
import (
"fmt"
"sort"
"strings"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/kubernetes"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/command/stack/swarm"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type removeOptions struct {
namespaces []string
}
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
var opts options.Remove
var opts removeOptions
cmd := &cobra.Command{
Use: "rm STACK [STACK...]",
@ -18,16 +28,134 @@ func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
Short: "Remove one or more stacks",
Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespaces = args
if dockerCli.ClientInfo().HasKubernetes() {
kli, err := kubernetes.WrapCli(dockerCli, cmd)
if err != nil {
return err
}
return kubernetes.RunRemove(kli, opts)
}
return swarm.RunRemove(dockerCli, opts)
opts.namespaces = args
return runRemove(dockerCli, opts)
},
}
return cmd
}
func runRemove(dockerCli command.Cli, opts removeOptions) error {
namespaces := opts.namespaces
client := dockerCli.Client()
ctx := context.Background()
var errs []string
for _, namespace := range namespaces {
services, err := getServices(ctx, client, namespace)
if err != nil {
return err
}
networks, err := getStackNetworks(ctx, client, namespace)
if err != nil {
return err
}
var secrets []swarm.Secret
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.25") {
secrets, err = getStackSecrets(ctx, client, namespace)
if err != nil {
return err
}
}
var configs []swarm.Config
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.30") {
configs, err = getStackConfigs(ctx, client, namespace)
if err != nil {
return err
}
}
if len(services)+len(networks)+len(secrets)+len(configs) == 0 {
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", namespace)
continue
}
hasError := removeServices(ctx, dockerCli, services)
hasError = removeSecrets(ctx, dockerCli, secrets) || hasError
hasError = removeConfigs(ctx, dockerCli, configs) || hasError
hasError = removeNetworks(ctx, dockerCli, networks) || hasError
if hasError {
errs = append(errs, fmt.Sprintf("Failed to remove some resources from stack: %s", namespace))
}
}
if len(errs) > 0 {
return errors.Errorf(strings.Join(errs, "\n"))
}
return nil
}
func sortServiceByName(services []swarm.Service) func(i, j int) bool {
return func(i, j int) bool {
return services[i].Spec.Name < services[j].Spec.Name
}
}
func removeServices(
ctx context.Context,
dockerCli command.Cli,
services []swarm.Service,
) bool {
var hasError bool
sort.Slice(services, sortServiceByName(services))
for _, service := range services {
fmt.Fprintf(dockerCli.Out(), "Removing service %s\n", service.Spec.Name)
if err := dockerCli.Client().ServiceRemove(ctx, service.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove service %s: %s", service.ID, err)
}
}
return hasError
}
func removeNetworks(
ctx context.Context,
dockerCli command.Cli,
networks []types.NetworkResource,
) bool {
var hasError bool
for _, network := range networks {
fmt.Fprintf(dockerCli.Out(), "Removing network %s\n", network.Name)
if err := dockerCli.Client().NetworkRemove(ctx, network.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove network %s: %s", network.ID, err)
}
}
return hasError
}
func removeSecrets(
ctx context.Context,
dockerCli command.Cli,
secrets []swarm.Secret,
) bool {
var hasError bool
for _, secret := range secrets {
fmt.Fprintf(dockerCli.Out(), "Removing secret %s\n", secret.Spec.Name)
if err := dockerCli.Client().SecretRemove(ctx, secret.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove secret %s: %s", secret.ID, err)
}
}
return hasError
}
func removeConfigs(
ctx context.Context,
dockerCli command.Cli,
configs []swarm.Config,
) bool {
var hasError bool
for _, config := range configs {
fmt.Fprintf(dockerCli.Out(), "Removing config %s\n", config.Spec.Name)
if err := dockerCli.Client().ConfigRemove(ctx, config.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove config %s: %s", config.ID, err)
}
}
return hasError
}

View File

@ -1,39 +1,94 @@
package stack
import (
"fmt"
"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/kubernetes"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/command/stack/swarm"
cliopts "github.com/docker/cli/opts"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/service"
"github.com/docker/cli/opts"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/spf13/cobra"
"golang.org/x/net/context"
)
type servicesOptions struct {
quiet bool
format string
filter opts.FilterOpt
namespace string
}
func newServicesCommand(dockerCli command.Cli) *cobra.Command {
opts := options.Services{Filter: cliopts.NewFilterOpt()}
options := servicesOptions{filter: opts.NewFilterOpt()}
cmd := &cobra.Command{
Use: "services [OPTIONS] STACK",
Short: "List the services in the stack",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.Namespace = args[0]
if dockerCli.ClientInfo().HasKubernetes() {
kli, err := kubernetes.WrapCli(dockerCli, cmd)
if err != nil {
return err
}
return kubernetes.RunServices(kli, opts)
}
return swarm.RunServices(dockerCli, opts)
options.namespace = args[0]
return runServices(dockerCli, options)
},
}
flags := cmd.Flags()
flags.BoolVarP(&opts.Quiet, "quiet", "q", false, "Only display IDs")
flags.StringVar(&opts.Format, "format", "", "Pretty-print services using a Go template")
flags.VarP(&opts.Filter, "filter", "f", "Filter output based on conditions provided")
flags.SetAnnotation("filter", "swarm", nil)
flags.BoolVarP(&options.quiet, "quiet", "q", false, "Only display IDs")
flags.StringVar(&options.format, "format", "", "Pretty-print services using a Go template")
flags.VarP(&options.filter, "filter", "f", "Filter output based on conditions provided")
return cmd
}
func runServices(dockerCli command.Cli, options servicesOptions) error {
ctx := context.Background()
client := dockerCli.Client()
filter := getStackFilterFromOpt(options.namespace, options.filter)
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: filter})
if err != nil {
return err
}
// if no services in this stack, print message and exit 0
if len(services) == 0 {
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", options.namespace)
return nil
}
info := map[string]formatter.ServiceListInfo{}
if !options.quiet {
taskFilter := filters.NewArgs()
for _, service := range services {
taskFilter.Add("service", service.ID)
}
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: taskFilter})
if err != nil {
return err
}
nodes, err := client.NodeList(ctx, types.NodeListOptions{})
if err != nil {
return err
}
info = service.GetServicesStatus(services, nodes, tasks)
}
format := options.format
if len(format) == 0 {
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !options.quiet {
format = dockerCli.ConfigFile().ServicesFormat
} else {
format = formatter.TableFormatKey
}
}
servicesCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewServiceListFormat(format, options.quiet),
}
return formatter.ServiceListWrite(servicesCtx, services, info)
}

View File

@ -1,239 +0,0 @@
package swarm
import (
"strings"
"github.com/docker/cli/cli/compose/convert"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
version string
services []string
networks []string
secrets []string
configs []string
removedServices []string
removedNetworks []string
removedSecrets []string
removedConfigs []string
serviceListFunc func(options types.ServiceListOptions) ([]swarm.Service, error)
networkListFunc func(options types.NetworkListOptions) ([]types.NetworkResource, error)
secretListFunc func(options types.SecretListOptions) ([]swarm.Secret, error)
configListFunc func(options types.ConfigListOptions) ([]swarm.Config, error)
nodeListFunc func(options types.NodeListOptions) ([]swarm.Node, error)
taskListFunc func(options types.TaskListOptions) ([]swarm.Task, error)
nodeInspectWithRaw func(ref string) (swarm.Node, []byte, error)
serviceUpdateFunc func(serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error)
serviceRemoveFunc func(serviceID string) error
networkRemoveFunc func(networkID string) error
secretRemoveFunc func(secretID string) error
configRemoveFunc func(configID string) error
}
func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
return types.Version{
Version: "docker-dev",
APIVersion: api.DefaultVersion,
}, nil
}
func (cli *fakeClient) ClientVersion() string {
return cli.version
}
func (cli *fakeClient) ServiceList(ctx context.Context, options types.ServiceListOptions) ([]swarm.Service, error) {
if cli.serviceListFunc != nil {
return cli.serviceListFunc(options)
}
namespace := namespaceFromFilters(options.Filters)
servicesList := []swarm.Service{}
for _, name := range cli.services {
if belongToNamespace(name, namespace) {
servicesList = append(servicesList, serviceFromName(name))
}
}
return servicesList, nil
}
func (cli *fakeClient) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) {
if cli.networkListFunc != nil {
return cli.networkListFunc(options)
}
namespace := namespaceFromFilters(options.Filters)
networksList := []types.NetworkResource{}
for _, name := range cli.networks {
if belongToNamespace(name, namespace) {
networksList = append(networksList, networkFromName(name))
}
}
return networksList, nil
}
func (cli *fakeClient) SecretList(ctx context.Context, options types.SecretListOptions) ([]swarm.Secret, error) {
if cli.secretListFunc != nil {
return cli.secretListFunc(options)
}
namespace := namespaceFromFilters(options.Filters)
secretsList := []swarm.Secret{}
for _, name := range cli.secrets {
if belongToNamespace(name, namespace) {
secretsList = append(secretsList, secretFromName(name))
}
}
return secretsList, nil
}
func (cli *fakeClient) ConfigList(ctx context.Context, options types.ConfigListOptions) ([]swarm.Config, error) {
if cli.configListFunc != nil {
return cli.configListFunc(options)
}
namespace := namespaceFromFilters(options.Filters)
configsList := []swarm.Config{}
for _, name := range cli.configs {
if belongToNamespace(name, namespace) {
configsList = append(configsList, configFromName(name))
}
}
return configsList, nil
}
func (cli *fakeClient) TaskList(ctx context.Context, options types.TaskListOptions) ([]swarm.Task, error) {
if cli.taskListFunc != nil {
return cli.taskListFunc(options)
}
return []swarm.Task{}, nil
}
func (cli *fakeClient) NodeList(ctx context.Context, options types.NodeListOptions) ([]swarm.Node, error) {
if cli.nodeListFunc != nil {
return cli.nodeListFunc(options)
}
return []swarm.Node{}, nil
}
func (cli *fakeClient) NodeInspectWithRaw(ctx context.Context, ref string) (swarm.Node, []byte, error) {
if cli.nodeInspectWithRaw != nil {
return cli.nodeInspectWithRaw(ref)
}
return swarm.Node{}, nil, nil
}
func (cli *fakeClient) ServiceUpdate(ctx context.Context, serviceID string, version swarm.Version, service swarm.ServiceSpec, options types.ServiceUpdateOptions) (types.ServiceUpdateResponse, error) {
if cli.serviceUpdateFunc != nil {
return cli.serviceUpdateFunc(serviceID, version, service, options)
}
return types.ServiceUpdateResponse{}, nil
}
func (cli *fakeClient) ServiceRemove(ctx context.Context, serviceID string) error {
if cli.serviceRemoveFunc != nil {
return cli.serviceRemoveFunc(serviceID)
}
cli.removedServices = append(cli.removedServices, serviceID)
return nil
}
func (cli *fakeClient) NetworkRemove(ctx context.Context, networkID string) error {
if cli.networkRemoveFunc != nil {
return cli.networkRemoveFunc(networkID)
}
cli.removedNetworks = append(cli.removedNetworks, networkID)
return nil
}
func (cli *fakeClient) SecretRemove(ctx context.Context, secretID string) error {
if cli.secretRemoveFunc != nil {
return cli.secretRemoveFunc(secretID)
}
cli.removedSecrets = append(cli.removedSecrets, secretID)
return nil
}
func (cli *fakeClient) ConfigRemove(ctx context.Context, configID string) error {
if cli.configRemoveFunc != nil {
return cli.configRemoveFunc(configID)
}
cli.removedConfigs = append(cli.removedConfigs, configID)
return nil
}
func serviceFromName(name string) swarm.Service {
return swarm.Service{
ID: "ID-" + name,
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: name},
},
}
}
func networkFromName(name string) types.NetworkResource {
return types.NetworkResource{
ID: "ID-" + name,
Name: name,
}
}
func secretFromName(name string) swarm.Secret {
return swarm.Secret{
ID: "ID-" + name,
Spec: swarm.SecretSpec{
Annotations: swarm.Annotations{Name: name},
},
}
}
func configFromName(name string) swarm.Config {
return swarm.Config{
ID: "ID-" + name,
Spec: swarm.ConfigSpec{
Annotations: swarm.Annotations{Name: name},
},
}
}
func namespaceFromFilters(filters filters.Args) string {
label := filters.Get("label")[0]
return strings.TrimPrefix(label, convert.LabelNamespace+"=")
}
func belongToNamespace(id, namespace string) bool {
return strings.HasPrefix(id, namespace+"_")
}
func objectName(namespace, name string) string {
return namespace + "_" + name
}
func objectID(name string) string {
return "ID-" + name
}
func buildObjectIDs(objectNames []string) []string {
IDs := make([]string, len(objectNames))
for i, name := range objectNames {
IDs[i] = objectID(name)
}
return IDs
}

View File

@ -1,88 +0,0 @@
package swarm
import (
"fmt"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/compose/convert"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
// Resolve image constants
const (
defaultNetworkDriver = "overlay"
ResolveImageAlways = "always"
ResolveImageChanged = "changed"
ResolveImageNever = "never"
)
// RunDeploy is the swarm implementation of docker stack deploy
func RunDeploy(dockerCli command.Cli, opts options.Deploy) error {
ctx := context.Background()
if err := validateResolveImageFlag(dockerCli, &opts); err != nil {
return err
}
switch {
case opts.Bundlefile == "" && opts.Composefile == "":
return errors.Errorf("Please specify either a bundle file (with --bundle-file) or a Compose file (with --compose-file).")
case opts.Bundlefile != "" && opts.Composefile != "":
return errors.Errorf("You cannot specify both a bundle file and a Compose file.")
case opts.Bundlefile != "":
return deployBundle(ctx, dockerCli, opts)
default:
return deployCompose(ctx, dockerCli, opts)
}
}
// validateResolveImageFlag validates the opts.resolveImage command line option
// and also turns image resolution off if the version is older than 1.30
func validateResolveImageFlag(dockerCli command.Cli, opts *options.Deploy) error {
if opts.ResolveImage != ResolveImageAlways && opts.ResolveImage != ResolveImageChanged && opts.ResolveImage != ResolveImageNever {
return errors.Errorf("Invalid option %s for flag --resolve-image", opts.ResolveImage)
}
// client side image resolution should not be done when the supported
// server version is older than 1.30
if versions.LessThan(dockerCli.Client().ClientVersion(), "1.30") {
opts.ResolveImage = ResolveImageNever
}
return nil
}
// checkDaemonIsSwarmManager does an Info API call to verify that the daemon is
// a swarm manager. This is necessary because we must create networks before we
// create services, but the API call for creating a network does not return a
// proper status code when it can't create a network in the "global" scope.
func checkDaemonIsSwarmManager(ctx context.Context, dockerCli command.Cli) error {
info, err := dockerCli.Client().Info(ctx)
if err != nil {
return err
}
if !info.Swarm.ControlAvailable {
return errors.New("this node is not a swarm manager. Use \"docker swarm init\" or \"docker swarm join\" to connect this node to swarm and try again")
}
return nil
}
// pruneServices removes services that are no longer referenced in the source
func pruneServices(ctx context.Context, dockerCli command.Cli, namespace convert.Namespace, services map[string]struct{}) {
client := dockerCli.Client()
oldServices, err := getServices(ctx, client, namespace.Name())
if err != nil {
fmt.Fprintf(dockerCli.Err(), "Failed to list services: %s", err)
}
pruneServices := []swarm.Service{}
for _, service := range oldServices {
if _, exists := services[namespace.Descope(service.Spec.Name)]; !exists {
pruneServices = append(pruneServices, service)
}
}
removeServices(ctx, dockerCli, pruneServices)
}

View File

@ -1,74 +0,0 @@
package swarm
import (
"sort"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/compose/convert"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"golang.org/x/net/context"
"vbom.ml/util/sortorder"
)
// RunList is the swarm implementation of docker stack ls
func RunList(dockerCli command.Cli, opts options.List) error {
client := dockerCli.Client()
ctx := context.Background()
stacks, err := getStacks(ctx, client)
if err != nil {
return err
}
format := opts.Format
if len(format) == 0 {
format = formatter.TableFormatKey
}
stackCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewStackFormat(format),
}
sort.Sort(byName(stacks))
return formatter.StackWrite(stackCtx, stacks)
}
type byName []*formatter.Stack
func (n byName) Len() int { return len(n) }
func (n byName) Swap(i, j int) { n[i], n[j] = n[j], n[i] }
func (n byName) Less(i, j int) bool { return sortorder.NaturalLess(n[i].Name, n[j].Name) }
func getStacks(ctx context.Context, apiclient client.APIClient) ([]*formatter.Stack, error) {
services, err := apiclient.ServiceList(
ctx,
types.ServiceListOptions{Filters: getAllStacksFilter()})
if err != nil {
return nil, err
}
m := make(map[string]*formatter.Stack)
for _, service := range services {
labels := service.Spec.Labels
name, ok := labels[convert.LabelNamespace]
if !ok {
return nil, errors.Errorf("cannot get label %s for service %s",
convert.LabelNamespace, service.ID)
}
ztack, ok := m[name]
if !ok {
m[name] = &formatter.Stack{
Name: name,
Services: 1,
}
} else {
ztack.Services++
}
}
var stacks []*formatter.Stack
for _, stack := range m {
stacks = append(stacks, stack)
}
return stacks, nil
}

View File

@ -1,37 +0,0 @@
package swarm
import (
"fmt"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/idresolver"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/cli/cli/command/task"
"github.com/docker/docker/api/types"
"golang.org/x/net/context"
)
// RunPS is the swarm implementation of docker stack ps
func RunPS(dockerCli command.Cli, opts options.PS) error {
namespace := opts.Namespace
client := dockerCli.Client()
ctx := context.Background()
filter := getStackFilterFromOpt(opts.Namespace, opts.Filter)
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: filter})
if err != nil {
return err
}
if len(tasks) == 0 {
return fmt.Errorf("nothing found in stack: %s", namespace)
}
format := opts.Format
if len(format) == 0 {
format = task.DefaultFormat(dockerCli.ConfigFile(), opts.Quiet)
}
return task.Print(ctx, dockerCli, tasks, idresolver.New(client, opts.NoResolve), !opts.NoTrunc, opts.Quiet, format)
}

View File

@ -1,141 +0,0 @@
package swarm
import (
"fmt"
"sort"
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/docker/docker/api/types/versions"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
// RunRemove is the swarm implementation of docker stack remove
func RunRemove(dockerCli command.Cli, opts options.Remove) error {
namespaces := opts.Namespaces
client := dockerCli.Client()
ctx := context.Background()
var errs []string
for _, namespace := range namespaces {
services, err := getServices(ctx, client, namespace)
if err != nil {
return err
}
networks, err := getStackNetworks(ctx, client, namespace)
if err != nil {
return err
}
var secrets []swarm.Secret
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.25") {
secrets, err = getStackSecrets(ctx, client, namespace)
if err != nil {
return err
}
}
var configs []swarm.Config
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.30") {
configs, err = getStackConfigs(ctx, client, namespace)
if err != nil {
return err
}
}
if len(services)+len(networks)+len(secrets)+len(configs) == 0 {
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", namespace)
continue
}
hasError := removeServices(ctx, dockerCli, services)
hasError = removeSecrets(ctx, dockerCli, secrets) || hasError
hasError = removeConfigs(ctx, dockerCli, configs) || hasError
hasError = removeNetworks(ctx, dockerCli, networks) || hasError
if hasError {
errs = append(errs, fmt.Sprintf("Failed to remove some resources from stack: %s", namespace))
}
}
if len(errs) > 0 {
return errors.Errorf(strings.Join(errs, "\n"))
}
return nil
}
func sortServiceByName(services []swarm.Service) func(i, j int) bool {
return func(i, j int) bool {
return services[i].Spec.Name < services[j].Spec.Name
}
}
func removeServices(
ctx context.Context,
dockerCli command.Cli,
services []swarm.Service,
) bool {
var hasError bool
sort.Slice(services, sortServiceByName(services))
for _, service := range services {
fmt.Fprintf(dockerCli.Out(), "Removing service %s\n", service.Spec.Name)
if err := dockerCli.Client().ServiceRemove(ctx, service.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove service %s: %s", service.ID, err)
}
}
return hasError
}
func removeNetworks(
ctx context.Context,
dockerCli command.Cli,
networks []types.NetworkResource,
) bool {
var hasError bool
for _, network := range networks {
fmt.Fprintf(dockerCli.Out(), "Removing network %s\n", network.Name)
if err := dockerCli.Client().NetworkRemove(ctx, network.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove network %s: %s", network.ID, err)
}
}
return hasError
}
func removeSecrets(
ctx context.Context,
dockerCli command.Cli,
secrets []swarm.Secret,
) bool {
var hasError bool
for _, secret := range secrets {
fmt.Fprintf(dockerCli.Out(), "Removing secret %s\n", secret.Spec.Name)
if err := dockerCli.Client().SecretRemove(ctx, secret.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove secret %s: %s", secret.ID, err)
}
}
return hasError
}
func removeConfigs(
ctx context.Context,
dockerCli command.Cli,
configs []swarm.Config,
) bool {
var hasError bool
for _, config := range configs {
fmt.Fprintf(dockerCli.Out(), "Removing config %s\n", config.Spec.Name)
if err := dockerCli.Client().ConfigRemove(ctx, config.ID); err != nil {
hasError = true
fmt.Fprintf(dockerCli.Err(), "Failed to remove config %s: %s", config.ID, err)
}
}
return hasError
}

View File

@ -1,66 +0,0 @@
package swarm
import (
"fmt"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/service"
"github.com/docker/cli/cli/command/stack/options"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"golang.org/x/net/context"
)
// RunServices is the swarm implementation of docker stack services
func RunServices(dockerCli command.Cli, opts options.Services) error {
ctx := context.Background()
client := dockerCli.Client()
filter := getStackFilterFromOpt(opts.Namespace, opts.Filter)
services, err := client.ServiceList(ctx, types.ServiceListOptions{Filters: filter})
if err != nil {
return err
}
// if no services in this stack, print message and exit 0
if len(services) == 0 {
fmt.Fprintf(dockerCli.Err(), "Nothing found in stack: %s\n", opts.Namespace)
return nil
}
info := map[string]formatter.ServiceListInfo{}
if !opts.Quiet {
taskFilter := filters.NewArgs()
for _, service := range services {
taskFilter.Add("service", service.ID)
}
tasks, err := client.TaskList(ctx, types.TaskListOptions{Filters: taskFilter})
if err != nil {
return err
}
nodes, err := client.NodeList(ctx, types.NodeListOptions{})
if err != nil {
return err
}
info = service.GetServicesStatus(services, nodes, tasks)
}
format := opts.Format
if len(format) == 0 {
if len(dockerCli.ConfigFile().ServicesFormat) > 0 && !opts.Quiet {
format = dockerCli.ConfigFile().ServicesFormat
} else {
format = formatter.TableFormatKey
}
}
servicesCtx := formatter.Context{
Output: dockerCli.Out(),
Format: formatter.NewServiceListFormat(format, opts.Quiet),
}
return formatter.ServiceListWrite(servicesCtx, services, info)
}

View File

@ -10,14 +10,11 @@ import (
// NewSwarmCommand returns a cobra command for `swarm` subcommands
func NewSwarmCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "swarm",
Short: "Manage Swarm",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{
"version": "1.24",
"swarm": "",
},
Use: "swarm",
Short: "Manage Swarm",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"version": "1.24"},
}
cmd.AddCommand(
newInitCommand(dockerCli),

View File

@ -1,20 +1,13 @@
package system
import (
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"golang.org/x/net/context"
)
type fakeClient struct {
client.Client
version string
serverVersion func(ctx context.Context) (types.Version, error)
}
func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
return cli.serverVersion(ctx)
version string
}
func (cli *fakeClient) ClientVersion() string {

View File

@ -23,8 +23,6 @@ Client:{{if ne .Platform.Name ""}} {{.Platform.Name}}{{end}}
Git commit: {{.GitCommit}}
Built: {{.BuildTime}}
OS/Arch: {{.Os}}/{{.Arch}}
Experimental: {{.Experimental}}
Orchestrator: {{.Orchestrator}}
{{- end}}
{{- if .ServerOK}}{{with .Server}}
@ -71,8 +69,6 @@ type clientVersion struct {
Os string
Arch string
BuildTime string `json:",omitempty"`
Experimental bool
Orchestrator string `json:",omitempty"`
}
// ServerOK returns true when the client could connect to the docker server
@ -82,7 +78,7 @@ func (v versionInfo) ServerOK() bool {
}
// NewVersionCommand creates a new cobra.Command for `docker version`
func NewVersionCommand(dockerCli command.Cli) *cobra.Command {
func NewVersionCommand(dockerCli *command.DockerCli) *cobra.Command {
var opts versionOptions
cmd := &cobra.Command{
@ -109,7 +105,9 @@ func reformatDate(buildTime string) string {
return buildTime
}
func runVersion(dockerCli command.Cli, opts *versionOptions) error {
func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error {
ctx := context.Background()
templateFormat := versionTemplate
tmpl := templates.New("version")
if opts.format != "" {
@ -127,21 +125,22 @@ func runVersion(dockerCli command.Cli, opts *versionOptions) error {
vd := versionInfo{
Client: clientVersion{
Platform: struct{ Name string }{cli.PlatformName},
Version: cli.Version,
APIVersion: dockerCli.Client().ClientVersion(),
DefaultAPIVersion: dockerCli.DefaultVersion(),
GoVersion: runtime.Version(),
GitCommit: cli.GitCommit,
BuildTime: reformatDate(cli.BuildTime),
BuildTime: cli.BuildTime,
Os: runtime.GOOS,
Arch: runtime.GOARCH,
Experimental: dockerCli.ClientInfo().HasExperimental,
Orchestrator: string(dockerCli.ClientInfo().Orchestrator),
},
}
vd.Client.Platform.Name = cli.PlatformName
sv, err := dockerCli.Client().ServerVersion(context.Background())
// first we need to make BuildTime more human friendly
vd.Client.BuildTime = reformatDate(vd.Client.BuildTime)
sv, err := dockerCli.Client().ServerVersion(ctx)
if err == nil {
vd.Server = &sv
foundEngine := false

View File

@ -1,47 +0,0 @@
package system
import (
"fmt"
"strings"
"testing"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/api"
"github.com/docker/docker/api/types"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
func TestVersionWithoutServer(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{
serverVersion: func(ctx context.Context) (types.Version, error) {
return types.Version{}, fmt.Errorf("no server")
},
})
cmd := NewVersionCommand(cli)
cmd.SetOutput(cli.Err())
assert.Error(t, cmd.Execute())
assert.Contains(t, cleanTabs(cli.OutBuffer().String()), "Client:")
assert.NotContains(t, cleanTabs(cli.OutBuffer().String()), "Server:")
}
func fakeServerVersion(ctx context.Context) (types.Version, error) {
return types.Version{
Version: "docker-dev",
APIVersion: api.DefaultVersion,
}, nil
}
func TestVersionWithOrchestrator(t *testing.T) {
cli := test.NewFakeCli(&fakeClient{serverVersion: fakeServerVersion})
cli.SetClientInfo(func() command.ClientInfo { return command.ClientInfo{Orchestrator: "swarm"} })
cmd := NewVersionCommand(cli)
assert.NoError(t, cmd.Execute())
assert.Contains(t, cleanTabs(cli.OutBuffer().String()), "Orchestrator: swarm")
}
func cleanTabs(line string) string {
return strings.Join(strings.Fields(line), " ")
}

View File

@ -9,11 +9,10 @@ import (
// NewTrustCommand returns a cobra command for `trust` subcommands
func NewTrustCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "trust",
Short: "Manage trust on Docker images (experimental)",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
Annotations: map[string]string{"experimentalCLI": ""},
Use: "trust",
Short: "Manage trust on Docker images (experimental)",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
}
cmd.AddCommand(
newViewCommand(dockerCli),

View File

@ -243,5 +243,8 @@ func addStagedSigner(notaryRepo client.Repository, newSigner data.RoleName, sign
if err := notaryRepo.AddDelegationRoleAndKeys(trust.ReleasesRole, signerKeys); err != nil {
return err
}
return notaryRepo.AddDelegationPaths(trust.ReleasesRole, []string{""})
if err := notaryRepo.AddDelegationPaths(trust.ReleasesRole, []string{""}); err != nil {
return err
}
return nil
}

View File

@ -131,5 +131,8 @@ func removeSingleSigner(cli command.Cli, repoName, signerName string, forceYes b
if err = notaryRepo.RemoveDelegationRole(signerDelegation); err != nil {
return err
}
return notaryRepo.Publish()
if err = notaryRepo.Publish(); err != nil {
return err
}
return nil
}

View File

@ -62,7 +62,7 @@ func Networks(namespace Namespace, networks networkMap, servicesNetworks map[str
for internalName := range servicesNetworks {
network := networks[internalName]
if network.External.External {
externalNetworks = append(externalNetworks, network.Name)
externalNetworks = append(externalNetworks, network.External.Name)
continue
}

View File

@ -55,8 +55,10 @@ func TestNetworks(t *testing.T) {
},
},
"outside": composetypes.NetworkConfig{
External: composetypes.External{External: true},
Name: "special",
External: composetypes.External{
External: true,
Name: "special",
},
},
"attachablenet": composetypes.NetworkConfig{
Driver: "overlay",

View File

@ -230,7 +230,7 @@ func convertServiceNetworks(
}
target := namespace.Scope(networkName)
if networkConfig.External.External {
target = networkConfig.Name
target = networkConfig.External.Name
}
netAttachConfig := swarm.NetworkAttachmentConfig{
Target: target,

View File

@ -219,8 +219,10 @@ func TestConvertServiceNetworksOnlyDefault(t *testing.T) {
func TestConvertServiceNetworks(t *testing.T) {
networkConfigs := networkMap{
"front": composetypes.NetworkConfig{
External: composetypes.External{External: true},
Name: "fronttier",
External: composetypes.External{
External: true,
Name: "fronttier",
},
},
"back": composetypes.NetworkConfig{},
}
@ -257,8 +259,10 @@ func TestConvertServiceNetworks(t *testing.T) {
func TestConvertServiceNetworksCustomDefault(t *testing.T) {
networkConfigs := networkMap{
"default": composetypes.NetworkConfig{
External: composetypes.External{External: true},
Name: "custom",
External: composetypes.External{
External: true,
Name: "custom",
},
},
}
networks := map[string]*composetypes.ServiceNetworkConfig{}

View File

@ -22,37 +22,43 @@ func Volumes(serviceVolumes []composetypes.ServiceVolumeConfig, stackVolumes vol
return mounts, nil
}
func createMountFromVolume(volume composetypes.ServiceVolumeConfig) mount.Mount {
return mount.Mount{
Type: mount.Type(volume.Type),
Target: volume.Target,
ReadOnly: volume.ReadOnly,
Source: volume.Source,
Consistency: mount.Consistency(volume.Consistency),
}
}
func handleVolumeToMount(
func convertVolumeToMount(
volume composetypes.ServiceVolumeConfig,
stackVolumes volumes,
namespace Namespace,
) (mount.Mount, error) {
result := createMountFromVolume(volume)
result := mount.Mount{
Type: mount.Type(volume.Type),
Source: volume.Source,
Target: volume.Target,
ReadOnly: volume.ReadOnly,
Consistency: mount.Consistency(volume.Consistency),
}
if volume.Tmpfs != nil {
return mount.Mount{}, errors.New("tmpfs options are incompatible with type volume")
}
if volume.Bind != nil {
return mount.Mount{}, errors.New("bind options are incompatible with type volume")
}
// Anonymous volumes
if volume.Source == "" {
return result, nil
}
if volume.Type == "volume" && volume.Bind != nil {
return result, errors.New("bind options are incompatible with type volume")
}
if volume.Type == "bind" && volume.Volume != nil {
return result, errors.New("volume options are incompatible with type bind")
}
if volume.Bind != nil {
result.BindOptions = &mount.BindOptions{
Propagation: mount.Propagation(volume.Bind.Propagation),
}
}
// Binds volumes
if volume.Type == "bind" {
return result, nil
}
stackVolume, exists := stackVolumes[volume.Source]
if !exists {
return mount.Mount{}, errors.Errorf("undefined volume %q", volume.Source)
return result, errors.Errorf("undefined volume %q", volume.Source)
}
result.Source = namespace.Scope(volume.Source)
@ -79,62 +85,6 @@ func handleVolumeToMount(
}
}
// Named volumes
return result, nil
}
func handleBindToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, error) {
result := createMountFromVolume(volume)
if volume.Source == "" {
return mount.Mount{}, errors.New("invalid bind source, source cannot be empty")
}
if volume.Volume != nil {
return mount.Mount{}, errors.New("volume options are incompatible with type bind")
}
if volume.Tmpfs != nil {
return mount.Mount{}, errors.New("tmpfs options are incompatible with type bind")
}
if volume.Bind != nil {
result.BindOptions = &mount.BindOptions{
Propagation: mount.Propagation(volume.Bind.Propagation),
}
}
return result, nil
}
func handleTmpfsToMount(volume composetypes.ServiceVolumeConfig) (mount.Mount, error) {
result := createMountFromVolume(volume)
if volume.Source != "" {
return mount.Mount{}, errors.New("invalid tmpfs source, source must be empty")
}
if volume.Bind != nil {
return mount.Mount{}, errors.New("bind options are incompatible with type tmpfs")
}
if volume.Volume != nil {
return mount.Mount{}, errors.New("volume options are incompatible with type tmpfs")
}
if volume.Tmpfs != nil {
result.TmpfsOptions = &mount.TmpfsOptions{
SizeBytes: volume.Tmpfs.Size,
}
}
return result, nil
}
func convertVolumeToMount(
volume composetypes.ServiceVolumeConfig,
stackVolumes volumes,
namespace Namespace,
) (mount.Mount, error) {
switch volume.Type {
case "volume", "":
return handleVolumeToMount(volume, stackVolumes, namespace)
case "bind":
return handleBindToMount(volume)
case "tmpfs":
return handleTmpfsToMount(volume)
}
return mount.Mount{}, errors.New("volume type must be volume, bind, or tmpfs")
}

View File

@ -22,28 +22,7 @@ func TestConvertVolumeToMountAnonymousVolume(t *testing.T) {
assert.Equal(t, expected, mount)
}
func TestConvertVolumeToMountAnonymousBind(t *testing.T) {
config := composetypes.ServiceVolumeConfig{
Type: "bind",
Target: "/foo/bar",
Bind: &composetypes.ServiceVolumeBind{
Propagation: "slave",
},
}
_, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
assert.EqualError(t, err, "invalid bind source, source cannot be empty")
}
func TestConvertVolumeToMountUnapprovedType(t *testing.T) {
config := composetypes.ServiceVolumeConfig{
Type: "foo",
Target: "/foo/bar",
}
_, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
assert.EqualError(t, err, "volume type must be volume, bind, or tmpfs")
}
func TestConvertVolumeToMountConflictingOptionsBindInVolume(t *testing.T) {
func TestConvertVolumeToMountConflictingOptionsBind(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
@ -58,22 +37,7 @@ func TestConvertVolumeToMountConflictingOptionsBindInVolume(t *testing.T) {
assert.EqualError(t, err, "bind options are incompatible with type volume")
}
func TestConvertVolumeToMountConflictingOptionsTmpfsInVolume(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
Type: "volume",
Source: "foo",
Target: "/target",
Tmpfs: &composetypes.ServiceVolumeTmpfs{
Size: 1000,
},
}
_, err := convertVolumeToMount(config, volumes{}, namespace)
assert.EqualError(t, err, "tmpfs options are incompatible with type volume")
}
func TestConvertVolumeToMountConflictingOptionsVolumeInBind(t *testing.T) {
func TestConvertVolumeToMountConflictingOptionsVolume(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
@ -88,49 +52,6 @@ func TestConvertVolumeToMountConflictingOptionsVolumeInBind(t *testing.T) {
assert.EqualError(t, err, "volume options are incompatible with type bind")
}
func TestConvertVolumeToMountConflictingOptionsTmpfsInBind(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
Type: "bind",
Source: "/foo",
Target: "/target",
Tmpfs: &composetypes.ServiceVolumeTmpfs{
Size: 1000,
},
}
_, err := convertVolumeToMount(config, volumes{}, namespace)
assert.EqualError(t, err, "tmpfs options are incompatible with type bind")
}
func TestConvertVolumeToMountConflictingOptionsBindInTmpfs(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
Type: "tmpfs",
Target: "/target",
Bind: &composetypes.ServiceVolumeBind{
Propagation: "slave",
},
}
_, err := convertVolumeToMount(config, volumes{}, namespace)
assert.EqualError(t, err, "bind options are incompatible with type tmpfs")
}
func TestConvertVolumeToMountConflictingOptionsVolumeInTmpfs(t *testing.T) {
namespace := NewNamespace("foo")
config := composetypes.ServiceVolumeConfig{
Type: "tmpfs",
Target: "/target",
Volume: &composetypes.ServiceVolumeVolume{
NoCopy: true,
},
}
_, err := convertVolumeToMount(config, volumes{}, namespace)
assert.EqualError(t, err, "volume options are incompatible with type tmpfs")
}
func TestConvertVolumeToMountNamedVolume(t *testing.T) {
stackVolumes := volumes{
"normal": composetypes.VolumeConfig{
@ -310,35 +231,3 @@ func TestConvertVolumeToMountVolumeDoesNotExist(t *testing.T) {
_, err := convertVolumeToMount(config, volumes{}, namespace)
assert.EqualError(t, err, "undefined volume \"unknown\"")
}
func TestConvertTmpfsToMountVolume(t *testing.T) {
config := composetypes.ServiceVolumeConfig{
Type: "tmpfs",
Target: "/foo/bar",
Tmpfs: &composetypes.ServiceVolumeTmpfs{
Size: 1000,
},
}
expected := mount.Mount{
Type: mount.TypeTmpfs,
Target: "/foo/bar",
TmpfsOptions: &mount.TmpfsOptions{SizeBytes: 1000},
}
mount, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
assert.NoError(t, err)
assert.Equal(t, expected, mount)
}
func TestConvertTmpfsToMountVolumeWithSource(t *testing.T) {
config := composetypes.ServiceVolumeConfig{
Type: "tmpfs",
Source: "/bar",
Target: "/foo/bar",
Tmpfs: &composetypes.ServiceVolumeTmpfs{
Size: 1000,
},
}
_, err := convertVolumeToMount(config, volumes{}, NewNamespace("foo"))
assert.EqualError(t, err, "invalid tmpfs source, source must be empty")
}

View File

@ -1,4 +1,4 @@
version: "3.6"
version: "3.5"
services:
foo:
@ -10,7 +10,7 @@ services:
foo: bar
target: foo
network: foo
cache_from:
cache_from:
- foo
- bar
labels: [FOO=BAR]
@ -249,10 +249,6 @@ services:
source: ./opt
target: /opt
consistency: cached
- type: tmpfs
target: /opt
tmpfs:
size: 10000
working_dir: /code

View File

@ -657,7 +657,6 @@ services:
context: ./web
links:
- bar
pid: host
db:
image: db
build:
@ -671,7 +670,7 @@ services:
require.NoError(t, err)
unsupported := GetUnsupportedProperties(configDetails)
assert.Equal(t, []string{"build", "links", "pid"}, unsupported)
assert.Equal(t, []string{"build", "links"}, unsupported)
}
func TestBuildProperties(t *testing.T) {
@ -1149,9 +1148,6 @@ func TestFullExample(t *testing.T) {
{Source: homeDir + "/configs", Target: "/etc/configs/", Type: "bind", ReadOnly: true},
{Source: "datavolume", Target: "/var/lib/mysql", Type: "volume"},
{Source: workingDir + "/opt", Target: "/opt", Consistency: "cached", Type: "bind"},
{Target: "/opt", Type: "tmpfs", Tmpfs: &types.ServiceVolumeTmpfs{
Size: int64(10000),
}},
},
WorkingDir: "/code",
}
@ -1223,106 +1219,6 @@ func TestFullExample(t *testing.T) {
assert.Equal(t, expectedVolumeConfig, config.Volumes)
}
func TestLoadTmpfsVolume(t *testing.T) {
config, err := loadYAML(`
version: "3.6"
services:
tmpfs:
image: nginx:latest
volumes:
- type: tmpfs
target: /app
tmpfs:
size: 10000
`)
require.NoError(t, err)
expected := types.ServiceVolumeConfig{
Target: "/app",
Type: "tmpfs",
Tmpfs: &types.ServiceVolumeTmpfs{
Size: int64(10000),
},
}
require.Len(t, config.Services, 1)
assert.Len(t, config.Services[0].Volumes, 1)
assert.Equal(t, expected, config.Services[0].Volumes[0])
}
func TestLoadTmpfsVolumeAdditionalPropertyNotAllowed(t *testing.T) {
_, err := loadYAML(`
version: "3.5"
services:
tmpfs:
image: nginx:latest
volumes:
- type: tmpfs
target: /app
tmpfs:
size: 10000
`)
require.Error(t, err)
assert.Contains(t, err.Error(), "services.tmpfs.volumes.0 Additional property tmpfs is not allowed")
}
func TestLoadTmpfsVolumeSizeCanBeZero(t *testing.T) {
config, err := loadYAML(`
version: "3.6"
services:
tmpfs:
image: nginx:latest
volumes:
- type: tmpfs
target: /app
tmpfs:
size: 0
`)
require.NoError(t, err)
expected := types.ServiceVolumeConfig{
Target: "/app",
Type: "tmpfs",
Tmpfs: &types.ServiceVolumeTmpfs{},
}
require.Len(t, config.Services, 1)
assert.Len(t, config.Services[0].Volumes, 1)
assert.Equal(t, expected, config.Services[0].Volumes[0])
}
func TestLoadTmpfsVolumeSizeMustBeGTEQZero(t *testing.T) {
_, err := loadYAML(`
version: "3.6"
services:
tmpfs:
image: nginx:latest
volumes:
- type: tmpfs
target: /app
tmpfs:
size: -1
`)
require.Error(t, err)
assert.Contains(t, err.Error(), "services.tmpfs.volumes.0.tmpfs.size Must be greater than or equal to 0")
}
func TestLoadTmpfsVolumeSizeMustBeInteger(t *testing.T) {
_, err := loadYAML(`
version: "3.6"
services:
tmpfs:
image: nginx:latest
volumes:
- type: tmpfs
target: /app
tmpfs:
size: 0.0001
`)
require.Error(t, err)
assert.Contains(t, err.Error(), "services.tmpfs.volumes.0.tmpfs.size must be a integer")
}
func serviceSort(services []types.ServiceConfig) []types.ServiceConfig {
sort.Sort(servicesByName(services))
return services

File diff suppressed because one or more lines are too long

View File

@ -1,582 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "config_schema_v3.5.json",
"type": "object",
"required": ["version"],
"properties": {
"version": {
"type": "string"
},
"services": {
"id": "#/properties/services",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/service"
}
},
"additionalProperties": false
},
"networks": {
"id": "#/properties/networks",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/network"
}
}
},
"volumes": {
"id": "#/properties/volumes",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/volume"
}
},
"additionalProperties": false
},
"secrets": {
"id": "#/properties/secrets",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/secret"
}
},
"additionalProperties": false
},
"configs": {
"id": "#/properties/configs",
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"$ref": "#/definitions/config"
}
},
"additionalProperties": false
}
},
"patternProperties": {"^x-": {}},
"additionalProperties": false,
"definitions": {
"service": {
"id": "#/definitions/service",
"type": "object",
"properties": {
"deploy": {"$ref": "#/definitions/deployment"},
"build": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"context": {"type": "string"},
"dockerfile": {"type": "string"},
"args": {"$ref": "#/definitions/list_or_dict"},
"labels": {"$ref": "#/definitions/list_or_dict"},
"cache_from": {"$ref": "#/definitions/list_of_strings"},
"network": {"type": "string"},
"target": {"type": "string"},
"shm_size": {"type": ["integer", "string"]}
},
"additionalProperties": false
}
]
},
"cap_add": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cap_drop": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"cgroup_parent": {"type": "string"},
"command": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"configs": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"uid": {"type": "string"},
"gid": {"type": "string"},
"mode": {"type": "number"}
}
}
]
}
},
"container_name": {"type": "string"},
"credential_spec": {"type": "object", "properties": {
"file": {"type": "string"},
"registry": {"type": "string"}
}},
"depends_on": {"$ref": "#/definitions/list_of_strings"},
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"dns": {"$ref": "#/definitions/string_or_list"},
"dns_search": {"$ref": "#/definitions/string_or_list"},
"domainname": {"type": "string"},
"entrypoint": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"env_file": {"$ref": "#/definitions/string_or_list"},
"environment": {"$ref": "#/definitions/list_or_dict"},
"expose": {
"type": "array",
"items": {
"type": ["string", "number"],
"format": "expose"
},
"uniqueItems": true
},
"external_links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"extra_hosts": {"$ref": "#/definitions/list_or_dict"},
"healthcheck": {"$ref": "#/definitions/healthcheck"},
"hostname": {"type": "string"},
"image": {"type": "string"},
"ipc": {"type": "string"},
"isolation": {"type": "string"},
"labels": {"$ref": "#/definitions/list_or_dict"},
"links": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"logging": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"options": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number", "null"]}
}
}
},
"additionalProperties": false
},
"mac_address": {"type": "string"},
"network_mode": {"type": "string"},
"networks": {
"oneOf": [
{"$ref": "#/definitions/list_of_strings"},
{
"type": "object",
"patternProperties": {
"^[a-zA-Z0-9._-]+$": {
"oneOf": [
{
"type": "object",
"properties": {
"aliases": {"$ref": "#/definitions/list_of_strings"},
"ipv4_address": {"type": "string"},
"ipv6_address": {"type": "string"}
},
"additionalProperties": false
},
{"type": "null"}
]
}
},
"additionalProperties": false
}
]
},
"pid": {"type": ["string", "null"]},
"ports": {
"type": "array",
"items": {
"oneOf": [
{"type": "number", "format": "ports"},
{"type": "string", "format": "ports"},
{
"type": "object",
"properties": {
"mode": {"type": "string"},
"target": {"type": "integer"},
"published": {"type": "integer"},
"protocol": {"type": "string"}
},
"additionalProperties": false
}
]
},
"uniqueItems": true
},
"privileged": {"type": "boolean"},
"read_only": {"type": "boolean"},
"restart": {"type": "string"},
"security_opt": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
"shm_size": {"type": ["number", "string"]},
"secrets": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"properties": {
"source": {"type": "string"},
"target": {"type": "string"},
"uid": {"type": "string"},
"gid": {"type": "string"},
"mode": {"type": "number"}
}
}
]
}
},
"sysctls": {"$ref": "#/definitions/list_or_dict"},
"stdin_open": {"type": "boolean"},
"stop_grace_period": {"type": "string", "format": "duration"},
"stop_signal": {"type": "string"},
"tmpfs": {"$ref": "#/definitions/string_or_list"},
"tty": {"type": "boolean"},
"ulimits": {
"type": "object",
"patternProperties": {
"^[a-z]+$": {
"oneOf": [
{"type": "integer"},
{
"type":"object",
"properties": {
"hard": {"type": "integer"},
"soft": {"type": "integer"}
},
"required": ["soft", "hard"],
"additionalProperties": false
}
]
}
}
},
"user": {"type": "string"},
"userns_mode": {"type": "string"},
"volumes": {
"type": "array",
"items": {
"oneOf": [
{"type": "string"},
{
"type": "object",
"required": ["type"],
"properties": {
"type": {"type": "string"},
"source": {"type": "string"},
"target": {"type": "string"},
"read_only": {"type": "boolean"},
"consistency": {"type": "string"},
"bind": {
"type": "object",
"properties": {
"propagation": {"type": "string"}
}
},
"volume": {
"type": "object",
"properties": {
"nocopy": {"type": "boolean"}
}
},
"tmpfs": {
"type": "object",
"properties": {
"size": {
"type": "integer",
"minimum": 0
}
}
}
},
"additionalProperties": false
}
],
"uniqueItems": true
}
},
"working_dir": {"type": "string"}
},
"additionalProperties": false
},
"healthcheck": {
"id": "#/definitions/healthcheck",
"type": "object",
"additionalProperties": false,
"properties": {
"disable": {"type": "boolean"},
"interval": {"type": "string", "format": "duration"},
"retries": {"type": "number"},
"test": {
"oneOf": [
{"type": "string"},
{"type": "array", "items": {"type": "string"}}
]
},
"timeout": {"type": "string", "format": "duration"},
"start_period": {"type": "string", "format": "duration"}
}
},
"deployment": {
"id": "#/definitions/deployment",
"type": ["object", "null"],
"properties": {
"mode": {"type": "string"},
"endpoint_mode": {"type": "string"},
"replicas": {"type": "integer"},
"labels": {"$ref": "#/definitions/list_or_dict"},
"update_config": {
"type": "object",
"properties": {
"parallelism": {"type": "integer"},
"delay": {"type": "string", "format": "duration"},
"failure_action": {"type": "string"},
"monitor": {"type": "string", "format": "duration"},
"max_failure_ratio": {"type": "number"},
"order": {"type": "string", "enum": [
"start-first", "stop-first"
]}
},
"additionalProperties": false
},
"resources": {
"type": "object",
"properties": {
"limits": {
"type": "object",
"properties": {
"cpus": {"type": "string"},
"memory": {"type": "string"}
},
"additionalProperties": false
},
"reservations": {
"type": "object",
"properties": {
"cpus": {"type": "string"},
"memory": {"type": "string"},
"generic_resources": {"$ref": "#/definitions/generic_resources"}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"restart_policy": {
"type": "object",
"properties": {
"condition": {"type": "string"},
"delay": {"type": "string", "format": "duration"},
"max_attempts": {"type": "integer"},
"window": {"type": "string", "format": "duration"}
},
"additionalProperties": false
},
"placement": {
"type": "object",
"properties": {
"constraints": {"type": "array", "items": {"type": "string"}},
"preferences": {
"type": "array",
"items": {
"type": "object",
"properties": {
"spread": {"type": "string"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
}
},
"additionalProperties": false
},
"generic_resources": {
"id": "#/definitions/generic_resources",
"type": "array",
"items": {
"type": "object",
"properties": {
"discrete_resource_spec": {
"type": "object",
"properties": {
"kind": {"type": "string"},
"value": {"type": "number"}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"network": {
"id": "#/definitions/network",
"type": ["object", "null"],
"properties": {
"name": {"type": "string"},
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"ipam": {
"type": "object",
"properties": {
"driver": {"type": "string"},
"config": {
"type": "array",
"items": {
"type": "object",
"properties": {
"subnet": {"type": "string"}
},
"additionalProperties": false
}
}
},
"additionalProperties": false
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"internal": {"type": "boolean"},
"attachable": {"type": "boolean"},
"labels": {"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false
},
"volume": {
"id": "#/definitions/volume",
"type": ["object", "null"],
"properties": {
"name": {"type": "string"},
"driver": {"type": "string"},
"driver_opts": {
"type": "object",
"patternProperties": {
"^.+$": {"type": ["string", "number"]}
}
},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
},
"additionalProperties": false
},
"labels": {"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false
},
"secret": {
"id": "#/definitions/secret",
"type": "object",
"properties": {
"name": {"type": "string"},
"file": {"type": "string"},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
}
},
"labels": {"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false
},
"config": {
"id": "#/definitions/config",
"type": "object",
"properties": {
"name": {"type": "string"},
"file": {"type": "string"},
"external": {
"type": ["boolean", "object"],
"properties": {
"name": {"type": "string"}
}
},
"labels": {"$ref": "#/definitions/list_or_dict"}
},
"additionalProperties": false
},
"string_or_list": {
"oneOf": [
{"type": "string"},
{"$ref": "#/definitions/list_of_strings"}
]
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"list_or_dict": {
"oneOf": [
{
"type": "object",
"patternProperties": {
".+": {
"type": ["string", "number", "null"]
}
},
"additionalProperties": false
},
{"type": "array", "items": {"type": "string"}, "uniqueItems": true}
]
},
"constraints": {
"service": {
"id": "#/definitions/constraints/service",
"anyOf": [
{"required": ["build"]},
{"required": ["image"]}
],
"properties": {
"build": {
"required": ["context"]
}
}
}
}
}
}

View File

@ -17,12 +17,12 @@ var UnsupportedProperties = []string{
"links",
"mac_address",
"network_mode",
"pid",
"privileged",
"restart",
"security_opt",
"shm_size",
"sysctls",
"tmpfs",
"ulimits",
"userns_mode",
}
@ -283,7 +283,6 @@ type ServiceVolumeConfig struct {
Consistency string
Bind *ServiceVolumeBind
Volume *ServiceVolumeVolume
Tmpfs *ServiceVolumeTmpfs
}
// ServiceVolumeBind are options for a service volume of type bind
@ -296,11 +295,6 @@ type ServiceVolumeVolume struct {
NoCopy bool `mapstructure:"nocopy"`
}
// ServiceVolumeTmpfs are options for a service volume of type tmpfs
type ServiceVolumeTmpfs struct {
Size int64
}
// FileReferenceConfig for a reference to a swarm file object
type FileReferenceConfig struct {
Source string

View File

@ -44,8 +44,6 @@ type ConfigFile struct {
NodesFormat string `json:"nodesFormat,omitempty"`
PruneFilters []string `json:"pruneFilters,omitempty"`
Proxies map[string]ProxyConfig `json:"proxies,omitempty"`
Experimental string `json:"experimental,omitempty"`
Orchestrator string `json:"orchestrator,omitempty"`
}
// ProxyConfig contains proxy configuration settings

View File

@ -30,13 +30,12 @@ var (
// CommonOptions are options common to both the client and the daemon.
type CommonOptions struct {
Debug bool
Hosts []string
Orchestrator string
LogLevel string
TLS bool
TLSVerify bool
TLSOptions *tlsconfig.Options
Debug bool
Hosts []string
LogLevel string
TLS bool
TLSVerify bool
TLSOptions *tlsconfig.Options
}
// NewCommonOptions returns a new CommonOptions
@ -54,8 +53,6 @@ func (commonOpts *CommonOptions) InstallFlags(flags *pflag.FlagSet) {
flags.StringVarP(&commonOpts.LogLevel, "log-level", "l", "info", `Set the logging level ("debug"|"info"|"warn"|"error"|"fatal")`)
flags.BoolVar(&commonOpts.TLS, "tls", false, "Use TLS; implied by --tlsverify")
flags.BoolVar(&commonOpts.TLSVerify, FlagTLSVerify, dockerTLSVerify, "Use TLS and verify the remote")
flags.StringVar(&commonOpts.Orchestrator, "orchestrator", "", "Which orchestrator to use with the docker cli (swarm|kubernetes) (default swarm) (experimental)")
flags.SetAnnotation("orchestrator", "experimentalCLI", nil)
// TODO use flag flags.String("identity"}, "i", "", "Path to libtrust key file")

View File

@ -1,147 +0,0 @@
package store
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/docker/cli/cli/manifest/types"
"github.com/docker/distribution/reference"
)
// Store manages local storage of image distribution manifests
type Store interface {
Remove(listRef reference.Reference) error
Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error)
GetList(listRef reference.Reference) ([]types.ImageManifest, error)
Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error
}
// fsStore manages manifest files stored on the local filesystem
type fsStore struct {
root string
}
// NewStore returns a new store for a local file path
func NewStore(root string) Store {
return &fsStore{root: root}
}
// Remove a manifest list from local storage
func (s *fsStore) Remove(listRef reference.Reference) error {
path := filepath.Join(s.root, makeFilesafeName(listRef.String()))
return os.RemoveAll(path)
}
// Get returns the local manifest
func (s *fsStore) Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error) {
filename := manifestToFilename(s.root, listRef.String(), manifest.String())
return s.getFromFilename(manifest, filename)
}
func (s *fsStore) getFromFilename(ref reference.Reference, filename string) (types.ImageManifest, error) {
bytes, err := ioutil.ReadFile(filename)
switch {
case os.IsNotExist(err):
return types.ImageManifest{}, newNotFoundError(ref.String())
case err != nil:
return types.ImageManifest{}, err
}
var manifestInfo types.ImageManifest
return manifestInfo, json.Unmarshal(bytes, &manifestInfo)
}
// GetList returns all the local manifests for a transaction
func (s *fsStore) GetList(listRef reference.Reference) ([]types.ImageManifest, error) {
filenames, err := s.listManifests(listRef.String())
switch {
case err != nil:
return nil, err
case filenames == nil:
return nil, newNotFoundError(listRef.String())
}
manifests := []types.ImageManifest{}
for _, filename := range filenames {
filename = filepath.Join(s.root, makeFilesafeName(listRef.String()), filename)
manifest, err := s.getFromFilename(listRef, filename)
if err != nil {
return nil, err
}
manifests = append(manifests, manifest)
}
return manifests, nil
}
// listManifests stored in a transaction
func (s *fsStore) listManifests(transaction string) ([]string, error) {
transactionDir := filepath.Join(s.root, makeFilesafeName(transaction))
fileInfos, err := ioutil.ReadDir(transactionDir)
switch {
case os.IsNotExist(err):
return nil, nil
case err != nil:
return nil, err
}
filenames := []string{}
for _, info := range fileInfos {
filenames = append(filenames, info.Name())
}
return filenames, nil
}
// Save a manifest as part of a local manifest list
func (s *fsStore) Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error {
if err := s.createManifestListDirectory(listRef.String()); err != nil {
return err
}
filename := manifestToFilename(s.root, listRef.String(), manifest.String())
bytes, err := json.Marshal(image)
if err != nil {
return err
}
return ioutil.WriteFile(filename, bytes, 0644)
}
func (s *fsStore) createManifestListDirectory(transaction string) error {
path := filepath.Join(s.root, makeFilesafeName(transaction))
return os.MkdirAll(path, 0755)
}
func manifestToFilename(root, manifestList, manifest string) string {
return filepath.Join(root, makeFilesafeName(manifestList), makeFilesafeName(manifest))
}
func makeFilesafeName(ref string) string {
fileName := strings.Replace(ref, ":", "-", -1)
return strings.Replace(fileName, "/", "_", -1)
}
type notFoundError struct {
object string
}
func newNotFoundError(ref string) *notFoundError {
return &notFoundError{object: ref}
}
func (n *notFoundError) Error() string {
return fmt.Sprintf("No such manifest: %s", n.object)
}
// NotFound interface
func (n *notFoundError) NotFound() {}
// IsNotFound returns true if the error is a not found error
func IsNotFound(err error) bool {
_, ok := err.(notFound)
return ok
}
type notFound interface {
NotFound()
}

Some files were not shown because too many files have changed in this diff Show More