Compare commits
216 Commits
v17.12.0-c
...
v18.01.0-c
| Author | SHA1 | Date | |
|---|---|---|---|
| 44e2def671 | |||
| f0e6477914 | |||
| a971796cc5 | |||
| 4dd1acceb6 | |||
| 85c031e751 | |||
| 41aec7703b | |||
| e03fd4a1dd | |||
| 252489e04a | |||
| 5742bd3ccf | |||
| e3571070d5 | |||
| efb4eb2d0e | |||
| da47568e0d | |||
| e2368ae63b | |||
| 707c5a8bd6 | |||
| 9f589817fd | |||
| 17341a3ddc | |||
| 01e7ddaec8 | |||
| 3e3e540e8d | |||
| 2bb414da6f | |||
| 9c63a8a5b6 | |||
| 257633843b | |||
| bcb9583e82 | |||
| ce67d6e9b7 | |||
| 4360eb4024 | |||
| f8a7c9aa17 | |||
| 7cbef79c0e | |||
| 49ff2398e6 | |||
| 5965f4a425 | |||
| 3b99478053 | |||
| 42bcb27498 | |||
| 8e9edf70c7 | |||
| 5b7cd70f03 | |||
| 6635d18f37 | |||
| fa250e5c10 | |||
| ae19592ca8 | |||
| 6aa9598694 | |||
| 25c9e80a84 | |||
| 414d05d8c4 | |||
| cfc9623c01 | |||
| 4b57d7f1d3 | |||
| 31e8b1c077 | |||
| f1638ade2e | |||
| 8befd2d809 | |||
| 2a34102708 | |||
| dd0cbe7272 | |||
| e25f9d0953 | |||
| 9bf811bf85 | |||
| f63a46268d | |||
| f46e42c6ec | |||
| 0ba6b0ad15 | |||
| cb8fda044c | |||
| effda473ed | |||
| d7b5fca8a7 | |||
| d05eafef63 | |||
| 486a48d270 | |||
| c52c03e94f | |||
| d1cf6ac921 | |||
| 84f5ef8c85 | |||
| 40a74c51c6 | |||
| dcfd719f61 | |||
| f4626b5de8 | |||
| 1d466a29ee | |||
| 65271c3145 | |||
| b5273bceed | |||
| 507de4363e | |||
| 1e23b82bb7 | |||
| baa35d70b2 | |||
| cbbf94a252 | |||
| 1ebc428804 | |||
| 4dba81f210 | |||
| 4b3a19ed1e | |||
| c88231e80c | |||
| 0a947da27f | |||
| 4cbf52bf35 | |||
| ed8df8d90f | |||
| 2ac4f6cd3a | |||
| 2a46f74679 | |||
| 7c289486a2 | |||
| 751e6f56a0 | |||
| ee811e7a56 | |||
| b1a25d1e15 | |||
| 3e15d0b417 | |||
| 618f1e2388 | |||
| 7fa76a7822 | |||
| 87487ae8dd | |||
| b8be0fb3b8 | |||
| aa8a79c526 | |||
| 1f700eda21 | |||
| 02056dd212 | |||
| c61589a0f0 | |||
| d109b596b8 | |||
| 2af5d43021 | |||
| e0ed436e6c | |||
| e190404195 | |||
| 2a820c0f5f | |||
| 88269f42ba | |||
| e5199a0f6e | |||
| 3b758ede4d | |||
| 847487f5da | |||
| 50de5d926a | |||
| f0d368cb33 | |||
| 3f7322c602 | |||
| 760baaebbb | |||
| b225d1b061 | |||
| 0acf31e126 | |||
| 92961327ba | |||
| f1ba211fd1 | |||
| 822fd111b3 | |||
| 50a3444995 | |||
| 04606e240e | |||
| 7a36094339 | |||
| 3e7449957b | |||
| 048a0b941a | |||
| c5f1cdf92c | |||
| df88bdc9c9 | |||
| fd08bae89c | |||
| ef4dfd2f67 | |||
| 7e2ee76b7e | |||
| 15c0c88784 | |||
| f3becf1d35 | |||
| 343f73cf3d | |||
| 435d4943db | |||
| ef43ef8bc9 | |||
| ebbba75d0a | |||
| a67c7bc0ee | |||
| e1fb11f9ea | |||
| fbc087df98 | |||
| b16d6e9f15 | |||
| c687cf1b38 | |||
| 72249a1f2e | |||
| 5e5a802f6b | |||
| 7ad7bb59be | |||
| ba6f88e84d | |||
| edf70bdb82 | |||
| 58f0e7706b | |||
| b845d8f122 | |||
| 600655d01d | |||
| eeeede3c5f | |||
| e653cda753 | |||
| 14b537bf91 | |||
| 5b55002724 | |||
| 3ac0d82798 | |||
| 57c4619d75 | |||
| a49939a820 | |||
| 5dfe871bd5 | |||
| feccd72a49 | |||
| 63a4263b5f | |||
| 0ef0e17180 | |||
| 5b033215e5 | |||
| 9626c25157 | |||
| f76cba5158 | |||
| 05b6e11768 | |||
| 4a3429a762 | |||
| e5dd0c19d9 | |||
| 78fc682ae3 | |||
| 1fd0a9d87b | |||
| f1310bd820 | |||
| faf7b2e58e | |||
| 4ee5590aac | |||
| dc415e6d5e | |||
| 75b526540d | |||
| 0810853589 | |||
| ea904b1a94 | |||
| ee5a10619c | |||
| a85f0c817f | |||
| 467a8bb243 | |||
| 6959a6fac2 | |||
| aa0cf4fa09 | |||
| a4823e1268 | |||
| 74ffa93aa3 | |||
| 5e0f8059c9 | |||
| d81d63c977 | |||
| ec88074283 | |||
| 1aec0724c8 | |||
| c8a642f128 | |||
| 35175d99d5 | |||
| 5b1b2d3dcf | |||
| 209f0e3356 | |||
| 90cb59fabc | |||
| d586c2cf19 | |||
| 6c564b6785 | |||
| eae25b27ea | |||
| ef0d4a2bec | |||
| 1e22870b24 | |||
| 29bb8562cc | |||
| ee263ca445 | |||
| 099ae9b4b0 | |||
| 1305053a0b | |||
| 443795270f | |||
| 6f58b7f1de | |||
| 16d5b20e33 | |||
| 6fdef05177 | |||
| 27e78af99a | |||
| 141bc0558d | |||
| cd7f41a503 | |||
| 04040622fb | |||
| 7b0f6565c5 | |||
| 376d2e9872 | |||
| 6edf75f3d5 | |||
| 782ef5e9db | |||
| fd25684fc8 | |||
| c3b54b0b2b | |||
| 17ee87b9ed | |||
| 4ef94db1ce | |||
| 90f62259ac | |||
| 4343c7deaa | |||
| 43ace3ab68 | |||
| 6a7f05996f | |||
| 090ce73a5d | |||
| 74dc65818c | |||
| 9b7822f4af | |||
| 1be15401b4 | |||
| a017ada68b | |||
| f1cfd038e2 | |||
| 25c006f1cc | |||
| d07549fe22 |
100
CHANGELOG.md
100
CHANGELOG.md
@ -5,89 +5,59 @@ 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.01.0-ce (2018-01-DD)
|
||||
|
||||
### Builder
|
||||
|
||||
- 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)
|
||||
* Fix files not being deleted if user-namespaces are enabled [moby/moby#35822](https://github.com/moby/moby/pull/35822)
|
||||
- Add support for expanding environment-variables in `docker commit --change ...` [moby/moby#35582](https://github.com/moby/moby/pull/35582)
|
||||
|
||||
### Client
|
||||
|
||||
* 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)
|
||||
- Fix external networks in stacks [docker/cli#743](https://github.com/docker/cli/pull/743)
|
||||
* Return errors from client in stack deploy configs [docker/cli#757](https://github.com/docker/cli/pull/757)
|
||||
- Fix description of filter flag in prune commands [docker/cli#774](https://github.com/docker/cli/pull/774)
|
||||
+ Add "pid" to unsupported options list [docker/cli#768](https://github.com/docker/cli/pull/768)
|
||||
+ Add support for experimental Cli configuration [docker/cli#758](https://github.com/docker/cli/pull/758)
|
||||
+ Add support for generic resources to bash completion [docker/cli#749](https://github.com/docker/cli/pull/749)
|
||||
- Fix error in zsh completion script for docker exec [docker/cli#751](https://github.com/docker/cli/pull/751)
|
||||
+ Add a debug message when client closes websocket attach connection [moby/moby#35720](https://github.com/moby/moby/pull/35720)
|
||||
- Fix bash completion for `"docker swarm"` [docker/cli#772](https://github.com/docker/cli/pull/772)
|
||||
|
||||
|
||||
### Documentation
|
||||
* Correct references to `--publish` long syntax in docs [docker/cli#746](https://github.com/docker/cli/pull/746)
|
||||
* Corrected descriptions for MAC_ADMIN and MAC_OVERRIDE [docker/cli#761](https://github.com/docker/cli/pull/761)
|
||||
* Updated developer doc to explain external CLI [moby/moby#35681](https://github.com/moby/moby/pull/35681)
|
||||
- Fix `"on-failure"` restart policy being documented as "failure" [docker/cli#754](https://github.com/docker/cli/pull/754)
|
||||
- Fix anchors to "Storage driver options" [docker/cli#748](https://github.com/docker/cli/pull/748)
|
||||
|
||||
* Update API version history for 1.35 [moby/moby#35724](https://github.com/moby/moby/pull/35724)
|
||||
### Experimental
|
||||
|
||||
+ Add kubernetes support to `docker stack` command [docker/cli#721](https://github.com/docker/cli/pull/721)
|
||||
* Don't append the container id to custom directory checkpoints. [moby/moby#35694](https://github.com/moby/moby/pull/35694)
|
||||
|
||||
### Logging
|
||||
|
||||
* 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)
|
||||
* Update Graylog2/go-gelf [moby/moby#35765](https://github.com/moby/moby/pull/35765)
|
||||
* Fix daemon crash when using the GELF log driver over TCP when the GELF server goes down [moby/moby#35765](https://github.com/moby/moby/pull/35765)
|
||||
- Fix awslogs batch size calculation for large logs [moby/moby#35726](https://github.com/moby/moby/pull/35726)
|
||||
|
||||
### Networking
|
||||
|
||||
* 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)
|
||||
- Clean up node management logic [docker/libnetwork#2036](https://github.com/docker/libnetwork/pull/2036)
|
||||
- Windows: Fix to allow docker service to start on Windows VM [docker/libnetwork#1916](https://github.com/docker/libnetwork/pull/1916)
|
||||
- Fix for docker intercepting DNS requests on ICS network [docker/libnetwork#2014](https://github.com/docker/libnetwork/pull/2014)
|
||||
+ Windows: Added a new network creation driver option [docker/libnetwork#2021](https://github.com/docker/libnetwork/pull/2021)
|
||||
|
||||
|
||||
### Runtime
|
||||
|
||||
* 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)
|
||||
* Disallow using legacy (v1) registries [moby/moby#35751](https://github.com/moby/moby/pull/35751) and [docker/cli#747](https://github.com/docker/cli/pull/747)
|
||||
* Re-validate Mounts on container start [moby/moby#35833](https://github.com/moby/moby/pull/35833)
|
||||
- Fix overlay2 storage driver inside a user namespace [moby/moby#35794](https://github.com/moby/moby/pull/35794)
|
||||
* Zfs: fix busy error on container stop [moby/moby#35674](https://github.com/moby/moby/pull/35674)
|
||||
- Fix #35843 regression on health check workingdir [moby/moby#35845](https://github.com/moby/moby/pull/35845)
|
||||
- Fix VFS graph driver failure to initialize because of failure to setup fs quota [moby/moby#35827](https://github.com/moby/moby/pull/35827)
|
||||
- Fix containerd events being processed twice [moby/moby#35896](https://github.com/moby/moby/pull/35896)
|
||||
|
||||
### Swarm Mode
|
||||
|
||||
+ Added support for swarm service isolation mode [moby/moby#34424](https://github.com/moby/moby/pull/34424)
|
||||
|
||||
### Packaging
|
||||
|
||||
+ Add Packaging for Fedora 27 [docker/docker-ce-packaging#59](https://github.com/docker/docker-ce-packaging/pull/59)
|
||||
* Change 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)
|
||||
* Pass Version to engine static builds [docker/docker-ce-packaging#70](https://github.com/docker/docker-ce-packaging/pull/70)
|
||||
- Fix published ports not being updated if a service has the same number of host-mode published ports with Published Port 0 [docker/swarmkit#2376](https://github.com/docker/swarmkit/pull/2376)
|
||||
* Make the task termination order deterministic [docker/swarmkit#2265](https://github.com/docker/swarmkit/pull/2265)
|
||||
|
||||
472
components/cli/.mailmap
Normal file
472
components/cli/.mailmap
Normal file
@ -0,0 +1,472 @@
|
||||
# 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>
|
||||
|
||||
627
components/cli/AUTHORS
Normal file
627
components/cli/AUTHORS
Normal file
@ -0,0 +1,627 @@
|
||||
# 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>
|
||||
@ -54,6 +54,10 @@ vendor: vendor.conf ## check that vendor matches vendor.conf
|
||||
vndr 2> /dev/null
|
||||
scripts/validate/check-git-diff vendor
|
||||
|
||||
.PHONY: authors
|
||||
authors: ## generate AUTHORS file from git history
|
||||
scripts/docs/generate-authors.sh
|
||||
|
||||
.PHONY: manpages
|
||||
manpages: ## generate man pages from go source and markdown
|
||||
scripts/docs/generate-man.sh
|
||||
|
||||
@ -1 +1 @@
|
||||
17.12.0-ce-rc3
|
||||
18.01.0-ce-rc1
|
||||
|
||||
@ -42,24 +42,26 @@ type Cli interface {
|
||||
SetIn(in *InStream)
|
||||
ConfigFile() *configfile.ConfigFile
|
||||
ServerInfo() ServerInfo
|
||||
ClientInfo() ClientInfo
|
||||
NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
|
||||
DefaultVersion() string
|
||||
}
|
||||
|
||||
// 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
|
||||
defaultVersion string
|
||||
server ServerInfo
|
||||
configFile *configfile.ConfigFile
|
||||
in *InStream
|
||||
out *OutStream
|
||||
err io.Writer
|
||||
client client.APIClient
|
||||
serverInfo ServerInfo
|
||||
clientInfo ClientInfo
|
||||
}
|
||||
|
||||
// DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified.
|
||||
func (cli *DockerCli) DefaultVersion() string {
|
||||
return cli.defaultVersion
|
||||
return cli.clientInfo.DefaultVersion
|
||||
}
|
||||
|
||||
// Client returns the APIClient
|
||||
@ -104,7 +106,12 @@ 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.server
|
||||
return cli.serverInfo
|
||||
}
|
||||
|
||||
// ClientInfo returns the client details for the cli
|
||||
func (cli *DockerCli) ClientInfo() ClientInfo {
|
||||
return cli.clientInfo
|
||||
}
|
||||
|
||||
// Initialize the dockerCli runs initialization that must happen after command
|
||||
@ -125,17 +132,36 @@ 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 (cli *DockerCli) initializeFromClient() {
|
||||
cli.defaultVersion = cli.client.ClientVersion()
|
||||
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() {
|
||||
ping, err := cli.client.Ping(context.Background())
|
||||
if err != nil {
|
||||
// Default to true if we fail to connect to daemon
|
||||
cli.server = ServerInfo{HasExperimental: true}
|
||||
cli.serverInfo = ServerInfo{HasExperimental: true}
|
||||
|
||||
if ping.APIVersion != "" {
|
||||
cli.client.NegotiateAPIVersionPing(ping)
|
||||
@ -143,7 +169,7 @@ func (cli *DockerCli) initializeFromClient() {
|
||||
return
|
||||
}
|
||||
|
||||
cli.server = ServerInfo{
|
||||
cli.serverInfo = ServerInfo{
|
||||
HasExperimental: ping.Experimental,
|
||||
OSType: ping.OSType,
|
||||
}
|
||||
@ -176,6 +202,18 @@ 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}
|
||||
|
||||
@ -1,17 +1,18 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"crypto/x509"
|
||||
|
||||
cliconfig "github.com/docker/cli/cli/config"
|
||||
"github.com/docker/cli/cli/config/configfile"
|
||||
"github.com/docker/cli/cli/flags"
|
||||
"github.com/docker/cli/internal/test/testutil"
|
||||
"github.com/docker/docker/api"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/gotestyourself/gotestyourself/fs"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -124,13 +125,155 @@ func TestInitializeFromClient(t *testing.T) {
|
||||
|
||||
cli := &DockerCli{client: apiclient}
|
||||
cli.initializeFromClient()
|
||||
assert.Equal(t, defaultVersion, cli.defaultVersion)
|
||||
assert.Equal(t, testcase.expectedServer, cli.server)
|
||||
assert.Equal(t, testcase.expectedServer, cli.serverInfo)
|
||||
assert.Equal(t, testcase.negotiated, apiclient.negotiated)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestExperimentalCLI(t *testing.T) {
|
||||
defaultVersion := "v1.55"
|
||||
|
||||
var testcases = []struct {
|
||||
doc string
|
||||
configfile string
|
||||
expectedExperimentalCLI bool
|
||||
}{
|
||||
{
|
||||
doc: "default",
|
||||
configfile: `{}`,
|
||||
expectedExperimentalCLI: false,
|
||||
},
|
||||
{
|
||||
doc: "experimental",
|
||||
configfile: `{
|
||||
"experimental": "enabled"
|
||||
}`,
|
||||
expectedExperimentalCLI: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
|
||||
defer dir.Remove()
|
||||
apiclient := &fakeClient{
|
||||
version: defaultVersion,
|
||||
}
|
||||
|
||||
cli := &DockerCli{client: apiclient, err: os.Stderr}
|
||||
cliconfig.SetDir(dir.Path())
|
||||
err := cli.Initialize(flags.NewClientOptions())
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testcase.expectedExperimentalCLI, cli.ClientInfo().HasExperimental)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrchestratorSwitch(t *testing.T) {
|
||||
defaultVersion := "v0.00"
|
||||
|
||||
var testcases = []struct {
|
||||
doc string
|
||||
configfile string
|
||||
envOrchestrator string
|
||||
flagOrchestrator string
|
||||
expectedOrchestrator string
|
||||
expectedKubernetes bool
|
||||
}{
|
||||
{
|
||||
doc: "default",
|
||||
configfile: `{
|
||||
"experimental": "enabled"
|
||||
}`,
|
||||
expectedOrchestrator: "swarm",
|
||||
expectedKubernetes: false,
|
||||
},
|
||||
{
|
||||
doc: "kubernetesIsExperimental",
|
||||
configfile: `{
|
||||
"experimental": "disabled",
|
||||
"orchestrator": "kubernetes"
|
||||
}`,
|
||||
envOrchestrator: "kubernetes",
|
||||
flagOrchestrator: "kubernetes",
|
||||
expectedOrchestrator: "swarm",
|
||||
expectedKubernetes: false,
|
||||
},
|
||||
{
|
||||
doc: "kubernetesConfigFile",
|
||||
configfile: `{
|
||||
"experimental": "enabled",
|
||||
"orchestrator": "kubernetes"
|
||||
}`,
|
||||
expectedOrchestrator: "kubernetes",
|
||||
expectedKubernetes: true,
|
||||
},
|
||||
{
|
||||
doc: "kubernetesEnv",
|
||||
configfile: `{
|
||||
"experimental": "enabled"
|
||||
}`,
|
||||
envOrchestrator: "kubernetes",
|
||||
expectedOrchestrator: "kubernetes",
|
||||
expectedKubernetes: true,
|
||||
},
|
||||
{
|
||||
doc: "kubernetesFlag",
|
||||
configfile: `{
|
||||
"experimental": "enabled"
|
||||
}`,
|
||||
flagOrchestrator: "kubernetes",
|
||||
expectedOrchestrator: "kubernetes",
|
||||
expectedKubernetes: true,
|
||||
},
|
||||
{
|
||||
doc: "envOverridesConfigFile",
|
||||
configfile: `{
|
||||
"experimental": "enabled",
|
||||
"orchestrator": "kubernetes"
|
||||
}`,
|
||||
envOrchestrator: "swarm",
|
||||
expectedOrchestrator: "swarm",
|
||||
expectedKubernetes: false,
|
||||
},
|
||||
{
|
||||
doc: "flagOverridesEnv",
|
||||
configfile: `{
|
||||
"experimental": "enabled"
|
||||
}`,
|
||||
envOrchestrator: "kubernetes",
|
||||
flagOrchestrator: "swarm",
|
||||
expectedOrchestrator: "swarm",
|
||||
expectedKubernetes: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.doc, func(t *testing.T) {
|
||||
dir := fs.NewDir(t, testcase.doc, fs.WithFile("config.json", testcase.configfile))
|
||||
defer dir.Remove()
|
||||
apiclient := &fakeClient{
|
||||
version: defaultVersion,
|
||||
}
|
||||
if testcase.envOrchestrator != "" {
|
||||
defer patchEnvVariable(t, "DOCKER_ORCHESTRATOR", testcase.envOrchestrator)()
|
||||
}
|
||||
|
||||
cli := &DockerCli{client: apiclient, err: os.Stderr}
|
||||
cliconfig.SetDir(dir.Path())
|
||||
options := flags.NewClientOptions()
|
||||
if testcase.flagOrchestrator != "" {
|
||||
options.Common.Orchestrator = testcase.flagOrchestrator
|
||||
}
|
||||
err := cli.Initialize(options)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, testcase.expectedKubernetes, cli.ClientInfo().HasKubernetes())
|
||||
assert.Equal(t, testcase.expectedOrchestrator, string(cli.ClientInfo().Orchestrator))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetClientWithPassword(t *testing.T) {
|
||||
expected := "password"
|
||||
|
||||
|
||||
@ -10,11 +10,14 @@ 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"},
|
||||
Use: "config",
|
||||
Short: "Manage Docker configs",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{
|
||||
"version": "1.30",
|
||||
"swarm": "",
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newConfigListCommand(dockerCli),
|
||||
|
||||
@ -14,11 +14,14 @@ 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"},
|
||||
Use: "node",
|
||||
Short: "Manage Swarm nodes",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{
|
||||
"version": "1.24",
|
||||
"swarm": "",
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newDemoteCommand(dockerCli),
|
||||
|
||||
59
components/cli/cli/command/orchestrator.go
Normal file
59
components/cli/cli/command/orchestrator.go
Normal file
@ -0,0 +1,59 @@
|
||||
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", "k8s":
|
||||
return OrchestratorKubernetes
|
||||
case "swarm", "swarmkit":
|
||||
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
|
||||
}
|
||||
@ -10,11 +10,14 @@ 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"},
|
||||
Use: "secret",
|
||||
Short: "Manage Docker secrets",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{
|
||||
"version": "1.25",
|
||||
"swarm": "",
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newSecretListCommand(dockerCli),
|
||||
|
||||
@ -10,11 +10,14 @@ 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"},
|
||||
Use: "service",
|
||||
Short: "Manage services",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{
|
||||
"version": "1.24",
|
||||
"swarm": "",
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newCreateCommand(dockerCli),
|
||||
|
||||
@ -18,10 +18,17 @@ func NewStackCommand(dockerCli command.Cli) *cobra.Command {
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@ -1,36 +1,16 @@
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"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/docker/cli/cli/command/stack/kubernetes"
|
||||
"github.com/docker/cli/cli/command/stack/options"
|
||||
"github.com/docker/cli/cli/command/stack/swarm"
|
||||
"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 deployOptions
|
||||
var opts options.Deploy
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "deploy [OPTIONS] STACK",
|
||||
@ -38,85 +18,32 @@ 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]
|
||||
return runDeploy(dockerCli, opts)
|
||||
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)
|
||||
},
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
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.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")
|
||||
flags.SetAnnotation("prune", "version", []string{"1.27"})
|
||||
flags.StringVar(&opts.resolveImage, "resolve-image", resolveImageAlways,
|
||||
`Query the registry to resolve image digest and supported platforms ("`+resolveImageAlways+`"|"`+resolveImageChanged+`"|"`+resolveImageNever+`")`)
|
||||
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.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)
|
||||
}
|
||||
|
||||
32
components/cli/cli/command/stack/kubernetes/check.go
Normal file
32
components/cli/cli/command/stack/kubernetes/check.go
Normal file
@ -0,0 +1,32 @@
|
||||
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)
|
||||
}
|
||||
76
components/cli/cli/command/stack/kubernetes/cli.go
Normal file
76
components/cli/cli/command/stack/kubernetes/cli.go
Normal file
@ -0,0 +1,76 @@
|
||||
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.PersistentFlags().Changed("namespace") {
|
||||
cli.kubeNamespace, err = cmd.PersistentFlags().GetString("namespace")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
kubeConfig := ""
|
||||
if cmd.PersistentFlags().Changed("kubeconfig") {
|
||||
kubeConfig, err = cmd.PersistentFlags().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
|
||||
}
|
||||
71
components/cli/cli/command/stack/kubernetes/client.go
Normal file
71
components/cli/cli/command/stack/kubernetes/client.go
Normal file
@ -0,0 +1,71 @@
|
||||
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)
|
||||
}
|
||||
54
components/cli/cli/command/stack/kubernetes/collision.go
Normal file
54
components/cli/cli/command/stack/kubernetes/collision.go
Normal file
@ -0,0 +1,54 @@
|
||||
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
|
||||
}
|
||||
174
components/cli/cli/command/stack/kubernetes/conversion.go
Normal file
174
components/cli/cli/command/stack/kubernetes/conversion.go
Normal file
@ -0,0 +1,174 @@
|
||||
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
|
||||
}
|
||||
41
components/cli/cli/command/stack/kubernetes/convert.go
Normal file
41
components/cli/cli/command/stack/kubernetes/convert.go
Normal file
@ -0,0 +1,41 @@
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
134
components/cli/cli/command/stack/kubernetes/deploy.go
Normal file
134
components/cli/cli/command/stack/kubernetes/deploy.go
Normal file
@ -0,0 +1,134 @@
|
||||
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
|
||||
}
|
||||
74
components/cli/cli/command/stack/kubernetes/list.go
Normal file
74
components/cli/cli/command/stack/kubernetes/list.go
Normal file
@ -0,0 +1,74 @@
|
||||
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,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
169
components/cli/cli/command/stack/kubernetes/loader.go
Normal file
169
components/cli/cli/command/stack/kubernetes/loader.go
Normal file
@ -0,0 +1,169 @@
|
||||
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
|
||||
}
|
||||
34
components/cli/cli/command/stack/kubernetes/loader_test.go
Normal file
34
components/cli/cli/command/stack/kubernetes/loader_test.go
Normal file
@ -0,0 +1,34 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
106
components/cli/cli/command/stack/kubernetes/ps.go
Normal file
106
components/cli/cli/command/stack/kubernetes/ps.go
Normal file
@ -0,0 +1,106 @@
|
||||
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 }
|
||||
}
|
||||
25
components/cli/cli/command/stack/kubernetes/remove.go
Normal file
25
components/cli/cli/command/stack/kubernetes/remove.go
Normal file
@ -0,0 +1,25 @@
|
||||
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
|
||||
}
|
||||
64
components/cli/cli/command/stack/kubernetes/services.go
Normal file
64
components/cli/cli/command/stack/kubernetes/services.go
Normal file
@ -0,0 +1,64 @@
|
||||
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)
|
||||
}
|
||||
117
components/cli/cli/command/stack/kubernetes/watcher.go
Normal file
117
components/cli/cli/command/stack/kubernetes/watcher.go
Normal file
@ -0,0 +1,117 @@
|
||||
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"
|
||||
}
|
||||
@ -1,26 +1,16 @@
|
||||
package stack
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"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/docker/cli/cli/command/stack/kubernetes"
|
||||
"github.com/docker/cli/cli/command/stack/options"
|
||||
"github.com/docker/cli/cli/command/stack/swarm"
|
||||
"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 := listOptions{}
|
||||
opts := options.List{}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "ls",
|
||||
@ -28,69 +18,18 @@ func newListCommand(dockerCli command.Cli) *cobra.Command {
|
||||
Short: "List stacks",
|
||||
Args: cli.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runList(dockerCli, opts)
|
||||
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)
|
||||
},
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
41
components/cli/cli/command/stack/options/opts.go
Normal file
41
components/cli/cli/command/stack/options/opts.go
Normal file
@ -0,0 +1,41 @@
|
||||
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
|
||||
}
|
||||
@ -1,51 +0,0 @@
|
||||
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
|
||||
}
|
||||
@ -1,69 +1,41 @@
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"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/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/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 {
|
||||
options := psOptions{filter: opts.NewFilterOpt()}
|
||||
opts := options.PS{Filter: cliopts.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 {
|
||||
options.namespace = args[0]
|
||||
return runPS(dockerCli, options)
|
||||
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)
|
||||
},
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
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")
|
||||
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")
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@ -1,26 +1,16 @@
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"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/docker/cli/cli/command/stack/kubernetes"
|
||||
"github.com/docker/cli/cli/command/stack/options"
|
||||
"github.com/docker/cli/cli/command/stack/swarm"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type removeOptions struct {
|
||||
namespaces []string
|
||||
}
|
||||
|
||||
func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts removeOptions
|
||||
var opts options.Remove
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "rm STACK [STACK...]",
|
||||
@ -28,134 +18,16 @@ 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
|
||||
return runRemove(dockerCli, opts)
|
||||
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)
|
||||
},
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
@ -1,94 +1,39 @@
|
||||
package stack
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/docker/cli/cli"
|
||||
"github.com/docker/cli/cli/command"
|
||||
"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/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/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 {
|
||||
options := servicesOptions{filter: opts.NewFilterOpt()}
|
||||
opts := options.Services{Filter: cliopts.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 {
|
||||
options.namespace = args[0]
|
||||
return runServices(dockerCli, options)
|
||||
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)
|
||||
},
|
||||
}
|
||||
flags := cmd.Flags()
|
||||
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")
|
||||
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)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
239
components/cli/cli/command/stack/swarm/client_test.go
Normal file
239
components/cli/cli/command/stack/swarm/client_test.go
Normal file
@ -0,0 +1,239 @@
|
||||
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
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/cli/compose/convert"
|
||||
88
components/cli/cli/command/stack/swarm/deploy.go
Normal file
88
components/cli/cli/command/stack/swarm/deploy.go
Normal file
@ -0,0 +1,88 @@
|
||||
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)
|
||||
}
|
||||
@ -1,16 +1,23 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
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 deployOptions) error {
|
||||
bundle, err := loadBundlefile(dockerCli.Err(), opts.namespace, opts.bundlefile)
|
||||
func deployBundle(ctx context.Context, dockerCli command.Cli, opts options.Deploy) error {
|
||||
bundle, err := loadBundlefile(dockerCli.Err(), opts.Namespace, opts.Bundlefile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -19,9 +26,9 @@ func deployBundle(ctx context.Context, dockerCli command.Cli, opts deployOptions
|
||||
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{}{}
|
||||
@ -87,5 +94,31 @@ func deployBundle(ctx context.Context, dockerCli command.Cli, opts deployOptions
|
||||
if err := createNetworks(ctx, dockerCli, namespace, networks); 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 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
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -10,6 +10,7 @@ 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"
|
||||
@ -22,8 +23,8 @@ import (
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOptions) error {
|
||||
configDetails, err := getConfigDetails(opts.composefile, dockerCli.In())
|
||||
func deployCompose(ctx context.Context, dockerCli command.Cli, opts options.Deploy) error {
|
||||
configDetails, err := getConfigDetails(opts.Composefile, dockerCli.In())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -54,9 +55,9 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOption
|
||||
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{}{}
|
||||
@ -93,7 +94,7 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOption
|
||||
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{} {
|
||||
@ -248,13 +249,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 {
|
||||
errors.Wrapf(err, "failed to update config %s", configSpec.Name)
|
||||
return 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 {
|
||||
errors.Wrapf(err, "failed to create config %s", configSpec.Name)
|
||||
return errors.Wrapf(err, "failed to create config %s", configSpec.Name)
|
||||
}
|
||||
default:
|
||||
return err
|
||||
@ -339,7 +340,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]:
|
||||
@ -369,7 +370,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
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
import (
|
||||
"os"
|
||||
@ -1,4 +1,4 @@
|
||||
package stack
|
||||
package swarm
|
||||
|
||||
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)
|
||||
74
components/cli/cli/command/stack/swarm/list.go
Normal file
74
components/cli/cli/command/stack/swarm/list.go
Normal file
@ -0,0 +1,74 @@
|
||||
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
|
||||
}
|
||||
37
components/cli/cli/command/stack/swarm/ps.go
Normal file
37
components/cli/cli/command/stack/swarm/ps.go
Normal file
@ -0,0 +1,37 @@
|
||||
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)
|
||||
}
|
||||
141
components/cli/cli/command/stack/swarm/remove.go
Normal file
141
components/cli/cli/command/stack/swarm/remove.go
Normal file
@ -0,0 +1,141 @@
|
||||
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
|
||||
}
|
||||
66
components/cli/cli/command/stack/swarm/services.go
Normal file
66
components/cli/cli/command/stack/swarm/services.go
Normal file
@ -0,0 +1,66 @@
|
||||
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)
|
||||
}
|
||||
@ -10,11 +10,14 @@ 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"},
|
||||
Use: "swarm",
|
||||
Short: "Manage Swarm",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{
|
||||
"version": "1.24",
|
||||
"swarm": "",
|
||||
},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newInitCommand(dockerCli),
|
||||
|
||||
@ -1,13 +1,20 @@
|
||||
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
|
||||
version string
|
||||
serverVersion func(ctx context.Context) (types.Version, error)
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ServerVersion(ctx context.Context) (types.Version, error) {
|
||||
return cli.serverVersion(ctx)
|
||||
}
|
||||
|
||||
func (cli *fakeClient) ClientVersion() string {
|
||||
|
||||
@ -23,6 +23,8 @@ 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}}
|
||||
@ -69,6 +71,8 @@ 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
|
||||
@ -78,7 +82,7 @@ func (v versionInfo) ServerOK() bool {
|
||||
}
|
||||
|
||||
// NewVersionCommand creates a new cobra.Command for `docker version`
|
||||
func NewVersionCommand(dockerCli *command.DockerCli) *cobra.Command {
|
||||
func NewVersionCommand(dockerCli command.Cli) *cobra.Command {
|
||||
var opts versionOptions
|
||||
|
||||
cmd := &cobra.Command{
|
||||
@ -105,9 +109,7 @@ func reformatDate(buildTime string) string {
|
||||
return buildTime
|
||||
}
|
||||
|
||||
func runVersion(dockerCli *command.DockerCli, opts *versionOptions) error {
|
||||
ctx := context.Background()
|
||||
|
||||
func runVersion(dockerCli command.Cli, opts *versionOptions) error {
|
||||
templateFormat := versionTemplate
|
||||
tmpl := templates.New("version")
|
||||
if opts.format != "" {
|
||||
@ -125,22 +127,21 @@ func runVersion(dockerCli *command.DockerCli, 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: cli.BuildTime,
|
||||
BuildTime: reformatDate(cli.BuildTime),
|
||||
Os: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
Experimental: dockerCli.ClientInfo().HasExperimental,
|
||||
Orchestrator: string(dockerCli.ClientInfo().Orchestrator),
|
||||
},
|
||||
}
|
||||
vd.Client.Platform.Name = cli.PlatformName
|
||||
|
||||
// first we need to make BuildTime more human friendly
|
||||
vd.Client.BuildTime = reformatDate(vd.Client.BuildTime)
|
||||
|
||||
sv, err := dockerCli.Client().ServerVersion(ctx)
|
||||
sv, err := dockerCli.Client().ServerVersion(context.Background())
|
||||
if err == nil {
|
||||
vd.Server = &sv
|
||||
foundEngine := false
|
||||
|
||||
47
components/cli/cli/command/system/version_test.go
Normal file
47
components/cli/cli/command/system/version_test.go
Normal file
@ -0,0 +1,47 @@
|
||||
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), " ")
|
||||
}
|
||||
@ -9,10 +9,11 @@ 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()),
|
||||
Use: "trust",
|
||||
Short: "Manage trust on Docker images (experimental)",
|
||||
Args: cli.NoArgs,
|
||||
RunE: command.ShowHelp(dockerCli.Err()),
|
||||
Annotations: map[string]string{"experimentalCLI": ""},
|
||||
}
|
||||
cmd.AddCommand(
|
||||
newViewCommand(dockerCli),
|
||||
|
||||
@ -657,6 +657,7 @@ services:
|
||||
context: ./web
|
||||
links:
|
||||
- bar
|
||||
pid: host
|
||||
db:
|
||||
image: db
|
||||
build:
|
||||
@ -670,7 +671,7 @@ services:
|
||||
require.NoError(t, err)
|
||||
|
||||
unsupported := GetUnsupportedProperties(configDetails)
|
||||
assert.Equal(t, []string{"build", "links"}, unsupported)
|
||||
assert.Equal(t, []string{"build", "links", "pid"}, unsupported)
|
||||
}
|
||||
|
||||
func TestBuildProperties(t *testing.T) {
|
||||
|
||||
@ -17,6 +17,7 @@ var UnsupportedProperties = []string{
|
||||
"links",
|
||||
"mac_address",
|
||||
"network_mode",
|
||||
"pid",
|
||||
"privileged",
|
||||
"restart",
|
||||
"security_opt",
|
||||
|
||||
@ -44,6 +44,8 @@ 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
|
||||
|
||||
@ -30,12 +30,13 @@ var (
|
||||
|
||||
// CommonOptions are options common to both the client and the daemon.
|
||||
type CommonOptions struct {
|
||||
Debug bool
|
||||
Hosts []string
|
||||
LogLevel string
|
||||
TLS bool
|
||||
TLSVerify bool
|
||||
TLSOptions *tlsconfig.Options
|
||||
Debug bool
|
||||
Hosts []string
|
||||
Orchestrator string
|
||||
LogLevel string
|
||||
TLS bool
|
||||
TLSVerify bool
|
||||
TLSOptions *tlsconfig.Options
|
||||
}
|
||||
|
||||
// NewCommonOptions returns a new CommonOptions
|
||||
@ -53,6 +54,8 @@ 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")
|
||||
|
||||
|
||||
@ -193,22 +193,40 @@ func dockerPreRun(opts *cliflags.ClientOptions) {
|
||||
|
||||
type versionDetails interface {
|
||||
Client() client.APIClient
|
||||
ClientInfo() command.ClientInfo
|
||||
ServerInfo() command.ServerInfo
|
||||
}
|
||||
|
||||
func hideFeatureFlag(f *pflag.Flag, hasFeature bool, annotation string) {
|
||||
if hasFeature {
|
||||
return
|
||||
}
|
||||
if _, ok := f.Annotations[annotation]; ok {
|
||||
f.Hidden = true
|
||||
}
|
||||
}
|
||||
|
||||
func hideFeatureSubCommand(subcmd *cobra.Command, hasFeature bool, annotation string) {
|
||||
if hasFeature {
|
||||
return
|
||||
}
|
||||
if _, ok := subcmd.Annotations[annotation]; ok {
|
||||
subcmd.Hidden = true
|
||||
}
|
||||
}
|
||||
|
||||
func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
||||
clientVersion := details.Client().ClientVersion()
|
||||
osType := details.ServerInfo().OSType
|
||||
hasExperimental := details.ServerInfo().HasExperimental
|
||||
hasExperimentalCLI := details.ClientInfo().HasExperimental
|
||||
hasKubernetes := details.ClientInfo().HasKubernetes()
|
||||
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
// hide experimental flags
|
||||
if !hasExperimental {
|
||||
if _, ok := f.Annotations["experimental"]; ok {
|
||||
f.Hidden = true
|
||||
}
|
||||
}
|
||||
|
||||
hideFeatureFlag(f, hasExperimental, "experimental")
|
||||
hideFeatureFlag(f, hasExperimentalCLI, "experimentalCLI")
|
||||
hideFeatureFlag(f, hasKubernetes, "kubernetes")
|
||||
hideFeatureFlag(f, !hasKubernetes, "swarm")
|
||||
// hide flags not supported by the server
|
||||
if !isOSTypeSupported(f, osType) || !isVersionSupported(f, clientVersion) {
|
||||
f.Hidden = true
|
||||
@ -216,13 +234,10 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
||||
})
|
||||
|
||||
for _, subcmd := range cmd.Commands() {
|
||||
// hide experimental subcommands
|
||||
if !hasExperimental {
|
||||
if _, ok := subcmd.Annotations["experimental"]; ok {
|
||||
subcmd.Hidden = true
|
||||
}
|
||||
}
|
||||
|
||||
hideFeatureSubCommand(subcmd, hasExperimental, "experimental")
|
||||
hideFeatureSubCommand(subcmd, hasExperimentalCLI, "experimentalCLI")
|
||||
hideFeatureSubCommand(subcmd, hasKubernetes, "kubernetes")
|
||||
hideFeatureSubCommand(subcmd, !hasKubernetes, "swarm")
|
||||
// hide subcommands not supported by the server
|
||||
if subcmdVersion, ok := subcmd.Annotations["version"]; ok && versions.LessThan(clientVersion, subcmdVersion) {
|
||||
subcmd.Hidden = true
|
||||
@ -231,19 +246,23 @@ func hideUnsupportedFeatures(cmd *cobra.Command, details versionDetails) {
|
||||
}
|
||||
|
||||
func isSupported(cmd *cobra.Command, details versionDetails) error {
|
||||
if err := areSubcommandsSupported(cmd, details); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := areFlagsSupported(cmd, details); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func areFlagsSupported(cmd *cobra.Command, details versionDetails) error {
|
||||
clientVersion := details.Client().ClientVersion()
|
||||
osType := details.ServerInfo().OSType
|
||||
hasExperimental := details.ServerInfo().HasExperimental
|
||||
|
||||
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
|
||||
for curr := cmd; curr != nil; curr = curr.Parent() {
|
||||
if cmdVersion, ok := curr.Annotations["version"]; ok && versions.LessThan(clientVersion, cmdVersion) {
|
||||
return fmt.Errorf("%s requires API version %s, but the Docker daemon API version is %s", cmd.CommandPath(), cmdVersion, clientVersion)
|
||||
}
|
||||
if _, ok := curr.Annotations["experimental"]; ok && !hasExperimental {
|
||||
return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath())
|
||||
}
|
||||
}
|
||||
hasKubernetes := details.ClientInfo().HasKubernetes()
|
||||
hasExperimentalCLI := details.ClientInfo().HasExperimental
|
||||
|
||||
errs := []string{}
|
||||
|
||||
@ -260,12 +279,48 @@ func isSupported(cmd *cobra.Command, details versionDetails) error {
|
||||
if _, ok := f.Annotations["experimental"]; ok && !hasExperimental {
|
||||
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker daemon with experimental features enabled", f.Name))
|
||||
}
|
||||
if _, ok := f.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
|
||||
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported when experimental cli features are enabled", f.Name))
|
||||
}
|
||||
if _, ok := f.Annotations["kubernetes"]; ok && !hasKubernetes {
|
||||
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker cli with kubernetes features enabled", f.Name))
|
||||
}
|
||||
if _, ok := f.Annotations["swarm"]; ok && hasKubernetes {
|
||||
errs = append(errs, fmt.Sprintf("\"--%s\" is only supported on a Docker cli with swarm features enabled", f.Name))
|
||||
}
|
||||
}
|
||||
})
|
||||
if len(errs) > 0 {
|
||||
return errors.New(strings.Join(errs, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
|
||||
func areSubcommandsSupported(cmd *cobra.Command, details versionDetails) error {
|
||||
clientVersion := details.Client().ClientVersion()
|
||||
hasExperimental := details.ServerInfo().HasExperimental
|
||||
hasExperimentalCLI := details.ClientInfo().HasExperimental
|
||||
hasKubernetes := details.ClientInfo().HasKubernetes()
|
||||
|
||||
// Check recursively so that, e.g., `docker stack ls` returns the same output as `docker stack`
|
||||
for curr := cmd; curr != nil; curr = curr.Parent() {
|
||||
if cmdVersion, ok := curr.Annotations["version"]; ok && versions.LessThan(clientVersion, cmdVersion) {
|
||||
return fmt.Errorf("%s requires API version %s, but the Docker daemon API version is %s", cmd.CommandPath(), cmdVersion, clientVersion)
|
||||
}
|
||||
if _, ok := curr.Annotations["experimental"]; ok && !hasExperimental {
|
||||
return fmt.Errorf("%s is only supported on a Docker daemon with experimental features enabled", cmd.CommandPath())
|
||||
}
|
||||
if _, ok := curr.Annotations["experimentalCLI"]; ok && !hasExperimentalCLI {
|
||||
return fmt.Errorf("%s is only supported when experimental cli features are enabled", cmd.CommandPath())
|
||||
}
|
||||
if _, ok := curr.Annotations["kubernetes"]; ok && !hasKubernetes {
|
||||
return fmt.Errorf("%s is only supported on a Docker cli with kubernetes features enabled", cmd.CommandPath())
|
||||
}
|
||||
if _, ok := curr.Annotations["swarm"]; ok && hasKubernetes {
|
||||
return fmt.Errorf("%s is only supported on a Docker cli with swarm features enabled", cmd.CommandPath())
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -17,3 +17,6 @@ ignore:
|
||||
- "**/internal/test"
|
||||
- "vendor/*"
|
||||
- "cli/compose/schema/bindata.go"
|
||||
- "cli/command/stack/kubernetes/api/openapi"
|
||||
- "cli/command/stack/kubernetes/api/client"
|
||||
- ".*generated.*"
|
||||
@ -2220,6 +2220,7 @@ _docker_daemon() {
|
||||
--metrics-addr
|
||||
--mtu
|
||||
--network-control-plane-mtu
|
||||
--node-generic-resource
|
||||
--oom-score-adjust
|
||||
--pidfile -p
|
||||
--registry-mirror
|
||||
@ -2865,6 +2866,7 @@ _docker_inspect() {
|
||||
$(__docker_services)
|
||||
$(__docker_volumes)
|
||||
" -- "$cur" ) )
|
||||
__ltrim_colon_completions "$cur"
|
||||
;;
|
||||
container)
|
||||
__docker_complete_containers_all
|
||||
@ -3395,6 +3397,7 @@ _docker_service_update_and_create() {
|
||||
--dns-option
|
||||
--dns-search
|
||||
--env-file
|
||||
--generic-resource
|
||||
--group
|
||||
--host
|
||||
--mode
|
||||
@ -3456,6 +3459,8 @@ _docker_service_update_and_create() {
|
||||
--dns-rm
|
||||
--dns-search-add
|
||||
--dns-search-rm
|
||||
--generic-resource-add
|
||||
--generic-resource-rm
|
||||
--group-add
|
||||
--group-rm
|
||||
--host-add
|
||||
@ -4940,6 +4945,7 @@ _docker() {
|
||||
secret
|
||||
service
|
||||
stack
|
||||
swarm
|
||||
system
|
||||
volume
|
||||
)
|
||||
@ -4983,7 +4989,6 @@ _docker() {
|
||||
start
|
||||
stats
|
||||
stop
|
||||
swarm
|
||||
tag
|
||||
top
|
||||
unpause
|
||||
|
||||
@ -745,7 +745,7 @@ __docker_container_subcommand() {
|
||||
"($help)--privileged[Give extended Linux capabilities to the command]" \
|
||||
"($help -t --tty)"{-t,--tty}"[Allocate a pseudo-tty]" \
|
||||
"($help -u --user)"{-u=,--user=}"[Username or UID]:user:_users" \
|
||||
"($help -w --workdir)"{-w=,--workdir=}"[Working directory inside the container]:directory:_directories"
|
||||
"($help -w --workdir)"{-w=,--workdir=}"[Working directory inside the container]:directory:_directories" \
|
||||
"($help -):containers:__docker_complete_running_containers" \
|
||||
"($help -)*::command:->anycommand" && ret=0
|
||||
case $state in
|
||||
|
||||
@ -88,6 +88,10 @@ vendor: build_docker_image vendor.conf
|
||||
dynbinary: build_cross_image
|
||||
docker run -ti --rm $(ENVVARS) $(MOUNTS) $(CROSS_IMAGE_NAME) make dynbinary
|
||||
|
||||
.PHONY: authors
|
||||
authors: ## generate AUTHORS file from git history
|
||||
docker run -ti --rm $(ENVVARS) $(MOUNTS) $(DEV_DOCKER_IMAGE_NAME) make authors
|
||||
|
||||
## generate man pages from go source and markdown
|
||||
.PHONY: manpages
|
||||
manpages: build_docker_image
|
||||
|
||||
@ -74,9 +74,13 @@ The `filter` param to filter the list of image by reference (name or name:tag) i
|
||||
### `repository:shortid` image references
|
||||
**Deprecated In Release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)**
|
||||
|
||||
**Target For Removal In Release: v17.12**
|
||||
**Removed In Release: v17.12**
|
||||
|
||||
`repository:shortid` syntax for referencing images is very little used, collides with tag references can be confused with digest references.
|
||||
The `repository:shortid` syntax for referencing images is very little used,
|
||||
collides with tag references, and can be confused with digest references.
|
||||
|
||||
Support for the `repository:shortid` notation to reference images was removed
|
||||
in Docker 17.12.
|
||||
|
||||
### `docker daemon` subcommand
|
||||
**Deprecated In Release: [v1.13.0](https://github.com/docker/docker/releases/tag/v1.13.0)**
|
||||
|
||||
@ -48,7 +48,7 @@ Total reclaimed space: 212 B
|
||||
|
||||
### Filtering
|
||||
|
||||
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
|
||||
The filtering flag (`--filter`) format is of "key=value". If there is more
|
||||
than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
|
||||
|
||||
The currently supported filters are:
|
||||
|
||||
@ -104,7 +104,6 @@ axqh55ipl40h vossibility_vossibility-collector replicated 1/1 icecrime/
|
||||
|
||||
## Related commands
|
||||
|
||||
* [stack config](stack_config.md)
|
||||
* [stack deploy](stack_deploy.md)
|
||||
* [stack ls](stack_ls.md)
|
||||
* [stack ps](stack_ps.md)
|
||||
|
||||
@ -70,7 +70,7 @@ Total reclaimed space: 16.43 MB
|
||||
|
||||
### Filtering
|
||||
|
||||
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
|
||||
The filtering flag (`--filter`) format is of "key=value". If there is more
|
||||
than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
|
||||
|
||||
The currently supported filters are:
|
||||
|
||||
@ -60,7 +60,7 @@ $ cat ~/my_password.txt | docker login --username foo --password-stdin
|
||||
`docker login` requires user to use `sudo` or be `root`, except when:
|
||||
|
||||
1. connecting to a remote daemon, such as a `docker-machine` provisioned `docker engine`.
|
||||
2. user is added to the `docker` group. This will impact the security of your system; the `docker` group is `root` equivalent. See [Docker Daemon Attack Surface](https://docs.docker.com/security/security/#docker-daemon-attack-surface) for details.
|
||||
2. user is added to the `docker` group. This will impact the security of your system; the `docker` group is `root` equivalent. See [Docker Daemon Attack Surface](https://docs.docker.com/engine/security/security/#docker-daemon-attack-surface) for details.
|
||||
|
||||
You can log into any public or private repository for which you have
|
||||
credentials. When you log in, the command stores credentials in
|
||||
|
||||
@ -36,7 +36,7 @@ n2
|
||||
|
||||
### Filtering
|
||||
|
||||
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
|
||||
The filtering flag (`--filter`) format is of "key=value". If there is more
|
||||
than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
|
||||
|
||||
The currently supported filters are:
|
||||
|
||||
@ -588,11 +588,11 @@ Use Docker's `--restart` to specify a container's *restart policy*. A restart
|
||||
policy controls whether the Docker daemon restarts a container after exit.
|
||||
Docker supports the following restart policies:
|
||||
|
||||
| Policy | Result |
|
||||
|:----------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `no` | Do not automatically restart the container when it exits. This is the default. |
|
||||
| `failure` | Restart only if the container exits with a non-zero exit status. Optionally, limit the number of restart retries the Docker daemon attempts. |
|
||||
| `always` | Always restart the container regardless of the exit status. When you specify always, the Docker daemon will try to restart the container indefinitely. The container will also always start on daemon startup, regardless of the current state of the container. |
|
||||
| Policy | Result |
|
||||
|:---------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `no` | Do not automatically restart the container when it exits. This is the default. |
|
||||
| `on-failure[:max-retries]` | Restart only if the container exits with a non-zero exit status. Optionally, limit the number of restart retries the Docker daemon attempts. |
|
||||
| `always` | Always restart the container regardless of the exit status. When you specify always, the Docker daemon will try to restart the container indefinitely. The container will also always start on daemon startup, regardless of the current state of the container. |
|
||||
|
||||
```bash
|
||||
$ docker run --restart=always redis
|
||||
|
||||
@ -46,7 +46,7 @@ Total reclaimed space: 36 B
|
||||
|
||||
## Filtering
|
||||
|
||||
The filtering flag (`-f` or `--filter`) format is of "key=value". If there is more
|
||||
The filtering flag (`--filter`) format is of "key=value". If there is more
|
||||
than one filter, then pass multiple flags (e.g., `--filter "foo=bar" --filter "bif=baz"`)
|
||||
|
||||
The currently supported filters are:
|
||||
|
||||
@ -1198,8 +1198,8 @@ The next table shows the capabilities which are not granted by default and may b
|
||||
| SYS_TIME | Set system clock (settimeofday(2), stime(2), adjtimex(2)); set real-time (hardware) clock. |
|
||||
| SYS_TTY_CONFIG | Use vhangup(2); employ various privileged ioctl(2) operations on virtual terminals. |
|
||||
| AUDIT_CONTROL | Enable and disable kernel auditing; change auditing filter rules; retrieve auditing status and filtering rules. |
|
||||
| MAC_OVERRIDE | Allow MAC configuration or state changes. Implemented for the Smack LSM. |
|
||||
| MAC_ADMIN | Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM). |
|
||||
| MAC_ADMIN | Allow MAC configuration or state changes. Implemented for the Smack LSM. |
|
||||
| MAC_OVERRIDE | Override Mandatory Access Control (MAC). Implemented for the Smack Linux Security Module (LSM). |
|
||||
| NET_ADMIN | Perform various network-related operations. |
|
||||
| SYSLOG | Perform privileged syslog(2) operations. |
|
||||
| DAC_READ_SEARCH | Bypass file read permission checks and directory read and execute permission checks. |
|
||||
|
||||
@ -15,14 +15,17 @@ import (
|
||||
)
|
||||
|
||||
type cmdOption struct {
|
||||
Option string
|
||||
Shorthand string `yaml:",omitempty"`
|
||||
ValueType string `yaml:"value_type,omitempty"`
|
||||
DefaultValue string `yaml:"default_value,omitempty"`
|
||||
Description string `yaml:",omitempty"`
|
||||
Deprecated bool
|
||||
MinAPIVersion string `yaml:"min_api_version,omitempty"`
|
||||
Experimental bool
|
||||
Option string
|
||||
Shorthand string `yaml:",omitempty"`
|
||||
ValueType string `yaml:"value_type,omitempty"`
|
||||
DefaultValue string `yaml:"default_value,omitempty"`
|
||||
Description string `yaml:",omitempty"`
|
||||
Deprecated bool
|
||||
MinAPIVersion string `yaml:"min_api_version,omitempty"`
|
||||
Experimental bool
|
||||
ExperimentalCLI bool
|
||||
Kubernetes bool
|
||||
Swarm bool
|
||||
}
|
||||
|
||||
type cmdDoc struct {
|
||||
@ -43,6 +46,9 @@ type cmdDoc struct {
|
||||
Deprecated bool
|
||||
MinAPIVersion string `yaml:"min_api_version,omitempty"`
|
||||
Experimental bool
|
||||
ExperimentalCLI bool
|
||||
Kubernetes bool
|
||||
Swarm bool
|
||||
}
|
||||
|
||||
// GenYamlTree creates yaml structured ref files
|
||||
@ -110,6 +116,15 @@ func GenYamlCustom(cmd *cobra.Command, w io.Writer) error {
|
||||
if _, ok := curr.Annotations["experimental"]; ok && !cliDoc.Experimental {
|
||||
cliDoc.Experimental = true
|
||||
}
|
||||
if _, ok := curr.Annotations["experimentalCLI"]; ok && !cliDoc.ExperimentalCLI {
|
||||
cliDoc.ExperimentalCLI = true
|
||||
}
|
||||
if _, ok := curr.Annotations["kubernetes"]; ok && !cliDoc.Kubernetes {
|
||||
cliDoc.Kubernetes = true
|
||||
}
|
||||
if _, ok := curr.Annotations["swarm"]; ok && !cliDoc.Swarm {
|
||||
cliDoc.Kubernetes = true
|
||||
}
|
||||
}
|
||||
|
||||
flags := cmd.NonInheritedFlags()
|
||||
@ -186,6 +201,15 @@ func genFlagResult(flags *pflag.FlagSet) []cmdOption {
|
||||
if v, ok := flag.Annotations["version"]; ok {
|
||||
opt.MinAPIVersion = v[0]
|
||||
}
|
||||
if _, ok := flag.Annotations["experimentalCLI"]; ok {
|
||||
opt.ExperimentalCLI = true
|
||||
}
|
||||
if _, ok := flag.Annotations["kubernetes"]; ok {
|
||||
opt.Kubernetes = true
|
||||
}
|
||||
if _, ok := flag.Annotations["swarm"]; ok {
|
||||
opt.Kubernetes = true
|
||||
}
|
||||
|
||||
result = append(result, opt)
|
||||
})
|
||||
|
||||
@ -32,7 +32,8 @@ func SetupConfigFile(t *testing.T) fs.Dir {
|
||||
"https://notary-server:4443": {
|
||||
"auth": "ZWlhaXM6cGFzc3dvcmQK"
|
||||
}
|
||||
}
|
||||
},
|
||||
"experimental": "enabled"
|
||||
}
|
||||
`))
|
||||
return *dir
|
||||
|
||||
@ -36,11 +36,21 @@ func deployFullStack(t *testing.T, stackname string) {
|
||||
}
|
||||
|
||||
func cleanupFullStack(t *testing.T, stackname string) {
|
||||
result := icmd.RunCmd(shell(t, "docker stack rm %s", stackname))
|
||||
result.Assert(t, icmd.Success)
|
||||
// FIXME(vdemeester) we shouldn't have to do that. it is hidding a race on docker stack rm
|
||||
poll.WaitOn(t, stackRm(stackname), pollSettings)
|
||||
poll.WaitOn(t, taskCount(stackname, 0), pollSettings)
|
||||
}
|
||||
|
||||
func stackRm(stackname string) func(t poll.LogT) poll.Result {
|
||||
return func(poll.LogT) poll.Result {
|
||||
result := icmd.RunCommand("docker", "stack", "rm", stackname)
|
||||
if result.Error != nil {
|
||||
return poll.Continue("docker stack rm %s failed : %v", stackname, result.Error)
|
||||
}
|
||||
return poll.Success()
|
||||
}
|
||||
}
|
||||
|
||||
func taskCount(stackname string, expected int) func(t poll.LogT) poll.Result {
|
||||
return func(poll.LogT) poll.Result {
|
||||
result := icmd.RunCommand(
|
||||
|
||||
@ -4,6 +4,9 @@
|
||||
"Sort": ["linter", "severity", "path", "line"],
|
||||
"Exclude": [
|
||||
"cli/compose/schema/bindata.go",
|
||||
"cli/command/stack/kubernetes/api/openapi",
|
||||
"cli/command/stack/kubernetes/api/client",
|
||||
".*generated.*",
|
||||
"parameter .* always receives"
|
||||
],
|
||||
"EnableGC": true,
|
||||
|
||||
@ -15,6 +15,7 @@ import (
|
||||
)
|
||||
|
||||
type notaryClientFuncType func(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error)
|
||||
type clientInfoFuncType func() command.ClientInfo
|
||||
|
||||
// FakeCli emulates the default DockerCli
|
||||
type FakeCli struct {
|
||||
@ -26,6 +27,7 @@ type FakeCli struct {
|
||||
err *bytes.Buffer
|
||||
in *command.InStream
|
||||
server command.ServerInfo
|
||||
clientInfoFunc clientInfoFuncType
|
||||
notaryClientFunc notaryClientFuncType
|
||||
}
|
||||
|
||||
@ -88,6 +90,19 @@ func (c *FakeCli) ServerInfo() command.ServerInfo {
|
||||
return c.server
|
||||
}
|
||||
|
||||
// ClientInfo returns client information
|
||||
func (c *FakeCli) ClientInfo() command.ClientInfo {
|
||||
if c.clientInfoFunc != nil {
|
||||
return c.clientInfoFunc()
|
||||
}
|
||||
return c.DockerCli.ClientInfo()
|
||||
}
|
||||
|
||||
// SetClientInfo sets the internal getter for retrieving a ClientInfo
|
||||
func (c *FakeCli) SetClientInfo(clientInfoFunc clientInfoFuncType) {
|
||||
c.clientInfoFunc = clientInfoFunc
|
||||
}
|
||||
|
||||
// OutBuffer returns the stdout buffer
|
||||
func (c *FakeCli) OutBuffer() *bytes.Buffer {
|
||||
return c.outBuffer
|
||||
|
||||
4
components/cli/kubernetes/README.md
Normal file
4
components/cli/kubernetes/README.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Kubernetes client libraries
|
||||
|
||||
This package (and sub-packages) holds the client libraries for the kubernetes integration in
|
||||
the docker platform. Most of the code is currently generated.
|
||||
@ -0,0 +1,88 @@
|
||||
package clientset
|
||||
|
||||
import (
|
||||
composev1beta1 "github.com/docker/cli/kubernetes/client/clientset_generated/clientset/typed/compose/v1beta1"
|
||||
glog "github.com/golang/glog"
|
||||
discovery "k8s.io/client-go/discovery"
|
||||
rest "k8s.io/client-go/rest"
|
||||
flowcontrol "k8s.io/client-go/util/flowcontrol"
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Discovery() discovery.DiscoveryInterface
|
||||
ComposeV1beta1() composev1beta1.ComposeV1beta1Interface
|
||||
// Deprecated: please explicitly pick a version if possible.
|
||||
Compose() composev1beta1.ComposeV1beta1Interface
|
||||
}
|
||||
|
||||
// Clientset contains the clients for groups. Each group has exactly one
|
||||
// version included in a Clientset.
|
||||
type Clientset struct {
|
||||
*discovery.DiscoveryClient
|
||||
*composev1beta1.ComposeV1beta1Client
|
||||
}
|
||||
|
||||
// ComposeV1beta1 retrieves the ComposeV1beta1Client
|
||||
func (c *Clientset) ComposeV1beta1() composev1beta1.ComposeV1beta1Interface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.ComposeV1beta1Client
|
||||
}
|
||||
|
||||
// Deprecated: Compose retrieves the default version of ComposeClient.
|
||||
// Please explicitly pick a version.
|
||||
func (c *Clientset) Compose() composev1beta1.ComposeV1beta1Interface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.ComposeV1beta1Client
|
||||
}
|
||||
|
||||
// Discovery retrieves the DiscoveryClient
|
||||
func (c *Clientset) Discovery() discovery.DiscoveryInterface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.DiscoveryClient
|
||||
}
|
||||
|
||||
// NewForConfig creates a new Clientset for the given config.
|
||||
func NewForConfig(c *rest.Config) (*Clientset, error) {
|
||||
configShallowCopy := *c
|
||||
if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 {
|
||||
configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst)
|
||||
}
|
||||
var cs Clientset
|
||||
var err error
|
||||
cs.ComposeV1beta1Client, err = composev1beta1.NewForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to create the DiscoveryClient: %v", err)
|
||||
return nil, err
|
||||
}
|
||||
return &cs, nil
|
||||
}
|
||||
|
||||
// NewForConfigOrDie creates a new Clientset for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewForConfigOrDie(c *rest.Config) *Clientset {
|
||||
var cs Clientset
|
||||
cs.ComposeV1beta1Client = composev1beta1.NewForConfigOrDie(c)
|
||||
|
||||
cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c)
|
||||
return &cs
|
||||
}
|
||||
|
||||
// New creates a new Clientset for the given RESTClient.
|
||||
func New(c rest.Interface) *Clientset {
|
||||
var cs Clientset
|
||||
cs.ComposeV1beta1Client = composev1beta1.New(c)
|
||||
|
||||
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
|
||||
return &cs
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
// This package is generated by client-gen with custom arguments.
|
||||
|
||||
// This package has the automatically generated clientset.
|
||||
package clientset
|
||||
@ -0,0 +1,4 @@
|
||||
// This package is generated by client-gen with custom arguments.
|
||||
|
||||
// This package contains the scheme of the automatically generated clientset.
|
||||
package scheme
|
||||
@ -0,0 +1,37 @@
|
||||
package scheme
|
||||
|
||||
import (
|
||||
composev1beta1 "github.com/docker/cli/kubernetes/compose/v1beta1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
)
|
||||
|
||||
var Scheme = runtime.NewScheme()
|
||||
var Codecs = serializer.NewCodecFactory(Scheme)
|
||||
var ParameterCodec = runtime.NewParameterCodec(Scheme)
|
||||
|
||||
func init() {
|
||||
v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"})
|
||||
AddToScheme(Scheme)
|
||||
}
|
||||
|
||||
// AddToScheme adds all types of this clientset into the given scheme. This allows composition
|
||||
// of clientsets, like in:
|
||||
//
|
||||
// import (
|
||||
// "k8s.io/client-go/kubernetes"
|
||||
// clientsetscheme "k8s.io/client-go/kuberentes/scheme"
|
||||
// aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme"
|
||||
// )
|
||||
//
|
||||
// kclientset, _ := kubernetes.NewForConfig(c)
|
||||
// aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme)
|
||||
//
|
||||
// After this, RawExtensions in Kubernetes types will serialize kube-aggregator types
|
||||
// correctly.
|
||||
func AddToScheme(scheme *runtime.Scheme) {
|
||||
composev1beta1.AddToScheme(scheme)
|
||||
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
"github.com/docker/cli/kubernetes/client/clientset_generated/clientset/scheme"
|
||||
v1beta1 "github.com/docker/cli/kubernetes/compose/v1beta1"
|
||||
serializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type ComposeV1beta1Interface interface {
|
||||
RESTClient() rest.Interface
|
||||
StacksGetter
|
||||
}
|
||||
|
||||
// ComposeV1beta1Client is used to interact with features provided by the compose.docker.com group.
|
||||
type ComposeV1beta1Client struct {
|
||||
restClient rest.Interface
|
||||
}
|
||||
|
||||
func (c *ComposeV1beta1Client) Stacks(namespace string) StackInterface {
|
||||
return newStacks(c, namespace)
|
||||
}
|
||||
|
||||
// NewForConfig creates a new ComposeV1beta1Client for the given config.
|
||||
func NewForConfig(c *rest.Config) (*ComposeV1beta1Client, error) {
|
||||
config := *c
|
||||
if err := setConfigDefaults(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := rest.RESTClientFor(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ComposeV1beta1Client{client}, nil
|
||||
}
|
||||
|
||||
// NewForConfigOrDie creates a new ComposeV1beta1Client for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewForConfigOrDie(c *rest.Config) *ComposeV1beta1Client {
|
||||
client, err := NewForConfig(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// New creates a new ComposeV1beta1Client for the given RESTClient.
|
||||
func New(c rest.Interface) *ComposeV1beta1Client {
|
||||
return &ComposeV1beta1Client{c}
|
||||
}
|
||||
|
||||
func setConfigDefaults(config *rest.Config) error {
|
||||
gv := v1beta1.SchemeGroupVersion
|
||||
config.GroupVersion = &gv
|
||||
config.APIPath = "/apis"
|
||||
config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
|
||||
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *ComposeV1beta1Client) RESTClient() rest.Interface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.restClient
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
// This package is generated by client-gen with custom arguments.
|
||||
|
||||
// This package has the automatically generated typed clients.
|
||||
package v1beta1
|
||||
@ -0,0 +1,3 @@
|
||||
package v1beta1
|
||||
|
||||
type StackExpansion interface{}
|
||||
@ -0,0 +1,158 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
scheme "github.com/docker/cli/kubernetes/client/clientset_generated/clientset/scheme"
|
||||
v1beta1 "github.com/docker/cli/kubernetes/compose/v1beta1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// StacksGetter has a method to return a StackInterface.
|
||||
// A group's client should implement this interface.
|
||||
type StacksGetter interface {
|
||||
Stacks(namespace string) StackInterface
|
||||
}
|
||||
|
||||
// StackInterface has methods to work with Stack resources.
|
||||
type StackInterface interface {
|
||||
Create(*v1beta1.Stack) (*v1beta1.Stack, error)
|
||||
Update(*v1beta1.Stack) (*v1beta1.Stack, error)
|
||||
UpdateStatus(*v1beta1.Stack) (*v1beta1.Stack, error)
|
||||
Delete(name string, options *v1.DeleteOptions) error
|
||||
DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error
|
||||
Get(name string, options v1.GetOptions) (*v1beta1.Stack, error)
|
||||
List(opts v1.ListOptions) (*v1beta1.StackList, error)
|
||||
Watch(opts v1.ListOptions) (watch.Interface, error)
|
||||
Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Stack, err error)
|
||||
StackExpansion
|
||||
}
|
||||
|
||||
var _ StackInterface = &stacks{}
|
||||
|
||||
// stacks implements StackInterface
|
||||
type stacks struct {
|
||||
client rest.Interface
|
||||
ns string
|
||||
}
|
||||
|
||||
// newStacks returns a Stacks
|
||||
func newStacks(c *ComposeV1beta1Client, namespace string) *stacks {
|
||||
return &stacks{
|
||||
client: c.RESTClient(),
|
||||
ns: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Create takes the representation of a stack and creates it. Returns the server's representation of the stack, and an error, if there is any.
|
||||
func (c *stacks) Create(stack *v1beta1.Stack) (result *v1beta1.Stack, err error) {
|
||||
result = &v1beta1.Stack{}
|
||||
err = c.client.Post().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
Body(stack).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Update takes the representation of a stack and updates it. Returns the server's representation of the stack, and an error, if there is any.
|
||||
func (c *stacks) Update(stack *v1beta1.Stack) (result *v1beta1.Stack, err error) {
|
||||
result = &v1beta1.Stack{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
Name(stack.Name).
|
||||
Body(stack).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// UpdateStatus was generated because the type contains a Status member.
|
||||
// Add a +genclientstatus=false comment above the type to avoid generating UpdateStatus().
|
||||
|
||||
func (c *stacks) UpdateStatus(stack *v1beta1.Stack) (result *v1beta1.Stack, err error) {
|
||||
result = &v1beta1.Stack{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
Name(stack.Name).
|
||||
SubResource("status").
|
||||
Body(stack).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the stack and deletes it. Returns an error if one occurs.
|
||||
func (c *stacks) Delete(name string, options *v1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
Name(name).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *stacks) DeleteCollection(options *v1.DeleteOptions, listOptions v1.ListOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
VersionedParams(&listOptions, scheme.ParameterCodec).
|
||||
Body(options).
|
||||
Do().
|
||||
Error()
|
||||
}
|
||||
|
||||
// Get takes name of the stack, and returns the corresponding stack object, and an error if there is any.
|
||||
func (c *stacks) Get(name string, options v1.GetOptions) (result *v1beta1.Stack, err error) {
|
||||
result = &v1beta1.Stack{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
Name(name).
|
||||
VersionedParams(&options, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of Stacks that match those selectors.
|
||||
func (c *stacks) List(opts v1.ListOptions) (result *v1beta1.StackList, err error) {
|
||||
result = &v1beta1.StackList{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested stacks.
|
||||
func (c *stacks) Watch(opts v1.ListOptions) (watch.Interface, error) {
|
||||
opts.Watch = true
|
||||
return c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Watch()
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched stack.
|
||||
func (c *stacks) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (result *v1beta1.Stack, err error) {
|
||||
result = &v1beta1.Stack{}
|
||||
err = c.client.Patch(pt).
|
||||
Namespace(c.ns).
|
||||
Resource("stacks").
|
||||
SubResource(subresources...).
|
||||
Name(name).
|
||||
Body(data).
|
||||
Do().
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
5
components/cli/kubernetes/compose/doc.go
Normal file
5
components/cli/kubernetes/compose/doc.go
Normal file
@ -0,0 +1,5 @@
|
||||
// +k8s:deepcopy-gen=package,register
|
||||
// +groupName=compose.docker.com
|
||||
|
||||
// Package compose is the internal version of the API.
|
||||
package compose
|
||||
43
components/cli/kubernetes/compose/register.go
Normal file
43
components/cli/kubernetes/compose/register.go
Normal file
@ -0,0 +1,43 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name used to register these objects
|
||||
const GroupName = "compose.docker.com"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal}
|
||||
|
||||
// Kind takes an unqualified kind and returns back a Group qualified GroupKind
|
||||
func Kind(kind string) schema.GroupKind {
|
||||
return SchemeGroupVersion.WithKind(kind).GroupKind()
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns back a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
|
||||
var (
|
||||
// SchemeBuilder collects functions that add things to a scheme. It's to allow
|
||||
// code to compile without explicitly referencing generated types. You should
|
||||
// declare one in each package that will have generated deep copy or conversion
|
||||
// functions.
|
||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
||||
|
||||
// AddToScheme applies all the stored functions to the scheme. A non-nil error
|
||||
// indicates that one function failed and the attempt was abandoned.
|
||||
AddToScheme = SchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// adds the list of known types to api.Scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Stack{},
|
||||
&StackList{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
117
components/cli/kubernetes/compose/types.go
Normal file
117
components/cli/kubernetes/compose/types.go
Normal file
@ -0,0 +1,117 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ImpersonationConfig holds information use to impersonate calls from the compose controller
|
||||
type ImpersonationConfig struct {
|
||||
// UserName is the username to impersonate on each request.
|
||||
UserName string
|
||||
// Groups are the groups to impersonate on each request.
|
||||
Groups []string
|
||||
// Extra is a free-form field which can be used to link some authentication information
|
||||
// to authorization information. This field allows you to impersonate it.
|
||||
Extra map[string][]string
|
||||
}
|
||||
|
||||
// Stack defines a stack object to be register in the kubernetes API
|
||||
// +genclient=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type Stack struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ObjectMeta
|
||||
Spec StackSpec
|
||||
Status StackStatus
|
||||
}
|
||||
|
||||
// StackStatus defines the observed state of Stack
|
||||
type StackStatus struct {
|
||||
Phase StackPhase
|
||||
Message string
|
||||
}
|
||||
|
||||
// StackSpec defines the desired state of Stack
|
||||
type StackSpec struct {
|
||||
ComposeFile string
|
||||
Owner ImpersonationConfig
|
||||
}
|
||||
|
||||
// StackPhase defines the status phase in which the stack is.
|
||||
type StackPhase string
|
||||
|
||||
// These are valid conditions of a stack.
|
||||
const (
|
||||
// Available means the stack is available.
|
||||
StackAvailable StackPhase = "Available"
|
||||
// Progressing means the deployment is progressing.
|
||||
StackProgressing StackPhase = "Progressing"
|
||||
// StackFailure is added in a stack when one of its members fails to be created
|
||||
// or deleted.
|
||||
StackFailure StackPhase = "Failure"
|
||||
)
|
||||
|
||||
// StackList defines a list of stacks
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type StackList struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ListMeta
|
||||
Items []Stack
|
||||
}
|
||||
|
||||
// Owner defines the owner of a stack. It is used to impersonate the controller calls
|
||||
// to kubernetes api.
|
||||
type Owner struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ObjectMeta
|
||||
Owner ImpersonationConfig
|
||||
}
|
||||
|
||||
// OwnerList defines a list of owner.
|
||||
type OwnerList struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ListMeta
|
||||
Items []Owner
|
||||
}
|
||||
|
||||
// FIXME(vdemeester) are those necessary ??
|
||||
|
||||
// NewStatus is newStatus
|
||||
func (Stack) NewStatus() interface{} {
|
||||
return StackStatus{}
|
||||
}
|
||||
|
||||
// GetStatus returns the status
|
||||
func (pc *Stack) GetStatus() interface{} {
|
||||
return pc.Status
|
||||
}
|
||||
|
||||
// SetStatus sets the status
|
||||
func (pc *Stack) SetStatus(s interface{}) {
|
||||
pc.Status = s.(StackStatus)
|
||||
}
|
||||
|
||||
// GetSpec returns the spec
|
||||
func (pc *Stack) GetSpec() interface{} {
|
||||
return pc.Spec
|
||||
}
|
||||
|
||||
// SetSpec sets the spec
|
||||
func (pc *Stack) SetSpec(s interface{}) {
|
||||
pc.Spec = s.(StackSpec)
|
||||
}
|
||||
|
||||
// GetObjectMeta returns the ObjectMeta
|
||||
func (pc *Stack) GetObjectMeta() *metav1.ObjectMeta {
|
||||
return &pc.ObjectMeta
|
||||
}
|
||||
|
||||
// SetGeneration sets the Generation
|
||||
func (pc *Stack) SetGeneration(generation int64) {
|
||||
pc.ObjectMeta.Generation = generation
|
||||
}
|
||||
|
||||
// GetGeneration returns the Generation
|
||||
func (pc Stack) GetGeneration() int64 {
|
||||
return pc.ObjectMeta.Generation
|
||||
}
|
||||
11
components/cli/kubernetes/compose/v1beta1/doc.go
Normal file
11
components/cli/kubernetes/compose/v1beta1/doc.go
Normal file
@ -0,0 +1,11 @@
|
||||
// Package v1beta1 holds the v1beta1 versions of our stack structures.
|
||||
// API versions allow the api contract for a resource to be changed while keeping
|
||||
// backward compatibility by support multiple concurrent versions
|
||||
// of the same resource
|
||||
//
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:deepcopy-gen=package,register
|
||||
// +k8s:conversion-gen=github.com/docker/cli/kubernetes/compose
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +groupName=compose.docker.com
|
||||
package v1beta1 // import "github.com/docker/cli/kubernetes/compose/v1beta1"
|
||||
@ -0,0 +1,25 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/docker/cli/kubernetes/compose"
|
||||
)
|
||||
|
||||
// Owner defines the owner of a stack. It is used to impersonate the controller calls
|
||||
// to kubernetes api.
|
||||
// +genclient=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +subresource-request
|
||||
type Owner struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
Owner compose.ImpersonationConfig `json:"owner,omitempty"`
|
||||
}
|
||||
|
||||
// OwnerList defines a list of owner.
|
||||
type OwnerList struct {
|
||||
metav1.TypeMeta
|
||||
metav1.ListMeta
|
||||
Items []Owner
|
||||
}
|
||||
47
components/cli/kubernetes/compose/v1beta1/register.go
Normal file
47
components/cli/kubernetes/compose/v1beta1/register.go
Normal file
@ -0,0 +1,47 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name used to register these objects
|
||||
const GroupName = "compose.docker.com"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"}
|
||||
|
||||
var (
|
||||
// SchemeBuilder collects functions that add things to a scheme. It's to allow
|
||||
// code to compile without explicitly referencing generated types. You should
|
||||
// declare one in each package that will have generated deep copy or conversion
|
||||
// functions.
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
|
||||
// AddToScheme applies all the stored functions to the scheme. A non-nil error
|
||||
// indicates that one function failed and the attempt was abandoned.
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(addKnownTypes)
|
||||
}
|
||||
|
||||
// Adds the list of known types to api.Scheme.
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Stack{},
|
||||
&StackList{},
|
||||
&Owner{},
|
||||
&OwnerList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
||||
68
components/cli/kubernetes/compose/v1beta1/stack_types.go
Normal file
68
components/cli/kubernetes/compose/v1beta1/stack_types.go
Normal file
@ -0,0 +1,68 @@
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// StackList defines a list of stacks
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type StackList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
|
||||
|
||||
Items []Stack `json:"items" protobuf:"bytes,2,rep,name=items"`
|
||||
}
|
||||
|
||||
// +genclient=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Stack defines a stack object to be register in the kubernetes API
|
||||
// +k8s:openapi-gen=true
|
||||
// +resource:path=stacks,strategy=StackStrategy
|
||||
// +subresource:request=Owner,path=owner,rest=OwnerStackREST
|
||||
type Stack struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec StackSpec `json:"spec,omitempty"`
|
||||
Status StackStatus `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// StackSpec defines the desired state of Stack
|
||||
type StackSpec struct {
|
||||
ComposeFile string `json:"composeFile,omitempty"`
|
||||
}
|
||||
|
||||
// StackPhase defines the status phase in which the stack is.
|
||||
type StackPhase string
|
||||
|
||||
// These are valid conditions of a stack.
|
||||
const (
|
||||
// Available means the stack is available.
|
||||
StackAvailable StackPhase = "Available"
|
||||
// Progressing means the deployment is progressing.
|
||||
StackProgressing StackPhase = "Progressing"
|
||||
// StackFailure is added in a stack when one of its members fails to be created
|
||||
// or deleted.
|
||||
StackFailure StackPhase = "Failure"
|
||||
)
|
||||
|
||||
// StackStatus defines the observed state of Stack
|
||||
type StackStatus struct {
|
||||
// Current condition of the stack.
|
||||
// +optional
|
||||
Phase StackPhase `json:"phase,omitempty" protobuf:"bytes,1,opt,name=phase,casttype=StackPhase"`
|
||||
// A human readable message indicating details about the stack.
|
||||
// +optional
|
||||
Message string `json:"message,omitempty" protobuf:"bytes,5,opt,name=message"`
|
||||
}
|
||||
|
||||
// Clone implements the Cloner interface for kubernetes
|
||||
func (s *Stack) Clone() (*Stack, error) {
|
||||
scheme := runtime.NewScheme()
|
||||
if err := AddToScheme(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.DeepCopy(), nil
|
||||
}
|
||||
9
components/cli/kubernetes/compose/v1beta1/testdata/redis-nginx.input.yaml
vendored
Normal file
9
components/cli/kubernetes/compose/v1beta1/testdata/redis-nginx.input.yaml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
nginx:
|
||||
image: nginx
|
||||
deploy:
|
||||
replicas: 2
|
||||
|
||||
redis:
|
||||
image: redis:alpine
|
||||
10
components/cli/kubernetes/compose/v1beta1/testdata/redis-nginx.output.yaml
vendored
Normal file
10
components/cli/kubernetes/compose/v1beta1/testdata/redis-nginx.output.yaml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
nginx:
|
||||
image: nginx
|
||||
deploy:
|
||||
replicas: 2
|
||||
redis:
|
||||
image: redis:alpine
|
||||
deploy:
|
||||
replicas: 5
|
||||
10
components/cli/kubernetes/compose/v1beta1/testdata/redis-with-memory.input.yaml
vendored
Normal file
10
components/cli/kubernetes/compose/v1beta1/testdata/redis-with-memory.input.yaml
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 64M
|
||||
reservations:
|
||||
memory: 64M
|
||||
11
components/cli/kubernetes/compose/v1beta1/testdata/redis-with-memory.output.yaml
vendored
Normal file
11
components/cli/kubernetes/compose/v1beta1/testdata/redis-with-memory.output.yaml
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 64M
|
||||
reservations:
|
||||
memory: 64M
|
||||
replicas: 5
|
||||
6
components/cli/kubernetes/compose/v1beta1/testdata/redis-with-replicas.input.yaml
vendored
Normal file
6
components/cli/kubernetes/compose/v1beta1/testdata/redis-with-replicas.input.yaml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
deploy:
|
||||
replicas: 2
|
||||
6
components/cli/kubernetes/compose/v1beta1/testdata/redis-with-replicas.output.yaml
vendored
Normal file
6
components/cli/kubernetes/compose/v1beta1/testdata/redis-with-replicas.output.yaml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
deploy:
|
||||
replicas: 5
|
||||
4
components/cli/kubernetes/compose/v1beta1/testdata/single-redis.input.yaml
vendored
Normal file
4
components/cli/kubernetes/compose/v1beta1/testdata/single-redis.input.yaml
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
6
components/cli/kubernetes/compose/v1beta1/testdata/single-redis.output.yaml
vendored
Normal file
6
components/cli/kubernetes/compose/v1beta1/testdata/single-redis.output.yaml
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
version: "3.2"
|
||||
services:
|
||||
redis:
|
||||
image: redis:alpine
|
||||
deploy:
|
||||
replicas: 5
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user